Files
jiantw83 b7b1d8bb01 docs(skills): 四支階段技能的目錄頁讀取與寫入敘述同步條列版面
What
- `skills/plan`、`skills/analyze`、`skills/implement`、`skills/maintain`:目錄頁的讀取敘述改成從 H2 區塊取值,寫入敘述從「單列 upsert」改成單一 H2 區塊 upsert,鍵補上內容頁頁名這個引數,並註明第四個引數是區塊檔。
- `skills/maintain`:讀寫的鍵改成該存取庫的 `{owner}/{repo}`,因為這個型別沒有內容頁。
- `references/behaviors.md`:四支技能的關鍵步驟、外部呼叫與可驗證跡象同步,跡象從「留下那一列」改成留下那一個 H2 區塊。
- `references/consensus.md`:查已答問題那一條補上問答目錄頁也是條列式版面、要從區塊取值而不是表格列。
- `references/stage-report.md`:目錄頁也算寫入那一段補上「改動一個區塊也算寫過那一頁」,並統一用 `CONTENTS` 這個型別餵進去。
- `README.md`:四支技能的流程敘述與 wiki 規則段同步,並補上五個目錄頁的版面規則、鍵的落點與各頁鍵欄的正確序號。

Why
- 範本已經改成條列版面,技能內文還寫著「那一列」,執行時就會照舊敘述組出表格列,跟工具的單一區塊 upsert 對不上。
- 讀取端的敘述沒跟著改,技能會拿表格的解析方式去讀一頁條列,既有紀錄一筆都認不出來。
- 呼叫少帶鍵這個引數,工具無從判斷要換掉哪一個區塊,同一筆會被當成新的附加上去。
- 行為清單是稽核與驗證的比對基準,敘述沒跟上,稽核會拿舊描述判合規。

How
- 四支技能的呼叫一律寫成 `wiki-contents.sh upsert {TYPE} {鍵欄} "{鍵}" {區塊檔} [{範本}]`,各頁的鍵欄序號照線上那一頁實際的欄位排法寫定。
- 完成條件與可驗證跡象改用區塊的說法,連結範例改成 `- {欄位名}:[{頁名}]({連結})` 的形態。
- 只改敘述與說明,不動任何腳本;轉檔與 upsert 的實作在別的存取庫。

Who
- 本存取庫四支階段技能,以及讀這幾份說明檔決定共識判定與階段回報寫法的流程。
- 稽核與驗證流程改拿新的行為清單比對。
2026-09-02 17:21:10 +08:00

31 KiB
Raw Permalink Blame History

name, description
name description
analyze SDLC analysis stage. Gate on capability tags enforced in code by sdlc-gate (analyze requires reasoning-max), pick a plan from PLAN_CONTENTS first, then confirm that plan's source branch and question until consensus per references/consensus.md. Analyze its user stories against the current state (working directory plus REPO_{HASH} inventory for reuse), then run WBS with a standalone delivery/handover WP-01, CPM estimates and TDD todos. Write wiki page ANALYZE_{HASH} with real sample data into the ANALYZE wiki repo, upsert every directory H2 block (ANALYZE_CONTENTS, PLAN_CONTENTS, REPO_CONTENTS - all in the separate CONTENTS repo) through jsc-gitea/tools/wiki-contents.sh with absolute wiki-url links, then close with tools/stage-report.sh - model tag verdict, worklog link, every wiki link written; logic only - never write code or modify files. Use after planning and before implementation; not for writing code (that is implement), and not before a plan exists in PLAN_CONTENTS.

analyze

Goal: create or extend the wiki analysis page ANALYZE_{HASH}. This skill is a logic-only stage: never output code, and never modify any file.

{HASH} = the shared wiki hash for {owner}/{repo}, computed by jsc-gitea/tools/hash-id (see jsc-gitea:wiki). It prints the full 40-character uppercase SHA-1 of its input — no truncation to 8 characters, no prefix rewrite. Never shorten it by hand: a shortened name points at a page nobody else writes to. REPO_{HASH} runs the same command over that repository's own {owner}/{repo}, so a multi-repository analysis holds one inventory page per repository.

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. All three are bullet-list directory pages, not tables: one H2 block per entry, the heading being that entry's content page actual name — the page the run itself wrote, which on the live wiki looks like ANALYZE_20260821_100552_104F0709, never a {TYPE}_{HASH} formula — and the fields a one-level bullet list under it, each written - {欄位名}:{值} in the order that page's template gives. Every directory entry 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. Steps 11 and 12 still run 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 analyze. This stage requires the reasoning-max capability tag. 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. Find out whether there is anything to analyze, before anything else costs the user a round. Both directory pages come out of the CONTENTS wiki repo (gitea.sh wiki-repo CONTENTS, resolved once and reused). Read PLAN_CONTENTS for plans whose status is the literal 「未分析」 (name and HASH), and read ANALYZE_CONTENTS for existing analyses. Read both as H2 blocks, not table rows: on PLAN_CONTENTS each ## PLAN_{HASH} heading is one plan, and a plan is 未分析 when its - 狀態: bullet reads 「未分析」; on ANALYZE_CONTENTS each ## ANALYZE_{HASH} heading is one analysis, with its 計畫名稱、分析頁、HASH、工作包、未完成項目、狀態 as the bullets under it. The two pages are independent — read them concurrently, and do not wait for the source branch: which branch the analysis reads from depends on the plan, so it is confirmed in step 4, after the target is known. Completion condition: you have listed every 未分析 plan with its name and HASH plus every existing analysis, or reported that both lists are empty and stopped.

  3. Let the user choose per jsc-ask:ask rules: extend an existing analysis or analyze a new plan. Build each option from one H2 block: the heading gives the page name and the HASH, and the option text comes from that block's bullets — 計畫名稱 plus 未完成項目 and 狀態 for an existing analysis, 計畫名稱 plus 建立時間 for a plan. State the impact scope on every option. Completion condition: the user has picked one option explicitly, and you have named the target — the existing ANALYZE_{HASH} page, or the plan the new analysis covers.

  4. Confirm the source branch for that target — the branch whose code counts as the current state, confirmed before any code is read and before the analysis page is written:

    1. Run git fetch --prune origin first — without it, every origin/... reference is stale cache. Then report the working directory's current branch, the remote branches available (git branch -r; never git branch) and whether the working tree is clean.
    2. Ask per jsc-ask:ask rules which branch the analysis reads from; state the impact scope on every option (analysing the wrong branch produces work packages for code that does not exist).
    3. The current state is always the remote branch origin/{source-branch}, never the local one. The working directory's HEAD must point at the same commit as origin/{source-branch}; behind, ahead or diverged all mean you would be analysing code that is not what the remote holds. Report the gap (git rev-list --left-right --count origin/{source-branch}...HEAD) and stop — this stage never switches branches, never stashes, never pulls and never touches the working tree. Rules in references/branch.md.
    4. Record the confirmed branch and the head sha of origin/{source-branch} on the analysis page. Completion condition: the user has confirmed the branch explicitly, and the origin consistency check above has passed; never infer the branch from the current checkout alone.
  5. Analyze the plan page's user stories one by one against the current state, questioning until consensus per references/consensus.md (the single authority for both planning and analysis): every answer produces the next question, and consensus needs both no output-changing unknown and the user's explicit confirmation. Never assume a missing detail, and never start the WBS while any item is still open. Current state means:

    1. Every file in the working directory, at the commit origin/{source-branch} points to (verified in step 4).
    2. Reuse an existing method or endpoint unless its logic cannot satisfy the requirement:
      • Check the REPO_{HASH} inventory page first, in the REPO wiki repo. Its {HASH} is hash-id over that repository's own {owner}/{repo} — one inventory page per repository, so a multi-repository analysis holds one REPO_{HASH} per repository and never one shared page. Re-inventory when the feature or endpoint is missing, or when the recorded commit sha differs from the current one.
      • Re-inventory MUST run as a sub agent: analyze the repository's features and endpoints, attach the current commit sha, write back to REPO_{HASH} with templates/repo-page.md, then upsert this repository's H2 entry block in REPO_CONTENTS with jsc-gitea/tools/wiki-contents.sh upsert REPO 2 {inventory page name} {entry file} templates/repo-contents.md. The entry file holds the heading ## {inventory page name}, a blank line, then one bullet per field in templates/repo-contents.md's order — 存取庫、盤點頁、HASH、commit sha、盤點時間 — each written - {欄位名}:{值} with a full-width colon. The third argument is the H2 heading, that is the inventory page's actual name — the very page this sub agent just wrote, matching the entry file's own heading byte for byte; do not derive it from a REPO_{HASH} formula, take the page name you actually saved. The 2 is the column number of the old table column that holds the content-page link — the 盤點頁 column — and it is used only for the automatic conversion: while the page on the wiki is still a markdown table, the script takes the last path segment of that column's link URL as the H2 heading, and once the page is bullet-list shaped the number is ignored. A wrong number is not harmless: the heading it converts to will not match the key, this repository's existing entry gets appended as a new one, one repository ends up with two blocks, and the old block is never updated again. Its 盤點頁 bullet holds the absolute URL from gitea.sh wiki-url {REPO repo} REPO_{HASH}, written as [{text}]({url}) — take that URL first, check it with jsc-gitea/tools/link-check.sh, and branch on both exit codes per "Every link comes from wiki-url, and is checked before it is written" below, because the entry must never carry an empty or dead link bullet.
      • Never hand-edit REPO_CONTENTS, and never touch a block belonging to another repository. Branch on the script's exit code — see "Contents pages are appended, never overwritten" below. REPO_{HASH} is a content page for one repository, so rewriting it whole is correct; the directory page around it is not.
      • For each reuse candidate, confirm the file path and method name first, then analyze whether its logic fits the requirement. Reject a candidate only for a stated reason, and record both the candidate and that reason in the analysis page's 複用決策 field.

    Completion condition: every user story has reached consensus under both conditions of references/consensus.md, and every reuse decision — reused, or rejected with its reason — is recorded in 複用決策.

  6. Run a Work Breakdown Structure (WBS): split the user stories into work packages, number them sequentially (WP-01, WP-02, ...) and mark dependencies. Completion condition: every user story on the plan page maps to at least one numbered work package, and every dependency edge is recorded in the WBS table's 相依 column.

  7. WP-01 is always the delivery/handover work package — see "Delivery package is WP-01" below. It stands alone, never merged into an implementation package, and every implementation package that consumes its spec depends on it. Completion condition: WP-01 is marked 交付 是 and holds only spec-shaped items, and every implementation package that consumes its spec names WP-01 in its 相依 column — or the no-handover case below is confirmed with the user and its reason is written on the page.

  8. Estimate every work package's effort in hours and days with the Critical Path Method (CPM), and mark the critical path. Draw the critical path as a mermaid gantt chart per references/cpm-chart.md — its date/duration rules are mandatory, not a suggestion; skipping them is how the Invalid date rendering failure happens. Completion condition: every work package carries an hours figure and a days figure, the critical path plus its total days are written on the page, and the gantt chart follows references/cpm-chart.md exactly (integer-hour durations, after chaining, no dateFormat X).

  9. Write the user story acceptance plan first, then the TDD todos. The acceptance plan goes under the section ## 使用者故事驗收計畫; every work package is then broken down under ## 測試計畫(TDD), with [ ] for an open item and [x] for a finished one.

    1. Every user story gets acceptance scenarios, and every scenario states its acceptance method as either 真實資料 or 邏輯推論. Scenario count follows the story's complexity: a simple story takes at least 1; a medium story at least 2, covering one main flow plus one boundary or error flow; a complex story at least 3, covering the main flow, the boundary flow and the failure flow.
    2. Every scenario states its input, its expected result, its data source and the work package it belongs to.
    3. Every TDD todo is one whole cycle: one seam, one failing test first, one minimal implementation, one green verification, and a post-green refactor where it is needed. Never split the red test, the minimal implementation and the green verification into separate todos. Seams and anti-patterns: references/tdd.md.
    4. Completion condition: every user story has scenarios under ## 使用者故事驗收計畫; every scenario carries an explicit acceptance method and data source; the scenario count matches the story's complexity; every work package has its own subsection under ## 測試計畫(TDD); every implementation work package holds at least one test-first [ ] todo; and every todo states its seam, its acceptance scenario, the behaviour the test asserts, the minimal implementation scope and how green is verified. A pure delivery package may use document-verification or sample-data-verification todos instead, and still states the test evidence or the review evidence that proves the spec is usable.
  10. Write the analysis page and its catalogue entries in one wiki pass. Apply templates/analyze-page.md to create or update the analysis page in the ANALYZE wiki repo and write it back via jsc-gitea:wiki; the page content is Traditional Chinese, exactly as the template dictates. Every link the page carries — the plan page, the inventory page, issues, anything external — is written as [{text}]({url}) and passes jsc-gitea/tools/link-check.sh before the write, per "Every link comes from wiki-url, and is checked before it is written" below. Both directory entries then go through jsc-gitea/tools/wiki-contents.sh, never a hand-edited page. Each entry file holds one H2 block: the heading, a blank line, then one bullet per field in that template's order, each written - {欄位名}:{值} with a full-width colon.

    • jsc-gitea/tools/wiki-contents.sh upsert ANALYZE 2 {analysis page name} {entry file} templates/analyze-contents.md — the block follows templates/analyze-contents.md (計畫名稱、分析頁、HASH、工作包、未完成項目、狀態) and the third argument is the H2 heading, that is the analysis page's actual name — the very page this step just saved (on the live wiki they look like ANALYZE_20260821_100552_104F0709, a timestamp plus 8 characters, so never build the key from an ANALYZE_{HASH} formula). The 2 is the column number of the old table column that holds the content-page link — the 分析頁 column — and it is used only for the automatic conversion: while the page is still a markdown table, the script takes the last path segment of that column's link URL as the H2 heading, and once the page is bullet-list shaped the number is ignored. A wrong number is not harmless: the heading it converts to will not match the key, this analysis's existing entry gets appended as a new one, one analysis ends up with two blocks, and the old block is never updated again. Its 分析頁 bullet holds the absolute URL from gitea.sh wiki-url {ANALYZE repo} ANALYZE_{HASH}, written as [{text}]({url}): take that URL after the analysis page is saved, check it with jsc-gitea/tools/link-check.sh, and branch on both exit codes per "Every link comes from wiki-url, and is checked before it is written" below, because the entry must never carry an empty or dead link bullet.
    • jsc-gitea/tools/wiki-contents.sh upsert PLAN 2 {plan page name} {entry file} templates/plan-contents.md — rebuild that plan's block from the block PLAN_CONTENTS already holds, change only the - 狀態: bullet to the literal 「已分析」, and keep every other bullet (including the absolute plan-page link) byte-for-byte as it was. The third argument is again the H2 heading: the plan page's actual name, copied from the heading of the block step 2 read off the page, never rebuilt from a PLAN_{HASH} formula. The 2 is the column number of the old table column that holds the content-page link — the 計畫頁 column — used only for the automatic table conversion, on the same terms and with the same duplicate-block consequence as the ANALYZE call above.

    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 entry, 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 ## ANALYZE_{HASH} block while the ## PLAN_{HASH} block 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 entry block 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 block, wiki-contents.sh returned 3, or tools/stage-report.sh exited 1. The analysis exists; what is missing is a directory entry 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

ANALYZE_CONTENTS, PLAN_CONTENTS and REPO_CONTENTS are shared directories in the CONTENTS wiki repo: every H2 block on them belongs to somebody's plan, analysis or repository, and this run reads none of those blocks from anywhere else. So every write to them is an upsert of one block — never a whole-page overwrite, and never a block this run does not own. jsc-gitea/tools/wiki-contents.sh upsert is the one way this stage does it: it resolves the CONTENTS repo, reads the whole page, converts a page still holding a markdown table into blocks first, replaces the block whose H2 heading matches the key and appends at the end when none matches, then writes the page back.

Branch on its exit code:

Exit What this step does
0 the block is in place — carry the updated or added word it printed into the stage report
1 the page content could not be assembled, or the write failed — report it as a failed write and go to step 11 as a failure. A page holding no matching block is not this code: with nothing to replace the script appends the block and exits 0
2 an argument was rejected (unknown type, bad key-column number, missing entry file) — fix the argument and run it again; nothing was written
3 no CONTENTS wiki repo is configured — stop and report JSC_WIKI_REPO_CONTENTS and JSC_WIKI_REPO as the two variables to set. The content page this run wrote is saved and stays saved
4 the directory page is absent and no template was passed. Every call in this stage already passes that page's template, so this code does not come out of this skill's call — a wrong template path is rejected as 2, not as 4. Seeing it anyway means the template file is not where the plugin puts it: confirm the plugin installation is complete and run it again. Never answer it by dropping the template argument
7 the key is invalid or lacks permission — stop and report the key problem; the script wrote nothing, which is what keeps every other block alive
8 any other API failure — stop and report that status, 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" would write a fresh template over a live directory, and every other block is gone — the write carries no merge and no backup. Content pages (ANALYZE_{HASH}, PLAN_{HASH}, REPO_{HASH}) are the opposite case: each belongs to one subject and lives in its own type's repo, so rewriting one whole is correct. The distinction is the page, not the write.

Completion condition: every directory-page write this stage made names the wiki-contents.sh exit code it branched on, and no directory page was created on any code other than 4.

One syntax. Every link this stage writes — on ANALYZE_{HASH} and REPO_{HASH}, in the ANALYZE_CONTENTS, PLAN_CONTENTS and REPO_CONTENTS entry blocks, in the stage report — is written as [{text}]({url}). The {url} is the absolute URL gitea.sh wiki-url {repo} {page} printed, used verbatim: never assemble a wiki path by hand, and never write a link as [[頁名]] or [[顯示文字|頁名]]. That form resolves only inside the wiki it sits in, and it fails without an error — the reader sees plain text or a dead link, so a wrong link is neither noticed nor fixable.

Run wiki-url after the content page it names is saved, and branch on its exit code; this is the same branching jsc-log:worklog runs over the same call.

Exit What this step does
0 use the URL it printed, verbatim
4 the page is not on the wiki, so the write that was supposed to create it has not landed — go back to that write (step 5.2 for REPO_{HASH}, step 10 for ANALYZE_{HASH}) and come here again only once the page is saved
5 the page exists but carries no html_url — stop and report it, and never assemble the URL by hand; a hand-built path is not the one Gitea serves
7 the key is invalid or lacks permission (HTTP 401/403) — stop and report the key problem. Never read this as exit 4: the content page is alive, and treating it as absent records a live page as one that was never written
8 any other API failure — stop and report that status, and do not retry the same call unchanged

Never let an empty string stand in for the URL. An entry whose link bullet is empty is a directory entry that points nowhere, the reader has no way to reach the page it names, and the next run replaces that block as if it were correct.

Checked before it is written. Collect every link the page or the entry block is about to carry, hand them all to jsc-gitea/tools/link-check.sh in one run — link-check.sh {網址}..., or the same URLs on stdin, one per line — and write only when that run exits 0. It prints one {OK|DEAD|SKIP}<TAB>{網址}<TAB>{說明} line per URL. The check goes through the API, never a web status code: a private repository's web URL answers 404 to a request carrying no key, so a status-code check marks live pages dead.

Exit What this step does
0 every link answers — write the page or upsert the entry block
1 at least one link is dead — write nothing, and report the DEAD lines to the user
2 usage error: not one URL was passed — pass the links and run it again
3 the list holds a Gitea URL but GITEA_HOST is unset — report it as a setting to fix and run it again, and never skip the check instead
7 Gitea authentication failed (401/403) — stop and report the key problem. Never read this as exit 1: an expired key makes live pages look absent, and a block rewritten on that reading loses the links that were fine

Completion condition: every entry block this stage upserted carries a URL that came out of a wiki-url run that exited 0, every page and block this stage wrote was cleared by a link-check.sh run that exited 0, and every non-zero code from either script was branched on as these tables say.

Delivery package is WP-01

A work package is a delivery/handover package when someone else — another person, another CLI, another team — has to act on its output. Interfaces, schemas, spec documents, acceptance criteria and sample payloads are delivery output; endpoint bodies and UI wiring are not.

  • WP-01 is the delivery/handover package, always first, always standalone. Pull every spec-shaped item out of the implementation packages and put it here. Never merge a spec into the package that implements it — merging removes the ability to hand the spec over early, which is the whole point.
  • Implementation packages depend on WP-01 when they consume its spec, and they are the backup queue: still numbered, still estimated, still on the page, just ranked after it.
  • Mark every work package as 交付 是 / 否 in the WBS table, and record WP-01's intended content type in the 交付型別 column when the user has already decided it (options in references/deliver-formats.md; the type is confirmed for real when implement starts that package).
  • Dependencies still win among the rest: never place a package before one it depends on. Within the same dependency level, delivery-shaped packages come first.
  • When nothing is genuinely deliverable (say, an internal refactor with no interface change), do not invent an empty WP-01: confirm with the user per jsc-ask:ask that this analysis has no handover output, then note the reason on the page and number the implementation packages from WP-01.

Sample data

Every sample value on the analysis page — request and response payloads, field values, config snippets, test fixtures — follows references/deliver-formats.md, which owns the source order, the labelling and the API-document layout. Record each value's origin in the WBS table's 資料來源 column. Completion condition: every sample value on the page carries a 資料來源 label of either 「真實:{source}」 or 「推論:無來源」, and no sample value contains personal data.

Hard limits

  • Never output a code snippet (file paths and method names are allowed).
  • 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 analyze 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.
  • Every entry file this stage builds — REPO_CONTENTS in step 5.2, ANALYZE_CONTENTS and PLAN_CONTENTS in step 10 — and the pending-file of step 11 are produced with a Bash heredoc or mktemp, in a temporary directory, never with the Write or Edit tool. That gate blocks on the stage lock alone and never looks at the path, so a Write of any of those four files is blocked by this stage's own lock and the stage cannot finish: no directory entry gets written and this stage's log content is never parked. All four are scratch input to a script, they live outside the working directory, and writing them this way keeps the limit above intact — nothing in the working directory is touched.
  • Never switch, create or clean branches in this stage; ask the user to do it.