Files
meta/skills/skillset-update/SKILL.md
T
jiantw83 1a34529231 feat(meta): 委派判定接進五支技能異動流程
上一輪建好了判準文件、清單與檢核腳本,但沒有任何一支技能會去用它。清單不接進流程,過幾天就跟實際技能脫節,回到手工盤點的老問題。

新增技能要走完決策樹五題加接續技能那一題,產出判定結果寫進清單,沒有那一列不算建立完成。修改技能動到流程或描述就重判,只改文案可沿用舊結論但要更新版本號。刪除技能要刪掉那一列。一次改多支要逐支重判,一支都不能跳。例行稽核把清單一致性排進第一組檢查。

檢核腳本的四個結束碼在五支裡逐一路由。特別寫清楚「回 0 但帶提示」那一種:版本落後與待複核的種入列都回 0,那是提示不是缺失,不能因為看到輸出就判成失敗。版本號是 domain 層級,改一支會標到整個 domain,當成缺失看每次發版整片紅,提示很快就沒人看。

接線時撞到四個原本沒看到的問題,一併處理:

清單放在技能組的中樞存放庫,但改的技能常在別的存放庫,所以四支異動技能各加一條「不在中樞時另開一條清單 PR」,並把「技能 PR 開了、清單 PR 沒開」列進部分完成。不加的話清單改動沒有落地路徑。

一次改多支那一支是平行處理,每個 sub agent 改自己的存放庫。那個設計在各改各的檔案時正確,一加入全技能組共用的單一清單就變成資料競爭。改成 sub agent 只回傳判定列,主 agent 收齊後一次併檔。

技能改名或刪除時,別列指過來的接續欄會變成指向不存在的技能,那正是檢核腳本會抓出來的一種缺失。修改與刪除兩支都加了連動處理。

刪除那一支的參照盤點會撈到清單那一列,盤點步驟與刪除步驟都可能去改它。明寫留給刪除步驟,盤點步驟的完成條件多一種合法結論。

清單十一欄沒有備註欄,多寫一欄會被檢核擋下,所以「沿用前一輪判定」寫進 PR 描述與異動報告,列上只動版本號。

刪除技能還要「移除待辦簿裡引用它的內建項」,但待辦簿本身還不存在,那一半據實寫成尚未接線,並要求帶進異動報告,免得日後被讀成已經清乾淨。

委派清單沒有加進審核檢查清單。那份清單每一項都是逐 domain 判定,委派清單是整輪一份、只存在於中樞存放庫;加進去會讓每個 domain 的 sub agent 各判一次同一個檔。改成在例行稽核裡明寫它不是那幾項之一。
2026-09-03 14:43:15 +08:00

19 KiB
Raw Blame History

name, description
name description
skillset-update Apply one change request across the whole jsc skill set — multiple skills in multiple domains in one pass. Sync every domain repo from the Gitea canonical marketplace while the decision tree asks the change details, apply the change per affected domain via parallel sub agents, re-check against the guidelines checklist until it passes, open a PR per affected repo via jsc-git pr, then deploy and verify per references/deploy-verify.md from a fresh CLI process, and append the change report to wiki SKILLSET_{HASH}. Use when a change spans multiple skills or domains; not for a single skill (use skill-update).

skillset-update — apply one change across the skill set

Single source of guidelines: ../../references/guidelines.md.

Flow

  1. Start tools/sync-domains.sh and the change-details decision tree in parallel — the sync touches no answer the tree needs, and the tree's answers change nothing the sync does, so waiting for one before the other only adds idle time.

    1. Run tools/sync-domains.sh to sync every domain repo of the Gitea canonical marketplace. Exit 0 is the only code that means every repo is present and current; keep the domain<TAB>path rows. 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.sh was not found, or the canonical marketplace was unreadable; when stderr says the root could not be derived, set JSC_PLUGINS_ROOT to 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.
    2. Ask for the change details via the jsc-ask:ask decision tree: what rule or behavior changes, which skills and which domains are affected. Include three required checks before the affected-skill list is final: whether any deterministic input/output flow must move to tools/, whether any detailed flow must run as a sub agent, and whether any wiki or Gitea flow must read inherited environment variables before asking the user. Every option states its impact scope (example: changing a shared flow step touches every skill that calls it). These three are a shaping guardrail asked before any file is touched; keep asking them even when a later step would catch the same problem.

    Completion condition: the domain<TAB>path rows are in hand, and the affected-skill list plus the three checks are agreed with the user.

  2. Apply the change to every affected skill — the modification part 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. Every sub agent also updates its own repo's references/behaviors.md in the same pass: a changed behavior rewrites that skill's ## {name} section, a new skill gets a section inserted in dictionary order, a removed skill loses its section. Keep all five rows filled — 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象. Each repo's behavior list ships in that repo's own PR, so no cross-repo PR pair has to be merged in order.

    Every sub agent also re-runs the delegation decision tree of ../../references/delegate-criteria.md for every skill its repo touched — one skill at a time, not one verdict for the repo, and not one skipped. A batch is exactly where skipping happens: the change that turned three skills from read-only into file-writing looks like one change, and re-judging only the obvious one leaves the other two being triggered on a verdict that no longer describes them. A skill whose text this batch rewrote without touching its flow or its description may keep its verdict, and then only its version moves; that reuse is stated in step 5.2's wiki section, exactly as a fresh verdict is.

    The sub agents do not write those verdicts. jsc-meta/tools/delegate-spec.tsv is one file for the whole skill set, and parallel sub agents writing one file overwrite each other's rows. Each sub agent returns its verdicts as rows — eleven tab-separated columns each, - in every column its verdict leaves unused, origin set to judged for a fresh verdict and left as it was for a reused one — and the main agent merges them into the file in one edit after the sub agents finish. When meta is one of the affected repos, that edit rides in its PR; when it is not, it is a change to plugins/meta and step 4 opens the extra Push Request for it. Then run tools/sync-skill-manifest.sh {domain-path} directly (no sub agent needed) for each affected domain repo to sync that domain README's 「Skills 目錄」 section and bump the version in all three manifests; these runs are independent per repo and may also go in parallel. Route each exit code: 0 — the README block and all three manifests are synced; 1 — the domain path, skills/, README.md, the JSC-SKILLS markers, a SKILL.md, a manifest, or a manifest version field 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 under set -e, so treat it as an environment fault and stop, never as a successful sync. Completion condition: every affected domain repo carries the change, its behavior-list update, the README sync, and the manifest bump; and every touched skill has a delegation verdict from this run — fresh, or recorded as reused with the reason — merged into tools/delegate-spec.tsv by the main agent, with no touched skill left without one.

  3. Check every item of the guidelines.md audit checklist for each touched skill — one sub agent per affected domain repo, run in parallel. Each sub agent runs tools/check-behaviors.sh {domain-path} for the behavior-list item of its own repo instead of comparing by eye, and routes each exit code: 0 — that repo's list matches its skills/ and all five rows are filled; 1 — every mismatch is printed on stderr as {檔案}:{技能名}:{說明}, so fix each one and rerun; 2 — usage error, the tool takes exactly one argument; 3 — nothing was checked, because references/behaviors.md is missing, skills/ is missing, or no SKILL.md was found, so create the missing file and rerun. Exit 3 is never a pass.

    The main agent then runs tools/check-delegate.sh once for the whole batch, not inside the per-repo sub agents: the list is one file covering every domain, so a run per repo checks the same file over again and hands back the same lines from every agent, with nobody holding one verdict. Route each exit code:

    • 0 — the list matches the skills on this machine and every mandatory column is filled. A run that printed lines on stdout and exited 0 still passed. Those lines are hints, not defects: origin=seed marks a row seeded from the earlier inventory and awaiting review, and a version-behind line marks a row whose version trails its domain's current one. A batch bumps several domains at once, so it produces those lines by the dozen — reading them as failures would fail every batch this skill ever runs. Report them as hints. The ones worth acting on are the version-behind lines naming skills this batch touched: their version was supposed to move in step 2, so go back and move it.
    • 1 — a missing row, a duplicate row, a row for a skill this machine does not have, an empty column, a column value outside its vocabulary, or a next pointing at a skill that does not exist. Every one is printed on stderr as {清單路徑}:{domain}/{技能名}:{說明}; fix each and rerun. A merge that lost one sub agent's rows shows up here as those skills missing, so read this code as a merge check too.
    • 2 — usage error: the script takes at most one argument. Fix the call and rerun.
    • 3 — nothing was checked, because tools/delegate-spec.tsv is missing, the root could not be derived, or list-skills.sh listed no skill. Read stderr and fix the named cause; set JSC_PLUGINS_ROOT to the directory holding the domain repos for the root case, as in step 1.1. Exit 3 is never a pass.

    On any failure, return to step 1.2: ask again and fix, until all items pass. Completion condition: every checklist item passes for every touched skill, tools/check-behaviors.sh exits 0 for every affected domain repo, and tools/check-delegate.sh exits 0 once for the batch with its hint lines reported as hints.

  4. Call jsc-git:pr once per affected domain repo to open a Push Request, plus one against plugins/meta when the tools/delegate-spec.tsv merge landed there and meta is not itself an affected repo. Completion condition: every affected repo has a PR URL, plugins/meta included when the list changed there, and all URLs are reported in one table with the format in ../../references/pr-report.md.

  5. Deploy the batch change, verify it runs, then report:

    1. Follow ../../references/deploy-verify.md from section 1 to section 5, once per affected domain repo — the route judgements run in parallel. The batch takes the deploy route only when every affected repo's tools/deploy-route.sh exits 0; a single exit 3 puts the whole batch on the worktree route, because the change reaches the CLIs only when the last repo merges, so name every outstanding release PR. 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 bodies. Verify every touched skill's row in tools/list-skills.sh, every tool this change touched, and one minimal prompt per affected domain per checkable CLI — the per-CLI and per-domain prompts run in parallel. Completion condition: every completion condition in deploy-verify.md sections 1 to 5 holds for every affected domain repo.

    2. Write the change report to the wiki — this part MUST run as a sub agent, one sub agent per affected domain repo, run in parallel. Each sub agent writes two pages in two repos, and they must not be mixed up.

      • Content page SKILLSET_{HASH}. Resolve its repo with jsc-gitea/tools/gitea.sh wiki-repo SKILLSET, which reads JSC_WIKI_REPO_SKILLSET first, then JSC_WIKI_REPO. {HASH} is gitea.sh hash-id "{owner}/{repo}" of that repo, used at the full 40 characters it prints. Write it through jsc-gitea:wiki following ../../templates/skillset-page.md: append a section for this change — date, 「批次更新」, the change request in one line, touched skills, changed files, PR URL, the step 5.1 route verdict and verification result per item, and this repo's skills' delegation verdicts from step 2: each fresh verdict with its columns, each reused one as 「沿用前一輪判定」 with the date of the judgement being reused. The row itself has no note column, so this section is the only place that distinction is kept — and keep every earlier section.

      • Directory page SKILLSET_CONTENTS. One shared page holds every domain's block, so each sub agent writes only its own. It lives in the CONTENTS repo, never in the SKILLSET one: wiki-contents.sh resolves it itself with gitea.sh wiki-repo CONTENTS, whose chain is JSC_WIKI_REPO_CONTENTS then JSC_WIKI_REPO and never falls back to JSC_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 that gitea.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.md

        The key is the H2 heading SKILLSET_{HASH}, so one domain keeps exactly one block and no sibling sub agent's block moves. That page name depends only on {owner}/{repo}, which is why it is the key: a host rename or a changed JSC_WIKI_REPO_SKILLSET leaves it untouched, so the match still finds the existing block. The 2 is 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 live SKILLSET_CONTENTS reads | 存放庫 | 異動報告 | 目前版本 | 最後更新 |, so the link sits in column 2 while column 1 is plain text like plugins/ask. Passing 1 would make the heading plugins/ask, which never matches the key SKILLSET_{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, and never overwrite it as a whole. 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-repo 2 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_SKILLSET for the content page, JSC_WIKI_REPO_CONTENTS for the directory page) and JSC_WIKI_REPO, ask per the jsc-ask:ask rules, then rerun
        gitea.sh hash-id 1 No SHA-1 helper on this machine. Stop and report that sha1sum or shasum has to be installed, and never hand-compute the hash
        2 Empty input, so the {owner}/{repo} was never resolved. Fix that first
        Content page read 0 Append into the sections already there
        4 The page does not exist yet, so build it from templates/skillset-page.md
        7 or 8 Stop and write nothing: a page rebuilt on top of an unread read loses every section already on it
        gitea.sh wiki-url 4 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 name
        link-check.sh 0 Every link is reachable. Write the page
        1 At least one link is dead. Write nothing, and report the DEAD lines it printed
        2 No URL was passed, which is a defect here. Pass the links and rerun
        3 GITEA_HOST is unset. Set it and rerun; never skip the check instead
        7 Gitea authentication failed. Stop and report the key problem, and never read it as a dead link
        wiki-contents.sh upsert 0 The block is in place. Report the updated or added it printed
        1 The page content could not be assembled, or the write failed. Report SKILLSET_CONTENTS as not written, together with the block content
        2 An argument was rejected. Fix it and rerun; nothing was written
        3 No CONTENTS wiki repo is configured. Report JSC_WIKI_REPO_CONTENTS and JSC_WIKI_REPO as the two variables to set; the new section is on SKILLSET_{HASH} and stays there
        4 The directory page is absent and no template was passed. Rerun with templates/skillset-contents.md as the fifth argument
        7 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: every affected repo's SKILLSET_{HASH} holds the new section plus all earlier sections, and every one of those repos has a ## SKILLSET_{HASH} block on SKILLSET_CONTENTS written by a wiki-contents.sh upsert that exited 0, linking its page by absolute URL.

  6. Report this run's outcome to the local event stream — the last step of every run, the ones that stop early included. One event for the whole batch, not one per domain. Run:

    jsc-hooks/tools/report-status.sh skill-end jsc-meta:skillset-update {status} {exit code} [detail]

    Resolve jsc-hooks from the domain<TAB>path row step 1.1 printed for the hooks domain, 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
    ok every affected repo carries the change and its behavior-list update, every touched skill has this run's delegation verdict in delegate-spec.tsv, every checklist passes, every repo has a PR URL, deploy-verify.md sections 1 to 5 hold for all of them, and every wiki write exited 0
    blocked a gate or a missing prerequisite stopped the run before any file changed — sync-domains.sh never reached exit 0, or the affected-skill list was never agreed
    failed the run broke mid-way — the step 3 checklist loop kept failing for some repo, or a wiki write failed again after its one retry
    degraded part of the batch landed and part did not — some repos got their PR and others did not, the domain PRs are open while the delegate-spec.tsv PR is not, or a content page was written while its SKILLSET_CONTENTS block was not. Name the repos in detail
    aborted the 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: 0 for ok, non-zero otherwise. detail is optional, one line, at most 200 characters.

    The matching skill-start comes free from the hook, which fires when the skill loads. The batch itself happens in the model turns after that, so no hook can see how the run ended — a start with no end reads as an abort, which is why writing the end is this skill's own job.

    Completion condition: one skill-end line for this run is appended to $JSC_HOME/usage/events.jsonl, or the script was absent and the final report says so.