Files
meta/skills/skillset-update/SKILL.md
T
jiantw83 f2332f965c feat(delegate): 清單加一欄記唯讀盤點的實際指令
切片交的那幾支,助理現在只會提醒不會動手,因為沒有東西記得下「那一段唯讀盤點到底要跑什麼」。清單加第十二欄記具體指令,種入那一支有值就拿它當動作、沒值才退回只提醒。

只認三種寫法。一行指令,路徑一律寫成代入點開頭,由種入那一支代進字面絕對根目錄;指令裡不可以出現金錢符號或波浪號,那兩種在無人值守那一輪解不出來也進不了允許清單,會被靜靜擋掉。要標未接線就寫理由,留白的話下一輪分不出是刻意還是漏填。沒有唯讀入口的寫減號。

觸發型與不交的列一律填減號。觸發的意思是呼叫整支技能,這一欄填了指令會讓種入那一支改拿指令當動作,於是整支交出降級成只跑一支腳本,該寫的頁一頁都不會寫,而且看起來完全正常。這條由檢核腳本擋。

逐項回各存放庫核對,不照抄既有盤點的措辭,因而抓到五處對不上實際腳本的地方。

最要緊的一處:既有盤點點名的那支工具腳本要三個參數,其中兩個無人值守那一輪根本拿不到,而且它是唯一會去問遠端的一方。改用同一件事的本機那一半,而那個子命令剛好在閘門腳本「免讀標準輸入」的清單裡。這一點非確認不可:同一支腳本的另一個子命令不在那份清單上,標準輸入是管線又沒人關閉時會一直等,實測會無限卡住。腳本自己的註解就記著這個坑,說工具腳本轉呼叫時曾經整支卡死——那正是先前心跳斷掉的同一種死法。

另一處:既有盤點點名的同步子命令會寫檔,不是唯讀。那一列剛好是觸發型所以填減號、衝突沒落地,但措辭本身是錯的。

七項標成未接線,理由都是要連網要金鑰。連網那一種一過期就讓那一項每輪失敗,或每輪靜靜回報沒事——後者更難查;而純本機讀取本來一輪都不會失敗。先填會連網的,等於用一批每輪報錯的項目把真的發現蓋掉。

還記下一件接線那天要注意的事:查遠端版本那一支永遠回成功,查不到就安靜放行,所以金鑰失效時它會靜靜回報沒有新版。接線要用另一個子命令,那個有「查不出來」這第三種結論。

檢核腳本從四項檢查改成五項。填錯欄位、指到不存在的腳本、用了認不得的代入點都算缺失;標未接線只印提示,那是判過知道還沒接,不是漏填;指到的 domain 本機沒裝也只提示,那是機器少裝一套不是清單填錯,算缺失會讓半套機器每輪亮紅。

五支技能異動流程一併改:寫清單時要問到並寫下這一欄,重判時要一併重問,刪除時要檢查別列有沒有指到一起刪掉的腳本,一次改多支時要注意跨存放庫搬腳本是這一欄最容易過期的地方。
2026-09-04 09:56:55 +08:00

90 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: skillset-update
description: 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`](../../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`](../../references/delegate-criteria.md) for **every** skill its repo touched — all five questions, the `next` question and the `probe` question, 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 — twelve tab-separated columns each, `probe` last, `-` 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. A batch is where the `probe` column goes stale fastest: a change that moves or renames a `tools/` script across several domains leaves every row naming it pointing at nothing, so each sub agent verifies that column against its own repo's files — which script, whether it takes a read-only flag, how a failure is reported — and returns `pending:{reason}` rather than a command for any slice that would need the network. 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 `probe=pending:` line marks a delegated slice whose read-only entry point is not wired yet, and a line saying a `probe` domain is not installed here marks a script this machine cannot check. 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, a `next` pointing at a skill that does not exist, or a `probe` in the wrong shape — a command on a row whose `way` holds `invoke`, a `-` on a row whose `way` holds only `patrol` or `remind`, a dollar sign or tilde, an unknown substitution point, or a script 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`](../../references/pr-report.md).
5. Deploy the batch change, verify it runs, then report:
1. Follow [`../../references/deploy-verify.md`](../../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`](../../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`](../../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.