feat(狀態回報): 收尾寫一筆 skill-end 事件

現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就
中止的技能,在紀錄裡長得一模一樣。

start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾
步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在
原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。

status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜
跳過,回報失敗一律不改變技能自己的結論。
This commit is contained in:
2026-09-02 16:01:18 +08:00
parent 0191e7ba8e
commit f38d1f3087
6 changed files with 102 additions and 22 deletions
+16 -1
View File
@@ -12,7 +12,7 @@ This skill is a **logic-only** stage: never output code, and **never modify any
**Content pages and directory pages live in different wiki repos.** `ANALYZE_{HASH}` sits in the repo `jsc-gitea/tools/gitea.sh wiki-repo ANALYZE` resolves, `REPO_{HASH}` in the one `gitea.sh wiki-repo REPO` resolves. All three directory pages — `ANALYZE_CONTENTS`, `PLAN_CONTENTS` and `REPO_CONTENTS` — sit in the repo `gitea.sh wiki-repo CONTENTS` resolves: `JSC_WIKI_REPO_CONTENTS` first, `JSC_WIKI_REPO` second, exit 3 when neither is set; it **never** falls back to `JSC_WIKI_REPO_ANALYZE`, `JSC_WIKI_REPO_PLAN` or `JSC_WIKI_REPO_REPO`. Every directory row links its content page by the absolute URL from `gitea.sh wiki-url {content repo} {page}`, written as `[{text}]({url})` — one link syntax, whichever wiki the two pages sit in. The syntax and the check that runs before every write: "Every link is checked before it reaches a page" below.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Step 11 still runs after such a stop.
All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or write stops this stage**: report which page and which operation failed, never carry on against a page you could not read, and never report a page as saved when the write failed. Steps 11 and 12 still run after such a stop.
## Steps
@@ -47,6 +47,21 @@ All wiki reads and writes go through `jsc-gitea:wiki`. **A failed wiki read or w
Both runs branch on the exit code per "Contents pages are appended, never overwritten" below. Completion condition: the analysis page is saved on the wiki carrying every section the template dictates — the source branch, the head sha and the 未決項 section (「無」 when there is none) included — every `wiki-url` call this step made returned 0 and its URL is the one in the row, every link written by this step was cleared by a `link-check.sh` run that exited 0, both `wiki-contents.sh` runs exited 0, and `ANALYZE_CONTENTS` shows this analysis's row while `PLAN_CONTENTS` shows the literal 「已分析」.
11. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, the working tree did not match `origin/{source-branch}`, no plan was selectable, a wiki read or write failed). Run `tools/stage-report.sh analyze` with one `--page TYPE:{page}` per wiki page this run wrote — `--page ANALYZE:ANALYZE_{HASH}`, `--page CONTENTS:ANALYZE_CONTENTS`, `--page CONTENTS:PLAN_CONTENTS`, and `--page REPO:REPO_{HASH}` plus `--page CONTENTS:REPO_CONTENTS` when a re-inventory happened. **Every directory page takes the `CONTENTS` type**: the script resolves each page's repo from the TYPE you pass, and a directory page passed under its old type resolves the wrong repo and prints no URL. Add `--worklog` and `--worklog-heading` when a work log entry exists. No work log yet: write this stage's log content to a file — with a Bash heredoc or `mktemp` per Hard limits, never with `Write` or `Edit` — and pass `--pending-file {file} --log-hash {HASH}` so it is held for the next `jsc-log:worklog` run. Rules and exit codes: `references/stage-report.md`. Exit 1 is a warning, never a block. Completion condition: the script's output is reported to the user verbatim, and every wiki page this run wrote appears in it.
12. **Write this run's `skill-end` status event — the very last thing this stage does, right after step 11, on every path including every early stop.** Run `jsc-hooks/tools/report-status.sh skill-end jsc-sdlc:analyze {status} {exit} [detail]`, naming the script the way this stage already names `jsc-hooks/hooks/sdlc-gate.sh` in step 1. The matching `skill-start` event is written by jsc-hooks on its own, so this step owes only the `end`: a hook fires on the skill tool call and this stage's work happens in the model turns after it, so **no hook can see how this run ended**. A `start` with no `end` is what an aborted run looks like in the record, and this step is the only thing that keeps a finished run from looking like one.
`{status}` is one of five words, never a sixth:
| Status | When `analyze` reports it |
| --- | --- |
| `ok` | Every step's completion condition is met: the gate passed, the source branch was confirmed and `origin/{source-branch}` matched HEAD, every user story reached consensus, the WBS, the CPM figures and the TDD todos are on the page, `ANALYZE_{HASH}` is saved, every directory row this run owed was upserted, and `tools/stage-report.sh` exited 0 |
| `blocked` | A check that lives in code stopped the run before any analysis started: `sdlc-gate.sh lock analyze` exited non-zero because the model carries no `reasoning-max` tag, or step 4.3 found HEAD not pointing at the same commit as `origin/{source-branch}`. Nothing was analyzed, so this is **never `failed`** — both are the guard working, and recording either as a failure sends the next reader hunting for a defect that is not there |
| `failed` | The run got past those checks and then a write did not land: the `ANALYZE_{HASH}` or `REPO_{HASH}` write failed, `wiki-url` returned 5, 7 or 8, `link-check.sh` returned 1 so nothing was written, or a `wiki-contents.sh` run returned 1, 7 or 8 |
| `degraded` | The analysis page is saved but not every directory followed it: `ANALYZE_CONTENTS` was upserted while `PLAN_CONTENTS` still shows 「未分析」, a re-inventory wrote `REPO_{HASH}` but not its `REPO_CONTENTS` row, `wiki-contents.sh` returned 3, or `tools/stage-report.sh` exited 1. The analysis exists; what is missing is a directory row that lets anyone find it |
| `aborted` | The user stopped the run, or the run stopped itself because its premise did not hold — step 2 found both directory pages empty, so there was nothing to analyze |
`{exit}` is the exit code of the script whose verdict decided the status — the gate's code for `blocked`, the failing script's code for `failed` and `degraded` — and `0` when nothing exited non-zero, `ok` and `aborted` included. `[detail]` is optional and Traditional Chinese per the STE100 rule: one line, no line break, naming what decided the status (for example 「工作目錄與來源分支不一致」 or 「計畫目錄頁狀態未改」). The script truncates it at 200 characters, so put the short reason there and nothing else.
**A failure in this step never changes this stage's verdict.** The script is not found (jsc-hooks is not installed on this machine, or this CLI's layout puts it somewhere else) → skip the event quietly and carry on; nothing is reported to the user and no step is re-run. The three recording sub-commands are built to exit 0 even when the write fails, so a non-zero code here means only that the call itself was malformed (exit 2, a usage error) — fix the arguments once and, either way, never turn a finished stage into a failed one because the record of it failed. Completion condition: one `skill-end` event has been written for this run, or the script could not be found and that skip is the reason no event exists.
## Contents pages are appended, never overwritten