上一輪建好了判準文件、清單與檢核腳本,但沒有任何一支技能會去用它。清單不接進流程,過幾天就跟實際技能脫節,回到手工盤點的老問題。 新增技能要走完決策樹五題加接續技能那一題,產出判定結果寫進清單,沒有那一列不算建立完成。修改技能動到流程或描述就重判,只改文案可沿用舊結論但要更新版本號。刪除技能要刪掉那一列。一次改多支要逐支重判,一支都不能跳。例行稽核把清單一致性排進第一組檢查。 檢核腳本的四個結束碼在五支裡逐一路由。特別寫清楚「回 0 但帶提示」那一種:版本落後與待複核的種入列都回 0,那是提示不是缺失,不能因為看到輸出就判成失敗。版本號是 domain 層級,改一支會標到整個 domain,當成缺失看每次發版整片紅,提示很快就沒人看。 接線時撞到四個原本沒看到的問題,一併處理: 清單放在技能組的中樞存放庫,但改的技能常在別的存放庫,所以四支異動技能各加一條「不在中樞時另開一條清單 PR」,並把「技能 PR 開了、清單 PR 沒開」列進部分完成。不加的話清單改動沒有落地路徑。 一次改多支那一支是平行處理,每個 sub agent 改自己的存放庫。那個設計在各改各的檔案時正確,一加入全技能組共用的單一清單就變成資料競爭。改成 sub agent 只回傳判定列,主 agent 收齊後一次併檔。 技能改名或刪除時,別列指過來的接續欄會變成指向不存在的技能,那正是檢核腳本會抓出來的一種缺失。修改與刪除兩支都加了連動處理。 刪除那一支的參照盤點會撈到清單那一列,盤點步驟與刪除步驟都可能去改它。明寫留給刪除步驟,盤點步驟的完成條件多一種合法結論。 清單十一欄沒有備註欄,多寫一欄會被檢核擋下,所以「沿用前一輪判定」寫進 PR 描述與異動報告,列上只動版本號。 刪除技能還要「移除待辦簿裡引用它的內建項」,但待辦簿本身還不存在,那一半據實寫成尚未接線,並要求帶進異動報告,免得日後被讀成已經清乾淨。 委派清單沒有加進審核檢查清單。那份清單每一項都是逐 domain 判定,委派清單是整輪一份、只存在於中樞存放庫;加進去會讓每個 domain 的 sub agent 各判一次同一個檔。改成在例行稽核裡明寫它不是那幾項之一。
22 KiB
name, description
| name | description |
|---|---|
| skill-delete | Remove a skill from the jsc skill set safely. Pick the skill from the Gitea canonical marketplace skill list, inventory every file referencing it, fix each affected file through decision-tree questions in parallel sub agents until guideline checks pass, delete the skill, open a PR via jsc-git pr, then deploy per references/deploy-verify.md and verify from a fresh CLI process that no on-disk leftover remains, and append the change report to wiki SKILLSET_{HASH}. Use only for removal; not for renaming (use skill-update). |
skill-delete — delete a skill
Single source of guidelines: ../../references/guidelines.md.
Flow
-
Run
tools/sync-domains.shto sync every domain repo of the Gitea canonical marketplace. Completion condition: the script exits 0 and prints onedomain<TAB>pathline per marketplace domain — exit 0 is the only code that means every repo is present and current. Exit 3 means some repos were not updated: reconcile every path named on stderr (commit or stash the dirty tree, or fix the failing pull) and rerun; when the user confirms a dirty tree is intentional local work, record that decision and continue on the local version — never read exit 3 as current. Exit 2 means a domain could not be cloned. Exit 1 means the root could not be derived,gitea.shwas not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, setJSC_PLUGINS_ROOTto the directory that holds the domain repos and rerun, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there instead of the domain workspace. Resolve 2 and 1 before continuing. -
Run
tools/list-skills.shand present itsdomain / name / descriptionrows to the user. The tool prints skills, not domains, so read the domain column to prove coverage. Exit 1 means the root could not be derived, the domain list was unreadable, or no skill was found — read stderr, fix the named cause (JSC_PLUGINS_ROOTfor the root case, as in step 1) and rerun; never read it as an empty skill set. Completion condition: the script exits 0 and every domain printed by step 1 appears in at least one row; a domain with no row means its repo is missing or holds no skill — return to step 1 for that domain. -
Let the user pick the skill to delete. Options state the impact scope: which skills reference it, and that its command stops working after deletion. Completion condition: one
{domain}/{name}pair is confirmed for deletion. -
Inventory every file related to the skill: run
tools/find-skill-refs.sh {domain} {name}to list every file that references the skill name or its/jsc-{domain}:{name}command form, across every marketplace domain repo on this machine (covers other SKILL.md files, the domain README's 「Skills 目錄」 section, the two marketplace.json files inplugins/metaplus their synced copies in every domain repo,tools/, and thejsc-hookswiring). The skill-name pattern is a bare substring match, so the list also carries other skills whose name starts with the same word plus plain prose — treat it as candidates to read, not as files that must change. Exit 0 means hits were printed; exit 1 means a clean zero-hit scan; exit 2 means a usage error, so fix the two arguments and rerun; exit 3 means the scan never ran — the root could not be derived, the domain list was unreadable, no domain repo is on this machine, or grep failed. Read stderr and fix the named cause; when it names the root, setJSC_PLUGINS_ROOTto the directory that holds the domain repos, because under a plugin install the script sits in the CLI's plugin cache and its built-in guess lands there. Never read exit 3 as zero hits — a failed scan taken as "no references" makes this skill skip files it must fix. Completion condition: the tool's file list is captured as the step 5 inventory. -
Fix every file in the step 4 inventory. For each file:
- Read the file and decide whether it needs a fix to keep its current behavior after the deletion. If no fix is needed, record it as no-fix-needed with the reason and skip the rest of this loop. Completion condition: the file carries a recorded verdict — needs-fix or no-fix-needed with a reason.
- Ask for fix details via the
jsc-ask:askdecision tree (call a replacement skill? move a deterministic input/output flow totools/? run the detailed flow as a sub agent? drop the feature too?). If the fix touches wiki or Gitea access, confirm it reads inherited environment variables before asking the user. Every option states its impact scope. Completion condition: every question has a recorded answer. - Apply the confirmed fix, then check the guidelines.md audit checklist for the file — the per-file fix work MUST run as a sub agent, one sub agent per affected domain repo, and those sub agents run in parallel: each repo's files are independent, so serialising them only adds waiting. Each sub agent reports one line per file: the path and either the applied fix or「無需修正」with the reason. On any checklist failure, return to step 5.2. Completion condition: the fix is in the file and every checklist item passes for it.
jsc-meta/tools/delegate-spec.tsvappears in that inventory as the row naming the skill. Mark it as handled in step 6 and edit it nowhere else: the row goes out there, together with the skill directory, and splitting the edit over two steps risks one of them removing a row the other still expects to be there.Completion condition: every file in the step 4 inventory is marked either fixed-with-a-clean-checklist, explicitly no-fix-needed with a reason, or deferred to step 6 as the delegation list is — no file is left without a verdict.
-
Delete the skill directory
skills/{name}/and remove that skill's## {name}section fromreferences/behaviors.md— the whole section, its table included, leaving every other section untouched. Both deletions ship in this same PR: a behavior list still carrying a deleted skill fails the domain's next audit, and the extra section is exactly whatcheck-behaviors.shreports. Then runtools/sync-skill-manifest.sh {domain-path}directly (no sub agent needed) to sync the domain README and bump the version in all three manifests. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path,skills/,README.md, theJSC-SKILLSmarkers, a remainingSKILL.md, a manifest, or a manifestversionfield is missing, so fix the named cause on stderr and rerun; 2 — usage error, the script takes exactly one argument; any other code — the script runs underset -e, so treat it as an environment fault and stop, never as a successful sync.Delete that skill's row from
jsc-meta/tools/delegate-spec.tsvin the same pass — that one row, every other row left byte for byte as it was. A list still carrying a deleted skill makes the background assistant trigger a skill that cannot be called, and a failing trigger does not pause itself: it retries every round, for good. Then repoint every remaining row whosenextcolumn named the deleted skill; those rows now name something that cannot be called either, and they are the second half of the same defect. The file lives inplugins/metawhichever domain lost the skill, so deleting a skill outsidemetachanges two repos and step 7 opens the second Push Request for this one.The task-book half is not wired yet.
../../references/delegate-criteria.mdalso asks this skill to drop the assistant task-book entries that name the deleted skill. That task book does not exist yet, so there is nothing to remove from and this skill does not go looking for it. When the task book ships, add that removal here as a step of its own. Until then, carry 「待辦簿引用尚未接線」 into the step 8.3 wiki section, so a later reader does not take this deletion as having cleaned a place it never touched.Then run
tools/check-behaviors.sh {domain-path}and route each exit code: 0 — the remaining sections match the remaining skills; 1 — every mismatch is printed on stderr as{檔案}:{技能名}:{說明}, so fix each one and rerun, the deleted skill's leftover section included; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, becausereferences/behaviors.mdis missing,skills/is missing, or noSKILL.mdwas found, so fix the named cause and rerun. Exit 3 is never a pass.Then run
tools/check-delegate.sh. It takes the plugins root, not a domain path, and the list is one file covering every domain, so it runs once for the whole flow. Route each exit code:- 0 — the list matches the skills on this machine and every mandatory column is filled; the deleted skill has no row left, and no surviving row points at it. A run that printed lines on stdout and exited 0 still passed. Those lines are hints, not defects:
origin=seedmarks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whoseversiontrails its domain's current one, which every skill of the domain this deletion just bumped will now show. Report them as hints and fix nothing for them; a deletion held open over a version-behind line would never close. - 1 — a row remains for a skill this machine no longer has, or a surviving row's
nextpoints at the deleted skill. Both are printed on stderr as{清單路徑}:{domain}/{技能名}:{說明}— the first is the row this step was supposed to remove, the second is anextthis step was supposed to repoint. Fix each and rerun. - 2 — usage error: the script takes at most one argument. Fix the call and rerun.
- 3 — nothing was checked, because
tools/delegate-spec.tsvis missing, the root could not be derived, orlist-skills.shlisted no skill. Read stderr and fix the named cause; setJSC_PLUGINS_ROOTto the directory holding the domain repos for the root case, as in step 1. Exit 3 is never a pass — a check that looked nowhere reports no leftover row either.
Completion condition: the directory is gone,
references/behaviors.mdholds no## {name}section for the deleted skill,tools/delegate-spec.tsvholds no row for it and nonextnaming it,tools/check-behaviors.sh {domain-path}andtools/check-delegate.shboth exit 0, the README's 「Skills 目錄」 no longer lists the skill, and all three manifests show the same new version. - 0 — the list matches the skills on this machine and every mandatory column is filled; the deleted skill has no row left, and no surviving row points at it. A run that printed lines on stdout and exited 0 still passed. Those lines are hints, not defects:
-
Call
jsc-git:prto open a Push Request. When the deleted skill lived in a domain other thanmeta, thetools/delegate-spec.tsvremoval is a change toplugins/metaand needs its own Push Request against that repo — two repos changed, two PRs, neither waiting on the other. Completion condition: a PR URL comes back for every repo this run changed,plugins/metaincluded when the row was removed there, and each is reported with the table format in../../references/pr-report.md. -
Deploy the deletion, verify it took, then report:
-
Follow
../../references/deploy-verify.mdfrom section 1 to section 5:tools/deploy-route.sh {domain-path}picks the route, the deploy route or the worktree route runs, and the verification then runs in a fresh CLI process, never in the session that ran the deploy. That session raised the restart gate itself and still holds the old skill set, so verifying inside it either gets blocked or passes on stale behavior. On the worktree route, add that the skill stays installed and stays callable until the outstanding release PR merges. Completion condition: every completion condition indeploy-verify.mdsections 1 to 5 holds for this domain repo. -
Verify the deletion concretely, on top of the
deploy-verify.mditems:tools/list-skills.shprints no row carrying the deleted{domain}/{name}.- Deep-delete verification. Run
tools/verify-skill-removed.sh {domain} {name}; it detects the installed CLIs and greps each one's skill cache and hook config. Route each exit code: exit 0 — no leftover in the locations listed on stderr; exit 1 — leftovers printed as{file}:{line}:{content}, so remove every one by hand and rerun; exit 2 — usage error, fix the two arguments and rerun; exit 3 — nothing was checkable, because the root could not be derived,detect-clis.shwas not found, no CLI was detected, or no config location exists. The script printed no leftover because it looked nowhere, so exit 3 is never clean: report「無處可查」with the reason from stderr, and carry that sentence into step 8.3's wiki section and the final report, so nobody later reads the deletion as verified on disk. This is the only place the deep-delete check runs — running it before the PR only scanned a deletion that had not been deployed yet, so it always came back clean and proved nothing. - Every tool and skill that step 5 fixed still finishes with its documented exit code — a fix that broke a caller shows up here, not earlier.
- One minimal prompt per checkable CLI, each in its own fresh process and all in parallel, confirming
/jsc-{domain}:{name}is gone or that the replacement path still works.
On any mismatch — the deleted skill still listed, a leftover from exit 1, a fixed caller that now fails, a prompt failure, or unexpected stderr — fix the cause and rerun this step from 8.1. Completion condition: the skill is absent from the list, the verification script exits 0 or its exit 3 is reported as「無處可查」and carried into step 8.3, every fixed caller ran, every checkable CLI completed the prompt with the expected result, and every untestable CLI has a stated reason.
-
Write the change report to the wiki — this part MUST run as a sub agent. It is two pages in two repos, and they must not be mixed up.
-
Content page
SKILLSET_{HASH}. Resolve its repo withjsc-gitea/tools/gitea.sh wiki-repo SKILLSET, which readsJSC_WIKI_REPO_SKILLSETfirst, thenJSC_WIKI_REPO.{HASH}isgitea.sh hash-id "{owner}/{repo}"of the domain repo that lost the skill, used at the full 40 characters it prints. Write it throughjsc-gitea:wikifollowing../../templates/skillset-page.md: append a section for this change — date, 「刪除」, skill name, changed files (the step 5 inventory verdicts included), PR URL, the step 8.1 route verdict and the step 8.2 verification result per item, the deep-delete verdict「無處可查」included when it applies, and the step 6 note 「待辦簿引用尚未接線」 — and keep every earlier section. -
Directory page
SKILLSET_CONTENTS. It lives in the CONTENTS repo, never in the SKILLSET one.wiki-contents.shresolves it itself withgitea.sh wiki-repo CONTENTS, whose chain isJSC_WIKI_REPO_CONTENTSthenJSC_WIKI_REPOand never falls back toJSC_WIKI_REPO_SKILLSET. That page is a heading-plus-bullets list and holds no markdown table: one## SKILLSET_{HASH}block per domain repo, every field one- {欄位名}:{值}line under it. Build one file holding this domain's single block, following../../templates/skillset-contents.md, with its 異動頁 bullet written as[SKILLSET_{HASH}]({url})from the absolute URL thatgitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}prints. The H2 heading itself carries no link, no URL, no affix and no date — only the content page name. Every link on both pages takes that[{text}]({url})form; the double-bracket wiki-link form resolves only inside one wiki, so it is never used. Then run:jsc-gitea/tools/wiki-contents.sh upsert SKILLSET 2 "SKILLSET_{HASH}" {entry file} templates/skillset-contents.mdThe key is the H2 heading
SKILLSET_{HASH}, so one domain keeps exactly one block and no other domain's block moves. That page name depends only on{owner}/{repo}, which is why it is the key: a host rename or a changedJSC_WIKI_REPO_SKILLSETleaves it untouched, so the match still finds the existing block. The2is the key column: the index of the column that held the content-page link in the old markdown table, and it matters only when such an old table still has to be converted automatically — the conversion takes the last path segment of that column's link URL as the H2 heading. Count that index from the live page's own column layout, never from the template's: the liveSKILLSET_CONTENTSreads| 存放庫 | 異動報告 | 目前版本 | 最後更新 |, so the link sits in column 2 while column 1 is plain text likeplugins/ask. Passing1would make the headingplugins/ask, which never matches the keySKILLSET_{HASH}, so the existing entry is appended as a brand-new one — one domain ends up with two blocks and the older one is never updated again. The fourth argument is the whole block, not a table row. Never hand-edit the directory page. Write the content page first and fetch the URL only after it exists. -
Check the links before writing. Hand every URL going onto the content page and into the directory block to
jsc-gitea/tools/link-check.sh, and write only when it exits 0. It verifies through the Gitea API, never a web status code: a private repo answers 404 to an unauthenticated web request, so a status-code check would call a live page dead. -
Exit codes. Route every one of them:
Call Exit Do gitea.sh wiki-repo2 The page type was misspelled. Fix the argument and rerun 3 No wiki repo is configured for that type. Name the variable ( JSC_WIKI_REPO_SKILLSETfor the content page,JSC_WIKI_REPO_CONTENTSfor the directory page) andJSC_WIKI_REPO, ask per thejsc-ask:askrules, then rerungitea.sh hash-id1 No SHA-1 helper on this machine. Stop and report that sha1sumorshasumhas to be installed, and never hand-compute the hash2 Empty input, so the {owner}/{repo}was never resolved. Fix that firstContent page read 0 Append into the sections already there 4 The page does not exist yet, so build it from templates/skillset-page.md7 or 8 Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it gitea.sh wiki-url4 The content page is not there, so the write above did not succeed. Go back and write it, and add no directory block until the page exists 5 The API answered with no html_url. Stop and report it; never assemble the URL by hand from the host and the page namelink-check.sh0 Every link is reachable. Write the page 1 At least one link is dead. Write nothing, and report the DEADlines it printed2 No URL was passed, which is a defect here. Pass the links and rerun 3 GITEA_HOSTis unset. Set it and rerun; never skip the check instead7 Gitea authentication failed. Stop and report the key problem, and never read it as a dead link wiki-contents.sh upsert0 The block is in place. Report the updatedoraddedit printed1 The page content could not be assembled, or the write failed. Report SKILLSET_CONTENTSas not written, together with the block content2 An argument was rejected. Fix it and rerun; nothing was written 3 No CONTENTS wiki repo is configured. Report JSC_WIKI_REPO_CONTENTSandJSC_WIKI_REPOas the two variables to set; the new section is onSKILLSET_{HASH}and stays there4 The directory page is absent and no template was passed. Rerun with templates/skillset-contents.mdas the fifth argument7 The token is invalid or lacks permission, so the other domains' blocks are unknown. Stop, report the token problem, and create no page 8 Some other API failure. Stop, report that status, and create no page
On any failure, hand the page name and the unwritten entry back to the user and leave this step open; never close the flow on an unwritten report. Completion condition:
SKILLSET_{HASH}holds the new section plus all earlier sections, andwiki-contents.sh upsertexited 0 with this domain's## SKILLSET_{HASH}block onSKILLSET_CONTENTSlinking that page by absolute URL. -
-
-
Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. Run:
jsc-hooks/tools/report-status.sh skill-end jsc-meta:skill-delete {status} {exit code} [detail]Resolve
jsc-hooksfrom thedomain<TAB>pathrow step 1 printed for thehooksdomain, the same way this skill resolves every other cross-plugin script. When that script is not on this machine, skip this step in silence and close the run as normal. A reporting path that is absent must never fail the run it reports on, and this call's own exit code never changes what this skill reports.Pick
{status}from what the run actually did:status Use it when okthe skill directory, its behavior-list section and its delegate-spec.tsvrow are gone, every PR this run needed is open,deploy-verify.mdsections 1 to 5 hold,verify-skill-removed.shandcheck-delegate.shboth exited 0, and both wiki writes exited 0blockeda gate or a missing prerequisite stopped the run before any file changed — sync-domains.shnever reached exit 0, or no skill could be listed to pick fromfailedthe run broke mid-way — a leftover from verify-skill-removed.shexit 1 could not be removed, or a wiki write failed again after its one retrydegradedthe deletion landed with a part missing — the deep-delete check came back 「無處可查」, the skill's PR is open while the delegate-spec.tsvPR is not, or the content page was written while itsSKILLSET_CONTENTSblock was notabortedthe user stopped the run, or a prerequisite turned out not to hold and this skill stopped on its own {exit code}is this run's own result as a number:0forok, non-zero otherwise.detailis optional, one line, at most 200 characters.The matching
skill-startcomes free from the hook, which fires when the skill loads. The deletion itself happens in the model turns after that, so no hook can see how the run ended — astartwith noendreads as an abort, which is why writing theendis this skill's own job.Completion condition: one
skill-endline for this run is appended to$JSC_HOME/usage/events.jsonl, or the script was absent and the final report says so.