Files
sdlc/skills/implement/SKILL.md
T
jiantw83 9e71474bfb feat(wiki): 五個目錄頁改走專用存取庫,補齊 wiki-url 退出碼分流
What:PLAN、ANALYZE、DELIVER、MAINTAIN、REPO 五個目錄頁改由 wiki-repo CONTENTS
解析並透過 wiki-contents.sh upsert 寫入,內容頁仍各走自己的型別。MAINTAIN 只有
目錄頁,整個型別都在專用存取庫。

Why:目錄頁與內容頁不再同庫,跨庫沒有 wiki 連結語法可用,一律改 wiki-url 的絕對
網址。原本四處取網址都沒有退出碼分流,5 被讀成空字串就寫出空連結,7 被讀成 4 就
把活著的頁當成沒寫成。

How:plan 與 analyze 會上階段鎖,而寫入閘門只看鎖不看路徑,所以流程要產的列檔與
暫存檔會被自己的閘門擋掉。兩支的限制段明寫這些檔一律用 heredoc 或 mktemp 產出。
wp-gate.sh 讀的是分析內容頁,維持走 ANALYZE,原地註明不得改成 CONTENTS。

Who:jsc-sdlc
2026-09-02 11:02:34 +08:00

34 KiB

name, description
name description
implement SDLC implementation stage. Gate on capability tags enforced in code by sdlc-gate (implement requires coding), settle every open work package's PR comments first, then claim a ready package before confirming the analysis page's source branch - it is both the worktree base and the PR target. Every already-open work package's PR comments get triaged via tools/wp-gate.sh check and fixed by sub agents only after jsc-ask consensus, never blocking an unrelated package; every candidate is gated in code by tools/wp-gate.sh check-deps before it reaches the options, and claiming records the package number via tools/wp-gate.sh claim so tools/wp-gate.sh owns keeps every session on its own package's PR. Claim a ready work package from ANALYZE_CONTENTS with a work ticket, confirm a delivery package's content type, then complete its TDD todos one at a time inside a worktree built from origin/{source-branch}, updating the wiki after every item, closing with the two side-by-side audits jsc-review code-review and jsc-review api-doc (the latter gated by swagger-detect.sh and explicitly skipped where the project has no Swagger support), one PR back to the source branch, a jsc-gitea pr-watch.sh poll that holds until that PR merges, a jsc-log:worklog entry per finished task, the chosen delivery document, an optional MAINTAIN_CONTENTS entry - every directory row upserted through jsc-gitea/tools/wiki-contents.sh into the separate CONTENTS wiki repo and linked by absolute wiki-url - and a tools/stage-report.sh report covering the model tag verdict, the worklog link, every wiki link written, the worktree and the three branches. Use when analysis is done and code must be written; not for planning or analysis.

implement

Goal: complete the analysis page's todos one by one; update the wiki status immediately after every completed item.

jsc-gitea/tools/hash-id prints the full 40-character uppercase SHA-1 of its input — no truncation to 8 characters, no prefix rewrite. Every {HASH} this stage builds carries that full length: the page names, the worktree path and the work ticket of step 6 alike. Never shorten one by hand.

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 and DELIVER_{HASH} in the one gitea.sh wiki-repo DELIVER resolves, while the directory pages ANALYZE_CONTENTS, DELIVER_CONTENTS and MAINTAIN_CONTENTS all 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 the page type's own variable. Because directory and content pages sit in different wikis, every directory row links its content page by the absolute URL from gitea.sh wiki-url {content repo} {page}; [[...]] resolves only inside one wiki and would dead-link from the directory.

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 13 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 implement. This stage requires the coding 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. Read the analysis pages, then settle every already-open work-package PR's comments — this keeps existing PRs moving and can clear a dependency for step 4, but it does not by itself decide which new package may start (step 4 does):

    1. Read ANALYZE_CONTENTS out of the CONTENTS wiki repo (gitea.sh wiki-repo CONTENTS, never the ANALYZE one), then read every analysis page it lists as unfinished out of the ANALYZE wiki repo. Keep this read: steps 3, 5 and 8 reuse it and never read the same pages again. Completion condition: for every unfinished analysis page you hold its WBS table, its PR column, its source branch and the repositories it names.
    2. Prefetch every open PR's state in one batch. For every work package holding a PR that is not marked merged, run jsc-sdlc/tools/wp-gate.sh check {owner}/{repo} {index} --since {the comment timestamp recorded in that PR column}. Drop --since when that package has no recorded timestamp yet. These calls do not depend on each other — run them concurrently and collect every result before you ask the user anything. The consensus rounds and the fixes that follow stay one comment at a time. Completion condition: every open PR has a recorded exit code and status= line.
    3. Exit 0 (status=merged) clears that package: remove its worktree (references/branch.md) and mark the package done on the analysis page.
    4. Exit 1 (status=open or status=closed-unmerged) means that package's own PR is not settled yet. This does not block picking a different, unrelated work package — SDLC implementation can run several independent packages in parallel; an unmerged PR only holds back packages that depend on it (step 4 checks that specifically), never the whole analysis page. Sub-steps 2.5 to 2.10 are the one comment round this skill owns; step 11.5 runs the same sub-steps for the PR it just opened.
    5. The PR has to be yours before you touch it. Run jsc-sdlc/tools/wp-gate.sh owns {owner}/{repo} {index} --wp {the work package number that PR column belongs to}. status=owned (exit 0) → carry on. status=foreign (exit 1) → leave that PR alone, name the work package the script says holds it, and let that package's own session handle it. status=unowned (exit 0) → carry on, and report that ownership could not be determined. status=usage (exit 2) → bad arguments; fix them and run it again, and change nothing on that PR until the script returns a verdict. Completion condition: the script has run for that PR and its verdict is reported.
    6. Agree on the fix before making it. Run the jsc-ask:ask decision tree over the comments the script printed — issue comments, review verdicts and inline comments alike — one option set per comment, each option stating its impact scope (which files it touches, whether it changes the package's TDD todos). Never fix a comment automatically: a reviewer's wording often allows two different fixes, and picking one silently costs another review round. Completion condition: the user has said how each comment is handled before any file is edited.
    7. Each round of fixes MUST run as a sub agent inside that package's own worktree, pushing to the same work branch, so the PR updates itself. Hand the sub agent the agreed decisions and let the comment text stay inside it — the main agent keeps only the decisions and the bookkeeping. Never open a second PR for the same package.
    8. Give every printed comment an outcome — fixed, no fix needed, or cannot fix. Reply to every handled comment with jsc-gitea/tools/gitea.sh comment-reply {owner}/{repo} {index} {issue|review|inline} {comment id} {reply file}. The reply states the outcome and the commit, file, or reason. Pure discussion and praise can be ignored only when you list the reason. Rules and the completion condition: jsc-meta/references/pr-report.md.
    9. Write the script's latest= value into that work package's PR column, appended after the existing PR link as #{index} 已處理留言 {ISO time}, and save the page back to the wiki. Next run passes it as --since, so handled comments stay handled. Reuse the existing PR column and add no new column; the analysis page's columns belong to analyze.
    10. One round of comment fixes is one finished task — call jsc-log:worklog now, under the Rules section's one-task-one-entry rule. Resolve that package's PLAN and ANALYZE wiki repositories and page URLs once and hand the resolved values to every call: the same package's later rounds reuse them, and only a change of repository forces a fresh resolution. Completion condition: the entry for this round is saved on LOG_{HASH} before the next round starts.
    11. Exit 2 (status=usage) means bad arguments — a malformed {owner}/{repo}, a missing index, or a --since value that is not the previous round's latest=. Fix the arguments and run it again. Completion condition: the rerun returned 0, 1 or 3; a usage error never counts as merged, settled or blocked.
    12. Exit 3 (status=missing-dep) means the gate could not decide (a missing dependency script, or the PR could not be found). Report it and stop — an undecidable gate never counts as merged.
    13. Completion condition: every work package holding an unmerged PR has had this run's comments triaged (fixed, no fix needed, or cannot fix), logged and reported; a package left unmerged after this does not block steps 3 and 4 for packages that do not depend on it.
  3. From the pages read in step 2.1, list what is unfinished: plan name, HASH, work package number, count of open items. A selectable work package satisfies both: unfinished, and not holding a work ticket. Dependencies are not judged here and never by eyeballing the 相依 column — step 4's wp-gate.sh check-deps is the only judge. Completion condition: you have listed every selectable work package, or reported that none is selectable and stopped.

  4. Every candidate passes the dependency gate before the user sees it — the gate lives in code, not in this text, and it judges one candidate at a time, never every open PR on the page:

    1. Run jsc-sdlc/tools/wp-gate.sh check-deps {owner}/{repo} {wp-number} --analyze ANALYZE_{HASH} once per candidate from step 3, before asking the user anything. Candidates do not depend on each other's verdict, so run these concurrently. The script re-reads the caller-specified analysis page and queries Gitea itself — it does not trust anything you already read or concluded, and it never guesses the page from the repository hash.
    2. Branch on each candidate's exit code. Exit 0 (status=ready) → put it in the option list. Exit 1 (status=blocked) → keep it out of the option list, and report which dependency work package's PR is not merged yet. Exit 3 (status=missing-dep) is an undecidable gate — report it and stop; never treat an undecidable result as either ready or blocked. Exit 2 (status=usage) → bad arguments or a missing --analyze; fix them and run that candidate again, and leave it out of the options until the rerun returns a verdict. Completion condition: every candidate from step 3 carries one of these verdicts, and only the ready ones remain.
    3. A dependency phrased as text pointing outside this analysis page (another plan, another analysis) cannot be checked by the script; it comes back listed separately as needing manual confirmation. Confirm it with the user before proceeding — an unchecked cross-page dependency is not the same as a cleared one.
    4. Let the user pick per jsc-ask:ask rules from the ready candidates only (options state open-item count and estimated effort). List delivery packages (交付 是) first — the analysis page makes WP-01 the standalone delivery package, so keep that order in the options. Completion condition: the user has named one ready work package, and any cross-page dependency of it is confirmed.
    5. Record the claim in code, so later steps can tell this package's PR from any other package's: run jsc-sdlc/tools/wp-gate.sh claim {owner}/{repo} {wp-number} --analyze ANALYZE_{HASH}, with the number read off the analysis page — the work package number is the only thing ownership is judged by (references/branch.md). One repository holds one claim at a time; wp-gate.sh check hands it back once the PR merges. Branch on the exit code: exit 0 (status=claimed) → proceed. Exit 2 (status=usage) → fix the arguments and run it again. Exit 3 (status=missing-dep) → the claim could not be recorded; report it and stop. No recorded claim, no work: without it the repository has no owner on record, owns answers unowned for every PR from then on, and every later session is waved through onto this package's PR. Completion condition: claim returned status=claimed; only then may you proceed.
  5. Confirm the source branch — it is also this stage's PR target:

    1. Run git fetch --prune origin, then take the source branch from the analysis page already read in step 2.1 and report it, along with the current branch and whether the working tree is clean.
    2. Show the recorded value and take a single-key confirmation. The analysis page already carries the answer, so this is a confirmation, not a fresh question: print origin/{source-branch} as both the worktree base and the PR target for this work package, and accept one key to confirm it. Two cases have no shortcut and go through the full jsc-ask:ask decision tree, each option stating its impact scope: the analysis page records no source branch, and origin/{source-branch} does not exist on the remote.
    3. A source branch missing from the remote is a stop-and-report condition, never a silent fallback. That rule (section 「來源分支在遠端找不到」), the remote-only basis and the uncommitted-changes rules: references/branch.md.
    4. Completion condition: the user has confirmed the source branch — by the single key, or through the decision tree in either exception case — and it is recorded in the analysis page's 「來源分支」 column.
  6. Generate a work ticket and claim the package on the page: format TICKET_{yyyyMMdd}_{HHmmss}_{HASH}. {HASH} = the shared wiki hash for {owner}/{repo}, computed by jsc-gitea/tools/hash-id (see jsc-gitea:wiki), so the ticket carries the same full 40 characters the page names do. The ticket is not a wiki page and no page-name rule applies to it, but it is still passed on whole: hand it to the session rename and write it into the column exactly as hash-id printed it. Rename the current session to the ticket name; skip the rename only when the CLI exposes no rename command. Write the ticket into the picked work package's ticket column (the zh-TW field 「工作證」) on the analysis page and save it back to the wiki. Completion condition: the ticket string exists, it is saved in that package's 「工作證」 column on the wiki, and you have reported it together with which branch applied — renamed, or skipped because this CLI has no rename command.

  7. A delivery/handover package confirms its content before its first todo:

    1. Ask per jsc-ask:ask rules what this delivery must contain. The options are fixed: 1. API 文件 and 2. 由使用者輸入. State the impact scope on each. Never assume the type, and never skip this — the answer decides what the whole package produces.
    2. Required fields, sample-data order and the new-versus-existing parameter marking: references/deliver-formats.md.
    3. Completion condition: the confirmed type is written into the analysis page's 交付型別 column and saved back to the wiki before the first todo starts.
  8. Create the worktree — before touching any code. Run git fetch --prune origin (this fetch is deliberate: the worktree must start from the newest remote state), then reuse the source branch confirmed in step 5 and the repository list read in step 2.1 — never read them off the page a second time. Ask per jsc-ask:ask rules how to handle the branch. A multi-repository analysis builds its worktrees concurrently; the repositories do not depend on each other. Path layout, the two git worktree add options, quoting, .git/info/exclude and the reporting duty: references/branch.md. Completion condition: every repository the analysis page names has a worktree built from origin/{source-branch}, and you have reported each worktree's path, checked-out branch, source branch and starting commit sha.

  9. Complete the work package's open items one at a time. Every item MUST run as a sub agent, working inside the worktree:

    1. One sub agent takes one item and follows the TDD loop: red before green, one vertical slice. Rules and anti-patterns: references/tdd.md (refactoring belongs to the review stage).
    2. The main agent keeps only the wiki bookkeeping: when the sub agent reports the item done, flip its [ ] to [x] on the analysis page and save to the wiki.
    3. A code comment states why the code is written this way; it never states where the work is documented. The tracking numbers this stage always holds — the work package number, the analysis page number, the TDD todo number, the source branch name and the PR number — stay out of every code comment; write the reason itself into the comment instead. Full list and the allowed exceptions: jsc-review/references/comment-scope.md. jsc-hooks/hooks/comment-scope.sh is wired on all five CLIs, but it fires at a different moment on each, so never treat it as one uniform warning: only claude scans the file the instant it is written, through PostToolUse; codex sweeps the whole worktree at the end of every turn, kiro at the next prompt submit, and copilot and antigravity only once when the session ends. Fix whatever it flags at once, then carry on with the same item. On the four CLIs that are not claude, none of that lands while the item is still being written, so the one pass that still arrives in time is the comment-scope.sh sweep that jsc-git:commit runs before it commits at step 11.1 — that sweep is what keeps a flagged comment out of the commit, and a line it flags is fixed there, never waived. Completion condition: the item's own diff holds no comment line carrying any of those numbers, every warning the hook printed for this item is fixed, and on a CLI other than claude you have reported that the step 11.1 sweep is the pass being relied on.
    4. Completion condition: every item of the package shows [x] on the saved analysis page, and each save happened before the next item's sub agent started.
  10. Two closing audits, side by side — a work package is finished only when both of them clear. Neither replaces the other, and neither waits for the other:

    1. Code review — when all items are done, call jsc-review:code-review over the work package's diff and wait for the verdict. The comment rule of step 9.3 is that review's own group, and jsc-git:commit sweeps the whole tree again at step 11.1, so this step runs no separate comment sweep of its own. Each round of fixes MUST run as a sub agent inside the same worktree. Completion condition: the review returned a passing verdict.
    2. API document audit — on a project that supports Swagger, the work package stays unfinished until its controller files are fully documented. Whether the project supports Swagger is decided in code by jsc-review/tools/swagger-detect.sh {worktree path}; run it and branch on its exit code instead of judging the project yourself:
      • 0 — the project supports Swagger. Call jsc-review:api-doc over the work package's changed controller files and wait for its verdict. What that audit checks belongs to jsc-review:api-doc; read the items there and keep no copy of them here.
      • 1 — the project does not support Swagger. Skip this audit explicitly and report the skip. A reported skip is a pass, never a failure.
      • 2 — bad arguments or a bad path. Fix them and run the script again; an undecidable detection is neither a pass nor a skip. A failing verdict is fixed, never waived: each round of fixes MUST run as a sub agent inside the same worktree, and jsc-review:api-doc runs again over the fixed files until it passes. Completion condition: swagger-detect.sh has run for this worktree and its exit code is reported, and either jsc-review:api-doc returned a passing verdict, or the skip is reported together with the exit code that caused it.
    3. Start both audits together and let them run side by side — they read the same diff and neither one's verdict changes the other's input, so the closing wait costs one audit, not two. Completion condition: both audits have returned, and both cleared — a pass from code-review, and either a pass or a reported skip from the API document audit.
  11. One work package finished → commit, push, PR back to the source branch, then hold on that PR until it merges:

    1. Call jsc-git:pr from inside the worktree, passing {source-branch} as the base branch. One package, one PR; the branch ladder and how the base is derived are in references/branch.md.
    2. Write the PR URL and number into that work package's PR column on the analysis page and save it back to the wiki, so the next run of this skill can find it (step 2).
    3. Run jsc-sdlc/tools/wp-gate.sh lock {owner}/{repo} {index} --wp {wp-number}. The lock is what makes step 2's gate hold across work sessions — a new session starts blocked until that PR merges — and --wp is what hangs this PR on the claim from step 4.5, so owns can keep other sessions off it. Leave --wp out and every session is free to touch this PR. Branch on the exit code: exit 0 (status=locked) → proceed. Exit 2 (status=usage) → fix the arguments and run it again. Exit 3 (status=missing-dep) → the lock could not be recorded; report it and stop, because without the lock the next session starts unblocked on a PR that has not merged. Completion condition: the script printed status=locked and named the work package.
    4. The work package is finished the moment its PR is open — call jsc-log:worklog now, under the Rules section's one-task-one-entry rule, reusing the PLAN and ANALYZE wiki repositories and page URLs resolved for this package in step 2.10. Completion condition: the entry is saved on LOG_{HASH} before the wait starts.
    5. Wait for the merge with jsc-gitea/tools/pr-watch.sh {owner}/{repo} {index} — it polls every 60 seconds (JSC_PR_WATCH_INTERVAL overrides), never times out, and never repeats a comment it already reported. Branch on its exit code:
      • 0 — the PR merged or was closed. Run jsc-sdlc/tools/wp-gate.sh check {owner}/{repo} {index} to tell merged from closed-unmerged, release the lock and hand the claim back. status=merged → remove the worktree (references/branch.md) and mark the package done on the analysis page; status=closed-unmerged → report it and stop, closed without merging is not finished. Comments printed in that final round still get an outcome per step 2.6 to 2.10.
      • 10 — new comments are waiting. Run step 2.5 to 2.10 over them (ownership, consensus, sub agent fixes, outcomes, timestamp, work log), then run pr-watch.sh again. Repeat for as many rounds as the reviewer needs.
      • 3 — the PR could not be found. Report and stop; a PR nobody can find never counts as merged.
      • 2 — bad arguments, or Gitea was unreachable on the very first poll. Fix the arguments or the environment and run it again; never fall back to eyeballing the PR page and calling it merged.
    6. Do not start another work package in this session. An unrelated package can still proceed, but in its own session with its own worktree; packages that depend on this one stay blocked until step 2 or step 11.5 sees it merged.
    7. Completion condition: the PR exists, its URL is saved on the analysis page and reported to the user with the table format in jsc-meta/references/pr-report.md, the lock existed (status=locked), the work log entry is saved, and pr-watch.sh has returned 0 with wp-gate.sh check confirming status=merged — or the run stopped on a reported 2, 3 or status=closed-unmerged.
  12. Deliver the document, then register for maintenance — one closing pass over the two questions this stage owes the user. A finished work package is a delivery, so always ask before producing it; never pick a format silently and never skip either question:

    1. Ask per jsc-ask:ask rules which delivery format to produce. The options are fixed: a DELIVER_{HASH} wiki page or a Gitea issue comment. State the impact scope on each (the wiki page lives beside the plan and analysis pages; the issue comment reaches whoever follows that issue).

    2. Both formats use the same structure — templates/deliver-page.md, in Traditional Chinese. Only the destination differs. Sample values and personal-data handling: references/deliver-formats.md.

    3. Wiki page: write DELIVER_{HASH} into the DELIVER wiki repo through jsc-gitea:wiki, where {HASH} comes from jsc-gitea/tools/hash-id over {owner}/{repo} plus the work package number (for example plugins/sdlc#WP-01), so each work package gets its own page instead of overwriting the previous one. That input is what keeps the pages apart, and the full 40 characters hash-id prints are what the page is named — hashing the repository alone, or shortening the result, puts two work packages on one page again. Then upsert this work package's row with jsc-gitea/tools/wiki-contents.sh upsert DELIVER 6 {HASH} {row file} templates/deliver-contents.md: the row follows templates/deliver-contents.md and the key is the HASH column, column 6, written exactly as the row file writes it. Its 交付頁 cell holds the absolute URL from gitea.sh wiki-url {DELIVER repo} DELIVER_{HASH} — run that after the delivery page is saved and branch on its exit code, 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 DELIVER_{HASH} write of this sub-step has not landed — write that page first and come here again only once it 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 delivery page is alive, and treating it as absent records a delivered work package as one that was never delivered
      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: a row whose 交付頁 cell is empty names a delivery nobody can open, and the next run replaces that row as if it were correct. Branch on the upsert's own exit code per "Contents pages are appended, never overwritten" below, and never hand-edit the directory page.

    4. Issue comment: confirm the issue number with the user (propose the one referenced by the work package or the PR; never guess), then post via jsc-gitea/tools/gitea.sh api POST /repos/{owner}/{repo}/issues/{n}/comments with the body passed in as a UTF-8 file — real newlines, never a literal \n.

    5. With the delivery produced, ask per jsc-ask:ask rules whether to register this project for maintenance: upsert this repository's row with jsc-gitea/tools/wiki-contents.sh upsert MAINTAIN 1 {owner}/{repo} {row file} templates/maintain-contents.md, the row built from templates/maintain-contents.md and the key column 1, the repository name. Required: repository {owner}/{repo}, maintenance method, start date. Optional: end date (NULL = maintain forever), last-maintained time. MAINTAIN has no content page — this directory page is the whole record — so branch on the exit code per "Contents pages are appended, never overwritten" below and never hand-edit it.

    6. Completion condition: the chosen delivery format has actually been produced and you have reported where it landed (wiki page name, or the comment URL); on the wiki-page format the wiki-url call returned 0 and its URL is the one in the 交付頁 cell, with any non-zero code branched on as sub-step 12.3 says; and the user has answered the maintenance question with a chosen registration saved on the wiki.

  13. Stage report — the last thing this stage does, including every early stop (the work package gate blocked, no work package was selectable, the source branch was missing from the remote, a wiki read or write failed). Run tools/stage-report.sh implement with:

    • one --page TYPE:{page} per wiki page this run wrote — --page ANALYZE:ANALYZE_{HASH}, --page DELIVER:DELIVER_{HASH}, --page CONTENTS:DELIVER_CONTENTS, --page CONTENTS:MAINTAIN_CONTENTS. 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;
    • --worklog and --worklog-heading pointing at the entry step 11.4 wrote — following the Rules section's one-task-one-entry rule, a stage that finished anything already has one. --pending-file {file} --log-hash {HASH} is the fallback for a stage that stopped before any task finished: it holds the content for the next jsc-log:worklog run, and held content is not a written log;
    • --worktree {path} --source-branch {name} --work-branch {name} --pr {url} — the script reads the commit count, the push state and whether the source branch exists on the remote by itself, so pass the names, not your own count.

    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 it names the worktree, all three branches and every wiki page this run wrote.

Contents pages are appended, never overwritten

DELIVER_CONTENTS and MAINTAIN_CONTENTS are shared directories in the CONTENTS wiki repo: every row on them belongs to somebody's work package or repository, and this run reads none of those rows from anywhere else. So every write to them is an upsert of one row — never a whole-page overwrite, and never a row 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, replaces the row whose key column matches and appends when none matches, then writes the page back.

Branch on its exit code:

Exit What this step does
0 the row is in place — carry the updated or added word it printed into the stage report
1 the write failed, or the page holds no markdown table — report it as a failed write and go to step 13 as a failure
2 an argument was rejected (unknown type, key column, missing row 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. A DELIVER_{HASH} page already written is saved and stays saved; the maintenance registration is not recorded anywhere else, so report it as unregistered
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 row 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 work package's row is gone — the write carries no merge and no backup. DELIVER_{HASH} is the opposite case: it is a content page belonging to one work package and living in the DELIVER repo, so writing it 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.

Rules

  • One finished task, one work log entry. A task is one of three things: one work package, one round of PR-comment fixes, or one standalone fix commit. Call jsc-log:worklog the moment one of them finishes — never let a stage end and then write a single catch-up entry, because by then the elapsed time, the token counts and the difficulties are gone. Every entry appends to the same LOG_{HASH} page, so one package that took five comment rounds leaves five entries. The PLAN and ANALYZE wiki repositories and page URLs are resolved once per work package and reused by every entry of that package; only a change of repository forces a fresh resolution. 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.
  • The work ticket is a mutex: always skip work packages that already hold a ticket; never take one over. Between the claim (step 4.5) and the ticket write (step 6), wp-gate.sh claim is what holds the package — the claim is recorded in code, so a second session sees it even before the ticket reaches the page.
  • Never batch wiki updates across items; one item, one update.
  • Sample data written into code or fixtures follows the analysis page's 資料來源 column, under the rules in references/deliver-formats.md.
  • Everything the skill writes out (wiki content, commit messages, PR descriptions) stays Traditional Chinese per the STE100 rule.

What the single-key source-branch confirmation gives up

Step 5.2 used to run a full decision tree that made the user say out loud that the source branch is also the PR target, every run. That explicit two-way confirmation is gone: a user who taps the confirm key now agrees to the analysis page's recorded branch as the PR target without being asked about the target separately. A stale or wrong PR target on the analysis page therefore reaches the PR unchallenged. The two exception cases in step 5.2 — no recorded branch, and a branch missing from the remote — are what remains of that guard, and they keep the full decision tree.