Preserve jangbu attribution in top-level subskills

The wrapper advertised upstream /jangbu-* routing for both Claude and agent-compatible installs, but only nested the pinned upstream checkout. The installer now registers the upstream subskills at top level in both home skill roots while appending the wrapper attribution and accounting disclaimer policy to direct subskill use.

Constraint: PR review required top-level discovery for ~/.claude/skills and ~/.agents/skills

Constraint: korean-jangbu-for responses must retain original link, @kimlawtech (SpeciAI), Apache-2.0, and accounting/tax disclaimer

Rejected: Copy raw upstream subskills unchanged | direct /jangbu-* use would bypass mandatory wrapper response policy

Confidence: high

Scope-risk: narrow

Tested: node --test scripts/skill-docs.test.js --test-name-pattern='korean-jangbu-for'

Tested: bash korean-jangbu-for/scripts/install.sh plus top-level Claude/agents subskill marker checks

Tested: upstream runtime install and verify with Python 3.11 shim

Tested: npm run ci
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-04-29 00:29:04 +09:00
commit 5a4ff0759f
5 changed files with 158 additions and 15 deletions

View file

@ -30,7 +30,7 @@
## 설치 흐름
`~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 pinned SHA 로 업스트림을 설치한다.
`~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 pinned SHA 로 업스트림을 설치한다. 동시에 업스트림 `skills/jangbu-*``~/.claude/skills/<skill-name>``~/.agents/skills/<skill-name>` top-level 경로로 등록해 slash skill discovery 가 중첩 `upstream/` 탐색에 의존하지 않게 한다. `/korean-jangbu-for` 는 wrapper top-level skill 로 유지한다.
```bash
bash korean-jangbu-for/scripts/install.sh
@ -44,7 +44,7 @@ git -C ~/.claude/skills/korean-jangbu-for/upstream rev-parse HEAD
git -C ~/.agents/skills/korean-jangbu-for/upstream rev-parse HEAD
```
세 SHA 가 모두 같면 wrapper 설치가 성공한 것이다. 실제 OCR/MCP 런타임까지 검증해야 할 때는 업스트림 설치 후 verify 를 실행한다.
세 SHA 가 모두 같`~/.agents/skills/jangbu-import/SKILL.md` 같은 top-level 하위 스킬이 존재하면 wrapper 설치가 성공한 것이다. 실제 OCR/MCP 런타임까지 검증해야 할 때는 업스트림 설치 후 verify 를 실행한다.
```bash
bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/install.sh

View file

@ -134,7 +134,7 @@ korean-law list
`korean-scholarship-search` 는 스킬 이름 `장학금 검색 및 조회` 로 동작한다. 별도 API key 없이 최신 웹 검색과 공식 공고 확인으로 장학금을 찾고, 한국장학재단·전국 대학교 본부·단과대·학과·재단·기업·공공기관 공고를 모아 금액/지원자격/지원구간/공식 링크를 정리한다. 설치된 helper `python3 scripts/scholarship_filter.py` 로 사용자 조건 필터링, KST(`Asia/Seoul`) 현재 날짜 기준 마감 상태 분류, readable report, 지원 가능 여부 확인을 할 수 있고, `python3 scripts/university_search_plan.py` 로 학교별 또는 전국 대학 검색 쿼리 팩을 만들 수 있다. 자세한 사용법은 [장학금 검색 및 조회 가이드](features/korean-scholarship-search.md)를 본다.
`korean-jangbu-for``kimlawtech/korean-jangbu-for` (Apache-2.0, 원저작자 @kimlawtech / SpeciAI) 업스트림을 중심으로 사용하는 thin wrapper 다. 별도 hosted proxy 없이 `bash korean-jangbu-for/scripts/install.sh` 로 pinned upstream 을 `~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 설치한다. CODEF 자동 수집은 사용자가 직접 발급한 키를 쓰는 BYOK 방식이며, 장부·재무제표·세무사 전달 CSV 는 참고용 초안이므로 신고 전 세무사 검토 및 외감 대상 공인회계사 감사가 필요하다. 자세한 사용법은 [한국 사업자 장부 자동화 가이드](features/korean-jangbu-for.md)를 본다.
`korean-jangbu-for``kimlawtech/korean-jangbu-for` (Apache-2.0, 원저작자 @kimlawtech / SpeciAI) 업스트림을 중심으로 사용하는 thin wrapper 다. 별도 hosted proxy 없이 `bash korean-jangbu-for/scripts/install.sh` 로 pinned upstream 을 `~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 설치하고, 업스트림 `jangbu-*` 하위 스킬을 양쪽 홈 디렉터리의 top-level skill 로 함께 등록한다. CODEF 자동 수집은 사용자가 직접 발급한 키를 쓰는 BYOK 방식이며, 장부·재무제표·세무사 전달 CSV 는 참고용 초안이므로 신고 전 세무사 검토 및 외감 대상 공인회계사 감사가 필요하다. 자세한 사용법은 [한국 사업자 장부 자동화 가이드](features/korean-jangbu-for.md)를 본다.
`korean-stock-search` 는 별도 설치 없이 기본 hosted proxy(`k-skill-proxy.nomadamas.org`)를 통해 바로 사용할 수 있다. 사용자 쪽 `KRX_API_KEY` 가 불필요하다. 원본 참고: `https://github.com/jjlabsio/korea-stock-mcp`. 자세한 사용법은 [한국 주식 정보 조회 가이드](features/korean-stock-search.md)를 본다.

View file

@ -46,7 +46,7 @@ metadata:
## Install (dual-install)
업스트림을 `~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 pinned SHA 로 체크아웃한다. 레포 내부에는 업스트림 payload 를 커밋하지 않는다.
업스트림을 `~/.claude/skills/korean-jangbu-for/upstream/``~/.agents/skills/korean-jangbu-for/upstream/` 양쪽에 pinned SHA 로 체크아웃한다. 또한 업스트림 `skills/jangbu-*` 를 양쪽 홈 디렉터리의 top-level skill 로 등록해 `/jangbu-connect`, `/jangbu-import`, `/jangbu-tag`, `/jangbu-tax`, `/jangbu-dash`, `/jangbu-jongso` 라우팅이 agent-compatible 런타임에서도 발견되게 한다. `/korean-jangbu-for` 는 이 wrapper 의 top-level skill 로 유지한다. 레포 내부에는 업스트림 payload 를 커밋하지 않는다.
```bash
bash korean-jangbu-for/scripts/install.sh
@ -119,6 +119,7 @@ bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/verify.sh
- 목표 산출물, 입력 자료, 사업자 유형, 기간, 민감정보 처리 수준이 확인되었다.
- `scripts/install.sh` 이 업스트림을 dual-install 했고 양쪽 `upstream/` 경로가 `scripts/upstream.pin` 과 같은 SHA 를 가리킨다.
- 업스트림 `skills/jangbu-*``~/.claude/skills/<skill-name>``~/.agents/skills/<skill-name>` top-level 경로에 등록되어 slash skill discovery 가 가능하다.
- 업스트림의 해당 하위 스킬(`/jangbu-import`, `/jangbu-tag`, `/jangbu-tax`, `/jangbu-dash`, `/jangbu-jongso`, `/jangbu-connect`) 흐름을 사용했다.
- 사용자가 공식 제출/신고 목적이라고 밝힌 경우 세무사 검토 또는 공인회계사 감사 필요성을 명시했다.
- 최종 답변에 원본 링크, 원저작자 @kimlawtech (SpeciAI), Apache-2.0, 회계·세무 면책 고지가 포함되었다.

View file

@ -15,7 +15,7 @@
set -euo pipefail
UPSTREAM_REPO="https://github.com/kimlawtech/korean-jangbu-for.git"
UPSTREAM_REPO="${KOREAN_JANGBU_FOR_UPSTREAM_REPO:-https://github.com/kimlawtech/korean-jangbu-for.git}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PIN_FILE="${SCRIPT_DIR}/upstream.pin"
SKILL_NAME="korean-jangbu-for"
@ -25,7 +25,7 @@ if [[ ! -f "${PIN_FILE}" ]]; then
exit 1
fi
UPSTREAM_SHA="$(tr -d '[:space:]' <"${PIN_FILE}")"
UPSTREAM_SHA="${KOREAN_JANGBU_FOR_UPSTREAM_SHA:-$(tr -d '[:space:]' <"${PIN_FILE}")}"
if [[ ! "${UPSTREAM_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
echo "[korean-jangbu-for] upstream.pin must contain a 40-char git SHA (got: ${UPSTREAM_SHA})" >&2
@ -34,6 +34,46 @@ fi
CACHE_DIR="${HOME}/.cache/k-skill/${SKILL_NAME}"
CLONE_DIR="${CACHE_DIR}/upstream"
UPSTREAM_SUBSKILLS=(
"jangbu-connect"
"jangbu-dash"
"jangbu-import"
"jangbu-jongso"
"jangbu-tag"
"jangbu-tax"
)
sync_dir() {
local source_dir="$1"
local target_dir="$2"
if [[ -e "${target_dir}" || -L "${target_dir}" ]]; then
rm -rf "${target_dir}"
fi
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete "${source_dir}/" "${target_dir}/"
else
cp -a "${source_dir}/" "${target_dir}/"
fi
}
append_response_policy() {
local skill_file="$1"
cat >>"${skill_file}" <<'POLICY'
## k-skill wrapper attribution and disclaimer
This top-level skill is registered by the k-skill `korean-jangbu-for` thin wrapper so agent-compatible runtimes can discover the upstream `jangbu-*` workflows. Preserve the upstream workflow above, but every response that uses this skill must include:
- 원본: https://github.com/kimlawtech/korean-jangbu-for
- 원저작자: **@kimlawtech (SpeciAI)**
- 라이선스: Apache-2.0 (`korean-jangbu-for/LICENSE.upstream`)
- 생성된 장부·재무제표·경영 리포트·세무사 전달 CSV 는 **참고용 초안**이며, **공식 회계감사** 또는 **세무신고** 를 대체하지 않는다.
- 법인세 신고 전에는 **세무사 검토** 가 필요하고, 외감 대상 법인은 **공인회계사 감사** 가 필요하다.
POLICY
}
mkdir -p "${CACHE_DIR}"
@ -66,17 +106,14 @@ HOME_DIRS=(
for HOME_SKILL_DIR in "${HOME_DIRS[@]}"; do
HOME_UPSTREAM="${HOME_SKILL_DIR}/upstream"
HOME_SKILLS_ROOT="$(dirname "${HOME_SKILL_DIR}")"
if [[ -L "${HOME_SKILL_DIR}" ]]; then
rm -f "${HOME_SKILL_DIR}"
fi
mkdir -p "${HOME_SKILL_DIR}"
cp "${SCRIPT_DIR}/../SKILL.md" "${HOME_SKILL_DIR}/SKILL.md"
if [[ -e "${HOME_UPSTREAM}" || -L "${HOME_UPSTREAM}" ]]; then
rm -rf "${HOME_UPSTREAM}"
fi
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete "${CLONE_DIR}/" "${HOME_UPSTREAM}/"
else
cp -a "${CLONE_DIR}/" "${HOME_UPSTREAM}/"
fi
sync_dir "${CLONE_DIR}" "${HOME_UPSTREAM}"
INSTALLED_SHA="$(git -C "${HOME_UPSTREAM}" rev-parse HEAD)"
@ -86,6 +123,21 @@ for HOME_SKILL_DIR in "${HOME_DIRS[@]}"; do
fi
echo "[korean-jangbu-for] installed upstream@${UPSTREAM_SHA} -> ${HOME_UPSTREAM}"
for UPSTREAM_SKILL in "${UPSTREAM_SUBSKILLS[@]}"; do
UPSTREAM_SKILL_DIR="${CLONE_DIR}/skills/${UPSTREAM_SKILL}"
HOME_UPSTREAM_SKILL_DIR="${HOME_SKILLS_ROOT}/${UPSTREAM_SKILL}"
if [[ ! -f "${UPSTREAM_SKILL_DIR}/SKILL.md" ]]; then
echo "[korean-jangbu-for] missing upstream skill: ${UPSTREAM_SKILL_DIR}/SKILL.md" >&2
exit 1
fi
sync_dir "${UPSTREAM_SKILL_DIR}" "${HOME_UPSTREAM_SKILL_DIR}"
append_response_policy "${HOME_UPSTREAM_SKILL_DIR}/SKILL.md"
echo "[korean-jangbu-for] registered upstream skill /${UPSTREAM_SKILL} -> ${HOME_UPSTREAM_SKILL_DIR}"
done
done
echo ""
@ -94,6 +146,7 @@ echo " pinned upstream SHA: ${UPSTREAM_SHA}"
echo " upstream repo: ${UPSTREAM_REPO}"
echo " runtime install: bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/install.sh"
echo " verify command: bash ~/.claude/skills/korean-jangbu-for/upstream/scripts/verify.sh"
echo " subskills: /korean-jangbu-for /jangbu-connect /jangbu-import /jangbu-tag /jangbu-tax /jangbu-dash /jangbu-jongso"
echo " 원저작자: @kimlawtech (SpeciAI) — 응답마다 원본 링크와 함께 언급해야 한다."
echo " 생성물은 참고용 초안이며 공식 회계감사·세무신고를 대체하지 않는다."
echo " 법인세 신고 전 세무사 검토, 외감 대상은 공인회계사 감사가 필요하다."

View file

@ -3000,6 +3000,95 @@ test("korean-jangbu-for ships an install.sh wrapper and a pinned upstream SHA",
assert.ok((stat.mode & 0o111) !== 0, "install.sh must be executable");
});
test("korean-jangbu-for installer registers upstream subskills for Claude and agents", () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "korean-jangbu-for-install-"));
const homeDir = path.join(tmpDir, "home");
const upstreamDir = path.join(tmpDir, "upstream");
const installPath = path.join(repoRoot, "korean-jangbu-for", "scripts", "install.sh");
const upstreamSubskills = [
"jangbu-connect",
"jangbu-dash",
"jangbu-import",
"jangbu-jongso",
"jangbu-tag",
"jangbu-tax",
];
fs.mkdirSync(path.join(upstreamDir, "skills"), { recursive: true });
fs.mkdirSync(homeDir, { recursive: true });
for (const skillName of ["korean-jangbu-for", ...upstreamSubskills]) {
const skillDir = path.join(upstreamDir, "skills", skillName);
fs.mkdirSync(skillDir, { recursive: true });
fs.writeFileSync(
path.join(skillDir, "SKILL.md"),
`---\nname: ${skillName}\n---\n\n# ${skillName}\n`,
);
}
fs.mkdirSync(path.join(upstreamDir, "scripts"), { recursive: true });
fs.writeFileSync(path.join(upstreamDir, "scripts", "verify.sh"), "#!/usr/bin/env bash\nexit 0\n");
childProcess.execFileSync("git", ["init"], { cwd: upstreamDir, stdio: "ignore" });
childProcess.execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: upstreamDir });
childProcess.execFileSync("git", ["config", "user.name", "Test"], { cwd: upstreamDir });
childProcess.execFileSync("git", ["add", "."], { cwd: upstreamDir });
childProcess.execFileSync("git", ["commit", "-m", "seed upstream skills"], { cwd: upstreamDir, stdio: "ignore" });
const upstreamSha = childProcess.execFileSync("git", ["rev-parse", "HEAD"], { cwd: upstreamDir, encoding: "utf8" }).trim();
fs.mkdirSync(path.join(homeDir, ".claude", "skills"), { recursive: true });
fs.symlinkSync(
path.join(upstreamDir, "skills", "korean-jangbu-for"),
path.join(homeDir, ".claude", "skills", "korean-jangbu-for"),
);
childProcess.execFileSync("bash", [installPath], {
cwd: repoRoot,
env: {
...process.env,
HOME: homeDir,
KOREAN_JANGBU_FOR_UPSTREAM_REPO: upstreamDir,
KOREAN_JANGBU_FOR_UPSTREAM_SHA: upstreamSha,
},
stdio: "pipe",
});
for (const root of [".claude", ".agents"]) {
const skillRoot = path.join(homeDir, root, "skills");
assert.ok(
fs.existsSync(path.join(skillRoot, "korean-jangbu-for", "upstream", "skills", "jangbu-connect", "SKILL.md")),
`${root} should keep the pinned upstream checkout nested under the wrapper`,
);
assert.match(
fs.readFileSync(path.join(skillRoot, "korean-jangbu-for", "SKILL.md"), "utf8"),
/@kimlawtech/,
`${root} should keep the korean-jangbu-for wrapper policy at the top level`,
);
assert.ok(
!fs.lstatSync(path.join(skillRoot, "korean-jangbu-for")).isSymbolicLink(),
`${root} should replace conflicting upstream korean-jangbu-for symlinks with a wrapper directory`,
);
for (const skillName of upstreamSubskills) {
const installedSubskillPath = path.join(skillRoot, skillName, "SKILL.md");
assert.ok(
fs.existsSync(installedSubskillPath),
`${root} should register upstream subskill ${skillName} as a top-level discoverable skill`,
);
const installedSubskill = fs.readFileSync(installedSubskillPath, "utf8");
assert.match(installedSubskill, /https:\/\/github\.com\/kimlawtech\/korean-jangbu-for/);
assert.match(installedSubskill, /@kimlawtech/);
assert.match(installedSubskill, /SpeciAI/);
assert.match(installedSubskill, /Apache-2\.0/);
assert.match(installedSubskill, /참고용 초안/);
assert.match(installedSubskill, /공식 회계감사/);
assert.match(installedSubskill, /세무신고/);
}
}
});
test("korean-jangbu-for feature doc documents source-first use and mandatory attribution", () => {
const featureDoc = read(path.join("docs", "features", "korean-jangbu-for.md"));