現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就 中止的技能,在紀錄裡長得一模一樣。 start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾 步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在 原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。 status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜 跳過,回報失敗一律不改變技能自己的結論。
102 lines
16 KiB
Markdown
102 lines
16 KiB
Markdown
---
|
|
name: skill-new
|
|
description: Create a new skill in the jsc skill set. Prefetch the skill list and the domain list in parallel, ask skill details via decision tree, generate the skill under the right jsc-{domain} per guidelines.md (create the domain from the template repo if missing), open a PR 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 the user wants to add a skill; not for editing an existing one (use skill-update).
|
|
---
|
|
|
|
# skill-new — create a skill
|
|
|
|
Single source of guidelines: [`../../references/guidelines.md`](../../references/guidelines.md).
|
|
|
|
## Flow
|
|
|
|
1. Collect the decision tree's inputs first, then ask:
|
|
1. Prefetch both inputs — run `tools/list-skills.sh` and `tools/sync-domains.sh` **in parallel**. They share no data, and both answers are needed before the first question, so running them after the questions only makes the user wait. Route each exit code:
|
|
- `list-skills.sh` exit 0 — keep the `domain<TAB>name<TAB>description` rows. Exit 1 — the root could not be derived, the domain list was unreadable, or no skill was found; read stderr and fix the named cause. When stderr says the root could not be derived, set `JSC_PLUGINS_ROOT` to the directory that holds the domain repos and rerun: under a plugin install the script sits in the CLI's plugin cache, so its built-in guess lands in that cache instead of the domain workspace.
|
|
- `sync-domains.sh` exit 0 — the only code that means every repo is present and current; keep the `domain<TAB>path` rows. Exit 3 — 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. Exit 2 — a domain could not be cloned. Exit 1 — the root could not be derived, `gitea.sh` was not found, or the canonical marketplace was unreadable; for the root case set `JSC_PLUGINS_ROOT` as above and rerun. Resolve 2 and 1 before continuing.
|
|
|
|
Completion condition: the skill rows and the `domain<TAB>path` rows are both in hand.
|
|
2. Ask for skill details via the `jsc-ask:ask` decision tree until no doubt remains:
|
|
- Goal (single and not duplicating an existing skill; show the similar skills from the step 1.1 rows for comparison — options must state the impact scope of "reuse existing" versus "create new")
|
|
- Trigger (when to use, when not to, trigger keywords)
|
|
- Input and output (can a standard input/output flow move down to `tools/`; does it need Gitea operations — if so, make the skill use `jsc-gitea/tools/gitea.sh` + token)
|
|
- Owning domain (offer the domain list from the step 1.1 `domain<TAB>path` rows — the domains registered in the canonical marketplace)
|
|
|
|
Completion condition: goal, trigger, input/output and owning domain each have a recorded answer.
|
|
2. If the domain does not exist (`tools/sync-domains.sh` clones every domain **registered in the marketplace**, so a missing directory means the domain is unregistered — the repository itself may already exist on Gitea):
|
|
1. Propose one short English word for the new domain (a single word preferred) and confirm it with the user. Completion condition: the user confirms the domain word.
|
|
2. Check before creating: run `jsc-gitea/tools/gitea.sh clone-url plugins/{domain}`. A URL comes back when the repository already exists — clone it, skip creation, and go on to step 2.3 to fill in whatever content is missing. Only when no URL comes back create the repository through the tool, never by hand: `gitea.sh api POST /orgs/plugins/repos` when `plugins` is an organization, `POST /user/repos` when `plugins` is the token's own account (`tea repo create` does the same job). Only when the call is refused (403 — the token has write but not admin rights on the owner) ask the user to create `plugins/{domain}` by hand, then continue. Completion condition: `gitea.sh clone-url plugins/{domain}` prints a URL and cloning it succeeds.
|
|
3. Build the content following the structure of `https://gitea.jsc.idv.tw/plugins/template`: three plugin manifests (plugin name `jsc-{domain}`, version starting at `0.0.1`), `skills/`, README.md, AGENTS.md. Completion condition: the three manifests, `skills/`, README.md and AGENTS.md all exist in the new repo.
|
|
4. Register the plugin: run `tools/sync-marketplace.sh {domain} {repo-url} {description}`. It needs `python3` on PATH — it edits the marketplace JSON with the json module. It writes the entry into both canonical marketplace files in `plugins/meta` and copies both into every domain repo, so any repo works as the registration entry point. Route each exit code:
|
|
- Exit 3 — written, but some domain repo is not present locally. Run `tools/sync-domains.sh`, then rerun this step.
|
|
- Exit 2 — usage error. Fix the three arguments and rerun.
|
|
- Exit 1 — the root could not be derived, python3 is missing, a canonical file was unreadable, or copies differ byte for byte. Read stderr and fix the named cause: install python3 for the second; for the root case set `JSC_PLUGINS_ROOT` to 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. Then rerun.
|
|
- Exit 0 — every copy holds identical bytes; the script verifies that itself.
|
|
|
|
Completion condition: the script exits 0 and prints the touched paths.
|
|
3. Generate the skill per guidelines.md — this step MUST run as a sub agent:
|
|
- `skills/{name}/SKILL.md`: entirely in English (description within either cap — ≤ 5 sentences or ≤ 5 steps — and stating when to use and when not to; body in STE100-style English)
|
|
- Rules enforceable by hooks go to `jsc-hooks` (never scattered in this domain); standard input/output flows go to `tools/`
|
|
- `references/behaviors.md`: add one `## {name}` section for the new skill, placed in dictionary order among the existing sections, carrying the five rows the guidelines' 「技能行為清單」 section defines — 觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象. Write what the skill really does; do not copy the `description`. A read-only skill still fills 可驗證跡象 with 「無寫入跡象,只有回報內容」. The behavior list ships in this same PR — a skill added without its section leaves the domain's list out of sync the moment this PR merges. When the domain has no `references/behaviors.md` yet, create it with the header line `# jsc-{domain} 技能行為清單`.
|
|
|
|
Then run `tools/sync-skill-manifest.sh {domain-path}` directly (no sub agent needed) to sync the domain README's 「Skills 目錄」 section 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`, 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: `skills/{name}/SKILL.md` exists, `references/behaviors.md` holds a `## {name}` section with all five rows filled, the README lists the skill, and all three manifests show the same new version.
|
|
4. Self-check every item of the guidelines.md audit checklist; fix anything that fails. Run `tools/check-behaviors.sh {domain-path}` for the behavior-list item instead of comparing by eye, and route each exit code: 0 — the list matches `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.** Completion condition: every checklist item passes and `tools/check-behaviors.sh {domain-path}` exits 0.
|
|
5. Call `jsc-git:pr` to open a Push Request. Completion condition: a PR URL comes back and is reported with the table format in [`../../references/pr-report.md`](../../references/pr-report.md).
|
|
6. Deploy the new skill, verify it runs, then report:
|
|
1. Follow [`../../references/deploy-verify.md`](../../references/deploy-verify.md) from 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 body, so verifying inside it either gets blocked or passes on stale behavior. Verify the added skill's row in `tools/list-skills.sh`, every tool the skill added, and one minimal prompt per checkable CLI — the per-CLI prompts run in parallel. Completion condition: every completion condition in `deploy-verify.md` sections 1 to 5 holds for this domain repo.
|
|
2. 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 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 the domain repo that gained the skill, 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, 「新增」, skill name, changed files, PR URL, the step 6.1 route verdict and verification result per item — and keep every earlier section.
|
|
- **Directory page `SKILLSET_CONTENTS`.** 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`. Build one file holding the single row from [`../../templates/skillset-contents.md`](../../templates/skillset-contents.md), its 異動頁 cell written as `[SKILLSET_{HASH}]({url})` with the **absolute** URL from `gitea.sh wiki-url {SKILLSET repo} SKILLSET_{HASH}`. 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 "{owner}/{repo}" {row file} templates/skillset-contents.md`
|
|
|
|
The key column is `2`, the 存取庫 column, holding that repo's `{owner}/{repo}` exactly as the row file writes it, so one domain keeps exactly one row and no other domain's row moves. 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 row 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 row 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 row is in place. Report the `updated` or `added` it printed |
|
|
| | 1 | The write failed. Report `SKILLSET_CONTENTS` as not written, together with the row 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' rows 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, and `wiki-contents.sh upsert` exited 0 with this domain's row on `SKILLSET_CONTENTS` linking that page by absolute URL.
|
|
7. 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-new {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` | the new `SKILL.md` and its behavior-list section are in place, the checklist passes, the PR is open, `deploy-verify.md` sections 1 to 5 hold, and both wiki writes exited 0 |
|
|
| `blocked` | a gate or a missing prerequisite stopped the run before any file was created — `sync-domains.sh` never reached exit 0, or Gitea refused the repository creation and nobody created it by hand |
|
|
| `failed` | the run broke mid-way — `sync-marketplace.sh` or `sync-skill-manifest.sh` kept failing, or a wiki write failed again after its one retry |
|
|
| `degraded` | the skill landed with a part missing — the content page was written while its `SKILLSET_CONTENTS` row was not, or a CLI could not be verified and the reason was recorded |
|
|
| `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 creation 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.
|