From 39a7e17b856a4f15cd4d393e9e31ab47617f7330 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 31 Aug 2026 11:10:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(contents):=20=E7=9B=AE=E9=8C=84=E9=A0=81?= =?UTF-8?q?=E6=94=B9=E7=82=BA=E5=85=88=E8=AE=80=E5=9B=9E=E5=86=8D=E9=99=84?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 目錄頁的每一列都屬於別人的計畫、工作包或存取庫。過去照範本整頁覆寫,別人的列會直接消失,而且寫入不做合併,也沒有備份。 - 五份目錄範本都寫明寫入語意:先讀回整頁,已有的列就更新,沒有才附加。 - 規劃與維護技能加上讀取結束碼分支表。只有「頁面真的不存在」才准照範本建頁;金鑰失效或 API 失敗一律停下來回報,不得當成沒有頁面。 - wiki 讀或寫失敗就停止該階段,並指出是哪一頁、哪一個動作失敗,避免把沒存成功的頁面報成已存。 - 補上寫入閘門只有 claude 擋得住的事實,其餘四支 CLI 只能靠內文約束。 - 記下工作包閘門對規劃階段降為提醒後放棄的在製品上限。 - 維護階段先整批對齊各專案再逐一交給 sub agent,專案之間互不相依。 --- skills/maintain/SKILL.md | 31 ++++++++++++++++++++++++------- skills/plan/SKILL.md | 29 +++++++++++++++++++++++++---- templates/analyze-contents.md | 2 ++ templates/deliver-contents.md | 2 ++ templates/maintain-contents.md | 2 ++ templates/plan-contents.md | 2 ++ templates/repo-contents.md | 2 ++ 7 files changed, 59 insertions(+), 11 deletions(-) diff --git a/skills/maintain/SKILL.md b/skills/maintain/SKILL.md index 06d7085..2640d94 100644 --- a/skills/maintain/SKILL.md +++ b/skills/maintain/SKILL.md @@ -6,15 +6,15 @@ description: SDLC maintenance stage. Gate on capability tags enforced in code by # maintain Goal: run routine maintenance for every project in the maintenance contents page. -All wiki reads and writes go through `jsc-gitea:wiki`. +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 5 still runs after such a stop. ## Steps 1. **Model gate and stage lock** — run `jsc-cli/tools/model-tags.sh sync`, then `jsc-hooks/hooks/sdlc-gate.sh lock maintain`. This stage requires no specific capability tag; the gate passes as long as the script can determine the actual model id. Rules: `references/model-gate.md`. Completion condition: the script exited 0, and you have reported the stage, the required tag, the actual model id it read from the transcript, and the verdict. 2. Read `MAINTAIN_CONTENTS` via `jsc-gitea:wiki` and filter projects **still inside their maintenance window**: start date ≤ today, and (end date is NULL or ≥ today). Completion condition: you have listed every in-window project with its `{owner}/{repo}` and window dates, or reported that none is in window and stopped. -3. Every project **MUST run as a sub agent** with this flow. Completion condition: every project listed in step 2 has its sub agent finished, and each one ends in either a PR link or a recorded skip reason. - 1. Run `git fetch --prune origin`, then put the project on its maintenance branch and align it with `origin/{branch}`. Which branch that is, the remote-is-the-basis rule, the diverged case and the never-pull-never-reset rule all live in `references/branch.md`; never guess the branch name. Completion condition: the project's HEAD points at the same commit as `origin/{branch}`, or you have reported the gap and skipped this project. - 2. Propose **at least five** maintenance methods, then let the user pick per `jsc-ask:ask` rules — every option states its impact scope (which files it touches, whether it can break the build, how much review it costs). Candidates: +3. **Align every project in one batch first, then run one sub agent per project.** Completion condition: every project listed in step 2 has its sub agent finished, and each one ends in either a PR link or a recorded skip reason. + 1. **Batch prefetch, run by the main agent before any sub agent starts.** For every in-window project from step 2, run `git fetch --prune origin`, then put it on its maintenance branch and align it with `origin/{branch}`. **The projects are independent — run this batch concurrently**, and hand each sub agent the branch name and the aligned commit sha instead of letting it fetch again. Which branch that is, the remote-is-the-basis rule, the diverged case and the never-pull-never-reset rule all live in `references/branch.md`; never guess the branch name. From sub-step 3.2 onward the flow is one project at a time, sequential, so that 3.5's work log rule holds. Completion condition: every project's HEAD points at the same commit as `origin/{branch}`, or its gap is reported and that project is skipped and left out of the sub agent runs. + 2. **From here on, one project at a time, and each project's maintenance MUST run as a sub agent.** Propose **at least five** maintenance methods, then let the user pick per `jsc-ask:ask` rules — every option states its impact scope (which files it touches, whether it can break the build, how much review it costs). Candidates: - dependency updates (reuse `jsc-pkg:pkg-update`) - security vulnerability scan and patching - dead code and stale comment cleanup @@ -23,9 +23,26 @@ All wiki reads and writes go through `jsc-gitea:wiki`. - build warning elimination Completion condition: the user has picked the methods to apply, and every picked method is either applied or reported with the reason it could not be. - 3. **A code comment states why the code is written this way; it never states where the work is tracked.** Issue numbers, commit hashes, branch names, people's names and `@` mentions stay out of every code comment this project's maintenance touches — including the comments the cleanup method rewrites. Full list and the allowed exceptions: `jsc-review/references/comment-scope.md`. `jsc-hooks/hooks/comment-scope.sh` compares each file after it is written and prints a warning; fix the flagged line at once, then carry on. Completion condition: this project's diff holds no comment line carrying an issue number, a commit hash, a branch name, a person's name or an `@` mention, and every warning the hook printed is fixed. + 3. **A code comment states why the code is written this way; it never states where the work is tracked.** Issue numbers, commit hashes, branch names, people's names and `@` mentions stay out of every code comment this project's maintenance touches — including the comments the cleanup method rewrites. Full list and the allowed exceptions: `jsc-review/references/comment-scope.md`. Two passes already cover the diff, so **run no separate manual sweep of your own**: `jsc-hooks/hooks/comment-scope.sh` compares each file after it is written and prints a warning — fix the flagged line at once, then carry on — and `jsc-git:commit` sweeps the whole working tree again in step 3.4, before anything is committed. **Coverage is not the same on every CLI**: only claude gets the per-file warning as the file is written. On codex, kiro, copilot and antigravity the hook fires late — at the end of the turn on codex, at the next prompt submit on kiro, at the end of the session on copilot and antigravity — so the pre-commit sweep in step 3.4 is the only pass on all four that lands in time to keep a flagged comment out of the commit. Completion condition: every warning the hook printed is fixed, and the step 3.4 sweep reported no remaining comment line carrying an issue number, a commit hash, a branch name, a person's name or an `@` mention. 4. Commit the changes to a new branch per `jsc-git:commit`, push, then open a PR per `jsc-git:pr` back to the branch of step 3.1, passing it explicitly as the base. Completion condition: the PR exists, and you have reported it with the table format in `jsc-meta/references/pr-report.md`. 5. **One project's maintenance is one finished task — call `jsc-log:worklog` right after its PR is open.** A task is one of three things: one work package, one round of PR-comment fixes, or one standalone fix commit; this stage produces the third kind, one per project. Never let the stage end and then write a single catch-up entry, and never let a second project start before the first one's entry is saved — by then the elapsed time, the token counts and the difficulties are gone. Every entry appends to the same `LOG_{HASH}` page. Content parked earlier by `tools/stage-report.sh --pending-file` is merged into that same write and cleared only once the write succeeds; parked content is not a written log. Completion condition: this project's entry is saved on `LOG_{HASH}` before the next project's sub agent starts. - 6. Update the project's last-maintained field (the zh-TW column 「前次維護時間」) in `MAINTAIN_CONTENTS` to today. Completion condition: `MAINTAIN_CONTENTS` shows today's date in 「前次維護時間」 for that project, saved on the wiki. + 6. Update the project's last-maintained field (the zh-TW column 「前次維護時間」) in `MAINTAIN_CONTENTS` to today, per "Contents pages are appended, never overwritten" below: read the page back, change only this project's row (add it if it is missing), and write the whole page. Completion condition: `MAINTAIN_CONTENTS` shows today's date in 「前次維護時間」 for that project, every other project's row is byte-for-byte unchanged, and the `wiki-get` exit code the write branched on is named. 4. The main agent reports the summary: maintenance methods applied per project, PR table rows, and failure reasons. The report and all generated wiki content, commits, and PR descriptions stay Traditional Chinese per the STE100 rule. Completion condition: the summary names every project read in step 2, each with its applied methods and either a PR table row or the reason it was skipped. -5. **Stage report — the last thing this stage does, including when no project was in window.** Run `tools/stage-report.sh maintain` with one `--page MAINTAIN:{page}` per wiki page this run wrote (`MAINTAIN_CONTENTS` counts), plus `--worklog` and `--worklog-heading` pointing at the entries step 3.5 wrote. `--pending-file {file} --log-hash {HASH}` is the fallback for a stage that stopped before any project finished: it holds the content for the next `jsc-log:worklog` run, and held content is not a written log. 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. +5. **Stage report — the last thing this stage does, including when no project was in window, and when a wiki read or write failed.** Run `tools/stage-report.sh maintain` with one `--page MAINTAIN:{page}` per wiki page this run wrote (`MAINTAIN_CONTENTS` counts), plus `--worklog` and `--worklog-heading` pointing at the entries step 3.5 wrote. `--pending-file {file} --log-hash {HASH}` is the fallback for a stage that stopped before any project finished: it holds the content for the next `jsc-log:worklog` run, and held content is not a written log. 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. + +## Contents pages are appended, never overwritten + +`MAINTAIN_CONTENTS` is a shared directory: every row on it belongs to somebody's project, and this run reads none of those rows from anywhere else. So step 3.6 is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh its 前次維護時間 — then `wiki-put` the whole page. Whole-page overwrite is forbidden, and a project this run did not maintain keeps its row untouched. + +That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code: + +| Exit | What this step does | +| --- | --- | +| 0 | the page is there — upsert this project's row into the content that came back, then write the whole page | +| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template, and it also means step 2 had no project to maintain | +| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing | +| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged | + +Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes step 3.6 write a fresh template over a live directory, and every other project's maintenance window is gone — the write carries no merge and no backup. Content pages, which belong to one subject each, are the opposite case and may be rewritten whole. The distinction is the page, not the write. + +Completion condition: the `MAINTAIN_CONTENTS` write names the `wiki-get` exit code it branched on, and no page was created on any code other than 4. diff --git a/skills/plan/SKILL.md b/skills/plan/SKILL.md index c8e7d75..f49e468 100644 --- a/skills/plan/SKILL.md +++ b/skills/plan/SKILL.md @@ -9,7 +9,7 @@ Goal: create or extend the wiki plan page `PLAN_{HASH}`. This skill is a **logic-only** stage: never output code, and **never modify any file**. `{HASH}` = the shared wiki hash for `{owner}/{repo}` used to build the `PLAN_{HASH}` page name, computed by `jsc-gitea/tools/hash-id` (see `jsc-gitea:wiki`). -All wiki reads and writes go through `jsc-gitea:wiki`. +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 8 still runs after such a stop. ## Steps @@ -26,12 +26,33 @@ All wiki reads and writes go through `jsc-gitea:wiki`. Completion condition: all three items are settled under both conditions of `references/consensus.md` — no remaining unknown that would change the output, **and** the user's explicit confirmation of the summary you read back. 5. Turn the consensus into **user stories** (the zh-TW pattern 「身為⋯⋯我想要⋯⋯以便⋯⋯」), one per line. Completion condition: every consensus item is covered by at least one user story in that pattern, and no user story rests on an unanswered question. 6. Apply `templates/plan-page.md` to create or update the plan page, and write it back via `jsc-gitea:wiki`. The page content is Traditional Chinese, exactly as the template dictates. Completion condition: the page is saved on the wiki and carries every section the template dictates — goal, scope, feasibility, user stories and the consensus summary — with no placeholder left unfilled. -7. If the plan page is new, add it to `PLAN_CONTENTS` using the entry format of `templates/plan-contents.md`, with status set to the literal 「未分析」. Completion condition: `PLAN_CONTENTS` shows the plan's row with the literal 「未分析」, saved on the wiki. -8. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, no plan was selectable). Run `tools/stage-report.sh plan` with one `--page PLAN:{page}` per wiki page this run wrote (`PLAN_{HASH}` and `PLAN_CONTENTS` both count), plus `--worklog` and `--worklog-heading` when a work log entry exists. No work log yet: write this stage's log content to a file 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. +7. Upsert this plan's row in `PLAN_CONTENTS` using the entry format of `templates/plan-contents.md`, with status set to the literal 「未分析」 — add the row if missing, otherwise refresh it. Read the page back first and write the whole page, per "Contents pages are appended, never overwritten" below; never overwrite it wholesale, and never touch a row belonging to another plan. Completion condition: `PLAN_CONTENTS` shows this plan's row with the literal 「未分析」, every other row is byte-for-byte unchanged, and the `wiki-get` exit code the write branched on is named. +8. **Stage report — the last thing this stage does, including every early stop** (the model gate blocked, no plan was selectable, a wiki read or write failed). Run `tools/stage-report.sh plan` with one `--page PLAN:{page}` per wiki page this run wrote (`PLAN_{HASH}` and `PLAN_CONTENTS` both count), plus `--worklog` and `--worklog-heading` when a work log entry exists. No work log yet: write this stage's log content to a file 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. + +## Contents pages are appended, never overwritten + +`PLAN_CONTENTS` is a shared directory: every row on it belongs to somebody's plan, and this run reads none of those rows from anywhere else. So the write is an upsert of one row on top of the content just read — add the row if missing, otherwise refresh it, then `wiki-put` the whole page. Whole-page overwrite is forbidden. + +That rests entirely on reading the old page back, so branch the `wiki-get` on its exit code: + +| Exit | What this step does | +| --- | --- | +| 0 | the page is there — upsert this plan's row into the content that came back, then write the whole page | +| 4 | the page really does not exist yet — this is the **only** code that permits building it from the template | +| 7 | the key is invalid or lacks permission — stop, report the code and its cause, create no page and write nothing | +| 8 | any other API failure — same as 7: stop and report, and do not retry the same call unchanged | + +Why 7 and 8 abort: both mean the old content is unknown, not that the page is missing. Reading either as "not there yet" makes step 7 write a fresh template over a live directory, and every other plan's row is gone — the write carries no merge and no backup. `PLAN_{HASH}` is the opposite case: it is a content page belonging to this one plan, so step 6 rewriting it whole is correct. The distinction is the page, not the write. + +Completion condition: the `PLAN_CONTENTS` write names the `wiki-get` exit code it branched on, and no page was created on any code other than 4. ## Hard limits - Never output a code snippet. -- Never modify any file in the working directory. +- **Never modify any file in the working directory.** This limit is enforced in code where the CLI allows it: `jsc-hooks/hooks/write-guard.sh` in `stage` mode runs as a `PreToolUse` hook and blocks `Write`, `Edit` and `MultiEdit` while this stage's lock exists — the same lock state `jsc-hooks/hooks/sdlc-gate.sh lock plan` writes in step 1. **Only claude has `PreToolUse`.** Codex, copilot, antigravity and kiro never reach that hook, so on those four CLIs this line is the only thing holding the limit. - Never skip the decision tree and assume requirements. - Never stop questioning after one round; consensus is reached only under `references/consensus.md`, and the user says so. + +## What the work package gate no longer stops here + +`jsc-hooks/hooks/sdlc-gate.sh wp-check` used to block this skill outright while the repository still held an unsettled work package PR. For `plan` it is now a reminder that prints and lets the run through. The protection given up is the **work-in-progress cap**: nothing stops a new plan from starting while packages from the last one are still open, so plans can pile up faster than they are implemented. The reminder still names the unsettled package, and the same gate still blocks `analyze` and `maintain`, so the cap holds one stage later. `implement` was always waved through — that is the path that settles the open PR, and a gate that blocked it would lock itself. diff --git a/templates/analyze-contents.md b/templates/analyze-contents.md index 53478e0..25f1ed0 100644 --- a/templates/analyze-contents.md +++ b/templates/analyze-contents.md @@ -1,5 +1,7 @@ # 分析目錄 +> 寫入語意:一列代表一份分析頁。寫入前先讀回整頁,該分析頁已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。 + | 計畫名稱 | 分析頁 | HASH | 工作包 | 未完成項目 | 狀態 | | --- | --- | --- | --- | --- | --- | | {計畫名稱} | [[{計畫名稱}|ANALYZE_{HASH}]] | {HASH} | WP-01、WP-02 | {n} | 未完成 | diff --git a/templates/deliver-contents.md b/templates/deliver-contents.md index be1aca8..163edec 100644 --- a/templates/deliver-contents.md +++ b/templates/deliver-contents.md @@ -1,5 +1,7 @@ # 交付目錄 +> 寫入語意:一列代表一個工作包的交付。寫入前先讀回整頁,該工作包已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。 + | 計畫名稱 | 工作包 | 交付 | 交付型別 | 交付頁 | HASH | 存取庫 | 交付時間 | diff --git a/templates/maintain-contents.md b/templates/maintain-contents.md index 5c06110..5439a9f 100644 --- a/templates/maintain-contents.md +++ b/templates/maintain-contents.md @@ -1,5 +1,7 @@ # 維護目錄 +> 寫入語意:一列代表一個受維護的存取庫。寫入前先讀回整頁,該存取庫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。 + | 存取庫 | 維護方式 | 維護起始日 | 維護截止日 | 前次維護時間 | diff --git a/templates/plan-contents.md b/templates/plan-contents.md index 42d5556..1e853ee 100644 --- a/templates/plan-contents.md +++ b/templates/plan-contents.md @@ -1,5 +1,7 @@ # 計畫目錄 +> 寫入語意:一列代表一份計畫。寫入前先讀回整頁,該計畫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。 + | 計畫名稱 | 計畫頁 | 存取庫 | HASH | 狀態 | 建立時間 | | --- | --- | --- | --- | --- | --- | | {計畫名稱} | [[{計畫名稱}|PLAN_{HASH}]] | {owner}/{repo} | {HASH} | 未分析 | {yyyy-MM-dd} | diff --git a/templates/repo-contents.md b/templates/repo-contents.md index f4ca9db..9b267f5 100644 --- a/templates/repo-contents.md +++ b/templates/repo-contents.md @@ -1,5 +1,7 @@ # 盤點目錄 +> 寫入語意:一列代表一個存取庫的盤點。寫入前先讀回整頁,該存取庫已經有列就更新那一列,沒有才在文末附加一列,最後整頁寫回。禁止整頁覆蓋,也不得改動別人的列。 + | 存取庫 | 盤點頁 | HASH | commit sha | 盤點時間 | | --- | --- | --- | --- | --- | | {owner}/{repo} | [[{owner}/{repo}|REPO_{HASH}]] | {HASH} | `{sha}` | {yyyy-MM-dd} |