Initial publish v0.1.0: standalone workflow core (corpus + examples + guards)

This commit is contained in:
octopus
2026-09-15 08:41:51 +08:00
commit bb35e661b2
114 changed files with 20240 additions and 0 deletions
View File
View File
@@ -0,0 +1,71 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Shared Rule — Round-Boundary Context Compaction
> **Canonical source of truth.** This file is the single authoritative copy of
> the round-boundary compaction rule. It is injected **directly** (via
> `<!-- inject -->`) by two skills:
>
> - `review-code/SKILL.md`
> - `review-artifact/SKILL.md`
>
> Both inject tags are resolved by `injectShared`
> (`<harness-package>/src/tool/skill.ts`) at skill-load time. Do not place this
> inject inside a file that is *itself* injected (a nested inject) —
> `injectShared` is single-pass and does not resolve nested inject tags; they
> survive as literal `<!-- inject -->` comments and the procedure is silently
> lost.
>
> **When editing:** update this file only. Every consumer picks up the change
> automatically at load time — do not copy the text into any skill.
>
> This is a reference document, not a skill (no `SKILL.md` frontmatter), so the
> skill loader does not register it. It exists purely so maintainers have one
> place to edit.
## Why a canonical copy exists
Every multi-round review skill (`review-artifact`, `review-code`) needs the model to
read this rule inline at the point it dispatches rounds — a cross-file
reference would not be loaded into the model's context. So the text is
injected directly into both skills at load time. The injection is intentional;
this file exists to keep the two consumers on identical wording without a
copy-paste mirror.
## Canonical text
The block below is what each consumer receives at the inject site. The guard
referenced is the agent-initiated `compact` tool's minimum-turns guard,
configured at `compaction.agent.min_turns_since_compaction` (default **3**).
> **Context compaction (round ≥ 2)**: The Orchestrator is the only long-lived
> context across rounds; per-round dimension outputs, synthesis, and
> convergence tracking must not accumulate unbounded. Once Phase B has posted
> the commit status (all round state persisted in the synthesis comment + commit status)
> and before dispatching the next
> round in Phase A, the Orchestrator MUST run `compact` to summarize the
> conversation through the current round, then re-read
> the latest synthesis comment via `工单评论 API(见 TERMINOLOGY` to restore
> `current_round` and convergence tracking. The
> Synthesizer's return summary (verdict, per-dimension results, re-audit
> targets) is carried in the compacted conversation; if it was lost, re-read
> the synthesis comment's fenced JSON block as fallback before proceeding.
> Skip on round 1
> (single-pass reviews never loop). Each round then starts from a bounded
> context anchored on file state, not conversation memory.
>
> This round-boundary compaction is mandatory, but it is NOT exempt from the
> `compact` tool's `min_turns_since_compaction` guard
> (`compaction.agent.min_turns_since_compaction`, default 3): no such exemption
> exists in the tool implementation. If the tool rejects with
> `insufficient_turns`, accept the rejection and continue into the next round
> without compacting — do NOT retry or work around the guard. A round with too
> few turns since the last compaction simply keeps its context; the synthesis
> comment + commit status still bound what accumulates.
## Canonical note line
There is no `*(Shared rule …)*` italic note line in any consumer — the inject
directive resolves to this file's full content (this header included) at load
time. Older revisions of this document described a two-step "copy the
canonical text into each mirror" sync procedure; that procedure no longer
exists and must not be reintroduced.
+35
View File
@@ -0,0 +1,35 @@
# Shared Rule — Large Prompts via `prompt_file`
> **Canonical source of truth.** This file is the single authoritative copy of
> the large-prompts-via-prompt_file rule. The following skills reference this
> rule via an `<!-- inject -->` comment and MUST NOT maintain their own copies:
>
> - `review-code/SKILL.md`
> - `implement/SKILL.md`
> - `frontend/SKILL.md`
>
> (The `roadmap`, `requirements-elicitation`, `design`, and `plan-iterations`
> referencing skills were archived 2026-08-21, [org-internal #3072] phase 3 — see
> `.octopus/archive/skills/`; their copies of this directive moved with them.)
>
> **When editing:** update this file only. Each skill references this file via a
> one-line `<!-- inject -->` directive — there are no verbatim mirrors to sync.
>
> This is a reference document, not a skill (no `SKILL.md` frontmatter), so the
> skill loader does not register it.
## Why a canonical copy exists
The `task-tool-discipline` rule in `AGENTS.md` requires large prompts to be
written to a file and passed via `prompt_file`. This guidance applies to every
skill that dispatches sub-agents with potentially large prompts. A single
canonical source prevents drift and ensures the rule, file path convention
(`/tmp/octopus/<slug>.md`), and justification stay consistent.
## Canonical text
> **Large Prompts via `prompt_file`**
>
> Follow the `task-tool-discipline` rule in `AGENTS.md`: any prompt expected to
> exceed ~1 KB MUST be written to a file first and passed via the `task` tool's
> `prompt_file` parameter (e.g. `/tmp/octopus/<slug>.md`), never inlined.
@@ -0,0 +1,75 @@
# Shared Final Report Template
> Canonical final-report markdown template shared across review skills. Read
> on-demand by the Orchestrator when writing the final report in Phase E.
```markdown
# {REVIEW_TYPE} Final Report
- **Project**: {slug}
- **Rounds completed**: {N}
- **Final verdict**: {PASS | WARN | FAIL}
- **Converged**: {true | false — reason}
## Dimension Summary
| Dimension | Final Verdict | Pass Rate | R1→RN |
| --------- | ------------- | --------- | ----- |
| ... | | | |
## Outstanding Findings
### Accepted Risks (for tech-debt issue creation in verify Phase 5.5)
> verify Phase 5.5 会将下表每项 Accepted Risk 创建为 Gitea issue
> label `tech-debt`,标题含 `TD-NNN`)。issue 集即技术债登记表——
> 不再有 `tech-debt.md` 文件或 `{slug}/tech-debt` wiki 页。
| Finding ID | Severity | Description | Recommended Reactivation Trigger |
|-----------|----------|-------------|--------------------------------|
| {ID} | HIGH/MEDIUM/LOW | {描述} | {可客观判断的条件} |
> Note: the Severity column uses the **TD-registry severity scale**
> (HIGH/MEDIUM/LOW → Priority/High|Medium|Low labels, feeding verify Phase
> 5.5's `## TD 登记` rows and promotion labels), NOT the review-finding
> severity scale (BLOCKER/MAJOR/MINOR/INFO) — reviewers assign the TD
> severity directly when accepting a risk.
### Other Open Items
...
## Review History
| Round | Verdict | Blockers | Majors | Minors |
| ----- | ------- | -------- | ------ | ------ |
| ... | | | | |
## Round-1 Findings Summary
> Mandatory for `review-code` (Phase E step 2.55): one row per round-1
> finding, produced by the round-1 Synthesizer as
> `.octopus/runs/{slug}/reviews/{stage}/round1/findings-summary.md`
> (`_shared/review-pipeline-phases.md` Phase B step 3 "Round-1 findings
> digest") and copied verbatim by the Orchestrator — the Orchestrator does
> NOT read the findings JSONs — before they are deleted at archive-at-close.
> This table is the durable record retro M7 reads. Other targets MAY include
> it when round-1 findings data exists.
| Dim | Finding ID | Severity | Summary |
| --- | ---------- | -------- | ------- |
| ... | | | |
```
## Placeholders
- `{REVIEW_TYPE}` — the value the skill's Phase Bindings declares, used
verbatim in the title (`# {REVIEW_TYPE} Final Report`): "Code Review"
(review-code — human label; its commit-status context is separately fixed
to `pipeline/review-code`), or the machine ids `audit-process` /
`review-dag` (review-artifact targets — same id feeds the
`pipeline/{REVIEW_TYPE}` commit-status context; see review-artifact Phase
Bindings). (The design-space/plan targets were archived 2026-08-21,
[org-internal #3072] phase 3.)
- `{DIMENSION_COUNT}` — 10 (code review; 9 in standalone-bugfix mode), 3 (review-dag single gate), or 10 (process audit); used in
the skill's SKILL.md reference text, not inside this template body
@@ -0,0 +1,168 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Shared Orchestrator Rules (Mandatory)
> Canonical Orchestrator behavior rules shared across review skills. Read
> on-demand by the Orchestrator. These rules constrain the Orchestrator's behavior;
> violating any of them invalidates the review.
1. **No scope narrowing**: The Orchestrator MUST NOT add qualifiers to the
reviewer prompt that discourage thoroughness. Forbidden phrases include:
"Focus on the most important issues", "Prioritize critical findings",
"Limit to top N items", "Be concise", or any wording that suggests the
reviewer should self-censor.
2. **Standardized prompts only**: Every reviewer receives the prompt from the
template field-for-field. The Orchestrator MUST NOT modify the template
structure, add constraints, or remove required sections.
3. **Quantitative verdicts only**: A PASS verdict REQUIRES pass_rate = 1.0
with quantitative_summary populated. The Orchestrator MUST NOT promote a
WARN to PASS based on subjective judgment. If pass_rate < 1.0, the
dimension is at best WARN; if pass_rate < 0.7 or any BLOCKER finding exists,
the dimension MUST be FAIL. INFO findings do NOT reduce `pass_rate` and do
NOT affect the dimension verdict — a dimension whose only findings are INFO
is PASS. INFO is dispositioned (`TD-candidate` / `DISCARDED`) at synthesis
and never enters the revision loop (see
`_shared/review-pipeline-phases.md` Phase D, "Severity handling — INFO
disposition").
4. **Synthesis is aggregation, not reinterpretation**: The Orchestrator MUST NOT
soften, rephrase, or downgrade reviewer findings during synthesis.
If a reviewer writes "BLOCKER", it stays "BLOCKER" in synthesis.
5. **Anonymous findings preservation**: If two reviewers independently flag
the same issue from different dimensions, preserve both entries. Do not
deduplicate by discarding one — cross-dimension agreement is itself a
signal.
6. **File-based communication**: Do not inline large text blocks in prompts.
Use wiki page paths. Reviewer output goes to the skill's configured findings
sink (see `_shared/review-pipeline-phases.md` "Output model — two findings
sinks"): either a Tier 1 local file
`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`
(validated by `reviewer-output.schema.json`) for migrated skills like
`review-code`, or an issue comment via `工单评论 API(见 TERMINOLOGY` (Pattern
5) for unmigrated skills. Tier boundary: `core/rules/two-tier-artifacts.md`.
Either way, reviewer output does NOT travel as agent reply text.
7. **Auditor independence**: Reviewers are read-only Explorers. Only the
{REVISION_ROLE} (a separate Worker, run in a different phase) may edit files.
The Orchestrator MUST NOT let a reviewer both judge and fix.
8. **Subagent dispatch contract** (binding — omitting `subagent_type` is the
#1 review-skill dispatch failure): every `task` tool call MUST resolve an
agent type. The skill prose uses role names ("Orchestrator", "Architect",
"Reviewer", "Developer", "Analyst", "Planner", "Verifier", "Remediator", "Synthesizer") — those are
`role` values, NOT agent types. Map them to the `task` tool parameters as
follows:
| Skill role | `role` param | `subagent_type` param |
| ----------------- | -------------- | --------------------- |
| Reviewer | `Reviewer` | `explorer` |
| Orchestrator | `Orchestrator` | `worker` |
| Architect | `Architect` | `worker` |
| Developer / Tester| `Developer` | `worker` |
| Analyst | `Analyst` | `worker` |
| Planner | `Planner` | `worker` |
| Verifier | `Verifier` | `worker` |
| Remediator | `Remediator` | `worker` |
| Synthesizer | `Synthesizer` | `worker` |
The `agent` type is declared in `roles/*.yaml` for the five roles that
have a role YAML — `producer.yaml` (worker), `reviewer.yaml` (explorer),
`tool.yaml` (explorer), `verifier.yaml` (worker), and `coordinator.yaml`
(worker; the pool coordinator session — not a task-dispatch role, so it
does not appear in the table above). The task tool derives
`subagent_type` from `role` automatically when `subagent_type` is omitted
AND the role's `agent` field is defined. All other roles in the table above
(Orchestrator, Architect, Developer, Analyst, Planner, Remediator,
Synthesizer) have NO role YAML and therefore MUST pass `subagent_type`
explicitly — never pass only `role` + `description` and assume a default.
Concrete example (dispatching a reviewer):
```
task(
description: "Review CMP dimension",
role: "Reviewer",
subagent_type: "explorer", // optional here (derivable from role), shown for clarity
permissions: [{ permission: "read", pattern: "*", action: "allow" }, ...],
temperature: 0.1,
prompt: <filled reviewer template>
)
```
Forbidden: a `task` call that names the role in prose ("Spawn the
Architect") but omits BOTH `subagent_type` and a defined-`agent` `role` —
it fails with `TaskMissingSubagentType`.
## Default Explorer Permissions
All review and audit skills that spawn Explorer sub-agents use the same
read-only BASE permission set. The canonical base template is:
```
permissions: [
{ permission: "read", pattern: "*", action: "allow" },
{ permission: "glob", pattern: "*", action: "allow" },
{ permission: "grep", pattern: "*", action: "allow" }
]
```
Each skill references this base template via `core/skills/_shared/review-orchestrator-rules.md`
rather than inlining the JSON array. This centralization ensures the base
permission surface stays consistent across all review/audit skills.
**Tier-1 local exception** ([org-internal #1988]): migrated skills that write raw per-dimension
findings to `<runs-root>/**` (e.g. `review-code`) add ONE scoped `edit` allow
to the base set — `{ permission: "edit", pattern: "<runs-root>/**", action:
"allow" }` — so the reviewer can write its OWN finding file. This is a
documented exception to the read-only base, not a divergence: it does NOT grant
edit on the code under review, so rule #7 (auditor independence) holds. See
`_shared/review-pipeline-phases.md` Phase A step 4 and `review-code/SKILL.md`
Phase A step 3 for the inline extended array.
Residual risk — accepted trade-off (TD/STD-F004, [org-internal #2688]): the grant is scoped
to `<runs-root>/**`, wider than the reviewer's own findings file. Per-file
narrowing was considered and rejected: findings paths are round- and
dimension-dependent (`{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`),
which makes per-file permission patterns brittle at spawn time, and `runs/**`
holds only transient Tier 1 artifacts (digest-summarized at archive). The
residual exposure — a misbehaving reviewer could overwrite a sibling stage's
Tier 1 files — is recorded here as accepted; auditor independence is
unaffected (code under review lives outside `runs/`).
### No bash for Explorer sub-agents (deliberate boundary, [org-internal #2115] RC4 / [org-internal #2126])
Explorer sub-agents (Reviewer / auditor / Tool roles) have **no bash**. The
`explorer` agent is built deny-by-default at
`<harness-package>/src/agent/agent.ts` (`"*": "deny"` + explicit `read`/`glob`/
`grep` allows + `bash: "deny"`), so the `permissions` array above is read-only
by construction — a reviewer cannot run commands, mutate state, or escape its
read surface even if a prompt tried to grant bash. This is a deliberate
security boundary that upholds rule #7 (auditor independence): a reviewer must
never alter the artifact under review or its environment.
**Reviewer guidance**: do everything with `read`, `glob`, `grep`, and the
`gitea-rest` skill (curl against `/api/v1`; `gitea-mcp` is retired). If a
review dimension seems to require running a command
(executing a build, running a test, computing a metric), that is the
Synthesizer / Verifier's job (Worker sub-agents, which DO have bash via the
`worker` agent defaults) — not the Reviewer's. Report the need in findings
instead of attempting bash.
> **[org-internal #2126] Task 2 conclusion — keep + document.** The `deny **` pushed per-tool
> at `<harness-package>/src/session/prompt.ts` is the **session tool-enable /
> disable map** (it disables `todowrite` / `task` / `experimental.primary_tools`
> for subagents and allows forwarded MCP tools). It is NOT the source of the
> Explorer bash deny — that comes from the `explorer` agent config above, which
> is the correct place. Relaxing it would break auditor independence; worker-
> class sub-agents already have bash wherever the pipeline needs to run
> commands. Decision: **keep the deny, document it here** (this section).
## Placeholder
- `{REVISION_ROLE}` — "Developer" (code review), "Analyst" (review-dag single
gate), or "Remediator" (process audit). The Worker role that revises
artifacts between rounds. (The Architect/Planner revision roles belonged to
the archived design-space / iteration-plan targets, [org-internal #3072] phase 3.)
@@ -0,0 +1,493 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Review Pipeline Phases (Canonical)
This document is the **single source of truth** for the shared Phase A→E
review/audit pipeline flow. Individual review and audit skills bind their own
parameters and reference this doc rather than duplicating the shared mechanics.
**Terminology**: "reviewer" and "auditor" are interchangeable — `audit-process`
legitimately uses "auditor"/"Re-audit" terminology. All generic descriptions
use `{REVIEWER_OR_AUDITOR}`.
**Output model — two findings sinks** ([org-internal #1988] Tier 1 migration): raw per-dimension
reviewer findings have ONE of two sinks, chosen per skill. The **synthesis**
(human gate) and **commit status** are ALWAYS Tier 2 (Gitea) regardless of the
findings sink.
| Sink | Skills | Reviewer writes findings to | Synthesizer reads findings from |
|---|---|---|---|
| `tier1-local` (migrated) | `review-code`, `review-artifact` (live targets: review-dag, audit-process; the roadmap/design-space/plan targets were archived 2026-08-21, [org-internal #3072] phase 3) | `<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json` (validated by `core/schemas/reviewer-output.schema.json`) | the local finding files (no Gitea call) |
| `tier2-comment` (fallback) | _(none — all targets migrated)_ | Gitea PR/issue comment via `工单评论 API(见 TERMINOLOGY` (Pattern 5) | `工单评论 API(见 TERMINOLOGY` (Pattern 3) |
Migration complete — every review/audit skill now outputs `tier1-local`; the
`tier2-comment` row remains as the documented fallback (boundary rule:
`core/rules/two-tier-artifacts.md`; a skill without `<runs-root>/`
write access falls back to comment-based output unchanged). A `tier1-local`
reviewer additionally
appends an `index.json` row per finding file (Tier 1 manifest, see
`core/templates/runs-layout.md`).
**Tier 2 outputs (always)**: synthesis is posted as a **Gitea PR/issue comment**
with a fenced ` ```json ` block — except for the `audit-process` target, which
has no source issue and posts the synthesis as a **wiki page**
`audit/{date}/round{N}/synthesis` instead (see `review-artifact/SKILL.md` process target
Output Artifacts); status tracking uses **REST commit status**
(`pipeline/{REVIEW_TYPE}` context). The full Gitea output pattern is documented
in `_shared/gitea-write-patterns.md`.
---
## Phase A — Parallel {REVIEWER_OR_AUDITOR_NAME}
**Orchestrator actions**:
0. **Move the source issue to the `review` column** on the Pipeline Stages
board per `_shared/gitea-write-patterns.md` Pattern 7.5. Skip if no
source issue exists. The review stage is a single board column covering
all review targets (DAG single gate / code review / audit-process).
0.5. **Initialize the review-status file** (round 1 only). The Orchestrator
creates `<runs-root>/{slug}/reviews/{stage}/status.json` before dispatching
round 1, with the full schema-required field set
(`core/schemas/review-status.schema.json`): `slug`, `started_at`
(RFC 3339 timestamp), `current_round: 1`, `max_rounds` (per-skill binding —
`review-artifact` = 3, `review-code` = 2 default / 3 high-risk —
`<instance-root>/workflow-routing.yaml` `review.max_rounds`), `converged: false`,
`history: []`. This
step is skipped on rounds ≥ 2 — the file already exists and the Synthesizer
appends to it in Phase B. Initialization is the Orchestrator's sole
responsibility; the Synthesizer never creates the file. The Orchestrator
also appends an `index.json` row for the file (`type` = `review-status`,
`stage` = `{REVIEW_TYPE}`, `produced_by` = `Orchestrator`, `schema` = the
review-status `$id`).
1. **Tool-availability probe** (defense-in-depth, [org-internal #2115] RC1 / [org-internal #2126]). Before
dispatching any {REVIEWER_OR_AUDITOR}, verify reviewers will be able to read
wiki artifacts (requirements / design / plan / prior-review reports). A
reviewer that cannot read its inputs aborts or hallucinates — catch this ONCE
in the Orchestrator instead of letting {DIMENSION_COUNT} parallel subagents
each rediscover the gap:
- **Primary path**: confirm `gitea_wiki__*` tools are registered (the
Orchestrator checks its own tool catalog; these tools are forwarded to
reviewer subagents when registered — [org-internal #2116] raised the tool cap 50→100 so
they are present in normal runs). If available → continue to step 2.
- **Fallback path** (when `gitea_wiki__*` is NOT registered, e.g. a
cross-repo scenario [org-internal #2115]): verify the **local wiki clone** at
`<wiki-clone-root>/{repo}-wiki/` is readable —
`glob(pattern="*.md", path="<wiki-clone-root>/{repo}-wiki/")`. If the clone
is readable → continue to step 2; reviewers use the local clone (see
`_shared/gitea-read-patterns.md` Pattern 1 fallback note and the "Tool
Availability & Path Resolution" section of this skill).
- **Both unavailable → FAIL FAST**: post a `failure` commit status (state
`"failure"`, context `pipeline/{REVIEW_TYPE}`, description naming the
missing capability) and abort — do NOT enter parallel dispatch. Report the
gap so the environment can be fixed (register the wiki MCP server, or
populate `<wiki-clone-root>/{repo}-wiki/`).
This probe is belt-and-suspenders: RC1 (tool cap, [org-internal #2116]) already fixed the
primary cause of missing `gitea_wiki__*` in subagents. It exists so a future
environment regression fails gracefully with one clear message instead of N
silent subagent aborts.
2. Post an initial pending commit status via REST:
```
POST /api/v1/repos/Octopus/octopus/statuses/{SHA}
state: "pending"
context: "pipeline/{REVIEW_TYPE}"
description: "Round {N}: {DIMENSION_COUNT} reviewers dispatched"
```
(See `_shared/gitea-write-patterns.md` Pattern 8.)
3. Spawn **all {DIMENSION_COUNT} {REVIEWER_OR_AUDITOR}s in parallel**. Each
receives:
- The standardized prompt with dimension-specific values filled in.
- Agent type: Explorer.
- Permission: Explorer read-only (see
`core/skills/_shared/review-orchestrator-rules.md` — Default
Explorer Permissions).
- Temperature: 0.1 (deterministic output).
4. Wait for all {REVIEWER_OR_AUDITOR}s to complete. Each reviewer writes its
dimension result to the skill's configured findings sink:
- `tier1-local` (e.g. `review-code`, `review-artifact`, `audit-process`): write the JSON to
`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`
(validated by `core/schemas/reviewer-output.schema.json`). The reviewer
is granted scoped write to `<runs-root>/**` (read-only everywhere else —
this emits the reviewer's OWN output, it does not touch the artifact under
review, so it does not violate rule #7 auditor independence).
- `tier2-comment` (fallback): post an issue comment via
`工单评论 API(见 TERMINOLOGY` (Pattern 5, `_shared/gitea-write-patterns.md`)
with a fenced ` ```json ` block on the target PR (pipeline mode) or issue
(standalone mode). The comment is labeled `**{DIMENSION_CODE} review — round {N}**`.
After all reviewers complete, for `tier1-local` the Orchestrator appends one
`index.json` row per finding file (type `review-findings`, stage
`{REVIEW_TYPE}`, produced_by `Reviewer`, schema = the reviewer-output `$id`).
5. If a {REVIEWER_OR_AUDITOR} fails (crash / timeout), retry once. If still
failed, mark that dimension as `UNRESOLVED` in the synthesis and continue.
Record it for deferred retry at the start of the next round.
---
## Phase B — Synthesis (Delegated)
> **Context optimization**: Phase B is delegated to a Synthesizer (Worker) to
> prevent the Orchestrator's context from accumulating N dimension finding
> Read results (up to N × 50KB per round). The Synthesizer reads all dimension
> findings in its own context, posts the synthesis comment + commit status,
> and returns a compact summary (≤ 2KB). The Orchestrator never reads
> dimension findings directly.
**Orchestrator actions**:
1. Fill the synthesis task template
(`core/skills/_shared/synthesis_task.md`) with:
- Round number. A round-1 dispatch carries the template's step-8
"Round-1 only — findings digest" duty (the Synthesizer writes
`round1/findings-summary.md`, consumed verbatim at Phase E — see the
digest bullet in step 3 below); verify it is present in the filled
template.
- Target PR number (pipeline mode) or issue number (standalone mode).
- List of UNRESOLVED dimensions (reviewers that crashed/timed out).
- Commit SHA and review type.
- For `tier1-local` skills (all review/audit): write the filled template to
`<runs-root>/{slug}/reviews/{stage}/round{N}/task-synthesizer.md` (Tier 1
synthesis prompt; append an `index.json` row, type `synthesis-prompt`). The
`/tmp/octopus/` embryo is NOT used — Tier 1 owns the prompt.
- For `tier2-comment` skills: write the filled template to
`/tmp/octopus/synthesis_task_{slug}_round{N}.md` for `prompt_file` use; the
authoritative copy is published as a Gitea wiki page at
`{slug}/reviews/{stage}/round{N}/task-synthesizer`.
2. Dispatch the **Synthesizer** (Worker) via the `task` tool:
- `role`: `Synthesizer`
- `subagent_type`: `worker`
- `prompt_file`: the synthesis-prompt path from step 1 (Tier 1 local path for
`tier1-local` skills; `/tmp/octopus/...` for `tier2-comment` skills)
- `permissions`: read + write + edit + bash + glob + grep (all allow)
- `temperature`: 0.1
- Foreground (the Orchestrator waits for completion).
3. The Synthesizer:
- Reads all dimension findings from the skill's configured sink:
- `tier1-local`: read the finding files at
`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-*.json` (in its
OWN context, not the Orchestrator's — no Gitea call).
- `tier2-comment`: read all dimension comments via `工单评论 API(见 TERMINOLOGY`
(Pattern 3, `_shared/gitea-read-patterns.md`) on the target PR/issue.
- **Post-check finding-id uniqueness (MANDATORY)**: verifies that `id`
values are unique across ALL findings files of the round (including
every `-r{k}` slot file) — the schema's `uniqueItems` does NOT enforce
this. On a duplicate id, keeps both findings, notes the collision in
the digest and marks the affected dimension WARN (per step 1b of
`_shared/synthesis_task.md`).
- Computes synthesis following `core/schemas/synthesis.schema.json`.
- Posts synthesis as a PR/issue comment via `工单评论 API(见 TERMINOLOGY`
(Pattern 5, `_shared/gitea-write-patterns.md`) — markdown summary + fenced
` ```json ` block matching `core/schemas/synthesis.schema.json`. (This
is the Tier 2 human-gate output — it stays on Gitea for both sinks.) For
the `audit-process` target (no source issue), post the synthesis as a wiki
page `audit/{date}/round{N}/synthesis` instead of a PR/issue comment.
- Posts a commit status via REST with context
`pipeline/{REVIEW_TYPE}` (Pattern 8, `_shared/gitea-write-patterns.md`):
`state: "success"` for PASS/WARN, `"failure"` for FAIL.
- Appends one `history[]` entry for this round to the run's review-status
file `<runs-root>/{slug}/reviews/{stage}/status.json` (validated by
`core/schemas/review-status.schema.json`) — `round`,
`overall_verdict`, `blockers`, `majors`, `minors` — the durable per-round
audit history (defined by `core/schemas/review-status.schema.json`
`history[]`; mirrors the audit-process checklist TRC 10.4), and updates
the file's `current_round` to this round number. (The Orchestrator initializes `status.json` before
round 1 — see Phase A step 0.5 — with the full required field set `slug` /
`started_at` / `current_round` / `max_rounds` / `converged` / `history`.
The Synthesizer only appends `history[]` and updates `current_round`; it
never re-creates the file.)
- **Round-1 findings digest (round 1 only)**: for skills whose final report
carries a Round-1 Findings Summary (`review-code` Phase E step 2.55 and
any target adopting the template section), the round-1 Synthesizer is the
PRODUCER of that table: it writes
`<runs-root>/{slug}/reviews/{stage}/round1/findings-summary.md` — the
rendered `## Round-1 Findings Summary` table (columns
`| Dim | Finding ID | Severity | Summary |`, one row per round-1 finding;
Summary = one-line 摘由/root cause) — distilled from the round-1 findings
it just read in its own context (append an `index.json` row, type
`review-synthesis` — Synthesizer-produced synthesis artifacts; the
round-verdict return summary `synthesis-return.md` keeps the generic
worker-report type `other` per
`_shared/worker-report-persistence.md`). This duty is wired into the
Synthesizer's operative prompt as step 8 of
`_shared/synthesis_task.md` ("Round 1 only — findings digest"). The
Orchestrator at Phase E copies this file
verbatim into the final report; it does NOT read the findings JSONs
(step 4 below) — the digest file is the wired producer→consumer path.
- Returns a compact summary (≤ 2KB) with verdict, per-dimension results,
and re-audit targets.
4. The Orchestrator receives the summary and proceeds to Phase C or Phase E
based on `overall_verdict`. The Orchestrator does NOT read dimension
comments or the synthesis comment directly — all decision-relevant data
is in the Synthesizer's return summary.
5. Present the Synthesizer's return summary to the user in chat.
---
## Phase C — {REVISION_ROLE} Revision
If `overall_verdict` is PASS, skip to Phase E.
Otherwise:
**Orchestrator actions**:
1. Prepare the {REVISION_ROLE}'s input as a structured task, published as a
Gitea wiki page at `{slug}/reviews/{stage}/round{N}/task-{ROLE}` (Pattern 1,
`_shared/gitea-write-patterns.md`). For the `audit-process` target, the
process target's Phase Bindings override the wiki namespace to
`audit/{date}/` — publish to `audit/{date}/round{N}/task-{ROLE}` instead
(see `review-artifact/SKILL.md` process target).
This must contain:
- Paths to all files that need revision.
- Reference to the synthesis comment on the PR/issue (posted in Phase B).
- References to the dimensions that have open BLOCKER, MAJOR, or MINOR
findings, via the skill's findings sink:
- `tier1-local`: paths to the Tier 1 finding files
(`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`).
- `tier2-comment`: references to the individual dimension issue comments.
Dimensions whose only findings are INFO are excluded — INFO is
dispositioned at synthesis and never revised (see the "Severity handling —
INFO disposition" rule in Phase D).
- The {REVISION_ROLE} MUST NOT receive the full text of all findings inline —
use finding-file path / comment references only to avoid context explosion.
- Convergence status: blockers/majors remaining vs. previous round.
- **Revision survival ([org-internal #2601]) — primary path**: the task tool's completion
output includes a `task_id`; passing it back resumes the SAME
{REVISION_ROLE} subagent session instead of creating a fresh one. Round 1:
persist the returned `task_id` with the round's working-notes entry (it
must survive round-boundary compaction). Round ≥ 2: dispatch with
`task_id` set to the previous revision round's — the reviser continues
in-session with its prior context. {REVIEWER_OR_AUDITOR}s are NEVER
resumed — per-round freshness is their independence.
- **Revision survival — fallback** (task_id lost to compaction before it
was persisted): for round ≥ 2, include the current
`<runs-root>/{slug}/working-notes.md` content (run narrative memory:
prior decisions, dead ends, earlier reviewer feedback) and a reference to
the previous round's revision summary. State explicitly that this revises
work a prior round already produced — re-derive from upstream artifacts
ONLY when a finding requires it.
2. Spawn the {REVISION_ROLE} (Worker) with revision instructions from
`core/skills/_shared/review-revision-prompt.md`. For round ≥ 2, pass
the previous reviser `task_id` to resume the same session (see Revision
survival above); reviewer/auditor dispatches are always fresh.
---
## Phase D — {REVIEWER_OR_AUDIT_LOOP} Loop
After the {REVISION_ROLE} completes revision:
1. Increment the round counter, and update `current_round` in
`<runs-root>/{slug}/reviews/{stage}/status.json` to match. Post the round
transition as a progress update to the commit status (Pattern 8):
```
state: "pending"
context: "pipeline/{REVIEW_TYPE}"
description: "Round {N+1}: re-reviewing after {REVISION_ROLE} revision"
```
2. Go back to Phase A (spawn a new round against the revised files).
**Convergence criteria** (stop the loop when ANY of these is true):
| Condition | Action |
| -------------------------------------- | ---------------------------------------------- |
| `overall_verdict` = PASS | Stop. Proceed to Phase E. |
| `current_round` > `{MAX_ROUNDS}` | Stop. Proceed to Phase E with final status. |
| No improvement in 2 consecutive rounds | Stop. (Same blocker count AND same major count |
| (compared to 2 rounds ago) | without reduction). Proceed to Phase E. |
| User explicitly approves current state | Stop. Proceed to Phase E. |
**`{MAX_ROUNDS}` binding** (per skill):
- `review-artifact` skill (process target): **3**.
- `review-artifact` skill (`review-dag` single-gate target): **per depth** from
`dag_metrics.review_depth` — D1 ≤2, D2 ≤3, D3 ≤3, D4 ≤4 (D4 reaches round 4,
exceeding the shared `review-artifact` default; the per-depth cap takes precedence —
see `review-artifact/SKILL.md` review-dag Phase D and
`core/schemas/review-status.schema.json`).
- `review-code` skill: **2** default / **3** high-risk (a BLOCKER survived a
round, or the diff touches a frozen cross-session contract) —
`<instance-root>/workflow-routing.yaml` `review.max_rounds` ([org-internal #3567], process-diet
P0b). At budget without convergence: STOP, register the remaining
BLOCKER/MAJOR findings as TD registry rows, verdict FAIL if any BLOCKER
remains else PASS — the debt is carried by those TD registry rows
(see `review-code/SKILL.md` Phase D). This
supersedes the 2026-08-25 [org-internal #3380] calibration (p50=2, p95=5, n=66): the runs
past round 3 are the marathon tail this budget exists to cut; their residue
flows to the TD registry instead of reviewer rounds. Ledger:
`docs/workflow-refactor/thresholds-ledger.md`.
Rationale for `review-artifact` = 3 (evaluated in [org-internal #1969], replacing the prior blanket
default of 5): (1) the Round-3 Escalation Guardrail below already detects
non-convergence at the start of round 3; (2) the INFO-disposition rule below
removes spurious WARN rounds previously caused by non-blocking INFO findings;
(3) the "no improvement in 2 consecutive rounds" stop condition naturally
fires by round 3; (4) the marginal value of rounds 4-5 is low once the
guardrail has fired and they burn reviewer budget. (`review-code` no longer
reaches round 4 — its round budget is 2/3, see the binding above.)
**Provisional, not empirically validated**: the four safeguards make `review-artifact`=3
defensible, but it has no fresh run-data yet. Treat it as a hypothesis to
confirm via the retrospective signal ([org-internal #1834]; gate-trim's landing machinery was
retired, [org-internal #3072] phase 3 — the check is observational, not a trim proposal): if
live `review-artifact` runs frequently hit round 3 still improving yet are
forced to stop at WARN/FAIL, bump it back to 4.
**Severity handling — INFO disposition** (non-blocking, non-revising): INFO
findings are observations/recommendations beyond the checklist. They NEVER
block convergence and NEVER drive a reviser round.
- A dimension whose ONLY open findings are INFO has effective verdict **PASS**
— INFO does not reduce `pass_rate` and does not cause WARN/FAIL (see
orchestrator rule #3). Such a dimension does NOT count against
`overall_verdict`.
- At synthesis, the Synthesizer dispositions EVERY INFO finding as one of:
- `TD-candidate` — actionable observation worth a tech-debt item; record a
suggested REGISTRY ROW (one-line summary + reactivation trigger,
`core/rules/ticket-lifecycle.md`). Actual `TD-NNN` registration is
deferred to the `verify` skill's Phase 5.5 registry filing — a
TD-candidate does NOT open an independent ticket at review time
(consistent with how `review-code` Accepted Risks flow to verify).
- `DISCARDED` — not worth tracking; record a one-line reason.
Dispositions are listed in the synthesis comment (markdown). INFO counts
carry in `info_count` / `infos` for reporting only.
- Phase C reviser task (step 1 above) references ONLY dimensions with open
BLOCKER/MAJOR/MINOR findings. INFO findings are NEVER passed to the
{REVISION_ROLE}.
**Round-3 Escalation Guardrail** (applies to the `review-artifact` skill's
live targets — the process audit target AND the review-dag single-gate
target; the roadmap / design / plan targets were archived 2026-08-21,
[org-internal #3072] phase 3). `review-code` does NOT use this guardrail; it uses its round
budget ([org-internal #3567], see the `{MAX_ROUNDS}` binding above).
At the start of round 3, if the combined blocker+major count is still >50% of
round 1's count, STOP the loop and surface to the user:
> Review not converging: round 1 had {N1} blockers+majors, round 2 still has
> {N2} ({pct}% of round 1). This suggests the artifact under review needs a
> substantive rewrite, not incremental patching. Options:
> (a) Return to upstream — for the `process` audit target, escalate to the
> octopus maintainer for a process-level redesign (the legacy producer skills
> `roadmap` / `requirements-elicitation` / `design` / `plan-iterations` were
> archived 2026-08-21, [org-internal #3072] phase 3 — `<instance-root>/archive/`), then restart this
> review at round 1.
> (b) Continue iterating — accept that more rounds may be needed (capped by
> `{MAX_ROUNDS}`).
Choosing (a) returns control to the upstream skill (or maintainer); the
review's commit status is marked with description `"escalated"` and
`converged: false`.
**Round-4 user-choice escalation point (shared)**: a multi-round review skill
MUST offer the user an explicit choice before entering round 4 — (a) stop the
review and escalate to a human, or (b) continue into round 4 — via the
`question` tool. The guard means "escalate with a choice", NOT "force-stop".
The choice is UNCONDITIONAL at the round-3→4 transition — instance skills may
narrow when an escalation *variant* (e.g. a design-level decision) fires, but
never narrow the choice gate itself. The call MUST be optionized ([org-internal #3378]):
two options — `Stop & escalate to a human` (first, when recommended) /
`Continue into round 4` — with `stage` set to the running skill's stage so
auto-approval applies in autonomous mode; free-text asking is a spec
violation.
Instance: review-dag D4 option-b (`review-artifact/SKILL.md` §"Round-3
Escalation Guardrail interaction") — the sole live instance, its D4 round cap
(≤4) being the only budget that reaches round 4 (`review-code` is capped 2/3,
[org-internal #3567]).
Rationale (retro 2026-08-21 / [org-internal #3050]): unattended late-round loops drift into
review marathons — the choice forces the design-level stall to surface to a
human at round 4 instead of round 6.
**Efficiency optimization**: From round 2 onward ([org-internal #2598]), only spawn
{REVIEWER_OR_AUDITOR}s for dimensions that had FAIL, WARN, or UNRESOLVED in
the previous round. Every 2 rounds, run a full {DIMENSION_COUNT}-reviewer
sweep to catch regressions. From round 2 onward reviewer input is also
delta-scoped: the file list handed to each reviewer covers only the files
changed since the previous round plus their convention-comparison neighbors,
not the round-1 full set.
---
## Phase E — Final Report
**Orchestrator actions**:
1. Write the final report as a Gitea wiki page at
`{slug}/reviews/{stage}/final/report` (Pattern 1,
`_shared/gitea-write-patterns.md`), using the template at
`core/skills/_shared/review-final-report-template.md`. For the
`audit-process` target, write to `audit/{date}/final/report` instead (wiki
namespace override `audit/{date}/`). Also set the lifecycle `converged` flag
in `<runs-root>/{slug}/reviews/{stage}/status.json`: `true` when the final
verdict is PASS (or WARN with no unresolved BLOCKERs), else `false`. This is
the ONLY place `converged` is written — the Orchestrator at Phase E; the
Synthesizer never sets it.
2. Update the `## 工件索引` comment on the source issue per
`_shared/gitea-write-patterns.md` Pattern 10: add/update the
`REVIEW-{stage}` row (位置 = the final report page, 重读 = CORE —
review conclusions are part of the compaction re-read set).
Skip if no source issue exists (Pattern 10 step 1).
2.5. **Inline-ization (skills that bind it)** — when the review target is a
PR and the skill's Phase E binds the inline-comment step (currently
`review-code` only, as its Phase E step 2.56), execute it now: post the
final round's findings as PR inline comments via
`<harness-package>/script/post-inline-comments.ts` (consumer of frozen
contract `shared/review-inline-v1`): noise findings (INFO / DISCARDED /
`WAIVED-{id}`) are never inlined (contract behavior 2), multi-round
posts delete the identity's previous inline comments first (behavior 5),
stale lines degrade to the file's first diff hunk (behavior 4; caveat:
this server build returns HTTP 500 rather than the contract's 422 for
stale lines — TD-611 — so stale items currently surface under `failed`
with `fallback` empty), and partial failures are accounted — never
blocking the verdict (behavior 6) — in the synthesis comment and final
report's `inline: X posted / Y file-level / Z fallback / W failed` line.
Skills whose target has no PR (e.g. `audit-process`) skip this step. The
skill-specific binding (runner invocation, findings paths, identity,
cleanup) is normative in the binding skill's own Phase E section.
3. Post final commit status via REST:
```
POST /api/v1/repos/Octopus/octopus/statuses/{SHA}
state: "success"|"failure"
context: "pipeline/{REVIEW_TYPE}"
description: "Final: {PASS|WARN|FAIL} after {N} rounds — converged: {true|false}"
target_url: "{wiki page link}"
```
3.5. **Gitea approval timing (mandatory when the PR needs a human approval
gate)**: if the flow requests approval from a human reviewer on Gitea,
register the reviewer FIRST via `gitea_pull__add_reviewer`, wait for the
`REQUEST_REVIEW` registration to be recorded, and only THEN ask the human
to approve. Approving before the reviewer is registered produces
`official: false` on the approval — the gate treats the PR as unapproved
and the approval must be re-done. ([org-internal #1931]: audit R2→R3 re-loop + re-approve,
~30min wasted on exactly this ordering.)
4. Present the final report to the user and obtain approval via the `question`
tool, passing `stage: "{STAGE}"`.
The `question` tool auto-approves per `core/rules/auto-approve.md` (stage `"{STAGE}"`).
Do NOT advance without approval (auto or explicit).
---
## Orchestrator Rules (Mandatory)
See `core/skills/_shared/review-orchestrator-rules.md` for the mandatory
Orchestrator behavior rules. Substitute `{REVISION_ROLE}`: {REVISION_ROLE}.
@@ -0,0 +1,135 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Shared Reviewer Prompt Template
> Canonical template for spawning parallel Explorer reviewers. Read on-demand
> by the Orchestrator when dispatching reviewers. The Orchestrator substitutes
> skill-level constants (fixed per skill, documented in the skill's SKILL.md)
> and per-dimension values (filled from the skill's dimension mapping table).
The prompt text below is sent to each reviewer. Substitute all `{PLACEHOLDER}`
values before dispatch.
```
You are a {REVIEWER_ROLE}. Your responsibility is exactly the dimension(s)
assigned to you — one dimension in the standard dispatch, or the named group
of dimensions in a risk-tier grouped dispatch (review-code S/M tiers). Do NOT
expand or narrow your scope beyond the assignment. Examine every file
assigned to you and flag every issue you find, regardless of perceived
importance.
{BASELINE_SECTION}
## Your Dimension(s)
Code: {DIMENSION_CODE}
Label: {DIMENSION_NAME}
## Checklist Items You Must Verify
{CHECKLIST_ITEMS}
{FILE_SECTIONS}
## Instructions
{READ_FIRST_STEPS}
4. For each checklist item, determine whether the {ARTIFACT_NOUN} satisfies it.
Every checklist item must receive a PASS or FAIL verdict.
Items that are genuinely not applicable must be marked NA with a reason.
5. For every FAIL, produce a finding with: a unique id (pattern {FINDING_ID_PREFIX}-F{NNN},
e.g. {FINDING_ID_EXAMPLE}), severity, checklist ref, {FINDING_DETAILS}, and a concrete
suggestion for how to fix it. {FINDING_ID_PREFIX} is the finding's OWN
dimension code — equal to {DIMENSION_CODE} in the standard single-dimension
dispatch; for a grouped dispatch each finding's id uses its own dimension
code (e.g. COR-F001, TST-F001), NOT the joined group code
(never `COR+TST+DBT-F001`). When your dispatch is multi-slot
(reviewer_count > 1), use the slot-prefixed form {DIM}R{k}-F{NNN} with your
1-based slot k (e.g. TOPOR1-F001) so parallel slot files never collide on id.
{CONTRADICTION_RULE}
7. Assign severity strictly:
{SEVERITY_DEFINITIONS}
8. Count every PASS and FAIL. Compute pass_rate = passed / total_checks.
Verdict rules:
- PASS: pass_rate = 1.0 (no failed checks)
- WARN: pass_rate >= 0.7 and no BLOCKER findings
- FAIL: any BLOCKER finding OR pass_rate < 0.7
9. Place the computed counts in a `quantitative_summary` object with fields
`total_checks`, `passed`, `failed`, `pass_rate`, `na_count` (if any items were marked NA),
and the four severity counts `blocker_count`, `major_count`, `minor_count`,
`info_count` (count the BLOCKER/MAJOR/MINOR/INFO findings you produced —
the Synthesizer consumes these for aggregation).
10. Write findings to `{OUTPUT_FILE}`
using the JSON Schema defined at `core/schemas/reviewer-output.schema.json`.
Populate `reviewed_files` with the list of file paths you actually read
during review.
## What You Must NOT Do
- Do NOT fabricate results. If a tool you need (e.g. `gitea_wiki__get_page`,
`read`, `codegraph_*`) is unavailable, errors, or returns empty, report the
failure honestly — emit a finding stating the dimension could not be
reviewed due to the missing tool. NEVER invent findings, severities, or
file references you did not derive from a successful tool call. Fabricated
review output is a critical defect (per `core/rules/claim-discipline.md`
§ "Tool-failure honesty").
- Do NOT narrow your scope because "this seems minor".
- Do NOT skip checklist items because "{LOOKS_FINE_PHRASE}".
- Do NOT produce a PASS verdict to save effort. Base verdicts on data.
- Do NOT add findings for dimensions you are not assigned to.
- Do NOT read files outside your assigned list.
- Do NOT edit any file EXCEPT your own findings file at `{OUTPUT_FILE}`. You are read-only for all other files.
```
## Placeholders
### Skill-level constants (fixed per skill — see the skill's SKILL.md)
- `{REVIEWER_ROLE}` — reviewer identity (e.g. "code reviewer", "DAG reviewer", "process auditor")
- `{BASELINE_SECTION}` — the full baseline section heading + body (differs structurally
per skill: single design baseline vs. dual requirements+design baselines)
- `{READ_FIRST_STEPS}` — instruction steps 13 (what to read first; includes the `1.` `2.`
`3.` prefixes)
- `{ARTIFACT_NOUN}` — "code" or "artifact" (used in step 4)
- `{FINDING_DETAILS}` — the location/evidence phrase in step 5 (e.g. "file path and line
number, a summary, detailed explanation, quoted code evidence")
- `{FINDING_ID_EXAMPLE}` — example finding id (e.g. COR-F001, CMP-F001)
- `{FINDING_ID_PREFIX}` — the id prefix for findings: the finding's own
dimension code (equal to `{DIMENSION_CODE}` in the standard dispatch; the
own-code of each finding inside a group in a grouped dispatch; see step 5's
per-finding rule and the multi-slot `{DIM}R{k}-F{NNN}` form)
- `{CONTRADICTION_RULE}` — step 6 in full (includes the `6.` prefix); the rule for when
an artifact contradicts its baseline
- `{SEVERITY_DEFINITIONS}` — the four severity bullets (BLOCKER/MAJOR/MINOR/INFO) with
leading indentation matching step 7's sub-items
- `{LOOKS_FINE_PHRASE}` — "the code looks fine" / "the artifacts look fine"
### Per-dimension values (filled by the Orchestrator from the dimension mapping table)
- `{DIMENSION_CODE}` — the dimension code (e.g. COR, ARCH). For a grouped
dispatch (review-code S/M risk tiers, where one reviewer covers several
dimensions), fill with the joined group, e.g. `COR+TST+DBT` — the reviewer
then emits one findings file for the group with per-finding ids prefixed by
each finding's own dimension code.
- `{DIMENSION_NAME}` — the dimension label (e.g. Correctness, Architecture);
for a grouped dispatch, the joined labels of the group.
- `{CHECKLIST_ITEMS}` — the inline checklist items text for this dimension (read from
the checklist file; inlined so the reviewer does not need to open the checklist)
- `{FILE_SECTIONS}` — the file sections block (skill-specific section headers + the
dimension's assigned file paths; include the "(none)" note where applicable)
- `{OUTPUT_FILE}` — the output target for this reviewer's findings. The skill
picks the findings sink (see `_shared/review-pipeline-phases.md` "Output model
— two findings sinks"):
- Tier 1 local (migrated skills, e.g. `review-code`): the absolute path inside
the worktree, e.g.
`<worktrees-root>/<repo>-<slug>/<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`
— a JSON file validated by `core/schemas/reviewer-output.schema.json`.
The reviewer is granted scoped write to `<runs-root>/**` for this.
When the dispatch assigns MORE THAN ONE reviewer to this dimension
(dim_split > 1), each reviewer writes its OWN slot file
`findings-{DIMENSION_CODE}-r{k}.json` (k = its 1-based slot) with
`reviewer_slot` / `reviewer_count` set — parallel reviewers never share
one file.
- Tier 2 comment (unmigrated fallback): post an issue comment via
`工单评论 API(见 TERMINOLOGY` (Pattern 5) with a fenced ` ```json ` block,
labeled `{DIMENSION_CODE} review — round {N}` (multi-slot reviewers append
` · r{k}` to the label).
@@ -0,0 +1,57 @@
# Shared Revision Prompt Template
> Canonical prompt for spawning the {ROLE} (Worker) that revises artifacts
> between review rounds. Read on-demand by the Orchestrator in Phase C. The
> Orchestrator substitutes skill-level constants before dispatch.
```
Read `{TASK_FILE}` for your input files. {REVISION_SCOPE}
{RESOLUTION_RULE}
Your task file references ONLY dimensions with open BLOCKER, MAJOR, or MINOR
findings. INFO findings are NEVER in your scope — they are dispositioned
(`TD-candidate` or `DISCARDED`) at synthesis and excluded from revision (see
`.octopus/skills/_shared/review-pipeline-phases.md` Phase D, "Severity
handling — INFO disposition"). Do NOT act on or chase INFO observations.
From round 2 onward, every remaining purely cosmetic MINOR finding
({COSMETIC_MINOR_EXAMPLES} — per the severity definition above) MUST be explicitly
dispositioned: either fix it and mark it resolved, or waive it by recording
`WAIVED-{finding-id}` (e.g. `{WAIVED_EXAMPLE}`) with a one-line reason and setting
the finding's status to `WAIVED`. Cosmetic MINORs MUST NOT carry forward as open
items beyond round 2 — an unresolved cosmetic MINOR is itself a process violation.
This is the canonical definition of the `WAIVED-{id}` convention referenced in
`{CHECKLIST_REF}`.
After revising, report a summary of changes made:
- Files modified: {FILES_MODIFIED_LINE}
- Findings addressed: {counts by severity}
- Findings deferred with justification: {list}
- Findings waived as cosmetic MINOR (round 2+): {WAIVED-{id} → reason}
Persist that summary to `{RETURN_FILE}` as your LAST action, BEFORE
returning it — worker-report persistence ([org-internal #2847], see
`.octopus/skills/_shared/worker-report-persistence.md`). The persisted copy
is the report of record; if your completion notification is lost, the
Orchestrator recovers the revision outcome from this file.
```
## Placeholders
### Skill-level constants (fixed per skill — see the skill's SKILL.md)
- `{ROLE}` — "Developer" (code review), "Analyst" (review-dag single gate), or "Remediator" (process audit)
- `{TASK_FILE}``task-developer`, `task-architect`, `task-analyst`, `task-planner`, or `task-remediator` (wiki page name under `{slug}/reviews/{stage}/round{N}/`)
- `{REVISION_SCOPE}` — the opening paragraph: what to revise and the
minimal/surgical constraint
- `{RESOLUTION_RULE}` — the design-deviation / resolution-comment rule
- `{COSMETIC_MINOR_EXAMPLES}` — the parenthetical examples of cosmetic MINORs
- `{WAIVED_EXAMPLE}` — example waived id (e.g. `WAIVED-COR-R2-001`)
- `{CHECKLIST_REF}` — the checklist file(s) the WAIVED convention is defined in
- `{FILES_MODIFIED_LINE}``{list}` or `{list by artifact type}`
- `{RETURN_FILE}` — the Tier 1 return-persistence target for this dispatch,
following the skill's canonical stage path (e.g. review-code:
`.octopus/runs/{slug}/reviews/code/round{N}/revision-return.md`); default
per convention: `.octopus/runs/{slug}/workers/{chunk-id}-worker-{seq}.md`,
lightweight fallback `/tmp/octopus/{chunk-id}-worker-{seq}.md`
@@ -0,0 +1,86 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
name: Coordinator
purpose: >
Pool dispatch coordinator (billing-v2 [org-internal #4041] §7, REQ-F-012): a wake-driven,
STATELESS decision-maker session living in the pool pod alongside workers.
List eligible issues (Gitea API, scoped token) → produce DAG-aware dispatch
proposals → commit each dispatch through the N-16 assignment API
(POST /api/pools/:repo/assignments). The coordinator decides; the
control-plane dumbwaiters make the commit atomic. It never holds wallet
internals, never bypasses the central ledger, and never merges (E-4).
# Layer 5 — Executable
agent: worker
model: null
agents_md:
- workflow-routing
- claim-discipline
- code-graph
- compact
# Stateless iron rule (§7.1): the coordinator's ENTIRE dispatch state is the
# work_order_assignments table. After any wake (first start, re-wake after
# idle, or takeover after the previous coordinator died) the first action is
# ALWAYS: GET /api/pools/:repo/assignments?state=assigned — rebuild the
# in-flight view from the table, never from memory or notes. Dying costs
# nothing; the pool reconciler migrates the role to the earliest surviving
# member by convention (no election).
#
# Decision procedure (AC-1) — full text: docs/runbooks/pool-coordinator.md.
# 1. recover — read the assignment table (state=assigned) + org
# notifications (pools.assignment.expired = re-dispatch inbox)
# 2. list — eligible issues via Gitea API (scoped token): labeled
# Kind/* and unassigned/without active assignment row
# 3. propose — DAG-aware: respect blockedBy edges (epic DAG / node deps),
# sibling load (least-loaded member first), and the N>1
# self-assign constraints below; fast path (AC-1 简单场景):
# exactly one eligible issue and one available member →
# deterministic pick, no elaborate deliberation needed
# 4. commit — one POST per dispatch; proposal payload carries the audit
# trace (§audit contract below); 409/403 envelopes are
# terminal outcomes, not retried blindly
#
# Self-assign constraints (AC-2, §7.2 N>1) — the ledger is identity-blind, so
# the constraints live HERE and are enforced by convention + audited via the
# trace: least-loaded ordering puts self LAST (self-assign only when no other
# member is available); the coordinator holds at most 1 active assignment at
# a time, same cap as any worker; self-dispatch still goes through the same
# POST (铁律 — never "start working because I am the coordinator"). N=1:
# self-dispatch is the only form and is NOT a special branch.
#
# Audit contract (AC-4/AC-6): every POST carries proposal =
# { version: 1,
# decision: { coordinator, self_position: "last"|"n/a", held_active,
# cap: 1, fast_path: boolean, reason },
# eligible: [{ issue, blockedBy: [issue,...] }...],
# chosen: { issue, member } }
# The trace lands verbatim on the assignment row (jsonb proposal_payload),
# is queryable via GET /api/pools/:repo/assignments, and is exported in the
# assignmentAuditRecord shape (runs archive / pool-drill.sh). LLM decisions
# are not replayable — the recorded decision + the deterministic commit is
# the audit story.
#
# Security posture (AC-5, see docs/security/threat-model.md §自治批处理):
# the coordinator authenticates with a workspace-level API token
# (TD-687/[org-internal #3898] owner-minted, jti-revocable, role claim org_role). TODAY'S
# mint shape hardcodes org_role=viewer — read-capable only (assignment table
# + pool surfaces); the editor-capable mint needed for dispatch submission is
# an explicit deployment-time decision on the [org-internal #3898] channel (not landed with
# N-19). Either way the token can NOT mint tokens, touch wallet internals, or
# merge PRs — the merge gate stays human (E-4).
#
# Worker-report persistence ([org-internal #2847] — see
# core/skills/_shared/worker-report-persistence.md): a Coordinator
# sub-agent persists its FINAL RETURN REPORT to disk as its LAST action,
# before returning — <runs-root>/{slug}/workers/{chunk-id}-worker-{seq}.md
# (Tier 1 preferred; else /tmp/octopus/{chunk-id}-worker-{seq}.md), and
# appends its working-notes entry at stage exit per
# core/templates/runs-layout.md. The persisted copy is the report of
# record; re-read persisted artifacts after any compaction, never rely on
# conversation memory.
compact:
preserve:
- "docs/runbooks/pool-coordinator.md — the decision procedure is file-driven, never memory-driven; re-read after any compaction"
- "the recovered in-flight view (GET /api/pools/:repo/assignments?state=assigned) — the coordinator holds NO dispatch state in memory; re-read the table first after every wake/compaction"
- "the audit trace schema (decision.self_position / decision.held_active / decision.cap / eligible[].blockedBy) — drift here breaks AC-6 auditability"
- "self-assign constraints: self LAST in least-loaded order, active-assignment cap 1, self-dispatch goes through the same POST — never a special branch (§7.2 铁律)"
+41
View File
@@ -0,0 +1,41 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
name: Producer
purpose: >
Write production artifacts — requirements, design, plans, code, fixes,
refactors, releases, retrospectives. The specific task, its checklist, and
the current pipeline stage are injected by the calling skill.
# Layer 5 — Executable
agent: worker
model: null
agents_md:
- code-graph
- testing
- type-checking
- claim-discipline
- compact
# Worker-report persistence ([org-internal #2847], retro-2696 AI#2 — see
# core/skills/_shared/worker-report-persistence.md): every Producer
# sub-agent persists its FINAL RETURN REPORT to disk as its LAST action,
# before returning — <runs-root>/{slug}/workers/{chunk-id}-worker-{seq}.md
# (Tier 1 preferred; skill-canonical stage path when the skill defines one),
# else /tmp/octopus/{chunk-id}-worker-{seq}.md. The persisted copy is the
# report of record; the task notification is a convenience copy.
#
# Working-notes contract ([org-internal #2600] — see core/templates/runs-layout.md §
# working-notes.md): at stage exit the Producer appends ONE entry (≤30 lines:
# decisions and why, dead ends, reviewer feedback that changed the work, hints
# for the next stage) to <runs-root>/{slug}/working-notes.md, and registers
# its index.json row (type working-notes) once if absent.
#
# Role-level compaction contract (harness-enforced — see core/rules/compact.md
# § "Role-level re-read set"). After any compaction, a Producer sub-agent re-reads
# these persisted artifacts before continuing; it never relies on conversation
# memory for them.
compact:
preserve:
- "<runs-root>/{slug}/working-notes.md (re-read FIRST, before formal artifacts — carries prior stages' tacit context: decisions, dead ends, reviewer feedback)"
- "the work item / node spec being implemented (issue body or the frozen DAG copy {epic-slug}/dag + {epic-slug}/dag-nodes/{node-id} subpages)"
- "the node's design baseline: node spec + cross-session edge contracts in {epic-slug}/dag (design detail is folded into node AC + contracts)"
- "the acceptance criteria for the current node (node acceptance_criteria in {epic-slug}/dag, or the task ticket body)"
+30
View File
@@ -0,0 +1,30 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
name: Reviewer
purpose: >
Review one dimension of an artifact against its checklist, read-only.
Find issues that would block merge. One reviewer per dimension, all
running in parallel.
# Layer 5 — Executable
agent: explorer
model: null
agents_md:
- code-graph
- claim-discipline
- ticket-lifecycle
- compact
# Working-notes contract ([org-internal #2600] — see core/templates/runs-layout.md §
# working-notes.md): the Reviewer side appends ONE distilled entry via the
# Synthesizer at review convergence — never per dimension dispatch (9
# dimension dispatches = 1 entry) — to <runs-root>/{slug}/working-notes.md.
#
# Role-level compaction contract (harness-enforced — see core/rules/compact.md
# §"Role-level re-read set"). A Reviewer is read-only and short-lived (one
# dimension per dispatch); after any compaction it re-reads its checklist and
# the artifact under review, never conversation memory.
compact:
preserve:
- "<runs-root>/{slug}/working-notes.md (re-read FIRST — prior stages' tacit context; also the file the Synthesizer's convergence entry appends to)"
- "the dimension checklist being applied (core/checklists/*.md)"
- "the artifact under review ({slug}/ wiki page or PR diff)"
+26
View File
@@ -0,0 +1,26 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
name: Tool
purpose: >
Specialized tool agent for image interpretation and other vision/utility
tasks that require a specific model.
# Layer 5 — Executable
agent: explorer
model: opencode/claude-haiku-4-5
agents_md:
- code-graph
- claim-discipline
- compact
# Working-notes contract ([org-internal #2600] — see core/templates/runs-layout.md §
# working-notes.md): Tool agents append to <runs-root>/{slug}/working-notes.md
# ONLY when the task produced a load-bearing finding (e.g. image evidence that
# changed a decision) — not for routine single-shot output.
#
# Role-level compaction contract (harness-enforced — see core/rules/compact.md
# §"Role-level re-read set"). Tool agents are single-shot; after any compaction
# they re-read the task prompt's input artifact, never conversation memory.
compact:
preserve:
- "<runs-root>/{slug}/working-notes.md (re-read FIRST — prior stages' tacit context)"
- "the input artifact named in the task prompt (image path, file, or URL)"
+40
View File
@@ -0,0 +1,40 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
name: Verifier
purpose: >
Run the iteration's DoD matrix, integration tests, NFR validation, and
regression checks. The iteration is not Done until all DoD items pass.
# Layer 5 — Executable
agent: worker
model: null
agents_md:
- code-graph
- testing
- ticket-lifecycle
- type-checking
- claim-discipline
- compact
# Worker-report persistence ([org-internal #2847], retro-2696 AI#2 — see
# core/skills/_shared/worker-report-persistence.md): the Verifier
# persists its FINAL RETURN REPORT (DoD verdict + evidence pointers) to disk
# as its LAST action, before returning —
# <runs-root>/{slug}/workers/{chunk-id}-worker-{seq}.md (Tier 1 preferred),
# else /tmp/octopus/{chunk-id}-worker-{seq}.md. The persisted copy is the
# report of record; the task notification is a convenience copy.
#
# Working-notes contract ([org-internal #2600] — see core/templates/runs-layout.md §
# working-notes.md): the Verifier appends ONE entry at verify exit (what the
# DoD sweep caught, flaky/deferral context the next run should know). The
# Phase 5.5 archive step additionally inlines the final working-notes.md
# content into the archive bundle's working_notes field.
#
# Role-level compaction contract (harness-enforced — see core/rules/compact.md
# §"Role-level re-read set"). After any compaction, a Verifier re-reads these
# persisted artifacts before continuing; it never relies on conversation memory.
compact:
preserve:
- "<runs-root>/{slug}/working-notes.md (re-read FIRST, before formal artifacts — carries prior stages' tacit context: decisions, dead ends, reviewer feedback)"
- "the DoD matrix for the current iteration ({slug}/05-verify-*)"
- "the acceptance criteria (node acceptance_criteria in the frozen DAG copy {epic-slug}/dag, or the task ticket body)"
- "the final code-review report ({slug}/reviews/code/final/report)"
+236
View File
@@ -0,0 +1,236 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Synthesis Task — Round {ROUND_NUMBER}
> Shared template filled by the Orchestrator and dispatched to the Synthesizer
> (Worker) during Phase B of all review and audit skills. The Orchestrator
> writes this template to the skill's configured prompt location (Tier 1 local
> `<runs-root>/{slug}/reviews/{stage}/round{N}/task-synthesizer.md` for
> `tier1-local` skills like `review-code`; `/tmp/octopus/synthesis_task_{slug}_round{N}.md`
> + a Gitea wiki page for `tier2-comment` skills) for `prompt_file` dispatch.
>
> The Synthesizer reads all dimension findings from the skill's findings sink
> (local files or issue comments), posts the synthesis to the pipeline PR /
> source issue comment — for audit-process runs, the wiki page
> `audit/{date}/round{N}/synthesis` — with a fenced JSON block (Tier 2 human
> gate — always Gitea), updates the commit status, and returns a compact
> summary (≤ 2KB).
## Input
- **Target**: PR #{PR_NUMBER} (pipeline mode) or issue #{ISSUE_NUMBER} (standalone mode)
— for audit-process runs, the wiki page `audit/{date}/round{N}/synthesis`.
Post the synthesis to this target.
- **Findings sink**: `{FINDINGS_SINK}``tier1-local` or `tier2-comment`
(set by the skill; see `_shared/review-pipeline-phases.md`).
- **Dimension findings** — already produced by individual reviewers:
- `tier1-local`: read the finding files at
`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-{DIMENSION_CODE}.json`,
each a JSON document matching `core/schemas/reviewer-output.schema.json`.
Read every `findings-*.json` in that round directory. A dimension with
multiple reviewer slots (dim_split > 1) contributes several files
(`findings-{DIM}-r1.json`, `-r2.json`, …) — aggregate them as ONE
dimension's findings (union of findings; worst verdict wins).
- `tier2-comment`: read the dimension issue comments via
`工单评论 API(见 TERMINOLOGY` (Pattern 3, `_shared/gitea-read-patterns.md`).
Each comment body contains a `**{DIMENSION_CODE} review — round {N}**` header
followed by a fenced ` ```json ` block matching
`core/schemas/reviewer-output.schema.json`.
- **Dimensions marked UNRESOLVED** (reviewer crashed/timed out, no finding exists):
`{UNRESOLVED_DIMENSIONS}`
Exclude these from aggregation.
- **Synthesis schema**: `core/schemas/synthesis.schema.json`
- **Commit SHA**: `{SHA}` (the HEAD commit being reviewed).
- **Review type**: `{REVIEW_TYPE}` — one of `review-code`, `review-dag`,
`audit-process` (the `review-design-space` / `review-iteration-plan` /
`review-roadmap` targets were archived 2026-08-21, [org-internal #3072] phase 3 — their
historical synthesis data remains readable).
## Your Job
1. Read all dimension findings from the configured sink. For each dimension,
extract:
- `verdict` (PASS / WARN / FAIL / UNRESOLVED)
- `pass_rate`
- `blocker_count`, `major_count`, `minor_count`, `info_count`
- `findings` array (each finding has: id, severity, checklist_ref, summary)
1b. **Finding-id uniqueness post-check (MANDATORY)**: after reading ALL
findings files of the round (including every `-r{k}` slot file), verify
that `id` values are unique across the whole round. The reviewer-output
schema's `uniqueItems` compares whole objects and does NOT enforce id
uniqueness — this step is the enforcement. On a duplicate id: do NOT
silently drop or renumber it; keep both findings in the raw files, note
the collision in the synthesis digest, and mark the affected dimension
WARN with the collision listed (the dispatch/prompt defect that produced
it — e.g. a missing slot prefix — is the fix target).
2. Compute `aggregated_stats`:
- `total_dimensions`: count of all dispatched dimensions (including UNRESOLVED)
- `passed_dimensions`, `warn_dimensions`, `failed_dimensions`
- `total_findings`, `blockers`, `majors`, `minors`, `infos`
- `unresolved_dimensions`: count of UNRESOLVED
3. Collect `top_findings` — top 10 ranked by severity:
- Order: BLOCKER > MAJOR > MINOR > INFO
- Within each severity, order by dimension code alphabetically
- Each entry: `{ id, dimension, severity, checklist_ref, summary }``id` is
the source reviewer finding id (Tier 1 findings file; slot-prefixed for
multi-slot dimensions), so each headline traces back to its raw finding
4. Compute `overall_verdict`:
- **PASS**: all available dimensions have verdict PASS.
- **WARN**: no FAIL dimensions AND no BLOCKER findings.
- **FAIL**: any FAIL dimension OR any BLOCKER finding.
5. Post the synthesis as a **PR comment** (pipeline mode) or **issue comment**
(standalone mode) via `工单评论 API(见 TERMINOLOGY`:
- **Exception — `audit-process` target**: process audits have no source
issue, so post the synthesis as a **wiki page** `audit/{date}/round{N}/synthesis`
via `wiki 读写 API(见 TERMINOLOGY` instead of a PR/issue comment (see
`_shared/review-pipeline-phases.md` Phase B step 3 and review-artifact/SKILL.md
process target Output Artifacts). The commit-status `target_url` then
carries that wiki page's `html_url`.
- The comment body MUST contain a **markdown summary** (dimension table,
top findings, verdict) followed by a **fenced ` ```json ` block** with
the structured synthesis data.
- Format:
```
## Synthesis — Round {N}
**Verdict**: {PASS|WARN|FAIL}
**Target**: PR #{N} | Issue #{N} (audit-process: wiki `audit/{date}/round{N}/synthesis`)
**Dimensions**: {passed} passed, {warned} warned, {failed} failed, {unresolved} unresolved
**Findings**: {B} BLOCKERs, {M} MAJORs, {m} MINORs, {I} INFOs
### Dimension Summary
| Dimension | Verdict | Pass Rate | B | M | m | I |
|-----------|---------|-----------|---|---|---|---|
| ... | ... | ... | | | | |
### Top Findings
1. [{DIM_CODE}] [{SEVERITY}] {checklist_ref}: {summary}
2. ...
```json
{
"round": N,
"overall_verdict": "PASS|WARN|FAIL",
"dimensions": {
"DIM_CODE": { "verdict": "PASS|WARN|FAIL|UNRESOLVED", "pass_rate": N, "blocker_count": N, "major_count": N, "minor_count": N, "info_count": N }
},
"aggregated_stats": {
"total_dimensions": N,
"passed_dimensions": N,
"warn_dimensions": N,
"failed_dimensions": N,
"total_findings": N,
"blockers": N,
"majors": N,
"minors": N,
"infos": N,
"unresolved_dimensions": N
},
"top_findings": [
{ "id": "CODE-F001", "dimension": "CODE", "severity": "BLOCKER|MAJOR|MINOR|INFO", "checklist_ref": "X.Y", "summary": "..." }
]
}
```
```
6. Post a **commit status** via REST:
```
POST /api/v1/repos/Octopus/octopus/statuses/{SHA}
Authorization: token {GITEA_TOKEN}
Content-Type: application/json
{
"state": "success"|"failure",
"context": "pipeline/{REVIEW_TYPE}",
"description": "Round {N}: {PASS|WARN|FAIL} — {passed}/{total} dims passed, {B}B {M}M",
"target_url": "{link to synthesis comment}"
}
```
- `state` is `"success"` for PASS or WARN, `"failure"` for FAIL (any BLOCKER).
- `context` uses the review type: `pipeline/review-code`,
`pipeline/review-dag`, `pipeline/audit-process` (slug `audit`).
(See `_shared/gitea-write-patterns.md` Pattern 8 for the full commit-status recipe.)
7. Append one `history[]` entry for this round to the run's review-status file
`<runs-root>/{slug}/reviews/{stage}/status.json` (validated by
`core/schemas/review-status.schema.json`): `round`,
`overall_verdict`, `blockers`, `majors`, `minors`, and update the file's
top-level `current_round` to this round number. This is the durable
per-round audit history (defined by `core/schemas/review-status.schema.json`
`history[]`; mirrors the audit-process checklist TRC 10.4). Initialization is the **Orchestrator**'s
job (Phase A step 0.5 of `_shared/review-pipeline-phases.md`): the file is
created before round 1 with the full schema-required field set `slug`,
`started_at`, `current_round`, `max_rounds`, `converged`, `history` — do NOT
create it here; the Synthesizer only appends `history[]` and updates
`current_round`.
8. **Round 1 only — findings digest** (rounds ≥ 2: skip this step): write the
rendered `## Round-1 Findings Summary` table (columns
`| Dim | Finding ID | Severity | Summary |`, one row per round-1 finding;
Summary = one-line 摘由/root cause) to
`<runs-root>/{slug}/reviews/{stage}/round1/findings-summary.md`,
distilled from the round-1 findings you just read in your own context, and
append the `index.json` row (type `review-synthesis`, stage
`{REVIEW_TYPE}`, produced_by `Synthesizer`). The Orchestrator copies this
file verbatim into the final report's Round-1 Findings Summary section at
Phase E (mandatory for `review-code` step 2.55; other targets MAY include
it — `_shared/review-final-report-template.md`) and does NOT read the
findings files itself (`_shared/review-pipeline-phases.md` Phase B step 3
"Round-1 findings digest").
9. Persist your return summary (the exact text from the Return Format
below) to `<runs-root>/{slug}/reviews/{stage}/round{N}/synthesis-return.md`
(Tier 1) as your LAST action, BEFORE returning it — worker-report
persistence ([org-internal #2847], `_shared/worker-report-persistence.md`). The persisted
copy is the report of record; if your completion notification is lost, the
Orchestrator recovers the round verdict from this file.
## Governance (Binding)
- **Rule 4 — Aggregation, not reinterpretation**: You MUST NOT soften, rephrase,
or downgrade reviewer findings. If a reviewer writes "BLOCKER", it stays
"BLOCKER" in synthesis.
- **Rule 5 — Anonymous findings preservation**: If two reviewers independently
flag the same issue from different dimensions, preserve both entries. Do not
deduplicate by discarding one — cross-dimension agreement is itself a signal.
- **Schema fidelity**: The fenced JSON block MUST validate against
`synthesis.schema.json`. Read the schema file first if unsure of field names.
## Return Format (Binding — ≤ 2KB)
Return ONLY this summary as your final message. Do NOT include the full
synthesis JSON — it is already posted to the pipeline PR / source issue
comment (for audit-process runs, the wiki page `audit/{date}/round{N}/synthesis`).
Persist this summary verbatim to
`<runs-root>/{slug}/reviews/{stage}/round{N}/synthesis-return.md` before
returning (step 9 above — worker-report persistence [org-internal #2847]).
```
## Synthesis Complete
**Round**: {N}
**Verdict**: {PASS|WARN|FAIL}
**Pass Rate**: {avg_pass_rate}
**Dimensions**: {passed} passed, {warned} warn, {failed} failed, {unresolved} unresolved
**Findings**: {B} BLOCKER, {M} MAJOR, {m} MINOR, {I} INFO
### Failed/Warn Dimensions (re-audit targets for next round):
- {DIM_CODE}: verdict={FAIL|WARN}, findings={count} ({finding_ids})
- ...
### Top Findings (for revision task):
1. [{DIM_CODE}] [{SEVERITY}] {checklist_ref}: {summary}
2. ...
### Artifacts Posted:
- Synthesis comment on {PR_OR_ISSUE} #{N} (audit-process: wiki `audit/{date}/round{N}/synthesis`)
- Commit status: pipeline/{REVIEW_TYPE} → {success|failure}
```
@@ -0,0 +1,98 @@
# Worker Report Persistence (mandatory)
> Canonical convention for every worker sub-agent dispatch (roles whose
> `agent:` is `worker` — Producer sub-roles, Verifier, Synthesizer, …),
> foreground or background (`task background=true`). Origin: retro WWW#5 /
> [org-internal #2696] action item #2 ([org-internal #2847]) — at least one confirmed case where a
> background worker's completion report reached the orchestrating session
> only via the task-tool notification; the notification was lost and the
> report evaporated, leaving nothing but reverse-engineering from artifacts.
## The problem
A worker's final return report (status, verdict, judgment, evidence
pointers, open items) travels to the orchestrating session through a single
channel: the task completion notification. If that notification is lost —
stream break, orchestrator interrupted before receipt, session restart —
the report is gone even though the worker finished its work. Conclusions
that exist only in a notification are not durable.
## The rule (Iron Law)
**Every worker MUST persist its final return report to disk as its LAST
action, BEFORE returning.** The persisted copy is the report of record; the
in-conversation return is a convenience copy. No worker return may exist
only in the task notification.
## Where to persist (path selection, in order)
1. **Tier 1 run workspace (preferred when it exists)** — if
`.octopus/runs/{slug}/` exists (see `.octopus/templates/runs-layout.md`),
write the report to:
- Default: `.octopus/runs/{slug}/workers/{chunk-id}-worker-{seq}.md`
- Skill-canonical override: when the dispatching skill already owns a
stage directory for this worker, the skill's canonical path replaces
the generic name (e.g. the review-code Synthesizer writes
`reviews/{stage}/round{N}/synthesis-return.md` next to its task file).
- Register one `index.json` row: `type: "other"`, `schema: null`
(markdown), `stage`: producing stage, `produced_by`: role name.
2. **Lightweight fallback (no run workspace)** — standalone / ad-hoc
sessions with no `.octopus/runs/{slug}/`: write to
`/tmp/octopus/{chunk-id}-worker-{seq}.md`. Survives session death and
compaction; not expected to survive machine restarts — acceptable for
the notification-loss window it covers.
### Naming
- `{chunk-id}` — the owning work identifier stated in the dispatch prompt
(ticket number, `WI-N`, chunk id, e.g. `2847`, `WI-3`).
- `{seq}` — 1-based dispatch sequence of THIS worker within that chunk-id
(a chunk dispatched a second time → `seq` 2). For review loops the round
number in the skill-canonical path plays this role.
- The dispatching Orchestrator MUST state both values (or the canonical
path) in the dispatch prompt — see "Dispatch duty" below.
## What to persist
The **verbatim final return report** — the exact text the worker returns
via the task result: status (`done` / `blocked`), verdict if any, key
results, evidence pointers (commit sha, test names, `file:line`), and open
items. Markdown, no schema. Keep it identical to the returned summary so
recovery reads exactly what the orchestrator would have received.
## Orchestrator recovery path (notification missing)
When a dispatched worker's completion notification never arrives —
background task result lost, or the orchestrating session was interrupted
and resumed — the orchestrating session recovers the worker's conclusion
from disk:
1. Identify the dispatch's `{chunk-id}` (from the dispatch record /
working-notes / task log).
2. Read, in order, the newest match by `{seq}` / mtime:
- `.octopus/runs/{slug}/workers/{chunk-id}-worker-*.md`
- the skill-canonical return file when the dispatch named one (e.g.
`reviews/{stage}/round{N}/synthesis-return.md`)
- `/tmp/octopus/{chunk-id}-worker-*.md`
3. The newest persisted report IS the worker's conclusion of record —
resume the pipeline from it. Do NOT re-dispatch a worker whose persisted
report shows `done`; re-dispatch (or resume via `task_id`) only on
`blocked` or a missing file.
## Dispatch duty (high-frequency dispatch points)
Every dispatch prompt for a worker MUST carry: (a) the persist instruction
("persist your final report to `{path}` before returning, per
`_shared/worker-report-persistence.md`"), and (b) the concrete target path
with `{chunk-id}`/`{seq}` or the skill-canonical location filled in. The
high-frequency templates carry it inline:
- `implement/SKILL.md` — Agent Role responsibilities + Phase 5 (Report)
persist step (applies to every mode's report phase).
- `review-code/SKILL.md` Phase B / Phase C dispatch steps, via the shared
templates `_shared/synthesis_task.md` and
`_shared/review-revision-prompt.md`.
Reviewer (Explorer) dispatches are covered structurally: their findings
JSON is already a Tier 1 persisted artifact, and the Synthesizer's
persisted return is the round's recoverable conclusion.
+503
View File
@@ -0,0 +1,503 @@
---
name: analyze-dag
description: >
Use ONLY when an Epic is routed to the DAG ticket pipeline (Kind/Epic or
Kind/Feature — both resolve to analyze-dag via the routes table directly,
[org-internal #3072] phase 3 / [org-internal #3061] Phase 2). The
Analyst decomposes the Epic into a single task-DAG artifact — merging
roadmap+requirements+design+plan into one graph (nodes = acceptance
criteria, edges = contracts, topology = plan).
triggers:
- analyze-dag
- "dag pipeline"
- "DAG 管线"
- "任务 DAG"
- "task DAG"
- "DAG decomposition"
- "DAG 分解"
# `stage` is intentionally omitted: analyze-dag is the DAG route's entry skill,
# replacing roadmap+requirements+design+plan — it has no single stage identifier
# in the stage registry (see `<instance-root>/workflow-routing.yaml` dag.route and
# core/schemas/workflow-routing.schema.json stage enum). Phase-boundary
# signalling for the DAG route is the review-dag single gate and verify
# milestone, not a StageDone event.
role: Producer
---
# Analyze DAG — Task-DAG Decomposition (DAG route entry skill)
## Purpose
analyze-dag is the **entry skill** of the DAG ticket pipeline
(`dag.route.entry.skill`, spec-07 §2). For a DAG-routed Epic
(`Kind/Epic`, or `Kind/Feature` — both resolve here via the routes table
directly, [org-internal #3072] phase 3 / [org-internal #3061] Phase 2 / TD-388 —
including BACKFILL mode for MVP graduates and big-bug relabels), it
**replaces** `roadmap` + `requirements-elicitation` + `design` +
`plan-iterations` and produces **ONE artifact**: the task DAG, published as
wiki page `{epic-slug}/dag` (Tier 2 — human-visible, cross-worktree stable).
The task DAG merges three views into a single graph:
| View | Where it lives in the DAG |
| ---- | ------------------------- |
| requirements | node `acceptance_criteria` (each node is its own spec) |
| design | edge `contract_ref` (inter-node interface contract) |
| plan | topology (edge direction = dependency / execution order) |
`{epic-slug}` = the slug of the routed Epic — **not** this design's own slug
`dag-pipeline`.
## Agent Role
The **Analyst** (Producer) owns and produces the task DAG. The Analyst
decomposes the Epic into nodes (acceptance criteria), edges (contracts), and
a topological order (the plan), then publishes the single graph.
## Route Disqualifiers — 适用性预检(分解前必查)
分解前,Analyst 必须对照下表预检。任一命中 → **不分解**,将具体未决问题
列回 Epic 并 bounce(在不稳定需求上分解会产出一个即刻腐烂的冻结图;
"先出个小 DAG" 不是降级选项——不进管线,先稳定需求):
| Disqualifier | 判定 | Bounce 动作 |
| --- | --- | --- |
| 需求未稳 | scope item 含糊、互相矛盾、或只能以 "视情况" 回答;关键干系人问题仍开放 | 在 Epic 上列出澄清问题清单;问题解决后重入 analyze-dag |
| 设计主导 | 主要风险是一个有 ≥2 个可行选项的架构/平台决策,而非任务分解;需要先做一次探索 | 将开放问题转 `prototype`THROWAWAY spike)或设计探索;决策记录后重入 |
| 外部契约未决 | scope 依赖未决的外部契约(第三方 schema、上游 API 未定) | 按 External-System Rule(见下文)解析或围栏;不可拆的残余隔离后再重入 |
**反向拦截(Kind/MVP 降级建议,[org-internal #3061]**:一个 `Kind/Feature` 工单在分解预检中
暴露"概念未稳"(scope 仍在探索、成功标准未定、决策日志会推翻节点划分)时,
正确出口不是硬分解,而是建议改标签 `Kind/MVP` 先行交互式验证——概念稳定后
经 backfill 模式(见下文)带着决策日志重入。
### Phase 0 需求澄清(disqualifier 的结构化扩展,[org-internal #3061]
预检不止于"能否分解",还须证明"需求已澄清到可分解"。两项强制检查:
**1. Scope item 可证伪性预检**:逐条检查 Epic 的 scope item。每条必须能改写为
一个可观察的通过条件(给定输入/操作 → 可观测的结果);写不出来的条目就是
澄清问题,列入 bounce 清单。禁止把不可证伪条目直接塞进 node AC。
**2. 横向完备性三态清单**:对下表每个维度做三态判定——**已覆盖**(scope 有
对应条目)/ **不适用**(一句话理由)/ **待澄清**(列具体问题)。任一维度停在
"待澄清" → bounce(澄清问题清单随 bounce 一起给出,不让用户猜缺什么):
| 维度 | 检查 |
| --- | --- |
| 外部系统 | 沿用 External-System Rule(见下文):每个外部交互有明确协议与失败语义 |
| 错误路径 | 关键流程的失败分支有定义(不是只写 happy path |
| 数据生命周期 | 数据的创建/保留/删除策略已明确 |
| 权限/安全 | 访问边界与敏感数据处理已明确(或显式声明"无安全面" |
| NFR | 性能/容量/兼容性约束已枚举(见下) |
| 边界条件 | 值域/并发/时序等边界有约定(或声明"无特殊边界" |
**NFR 输入来源明确化**NFR 不允许"分解时脑补"。Epic 必须自带 NFR 输入——
scope item 中带 `NFR:` 前缀的条目,或一行显式声明"无 NFR 约束"。两者皆缺 →
作为澄清问题 bounce,而不是默认无约束。node schema 的 `NFR:` 条目只能来自
该枚举,不得新增臆造。
## Node Schema (spec-03 §1)
```yaml
node:
id: "N-01" # task node N-{nn}; milestone node M-{nn}
title: "契约骨架" # one line
type: task # task | milestone
acceptance_criteria: # "requirements" view — one falsifiable AC per line
- "AC-1: <可证伪的验收条件, 映射 test_id>"
- "NFR: <产品 NFR 条目>" # NFR:-prefixed entry — enumerated storage, no REQ-F number
req_refs: # functional-requirement references
- "REQ-F-001"
status: pending # pending | ready | in_progress | done | blocked | green(仅里程碑)
owner_session: null # assigned session id, null when unassigned
size_attrs: # decomposition-time measured attributes (spec-06)
cross_session_in: 1
cross_session_out: 1
contract_change: additive # none | additive | breaking (out-edge aggregation)
estimated_hours: 8 # task nodes only; milestone nodes omitted
estimated_sessions: 1 # task nodes only; milestone nodes omitted
```
Field semantics:
- `acceptance_criteria` — the node **is** its spec. Each AC is falsifiable and
maps a `test_id``{ file-path::test-name | MANUAL | BENCH:<script> }`
(the plan-iterations §6 SDD→TDD handshake; no other values). Entries MAY be
`NFR:`-prefixed (product NFRs — written into node AC as enumerated storage;
they carry no `REQ-F` number and are not functional ACs).
- `req_refs` — functional-requirement ids this node covers, derived from the
requirement registry (see below). Drives the REQMAP "every requirement ≥1
node / every node ≥1 requirement" mechanical check.
- `status` — drives rolling release (spec-04 RELEASE). `ready` = all
cross-session in-edge source nodes reached their terminal state (task `done`,
milestone `green`; same-session edges do not block). `green` is **milestone
only** (a task node's terminal state is `done`).
- `size_attrs` — decomposition-time measured inputs; the DAG-level `dag_metrics`
aggregates them. `estimated_hours` / `estimated_sessions` are **required only
for `type: task` nodes** (milestone nodes have no implementation work and
omit them). The two fields are two units of the same estimate; the
consistency invariant is `|estimated_hours 8 × estimated_sessions| ≤ 2`,
and **granularity is always judged by `estimated_sessions`** (1 session ≈ 8h).
## Edge Schema (spec-03 §2)
```yaml
edge:
from: "N-01" # source node id
to: "N-02" # target node id
contract_ref: "shared/types-v1" # contract pointer (wiki {epic-slug}/shared/ or schema $id)
cross_session: true # from/to owned by different sessions?
contract_state: draft # draft | frozen (only meaningful for task-to-task cross-session edges)
change_type: additive # none | additive | breaking (only cross-session edges)
```
Field semantics:
- `contract_ref` — the "design" view: where the contract lives. Cross-session
task edges point at wiki `{epic-slug}/shared/` or a schema `$id`; same-session
contracts may be inlined.
- `cross_session` — contract freezing applies **only** to `true` edges (spec-02 §1 D-05).
Same-session edges are `cross_session: false` (zero freeze cost).
- `contract_state``draft` (initial, freely revisable) → `frozen` (after the
single-gate PASS). Only meaningful for task-to-task cross-session edges.
- `change_type` — the contract change surface, feeding `contract_change_surface`.
**Milestone edge exception (frozen, spec-03 §2 / spec-02 §2.1):**
a milestone is a session-less integration point, so every edge connected to a
milestone (in + out) is `cross_session: true` but carries **NO `contract_ref`,
NO `change_type`**, and `contract_state` is **not applicable** (never frozen).
Milestone edges do not participate in `contract_change_surface` aggregation
(treated as `none`).
## Topology Constraints (spec-03 §3 — must satisfy, or TOPO fails)
1. **Acyclicity** — the directed graph must have no cycle. A cycle = BLOCKER.
2. **Milestone weld rule** — any node with `cross_session_in ≥ 2` (≥2
cross-session in-edges converging on it) MUST have a milestone `M` welded at
the convergence point, such that:
- (a) all cross-session in-edges terminate at `M` (M **is** the convergence
point — fan-in edges end at M, no separate original convergence task node);
- (b) M has no implementation work (`type: milestone`, no
`acceptance_criteria`, only a DoD);
- (c) M's DoD = integration verification of the converged scope (spec-05);
- (d) M is the **必经 (must-pass) node** for all downstream consumers —
downstream nodes may not become `ready` before M is `green`.
A convergence point with no downstream consumer is a sink milestone (in-edges
only).
3. **Granularity floor** — every `type: task` node's implementation estimate is
carried by `size_attrs.estimated_hours` / `estimated_sessions` (milestones
omit both). Judged by `estimated_sessions` (1 session ≈ 8h). The SINGLE
SOURCE of the judging criteria — floor/ceiling bounds, the severity
mapping (field-inconsistency, below-floor, above-ceiling, giant node), and
the folded checklist rows — is `core/checklists/dag-single-gate.md`
§TOPO: recalibrate there, not here. (One-line summary: sessions far below
the floor → merge candidates; far above → split/giant; hours-sessions
mismatch → field inconsistency.)
## Requirement Registry (需求登记表, spec-02 §2.9)
Maintained at the DAG root. Each row:
```yaml
- id: "REQ-F-001" # derived from the Epic scope-item number
title: "<需求标题>"
source: "<来源 scope item>"
refs_by: ["N-01", "N-02"] # node ids that reference this requirement
```
Rules:
- **Only functional requirements** (Epic scope items) get `REQ-F-{NNN}` numbers
and rows in the registry.
- **Product NFRs do NOT get `REQ-F` numbers** and are NOT registered as rows —
they are written into node `acceptance_criteria` as `NFR:`-prefixed entries
(enumerated storage), so "every NFR ≥1 node" is mechanically checkable via
node-AC enumeration.
- `refs_by` is derived from the REQMAP "every requirement ≥1 node" criterion
(missing coverage = a requirement with no node).
## Breadth Coverage — 类目覆盖矩阵 + 排除账本 ([org-internal #2905] 方案 1)
The four-stage fold that produced the single DAG artifact kept requirement
**depth** (REQMAP three-path coverage + falsifiable AC) but dropped legacy
elicitation's **breadth** scanning ("还有什么别的需求?"-style category
sweep). The registry above only registers what the proposer wrote — a
requirement nobody wrote does not exist in the system, and REQMAP's
bidirectional mapping checks *in-table* consistency, never `表 ↔ 现实`
completeness. The coverage matrix is the mechanical, enumerable part of the
compensation (three layers: artifact = matrix, graph = probe nodes, data =
`dag.route.preflight` learning loop; this section is the artifact layer).
analyze-dag MUST emit both sections in `{epic-slug}/dag` (template
`core/templates/dag.md` §2.1/§2.2):
1. **类目覆盖矩阵 (coverage matrix)** — the fixed category set (≤10
categories, extracted from legacy
`<instance-root>/archive/checklists/requirements-analysis.md`
CMP/SAF dimensions) × state ∈ {已覆盖, 明确排除, 待确认}. The category
list is FIXED in the template — do not invent or drop categories per
Epic; state is per-Epic.
- **已覆盖** — the category has registered demand (registry `REQ-F-{NNN}`
row or a node `NFR:` entry); evidence cell cites the id(s).
- **明确排除** — out of scope, with a ledger row (below). An exclusion
without a ledger row is NOT valid.
- **待确认** — intermediate state; MUST carry (向{确认人}确认{什么}) so it
is distinguishable from 漏了. 待确认 rows SHOULD be driven to a terminal
state (已覆盖/明确排除) before the draft is submitted to review-dag.
2. **排除账本 (exclusion ledger)** — one `E-n` row per exclusion:
条目 — out of scope, 理由, 确认人. Exclusions are reversible (delete the
row + flip the matrix state in the same revision).
**明确排除 ≠ 漏了 (distinguishability)**: excluded = matrix row 明确排除 +
ledger `E-n` row with reason and confirmer; 漏了 = blank state, or an "N/A"
without a ledger row. The single gate does NOT gain criteria for this —
REQMAP's three tables are frozen (spec-04 §1) and `表 ↔ 现实`
completeness is a **production-side obligation** of analyze-dag; reviewers
may still raise findings on the matrix as ordinary artifact content.
**Relation to the Product-Epic External-System Rule**: that rule is the
external-integration category applied to product Epics — the matrix
generalizes its shape from 1 category to N. For a product-type Epic the
外部系统集成 category remains mandatory-covered (task node or cross-session
contract edge, see the rule below). Process-infrastructure Epics will
typically 明确排除 most product-facing categories — that is legitimate;
the ledger records why.
**Page-size budget**: the matrix + ledger participate in the ≤800-line
budget (spec-02 §2.6). If the page exceeds, sink category DETAIL to
sub-page `{epic-slug}/dag-coverage` using the same mechanics as AC sinking
(in-page pointer, return link above the status line, version stamp rides the
frozen copy) — purely organizational, NOT an oversize signal, no
re-derivation.
### Breadth probe nodes (P2, [org-internal #2905] 方案 2)
When a 待确认 category is cheapest to resolve empirically, a **probe node**
is a legal task node at the granularity floor:
```yaml
- id: "N-07"
title: "探查:{类目} 存量/可行性核对"
type: task
acceptance_criteria:
- "AC-1: 产出 {类目} 结论(进/不进登记表)— MANUAL"
req_refs: ["REQ-F-00n"] # 指向被其澄清的需求;探查本身不派生新 REQ-F
status: pending
size_attrs:
cross_session_in: 0
cross_session_out: 0
contract_change: none
estimated_hours: 2
estimated_sessions: 0.25 # 粒度下限(Topology Constraints 3)——合法,不得更低
```
- `MANUAL` is a legal `test_id` value — a probe's deliverable is a human
verdict (matrix flip), not an automated test.
- A probe node anchors to an **existing** requirement it clarifies
(`req_refs` non-empty — REQMAP 节点溯源 requires it). A 待确认 category
with NO anchor requirement is resolved by confirmation (向确认人问询),
not by a probe node.
- The probe's conclusion flips the category: → 已覆盖 (new registry row +
covering nodes) or → 明确排除 (new ledger row). A conclusion landing
BEFORE freeze is ordinary draft revision (no signal).
- **登记表单独加行 ≡ node_split**(显式化):post-freeze 新增登记表行(新需求)
`node_split` oversize 信号处理——触发重派生 + 无条件重跑 review-dag、修订
冻结副本(新增需求必然改变节点集合或 `req_refs`;需要新节点时全额按
node_split 建单传播)。
### Data layer — read `dag.route.preflight` BEFORE decomposition ([org-internal #2905] 方案 3)
Before starting decomposition, analyze-dag MUST read
`<instance-root>/workflow-routing.yaml` `dag.route.preflight` and, when the list is
non-empty, verify each item as a producer self-check (the same injection
contract `implement` applies for `routes.{Kind}.preflight`, [org-internal #2599] — landed
items carry their retro evidence in `evidence`):
1. Read the `dag:` block's `route.preflight` array (live yaml, repo root).
2. For each item: run its `item` self-check against the Epic's scope /
breadth matrix before writing the DAG (items are typically recurring
late-discovery categories, e.g. the retro-derived
`REQ × late-discovery` class — requirements that surfaced only after
previous DAGs froze).
3. An item that fires becomes a 待确认 matrix row (or a probe node, above)
— never silently dropped.
4. Absent/empty array → no injection, proceed (current state: the list is
empty until a retro cycle lands evidence).
## dag_metrics (spec-03 §4 / spec-06)
Stored at the DAG root / Epic aggregator. Keys are frozen (spec-02 §2.6):
```yaml
dag_metrics:
node_count: 6 # task + milestone nodes total
cross_session_edge_count: 7 # total cross_session:true edges (milestone edges counted too)
contract_change_surface: additive # none | additive | breaking
review_depth: D4 # derived tier D1D4
```
- `contract_change_surface`**worst-value aggregation** of `change_type` across
cross-session **task edges only** (`breaking > additive > none`). Milestone
edges are excluded (treated as `none` — no contract, no `change_type`).
- `review_depth` — derived (decomposition **is** the sizing, 分解即定档):
```
review_depth = max(
depth_by(node_count),
depth_by(cross_session_edge_count),
depth_by(contract_change_surface)
)
```
Thresholds (spec-06 §2 — authoritative):
| Measured attribute | D1 | D2 | D3 | D4 |
| --- | --- | --- | --- | --- |
| `node_count` | ≤3 | ≤8 | ≤15 | >15 |
| `cross_session_edge_count` | 0 | — | 13 | ≥4 |
| `contract_change_surface` | none | — | additive | breaking |
- `cross_session_edge_count`: 0 → D1; 13 → D3; ≥4 → D4. **D2 is not derivable
from this dimension.**
- `contract_change_surface`: none → D1; additive → D3; breaking → D4 (D2 not
derivable from this dimension).
- `review_depth` is the review-dag review-resource parameter (reviewers / round
cap, spec-06 §3) — analyze-dag computes it; it does not assign `Size/*`
labels (DAG-routed tickets ignore `Size/*`).
## Page-Size Budget (spec-02 §2.6)
> Threshold single source: the page-size budget and the `> 页尺寸自检: 超限`
> signal are judged by `core/checklists/dag-single-gate.md` (TOPO 页尺寸自检信号
> row) — recalibrate there, not here. (One-line summary: the `{epic-slug}/dag`
> body has a line budget (~800 lines at calibration time); on breach, sink
> node detail to sub-pages and emit the self-check signal per the checklist
> row.)
The `{epic-slug}/dag` page body — node table + edge table + requirement registry
+ breadth coverage matrix (§2.1) + exclusion ledger (§2.2) + `dag_metrics`
must fit the checklist's page-size budget (see the single-source note above;
~800 lines at calibration time). On breach, analyze-dag MUST:
1. Sink node AC detail into sub-pages `{epic-slug}/dag-nodes/{node-id}`
(sub-page naming `{epic-slug}/dag-nodes/{node-id}` is **frozen**);
2. Keep an in-page pointer `{node-id} → {epic-slug}/dag-nodes/{node-id}` for
each sunk node;
3. Put a return-link line on each sub-page, **above** its status line:
`> 返回 [{epic-slug}/dag](…)`;
4. Write a page-size self-check signal into the DAG page header, **parallel to**
the `> DAG 工件状态` line:
`> 页尺寸自检: 超限({node-id} 已下沉子页)`.
Category detail (matrix/ledger 细目) may likewise sink to
`{epic-slug}/dag-coverage` (same mechanics — see Breadth Coverage). This is
**NOT** an `oversize_signal` and does **NOT** trigger re-derivation —
page overflow does not change the three derivation inputs
(`node_count` / `cross_session_edge_count` / `contract_change_surface`); sub-page
sinking is purely organizational. (Re-derivation is triggered only by the four
spec-06 §5 oversize signals.)
## Product-Epic External-System Rule (spec-04 §2 ARCH 1.1 alternative)
For a **product-type** Epic, external systems and integration protocols MUST
explicitly appear in the DAG — **either** as task nodes (with acceptance
criteria) **or** as cross-session contract edges (`contract_ref` pointing at an
external contract). This is what lets the REQMAP "需求覆盖" (requirement
coverage) check mechanically verify that external-integration requirements map
to nodes. (The `ARCH 1.1` system-context diagram is discarded only for
process-infrastructure-type Epics; product Epics get this alternative rule
instead.) [org-internal #2905]: this rule is the 外部系统集成 category of the breadth
coverage matrix held at its original strength — see Breadth Coverage above.
## Exec-Resource Configuration (spec-07 §4 — mechanical destination of folded checklist rows)
This clause is the **mechanically checkable destination** of the folded plan
checklist rows `RISK 3.73.10` / `EST 6.36.12` / `EXE 8.78.11` (single-gate
does not re-audit these — they live here, in the skill). A analyze-dag run MUST
observe:
| Resource | Configuration |
| --- | --- |
| Model tier assignment | reasoning-tier for decomposition; fast-tier for mechanical transcription (node/edge/registry/dag_metrics table assembly). reviewer-tier — i.e. the `Reviewer` Explorer role (`_shared/roles/reviewer.yaml`; the vocabulary the review skills use for reviewer dispatch is agent type Explorer) — is used **only** via the review-dag gate (see `review-artifact` target review-dag "Reviewer Constants": Model tier) — **never** inside analyze-dag. |
| Token budget | a single analyze-dag agent call's estimated token consumption must stay within the model's context-window capacity, and the `{epic-slug}/dag` page body ≤ 800 lines (page-size budget) bounds the artifact's token footprint. |
| Context-window ceiling | keep single-call consumption ≤ 70% of the model's context window. |
| RAG supplement | if the Epic's scope touches knowledge newer than the model's training cutoff, configure RAG supplementation before decomposition. |
| API-unavailable fallback | define a fallback path (retry / degrade to a lower tier / pause and report) when the model API is unavailable. |
| Deterministic tasks | deterministic tasks (mechanical transcription) run at `temperature ≤ 0.1`. |
| AI-artifact origin annotation | the DAG artifact is annotated with its origin — model + temperature + timestamp. |
## Output & Lifecycle
1. analyze-dag writes wiki page `{epic-slug}/dag` with first line:
`> DAG 工件状态: draft`.
2. After the single-gate (`review-dag`) PASS, the **aggregator agent** (main
session / Orchestrator — not analyze-dag) asks the freeze confirmation
question ([org-internal #3379]), then on confirmation flips the page to
`> DAG 工件状态: frozen` and stamps version `v1`. The confirmation is an
OPTIONIZED `question` call (stage `review-dag`, never `destructive`
freeze is a process gate, not a business decision): options
`Freeze as v1 (Recommended)` / `Hold — back to review`, plus a one-line
summary of the gate verdict (rounds, worst severity, open waivers) in the
question text. Choosing hold = do NOT flip — leave the page `draft`,
record the hold and the reason as a comment on the source Epic, and route
back to `review-artifact`. An unconfirmed flip is a spec violation: the
flip MUST be preceded by the question call in the same session, and the
confirmation leaves a trace — post a comment on the source Epic
(`DAG frozen: {epic-slug}/dag v1 — confirmed via question, {date}`) before
flipping. In autonomous runs the question auto-approves via its stage
(choosing the recommended freeze) — the gate guarantees the decision
SURFACES with a defined shape at a defined moment, not that a human
always attends it.
3. On each post-freeze re-review PASS (an oversize-signal re-run, spec-06
§5), the aggregator agent updates the frozen copy in place and increments
the version stamp `vN → vN+1` (no re-confirmation — only the initial
freeze gates on a decision).
## Backfill Input Mode (Kind/MVP graduation & big-bug relabels, [org-internal #3061])
When a `Kind/MVP` ticket graduates (relabel → `Kind/Feature` → Step 0 → this
skill), the DAG is derived from **what was actually built**, not from greenfield
decomposition:
- **Inputs**: the ticket body's `## 决策日志` (one line per decision: what /
why / expiry) + `## Debt Register` section + codegraph reverse call graph
of the implemented surface.
- **Nodes** follow the code's actual module/contract structure; each node's AC
traces to a decision-log line or an existing test. The decision log is the
REQMAP baseline for the single gate — decisions still marked deferred/expires
become explicit open questions on the Epic, not silent assumptions.
- **Debt register items** map to node ACs when they describe unfinished
behavior; remaining shortcuts stay debt (prototype/EVOLUTIONARY path →
verify Phase 5.5 registration).
- **Big-bug relabel inputs** (a `Kind/Bug` relabeled `Kind/Feature` per the
big-bug rule, `rules/workflow-routing.md` §"Runtime growth signals
(post-sizing)" — big-bug relabel rule): the repro
+ root-cause notes (wiki page `{slug}/repro-notes`) carry over as node
input — node ACs trace to the reproduction steps and root-cause findings,
and the root-cause analysis (not a decision log) serves as the REQMAP
baseline for the single gate.
- Disqualifiers STILL apply in backfill mode: an MVP whose decision log shows
unresolved conflicting deferred decisions bounces (stabilize first) instead
of freezing a contradictory graph.
## What analyze-dag does NOT do
- **No synthesis.** analyze-dag does not produce a synthesis — the review-artifact skill
Phase E produces the single-gate synthesis after review-dag converges.
- **No `## 工件索引` issue comment.** analyze-dag is a sub-agent; it does NOT
create the Epic's `## 工件索引` comment. The aggregator agent / main session
creates and maintains that comment (issue-cross-linking Pattern 10), and the
review / verify stages report their rows to it — analyze-dag only publishes the
`{epic-slug}/dag` artifact.
## References
- `core/templates/dag.md` — the DAG artifact structure contract (this skill writes `{epic-slug}/dag` against it; node/edge/milestone/dag_metrics shape + §6 topology constraints)
- `dag-pipeline/spec-02` — frozen contracts (§2.1 node/edge, §2.6 dag_metrics + page-size budget, §2.9 requirement registry + test_id)
- `dag-pipeline/spec-03` — node/edge schema + topology constraints
- `dag-pipeline/spec-04` — single-gate TOPO/REQMAP/RELEASE dimensions (folded-row destinations)
- `dag-pipeline/spec-06` — dag_metrics derivation + thresholds
- `dag-pipeline/spec-07` — dag.route landing row (entry skill, skip set, single-gate) + §4 landing list
- `dag-pipeline/spec-08` — DAG artifact lifecycle / version stamp
- `<instance-root>/archive/skills/roadmap/SKILL.md` — legacy route (archived 2026-08-21, [org-internal #3072] phase 3; this skill replaces it for all Epics)
+297
View File
@@ -0,0 +1,297 @@
---
name: browser-debug
description: >
Use ONLY when an agent needs to interact with a live browser to debug, verify,
or iterate on Web UI work in packages/app — navigate to the dev server, snapshot
the DOM/accessibility tree, capture console and network errors, screenshot
component states, and exercise user flows end-to-end. Triggers on UI bugs,
visual regressions, layout/positioning issues, keyboard-focus problems,
console-error reports, and any "open the page and check X" task. Loads the
Playwright MCP toolset (browser_navigate, browser_snapshot,
browser_take_screenshot, browser_console_messages, browser_evaluate, etc.).
Use ONLY for interactive browser work; for writing Playwright *test files*
use the existing e2e/ suites under packages/app, not this skill.
triggers:
- browser debug
- open the page
- check the page
- screenshot the
- inspect element
- console error
- visual bug
- visual regression
- layout broken
- alignment off
- focus ring
- tab order
- 浏览器调试
- 打开页面
- 截图
- 视觉
- 布局错乱
- 对齐
- 焦点
- 控制台报错
role: Producer
---
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
# Browser Debug
Interactive browser automation for Web UI work in `packages/app`. This skill
teaches the *recipe* for driving the Playwright MCP toolset — when to reach
for it, which tool for which job, and how to capture evidence a reviewer can
trust.
## When this skill applies
Reach for `browser-debug` when a task requires **observing** the live
application, not just reasoning about code:
- Reproducing a UI bug (focus lost, dropdown won't close, scroll jumps).
- Verifying a visual change actually landed (computed style, layout box,
dark-mode token).
- Capturing console errors or failed network requests during a user flow.
- Recording a screenshot / trace as evidence for a review or DoD entry.
- Exercising a keyboard / screen-reader flow end-to-end.
Do **not** reach for it when:
- You are writing Playwright *test files* — those live in
`packages/app/e2e/...` and run via `cd packages/app && bun run test:e2e`
(variant suites: `test:e2e:perf`, `test:e2e:a11y`, `test:e2e:smoke`,
`test:e2e:visual`). This skill is for
ad-hoc, agent-driven sessions, not for adding tests.
- The question can be answered by reading code or running a unit test.
Browser time is expensive; spend it only when the live DOM is the oracle.
## Preconditions
1. **Playwright MCP is configured.** The project `octopus.jsonc` carries a
`mcp.playwright` block launching `@playwright/mcp`. If the MCP tools are
not visible, run `/kickoff` or follow the `codegraph-setup` skill's
recipe (the closest analog for debugging missing MCP servers).
2. **Dev server is running.** The app under test must be reachable. For
`packages/app`, run `bash script/dev-fresh.sh --full` for the dev server
(Windows workstations: `bash script/dev-fresh-windows.sh` — dev-fresh.sh's
stop path depends on setsid/ss, silently ineffective under Git Bash; see
local-workstation-quirks.md §5);
frontend is at `http://localhost:4444` and backend at `http://localhost:4096`
(see AGENTS.md Dev services section).
3. **Chromium is installed.** First run of `@playwright/mcp` downloads it
automatically; if the tool reports a missing browser, run
`bunx playwright install chromium` once in any shell.
## Tool selection recipe
The Playwright MCP exposes many tools. Pick by *what you need to observe*,
not by familiarity:
| Need | Tool | Why |
| --- | --- | --- |
| Open a URL | `browser_navigate` | Always the first call. |
| See the page structure (roles, names) | `browser_snapshot` | Returns the accessibility tree — stable across CSS refactors. Prefer this over `browser_take_screenshot` when asserting "does element X exist / is it labelled Y?". |
| See the page *visually* | `browser_take_screenshot` | For layout, spacing, color, overflow. Attach the PNG to your report. |
| Read console errors / warnings | `browser_console_messages` | Filter by level (`error`, `warn`). The first stop for any "page is blank" bug. |
| Read failed network calls | `browser_network_requests` | Locate 4xx/5xx, CORS failures, hanging fetches. |
| Click, type, hover, select | `browser_click`, `browser_type`, `browser_hover`, `browser_select_option` | Drive the flow. Always `browser_snapshot` first to get the `ref` for the target element. |
| Assert computed style / DOM state | `browser_evaluate` | Last resort. Snapshots cover 90%; reach for `evaluate` only when you need `getComputedStyle`, scroll positions, or arbitrary JS state. |
| Keyboard navigation | `browser_press_key` | For tab order, focus rings, escape-to-close. |
| Record a video / trace | `browser_start_tracing`, `browser_stop_tracing` | Heavy; use only when a bug is timing-sensitive and console+snapshot can't catch it. |
## Standard recipe — reproduce a UI bug
Follow these steps in order. Do not skip the snapshot step: the `ref` it
returns is the stable handle every subsequent tool needs.
1. `browser_navigate` to the page where the bug lives.
2. `browser_console_messages` — record any errors *before* touching anything.
Many "interaction bugs" are actually load-time errors.
3. `browser_snapshot` — locate the target element; capture its `ref` and
accessible name.
4. Drive the interaction with `browser_click` / `browser_type` /
`browser_press_key` until the bug reproduces.
5. Re-read `browser_console_messages` and `browser_network_requests`
capture the *delta* introduced by the interaction.
6. `browser_take_screenshot` (or a second `browser_snapshot`) to freeze the
broken state.
7. Report: URL, the minimal interaction sequence, console delta, network
delta, screenshot, and the suspected component path in
`packages/app/src/...`.
## Standard recipe — verify a UI fix
1. `browser_navigate` to the affected page.
2. `browser_snapshot` — confirm the fixed element is present with the
expected accessible name.
3. `browser_take_screenshot` — full page **and** element-cropped if the fix
is local.
4. If the fix touched layout, `browser_evaluate` a `getBoundingClientRect()`
or `getComputedStyle()` on the key node and paste the value into your
report. Numbers beat adjectives.
5. If the fix touched interaction, replay the original bug reproduction
steps and assert the previous failure no longer fires.
## Standard recipe — capture evidence for verify / DoD
When a `verify` work item has an E2E or UI DoD entry:
1. Run the recipe above for each DoD line.
2. Save each screenshot as `.playwright-mcp/dod-{item-id}-{state}.png`
the worktree's gitignored output dir. See "Screenshot output paths"
below.
3. In the verification report, cite the file names and tell the reviewer
where to look: open the worktree as an octopus project → session side
panel "Screenshots" tab (or the standalone screenshots page). Verify
with `GET /screenshots?directory={worktree}` before citing. A screenshot
the reviewer can open beats a paragraph of "I checked it".
4. When the DoD row will cite the evidence as a `BROWSER:{pack-ref}`
reference (UI-touching PRs, `verify` skill Phase 2.7 / contract
`browser-evidence-4486/shared/evidence-ref-v1`), promote the captures
into an evidence pack first — feed COPIES through
`writeEvidencePack` (`<harness-package>/src/browser/evidence-pack.ts`,
the single mandatory sanitize boundary; the `.playwright-mcp/` layer
keeps its current semantics, additive not replaced) and cite the pack
directory. Evidence ladder: raw capture → `.playwright-mcp/` working
layer (gitignored) → sanitized Tier-1 pack → `BROWSER:` reference the
verifier validates mechanically.
## Rationalizations to refuse
| Excuse | Reality |
| --- | --- |
| "The unit test already covers it." | Unit tests assert component logic, not what Chromium actually renders. Visual and focus bugs live in the gap. |
| "I can tell from the JSX that it's aligned." | Computed layout depends on parent containers, fonts, and viewport. Screenshot the real page. |
| "Browser automation is overkill for a small change." | A `browser_navigate` + `browser_take_screenshot` pair is ~2 seconds. The cost is in *not* looking. |
| "I'll just run the whole e2e suite." | The suite answers "did anything break?" — it does not answer "does this specific change look right?". |
| "Snapshot is enough, skip the screenshot." | Accessibility trees hide overflow, clipping, and z-index bugs. Take both for visual changes. |
## Boundaries
- **Read-only by default.** This skill never writes files under
`packages/app/src/` — it observes. Edits flow through `implement`
(bugfix/refactor/port mode).
- **One page at a time.** Close tabs (`browser_close`) between scenarios so
state doesn't leak.
- **Evidence lives in the worktree's `.playwright-mcp/` dir.** Any screenshot
or trace you reference in a PR comment, verification report, or DoD entry
must be copied into the workflow worktree's `.playwright-mcp/` directory
(gitignored) — never left in `/tmp/`, `playwright-report/`, or any
machine-local path the reviewer cannot reach through octopus. Cite file
names + the worktree project path; the reviewer opens the octopus
screenshots UI (session panel tab or screenshots page) to view them.
## Session cleanup (mandatory)
A Playwright MCP browser is a real Chrome with a `/tmp` user-data-dir — its
mode follows `mcp.playwright.headless` (`headless-field-v1`, [org-internal #4393]): the repo
default `auto` spawns headed on desktop (win32/darwin; linux only with
DISPLAY/WAYLAND_DISPLAY set) and headless otherwise; an explicit `true`/`false`
or `OCTOPUS_MCP_HEADLESS=1|0` pins it. The mode is fixed per new session —
switching the config does not hot-switch a running browser. It
stays alive as long as its MCP server runs, and the MCP server stays alive as
long as its parent octopus backend runs. When a browser-debug session ends
without an explicit `browser_close`, OR the octopus session times out mid-task,
the Chrome + its MCP server are orphaned and accumulate across sessions —
burning CPU and leaving dead profile dirs (issue [org-internal #1315]); in headed mode the
orphan is a visible stray window.
**Iron rule: every browser-debug session MUST close its browser before ending.**
1. **Always close when done.** The last action of any browser-debug task is
`browser_close`. Treat it like a `return` — if you opened a browser, you
close it. Do not rely on session teardown to do it for you; it doesn't.
2. **Close on error too.** If the task aborts (bug not reproduced, tool error,
user redirect), still call `browser_close` before moving on. An abandoned
debugging session is the most common orphan source.
3. **If a browser was already orphaned** (you spot a stale Chrome in `ps`, or
a `/tmp/playwright_chromiumdev_profile-*` dir with no live session), do not
leave it. `bash script/dev-fresh.sh --full` (Windows workstations:
`dev-fresh-windows.sh`, same stop-path caveat as Preconditions §2) reaps
orphan dev sessions and
init-adopted browsers before restarting; run it to clean up. For a targeted
clean without restarting the dev server, kill the Chrome by its profile:
`pkill -f 'playwright_chromiumdev_profile'` (and any `agent-browser` harness
reparented to init).
**Why this matters:** a single forgotten `browser_close` leaves a Chrome
subtree (8+ processes) running indefinitely. Over days this is hundreds of
orphaned processes and wasted CPU. The MCP server does not auto-close its
browser on disconnect — only an explicit `browser_close` (or process reaping)
releases it.
## Screenshot output paths
The Playwright MCP server is launched WITHOUT `--output-dir` (see
`octopus.jsonc``mcp.playwright.command`): its default output location is
`.playwright-mcp/` in the process cwd (the worktree root), gitignored and
auto-evicted at 100 MB via `--output-max-size`. Auto-named output (no
`filename`) lands there — but an explicit `filename` can resolve against the
cwd and drop the PNG into the repo root, where the `/*.png` gitignore
backstop hides it from `git status` and nothing ever cleans it up.
**Iron rule: 截图文件名必须写进 `.playwright-mcp/`。** Every `filename`
passed to `browser_take_screenshot` (or any tool that writes a file) MUST
resolve into `.playwright-mcp/` — write it as `.playwright-mcp/<name>.png`,
or use an absolute path under `/tmp/octopus/`. NEVER a bare relative name
like `2202-after-models.png`: it leaks a root-level PNG that gitignore masks
but nobody deletes (the 2026-08 repo-root buildup — 40+ orphan PNGs — came
from exactly this).
Two valid destinations for screenshots, by purpose:
| Purpose | Destination | Why |
| --- | --- | --- |
| **Ad-hoc / throwaway** (debug a layout, confirm a fix landed, never cited in any report) | `.playwright-mcp/<name>.png`, or omit `filename` to auto-save into the same dir | Survives across the session, auto-evicted at 100 MB, never pollutes the repo. |
| **Evidence cited in a PR / report / DoD entry** | `.playwright-mcp/<name>.png` — the cwd is the worktree root, so no copy step is needed | Zero repo bloat (gitignored, never committed); viewable by the reviewer through octopus's own screenshots UI (session panel "Screenshots" tab / standalone screenshots page, served by `GET /screenshots?directory={worktree}`); lifecycle bounded to the worktree = the review window. |
**Evidence publishing ladder** (verified 2026-08-14 on this Gitea version):
1. **Copy into the workflow worktree's `.playwright-mcp/`** — primary path.
Cite file names + worktree project path; reviewer views via the octopus
screenshots UI. Verify reachability with
`GET /screenshots?directory={worktree}` before citing. Never commit
evidence binaries to git — screenshots accumulate per PR and git history
is unreclaimable (measured ~80KB/PR today, unbounded growth). Do not put
routine debug screenshots on the wiki either — base64-in-page bloats the
wiki git repo equivalently; the wiki evidence namespace is reserved for
the durable verify-stage evidence channel (rung 3).
2. **Issue attachments API** (`POST /api/v1/repos/{o}/{r}/issues/{n}/assets`)
— alternative when the reviewer cannot reach the worktree through the
octopus UI. The endpoint exists but has returned 500 on this instance;
verify before relying on it.
3. **Publish to the wiki evidence namespace `{slug}/verify/evidence/{name}`
via `wiki 读写 API(见 TERMINOLOGY`** — last resort only, when no other channel
works AND the evidence must outlive the worktree (this is the same durable
channel `verify` mandates for DoD evidence entries — see
`core/skills/verify/SKILL.md` References). Keep it small:
≤5 images per PR, each ≤200KB (compress/downscale, prefer element
crops over full-page).
**Iron rule: never cite a machine-local path (`/tmp/...`, `~`, project
root) as evidence in a Tier-2 output** (PR comment, issue comment,
verification report). The reviewer must be able to open the evidence
through octopus (`.playwright-mcp/` + screenshots UI) or a repo/wiki URL
(worktree files, or the `{slug}/verify/evidence/{name}` wiki pages). A
citation they cannot reach is a fabricated citation.
Never write screenshots to the project root. The `.gitignore` rejects
`/*.png`, `/.playwright-mcp/`, and `/playwright-report/` only as a backstop —
an ignored file is still an orphan on disk. The `.playwright-mcp/` filename
prefix is the real fix; rely on that, not on the backstop.
## References
- `core/skills/frontend/SKILL.md` — where UI changes are planned; invoke
this skill during Phase 3 / Phase 4 to verify each state visually.
- `core/skills/implement/SKILL.md` — Frontend Mode; pair with
`browser-debug` when the work item touches rendering.
- `core/skills/implement/SKILL.md` (Mode: bugfix) — Phase 1 reproduction; use this skill
to capture the failing state before isolating root cause.
- `core/skills/verify/SKILL.md` — Phase 2 / DoD matrix; use this skill
to generate screenshot evidence for UI-tagged DoD entries.
- `packages/app/e2e/` — permanent Playwright test suites; this skill is the
ad-hoc complement, not a replacement.
- `rules/dev-server` on the wiki — how to run the dev server for TUI work;
consult `packages/app/README.md` for the web equivalent.
+213
View File
@@ -0,0 +1,213 @@
---
name: codegraph-setup
description: >
Use ONLY when installing, configuring, initializing, or troubleshooting CodeGraph
(@colbymchenry/codegraph — https://github.com/colbymchenry/codegraph) in the
octopus environment. Covers global CLI install, per-project `.codegraph/`
indexing, and maintenance commands (status, sync, upgrade, uninstall). MCP
wiring into octopus.jsonc is handled by the kickoff pipeline
(`octopus kickoff` or `/kickoff`). Also use when a project session reports a
missing/stale `.codegraph/` index.
triggers:
- codegraph
- code graph
- codegraph setup
- codegraph install
- codegraph init
- .codegraph
- codegraph_explore
- codegraph_node
- codegraph_callers
- codegraph_status
- install codegraph
# Chinese — bare nouns for code-graph concepts. The English "codegraph" brand
# name above already covers mixed-language phrasings; these cover pure Chinese.
- 代码图
- 代码图谱
- 代码索引
- 索引代码
- 装代码图
- 初始化代码图
role: Producer
---
# CodeGraph Setup in the Octopus Environment
CodeGraph is a local-first code intelligence layer (https://github.com/colbymchenry/codegraph)
that octopus consumes as a **stdio MCP server**. It exposes the `codegraph_explore`,
`codegraph_node`, `codegraph_callers`, `codegraph_search`, and `codegraph_status`
tools, letting agents query a pre-indexed knowledge graph instead of scanning files.
Per `.octopus/rules/code-graph.md` (the L1 code-graph rule): **Code Graph First is mandatory.**
Structural questions MUST use `codegraph_explore` / `codegraph_search` /
`codegraph_callers`; grep is the fallback for fuzzy text only. So a working
CodeGraph install is a prerequisite for every cross-file task.
Setup has two independent layers. Each can be installed/reinstalled without
touching the others:
1. **Global CLI** — the `codegraph` binary, installed once per machine.
2. **Per-project index** — the `.codegraph/` directory, built once per repo.
MCP wiring into octopus.jsonc is handled by the kickoff pipeline
(`octopus kickoff` or `/kickoff`). See the `project-kickoff` skill for details.
---
## 1. Install the global CLI
The package is `@colbymchenry/codegraph` on npm. It bundles its own runtime and
ships platform-specific binaries as optional dependencies (e.g.
`@colbymchenry/codegraph-linux-x64`), so there is nothing to compile.
Pick one method (all equivalent; they only differ in how the binary lands on
`$PATH`):
```bash
# Via bun global (what this environment currently uses)
bun add -g @colbymchenry/codegraph
# Via npm global
npm i -g @colbymchenry/codegraph
# Run-once, no install
npx @colbymchenry/codegraph
# Via the official install scripts (puts `codegraph` on PATH, no shell change)
curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh # macOS / Linux
irm https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.ps1 | iex # Windows (PowerShell)
```
**In this environment**, the install is bun-global:
| Item | Value |
|------|-------|
| Binary on PATH | `~/.bun/bin/codegraph` (symlink) |
| Real package dir | `~/.bun/install/global/node_modules/@colbymchenry/codegraph/` |
| Entry point | `npm-shim.js` → resolves to the platform binary |
| Platform binary | `@colbymchenry/codegraph-linux-x64` |
| Version | `1.0.0` |
Verify the install:
```bash
codegraph --version # prints the installed version
which codegraph # should resolve under ~/.bun/bin or ~/.npm-global/bin
```
Upgrade any time — it auto-detects how you installed:
```bash
codegraph upgrade # upgrade to latest
codegraph upgrade --check # only report if an update exists
codegraph upgrade 1.2.3 # pin a specific version
```
---
## 2. Initialize the per-project index
Run once from inside each project you want indexed. `-i` builds the initial
graph in the same step; omit it to create the `.codegraph/` dir and build
later with `codegraph index`.
```bash
codegraph init -i # create .codegraph/ AND build the initial graph
codegraph init # create .codegraph/ only; run `codegraph index` afterwards
```
This creates (relative to the project root):
```
.codegraph/
├── .gitignore # ignores itself; safe to leave uncommitted
├── codegraph.db # the SQLite knowledge graph (full WAL mode)
├── codegraph.db-shm # SQLite shared-memory (WAL)
├── codegraph.db-wal # SQLite write-ahead log
├── daemon.log # watcher/MCP daemon log
├── daemon.pid # daemon process id
└── daemon.sock # daemon IPC socket
```
`codegraph init` writes a `.gitignore` inside `.codegraph/`, so the index dir
is normally left uncommitted (it is machine- and version-specific). If you
choose to commit it anyway, add `.codegraph/*.db*` to the repo `.gitignore`
instead and treat the index as a build artifact.
**In this environment** the index is already built:
| Metric | Value |
|--------|-------|
| Files indexed | 1,868 |
| Nodes | 32,592 |
| Edges | 118,912 |
| DB size | ~85 MB |
| Backend | `node:sqlite` — built-in (full WAL) |
Check any project's own stats at any time:
```bash
codegraph status
```
### Auto-sync — you do not normally run `codegraph sync`
Once the MCP server (`codegraph serve --mcp`) is running, three layers keep the
index in step with your code without manual sync:
1. **Debounced file watcher.** A native FSEvents (macOS) / inotify (Linux) /
ReadDirectoryChangesW (Windows) watcher captures every create/modify/delete
and re-indexes after a debounce. Default `2000 ms`; tune via
`CODEGRAPH_WATCH_DEBOUNCE_MS` (clamped to `[100ms, 60s]`).
2. **Connect-time catch-up.** On (re)connect the server reconciles
`(size, mtime)` + content-hash against the working tree, so edits made while
no MCP server was running (a `git pull` from the terminal, another editor, a
previous session that exited) are absorbed before the first query.
3. **On-demand sync.** `codegraph sync` is available as an escape hatch.
Disable the watcher on slow filesystems (WSL2 `/mnt` drives, network mounts):
```bash
codegraph serve --mcp --no-watch
```
### Workflow worktrees get their own index ([org-internal #2561] / TD-006)
The main checkout's `.codegraph/` does NOT cover git worktrees — they live
outside the repo path, so nearest-`.codegraph/` resolution finds nothing under
them and graph queries degrade to grep+read. Both worktree lifecycle scripts
hook a per-worktree init into their `create` flow:
- `script/session-worktree.sh create <slug>` and
`script/scratch-worktree.sh create <slug> <base>` run
`codegraph init -i <worktree-path>` in the background. Best-effort: a
missing CLI, an existing index, or an init failure never blocks worktree
creation — the ready/failed signal lands in `<worktree>/.codegraph/init.log`
(one line). The background subshell never holds the caller's stdout pipe, so
output-capturing callers (CI, nested scripts) are not blocked either.
- `CODEGRAPH_SKIP=1` disables the hook (selftests / CI).
- Worktrees created before this hook landed have no index — run
`codegraph init -i` once from inside them.
- After create, the index builds asynchronously (a full repo takes ~minutes).
Verify readiness with `codegraph status` INSIDE the worktree (`Files > 0`,
`Nodes > 0`) before relying on graph queries.
---
## 3. Verification checklist
After a fresh setup, confirm each layer is working:
1. **CLI**`codegraph --version` prints a version; `which codegraph` resolves.
2. **Index**`codegraph status` from the project root shows `Files > 0` and
`Nodes > 0`; `.codegraph/codegraph.db` exists.
---
## 4. How octopus uses CodeGraph (usage rules)
This skill covers **setup** only. Usage conventions (query the graph before
reading; `codegraph_explore` for flows, `codegraph_search`/`codegraph_callers`
for symbols; `grep` is the fallback) are normative in `.octopus/rules/code-graph.md` — refer there. The short version: run `codegraph_status`
on session start, and prefer graph tools over grep+read for any structural
question.
+322
View File
@@ -0,0 +1,322 @@
---
name: frontend
description: >
Use ONLY when doing frontend development work — building or modifying UI
components, pages, styles, and client-side interactions. Detects the
project's framework and styling approach, then implements with a plan
template, mandatory loading/empty/error/success state coverage, and an
accessibility + self-check pass against core/checklists/frontend.md.
For full pipeline features (a new page/route, ≥3 new components, new API
contracts or a new data model, a new state-management pattern, or work
spanning ≥5 files), use the `implement` skill and apply these templates
within it.
triggers:
# English — phrases + UI-element nouns (short nouns are safe here: they
# rarely embed in non-frontend words, and the description gates false fires).
- create component
- build ui
- implement frontend
- add page
- add screen
- implement page
- frontend
- UI component
- style this
- add styles
- make it responsive
- component
- button
- modal
- dropdown
- tooltip
- navbar
- checkbox
- toggle
# Chinese — high-frequency bare nouns only. Because matching is substring,
# a bare noun (组件) subsumes every verb-noun combo (写组件 / 做个组件 /
# 创建组件 / 改组件), so listing phrases is redundant. Chinese substrings
# do not collide with English words, so bare nouns are safe + maximal-recall.
- 前端
- 组件
- 页面
- 样式
- 按钮
- 表单
- 弹窗
- 模态框
- 输入框
- 导航
- 菜单
- 卡片
- 下拉
- 标签页
- 界面
- 交互
- 切图
- 响应式
role: Producer
---
# Frontend
Surgically implement frontend UI — components, pages, styles, and client-side
interactions — guided by framework conventions, with mandatory state-coverage
and accessibility checks before code review.
## Scope & Routing
This skill covers **standalone** frontend work: one or two components, a style
tweak, a single page or UI element. The user's description plus the existing
codebase is the specification.
Escalate to the `implement` skill (full pipeline: analyze-dag → review-dag →
implement → review-code → verify) when the work involves ANY of:
- ≥ 3 new components, or a new page/route
- new API contracts or a new data model
- a new state-management pattern
- ≥ 5 files changed
For pipeline work, still apply this skill's **Framework Detection**, **plan &
report templates**, and **self-check** within the `implement` workflow.
## Agent Role
Owned by the **Developer** (Worker). Responsibilities:
- Detect framework + styling from the existing code; match conventions exactly.
- Read ≥ 3 neighbor components before writing (brownfield).
- Implement all UI states: loading, empty, error, success, and edge cases.
- Ensure accessibility: semantic HTML, ARIA, keyboard nav, focus, contrast.
- Self-check against `core/checklists/frontend.md` before handoff.
- Write no more and no less than the scope — no opportunistic refactoring.
The Builder validates output and passes it to code review; it MUST NOT write
implementation code.
<!-- inject: ../_shared/large-prompts.md -->
> **Context compaction**: this skill is a pipeline stage boundary. The main
> session (orchestrator) compacts at this clean boundary ONLY when a
> capacity/projection trigger holds, per the L1 rule `core/rules/compact.md`
> §"Stage-boundary compaction" (long multi-stage runs — DAG Epic orchestration — keep the legacy
> every-boundary compaction; short runs — bugfix / DAG task — and standalone
> runs default to NOT compacting). The
> sub-agent this skill dispatches persists its artifacts to the Gitea wiki
> under `{slug}/` as it goes, so a mid-run compaction loses nothing — re-read
> the stage's wiki index to resume.
## Framework Detection
Before writing any code, detect the project's frontend framework:
| Signal | Framework |
| --------------------------------- | ---------- |
| `package.json` has `react` | React |
| `package.json` has `vue` | Vue |
| `package.json` has `svelte` | Svelte |
| `package.json` has `solid-js` | SolidJS |
| `package.json` has `@angular/core`| Angular |
| `.tsx`/`.jsx` files present | React or SolidJS (check package.json) |
| `.vue` files present | Vue |
| `.svelte` files present | Svelte |
Detect the styling approach:
| Signal | Approach |
| ----------------------------------------- | ----------------- |
| `tailwind.config.*` or `postcss.config.*` with tailwind | Tailwind CSS |
| `.module.css` or `.module.scss` files | CSS Modules |
| `styled-components` in package.json | styled-components |
| `@emotion/*` in package.json | Emotion |
| `uno.config.*` | UnoCSS |
| Plain `.css` or `.scss` imports | Plain CSS/SCSS |
Follow the detected convention exactly. Do NOT introduce a new styling
approach unless the work item explicitly requires it.
## Greenfield vs. Brownfield
**Greenfield** (new project): Create new files following the design. Use the
framework's standard conventions (`create-vite`, `create-next-app`, etc.) as
the baseline. Prefer functional components, TypeScript, and the framework's
current recommended patterns.
**Brownfield** (existing project + new UI):
- **Read neighbors first.** Before writing code, read at least 3 existing
files in the same module (component files for UI work) to absorb the
project's patterns: component structure, prop
typing, styling approach, state management, and file organization.
(Shared brownfield rule — canonical statement:
`core/skills/review-code/SKILL.md` §"Greenfield vs. Brownfield".)
- Match existing conventions exactly: component declaration style, export
pattern, file naming, directory structure, import ordering, and CSS
organization.
- New code MUST follow existing conventions consistently — no style drift.
- No opportunistic refactoring of unrelated components. If you see a pattern
violation, log it in the implementation report — do not fix it.
---
## Workflow
### Preconditions
- [ ] Task description exists (component name, props, behavior).
- [ ] Existing codebase is accessible for convention discovery.
- [ ] `core/checklists/frontend.md` is accessible.
If the task is vague ("make it look better"), ask for specifics:
1. What component/page needs work? (name or path)
2. What should it look like or do? (screenshot, description, or reference)
3. What states should it handle? (loading, empty, error, success)
4. Any accessibility requirements?
### Phase 1 — Parse Context
1. Detect framework and styling approach (see Framework Detection).
2. Read ≥ 3 neighbor components to absorb patterns.
3. Check existing tests — follow the same test pattern.
4. Check existing routes if adding a page.
5. Check design tokens (theme, spacing, palette) if the project has them.
### Phase 2 — Plan UI Implementation
Before writing code, produce a brief implementation plan:
```markdown
## Frontend Implementation Plan
**Component(s)**: {ComponentName} at {path}
**Framework**: {React / Vue / Svelte / SolidJS / Angular}
**Styling**: {Tailwind / CSS Modules / styled-components / plain CSS}
**State variants**: loading | empty | error | success | {edge case}
**Files to create**:
- `path/to/Component.tsx` — {purpose}
**Files to modify**:
- `path/to/existing.tsx` — {what changes, why}
**Component API**:
- Props: {prop}: {type} — {description}
- Events/Callbacks: {onX}: {signature} — {description}
- Slots/Children: {description if applicable}
**Accessibility checklist**:
- [ ] Semantic HTML elements used
- [ ] ARIA labels for icon-only buttons/images
- [ ] Keyboard navigation (Tab order, Enter/Space for actions)
- [ ] Focus management (auto-focus, focus trapping for modals)
- [ ] Color contrast ≥ 4.5:1 for text, ≥ 3:1 for large text
```
### Phase 3 — Implement
Implement in layers:
1. **Structure first** — scaffold the component with correct HTML semantics
and prop types. No styling yet.
2. **Add styling** — apply styles following the project's convention. Handle
responsive breakpoints if the design specifies them.
3. **Add state variants** — implement loading, empty, error, and edge case
states before the happy path. This forces you to handle all conditions.
4. **Wire interactivity** — add event handlers, form validation, keyboard
shortcuts, and focus management.
5. **Accessibility pass** — audit every interactive element for keyboard
access, every image/icon for alt text, every form control for labels.
**Rules**:
- Use the project's existing component library (Kobalte, Radix, Headless UI,
etc.) where applicable — do not reinvent accessible primitives.
- Every component MUST render gracefully in all four states: loading, empty,
error, and success.
- If the project uses TypeScript, all props must be typed — no `any`.
- Image `alt` text must be meaningful, not decorative if the image conveys
information.
- Form inputs must have associated `<label>` elements (not just placeholders).
### Phase 4 — Self-Check
Run the project's verification commands:
1. **Typecheck**: `bun typecheck` (or project equivalent). Fix all errors.
2. **Lint**: `bun oxlint --deny-warnings` (repo root; `bun lint` is the package-script alias). Fix all errors.
3. **Tests**: `bun run test:changed` (or project equivalent). All affected tests pass.
Then self-check against `core/checklists/frontend.md`:
- Verify every PRE item was satisfied before coding.
- Verify every POST item is satisfied now.
- For any failed item, fix before reporting.
### Phase 5 — Report
```markdown
## Frontend Implementation Report
**Component(s)**: {ComponentName}
**Framework**: {framework}
**Styling**: {approach}
### Files Changed
| File | Action | Purpose |
| ------------------ | -------- | -------------- |
| `path/to/file.tsx` | created | {purpose} |
| `path/to/file.css` | created | {purpose} |
### State Coverage
| State | Handled | How |
| ------- | ------- | -------------------------------- |
| loading | ✅ | Skeleton/spinner while fetching |
| empty | ✅ | "No items" message with CTA |
| error | ✅ | Error message with retry button |
| success | ✅ | Renders data as designed |
### Accessibility
| Check | Status | Notes |
| --------------------------- | ------ | -------------- |
| Semantic HTML | ✅ | |
| Keyboard navigation | ✅ | |
| Focus management | ✅ | |
| Color contrast | ✅ | |
| Screen reader labels | ✅ | |
### Verification
- Typecheck: {pass / fail}
- Lint: {pass / fail}
- Tests: {N} passed, {M} failed
### Open Items
{anything incomplete with reason, or "None"}
---
**Handoff**: {if > 20 lines or ≥ 3 files → run review-code | else → complete}
```
### Phase 6 — Approval
Present the report to the user. Route to code review if > 20 lines or ≥ 3 files.
---
## References
- `core/checklists/frontend.md` — Frontend self-check checklist
- `core/skills/implement/SKILL.md` — General + pipeline implementation workflow
- `core/skills/review-code/SKILL.md` — Code review (next step)
- `<instance-root>/archive/skills/design/SKILL.md` — Design document production (legacy upstream, archived [org-internal #3072] phase 3)
- `core/skills/browser-debug/SKILL.md` — Interactive browser verification (invoke during Phase 3 / 4 for visual + interaction evidence)
- `core/rules/testing.md`, `core/rules/type-checking.md` — Repository conventions (test commands, typecheck)
- `core/rules/code-graph.md` — Code graph first
- `core/adapters/gitea/reading.md` — Read SDLC artifacts from wiki
+183
View File
@@ -0,0 +1,183 @@
---
name: gitea-rest
description: Use ONLY when an agent must call the Gitea REST API over HTTP (curl / fetch / HttpClient) 鈥?not for other gitea tooling (the retired gitea-mcp wrapper / gitea_* MCP tools). Covers auth schemes + token scopes, pagination, error format, this fork's specifics, copy-paste recipes for the daily loop (issues, PRs, comments, labels, releases, raw files), and the on-demand recipe for extracting any endpoint's definition from the auto-generated swagger spec (`/swagger.v1.json`) instead of keeping an endpoint catalog in context. This is the only supported Gitea API path 鈥?the gitea-mcp wrapper is retired.
triggers:
# Direct Gitea HTTP/REST. Composite "gitea rest"/"gitea api" to avoid
# colliding with other gitea tooling.
- gitea rest
- gitea api
- gitea http
- call gitea api
- /api/v1
- gitea swagger
- swagger.v1.json
- 璋?gitea api
- gitea 鎺ュ彛
- gitea rest api
role: Producer
---
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛夈€傛湰鏂囦欢鍚屾椂浣滀负 gitea adapter 鐨?**reference implementation** 绀轰緥锛坒rontmatter 鍘熸牱锛夛紱瀹炰緥鏈瀵圭収 `core/adapters/TERMINOLOGY.md`銆?
# Gitea REST 鈥?conventions + on-demand endpoint lookup
Gitea ships a JSON REST API under **`/api/v1`**. The instance also publishes an
**auto-generated OpenAPI spec at `/swagger.v1.json`** (~889KB, 316 endpoints) 鈥?
never load it whole into context; extract single endpoint definitions with jq
(see "Endpoint lookup"). The spec is generated from this fork's code, so it is
always current 鈥?no manually maintained catalog can drift.
## Quick start
```sh
BASE=<instance-base-url>
TOKEN=$(cat <config-home>/octopus/gitea-token) # or $WORKSPACE_GIT_TOKEN inside a workspace container
AUTH="Authorization: token $TOKEN"
curl -fsS -H "$AUTH" $BASE/api/v1/version # => {"version":"1.22.0"}
```
Inside a workspace container: `WORKSPACE_GIT_TOKEN` already carries the
provisioned token, and the instance host resolves to the VPC-internal address
(no EIP bandwidth cap) 鈥?use it as `TOKEN` directly.
## Authentication
| Scheme | Header | Notes |
| --------------------- | ------------------------------------------ | ----------------------------------------------- |
| **PAT (recommended)** | `Authorization: token <PAT>` | Also accepts `bearer`. 40-char hex. |
| OAuth2 JWT | `Authorization: bearer <jwt>` | OAuth2-app token. |
| Basic | `Authorization: Basic <base64(user:pass)>` | Server must enable it; 2FA needs `X-Gitea-OTP`. |
- Query-param token (`?token=`) is deprecated 鈥?avoid.
- `Sudo: <username>` header acts as another user (admin only).
- Keep tokens out of git and out of process argv where possible: read from a
file/env into a shell var at call time, not in a committed script.
## Token scopes (write implies read)
Categories: `issue`, `repository`, `organization`, `user`, `notification`,
`package`, `admin`, `misc`, `activitypub` 鈥?each as `read:X` / `write:X`.
HTTP method sets the level: **GET 鈫?read**, **POST/PUT/PATCH/DELETE 鈫?write**.
A scope miss returns `403` with `token does not have at least one of required
scope(s)...` 鈥?re-issue the token rather than widening other permissions.
The full agent loop (push commits, create/merge PRs, comment) needs at minimum
**`write:repository` + `write:issue`**. Workspace container tokens are
provisioned `read:repository` by default 鈥?write operations fail with 403
until the operator widens the scope.
## Pagination
- `page` (1-based, default 1), `limit` (default 30, **hard cap 50**).
- `X-Total-Count` response header = total results.
- `Link: <url?page=2>; rel="next"` 鈥?walk `next` links until absent.
- Some endpoints (commits, pulls) also set `X-Page` / `X-PerPage` /
`X-PageCount` / `X-HasMore`.
## Errors
Every error is `{"message":"...","url":".../api/swagger"}` with the matching
status: `400` bad arg, `403` permission/scope, `404` not found or wrong BASE,
`409` already exists, `413` too large, `422` malformed JSON / missing field.
There is **no rate limiting** on `/api/v1` 鈥?don't expect `X-RateLimit-*`.
## Endpoint lookup (swagger, on demand)
For any endpoint not covered by the recipes below, pull the spec once per
session and jq out just the definition you need (50鈥?00 tokens each):
```sh
curl -s $BASE/swagger.v1.json -o /tmp/gitea-sw.json
jq '.paths["/repos/{owner}/{repo}/pulls"].post' /tmp/gitea-sw.json # create PR
jq '.paths["/repos/{owner}/{repo}/issues"].get.parameters' /tmp/gitea-sw.json
jq '.definitions.CreateIssueOption' /tmp/gitea-sw.json # a body model
jq -r '.paths | keys[]' /tmp/gitea-sw.json | grep actions # discover endpoints
```
Definitions include parameter names/types/required flags and `$ref` response
models (under `.definitions` on this Gitea version). Prefer discovering via
the spec over guessing paths.
## Daily-loop recipes
`BASE`/`TOKEN`/`AUTH` as in Quick start; `OWNER=Octopus REPO=octopus` as
the example. All bodies are JSON.
**Create an issue**
```sh
curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"title":"Bug: X fails","body":"steps...","labels":[12]}'
```
**Comment on an issue / PR** (same path for both 鈥?PRs are issues by index)
```sh
curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues/42/comments" -H "$AUTH" \
-H 'Content-Type: application/json' -d '{"body":"LGTM"}'
```
**Create a PR**
```sh
curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"head":"workflow/fix/x","base":"main","title":"fix: X"}'
```
**Merge a PR**
```sh
curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls/7/merge" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"Do":"merge","MergeTitleField":"feat: add X","MergeMessageField":"closes #7"}'
```
**Merge returned 405? Read the response body 鈥?two distinct causes:**
- `{"message":"head branch is behind the base branch"}` 鈥?the keep-mergeable
bot advanced main after your push, or Gitea's mergeable cache is stale.
Fix: `git fetch origin main && git merge origin/main && git push` the head
branch, wait ~2s, retry the merge. (Recurring on this server 鈥?seen 3 PRs
in a row on 2026-08-23.)
- `{"message":"The PR is already merged"}` 鈥?**treat as success**. The merge
landed during a previous "failed" attempt (racy async recompute). Confirm
with `GET .../pulls/7 | jq '.merged, .merge_commit_sha'` before assuming
failure. Never re-push/re-create the PR on this signal.
**Auth hygiene** 鈥?always pass the token via the `Authorization` header
(`-H "Authorization: token $TOKEN"`), never embedded in a remote URL
(`https://user:token@host/...`): it persists into `.git/config` and shell
history. If a token leaks into a remote URL, rewrite the remote
(`git remote set-url`) and rotate.
**List PRs / changed files** 鈥?`GET .../pulls?state=open`, `GET .../pulls/7/files`
**Search issues across repos** 鈥?`GET $BASE/api/v1/repos/issues/search?q=&type=pulls`
**List labels** 鈥?`GET .../labels?limit=50 | jq '.[] | {name,color}'` (label create/update via POST/PATCH on the same path)
**Raw file contents** 鈥?`GET .../raw/README.md` (plain text; the `contents/{path}` variant returns base64 in `.content`)
**Create a release** 鈥?`POST .../releases {"tag_name":"v1.2.0","target":"main",...}`
**Create a wiki page** 鈥?`POST .../wiki/new {"title":"My Page","content_base64":"<b64>","message":"add page"}`. **`content_base64` is the ONLY field that writes body text** 鈥?this fork **silently ignores** the upstream-style `content` field on wiki create/update (no error, `201` returned, page saved 0 bytes; evidence [org-internal #3944]: commits f5eaf18/4f0217d/bad5880). `content` is valid only on the file API, never on wiki.
**Read / edit / delete a wiki page** 鈥?`GET|PATCH|DELETE .../wiki/page/{pageName}`. **Read by the mangled name, not the logical title**: this fork rewrites stored filenames for titles containing `/`, spaces, or `:` (slash percent-encoded into the filename + a `.-` suffix 鈥?see [org-internal #3218]), so `GET /wiki/page/bugfix-3204/bugfix-report` 404s. Always `GET .../wiki/pages` first and use the returned `sub_url` **verbatim** (it already carries the `%2F` encoding and `.-` suffix). PATCH/DELETE take the same mangled `pageName`. For PATCH, **omit `title` to keep the page name** and send only `content_base64`+`message` (fixed in gitea `dev-421-g7ff56aec08`, [org-internal #3510] 鈥?before that a title-less PATCH silently renamed the page to `unnamed.md` and later edits deleted target pages). Same trap as create: a PATCH carrying `content` instead of `content_base64` returns `200` with a **0-byte page** ([org-internal #3944]) 鈥?always base64-encode the body and verify non-empty via a follow-up `GET`.
## This fork's specifics
- **Projects are repo-level only** 鈥?no `/orgs/.../projects` or `/users/.../projects` REST endpoints.
- **Aggregated inline review comments**: no single list-all endpoint 鈥?list
reviews, then fetch each review's `/comments`.
- Wiki REST = 6 method脳path combos on 4 paths (`new`, `page/{pageName}`
GET/PATCH/DELETE, `pages`, `revisions/{pageName}`). This fork has **no**
upstream `raw/{pageName}` or `pages/{pageName}` single-page endpoints, and
`page/{pageName}` only matches the mangled filename (see the wiki recipe
above and [org-internal #3218]) 鈥?clone the wiki git repo (default branch `main`) only if
you need history beyond `/wiki/revisions` or bulk filename surgery.
- The authoritative route table is `routers/api/v1/api.go` in the Gitea
source; the human-readable docs UI is `<BASE>/api/swagger`.
## In Effect code
Prefer `HttpClient` from `@effect/platform` with the same headers (see
`rules/effect-rules` on the wiki). Outside Effect, `curl` / `Bun.fetch` are
fine 鈥?the wire format is identical.
+547
View File
@@ -0,0 +1,547 @@
---
name: headless-session-ops
description: >
Use ONLY when an agent must autonomously launch a headless main session 鈥?
create a session and drive agent generation over the HTTP `prompt_async`
endpoint with NO persistent client attached (Web UI / TUI absent) 鈥?OR
lifecycle-manage such a session afterwards: health-check polling, hung-stream
diagnosis (state=generating + frozen message count), abort + re-wake rescue,
provider quota-wall recovery (worker killed mid-task by a usage limit 鈥?
detect, re-wake on a known-good provider, inject facts), and fact-baseline
injection to correct a stale worldview in a woken worker.
This is the path used by in-session agents spawning sibling sessions,
`octopus run --attach` non-interactive mode, and cron / CI drivers. Covers
the 3-step flow (create session 鈫?POST prompt_async WITH a known-good
`model` 鈫?poll `/session/:id/message` to verify generation), how to REUSE
the current session's model for the new session, the model-field
requirement, the directory default (current session's directory unless
targeting another repo), and the ticket-driven recipe (launch a session
seeded from issue #N). Do NOT use when a persistent client (Web UI / TUI)
is driving the session 鈥?those use the synchronous `prompt` endpoint and
always send `model`.
triggers:
# English 鈥?the brand phrases for headless launch.
- headless session
- launch session
- prompt_async
- prompt async
- autonomous session
# English 鈥?the failure mode (agent sees a stalled session and needs to know why).
- session not generating
- session generation stuck
# English 鈥?ticket-driven launch.
- launch session for issue
- launch session for ticket
- spawn session for ticket
# English 鈥?patrol mode (delayed self-wake polling loops, [org-internal #3937]).
- patrol loop
- patrol mode
- delayed wake
# Chinese 鈥?bare noun phrases (matches() is a contiguous substring).
- 鎷夎捣涓讳細璇?
- 鎷夎捣浼氳瘽
- 鑷不浼氳瘽
- 鏃犲ご浼氳瘽
# Chinese 鈥?patrol mode ([org-internal #3937]).
- 宸℃妯″紡
- 寤惰繜鍞ら啋
- 浼氳瘽涓嶇敓鎴?
- 浼氳瘽鍗℃
- 涓哄伐鍗曟媺璧蜂細璇?
- 宸ュ崟鎷夎捣浼氳瘽
# English 鈥?lifecycle (hung rescue, stale-worldview correction, [org-internal #2459]).
- abort session
- revive session
- session rescue
- fact baseline
# English 鈥?quota-wall recovery ([org-internal #3669]): worker killed by provider usage limit.
- quota wall
- usage limit reached
- quota exhausted
- switch provider
# Chinese 鈥?lifecycle ops ([org-internal #2459]).
- 鍞ら啋浼氳瘽
- 浼氳瘽鎸傛
- 浜嬪疄鍩虹嚎
# Chinese 鈥?quota-wall recovery ([org-internal #3669]).
- 閰嶉澧?
- 閰嶉鑰楀敖
- 鐢ㄩ噺涓婇檺
- 鎹緵搴斿晢
role: Producer
---
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
# Headless Main Session Launch (prompt_async)
Launch a **main session for a specific issue / work item from a headless
context** 鈥?no Web UI, no TUI, just HTTP. This is the "fire-and-forget" path: an
agent inside another session, a cron job, a CI script, or `octopus run --attach`
non-interactive mode all land here when they need to spin up a session that
actually generates.
## Directory convention 鈥?independent session in the current workspace, no worktree
The default is an **independent session sharing the current session's
directory** 鈥?the repo you are already working in. No new worktree, no
checkout, no extra `bun install` or `.codegraph/` rebuild. Session creation
and worktree discipline are **orthogonal**: this skill only opens a session
record pointed at a directory; whether the launched session later creates its
own worktree is a _workflow_ decision (per the worktree-discipline rule) it
makes when it starts actual work 鈥?not something the launcher does. One
exception: the **ticket-driven recipe** below, where claim-first ([org-internal #2297])
requires the launcher to push the workflow branch before launch, making a
pre-built worktree the claim carrier + ready workspace (see Pitfall #10
carve-out). So, unless a different repo / path is specifically targeted,
`?directory=` defaults to the current session's directory and is NOT mandatory:
- **You are an in-session agent** (most common): set `DIR` to your own
session's `directory` (you already know it from context, or read `.directory`
from your own session record via `GET /session/$LAUNCHER_SID`). The new
session runs independently in that same workspace. Pass an explicit
different `?directory=` only when the new session must work in another
**repo** 鈥?never a worktree of the same repo. The Web UI sidebar groups
sessions by directory (`sidebar-project.tsx` per-workspace
`workspaceSessions(directory)`, fetched via `session.list({ directory })`),
so a session pointed at a worktree disappears from the workspace the creator
manages. A pre-built ticket worktree is handed to the launched session via
the claim comment + seed prompt, NOT via `?directory=`.
- **No current session** (cron / CI / bare script): the server-side fallback
when `?directory=` is omitted is the **server process's `process.cwd()`**
(`server/routes/instance/httpapi/middleware/workspace-routing.ts:76`), which
is wherever the server was started 鈥?unreliable. In that context `?directory=`
remains effectively required.
- **Dispatch convention 鈥?directory = code location, not tracker repo.** When
launching a session for a ticket, set `?directory=` to the **main workspace**
checkout that holds the **code the ticket edits**, which may differ from the
repo where the ticket is tracked. Example: `<owner>/<backend-repo>#<n>` is filed in
the ticket-tracker repo but edits the main repo's workflow files 鈫?the
session's `?directory=` is the **code checkout root**
(`<workspace-root>/<org>/<repo>`), not the tracker repo checkout. Never point
`?directory=` at a ticket worktree 鈥?the worktree goes in the claim comment +
seed prompt, not the directory field (sidebar visibility, see bullet 1). The
owner-check (`GET /session?directory=$DIR`) is scoped per directory, so
rooting the session at the correct code checkout is also what makes the
duplicate-session guard meaningful. Server-side backstop ([org-internal #3190]): creating a
session whose directory IS a linked git worktree answers with an
`X-Session-Directory-Warning` response header and a server warn log naming
the owning main repo 鈥?if you see that header, the session will be invisible
to `GET /session?directory=<mainRepo>` owner-checks; re-check your launch
parameters before proceeding.
> **Authoritative reference:** `rules/headless-session-ops` wiki page
> (<<instance-base-url>/Octopus/octopus/wiki/rules%2Fheadless-session-ops>).
> This skill is its runtime carrier. Provenance: issue **[org-internal #1695]** (which
> corrected the misdiagnosed [org-internal #1691], see comment 9205). Code citations below were
> verified against `<harness-package>` at HEAD.
## The `model` field 鈥?always provide a known-good model (read this first)
`prompt_async` is **fire-and-forget**: after HTTP returns 204 (or 202 when the
message is queued behind a wedge/zombie session 鈥?no live runner in that
process), the server runs the full agent loop in a separate fiber. **Always
include an explicit `model`
in the payload, and reuse the current session's model for it** (see the next
section). Two reasons:
1. **Current code does not hard-require `model`.** The HTTP boundary marks
`model` as optional (`<harness-package>/src/session/prompt.ts:1860`), and
`createUserMessage` fills a missing model via a fallback chain
(`prompt.ts:858`):
```ts
const model = input.model ?? ag.model ?? yield * currentModel(input.sessionID)
// ^^^^^^^^^ agent ^^^^^^^^^^^^^^^^^ session/default
```
So omission does **not** produce a clean validation error 鈥?it silently
resolves to the agent's model, the session's last-used model, or the
provider default (`currentModel`, `prompt.ts:824-838`).
2. **But the fallback is not safe in the autonomous context.** If it resolves
to a model that is unusable here (region-blocked, wrong subscription tier,
not configured), the runLoop's `getModel` raises `ModelNotFoundError`
(`prompt.ts:1399`, `:804-822`). In the **async** handler that error is caught
by the delivery fork's failure handling (`forkPromptDelivery` in
`handlers/session.ts`) and never reaches the HTTP
caller 鈥?so the session looks healthy (HTTP 204, user message persisted) yet
**never generates**. This is exactly the "silent stall" observed empirically
in [org-internal #1695]: a session with a 1-message transcript (user only) and frozen
`time.updated`.
| payload | result |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `{agent, parts}` 鈥?no `model` | 204/202; model filled by fallback; if the resolved model is unusable 鈫?**apparent silent stall** (no error surfaced, no generation). |
| `{agent, model:{providerID,modelID}, parts}` | 204/202; deterministic generation within seconds (verified: "1+1 绛変簬 2銆? in ~6s). |
> **Iron Law:** every `prompt_async` payload MUST carry a **known-good** `model`,
> and the safest known-good model is **the one driving this session right now**
> (it is, by definition, generating). Never rely on the fallback chain in a
> headless/autonomous launch.
**Field-name gotcha when copying a model:** the prompt payload's `ModelRef` uses
`{ providerID, modelID }` (`prompt.ts:1852-1855`), but the **session record's**
model uses `{ id, providerID, variant }` (`session.ts:256-260`) 鈥?i.e. `modelID`
is called `id`. When you reuse a model read from a session record, remap
`model.id 鈫?modelID` (see recipe below). User-message `info.model` already uses
`modelID` (`message.ts:403-407`).
## Reuse the current session's model (recommended default)
The model powering the launching session is proven to work 鈥?reuse it. Discover
it by whichever path fits your context:
- **You are an in-session agent** (most common): read it straight from your own
system prompt, which states verbatim:
> "You are powered by the model named **{modelID}** (provider: **{providerID}**)."
e.g. `modelID = "glm-5.2"`, `providerID = "zai-coding-plan"`锛堟浘鍚?
zhipuai-coding-plan锛?026-08 鏇村悕锛?
- **Programmatic / no system prompt** (cron, CI, sibling caller): `GET` the
launching session's record and read `.model`:
```sh
curl -s "$BASE/session/$LAUNCHER_SID?directory=$DIR" \
| jq '.model' # 鈫?{ "id": "glm-5.2", "providerID": "zai-coding-plan", "variant": ... }
```
Then **remap** `model.id 鈫?modelID` when building the payload.
- **Verify the provider is actually connected** before launching (cheap
insurance 鈥?the model only generates if its provider is live):
```sh
curl -s "$BASE/provider?directory=$DIR" | jq '.connected | index("<current providerID>")'
# 鈫?null = provider NOT connected (do NOT launch); 0..n = connected
```
or call the `list-models` tool. Note: model-level availability is only
knowable at runtime 鈥?a _configured_ model can still fail (subscription-tier
or region errors); `GET /config/providers` lists _configured_ models, not
usable ones. The safest pre-flight is "provider connected" + reusing the
current session's model, which is generating by definition. There is **no
`GET /model` HTTP endpoint** (it 404s); do not use it.
## Prerequisites
- A reachable octopus HTTP instance (e.g. `http://127.0.0.1:4096`).
- The target directory (passed as `?directory=`). Defaults to the current
session's directory for in-session launches 鈥?see the Directory convention
above; only specify a different one explicitly when targeting another repo.
- A known-good `model` (reuse the current session's 鈥?see above). Model IDs
must match the provider config in `<config-home>/octopus/octopus.jsonc`;
credentials live in the octopus account keyring (not env), so an isolated
HOME cannot reuse them.
## The 3-step flow
Set the base URL and directory once:
```sh
BASE=http://127.0.0.1:4096
DIR=/data/octopus
```
### Step 1 鈥?Create the session
```sh
SID=$(curl -s -X POST "$BASE/session?directory=$DIR" \
-H 'content-type: application/json' \
-d '{"title":"#N 鈥?<short description>","agent":"builder"}' | jq -r .id)
echo "session=$SID"
```
- `POST /session` returns `Session.Info` with `.id` (`groups/session.ts:207-211`).
- `?directory=` points at the repo root. For in-session launches it defaults to
the **current session's directory** (Directory convention above) 鈥?keep it
that way so the new session stays visible in the creator's sidebar; pass an
explicit path only when targeting a different repo (a worktree of the same
repo is NOT a directory target 鈥?hand it over via the seed prompt), and
always pass it from cron / CI where no current session exists (server falls
back to its own `process.cwd()` otherwise).
- `agent` is `builder` (the main agent). Sub-agents (explorer/worker) are
spawned within a session 鈥?never named here.
### Step 2 鈥?Deliver the seed prompt (CRITICAL: include a known-good `model`)
```sh
curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
-H 'content-type: application/json' \
-d '{
"agent": "builder",
"model": { "providerID": "<current providerID>", "modelID": "<current modelID>" },
"parts": [ { "type": "text", "text": "<seed prompt: restate goal, constraints, acceptance criteria, cite wiki artifact paths>" } ]
}'
```
- Endpoint is `POST /session/:sessionID/prompt_async`; it answers **204, no
body** when processing starts now (idle session, live runner, noReply
injection, or waiting-question preemption), **202** when the message is
queued behind a session that is generating with no live runner in this
process (wedge/zombie shape 鈥?stored and re-driven on restart drain or the
next runner), **202** when `delay_sec` defers delivery to an in-memory
delayed wake ([org-internal #3937] A 鈥?see "宸℃妯″紡 / Patrol mode" below), and **409**
when the session is paused (message NOT stored) or the maintenance-mode
hold queue is full.
- **Global route alternative ([org-internal #4307]):** when the client only knows the
sessionID (no `?directory=` routing), `POST /prompt_async` (no path prefix)
resolves the instance from the app database by the body's `sessionID` and
delegates to the same delivery core 鈥?same 204/202/409 semantics, same
`delay_sec` contract, body is the session-scoped payload plus a required
`sessionID` field. Unknown sessionID 鈫?404 naming the session-scoped
route. This is the natural fit for the completion-report POST (fixed port,
no directory context).
- `delay_sec` (optional, integer 0..86400, else 400) schedules the prompt for
a one-shot delayed self-wake: the server holds the prompt in memory and
fires it exactly once after `delay_sec` seconds, only for an idle session
at intake. **Precedence ([org-internal #3937] review):** `delay_sec` applies only when the
intake would otherwise start processing now (immediate class). For the
queued-behind-wedge 202 member and under maintenance queue mode it is
stripped 鈥?retention semantics win (the server logs the drop; the delay is
NOT honored). Pending wakes are **in-memory only 鈥?a server restart abandons
them**; a patrol loop must therefore tolerate a missed wake and re-arm.
Never use `sleep <big>; curl ...` in a bash tool call to wait 鈥?that blocks
the turn with zero output (the [org-internal #3937] incident shape); the bash tool now
warns on bare sleeps > 120s (long-sleep guard, [org-internal #3937] C).
- `model` is `{ providerID, modelID }` or the shorthand string
`"providerID/modelID"` (split on the FIRST `/`; accepted since [org-internal #4307] 鈥?
e.g. `"zai-coding-plan/glm-5.2"`). **Do not omit it.** Fill the values from
your own session's model (see "Reuse the current session's model" above) 鈥?
never hardcode a specific model. A string without a `/` is rejected with
400 naming both accepted forms.
- When the seed prompt contains newlines/quotes, build the payload from a file
to avoid shell-escaping errors:
```sh
jq -n --rawfile p seed.txt '{agent:"builder",model:{providerID:"<current providerID>",modelID:"<current modelID>"},parts:[{type:"text",text:$p}]}'
```
### Step 3 鈥?Verify the session actually generated (do NOT assume success)
HTTP 204/202 鈮?generation success. Poll until an `assistant` message appears. The
endpoint is **`/session/:id/message` 鈥?SINGULAR**, and it returns a bare array
whose elements are `{ info, parts }` with `role` at `.info.role`
(`groups/session.ts:88,183-194`). Poll the **tail with `limit=1`** 鈥?the
no-`limit` form loads the ENTIRE transcript server-side (O(messages脳parts) per
poll; pitfall #14's 478-message session paid it on every health check), while
`limit=1` returns just the newest message (REQ-F-010 windowing, `message.ts`
`page()`):
```sh
# wait 5鈥?0s, then poll the TAIL 鈥?never the full transcript
curl -s "$BASE/session/$SID/message?directory=$DIR&limit=1" \
| jq '{latest_role: .[0].info.role, latest_id: .[0].info.id}'
```
Verdict: `latest_role == "assistant"` 鈫?the first generation completed (the
seed is `user`; the newest message flips to `assistant` only once the model
answered 鈥?an errored turn also persists an assistant message carrying the 鈿狅笍
usage-limit part, [org-internal #2912], so check the tail text before declaring success).
Stays `user` across 2鈥? polls spaced 10鈥?0 s 鈫?**apparent silent stall**;
almost certainly the model did not resolve to a usable one (fallback landed
badly, or you omitted `model`). Redeliver Step 2 with an explicit known-good
`model`. Need everything newer than a known point? Page forward with
`after=<cursor>&limit=N` and follow the `X-Next-Cursor` response header 鈥?
just never omit `limit`.
> 鈿狅笍 Do NOT write `/messages` (plural) 鈥?that path does not exist for listing.
> (`POST /session/:id/message`, same singular path but POST, is the synchronous
> prompt endpoint; don't confuse the two.)
## Launch a session for a specific ticket (#N)
The common case: an agent (or cron/CI) spins up a fresh main session to work a
tracked issue 鈥?seed prompt composed **from the issue body**, launched on the
current session's model. **Step-0 owner check is MANDATORY ([org-internal #1803])**: four data
sources (session-title scan, assignee/claim, open PRs, remote branches); any
live claim 鈫?ABORT the launch (`session-scope-guard.md` points here for that
pre-step). Full recipe 鈥?owner-check scan script with `X-Total-Count` /
`X-Has-More` pagination handling ([org-internal #3190]), claim-first atomic 3-step ([org-internal #2297],
+ one-command provisioning via `script/claim-provision.sh`, [org-internal #3642]),
seed-prompt MUSTs, backend duplicate-ticket hard guard ([org-internal #1989], on by default
[org-internal #2350]), launch + poll commands: `reference/ticket-recipe.md` (read BEFORE
creating any ticket-driven session).
## Completion-report protocol (瀹屾垚鍥炴姤鍗忚, [org-internal #2374])
`prompt_async` is fire-and-forget **for the launcher too**: without this
protocol the orchestrator has NO push channel and must poll forever ([org-internal #2366]).
The worker actively reports `status=done|blocked|handoff` back into the
orchestrator's session via `prompt_async`, with an issue-comment fallback.
Default report format is `branch=<ref>`-based 鈥?workers never open PRs
(TD-678/[org-internal #4425]); `pr=#N` appears only on `uncoordinated` self-opens.
Hard constraints: a headless worker MUST NOT call the `question` tool
([org-internal #2378] 鈥?directional/irreversible decisions are `status=blocked` reports,
then STOP), and MUST NOT self-merge its PR / self-close the issue / touch
`main` ([org-internal #2386] 鈥?keep the PR open; the orchestrator merges and closes). Full
protocol 鈥?orchestrator-side seed block (verbatim template), worker-side
steps, decision-authority layering: `reference/completion-report.md` (read
BEFORE composing a seed prompt for any worker you need to hear back from).
## Post-launch lifecycle: health check, hung rescue, stale-worldview correction ([org-internal #2459])
Launching is half the job 鈥?a headless worker can hang silently or wake with
a stale worldview. Health check = two polls 30鈥?0 s apart (`.state` + `progress`
+ message count); `state=generating` with ALL counters frozen across BOTH
samples = hung 鈫?abort FIRST (`POST /session/:id/abort`), then re-wake with a
known-good model + the anti-re-hang clause. Stale worldview 鈫?fact-baseline
injection (only the CURRENT authority injects; if YOU might be the stale one,
verify identity against the durable record first). Full runbooks 鈥?verdict
table + [org-internal #3215] dual-sample rule, abort + re-wake sequence, fact-baseline
structure, authorization asymmetry: `reference/lifecycle-ops.md` (read when
managing a launched session). Quota wall 鈥?the worker's turn killed by a
terminal usage-limit error (looks like silent completion: no report, ticket
stalled) 鈥?has its own runbook: detection signals (finish=error 鈿狅笍 tail,
provider quota markers), provider-switch re-wake, fact-baseline template,
pre-dispatch provider check: `reference/quota-wall-recovery.md` ([org-internal #3669], evidence
[org-internal #3627]; read when a worker goes silent mid-task).
## 宸℃妯″紡 / Patrol mode (delayed self-wake, [org-internal #3937])
A patrol loop periodically nudges a headless session without holding a
connection or burning a turn on a blocking sleep. Since [org-internal #3937] A the server
supports this natively via `delay_sec` on `prompt_async`.
```sh
curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
-H 'content-type: application/json' \
-d '{
"agent": "builder",
"model": { "providerID": "<current providerID>", "modelID": "<current modelID>" },
"parts": [ { "type": "text", "text": "<patrol instruction>" } ],
"delay_sec": 3600
}'
```
Rules of the road:
- **202 + `delay_sec` 鈮?queued-behind-wedge 202.** Both are 202, but the
delayed wake holds the prompt in memory and delivers it exactly once when
the delay elapses (session must have been idle at intake; `0` equals
omission 鈫?immediate 204 path). If the session was NOT idle at intake, the
intake keeps the queued semantics and `delay_sec` is ignored (logged
server-side) 鈥?the same precedence as maintenance queue mode, where the
intake is retained and `delay_sec` never delays the flush.
- **Paused at wake 鈫?dropped, not stored.** The wake's `prompt()` rejects and
the server publishes an error event; the message is never stored. A patrol
driver should check session state before assuming delivery.
- **Restart abandons pending wakes.** They are in-memory server fibers 鈥?no
persistence, no drain. After any server restart the patrol driver owns
re-arming the next wake; treat a missed wake as expected, not as an error.
- **Latest wake wins, per session ([org-internal #4069]).** Arming `delay_sec` on a session
that already has a pending wake SUPERSEDES it: the previous countdown is
cancelled, only the latest wake fires. Re-arming each turn is the intended
patrol pattern 鈥?a superseded wake never delivers its prompt and publishes
no error. (Pre-[org-internal #4069] stacks both FIFO 鈥?on old binaries, re-arm only after
the previous wake fired.)
- **Never emulate a delay with the bash tool.** `sleep 900; curl ...` blocks
the turn with zero output and gets manually aborted as a hang (the [org-internal #3937]
incident). The bash tool now prepends a `<shell_warning>` on bare sleeps
beyond 120s ([org-internal #3937] C) 鈥?treat that warning as a redirect to this section.
## Do NOT use this skill when
- A **persistent client** (Web UI / interactive TUI) is attached 鈥?those use the
synchronous `prompt` endpoint (it blocks until generation completes) and send
`model` automatically. Using `prompt_async` there gains nothing and loses the
in-line response. This includes the rescue runbook: a hung session driven by
a persistent client is the client user's to handle (refresh / re-prompt from
the UI) 鈥?abort + re-wake targets headless / managed sessions only.
- You want the response **inline** in the same HTTP call 鈥?`prompt_async` is
fire-and-forget; use the synchronous `prompt` endpoint instead.
## Common pitfalls
| # | Pitfall | Fix |
| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 1 | Omitting `model` / relying on the fallback 鈫?apparent silent stall (most common) | Always send a known-good `model:{providerID,modelID}` 鈥?reuse the current session's |
| 2 | Listing endpoint written `/messages` (plural) 鈫?404 | Use `/session/:id/message` (singular); returns a bare array, role at `.info.role` |
| 3 | Copying the session record's model verbatim into the payload | Remap field names: session `.model.id` 鈫?payload `modelID` (session uses `id`, payload uses `modelID`) |
| 4 | Stale `octopus` binary rejects newer config schema (`Unrecognized key`) | Use the source CLI `bun run ./src/index.ts ...`; the config itself is valid |
| 5 | Missing `?directory=` query param | In-session launch: default to the current session's directory. Cron/CI (no current session): pass the repo root explicitly 鈥?server falls back to its own `process.cwd()` |
| 6 | Using synchronous `prompt` for headless launch | `prompt` blocks until done 鈥?wrong for fire-and-forget; use `prompt_async` |
| 7 | Shell-escaping errors in a multi-line seed prompt | Build the payload with `jq -n --rawfile` from a file |
| 8 | Verifying models via `GET /model` 鈥?no such endpoint (404) | Use `GET /provider` 鈫?`.connected` (provider live check); `list-models` tool also works. `GET /config/providers` lists _configured_ models only |
| 9 | Launching a session for a ticket another live session already owns 鈫?conflicting diffs, duplicate work ([org-internal #1744]/[org-internal #1753]) | Run the Step-0 owner check (`GET /session` title scan + worktree/branch/PR scan); ABORT and report on conflict |
| 10 | Creating a worktree / fresh checkout for the new session (unnecessary `bun install` + `.codegraph/` rebuild cost) | Don't 鈥?for _general_ launches: a session is a conversation pointed at a directory, so reuse the current workspace; worktree creation is a separate _workflow_ decision the launched session makes if/when it starts a workflow. **Ticket-recipe carve-out ([org-internal #2297]):** the ticket's work happens in a dedicated worktree anyway (worktree discipline), and claim-first pushes the workflow branch before launch 鈥?so the launcher pre-builds the worktree as the claim carrier + ready workspace, and the session reuses it (cost is front-loaded, not wasted). The worktree path goes in the claim comment + seed prompt; `?directory=` stays on the creator's workspace |
| 11 | Launching a worker and never hearing back 鈥?orchestrator polls a few times, stops, loses visibility ([org-internal #2374]) | Append the completion-report block to the seed prompt (Completion-report protocol section): worker `prompt_async`s a one-line status back into the orchestrator session on done/blocked/handoff, with issue-comment fallback |
| 12 | Headless worker calls the `question` tool 鈫?no attached client: the question deadlocks in a queue, or `auto_approve` silently auto-approves a direction-setting decision ([org-internal #2378]) | NEVER call `question` from a headless worker. Decision boundary (Completion-report protocol section): directional/irreversible decision 鈫?`status=blocked` report + one-line decision point, then STOP; the orchestrator is the sole human-decision entry point |
| 13 | Headless worker self-merges its PR / self-closes the issue once the PR looks ready, despite a seed instruction to keep it open ([org-internal #2386]; N-02 [org-internal #2367] / N-03 [org-internal #2368], commits `d1565c99` / `040f21b7`) | Self-merge / self-close / touching `main` are irreversible directional actions 鈫?blocked by the Decision boundary. PR ready 鈫?keep it OPEN, report `status=done branch=<ref>`; the orchestrator opens/admits the PR, merges and closes uniformly |
| 14 | Trusting `state=generating` as "alive" 鈥?a hung stream goes unrescued (session A, 2026-08-16: `generating` 6+ min, count frozen at 478) | Poll state AND message count twice 30鈥?0 s apart; frozen count = hung 鈫?abort + re-wake (Post-launch lifecycle section) |
| 15 | Re-sending a wake prompt to a hung session and waiting 鈥?queued prompts are never consumed while the stream is dead | Abort FIRST (`POST /session/:id/abort` 鈫?200, verify `idle`), THEN re-send; a prompt queued before the abort may still never fire 鈥?always re-deliver |
| 16 | A worker woken from restart / long idle acts on its stale worldview 鈥?re-dispatches superseded work or claims authority it no longer holds (2026-08-16: revived worker re-ran an already-published FAIL verify; ops session announced an inherited "orchestrator" takeover) | Fact-baseline injection (Post-launch lifecycle section): authoritative facts + invalidated assumptions + one concrete re-assignment. Only the CURRENT authority injects; a session that suspects IT is stale verifies identity against the durable record first (`rules/compact.md` identity clause) |
| 17 | Worker goes silent mid-task: provider quota wall killed the turn (usage limit / 429) 鈥?no completion-report, no blocked report; from the issue side indistinguishable from silent completion (2026-08-29 [org-internal #3627]: opencode-go `weekly usage limit reached` after the 13:18 Round-1 FAIL; ticket stalled until manually re-woken) | Detect via tail probe (`finish=error` + 鈿狅笍 usage-limit part, `metadata.reason` in the quota family) + `GET /provider` quota markers; re-wake on a known-good provider with the fact-baseline template 鈥?`reference/quota-wall-recovery.md` ([org-internal #3669]). Prevent: pre-dispatch provider check + the seed's 閰嶉鑷姤 clause (`reference/completion-report.md`, [org-internal #3669]) 鈥?quota exhaustion is a `status=blocked reason=quota-exhausted` report, not a silent death |
## Known limitation (separate from this workaround)
A missing `model` producing an **apparent silent stall** (no surfaced error, no
generation) is an observability / robustness gap. Current code does NOT
hard-require `model` 鈥?it fills it via a fallback chain (`prompt.ts:858`) and any
resolution failure surfaces as a published `Session.Event.Error` on the
synchronous path (`prompt.ts:804-822`), but the **async** `prompt_async` handler
catches that cause (`forkPromptDelivery` in `handlers/session.ts`) so the HTTP
caller never sees it. The proper fix is to surface the error (or reject unusable
models at the boundary) so a stalled session is diagnosable. Until then, **always
provide an explicit known-good `model`** 鈥?this skill is the documented
workaround (see [org-internal #1695] "閬楃暀 minor").
**Update ([org-internal #2912], 2026-08-20)**: usage-limit turn failures (HTTP 429 /
FreeUsageLimitError / GoUsageLimitError) are no longer silent shells. When such
a turn terminates (retry schedule ends or the spin is aborted), the errored
assistant message persists `finish=error` plus a human-readable 鈿狅笍 text part
carrying provider, limit reason, and reset hint (`metadata: { error: true,
reason, provider }`) 鈥?visible via `GET /session/:id/message` and rendered in
the web UI like any assistant text. Pitfall #14's double-poll is still the
liveness check while a 429 retry spin is IN progress (the spin itself retries
with backoff until interrupted); the explicit error part only appears once the
turn ends. The general async-path observability gap above remains tracked by
[org-internal #1695].
## See also
- Source issue / full manual: **[org-internal #1695]** (this skill adds the ticket-driven recipe
- current-model reuse, and corrects the endpoint/claim against current code).
- Completion-report protocol: **[org-internal #2374]** (worker-side active report back to the
launching orchestrator session; fallback to issue comment).
- Quota-wall recovery recipe: **[org-internal #3669]** (evidence instance [org-internal #3627]) 鈥?detect a
worker killed by a provider usage limit, re-wake on a known-good provider,
pre-dispatch prevention.
- Decision boundary: **[org-internal #2378]** (headless worker MUST NOT call `question`;
directional decisions go through `status=blocked` reports to the
orchestrator) and **[org-internal #2386]** (the worker MUST NOT self-merge its PR,
self-close the issue, or touch `main` 鈥?the orchestrator merges and closes
uniformly).
- Correction of the prior misdiagnosis: **[org-internal #1691]** (closed Invalid), comment 9205.
- Synchronous client always sends `model`:
`packages/app/src/components/prompt-input/submit.ts:158-165`.
- Code citations: `<harness-package>/src/session/prompt.ts:858,804-822,1399`,
`handlers/session.ts` `forkPromptDelivery` (async failure handling),
`server/routes/instance/httpapi/groups/session.ts`.
## References
**On-demand references** (NOT injected 鈥?read at the declared timing):
- `reference/ticket-recipe.md` 鈥?ticket-driven launch (`Launch a session for a
specific ticket (#N)`): read BEFORE creating any session for issue #N 鈥?
Step-0 owner check, seed-prompt composition, launch + poll commands.
- `reference/completion-report.md` 鈥?completion-report protocol (瀹屾垚鍥炴姤鍗忚,
[org-internal #2374]): read BEFORE composing a seed prompt for a worker you need to hear
back from 鈥?orchestrator block template, worker-side report steps, decision
boundary ([org-internal #2378] / [org-internal #2386]), quota self-report clause ([org-internal #3669]).
- `reference/lifecycle-ops.md` 鈥?post-launch lifecycle ([org-internal #2459]): read when
managing a launched session 鈥?health-check polling, hung-stream diagnosis
(state=generating + frozen counters), abort + re-wake rescue,
fact-baseline injection, authorization asymmetry.
- `reference/quota-wall-recovery.md` 鈥?quota-wall recovery ([org-internal #3669], evidence
[org-internal #3627]): read when a worker goes silent mid-task (suspected provider usage
limit) or BEFORE dispatching a long-running worker 鈥?detection signals
(finish=error 鈿狅笍 tail, provider quota markers, log grep patterns),
provider-switch re-wake + fact-baseline template, pre-dispatch provider
check, quota self-report clause.
- `rules/headless-session-ops` wiki page 鈥?authoritative reference (this
skill is its runtime carrier; link in the Directory-convention note above).
- `core/rules/session-scope-guard.md` 鈥?owner-check + claim-first rule
(the ticket recipe's Step 0 is its launch-time application).
- `core/rules/compact.md` 鈥?identity-verification clause backing the
authorization-asymmetry rule (`reference/lifecycle-ops.md`).
@@ -0,0 +1,173 @@
> Extracted from headless-session-ops/SKILL.md (Completion-report protocol (完成回报协议, [org-internal #2374])) — moved verbatim 2026-08-26, ticket [org-internal #3480].
## Completion-report protocol (完成回报协议, [org-internal #2374])
`prompt_async` is fire-and-forget **for the launcher too**: once the worker
session starts generating, the orchestrator receives NO event when it finishes
or blocks. The `task` tool auto-returns `task_result` for subagents; a
`prompt_async`-launched independent main session has no equivalent — the
orchestrator's only built-in channel is bare polling of
`GET /session/:id/message?limit=1` (tail probe — the no-`limit` form loads the
whole transcript server-side), which it eventually stops doing ([org-internal #2366]: worker ran
~1h past the orchestrator's last poll, merged PR [org-internal #2371] and closed [org-internal #2366] while
the orchestrator had zero visibility).
The protocol below closes that gap WITHOUT changing server semantics: the
**worker** actively posts a completion/block message back into the
orchestrator's session via `prompt_async`. Fire-and-forget launch is
unchanged; the report is a worker-side obligation, not a new server dependency.
### Orchestrator side — pass your SID in the seed prompt
When launching a worker that you need to hear back from (DAG node execution,
parallel chunk work, any long-running delegated ticket), append a
completion-report block to the seed prompt. It MUST state:
- the orchestrator's **session id** (`$LAUNCHER_SID`) and **directory**;
- the report endpoint: `POST $BASE/session/$LAUNCHER_SID/prompt_async?directory=$DIR` (or, when the directory is unknown to the worker, the global route `POST $BASE/prompt_async` with the sessionID in the body — [org-internal #4307]);
- the exact report format (one-line, machine-greppable);
- the fallback (issue comment) if the report call fails.
Template (include verbatim in `seed.txt` when you are the launcher):
```text
## 完成回报协议 (mandatory, [org-internal #2374])
你由编排会话 (orchestrator) 启动。完成、阻塞、或将工单移交他人时,必须向编排会话回报:
POST <BASE>/session/<LAUNCHER_SID>/prompt_async?directory=<DIR>
body: {"agent":"builder","model":{...},"parts":[{"type":"text","text":"<report>"}]}
<report> 格式(单行,machine-greppable):
[#<ticket>] [<node-or-slug>] status=done|blocked|handoff branch=<ref> [pr=#<N>] verify=<changed+typecheck> risk=<high|low> summary=<one line>
默认(TD-678 / [org-internal #4425]):worker 只推分支不开 PR——交付推送前置本地验证
<harness-package> 下 test:changed 全绿 + typecheck 0 errorverify 字段回执);
risk 按 risk-classifier 冻结表(HIGH_RISK_GLOBS)自报。PR 由编排按容量串行开
1:1(正文携 worker 报告),或 batch 模式 epic 经 land-batch 技能按迭代统一合成
(见 core/skills/land-batch/)。pr=#<N> 仅限编排不可达自开(uncoordinated)。
若回报调用失败(HTTP 非 2xx / 连接拒绝 — orchestrator 会话可能已结束或实例重启),
退而在源 issue(或父 Epic)发同内容评论作为兜底(fallback, 非主通道)。
回报是你的 mandatory step,与 issue-checklist-sync 并列;不得静默结束。
决策边界([org-internal #2378]):headless worker 禁止调用 `question` 工具 —— 遇方向性/不可逆
决策点 → `status=blocked` + 一行决策点描述后 STOP,勿调 question(无人应答死锁 /
被 auto_approve 静默自动批准)。
配额自报([org-internal #3669]):若收到配额/用量上限错误(usage limit / 429 / 402
insufficient_quota),立即回报 status=blocked reason=quota-exhausted
summary=<provider 与 ⚠️ 部分中的重置提示>branch=<ref> 必带)后停止
——勿自行换模型重试;换供应商重唤醒由编排者执行
reference/quota-wall-recovery.md)。
PR 就绪后保持 open:不自行合并 PR、不关闭 issue、不动 main[org-internal #2386]);
合并与 issue 关闭由编排者统一执行。
```
The orchestrator discovers its own `$LAUNCHER_SID` from context (it is the
session the orchestrator IS — e.g. the SID it already used for the
`GET /session/$LAUNCHER_SID` model lookup in the recipe above) or from its
session record.
### Worker side — report on completion / block / handoff
A session whose seed prompt contains the completion-report block MUST, as its
final step (before idling):
1. **Report** by `prompt_async` into the orchestrator session, with the
one-line format above. Include `branch=<ref>` (plus `verify=`/`risk=` on
done); `pr=#N` only for an `uncoordinated` self-open; use
`status=blocked` + the blocker description when stuck; use
`status=handoff` when deliberately transferring the ticket.
1. **Quota exhaustion is a blocked report, not a silent death ([org-internal #3669]).** A
worker whose turn is killed by a usage-limit error (429 / 402 quota
family) cannot finish the turn — but per the seed's 配额自报 clause it
reports `status=blocked reason=quota-exhausted summary=<provider +
reset hint>` (with `branch=<ref>`) BEFORE stopping
(issue-comment fallback if the orchestrator session is unreachable).
The orchestrator then runs the provider-switch recovery in
`reference/quota-wall-recovery.md` instead of discovering the wall by
polling.
2. **On failure** of that POST (non-2xx / connection refused — the
orchestrator may have ended or the instance restarted), fall back to a
`工单评论 API(见 TERMINOLOGY` on the source issue / parent Epic carrying
the same one-line report. The issue comment is the durable record of last
resort, NOT the primary channel — the orchestrator does not watch issues in
real time.
3. The report is **additive** to existing obligations (issue checklist sync,
`## 当前状态` updates, archive-at-close) — it never replaces them. The issue
remains the stakeholder-facing record; the report is the
orchestrator-facing wake-up signal.
### Orchestrator side — serial PR admission on done reports ([org-internal #4425])
On a `status=done branch=<ref> verify=… risk=…` report:
1. Enqueue the ticket; admit PRs serially per `ticket-lifecycle.md` §PR
准入 — one open PR at a time, next only after double-green merge. Branch
rot is bounded by the existing keep-mergeable/syncMain machinery, not new
code; keep the queue shallow.
2. Open the 1:1 PR (title from the ticket node, body carrying the worker
report: branch / files / self-test / verify), apply the `Risk/*` label
from the report's `risk=` hint, merge via `script/pr-merge.sh` ([org-internal #3864]).
Batch-mode epics: one `land-batch` PR per iteration instead ([org-internal #3731]).
3. A first red on the opened PR goes back through the report fallback
channel (issue comment) — the worker may already be idle; the issue
comment is the durable re-entry point.
Prose discipline on the orchestrator session only — no new daemon, no
mergeable-state automation (merge-coordinator retired, [org-internal #4385]).
### Decision boundary (决策边界, [org-internal #2378])
The report channel defines how the worker *answers*; this subsection defines
the boundary of what it may *ask*. A headless worker session has NO attached
client (no Web UI / TUI), so calling the `question` tool has exactly two
outcomes, both structurally broken:
- the question sits in a queue nobody answers → the session deadlocks;
- or it is silently auto-approved by `auto_approve` → a direction-setting
decision gets decided by a default value, violating the human-gate
semantics of the review gates.
**Rule: a headless worker MUST NOT call the `question` tool.** Decision
authority is layered instead:
- **Directional / irreversible decisions** (scope changes, mid-stream
reclassification — big-bug relabel / DAG re-derivation, merge timing,
contract changes, approach selection) → the
worker reports `status=blocked` with a one-line description of the decision
point, then STOPs. The orchestrator is the sole human-decision entry point:
it interacts with the user, then replies to the worker (or acts on its
behalf).
- **No self-merge / no self-close / never touch main ([org-internal #2386])**: a headless
worker MUST NOT merge its own PR, close the source issue, or rebase /
force-push `main`. These are irreversible directional actions of exactly
the class this boundary blocks — even when the seed prompt's instruction
to "keep the PR open" is absent or the worker judges the PR ready. When
the PR is ready the worker keeps it OPEN and reports
`status=done pr=#N` to the orchestrator; the orchestrator performs the
merge and the issue close uniformly. (Counter-example: N-02 [org-internal #2367] /
N-03 [org-internal #2368] self-merged their PRs — commits `d1565c99` / `040f21b7`
despite a seed instruction to keep them open.)
- **Local implementation decisions within the AC scope** (pattern choice,
helper extraction, test shape) → the worker decides autonomously and notes
the choice in its report / PR body.
Rule of thumb: the worker's only way to "ask" is a `status=blocked` report.
A genuine ambiguity left by the seed prompt that would change scope is a
blocked report, NOT a `question` call. (Field sample: N-04 [org-internal #2369] — the
worker spontaneously did NOT merge its own PR and left merging to the
orchestrator; this subsection codifies that behavior.)
### Semantics preserved
- **Fire-and-forget launch is unchanged**: Step 13 above are identical; the
orchestrator still gets no server push at launch time.
- **No server change**: the report reuses the existing `prompt_async` endpoint
against the orchestrator's SID. Nothing new is required of the backend.
- **Polling stays as fallback**: an orchestrator that never got a report can
still poll `/session/:id/message`; the protocol removes the *need* to poll
forever, not the ability.
### (Optional / future) server-side callback
Longer-term, `prompt_async` could accept a `callback_session_id` so the server
itself posts an event to the callback session when the run loop terminates —
removing the prompt-level manual protocol. That is a separate platform
enhancement and intentionally NOT in this ticket's scope; the manual protocol
above works today and remains compatible with a future automatic callback.
@@ -0,0 +1,106 @@
> Extracted from headless-session-ops/SKILL.md (Post-launch lifecycle: health check, hung rescue, stale-worldview correction ([org-internal #2459])) — moved verbatim 2026-08-26, ticket [org-internal #3480].
## Post-launch lifecycle: health check, hung rescue, stale-worldview correction ([org-internal #2459])
Launching is half the job — a headless worker can hang silently or wake up
with an outdated worldview. All three failure modes below were observed in
production on 2026-08-16 during Epic [org-internal #2459] (ops-restart window); the runbooks
are extracted from that incident record.
### Health check — three states, one poll pattern
Poll two signals together — `GET /session?directory=` for `.state` (plus the
per-session `progress` object when present), and `GET
/session/:id/message?limit=1` for the newest message's `.info.id` — **twice,
spaced 3060 s**. `MessageID` is monotonic (`MessageID.ascending`,
`message.ts`), so a frozen newest-id across both samples is exactly the old
"message count frozen" signal at O(1) per poll instead of O(transcript) —
never poll the no-`limit` form for liveness, it loads the whole transcript
server-side:
| Verdict | Signals | Action |
|---|---|---|
| healthy | `state=generating` AND (`progress.stepCount` growing OR newest message id advancing) | leave it alone |
| idle | `state=idle` AND `progress` absent-or-stale across both polls | turn ended — read the LAST message (same `limit=1` fetch): task unfinished → wake prompt; `status=done` report → harvest |
| **hung** | `state=generating` AND `progress.stepCount`/`lastStepAt` AND newest message id ALL frozen across both polls | the generation stream is dead; queued prompts will NEVER be consumed — rescue required |
`state` alone lies in BOTH directions ([org-internal #3215]): a hung session still reports
`generating` (field sample: session A, 2026-08-16 — `state=generating` for 6+
minutes with the message count frozen at 478), and a healthy mid-step worker
can read `idle`/empty message tails in a single snapshot (2026-08-23 W3 wave:
4 healthy workers aborted off one snapshot). **The dual-sample delta is the
discriminator; a single snapshot is NEVER an abort basis.**
`progress` (`stepCount` cumulative LLM-round counter, `lastStepAt` epoch-ms
heartbeat — [org-internal #3215]) moves on every round even when message tails are
transiently empty or `state` flickers; it is absent for sessions that never
ran since instance start (treat absent = no signal, fall back to count
deltas). `lastStepAt` freshness alone does NOT prove liveness (a long tool
call inside one round keeps it stale for minutes) — always compare TWO
samples spaced ≥30 s.
### Hung rescue — abort, then re-wake (in this order)
```sh
# 1. Abort the dead stream (queued-but-unconsumed prompts do NOT unblock it)
curl -s -X POST "$BASE/session/$SID/abort?directory=$DIR" -o /dev/null -w "%{http_code}\n" # → 200
# 2. Verify idle
curl -s "$BASE/session?directory=$DIR" | jq -r --arg s "$SID" '.[] | select(.id==$s) | .state' # → idle
# 3. Check the worktree — the hung turn may have left uncommitted files (NOT lost)
git -C <worktree> status --short
# 4. Re-deliver the wake prompt (same Iron Law: known-good model).
# A prompt queued BEFORE the abort may still never fire — always re-send.
```
Endpoint: `POST /session/:sessionID/abort` (`groups/session.ts:113`,
`handlers/session.ts:311`). Abort stops the run loop; filesystem writes the
hung turn already made survive — inspect the worktree and list any recovered
files IN the wake prompt so the worker re-validates them instead of redoing
work (field sample: session A's hung turn had produced 3 src + 1 test file
that its revived self adopted).
**Anti-re-hang clause — include in every wake prompt.** The trigger for the
observed hang was a system-injected "请在适当的时机压缩当前会话" (compact at an
appropriate time): the worker ended its turn after compacting, leaving the
task half-done and idle. A wake prompt MUST carry, verbatim:
```text
若系统再注入「请在适当的时机压缩当前会话」:执行压缩后立即在后续 turn 继续任务,
绝不在任务未完成时以 idle 结束。
```
### Stale worldview — fact-baseline injection (correct BEFORE it acts)
A worker woken after an ops restart / long idle carries the worldview it went
to sleep with. It may re-dispatch superseded work, overwrite newer state, or
claim authority it does not hold (field samples, 2026-08-16: a revived worker
re-ran an M-01 verify another session had already published as FAIL —
deduplicated via flag #comment-21590; an ops-notification session inherited an
"orchestrator" identity from a compaction summary and announced a takeover —
corrected via 勘误 #comment-21654/[org-internal #21668]).
When you detect a stale-worldview session, do not wait for it to finish being
wrong — inject a fact-baseline prompt immediately (regular `prompt_async`),
structured as:
1. **You were woken; your worldview is stale** — name the event (restart /
maintenance window) and the current time.
2. **Authoritative state** — numbered facts with artifact links (wiki page,
issue comment), each with its timestamp; state explicitly which of the
recipient's standing assumptions are now INVALID.
3. **Your actual assignment now** — one concrete task (or explicit standby).
4. **Evidence rule** — verify each fact at its cited source before acting;
never act on this baseline alone.
(Field sample: the 2026-08-16 injection to session B pivoted it from the
superseded M-01 verify to the N-04b fix within one turn — the format works.)
### Authorization asymmetry — read this BEFORE "correcting" anyone
Only the session that CURRENTLY holds the authority may inject a baseline or
re-task a worker. If YOU might be the stale one — you woke from a restart,
your context came from a compaction summary, you cannot find your claim in
the durable record — assume YOU are stale: verify your identity/authority
against the record (issue assignee, claim comment, orchestrator session id)
BEFORE issuing any instruction. See the identity-verification clause in
`core/rules/compact.md` (recovery contract).
@@ -0,0 +1,226 @@
> Added from headless-session-ops/SKILL.md (triggers `quota wall` / `配额墙`,
> Post-launch lifecycle section, pitfall #17, References) — new runbook
> 2026-08-29, ticket [org-internal #3669] (evidence instance [org-internal #3627]). Companion runbooks:
> `lifecycle-ops.md` (hung rescue, fact-baseline structure) and
> `completion-report.md` §"Orchestrator side" (the quota self-report clause);
> §Prevention is this file's own closing section.
## Quota wall — worker killed mid-task by a provider usage limit
A **quota wall** is when a headless worker's turn is killed by a terminal
provider-quota error (usage limit / 429 family) mid-task. From the issue side
it looks EXACTLY like silent completion: no completion-report, no
`status=blocked` report, the ticket just stops moving. The stalled window
equals the orchestrator's polling interval, and without this runbook the
recovery was improvised.
**Evidence instance ([org-internal #3627], 2026-08-29)** — the shapes below are field-verified:
|时刻 (+08)| 事件 |
|---|---|
| 08-29 13:18 | review-code Round-1 synthesis (FAIL) posted on [org-internal #3627]; worker session `ses_fb46e705bffe9kYbSoHayoJ1OQ` (builder, opencode-go model, branch `workflow/session/3627-wedge-promptasync`) entered its revision loop |
| ~13:1816:00 | worker hit the provider's `weekly usage limit reached` mid-revision — turn killed, **no** completion-report, **no** blocked report; issue side silent |
| detection | orchestrator noticed the `/session` state stall, then confirmed via the quota error in the session log (message tail carries the ⚠️ usage-limit part, [org-internal #2912]) |
| recovery | re-wake via `prompt_async` on a known-good provider (`zhipuai-coding-plan`/glm-5.3 — since renamed `zai-coding-plan`) **plus a fact-baseline injection** (what happened / what is done / where to resume) |
| 16:26 / 16:51 | Round-2 synthesis PASS; `status=done pr=[org-internal #3675]` report received |
## Detection — three signals, in this order
Set the usual variables first:
```sh
BASE=http://127.0.0.1:4096 # dev backend (:4180 prod)
DIR=/data/octopus
SID=<worker session id>
```
**Signal 1 — tail probe (primary; survives restarts, lives in the DB).** Since
[org-internal #2912]/[org-internal #3190] a quota-killed turn persists an assistant message with
`finish=error` plus a human-readable ⚠️ text part whose metadata names the
quota family. Probe the TAIL (`limit=1`, never the no-`limit` full transcript):
```sh
curl -s "$BASE/session/$SID/message?directory=$DIR&limit=1" | jq '
{role: .[0].info.role, finish: .[0].info.finish,
error_parts: [.[0].parts[]? | select(.type=="text" and .metadata?.error == true)
| {text: .text[0:160], metadata: .metadata}]}'
```
Quota-wall verdict: `role=assistant` AND `finish=error` AND an error part with
`metadata.reason` in `{account_rate_limit, free_tier_limit, account_usage_limit,
rate_limit}` (metadata also carries `provider`). The ⚠️ text is one of — all
strings observed in this repo's runtime (`session/retry.ts`):
- `` `weekly usage limit` reached. It will reset in … `` / `Go limit reached`
(GoUsageLimitError — the [org-internal #3627] shape; `weekly` is the body's `limitName`)
- `Free limit reached` (FreeUsageLimitError)
- `Account-level usage limit — switching API keys does not reset it. You can
continue after HH:mm.` ([org-internal #3407], zhipu/zai code 1308 — account-level)
- `Provider <id> rate limited (HTTP 429)` (generic 429)
- `Generation failed — …` with a 402 `insufficient_quota` body (balance, not
window — recovery is top-up, not provider switch)
**Signal 2 — state (two shapes; use the lifecycle-ops dual-sample rule).**
```sh
curl -s "$BASE/session?directory=$DIR" | jq -r --arg s "$SID" \
'.[] | select(.id==$s) | .state'
```
- **idle** + Signal-1 tail → the turn already died on the quota error. No
abort needed; go straight to Recovery.
- **generating** with `progress`/newest-message-id frozen across two polls
3060 s apart → the 429 retry spin is still running (it retries with backoff
until the schedule ends) OR the stream is hung — treat exactly like the hung
shape in `lifecycle-ops.md`: abort FIRST, then re-wake. A single snapshot is
never an abort basis ([org-internal #3215]).
**Signal 3 — provider-level confirmation ([org-internal #2911] quota markers).** `GET
/provider` overlays active usage-limit cooldowns, so the dead provider is
visible without reading logs:
```sh
curl -s "$BASE/provider?directory=$DIR" | jq '[.all[] | select(.quota) | {id, quota}]'
# quota: {markedAt: <epoch-ms>, resetAt?: <epoch-ms>} — resetAt absent = reset unknown
date -d @$(($(curl -s "$BASE/provider?directory=$DIR" | jq '[.all[].quota.resetAt // 0] | max') / 1000)) # earliest full-reset wall clock
```
**Log grep (fallback; the dev log is restart-truncated).** Patterns actually
seen in the wild — [org-internal #3627] produced the first:
```sh
grep -E "usage limit reached|Go limit reached|Free limit reached|Account-level usage limit|GoUsageLimitError|FreeUsageLimitError|rate limited \(HTTP 429\)|insufficient_quota" \
/tmp/octopus-backend.log | tail -20
# the pool-failover WARN precedes the wall when same-name pools exist:
# "Usage limit reached on <provider> — retrying <model> on <provider>"
```
The wall is only terminal after failover has exhausted the same-name pools
([org-internal #2911] failover); the `retrying … on …` WARNs above tell you the pool was
already draining before it died.
## Recovery — abort (only if needed), re-wake on a known-good provider, inject facts
**Step 0 — classify the state (Signal 2).** `idle` → skip abort.
`generating` + frozen across dual samples → abort first, exactly per
`lifecycle-ops.md` (queued prompts are never consumed by a dead stream):
```sh
curl -s -X POST "$BASE/session/$SID/abort?directory=$DIR" -o /dev/null -w "%{http_code}\n" # → 200
curl -s "$BASE/session?directory=$DIR" | jq -r --arg s "$SID" '.[] | select(.id==$s) | .state' # → idle
```
**Step 1 — inventory surviving work.** The killed turn's filesystem writes
survive; list them so the worker re-validates instead of redoing:
```sh
git -C <worktree> status --short
git -C <worktree> log --oneline -3
```
**Step 2 — pick a known-good provider/model.** The re-wake payload MUST carry
an explicit `model` (SKILL.md Iron Law), and after a quota wall the CURRENT
provider is by definition NOT known-good. Pick a provider that is connected
AND not quota-marked:
```sh
curl -s "$BASE/provider?directory=$DIR" | jq -r \
'.connected as $c | [.all[] | select(.id as $id | $c | index($id)) | select(.quota | not) | .id] | join("\n")'
# then pick a modelID you know works on that provider (e.g. the orchestrator's
# own current model — it is generating by definition).
```
([org-internal #3627]: opencode-go dead → re-woken on zhipuai-coding-plan/glm-5.3, since
renamed `zai-coding-plan`. Do NOT "wait for reset" as the default — weekly
windows mean days.)
**Empty list — every connected provider is quota-marked.** The loop needs an
explicit exit; never re-wake into a wall you can see. Find the earliest reset
among the marked connected providers:
```sh
curl -s "$BASE/provider?directory=$DIR" | jq -r \
'.connected as $c
| [.all[] | select(.id as $id | $c | index($id)) | select(.quota)]
| min_by(.quota.resetAt // 9007199254740991) # resetAt absent → sorts last
| "\(.id): markedAt=\(.quota.markedAt) resetAt=\(.quota.resetAt // "unknown")"'
# resetAt is epoch-ms → wall clock: date -d @$((<resetAt> / 1000))
```
- Known `resetAt` within reach → suspend the re-wake (leave the worker down)
and re-run this Step after the reset; weekly windows mean days — schedule
the retry, do not poll in a tight loop.
- `resetAt` unknown, or the earliest window is unacceptable (402
`insufficient_quota` is balance — reset means top-up, not time) →
**escalate to the human orchestrator**: report every marked provider's
`id`/`markedAt`/`resetAt` and stop. Top-up or onboarding a new provider is
a human decision, not another re-wake.
**Step 3 — re-wake with a fact-baseline prompt.** Build the payload from a file
(`jq --rawfile`, SKILL.md pitfall #7), same wake shape as
`lifecycle-ops.md` but with the quota facts. The template also restates the
completion-report protocol ([org-internal #2374]) so the recovery is self-contained: it
assumes the original seed still contains the protocol block — if you are
unsure it does (e.g. the worker was dispatched before [org-internal #3669] added the quota
clause), the template's own block below carries it; fill
`<BASE>`/`<LAUNCHER_SID>`/`<DIR>` with the orchestrator session's values:
```sh
cat > wake.txt <<'EOF'
## 事实基线(配额墙恢复)
你上一回合被供应商配额错误杀死,世界观停留在被杀时刻。先读以下事实再行动:
1. 发生了什么:<HH:mm> 你的回合因 <provider> 配额耗尽终止(usage limit
预计重置 <HH:mm 或 未知>);本消息由已知良好供应商重新唤醒,非你自行恢复。
2. 已做什么:<已完成步骤/commit/PR 状态>worktree 未提交变更:
<git status --short 摘要>——先 re-validate 这些文件,勿重做已完成工作。
3. 从哪继续:<一件具体的下一步任务>。
4. 证据规则:逐条到引用来源(issue 评论 / PR / 工件)核实后再行动,
勿仅凭本基线行动。
## 完成回报协议(重申 [org-internal #2374]/[org-internal #3669];不确定原始 seed 是否含协议块时以本节为准)
完成、阻塞或移交时,必须回报编排会话(你的唤醒者):
POST <BASE>/session/<LAUNCHER_SID>/prompt_async?directory=<DIR>
body: {"agent":"builder","model":{...},"parts":[{"type":"text","text":"<report>"}]}
<report> 单行格式:[#<ticket>] [<node-or-slug>] status=done|blocked|handoff pr=#<N> summary=<一行>
约束:若再遇配额/用量上限错误,立即回报 status=blocked
reason=quota-exhausted summary=<provider 与重置提示>pr=#<N> 有 PR 时带上)
后停止,勿自行换模型重试;回报失败 → 源 issue 评论兜底。
EOF
jq -n --rawfile p wake.txt '{agent:"builder",
model:{providerID:"<known-good providerID>",modelID:"<known-good modelID>"},
parts:[{type:"text",text:$p}]}' \
| curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
-H 'content-type: application/json' -d @- -o /dev/null -w "%{http_code}\n" # → 204
```
Then poll the tail (`limit=1`) until an assistant message appears, and keep
expecting the completion-report per protocol — the re-woken worker owes you
`status=done|blocked` like any other (the restated block in the template
makes this hold even when the original seed predates [org-internal #3669]).
## Prevention — before dispatch, and in the seed
**Pre-dispatch provider check (30 seconds, catches most walls):**
```sh
# 1) no active quota markers on the dispatch provider (else pick another)
curl -s "$BASE/provider?directory=$DIR" | jq '[.all[] | select(.quota) | .id]'
# 2) dispatch provider is connected AND ≥1 other connected provider exists as fallback
curl -s "$BASE/provider?directory=$DIR" | jq '{connected, fallbacks: (.connected | length > 1)}'
```
If the intended provider is already quota-marked, dispatch on a different one
— do not launch into a wall you can see coming.
**Quota self-report clause (turns a silent death into a harvestable
blocked).** The canonical clause text lives in `completion-report.md`
§"Orchestrator side" (配额自报, [org-internal #3669]) — append it VERBATIM to the seed
prompt's completion-report block. Single source of truth: do not fork or
restate the clause here; its blocked payload already follows the one-line
key=value spec (`status=blocked reason=quota-exhausted summary=<provider +
reset hint> pr=#N`, completion-report.md `<report>` format).
With the clause in the seed, a worker that hits the wall reports `blocked`
instead of dying silently — the orchestrator harvests the report and runs the
Recovery section directly. The clause is also why the re-wake prompt's
constraint (Step 3) repeats it: the re-woken worker must know the rule still
holds on the new provider.
@@ -0,0 +1,182 @@
> Extracted from headless-session-ops/SKILL.md (Launch a session for a specific ticket (#N)) — moved verbatim 2026-08-26, ticket [org-internal #3480].
## Launch a session for a specific ticket (#N)
The common case: an agent (or cron/CI) needs to spin up a fresh main session to
work a tracked issue. Compose the seed prompt **from the issue itself** so the
new session starts with real context, and launch it on **the current session's
model**.
0. **Owner check (MANDATORY pre-step, [org-internal #1803]).** Before creating anything,
verify no other live session already owns this ticket or code area —
duplicate ownership is how [org-internal #1744]/[org-internal #1753] collided. The check spans four
data sources; any live claim → **ABORT the launch and report the
conflict**.
**Mechanized ([org-internal #3667])**: `bash script/session-conflicts.sh <N>` runs the
session-title scan (a), the branch scan (d), and the open-PR check (c)
in one read-only pass (exit 4 = conflict, `--json` for a machine
summary); the assignee/claim check (b) is `claim.sh`'s compare-and-swap
at claim time. The manual recipes below remain the fallback and the
normative definition (`core/rules/session-scope-guard.md`).
**a. Session-title scan (live session check).** The listing endpoint
returns at most `limit` (default 100) sessions — on busy repos (300+ live
sessions in one directory) that silently truncates and the scan misses
owners ([org-internal #3190]). Non-range listing responses carry `X-Total-Count` (full
filter population, untruncated) and `X-Has-More: true` (only when the
page is truncated). Scan procedure: fetch page 1, and when
`X-Has-More: true` is present re-fetch once with `limit=$TOTAL` — treat
any failure to obtain the full population as "more owners may exist"
(ABORT or widen the scan — never assume the first page is the whole
population).
```sh
# N must be the digits-only issue number (e.g. N=1803) — never interpolate
# raw issue text here; the regex below assumes digits.
[[ "$N" =~ ^[0-9]+$ ]] || { echo "N must be digits only"; exit 1; }
HDR=$(mktemp)
BATCH=$(curl -sD "$HDR" "$BASE/session?directory=$DIR")
TOTAL=$(awk -F': ' 'tolower($1)=="x-total-count"{print $2}' "$HDR" | tr -d '\r')
if [[ "$TOTAL" =~ ^[0-9]+$ ]] && awk -F': ' 'tolower($1)=="x-has-more"{print $2}' "$HDR" | grep -q true; then
# truncated page + known population — refetch the full set in one request
BATCH=$(curl -s "$BASE/session?limit=$TOTAL&directory=$DIR")
elif awk -F': ' 'tolower($1)=="x-has-more"{print $2}' "$HDR" | grep -q true; then
# truncated page but no usable X-Total-Count (older server) — the
# population is unknown; do NOT re-fetch with the same default limit
# (it would silently re-truncate). Escalate instead.
echo "ERROR: session listing truncated but X-Total-Count unavailable — cannot establish full owner population" >&2
rm -f "$HDR"; exit 1
fi
rm -f "$HDR"
echo "scanned $(echo "$BATCH" | jq 'length') of ${TOTAL:-?} sessions in $DIR" >&2
echo "$BATCH" | jq -r --arg n "$N" '.[] | select(.title | test("#" + $n + "([^0-9]|$)")) | "\(.id)\t\(.title)\t\(.time.updated)"'
```
- Any hit whose `time.updated` is recent (session still active) → **ABORT
the launch and report the conflict** (issue comment naming the owning
session id). Do not launch a second session for the same ticket.
**b. Assignee / claim check ([org-internal #2297]).** Read the issue via
`工单 API(见 TERMINOLOGYget(owner, repo, index: N)` (or `GET /api/v1/repos/$OWNER/$REPO/issues/$N`):
- If `assignee` is set and is **not** the launching agent → **ABORT** and
report (the ticket is already claimed).
- If a claim comment names a **different branch / session id** → **ABORT**
and coordinate on the issue before proceeding.
**c. Open-PR check.** List open PRs referencing `#N` —
`工单 API(见 TERMINOLOGYsearch(q: "#N", type: "pulls", state: "open")` (API source)
or `gitea_pull__list` — and **ABORT** if an open PR already covers the ticket.
**d. Remote branch check ([org-internal #2297]).** A local branch is invisible to other
sessions — check the remote too:
`git ls-remote origin 'workflow/*'` and scan for a branch tail covering `#N`.
Also check the repo side locally: `git worktree list` +
`git branch --list 'workflow/*'` for a branch / worktree already covering #N.
- A stale hit (session idle for hours / clearly abandoned) → do NOT
hand-post a takeover comment (retired admin workaround — unauditable);
dead-claim takeover is mechanized ([org-internal #3668]): `bash script/claim.sh
takeover <ticket> <branch> --session-id <id>`. It enforces the
evidence gate (branch ABSENT on origin AND (session 404 OR comment at
least CLAIM_TAKEOVER_STALE_DAYS old)) and exits 10 when the evidence
does not hold — a valid claim is never superseded; an idle-but-alive
session is NOT provably dead, coordinate on the issue instead (exit 4
= owned/conflict, in claim.sh and session-conflicts.sh alike).
Evidence rules: `core/rules/session-scope-guard.md`
§"Dead-claim takeover" (claim.sh header is normative).
The runtime counterpart of this check is `core/rules/session-scope-guard.md`
("One task, one owner"), injected into every session's prompt.
- **Backend hard guard ([org-internal #1989]):** `POST /session` hard-rejects duplicate-ticket
session creation with HTTP 400. The backend guard is **on by default ([org-internal #2350])**;
set `enabled: false` in config to opt out. It fails open ONLY on defect
paths (DB / config errors), never as a configured disable. The front-end
soft check above is now backed by this backend hard check for defense in
depth.
- **Claim-first ([org-internal #2297]).** Claiming a ticket is one atomic 3-step action:
(1) set the issue assignee to the working account, (2) post a claim comment
naming the workflow branch and session id, (3) push the workflow branch to
remote (`git push -u origin workflow/...`). The claim is valid ONLY when all
three steps complete — an incomplete claim is NOT a claim. Do NOT launch the
session until all three steps are done (see
`core/rules/session-scope-guard.md` §"Claim-first"). If a collision HAS
already happened, follow `rules/ownership-collision-runbook` (wiki, L2 on-demand).
- **Provisioning after claim ([org-internal #3642]) is ONE command** — claim + worktree +
Tier-1 runs scaffold:
`bash script/claim-provision.sh <N> workflow/<branch>` (runs claim.sh with
exit codes passed through, attaches the session worktree to the claimed
branch via `session-worktree.sh create <slug> --branch <branch>`, then
scaffolds `<runs-root>/{slug}/` via `<harness-package>/script/runs-init.ts`
with kind-aware exemptions; prints one JSON summary; idempotent re-runs).
Pitfall #10's pre-built worktree is exactly what it produces — put the
returned worktree path in the claim comment + seed prompt.
1. **Read the issue.** Use the REST API (`gitea-rest` skill; `gitea-mcp` is retired):
```sh
curl -s "$GITEA/api/v1/repos/$OWNER/$REPO/issues/$N" | jq '{title, body}'
```
or `工单 API(见 TERMINOLOGYget(owner, repo, index: N)`.
2. **Compose the seed prompt** from the issue body. The seed MUST, at minimum:
- restate the **goal** in one sentence;
- list the **constraints** and **acceptance criteria**;
- cite every **wiki artifact path** / related issue referenced in the body
(e.g. `{epic-slug}/dag`, `{epic-slug}/dag-nodes/{node-id}`, `[org-internal #1691]`);
- tell the new agent to follow the right pipeline skill for the work type
(implement / bugfix / design / …).
Write it to a file (handles newlines):
```sh
cat > seed.txt <<'EOF'
Work issue #N: <one-line title>.
Goal: <…>.
Constraints: <…>.
Acceptance: <…>.
Artifacts: <wiki paths from the issue body>.
Follow the `implement` skill (Mode: bugfix) for this.
Session scope guard (mandatory): if a test fails and it was NOT caused by
your change — classify (pre-existing/flaky), file a BF/FT issue per
core/rules/session-scope-guard.md, and continue this task. Do NOT fix
unrelated failing tests in place.
## 完成回报协议 (mandatory, [org-internal #2374])
完成、阻塞、或移交时,向编排会话回报:
POST <BASE>/session/<LAUNCHER_SID>/prompt_async?directory=<DIR>
(编排目录未知时可用全局路由 POST <BASE>/prompt_asyncbody 携 sessionID[org-internal #4307]
body: {"agent":"builder","model":{...},"parts":[{"type":"text","text":"<report>"}]}
<report>: [#N] [<node-or-slug>] status=done|blocked|handoff branch=<ref> [pr=#<PR>] verify=<changed+typecheck> risk=<high|low> summary=<one line>
约束:worker 只推分支不开 PR(PR 由编排串行开);交付推送前 test:changed+typecheck 须绿。若再遇配额/用量上限错误,立即回报 status=blocked reason=quota-exhausted summary=<provider 与重置提示>branch=<ref> 必带)后停止,勿自行换模型重试。
回报失败(非 2xx / 连接拒绝)→ 在本 issue(或父 Epic)发同内容评论兜底。
决策边界([org-internal #2378]):遇方向性决策点 → status=blocked 回报,勿调 question。
EOF
```
3. **Determine the known-good model** (reuse the current session's — see the
"Reuse the current session's model" section).
4. **Run the 3-step flow** with `title:"#N — <short>"` and the file-built
payload. Read the model from the launcher session's own record — this also
applies the `model.id → modelID` remap from the "Reuse" section:
```sh
MODEL=$(curl -s "$BASE/session/$LAUNCHER_SID?directory=$DIR" \
| jq -c '.model | {providerID, modelID: .id}')
SID=$(curl -s -X POST "$BASE/session?directory=$DIR" \
-H 'content-type: application/json' \
-d "{\"title\":\"#$N — <short>\",\"agent\":\"builder\"}" | jq -r .id)
PAYLOAD=$(jq -n --argjson m "$MODEL" --rawfile p seed.txt \
'{agent:"builder",model:$m,parts:[{type:"text",text:$p}]}')
curl -s -X POST "$BASE/session/$SID/prompt_async?directory=$DIR" \
-H 'content-type: application/json' -d "$PAYLOAD"
```
5. **Poll** Step 3 until an `assistant` message appears — bounded tail poll
(`&limit=1`), never the no-`limit` full-transcript form. Record `$SID` somewhere
durable (issue comment, CI log) so the session is traceable to the ticket.
6. **Add the completion-report block to the seed prompt** (see next section) so
the worker reports back when it finishes or blocks — without it, this
orchestrator has no push channel and must poll forever ([org-internal #2374]).
+82
View File
@@ -0,0 +1,82 @@
---
name: image-interpret
description: >
Use ONLY when the agent needs to read or analyze an image (screenshot,
design mockup, UI capture, .png/.jpg/.webp file) but the active model lacks
vision capability. Delegates to the Image Interpreter role (vision-capable
model) which returns a structured text spec — layout regions, colors (hex),
spacing (px), typography, component hierarchy, and responsive breakpoints.
Do not use for non-image tasks or when the active model already supports
vision.
triggers:
- screenshot
- mockup
- design mockup
- UI capture
- .png
- .jpg
- .jpeg
- .webp
- image
- 读图
- 设计稿
- 截图
- 视觉
role: Tool
---
# Image Interpret — delegate image reading to a vision-capable role
## When to use
You (the calling agent) cannot read images — your model has no vision. Any
task that requires *seeing* a visual artifact must be delegated to the
**Image Interpreter** role, which runs on a vision-capable model and returns a
structured text description you can act on.
Trigger situations:
- The user references a screenshot, design mockup, or image file.
- A task requires comparing rendered UI against a design.
- You need layout/color/spacing specs extracted from a picture.
- A file path ending in `.png` / `.jpg` / `.jpeg` / `.webp` must be understood.
## Workflow
1. **Identify the image.** Confirm the file path (or paths) that must be read.
Resolve it to an absolute path the subagent can access.
2. **Frame the question.** Decide exactly what the caller needs — full layout
spec, a specific region, color tokens, component list, diff vs. existing UI.
3. **Delegate via the Task tool with `role: "Tool"`.** Do NOT pass
`subagent_type` — it is derived automatically from the role.
```
Task(
role: "Tool",
description: "Extract header layout spec",
prompt: "Read the image at /abs/path/mockup.png. Extract: (1) header
layout regions with bounding positions, (2) all colors as hex,
(3) spacing in px, (4) typography (family/size/weight). Return
a structured text spec the caller can implement without seeing
the image."
)
```
4. **Consume the returned text spec.** The Image Interpreter returns a
structured description only — it cannot write code or artifacts. Use the
returned spec to drive your own work (code generation, design review,
planning).
5. **Do not attempt to read the image yourself.** If you are a non-vision
model, opening the image yields nothing. Always delegate.
## Notes
- The Image Interpreter is **read-only** — it describes images, it does not edit
files or write code.
- One delegation per distinct visual question. Batch multiple images into a
single delegation only when they must be compared.
- If the returned spec is ambiguous, delegate again with a sharper question
rather than guessing.
+350
View File
@@ -0,0 +1,350 @@
---
name: implement
description: >
Use ONLY when implementing a work item from an approved iteration plan,
OR fixing a bug (bugfix mode), OR refactoring code (refactor mode),
OR porting a feature (port mode). The Developer (Worker) reads the work
item, design sections, and acceptance criteria, writes code, and
self-checks against the relevant checklist before handing off to code
review.
# `stage` is intentionally omitted: `implement` is a production-phase name the
# stage registry rules invalid post-[org-internal #3072] phase 3 (see
# core/schemas/workflow-routing.schema.json stage enum — production-phase
# names are "no longer valid anywhere"). The implement skill has no registry
# gate id; the gates it feeds into are `review-code` and `verify`.
triggers:
- implement
- implement work item
- implement WI-
- work item
- 实现工作项
- 开始写代码
- implement the iteration
- bugfix
- fix a bug
- fix bug
- fix this bug
- 修复
- 修复bug
- help me fix
- doesn't work
- does not work
- not working
- is broken
- stack trace
- throws an error
- crashes
- refactor
- 重构
- restructure
- 删除死代码
- dead code
- improve code structure
- 重新组织代码
- graduate experimental
- remove experimental guard
- 移除实验性
- port
- porting
- port feature
- port this feature
- migrate feature
- 移植
- 迁移功能
- code change
- write code
role: Producer
---
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
# Skill: implement
## Mode Selection
This skill handles four work modes. The main session identifies the mode from the work item's `type` field or the user's request:
| Mode | When to use | Key difference |
|---|---|---|
| **implement** (default) | Work item from an approved iteration plan | Standard implementation per acceptance criteria |
| **bugfix** | User reports a bug / fix request | Reproduce → root cause → regression test (must FAIL first) → minimal fix |
| **refactor** | User asks to restructure code (no behavior change) | Establish baseline → transform in ≤10 steps → verify zero regression |
| **port** | User asks to port a feature from another project | Read source → map concepts → implement in target conventions → verify fidelity |
In **standalone mode** (user-initiated, no iteration plan), skip preconditions about plan/approval and go directly to the mode-specific workflow.
In **pipeline mode** (work item from iteration plan), follow the standard preconditions then the mode-specific phases.
**On-demand references**: each mode's phase-by-phase detail (templates, prompts, orchestration flows, historical notes) lives in `reference/{mode}-mode.md`, NOT injected — read it at mode entry. Full read-timing table: References section.
> **Progressive disclosure ([org-internal #3734])**: when dispatching a Developer scoped to
> one mode, pass `skills: ["implement:mode-bugfix"]` (preamble + that
> mode's workflow only) instead of the full body; pipeline-mode dispatches
> should also carry `pipeline-detection`
> (`"implement:mode-implement,pipeline-detection"`). Mode Selection, Agent
> Role, Greenfield vs. Brownfield, … are unmarked preamble and always
> inject. Full-body remains the default when no selector is passed.
## Agent Role
The implementation is owned and produced by the **Developer** (Worker). The Builder delegates each work item to a dedicated Developer sub-agent with the full design and plan context.
> **Role naming**: throughout this skill, "Builder", "Orchestrator", and "main session" name the same coordinating role (the main session that dispatches Developer sub-agents and validates output) — not a distinct role from the "Orchestrator" used by `review-code` and the shared review pipeline.
The Developer is responsible for:
- Reading the assigned work item, design sections, and acceptance criteria.
- Writing code that faithfully implements the design.
- Running typecheck, lint, and tests to self-verify.
- Self-checking against the relevant checklist before handoff.
- Persisting the final report to disk before returning — worker-report persistence ([org-internal #2847], see `../_shared/worker-report-persistence.md`).
- Writing no more and no less than the work item scope — no opportunistic refactoring of unrelated code.
The Builder's role is to validate the implementation output and pass it to code review. The Builder MUST NOT write or revise implementation code.
### Pre-flight checklist injection ([org-internal #2599])
Before dispatching ANY Developer (or Tester) sub-agent — in every mode (implement / bugfix / refactor / port; pipeline or standalone) — the orchestrator resolves the ticket's route (the Process Assessment Gate Step 0 already resolves the Kind/* route) and reads `<instance-root>/workflow-routing.yaml` `routes.{Kind}.preflight`. When the list is non-empty, prepend it to the sub-agent's task prompt as:
```
Pre-flight self-check (evidence-based, from retrospective — verify each
BEFORE writing code; if one is already satisfied, note why in impl-notes):
1. {item} (evidence: {evidence})
2. ...
```
Rules:
- The list is a human-landed checklist (retro proposes, a human lands it) — NEVER synthesize or extend items at dispatch time.
- Cap at `preflight.max_items` entries; beyond it, drop oldest by `added_cycle`.
- Items are self-checks, not gates: an unsatisfied item means the Developer addresses it in the implementation (and says how), not that dispatch aborts.
<!-- inject: ../_shared/large-prompts.md -->
> **Context compaction**: this skill is a pipeline stage boundary. The main session (orchestrator) compacts at this clean boundary ONLY when a capacity/projection trigger holds, per the L1 rule `core/rules/compact.md` §"Stage-boundary compaction" (long multi-stage runs — DAG Epic orchestration — keep the legacy every-boundary compaction; short runs — bugfix / DAG task — and standalone runs default to NOT compacting). The sub-agent this skill dispatches persists its artifacts to the Gitea wiki under `{slug}/` as it goes, so a mid-run compaction loses nothing — re-read the stage's wiki index to resume.
## Role Split: Developer vs Tester
Each execution mode below defines its own Tester focus and any mode-specific orchestration overrides.
## Greenfield vs. Brownfield
**Greenfield** (new project): Create new files following the design. Project conventions are defined by the design document.
**Brownfield** (existing project + new feature):
- **Read neighbors first.** Before writing code, read at least 3 existing files in the same module to absorb the project's code patterns. (Shared brownfield rule — canonical statement: `core/skills/review-code/SKILL.md` §"Greenfield vs. Brownfield"; the threshold and same-module scope are defined there.)
- Match existing conventions exactly: error handling style, logging format, naming, file structure, import ordering, type declaration placement.
- New code MUST follow existing conventions consistently — no style drift.
- No opportunistic refactoring of unrelated existing code. If you see a bug or improvement opportunity in unmodified files, log it in the implementation report as a separate observation — do not fix it in this work item.
- Phase 1 (Parse Context) includes reading neighboring code files to establish the project's conventions. Phase 4 (Self-Check) compares new code against these conventions.
---
## Execution Modes
<!-- section:mode-implement -->
### Mode: implement (default)
Standard workflow for work items from an approved iteration plan. Full phase detail, templates, prompts: `reference/implement-mode.md` (read at mode entry).
#### Preconditions
> **DAG-mode input path** (DAG ticket pipeline — `Kind/Epic` / `Kind/Feature` DAG parent, routes-table direct): DAG-routed tickets **ignore `Size/*`** — the tiered Preconditions below are replaced by the node spec: work item + acceptance criteria resolve from the **frozen DAG copy** wiki page `{epic-slug}/dag` (+ `{epic-slug}/dag-nodes/{node-id}` subpages when AC detail is sunk) and the node ticket's issue body — no legacy `{slug}/04-plan-*` page, no `Size/*`-tiered req/design page. The design-space + iteration-plan convergence preconditions are replaced by the **review-dag single-gate convergence**: `octopus review status --stage review-dag` must show `success` before the node is implemented.
> **DAG-route read map** (every legacy `{slug}/04-plan-*` / `{slug}/03-design-*` reference below resolves from the frozen DAG copy instead — mirror of `verify/SKILL.md`'s DAG branch; full map: `reference/implement-mode.md` § Preconditions):
>
> - Work item (`04-plan-04-iteration-assignment` / issue body) → node spec in `{epic-slug}/dag` + node ticket body.
> - Acceptance criteria (`04-plan-05-acceptance-criteria` / issue body) → node `acceptance_criteria` (+ sunk subpages) + node ticket body.
> - `test_id` (測試用例 ID) → the `test_id` declared on the node AC in `{epic-slug}/dag`.
> - Design sections / interface design (`03-design-**`) → node spec + cross-session edge contracts (no design page).
> - Component mapping (`03-design-08-traceability`) → node `req_refs` + component field.
>
> At Phase 6 handoff, pass `mode: "dag-task"` to review-code (its DAG Task Mode keys off the same frozen-DAG-copy detection).
> **Legacy pipeline preconditions retired ([org-internal #3072] phase 3, 2026-08-21)**: tiered artifact-existence and review-convergence checks belonged to the archived legacy pipeline. Live input modes: DAG task mode (above) and standalone modes (the request itself is the spec). Historical tiered publish targets: `reference/implement-mode.md` § Legacy notes.
Before starting implementation, confirm:
- [ ] Work item is specified (DAG node ticket `{node-id}`, or a clear task description in standalone modes).
- [ ] `core/checklists/implementation.md` is accessible.
- [ ] `slug` matches the run's slug (DAG: `{epic-slug}`).
- [ ] 跨阶段门控清单: `core/checklists/pipeline-gate.md` accessible and its DAG 路由变体 section confirmed — frozen DAG copy exists, single gate converged, upstream dependencies at terminal state (`ready`); else abort, listing the blocked nodes.
**If any precondition is unmet, abort and inform the user** — list every missing artifact, un-converged review, and blocked dependency (complete gate checklist + Recovery Protocol: `core/checklists/pipeline-gate.md`). When no work item is specified, resolve the ready/pending task nodes from `{epic-slug}/dag` and present them for selection (prompt: `reference/implement-mode.md` § Preconditions — work-item selection).
#### Phases skeleton
Every gate below is hard — agents rationalize skipping exactly these. Full detail: `reference/implement-mode.md`.
1. **Phase 1 — Parse Context**: read the work item (node spec + node ticket), acceptance criteria (every falsifiable `AC-{n}` / `NFR:` entry and its declared `test_id`), design context (cross-session edge contracts), and the existing codebase; resolve inputs per the DAG-route read map (standalone: the request). Read every referenced design file before writing code.
2. **Phase 2 — Plan Implementation**: brief plan (template: `reference/implement-mode.md` § Phase 2). GATES: **≤ 3 files per work item** (more → the Builder/user MUST split it); every file must map to a design component (else flag the design gap and abort); do NOT invent design decisions. Present the plan and ask: proceed? (yes / no / revise).
3. **Phase 3 — Implement**: design-exact code; tests cover every AC.
- **Design discipline**: component interfaces, method signatures, return types, data model fields, API endpoints/schemas/status codes MUST match the design exactly; an impossible design decision → stop and report the gap, never silently deviate.
- **Test discipline (Red → Green, declared test_ids)**: write each declared test FIRST and confirm it fails for the intended reason (Red) before writing the implementation (Green); the test's `file-path :: test-name` MUST match the declared `test_id` exactly — the implement-side handshake with `verify` (DOD-1.6). `MANUAL` / `BENCH:<script>` test_ids are exempt from the Red step; an already-passing test is noted in the Phase 5 report, not forced to fail.
- Code quality + incremental commitments (conventions, no unjustified dependencies, public-API docs; shared types → data access → logic → handlers, typecheck per unit): `reference/implement-mode.md` § Code Quality.
4. **Phase 4 — Self-Check** (all mandatory):
1. `bun typecheck` (or project-equivalent) — zero errors.
2. `bun oxlint --deny-warnings` (repo root — the review-code mechanical gate's canonical lint invocation; `bun lint` is the package-script alias) — zero errors.
3. `bun run test:changed` (or project-equivalent; full suite `bun run test:parallel` is verify's job, not a per-revision gate, [org-internal #2598]) — all tests pass.
4. Post-deletion cleanup (when any code was removed): re-run lint + typecheck to catch orphaned imports/variables/type references.
Then self-check `core/checklists/implementation.md`; every new function/method/exported API has ≥ 1 test; interface promises cross-checked against the node's edge contracts. **Review-readiness GATE**: self-attest `core/checklists/code-review.md` (COR, DGN, SEC, PERF, TST, STY, DBT, A11Y, DOC, TRC), record pass/fail per dimension in the Phase 5 report; handoff requires **0 BLOCKERs and 0 MAJORs** — if you can find a MAJOR, the formal review will too; fix it now.
5. **Phase 4.5 — Iteration Completion Commit**: after ALL work items in the iteration pass Phase 4, commit with format `[{chunk-id}][{iteration}] {summary}`; commit body REQUIRED for non-trivial commits (> 1 file or > 20 LOC): What (files + purpose + the `WI-{NNN}` ID — code-review TRC 10.1), Why (design/REQ motivation), Evidence (test names / verification commands). Full rules: `reference/implement-mode.md` § Phase 4.5.
6. **Phase 4.6 — Issue Checklist Sync (progressive)**: after committing, mark items this iteration delivered `- [x]` + `_(commit {sha}: file/component)_` per the `issue-checklist-sync` L1 rule; do NOT touch items outside this iteration's scope.
7. **Phase 4.7 — PR-Creation Sync**: the session pushes its branch and reports `status=done branch=<ref> verify=… risk=…` — the orchestrator opens the PR (serially per TD-678/[org-internal #4425]) and applies the `Risk/*` label from the report's `risk=` hint, computed per the risk-classifier frozen table (`HIGH_RISK_GLOBS` in `.gitea/scripts/risk-classifier.ts`: core/migrations/deploy/`core/rules/**`/`.gitea/workflows/**``Risk/High`, else `Risk/Low`); `Risk/High` PRs merge manually by design. Once the PR exists, ensure `## 当前状态` exists (PR / 代码评审 / CI rows are written by the `status-sync` poller, NOT by hand); append the PR reference to the matching Epic task-list row. **Never hand-sync main into the PR branch** — that is the keep-mergeable workflow's job. Detail: `reference/implement-mode.md` § Phase 4.6 / § Phase 4.7.
8. **Phase 5 — Report**: implementation report with AC → test traceability (template: `reference/implement-mode.md` § Phase 5). **Persist before returning ([org-internal #2847])** — the Developer's LAST action before returning the report: write it to `<runs-root>/{slug}/workers/{chunk-id}-worker-{seq}.md` (Tier 1 run workspace) else `/tmp/octopus/{chunk-id}-worker-{seq}.md` (`../_shared/worker-report-persistence.md`). The persisted copy is the report of record — applies to EVERY mode's report phase.
9. **Phase 6 — Handoff to Code Review**: present the report; signal readiness via `signal_stage_done`. Do NOT mark the work item complete until code review passes.
#### Tester focus & Common Rationalizations
Boundary + contract tests — the cases the Developer is structurally biased to miss; every acceptance criterion MUST map to ≥ 1 test. Implementation fails far more from **pressure** than from ignorance — full detail (11-row Excuse → Reality table): `reference/implement-mode.md` § Tester focus for implement / § Common Rationalizations.
---
<!-- section:mode-bugfix -->
### Mode: bugfix
Reproduce, isolate, and fix a bug with a regression test that MUST fail before the fix. Small localized bugs → standalone (existing system behavior is the specification; review gate optional — only when > 20 lines or ≥ 3 files); large/complex bugs → pipeline (review + verify mandatory). Phase detail, templates, prompts: `reference/bugfix-mode.md` (read at mode entry).
> **Routing override (ticket-seeded)**: the Optional/None gate above applies to *user-initiated* standalone mode. A `Kind/*` route whose `keep_gates` includes `review-code` / `verify` (e.g. `Kind/Bug`, `Kind/Testing`) makes those gates MANDATORY regardless of size (a gate is mandatory if EITHER the route OR the skill requires it; skipping is valid only when BOTH agree).
#### Phases skeleton
The regression test MUST fail before the fix — the Tester dispatches between Phase 2 and Phase 4, not after the fix (dispatch flow: `reference/bugfix-mode.md` § Role & Responsibilities; single-Developer invocation only for trivial single-file fixes — force the split when the fix touches ≥ 2 files or the root cause spans ≥ 2 levels of indirection).
1. **Phase 1 — Understand & Reproduce**: read relevant code; check existing tests (a passing test on this path → the bug is in the test or an uncovered branch); reproduce and document (template: `reference/bugfix-mode.md` § Phase 1). **If the bug CANNOT be reproduced, stop and report — do not guess-patch.**
2. **Phase 2 — Isolate Root Cause**: trace symptom → proximate cause → root cause (RCA template: `reference/bugfix-mode.md` § Phase 2); fixing a symptom → stop, go deeper; not found after 3 levels of indirection → pause and report, no surface-level patch. Then evaluate routing (below).
3. **Phase 3 — Write a Regression Test**: exercise the exact bug path with the failing inputs; it MUST fail with the bug's symptom NOW, before the fix (already passing → the test does not cover the bug, rewrite it; no failing test possible → most targeted test, marked `[flaky]`).
4. **Phase 4 — Fix**: minimum change resolving the root cause; one conceptual change per fix — no bundled refactoring, style changes, or "while I'm here" improvements (root cause in a different file → fix it there). Run the regression test (MUST pass) + relevant unit tests.
5. **Phase 5 — Self-Check & Report**: `bun typecheck`; `bun oxlint --deny-warnings`; `bun run test:changed` (all pass; full suite belongs to verify); verify `core/checklists/bugfix.md`; publish the bugfix report as wiki page `{slug}/bugfix-report` (template: `reference/bugfix-mode.md` § Bugfix Report); persist per the Phase 5 persistence rule (Mode: implement, [org-internal #2847]).
6. **Phase 5.5 — Issue Checklist Sync (standalone bugfix)**: sync the source issue at each transition (commit / PR / review / CI / close) per the `issue-checklist-sync` L1 rule and its standalone-flow table.
7. **Phase 6 — Approval**: present the report (prompt: `reference/bugfix-mode.md` § Phase 6 — Approval).
#### Routing Decision (after Phase 2)
Full escalate/stay criteria: `reference/bugfix-mode.md` § Routing Decision. In short — **escalate to pipeline** on ANY of: ≥ 5 files · ≥ 2 modules/components · design-level root cause · data migration / schema change · public-API / contract change · dependency change · > 50 lines · user requests full-process; **stay standalone** only when ALL the opposites hold. If uncertain, escalate — a false escalation costs review rounds; a false standalone decision skips quality gates.
**Big-bug relabel rule ([org-internal #3061])** — before the generic escalation above, split the triggers by kind:
- **Design-level triggers** (root cause is a design decision — protocol / schema / architecture; shared-contract or public-API change; data migration): do NOT push through bugfix — **relabel the ticket `Kind/Feature`** and reroute via Step 0 (DAG route; 13 node small DAG expected); repro + root-cause notes become node input.
- **Mechanical size triggers only** (many files / lines, same design): stay in bugfix — batch into iterations, keep review-code + verify. Scale alone never justifies a relabel.
#### Pipeline Mode (bugfix)
> **Legacy path retired ([org-internal #3072] phase 3)**: the requirements → design → review → plan front-end was archived; a big bug needing a design-level decision relabels `Kind/Feature` into the DAG route (big-bug relabel rule above).
When a bugfix escalates beyond standalone scope, the bug report becomes a pipeline input; the bugfix phases (reproduce, root cause, regression test, fix) are embedded within the implement stage, and review-code + verify remain mandatory. On the DAG route the node spec lives in `{epic-slug}/dag`; the Developer follows bugfix Phases 16 as the implementation method, then produces the standard implementation report (Mode: implement, Phase 5).
**Pipeline abort criteria** — before any code is written in pipeline mode, abort if ANY of: (1) bug no longer reproduces; (2) root-cause hypothesis falsified during re-isolation; (3) resolved by external change; (4) reproduction confidence < 3/5 after one re-isolation iteration. Abort procedure (`{slug}/ABORT` wiki page, no commit/merge, retrospective, archive): `reference/bugfix-mode.md` § Pipeline Abort Criteria. Once code is written, abort is no longer valid — the run proceeds review-code → verify.
**Common Rationalizations (bugfix) & Incident Triage** — full 9-row Excuse → Reality table + Incident Triage Carve-Out (under active incident pressure the Phase 2→3 ordering MAY be relaxed, never skipped — a stop-gap may ship first, BUT the full root-cause trace + failing regression test + proper fix MUST land in the same incident window): `reference/bugfix-mode.md` § Common Rationalizations (bugfix) / § Incident Triage Carve-Out.
---
<!-- section:mode-refactor -->
### Mode: refactor
Restructure existing code without changing observable behavior — the existing test suite is the safety net; every step MUST be verified before proceeding. Templates and prompts: `reference/refactor-mode.md` (read at mode entry). **Execution modes**: standalone (user says "refactor {X}"; review gate optional — only when > 50 lines or ≥ 5 files) vs pipeline (refactoring WI / DAG node; scope from the node spec, review mandatory).
#### Preconditions
- [ ] Scope is specified (which file, module, or pattern to refactor).
- [ ] An existing test suite covers the scope (if unknown, run with coverage first).
- [ ] No uncommitted changes (`git status` is clean); `core/checklists/refactoring.md` is accessible.
**No test coverage? Stop.** Refactoring without tests is rewriting with unknown side effects — write characterization tests first or skip this module (prompt: `reference/refactor-mode.md` § No Test Coverage? Stop.).
#### Phases skeleton
1. **Phase 1 — Scope & Baseline**: map exact files + dependents; run `bun run test:parallel` (the refactor baseline legitimately needs the full suite) — ANY pre-existing failure → stop ("Cannot begin refactoring with failing tests. Fix them first."). Capture baseline: test count, coverage, optional complexity (template: `reference/refactor-mode.md` § Baseline).
2. **Phase 2 — Define Target Pattern**: Extract / Inline / Rename / Move / Replace / Simplify / Upgrade, with one-sentence goal + success criteria (all tests pass unchanged; structural goal met; coverage does not decrease). Pipeline mode: align with the node spec + contracts, or justify in the report.
3. **Phase 3 — Decompose into Steps**: smallest individually-verifiable steps (each reversible, suite-passing, ONE conceptual transformation); present the plan before executing (user may approve / reorder / reject). **If > 10 steps, the scope is too large — split into multiple sessions.**
4. **Phase 4 — Incremental Execution**: per step — transform, run `bun run test:changed` (ALL pass; per-step scoped reruns — full suite is verify's job, [org-internal #2598]), commit `refactor: {what} from {where}`. **If FAIL: revert immediately** — do NOT fix the test or code within the same step; find a smaller decomposition (exception: fix a flaky test first as a prerequisite step, then retry).
5. **Phase 5 — Final Validation**: `bun run test:parallel` all pass; `bun typecheck` zero errors; `bun oxlint --deny-warnings` zero errors; coverage MUST NOT decrease vs baseline (±1% noise).
6. **Phase 6 — Report**: before/after metrics, steps executed, verification results, design deviations (template: `reference/refactor-mode.md` § Refactor Report).
7. **Phase 7 — Approval**: present the report (prompt: `reference/refactor-mode.md` § Phase 7 — Approval). Route to code review if > 50 lines or ≥ 5 files.
---
<!-- section:mode-port -->
### Mode: port
Port a feature from a source project into the current (target) project, preserving the source's behavioral contract while adapting every implementation detail to the target's tech stack and conventions. Orchestration, per-phase processes, and templates live in the existing `reference/*.md` port files (indexed in References); read `reference/port-mode.md` at mode entry.
**Tester focus (port)**: behavioral-fidelity tests — every `FID-*` item in `port-{name}/source-analysis/10-fidelity-baseline` MUST map to ≥ 1 ported test (rewrite the source test in the target framework's style, preserving the contract); port edge/error/boundary tests, not just the happy path (missing FID coverage is the #1 cause of incomplete porting); every `[FIDELITY DEVIATION]` gets a test locking the divergent behavior.
#### Phase 0 — Scope Assessment
Measure the source scope — file count and LOC alone are insufficient: (1) source files; (2) source LOC (exclude tests); (3) source modules / feature areas; (4) source packages touched; (5) implicit dependency artifacts (schema/migrations, config, env vars, CLI flags, theme files, routes, providers, build config — full list: Phase A1.8). Then:
- **Standalone** (Part 1): ≤ 5 files, ≤ 200 LOC, 1 package, 0 implicit deps.
- **Pipeline** (Part 2): > 5 files or > 200 LOC · spans ≥ 3 source modules · spans ≥ 2 packages · or ≥ 3 implicit dependency artifacts (implicit deps require capability boundary analysis).
Report the assessment and do NOT proceed without explicit approval (prompt: `reference/port-mode.md` § Phase 0 — Scope Assessment).
#### Preconditions
- [ ] Source project path/reference and source feature scope specified; target project structure accessible (all monorepo packages).
- [ ] Target is the current working directory, clean workspace, existing test framework (none → warn: behavioral fidelity cannot be guaranteed — `reference/port-mode.md` § Preconditions).
- [ ] Target's existing capabilities documented or discoverable (else run the structural survey, Phase A1.7, first); `core/checklists/port.md` is accessible.
#### Part 1 — Standalone Mode (Small Port)
The Developer executes the full port lifecycle directly; no DAG decomposition; **code review is mandatory regardless of size** (Phase A8). Phases: A1 source analysis → A1.5 review GATE → A1.7 target surface → A1.8 capability boundary GATE (`reference/source-analysis.md`) · A2 concept mapping → A3 gap analysis → A4 adaptation design (`reference/mapping-and-design.md`) · A5 implement + A5.5 self-check GATE (rules below) · A6 port tests & fidelity (`reference/fidelity-verification.md`) · A7 report (`reference/port-report-template.md`) · A8 approval → code review (`reference/port-mode.md` § Phase A8).
**Phase A5 — Implement (rules)**: one file at a time (port completely, verify, then move on); **target conventions are law**; **fidelity over aesthetics** — do not "improve" the source logic (port the exact validation, log `[NOTE: weak validation in source]`; stricter behavior is a separate feature item); port comments from source (translated); `bun typecheck` after each file; no new dependencies (use the adaptation-design alternative or reimplement the subset inline).
**Phase A5.5 — Self-Check GATE (MANDATORY)**: complete `core/checklists/port.md` in full — every item ☑ (pass) or ☐ (fail) with written justification; every ☐ item documents an inline fix plan; known deferrals include a reactivation path (chunk + trigger); publish as wiki page `port-{name}/self-check`. Do NOT proceed to Phase A6 until all items are ☑ OR all ☐ items have documented fix plans AND the Developer has printed `SELF-CHECK COMPLETE — {X} items passing, {Y} items deferred with plan`.
#### Part 2 — Pipeline Mode (Large Port)
The Developer produces a source analysis report as the foundation artifact, then the port flows through the pipeline with peer-review gates: `port (source analysis) → analyze-dag (decompose by source module) → review-dag single gate → per node (dag.task_route): implement → review-code → verify (integration + fidelity)`. Phases: B1 12-document source analysis → B1.5 10-dimensional review GATE → B1.7+B1.8 target surface & capability boundary artifacts → B2 checklist self-check → B3 handoff to analyze-dag (slug `port-{name}`) → B4 per-node DAG task route (`reference/large-port-pipeline.md`) · B5 verify — integration + fidelity, every `FID-*` traced to a passing test (`reference/fidelity-verification.md`) · B6 final approval (rules below).
**Phase B6 — Final Approval**: verify (1) CI is configured (absent → warn `[GAP: no CI — no automated gate before merge]`, flag in report); (2) all review gates passed (review-dag single gate + every node's code review `converged: true`); (3) typecheck + lint + tests pass fresh. Present the consolidated report (prompt: `reference/port-mode.md` § Phase B6). Post-merge cleanup: `reference/port-mode.md` § Phase B7.
---
<!-- section:pipeline-detection -->
## Pipeline Work Item Detection
Not all work items involve writing new code — some are refactoring, bugfix, or frontend work items. They use the workflows above (or the frontend skill) but flow through the same pipeline gates (review-code → verify; DAG-routed work resolves its spec from `{epic-slug}/dag`). When detected, read `reference/work-item-detection.md` BEFORE Phase 1 for the per-phase pipeline adaptations. Detection triggers (full keyword lists in the reference):
| Work item | Detected by (examples) | Route |
|---|---|---|
| **Refactoring** | "Refactor" / "重构" / "Extract" / "Rename" … prefixes; `[REFACTOR]` tag; behavior-preserving structural node spec; REQ-REFACTOR-* | Mode: refactor + adaptations — code review mandatory regardless of size, no "no review needed" bypass |
| **Bugfix** | "Bugfix" / "Fix" / "修复" / "Hotfix" prefixes; `[BUGFIX]` tag; correction-of-behavior node spec; bug report / stack trace / RCA reference | Mode: bugfix + adaptations — code review mandatory regardless of size |
| **Frontend** | "Frontend" / "UI" / "组件" / "页面" prefixes; `[FRONTEND]` tag; UI-layer node spec; components in `components/` `pages/` `views/` `ui/`; exclusively `.tsx`/`.jsx`/`.vue`/`.svelte`/`.astro`/`.css`/`.scss` files | `core/skills/frontend/SKILL.md` + adaptations — code review mandatory regardless of size |
## References
**On-demand mode references** (NOT injected — read at the declared timing):
- `reference/implement-mode.md` — Mode: implement: mode entry (phase detail); Phase 2 / Phase 5 templates; Tester focus; Common Rationalizations; legacy notes.
- `reference/bugfix-mode.md` — Mode: bugfix: mode entry; Phase 13 templates; Phase 5 report + publish; routing escalation; abort procedure; orchestration.
- `reference/refactor-mode.md` — Mode: refactor: mode entry; Phase 1 baseline; Phase 6 report; no-coverage stop prompt.
- `reference/port-mode.md` — Mode: port: mode entry; Phase 0 prompt; preconditions warnings; A8/B6 approval prompts; B7 cleanup.
- `reference/work-item-detection.md` — detected refactoring / bugfix / frontend work item: BEFORE Phase 1 of the matched mode.
- `core/checklists/implementation.md` — Implementation self-check checklist
- `core/checklists/bugfix.md` — Bugfix self-check checklist
- `core/checklists/refactoring.md` — Refactoring self-check checklist
- `core/checklists/port.md` — Porting self-check checklist
- `core/checklists/frontend.md` — Frontend self-check checklist
- `core/checklists/code-review.md` — Code review checklist (self-attestation)
- `core/checklists/pipeline-gate.md` — Cross-stage pipeline gate checklist
- `core/skills/frontend/SKILL.md` — Frontend workflow (for frontend work items)
- `core/skills/browser-debug/SKILL.md` — Interactive browser verification (for frontend work items) and UI bug reproduction (bugfix Phase 1)
- `core/skills/review-code/SKILL.md` — Code review (next step after implementation)
- `core/skills/verify/SKILL.md` — Integration + fidelity verification (pipeline-mode bugfix, large port)
- `<instance-root>/archive/skills/` — Legacy requirements/design/plan/roadmap skills (archived [org-internal #3072] phase 3; their templates moved to `<instance-root>/archive/templates/`)
- `reference/source-analysis.md` — Port Phase A1, A1.5, A1.7, A1.8 detailed processes & templates
- `reference/mapping-and-design.md` — Port Phase A2, A3, A4 detailed processes & templates
- `reference/large-port-pipeline.md` — Port Phase B1B4 detailed processes
- `reference/fidelity-verification.md` — Port Phase A6, B5 detailed processes & templates
- `reference/source-analysis-templates.md` — Port B1 document format templates
- `reference/capability-boundary-template.md` — Port A1.8/B1.8 13-dimension table
- `reference/target-surface-template.md` — Port A1.7/B1.7 output format
- `reference/port-report-template.md` — Port A7/B6 report format
- Martin Fowler, _Refactoring: Improving the Design of Existing Code_ (2nd ed.)
- `core/rules/` — Project engineering conventions (test commands, typecheck)
- L2 wiki pages for style-guide, effect-rules, and database conventions (see the "L2 on-demand reference" section of AGENTS.md)
@@ -0,0 +1,494 @@
> Extracted from implement/SKILL.md (Mode: bugfix) — moved verbatim 2026-08-25, ticket [org-internal #3381].
### Mode: bugfix
Reproduce, isolate, and fix a bug with a regression test to prevent
recurrence. For small, localized bugs, use standalone mode — the existing
system behavior is the specification. For large, complex bugs, route through
the full quality pipeline.
#### Process Overview
Every diamond below is a gate Developers rationalize skipping — especially
under "the bug is obvious" pressure.
```dot
digraph bugfix {
rankdir=TB;
node [shape=box, fontname="Helvetica"];
repro [shape=diamond, label="Bug reproduces?"];
norepro [label="STOP: report cannot-reproduce\n(do not guess-patch)"];
rc [shape=diamond, label="Root cause found\n(not just symptom)?"];
symptom [label="Go deeper — do NOT\npatch the symptom"];
route [shape=diamond, label="Routing:\nstandalone vs pipeline?"];
escalate [label="Uncertain → escalate\nto pipeline mode"];
rtest [shape=diamond, label="Regression test\nFAILS before fix?"];
notest [label="Test does not cover\nthe bug — rewrite it"];
fix [label="Phase 4: Fix\n(one change, root cause only)"];
green [shape=diamond, label="Regression test PASS\n+ full suite green?"];
done [shape=doublecircle, label="Phase 5/6:\nSelf-Check + Report"];
repro -> norepro [label="no"];
repro -> rc [label="yes"];
rc -> symptom [label="no"];
rc -> route [label="yes"];
route -> escalate [label="uncertain"];
route -> rtest [label="standalone"];
rtest -> notest [label="passes already"];
rtest -> fix [label="fails (confirmed)"];
fix -> green;
green -> fix [label="no: fix + re-run"];
green -> done [label="yes"];
}
```
#### Execution Modes
| Mode | Entry Point | Scope Source | Review Gate | Verify Gate |
| ---------- | -------------------------------------- | ------------------ | ------------ | ----------- |
| Standalone | User says "fix this bug" | Bug report + code | Optional (>20 lines or ≥3 files) | None |
| Pipeline | User requests full-process bugfix, or auto-escalation | Bug report → requirements → design → plan → implement | Mandatory | Mandatory |
> **Routing override (ticket-seeded)**: the Optional/None gate declarations
> above apply to *user-initiated* standalone mode. When the ticket carries a
> `Kind/*` route whose `keep_gates` includes `review-code` / `verify` (e.g.
> `Kind/Bug`, `Kind/Testing` — see `<instance-root>/workflow-routing.yaml`), those
> gates are MANDATORY regardless of size. Per `core/rules/workflow-routing.md`,
> a gate is mandatory if EITHER the route OR the skill requires it; skipping is
> valid only when BOTH agree it is skippable.
In pipeline mode, the bug report becomes a bugfix work item that flows through
the full requirements → design → plan → implement → review-code → verify
pipeline.
#### Role & Responsibilities
The bugfix is owned and executed by the **Developer** (Worker). The
Developer owns both implementation and bugfix — same role, same skill set.
The Developer is responsible for:
- Reproducing the bug from the description.
- Identifying the root cause (not just patching the symptom).
- Writing a regression test that fails before the fix and passes after.
- Applying the minimal surgical fix — one change, one purpose.
- Running the full test suite to confirm no regressions.
The Builder's role is to present the bugfix report and route it to code
review if the change is non-trivial (> 20 lines or touches ≥ 3 files).
#### Tester focus for bugfix
The Tester role in bugfix writes **regression tests** and, uniquely,
intervenes BEFORE the fix (a regression test must fail before the fix to
prove the bug exists):
- **Failing regression test** — read `repro-notes.md` (the Developer's
reproduction + root-cause analysis from Phases 12), write a test that
exercises the exact bug path and FAILS with the bug's symptom. This MUST
happen before Phase 4 (Fix), not after — it is Phase 3.
- **Passing confirmation** — after the Developer's fix, the same test MUST
pass (the Developer's green run in Phase 4 verifies this).
- **Boundary regression tests** — inputs adjacent to the bug trigger,
similar conditions that must NOT trigger the fix (guards against
over-fixing), and error paths near the root cause.
**Bugfix-specific orchestration** (overrides the standard role-split flow):
Because a regression test must fail BEFORE the fix, the bugfix role split
inverts the standard orchestration — the Tester dispatches between
Phase 2 and Phase 4, not after the fix:
```
[Worker: developer] Phase 1 reproduce + Phase 2 root cause
→ write repro-notes (reproduction steps, root cause, bug path,
expected behavior)
↓ persist: wiki page `{slug}/repro-notes` (gitea_wiki__create_page)
[Worker: tester] read repro-notes → Phase 3 write failing regression test
(confirms FAIL before fix)
↓ persist: wiki page `{slug}/test-report` (gitea_wiki__create_page, failing test confirmed)
[Worker: developer] Phase 4 fix → run test:changed to green
→ write impl-notes (post-fix behavior contract)
↓ persist: wiki page `{slug}/impl-notes` (gitea_wiki__create_page, post-fix behavior contract)
[Worker: tester] supplement boundary regression tests → run test:changed
↓ persist: update wiki page `{slug}/test-report` (gitea_wiki__update_page, final)
— consumed by the human stakeholder / next iteration planning for DoD regression-test evidence
```
For small, single-file bugfixes with an obvious fix, a single Developer
Worker may write the failing test, fix, and confirm green in one invocation
— the split is optional for trivial fixes (Phase 3 + Phase 4 in one
session). Force the split when the fix touches ≥ 2 files or the root cause
spans ≥ 2 levels of indirection.
Pipeline-mode bugfixes route through the implement pipeline (see ### Mode:
implement (default)), with the bugfix-specific Tester focus above layered
on top of the standard role-split orchestration.
---
#### Preconditions (standalone)
Before starting the bugfix, confirm:
- [ ] Bug description exists (user's message, issue tracker link, or error log).
- [ ] Existing codebase is accessible.
- [ ] `core/checklists/bugfix.md` is accessible.
If the user describes a symptom without specifics, ask for:
```
To fix this bug, I need:
1. What is the expected behavior? (what should happen)
2. What is the actual behavior? (what happens instead)
3. Steps to reproduce.
4. Any error messages, logs, or stack traces.
```
---
#### Routing Decision
After Phase 1 (reproduce) and Phase 2 (root cause), the Developer evaluates
whether the fix qualifies for standalone or pipeline mode:
**Escalate to pipeline mode when ANY of:**
| Condition | Reason |
| --------------------------------------------------- | ------------------------------------------------------------ |
| Fix touches ≥ 5 files | Cross-file changes need design review and integration tests |
| Fix spans ≥ 2 modules / components | Multi-module fixes need architectural validation |
| Root cause is in a design-level decision (protocol, schema, architecture) | Design change needs requirements + design review |
| Fix requires data migration or schema change | Schema changes need data design review and migration plan |
| Fix changes a public API or interface contract | API changes need interface design review and compatibility check |
| Fix introduces a new dependency or changes an existing one | Dependency changes need review (DGN dimension, code review) |
| Estimated lines changed > 50 | Large change carries high regression risk |
| User explicitly requests full-process bugfix | User wants quality gates |
**Stay in standalone mode when ALL of:**
| Condition |
| ---------------------------------------------- |
| Fix is ≤ 4 files |
| Fix is ≤ 1 module / component |
| Fix is a logic error, not a design error |
| No data migration or schema change |
| No API or interface contract change |
| No dependency change |
| Estimated lines changed ≤ 50 |
If the Developer is uncertain, escalate. A false pipeline escalation costs a few
extra review rounds. A false standalone decision risks missing a quality gate on
a complex change.
**Big-bug relabel rule ([org-internal #3061])** — before the generic escalation below, split
the triggers by kind:
- **Design-level triggers** (root cause is a design decision — protocol /
schema / architecture; shared-contract or public-API change; data
migration): do NOT push through bugfix and do NOT run the legacy pipeline
escalation — **relabel the ticket `Kind/Feature`** and reroute via Step 0
(DAG route; a 13 node small DAG is the expected shape for a single
design-level fix). The fix work already done (repro notes, root cause)
becomes node input, not wasted work.
- **Mechanical size triggers only** (many files / many lines, same design):
stay in bugfix — batch the change into iterations and keep the
review-code + verify gates. Scale alone never justifies a relabel.
When escalating, the Developer pauses after Phase 2, reports the routing
decision, and asks the user to confirm pipeline escalation:
```
This bugfix qualifies for pipeline mode:
- {N} files across {M} modules
- Root cause: {design-level issue}
- Estimated lines: {N}
→ Route through requirements → design → plan → implement → review → verify?
(yes / no — proceed with standalone)
```
---
#### Phase 1 — Understand & Reproduce
1. **Read relevant code** — find the module/component likely responsible for
the bug. Use `grep` for error messages, `glob` for related files.
2. **Check existing tests** — do existing tests cover this code path? If a
test exists but passes, the bug is in the test or in an uncovered branch.
3. **Reproduce** — run the relevant test(s) or manually trigger the bug.
Confirm the actual behavior matches the bug report. Document the
reproduction:
```markdown
## Reproduction
**Steps**:
1. {step}
2. {step}
**Expected**: {what should happen}
**Actual**: {what happens}
```
4. If the bug CANNOT be reproduced, stop and report:
```
Cannot reproduce the bug. Here's what I tried:
- {step 1}
- {step 2}
→ Is the environment different? Are there missing steps? Does a specific
data state trigger it?
```
---
#### Phase 2 — Isolate Root Cause
Trace from the symptom to the root cause:
1. **Symptom**: surface-level error (e.g. "500 on login").
2. **Proximate cause**: the code that throws or returns wrong (e.g. "password
hash comparison returns false for valid password").
3. **Root cause**: the underlying defect (e.g. "password hashing config changed
in commit abc123 but the stored hashes were not re-hashed").
```markdown
## Root Cause Analysis
**Symptom**: {error message or wrong behavior}
**Proximate cause**: {file}:{line} — {what the code does wrong}
**Root cause**: {underlying defect — config, data, logic, or assumption}
**Introduced in**: {commit hash or version if known}
```
**Rules**:
- If you're fixing a symptom (e.g. adding a null check where the real bug is
that null should never reach that line), stop and go deeper.
- If you can't find the root cause after examining 3 levels of indirection,
pause and report findings. Do NOT apply a surface-level patch.
- **After Phase 2, evaluate the routing decision** (see Routing Decision table
above). If the fix qualifies for pipeline mode, pause and present the
escalation prompt before proceeding to Phase 3.
---
#### Phase 3 — Write a Regression Test
Before fixing, write a test that proves the bug exists:
1. Write a test that exercises the bug path with the failing inputs.
2. Run the test — it MUST fail with the bug's symptom.
3. The test must be specific: test the exact condition that was broken, not
a general "endpoint returns 200" test.
```markdown
## Regression Test
- **File**: {path to test file}
- **Test name**: {test function name}
- **What it verifies**: {the expected behavior that was broken}
- **Fails before fix**: ✅ (confirmed)
```
**Rules**:
- If you cannot write a test that fails (bug is non-deterministic, environment-
specific), write the most targeted test you can and mark it `[flaky]`.
- The test must fail NOW, before you apply the fix. If it passes already, the
test does not cover the bug.
---
#### Phase 4 — Fix
Apply the minimum change that resolves the root cause:
1. **One conceptual change per fix** — do not bundle a bugfix with refactoring,
style changes, or "while I'm here" improvements.
2. **Fix the root cause**, not the symptom. If the root cause is in a different
file than the symptom, fix it there.
3. **Update only what's necessary** — if fixing a null-safety bug requires
adding a null check in one place, add one null check, not a comprehensive
null-safety overhaul of the entire module.
4. Run the regression test — it MUST pass.
5. Run the relevant unit tests — all existing tests must still pass.
---
#### Phase 5 — Self-Check & Report
##### Self-Check
1. **Typecheck**: `bun typecheck` — zero errors.
2. **Lint**: `bun oxlint --deny-warnings` — zero errors.
3. **Full test suite**: `bun run test:parallel` — all tests pass (new + existing).
4. **Checklist**: verify every item in `core/checklists/bugfix.md`.
5. **Publish bugfix report**: write the bugfix report as a wiki page via `wiki 读写 API(见 TERMINOLOGY` with page_name `{slug}/bugfix-report` (`_shared/gitea-write-patterns.md` Pattern 1).
##### Report
```markdown
## Bugfix Report
**Bug**: {one-line description}
**Root cause**: {file}:{line} — {explanation}
**Fix**: {file} — {single-sentence description of change}
**Lines changed**: {N}
**Regression test**: {test file}:{test name}
### Verification
- Regression test: {PASS | FAIL}
- Full test suite: {N} passed, 0 failed
- Typecheck: ✅
- Lint: ✅
### Files Changed
| File | Lines | Purpose |
| ------------------- | ------ | ------------------------------------------------------ |
| `src/auth/login.ts` | +3, -1 | Fix password hash comparison when salt version changes |
---
**Handoff**: {if changes > 20 lines or ≥ 3 files → run `core/skills/review-code/SKILL.md`
| otherwise → fix complete, no review needed}
> **Routing override**: when the ticket carries a `Kind/*` route whose
> `keep_gates` includes `review-code` / `verify` (e.g. `Kind/Bug`,
> `Kind/Testing`), those gates are MANDATORY even for small fixes — the
> "no review needed" branch above does not apply (see the Execution Modes
> routing-override note above).
```
---
#### Phase 5.5 — Issue Checklist Sync (standalone bugfix)
In standalone-bugfix mode there are no skill-exit boundaries between commit,
PR, review, and CI — without explicit sync points the issue goes stale. Per
the `issue-checklist-sync` L1 rule, sync at each externally visible
transition (skip any step if no source issue exists):
| When | Sync action |
|------|-------------|
| After the fix commit | Check off fix-delivered ACs with `_(commit {sha}: file)_` |
| After PR creation | Ensure the `## 当前状态` section exists (the PR row is auto-written by the status-sync poller — see `issue-checklist-sync.md` § Automated sync) |
| After review convergence | Review-related ACs get `_(reviewed: round N PASS)_` (done by review-code Phase E 2.7) |
| On CI state transitions | Update process-AC progress (e.g. "N consecutive green") with run number |
| At verify PASS / close | Final sweep per `verify` Phase 5.6 |
Bugfix mode delegates the "after commit" step to the same mechanics as Phase
4.6 above (fetch issue body → map `- [ ]` items → `工单 API(见 TERMINOLOGYupdate`), and
the PR-creation step to Phase 4.7.
---
#### Phase 6 — Approval
Present the report:
```
Bug fixed: {one-line description}
- Root cause: {file}:{line}
- {N} lines changed in {M} files
- Regression test added: {test name}
- Full test suite: ✅
→ {if review needed: "Run code review?" | else: "Fix complete. Approve?"}
```
---
#### Common Rationalizations (bugfix)
Bugfixes fail from **pressure and false confidence** far more than from
ignorance — "the bug is obvious" is the thought that precedes a symptom patch,
a bundled diff, or a regression that surfaces weeks later. These are the
excuses that precede every reopened bug. If you catch yourself thinking any
row's "Excuse", stop: the "Reality" column is the exact rule you are about to
break.
| Excuse | Reality (the rule being broken) |
|--------|---------------------------------|
| "Just add a null check where it crashes" | Symptom-patching. Phase 2: if you are fixing a symptom, stop and go deeper — the real defect is whatever let null reach that line. |
| "Can't reproduce, but I'm sure it's X" | Phase 1: if the bug cannot be reproduced, stop and report. Guess-patching a non-reproduced bug fixes nothing verifiable. |
| "3 levels deep, can't find it, patch the symptom" | Phase 2: after 3 levels of indirection with no root cause, pause and report — do NOT apply a surface patch. |
| "Bug's obvious, I'll fix then add the test" | Phase 3: the regression test MUST fail before the fix. Fix-first means you test your fix, not the bug. |
| "Test passed immediately, ship it" | Phase 3 Rules: a test that passes before the fix does not cover the bug — rewrite it until it fails. |
| "While I'm in this file, also clean up…" | Phase 4 rule 1: one conceptual change per fix. Bundling refactors/style/other-fixes pollutes the regression signal. |
| "Make the whole module null-safe while I'm here" | Phase 4 rule 3: update only what is necessary. Over-fixing turns a 3-line surgical fix into a high-risk diff. |
| "Fix is isolated, skip the full suite" | Phase 4 rule 5 + Phase 5: the full suite catches regressions your isolated view cannot. |
| "4 files but one module, standalone's fine" | Routing Decision: escalate when uncertain. A false-standalone call skips quality gates on a complex change. |
##### Incident Triage Carve-Out
When the bugfix occurs under **active production incident** pressure
(user-facing outage, on-call escalation), the Phase 2→3 ordering can be
**temporarily relaxed** — but never skipped:
1. A stop-gap (symptom patch) MAY ship first to restore service.
2. BUT the full root-cause trace + failing regression test + proper
root-cause fix MUST land in the **same incident window** — never deferred
to "tomorrow" or "a follow-up ticket".
3. If you defer, you have not fixed the bug — you have shipped a symptom patch
with a promise. Promises are not regression tests.
This carve-out exists because the rationalization table above cannot resolve a
*legitimate* priority conflict (service down vs process discipline). It
resolves it by permitting triage but forbidding deferral.
---
#### Pipeline Mode (bugfix)
> **Legacy path retired ([org-internal #3072] phase 3, 2026-08-21)**: the full
> requirements-elicitation → design → review-artifact(design-space) →
> plan-iterations → review-artifact(plan) front-end was archived
> (`<instance-root>/archive/skills/`). A big bug that needs a design-level decision
> now relabels `Kind/Feature` and enters the DAG route (see the big-bug
> relabel rule above) — repro + root-cause notes carry over as node input.
> The abort criteria below still apply to any multi-stage bug run before
> code is written.
When a bugfix escalates beyond standalone scope, the bug report becomes a
pipeline input; the original bugfix phases (reproduce, root cause, regression
test, fix) are embedded within the implement stage, and review-code + verify
remain mandatory gates.
##### Pipeline Abort Criteria
Before any code is written in pipeline mode, abort the pipeline if ANY of:
| # | Condition | Action |
|---|-----------|--------|
| 1 | Bug no longer reproduces after environment change (strace re-isolation returns 0 reproductions, user confirms symptom resolved) | Write ABORT to wiki page `{slug}/ABORT` (wiki 读写 API(见 TERMINOLOGY), preserve all completed artifacts, run retrospective |
| 2 | Root cause hypothesis is falsified during re-isolation (e.g., strace shows suspected git spawn is NOT hanging) | Write ABORT.md, escalate to Architect for design revision OR abort pipeline |
| 3 | Bug is resolved by external change (new binary build, dependency update, OS/kernel patch) | Write ABORT.md with resolution evidence, close without code changes |
| 4 | Reproduction confidence < 3/5 after re-isolation attempt | Write ABORT.md if confidence cannot be improved within 1 re-isolation iteration |
**Abort procedure**:
1. Write ABORT to wiki page `{slug}/ABORT` (wiki 读写 API(见 TERMINOLOGY) documenting the reason, evidence, and which artifacts are preserved.
2. Do NOT commit or merge the bugfix branch (no code was written).
3. Run retrospective to extract process improvements.
4. Archive artifacts to wiki page `_archive/{slug}/` (wiki 读写 API(见 TERMINOLOGY) after retrospective.
**Scope**: these criteria apply before the implement stage. Once code is written, the pipeline proceeds through review-code → verify — abort is no longer valid.
##### Stage: Implement → Code Review → Verify
On the DAG route a bug-fix node's spec (ACs tracing to the repro + root cause)
lives in `{epic-slug}/dag`; the Developer follows the bugfix Phases 16 (from
standalone mode above) as the implementation method, then produces the
standard implementation report (see ### Mode: implement (default), Phase 5).
Code review runs all 10 dimensions against the bugfix changes. Verify runs
the full DoD matrix including regression tests, integration tests, and NFR
validation. Output pages: code review → `{slug}/reviews/code/final/report`;
verification → `{slug}/05-verify-iteration-1`.
@@ -0,0 +1,76 @@
# Capability Boundary — Dimension Table & Output Template
> Used by Phase A1.8 (standalone) and Phase B1.8 (pipeline).
> Read this file when executing the Capability Boundary Definition phase.
> The 13-dimension table defines WHAT to analyze; the output template defines
> HOW to record it.
## Artifact Dimensions
Every capability MUST be analyzed across ALL of these dimensions. A dimension
with no artifacts is explicitly marked "N/A — none required" (not silently
skipped):
| # | Dimension | What to list | Why it matters |
|---|-----------|-------------|----------------|
| 1 | **Source code files** | Every .ts/.tsx/.js file in the feature scope | The obvious one — but not the only one |
| 2 | **Type definitions / interfaces** | Shared types, branded types, schemas (Zod/Schema.Class) | Types are consumed across files; missing types break compilation silently |
| 3 | **Database schema / migrations** | Table definitions, column additions, migration SQL | Data layer changes are invisible in code diffs but block runtime |
| 4 | **Configuration entries** | Config keys, settings entries, default values | Missing config = silent runtime failures |
| 5 | **Environment variables** | Env vars read by the feature, VITE_* vars | Missing env vars = undefined behavior at runtime |
| 6 | **CLI flags / commands** | CLI commands, flags, option definitions | CLI surface changes are easily forgotten |
| 7 | **Theme / style files** | CSS files, theme JSON, tailwind config, token files | Styling is per-component and easily orphaned |
| 8 | **Route definitions** | New routes, modified redirects, route guards | Routes are defined in a central file far from the feature code |
| 9 | **Provider / context hierarchy** | New providers, insertion points in provider tree, context keys | Provider ordering bugs are silent and hard to debug |
| 10 | **Build config changes** | vite.config, tsconfig, webpack, tailwind.config | Build config gates whether the feature compiles/bundles |
| 11 | **Package.json dependencies** | New npm deps, version changes, workspace dep additions | Missing deps = import errors at runtime |
| 12 | **Test files** | Unit tests, integration tests, test fixtures, test helpers | Tests are the fidelity contract — missing tests = unverified behavior |
| 13 | **Shared package changes** | Changes to SDK, UI, core packages that the feature depends on | Cross-package deps are the #1 source of incomplete ports |
## Output Template
Publish to wiki page `port-{name}/source-analysis/capability-boundary` (standalone) or
`port-{name}/source-analysis/12-capability-boundary` (pipeline) via `wiki 读写 API(见 TERMINOLOGY`.
```markdown
## Capability Boundary: {feature name}
### Dimension 1 — Source Code Files
| Source File | Target Location | Status | Notes |
| ----------- | --------------- | ------ | ----- |
| src/context/tabs.tsx | src/context/tabs.tsx | ☐ | New file |
| ... | ... | ... | ... |
### Dimension 2 — Type Definitions / Interfaces
| Source Type | Target Location | Status | Notes |
| ----------- | --------------- | ------ | ----- |
| Tab interface | src/context/tabs.tsx | ☐ | Co-located |
| ... | ... | ... | ... |
### Dimension 3 — Database Schema / Migrations
| Source Schema | Target Migration | Status | Notes |
| ------------- | ---------------- | ------ | ----- |
| N/A — none required | — | ⏭ | Feature uses in-memory state only |
### Dimension 4 — Configuration Entries
| Source Config Key | Target Config Key | Status | Notes |
| ----------------- | ----------------- | ------ | ----- |
| tabs.enabled | tabs.enabled | ☐ | New setting |
| ... | ... | ... | ... |
### Dimension 5 — Environment Variables
| Source Env Var | Target Env Var | Status | Notes |
| -------------- | -------------- | ------ | ----- |
| VITE_TABS_LIMIT | VITE_TABS_LIMIT | ☐ | New |
| N/A | — | ⏭ | No env vars required |
(... repeat for all 13 dimensions ...)
### Completeness Cross-Check
| Cross-Check | Result |
| ----------- | ------ |
| Every A1 Source Function Inventory item appears in D1 or D2? | ✅ / ❌ |
| Every A1.7 Structural Diff gap has a capability boundary entry? | ✅ / ❌ |
| All 13 dimensions filled in? | ✅ / ❌ |
| All ☐ items have deferral + reactivation path or are pre-implementation? | ✅ / ❌ |
```
@@ -0,0 +1,77 @@
# Fidelity Verification — Detailed Processes
> Extracted from `implement/SKILL.md` (Mode: port) Phase A6 and B5.
> Read this file when executing the test porting and fidelity verification phases.
---
## Phase A6 — Port Tests & Verify Fidelity
1. **Port every test** from the source — not just the happy path. Edge cases,
error paths, and boundary tests must all be ported.
2. **Rewrite assertions** to match the target test framework's assertion style.
3. **Run ported tests** — they must pass. If a test fails:
- Behavior mismatch: fix the implementation to match source behavior.
- Test logic error (e.g. wrong assertion library syntax): fix the test.
- Infrastructure gap (e.g. test tried to connect to Redis): adapt the test
to the alternative from Phase A4.
4. **Full test suite** — run the target project's existing tests + ported
tests. No regression in existing tests.
5. **Fidelity checklist** — for each source behavior, verify manually or
automatically:
### Fidelity Verification Template
```markdown
## Fidelity Verification
| Source Behavior | Tested? | Result | Notes |
| --------------------------------- | ------------- | ------ | -------------------------------------------- |
| User login with valid credentials | ✅ ported | PASS | |
| User login with invalid password | ✅ ported | PASS | |
| Session expiry at 1h | ✅ ported | PASS | Adapted to DB session store |
| Rate limiting: 5 attempts / min | ⚠️ not ported | — | Target has no rate limiting infra → deferred |
```
---
## Phase B5 — Verify (Integration + Fidelity)
After all chunks are implemented and reviewed, run
`core/skills/verify/SKILL.md` with the fidelity baseline
(wiki page `port-{name}/source-analysis/10-fidelity-baseline`, read via `wiki 读写 API(见 TERMINOLOGY`) as the
acceptance criteria:
- Every `FID-*` item must be traced to a passing test in the target project.
- Full test suite (existing + ported) must pass with no regressions.
- Typecheck and lint must be clean.
- Fidelity deviations from any chunk are consolidated into a final
fidelity report.
- **Reverse coverage (Ported? audit)**: The Source Function Inventory
(produced in Phase A1 / B1) "Ported?" column must have **zero** unexplained
☐ entries. Any residual ☐ MUST carry a matching `[DEFER]` row — with a
reactivation trigger — in the Port Fidelity Report. A bare ☐ is a FAIL,
not a deferral. This closes the #1 port-completeness gap: functions that
were never ported and never consciously deferred.
- **Symbol-level completeness (SRC-CMP)**: Run an automated export-symbol
diff between source and target packages. Every source symbol absent from
the target MUST appear as `[DEFER]` in the report; a silent gap is a FAIL.
```bash
diff <(codegraph exports <source-pkg>) <(codegraph exports <target-pkg>)
# Each left-only symbol must be DEFER'd or ported — silent gaps fail B5.
```
### Port Fidelity Report Template
```markdown
## Port Fidelity Report
| FID-* | Behavior | Chunk | Target Test | Status |
| ------- | ------------------ | ----------- | -------------------- | ------ |
| FID-001 | Login valid creds | chunk-auth | auth/login.test.ts | PASS |
| FID-002 | Login invalid pw | chunk-auth | auth/login.test.ts | PASS |
| FID-004 | Rate limiting | chunk-auth | — | DEFER |
**Summary**: {X}/{Y} behaviors verified, {Z} deferred
```
@@ -0,0 +1,592 @@
> Extracted from implement/SKILL.md (Mode: implement) — moved verbatim 2026-08-25, ticket [org-internal #3381].
### Mode: implement (default)
The standard implementation workflow for work items from an approved
iteration plan. Implement a single work item, guided by the approved design,
and self-verify before passing to code review.
#### Pre-flight
The pre-flight self-check prompt format ([org-internal #2599]), prepended to the Developer
sub-agent's task prompt when `routes.{Kind}.preflight` is non-empty:
```
Pre-flight self-check (evidence-based, from retrospective — verify each
BEFORE writing code; if one is already satisfied, note why in impl-notes):
1. {item} (evidence: {evidence})
2. ...
```
#### Process Overview
Every diamond below is a gate agents rationalize skipping. None are optional.
```dot
digraph implement {
rankdir=TB;
node [shape=box, fontname="Helvetica"];
pre [shape=diamond, label="Preconditions\n(artifacts + reviews\nconverged)?"];
abort [label="ABORT: list every\nmissing item"];
p1 [label="Phase 1: Parse Context"];
p2 [label="Phase 2: Plan\n(≤3 files per WI)"];
scope [shape=diamond, label="Scope ≤3 files\nAND maps to a\ndesign component?"];
gap [label="Flag design gap,\nDO NOT invent decisions"];
p3 [label="Phase 3: Implement\n(design-exact, tests cover AC)"];
p4 [label="Phase 4: Self-Check\n(typecheck + lint +\ntest:changed + review checklist)"];
clean [shape=diamond, label="0 BLOCKERs\nand 0 MAJORs?"];
p5 [label="Phase 5: Report\n(AC → test traceability)"];
p6 [shape=doublecircle, label="Phase 6: Handoff\nto review-code"];
pre -> abort [label="no"];
pre -> p1 [label="yes"];
p1 -> p2;
p2 -> scope;
scope -> gap [label="no"];
scope -> p3 [label="yes"];
p3 -> p4;
p4 -> clean;
clean -> p4 [label="no: fix + re-run"];
clean -> p5 [label="yes"];
p5 -> p6;
}
```
#### Tester focus for implement
The Tester role in implement writes **boundary + contract tests**:
- **Contract tests** — for every public API signature in `impl-notes.md`,
verify the documented inputs/outputs, error paths, and side effects.
Each acceptance criterion (node `acceptance_criteria` in `{epic-slug}/dag`;
historically `04-plan-05-acceptance-criteria`) MUST map to at least one
test.
- **Boundary tests** — empty values, malformed input, permission
boundaries, concurrency edges, and the edge cases the node spec's decision
tables / state machines imply.
- **Failure-path tests** — every error scenario the node's cross-session
edge contracts (historically the interface design,
`03-design-04-interface-design`) specify.
The Developer's Phase 4 self-check (`bun run test:changed` to green) covers
the happy path and existing tests; the Tester's job is the cases the
Developer is structurally biased to miss.
#### Preconditions
> **Publish target (tiered targeting retired, [org-internal #3072] phase 3)**: the
> `Size/*`-tiered publish rule (`rules/workflow-routing.md` §"Publish target
> by Size/* tier — RETIRED") was retired with the legacy producer skills.
> Artifacts publish where the live mode puts them: DAG task mode → node spec
> in the frozen `{epic-slug}/dag` copy (see the DAG-mode input path below);
> standalone bugfix → `{slug}/bugfix-report` + issue body per bugfix Phase 5.
> Legacy tiered locations (`{slug}/02-03-req-design`, `{slug}/04-plan-*`, …)
> stay readable for historical runs via `_shared/gitea-read-patterns.md`.
> **DAG-mode input path** (DAG ticket pipeline — `Kind/Epic` / `Kind/Feature`
> DAG parent, routes-table direct):
> DAG-routed tickets **ignore `Size/*`** (`core/skills/analyze-dag/SKILL.md`).
> When the ticket routes through the DAG pipeline, the tiered Preconditions
> below are replaced by the node spec: the work item and its acceptance
> criteria resolve from the **frozen DAG copy** wiki page `{epic-slug}/dag`
> (and the `{epic-slug}/dag-nodes/{node-id}` subpages when AC detail is sunk)
> plus the node ticket's issue body — there is no `{slug}/04-plan-*` page and
> no `Size/*`-tiered req/design page. The design-space + iteration-plan review
> convergence preconditions are replaced by the **review-dag single-gate
> convergence**: `octopus review status --stage review-dag` must show state
> `success` before the node is implemented.
> **DAG-route read map** (applies to Phase 1 read inputs and the Phase 3/4
> artifact references below — mirror `verify/SKILL.md`'s DAG branch): when
> DAG-routed, resolve each legacy tiered artifact reference (any mention below
> of `{slug}/04-plan-*` / `{slug}/03-design-*` pages) from the frozen
> DAG copy instead:
>
> - Work item — `{slug}/04-plan-04-iteration-assignment` / issue body → the
> node spec in `{epic-slug}/dag` + the node ticket's issue body.
> - Acceptance criteria — `{slug}/04-plan-05-acceptance-criteria` / issue body
> → the node `acceptance_criteria` in `{epic-slug}/dag` (+
> `{epic-slug}/dag-nodes/{node-id}` subpages when AC detail is sunk) + the
> node ticket's issue body.
> - `test_id` (測試用例 ID) declared in `04-plan-05-acceptance-criteria` → the
> `test_id` declared on the node AC in `{epic-slug}/dag`.
> - Design sections — `{slug}/03-design-**` / `{slug}/02-03-req-design` → the
> node spec + cross-session edge contracts in the frozen DAG copy (design
> detail is folded into node AC + contracts; there is no `{slug}/03-design-*`
> page).
> - Interface design — `03-design-04-interface-design` → the node's
> cross-session edge contracts in `{epic-slug}/dag`.
> - Component mapping — `{slug}/03-design-08-traceability` → the node
> `req_refs` + component field in `{epic-slug}/dag`.
>
> At Phase 6 handoff, pass `mode: "dag-task"` to review-code (its DAG Task
> Mode keys off the same frozen-DAG-copy detection).
Before starting implementation, confirm:
> **Legacy pipeline preconditions retired ([org-internal #3072] phase 3, 2026-08-21)**: the
> tier-dependent requirements/design/plan artifact-existence checks and the
> design-space / iteration-plan review-convergence checks that used to head
> this list belonged to the archived legacy pipeline (`<instance-root>/archive/`).
> Live input modes: DAG task mode (node spec from the frozen
> `{epic-slug}/dag` copy — see the DAG-mode input path above; convergence
> precondition = `octopus review status --stage review-dag` shows `success`)
> and standalone modes (bugfix / refactor / port — the request itself is the
> spec). Historical req/design/plan pages stay readable via
> `_shared/gitea-read-patterns.md`.
- [ ] Work item is specified (DAG node ticket `{node-id}`, or a clear task
description in standalone modes).
- [ ] `core/checklists/implementation.md` is accessible.
- [ ] `slug` matches the run's slug (DAG: `{epic-slug}`).
- [ ] 跨阶段门控清单: `core/checklists/pipeline-gate.md` is accessible and
its DAG 路由变体 section has been confirmed item by item. Specifically:
the frozen DAG copy exists and the single gate has converged; the
node's cross-session upstream dependencies are at terminal state
(`ready`). If any dependency is not complete → abort, listing the
blocked nodes.
**If any precondition is unmet, abort and inform the user.** Refer to
`core/checklists/pipeline-gate.md` for the complete gate checklist. List
every missing artifact, every un-converged review, and every blocked dependency
explicitly so the user knows exactly what upstream work remains before
implementation can begin. Refer to the Recovery Protocol in
`core/checklists/pipeline-gate.md` to determine the recovery action for
each missing item.
#### Work-item selection
When the user requests implementation without specifying a work item, resolve
the work-item list from the frozen DAG copy: the ready/pending task nodes in
`{epic-slug}/dag` (via `wiki 读写 API(见 TERMINOLOGY`), cross-checked against the
node tickets on the Epic's `## DAG 状态` table. (Legacy tier-based resolution
via `{slug}/04-plan-04-iteration-assignment` was archived 2026-08-21,
[org-internal #3072] phase 3.) Present the current ready nodes for selection:
```
Current iteration: Iteration {N}: {Goal}
Available work items:
| Work Item | Description | Complexity | Status |
|-----------|-------------|------------|--------|
| WI-001 | ... | 3 | PENDING |
| WI-002 | ... | 2 | PENDING |
→ Which work item should be implemented?
```
---
#### Phase 1 — Parse Context
> **Pipeline stage**: if the source issue exists, move it to the `implement`
> column on the Pipeline Stages board per `_shared/gitea-write-patterns.md`
> Pattern 7.5. Skip if no source issue exists.
Read the upstream artifacts to build a complete implementation context.
Resolve inputs per the DAG-route read map (Preconditions above); standalone
modes read the request/bug report instead:
1. **Work item** — the node spec in `{epic-slug}/dag` (+ the
`{epic-slug}/dag-nodes/{node-id}` subpage when detail is sunk) and the
node ticket's issue body:
- Node id, title, complexity (`size_attrs`).
- Requirements covered (`req_refs`).
- Component(s) involved (node component field).
2. **Acceptance criteria** — the node `acceptance_criteria` in
`{epic-slug}/dag` (+ sunk subpages) and the node ticket's issue body:
- Every falsifiable AC (`AC-{n}`) and `NFR:` entry.
- The declared 测试用例 ID (`test_id`) for each criterion — these drive the
Red → Green test-first order in Phase 3 and are the handshake with `verify`
(DOD-1.6).
3. **Design context** — the node spec + the node's cross-session edge
contracts in the frozen DAG copy (design detail is folded into node AC +
contracts; there is no separate design page). Historical
`{slug}/03-design-*` pages from legacy runs stay readable.
4. **Existing codebase** — use `glob` and `grep` to locate:
- Existing files in the component's directory.
- Existing tests.
- Existing type definitions, schemas, configuration files the work item
touches.
**Output**: internal only. The Developer MUST have read every referenced
design file before writing a single line of code.
---
#### Phase 2 — Plan Implementation
Before writing code, produce a brief implementation plan:
```markdown
## Implementation Plan: {WI-ID}
**Work item**: {description}
**Files to create**:
- `path/to/new/file.ts` — {purpose}
**Files to modify**:
- `path/to/existing/file.ts` — {what changes, why}
**Design compliance**:
- Component: {COMP-XXX} from {design-file}
- Interface: {iface-name} from {design-file}
- Data entity: {entity-name} from {design-file}
**Acceptance criteria to satisfy**:
- [ ] {criterion 1}
- [ ] {criterion 2}
```
**Rules**:
- If the implementation plan reveals that the work item touches > 3 files,
pause and ask: "This work item spans {N} files. Is the scope correct, or
should it be split?" The Builder (or user) MUST split it into smaller
work items each touching ≤ 3 files before proceeding.
- If the work item requires a file that doesn't map to any design component,
flag a design gap and abort. Do NOT invent design decisions.
Present the plan to the user:
```
Implementation plan for {WI-ID}:
- {N} files to create, {M} files to modify
- {K} acceptance criteria
→ Proceed? (yes / no / revise)
```
---
#### Phase 3 — Implement
Write code following these rules:
##### Design Discipline
- Component interfaces, method signatures, and return types MUST match the
design document exactly.
- Data model fields, types, and relationships MUST match the data design.
- API endpoints, request/response schemas, and status codes MUST match the
interface design.
- If a design decision proves impossible in practice, stop and report the gap
to the Builder. Do NOT silently deviate.
##### Code Quality
- Follow existing project conventions (read neighbor files first to
understand patterns).
- Use existing libraries and utilities already in the codebase — do not
introduce new dependencies without explicit justification.
- Keep functions small and single-purpose — but per `rules/style-guide`, do NOT
preemptively extract single-use helpers; inline at the call site unless the
helper is reused, hides a genuinely complex boundary, or has a clear
independent name that improves the caller.
- Handle errors at the appropriate layer (matching the design's error
handling strategy).
- Write self-documenting code; add comments only for genuinely non-obvious
logic.
- Document all new/modified public APIs inline (JSDoc/TSDoc/pydoc/etc.)
with parameter descriptions, return types, and thrown errors.
- If the project has an API documentation file (e.g. OpenAPI spec, API.md),
update it to reflect the new endpoints, schemas, or behavior changes.
##### Test Discipline
- **Test-first (Red → Green) for declared test_ids.** For every acceptance
criterion (node AC in `{epic-slug}/dag`, whose `test_id` mapping is declared
inline; historically the `04-plan-05-acceptance-criteria` table) that
declares a `test_id`, write that test FIRST and confirm it fails for the
intended reason (Red) before writing the implementation that satisfies it
(Green). The test's `file-path :: test-name` MUST match the declared
`test_id` exactly — this is the implement-side handshake with `verify`
(DOD-1.6). A `test_id` marked `MANUAL` or `BENCH:<script>` is implemented
per its method and is exempt from the Red step. If a test already passes
against existing code (the behavior is already present), note it in the
Phase 5 report rather than forcing an artificial failure.
- Write tests that verify the acceptance criteria.
- Tests must be independent (no shared mutable state).
- Test edge cases identified in the acceptance criteria.
- Test failure paths that the design specifies.
##### Incremental Commitments
- Implement in dependency order within the work item: shared types first,
then data access, then business logic, then API handlers.
- After each coherent unit, run typecheck to catch errors early.
---
#### Common Rationalizations
Implementation fails far more often from **pressure** than from ignorance — the
Developer knows the rules and rationalizes skipping them under context or time
pressure. These are the excuses that precede every review blocker and silent
defect. If you catch yourself thinking any row's "Excuse", stop: the "Reality"
column is the exact rule you are about to break, and breaking it is what turns
a one-pass implementation into a multi-round review.
| Excuse | Reality (the rule being broken) |
|--------|---------------------------------|
| "Design says X, but Y is simpler/better" | Silent deviation is a hidden design gap. Phase 3 Design Discipline: stop and report to the Builder — never silently deviate. |
| "Small change, a test is overkill" | A one-line edit can break a contract. Every acceptance criterion maps to ≥1 test (Phase 4 Brownfield check). 30 seconds now vs. a review blocker later. |
| "I'll write tests after it works" | Tests-after verify what you built, not what was required — you test your own bias, not the spec. |
| "Typecheck passed, lint is cosmetic" | Lint is a Phase 4 gate, not optional polish. Failing lint is an automatic review blocker. |
| "Self-check passed, I'll trust it" | Rubber-stamping misses the MAJORs the formal review will catch. Rule: if YOU can find a MAJOR, fix it now — the first review should never discover what you could have. |
| "This neighbor looks buggy, I'll fix it too" | Scope creep. Log it as an observation in the report; do not fix unrelated code (Greenfield/Brownfield rule). |
| "Spans 5 files but it's one logical change" | The ≤3-files rule is structural, not aesthetic. Split the work item via the Builder (Phase 2 rule). |
| "Design is ambiguous here, I'll pick the obvious option" | Inventing a design decision is a Phase 2 abort condition. Flag the gap; do not guess. |
| "Already manually verified it works" | Manual ≠ systematic — no record, can't re-run, can't bisect. `bun run test:changed` is the evidence the report demands. |
| "Report is busywork, the diff speaks for itself" | No report → review-code cannot trace AC→test. Phase 5 is the handoff contract; skip it and the review stalls. |
| "X× improvement — assumed, no measurement" | Quick-measure before it becomes an AC. Unverified assumptions in ACs waste framing cost ([org-internal #1932]: YAML token density assumed 2-3×, measured 0.95 — hypothesis rejected by data). |
---
#### Phase 4 — Self-Check
After writing all code, run the project's verification commands:
1. **Typecheck**: `bun typecheck` (or project-equivalent). Fix all type errors.
2. **Lint**: `bun oxlint --deny-warnings` (repo root — the review-code
mechanical gate's canonical lint invocation; `bun lint` is the package-script
alias). Fix all lint errors.
3. **Tests**: `bun run test:changed` (or project-equivalent). All affected tests must pass.
4. **Post-deletion cleanup** (mandatory when any code was removed): If files or code blocks were deleted (dead code, test cleanup, refactored-out modules), re-run `bun oxlint --deny-warnings` specifically to catch orphaned imports and unused variables — these are the most common post-deletion regressions. Re-run `bun typecheck` to catch orphaned type references to deleted modules.
Then self-check against `core/checklists/implementation.md`:
- Verify every checklist item marked PRE (pre-implementation) was satisfied
before coding.
- Verify every checklist item marked POST (post-implementation) is satisfied
now.
- For any failed checklist item, fix the code before reporting.
##### Brownfield Self-Check (additional)
For brownfield work items, additionally:
1. **Design spec cross-check**: Re-read the node's cross-session edge
contracts in the frozen DAG copy (historically the design's interface
design section, `03-design-04-interface-design`). Verify every interface
promise — method signatures, return types, output formats, error messages,
config field names, param descriptions — is satisfied exactly as specified.
Schema annotations MUST match actual code behavior.
2. **Test coverage**: For each new function, method, or exported API added,
confirm at least one test exercises it. If `bun run test:changed` reports zero new
tests, add them before handoff.
##### Review Readiness Self-Check (mandatory before handoff)
Before submitting to code review, the Developer MUST self-attest against the
code review checklist. This reduces round-trips by catching common defects
before the first review submission. **The self-check must achieve 0 BLOCKERs
and 0 MAJORs before handoff** — if the Developer can find a MAJOR issue during
self-check, the formal reviewers will find it too.
1. **Run the code review checklist**: Read `core/checklists/code-review.md`
and self-attest that the code likely passes, for each of its 10 dimensions
(COR, DGN, SEC, PERF, TST, STY, DBT, A11Y, DOC, TRC — the authoritative
dimension set lives in the checklist's section headers and
`review-code/reference/code-review-dimensions.md`; do NOT hand-maintain a
copy here).
2. Record the self-attestation in the Phase 5 report under "Review Readiness"
as a pass/fail per dimension. Any FAIL dimension MUST be fixed before handoff.
3. **Hard gate**: self-check MUST find 0 BLOCKERs and 0 MAJORs. If the
Developer finds even one MAJOR, fix it and re-run self-check before handoff.
The first formal code review should never discover issues the Developer
could have caught themselves.
---
#### Phase 4.5 — Iteration Completion Commit
After ALL work items in the current iteration have been implemented and passed
Self-Check (Phase 4), create a git commit BEFORE proceeding to the next
iteration. This preserves per-iteration traceability and enables `git bisect`
per iteration.
##### Commit Rules
1. Commit after the last WI of the iteration is done and self-checked.
2. Commit message format: `[{chunk-id}][{iteration}] {summary}`.
- Example: `[chunk-resolution][iter-1] feat: add two-pass chain resolution engine`
3. **Commit body is REQUIRED for non-trivial commits** (any commit touching > 1 file
or > 20 LOC). The body MUST contain:
- **What**: a 1-3 line summary of the changes (files + purpose), including
the work item ID (`WI-{NNN}`) the commit delivers — code-review TRC 10.1
requires the commit/PR description to carry the work item ID.
- **Why**: the design/requirement motivation (cite REQ-ID or ADR if applicable).
- **Evidence**: test names or verification commands run (e.g. `90 compaction
tests pass`).
- Subject-only commits are acceptable only for single-line fixes or doc tweaks.
4. Include all source + test files from the iteration.
5. After commit, proceed to Phase 4.6 (Issue Checklist Sync), then Phase 5
(Report) for the iteration, then start the next iteration's WIs.
##### Multi-Iteration Workflow
```
Iteration 1 WIs → Self-Check → Commit [iter-1] → Checklist Sync → Code Review →
Iteration 2 WIs → Self-Check → Commit [iter-2] → Checklist Sync → Code Review → Merge
```
---
#### Phase 4.6 — Issue Checklist Sync (progressive)
After committing the iteration, sync the source issue's checklist so
stakeholders see progress in real time. This is mandated by the
`issue-checklist-sync` L1 rule — follow its "How to sync (each point)"
procedure (identify source issue → fetch body → map → update, preserving
non-checklist content); this phase adds only the implement-specific annotation:
- **Stage-specific row**: for each `- [ ]` item the iteration's work satisfies,
mark `- [x]` and append `_(commit {sha}: file/component)_` or
`_(PR #NNN: file)_`.
- **Do NOT touch items outside this iteration's scope** — they will be caught
at a later sync point (next iteration, DAG-freeze aggregation sync, or
verify Phase 5.6). Only check off what this iteration actually delivered.
This is a **progressive** sync: the checklist fills in incrementally as
iterations complete, giving stakeholders a live view of progress without
waiting for the final verify gate.
#### Phase 4.7 — PR-Creation Sync
The session pushes its branch and reports `status=done branch=<ref> verify=…
risk=…`; the orchestrator admits the PR (serially, one open at a time) —
workers never open PRs (TD-678/[org-internal #4425]; `uncoordinated` self-open only when
the orchestrator is unreachable). Once that PR exists, update the source
issue so stakeholders see the mergeable state without waiting for code
review. Mandated by the `issue-checklist-sync` L1 rule; skip if no source
issue exists.
> PR shape per mode: default = one 1:1 PR per task (body carries the worker
> report); batch-mode epics ([org-internal #3731], per-epic opt-in) = the orchestrator
> composes ONE batch PR per iteration via the `land-batch` skill. This phase
> then runs per member issue as usual (N times), each pointing at its PR
> (batch: the single batch PR); the poller writes the PR/CI/review rows
> against every member issue (multi-close-ref fan-out).
1. Re-fetch the issue body via `工单 API(见 TERMINOLOGYget`.
2. **Ensure the `## 当前状态` live-status section exists** (create it if
absent — MANDATORY for incident / standalone-bugfix flows; for quiet
pipeline flows, create it only if it already exists, otherwise skip). The
`PR` row itself is written by the `status-sync` poller
(`.gitea/scripts/status-sync-poll.ts`), NOT this skill — do NOT manually
`工单 API(见 TERMINOLOGYupdate` the PR / 代码评审 / CI rows (per
`issue-checklist-sync.md` § Automated sync).
3. If this is an Epic task list, append the PR reference to the row that this
iteration's work corresponds to.
4. Preserve all non-checklist content.
5. **Never hand-sync main into the PR branch.** Keeping the PR mergeable is
the keep-mergeable workflow's job: once review converges the orchestrator
labels the PR `ready-to-merge` and the server-side keep-mergeable cron
(`.gitea/scripts/keep-mergeable.ts`, driven by
`script/keep-mergeable-cron.sh` under a systemd timer) fetches the PR head,
probes `merge-tree --write-tree`, and pushes a non-force `commit-tree` merge
into the head branch (the retired `POST /pulls/{n}/update-branch` API path
returned 405 on this instance — see AGENTS.md "PR keep-mergeable").
Hand-written `chore: merge origin/main (keep PR mergeable)` commits are
retired — each one re-triggered the full CI surface for near-zero re-tested
risk.
> **Kanban column lifecycle**: automated (`工单 API(见 TERMINOLOGYcreate` → Backlog,
> `gitea_pull__create` → Review; no manual moves). Single shared reference:
> `_shared/gitea-write-patterns.md` Pattern 7.5; column semantics: wiki
> `kanban-lifecycle`.
---
#### Phase 5 — Report
Produce an implementation report:
```markdown
## Implementation Report: {WI-ID}
**Work item**: {description}
**Iteration**: {iteration number}: {goal}
### Files Changed
| File | Action | Purpose |
| ------------------ | -------- | -------------- |
| `path/to/file.ts` | created | {purpose} |
| `path/to/other.ts` | modified | {what changed} |
### Acceptance Criteria
| Criterion | Status | Evidence |
| ------------- | ------ | ---------------------------------- |
| {criterion 1} | ✅ | {test name or manual verification} |
| {criterion 2} | ✅ | {test name or manual verification} |
### Verification Results
- Typecheck: {pass / fail + error count}
- Lint: {pass / fail + warning count}
- Tests: {N} passed, {M} failed, {K} skipped
### Design Deviations
{list any intentional deviations from design with rationale, or "None"}
### Open Items
{anything incomplete with reason, or "None"}
---
**Handoff**: Ready for `core/skills/review-code/SKILL.md`
```
**Persist before returning ([org-internal #2847])**: the Developer MUST write the final
report above to disk as its LAST action, BEFORE returning it —
`<runs-root>/{slug}/workers/{chunk-id}-worker-{seq}.md` when the Tier 1
run workspace exists, else `/tmp/octopus/{chunk-id}-worker-{seq}.md`
(`{chunk-id}`/`{seq}` come from the dispatch prompt — see
`../_shared/worker-report-persistence.md`). The persisted copy is the
report of record; the task notification is a convenience copy. The same
step applies to EVERY mode's report phase (bugfix Phase 5, refactor
Phase 6, port report) — no worker return may exist only in the task
notification.
---
#### Phase 6 — Handoff to Code Review
Present the report to the user and signal readiness for review:
```
Implementation of {WI-ID} complete.
- {N} files changed ({C} created, {M} modified)
- {T} tests passing
- All acceptance criteria satisfied
- Typecheck + lint clean
→ Run code review? (yes / no)
```
Do NOT mark the work item as complete until code review passes.
To notify workflow completion, call the `signal_stage_done` tool.
#### Legacy notes
> **Publish target (tiered targeting retired, [org-internal #3072] phase 3)**: the
> `Size/*`-tiered publish rule (`rules/workflow-routing.md` §"Publish target
> by Size/* tier — RETIRED") was retired with the legacy producer skills.
> Artifacts publish where the live mode puts them: DAG task mode → node spec
> in the frozen `{epic-slug}/dag` copy (see the DAG-mode input path above);
> standalone bugfix → `{slug}/bugfix-report` + issue body per bugfix Phase 5.
> Legacy tiered locations (`{slug}/02-03-req-design`, `{slug}/04-plan-*`, …)
> stay readable for historical runs via `_shared/gitea-read-patterns.md`.
@@ -0,0 +1,252 @@
# Large Port Pipeline — Detailed Processes
> Extracted from `implement/SKILL.md` (Mode: port) Phase B1 through B4.
> Read this file when executing the Pipeline Mode for large ports.
---
## Phase B1 — Source Analysis
Produce a comprehensive source analysis under
wiki page namespace `port-{name}/source-analysis/`. This is the authoritative
behavioral contract that every downstream stage references.
### Directory Structure
```
port-{name}/source-analysis/
├── index # Summary & reading guide (wiki page)
├── 01-source-overview # Source project context, tech stack
├── 02-public-api.md # Every public endpoint / method / interface
├── 03-data-model.md # Entities, fields, relationships, schemas
├── 04-business-logic.md # Validation, rules, edge cases, state machines
├── 05-error-handling.md # Error types, codes, messages, recovery paths
├── 06-dependencies.md # Libraries, infrastructure, external services
├── 07-test-coverage.md # Test inventory: happy path, edge cases, errors
├── 08-concept-mapping.md # Source → target concept mapping (Phase A2)
├── 09-gap-analysis.md # Gaps, alternatives, decisions (Phase A3)
├── 10-fidelity-baseline.md # Full behavioral inventory for end-to-end verify
├── 11-target-surface.md # Target project receiving surface analysis (Phase B1.7)
└── 12-capability-boundary.md # Complete artifact inventory per capability (Phase B1.8)
```
### Document Templates
The format templates for documents 0105 and 10 are in
`reference/source-analysis-templates.md` (read it when producing these
documents). Documents 0609 follow the same table-based format as their
Phase A1/A2/A3 counterparts in standalone mode. The `10-fidelity-baseline.md`
document is the master inventory — every source behavior is listed as a
checkable `FID-*` item with a `Chunk` column that drives DAG node decomposition.
---
## Phase B1.5 — Source Analysis Review (GATE)
Before proceeding to self-check, ALL source analysis documents MUST pass
peer review. This is the single highest-leverage quality gate in the port
pipeline — errors in source analysis propagate to every downstream stage.
### Review Process
1. **Spawn 10 parallel reviewer sub-agents** (Explorer), one per dimension.
Each reviewer receives:
- The relevant source analysis documents (as listed per dimension below).
- The source project files (or accessible copies).
- The target project files (for TGT-SURF and CAP-BOUND dimensions).
- Review instructions for that dimension.
2. **Review dimensions**:
| Dimension | Documents Reviewed | Key Question |
| --------- | ------------------ | ------------ |
| SRC-CMP | 01-source-overview, 03-data-model, 04-business-logic, 10-fidelity-baseline | Is every source behavior/entity/rule captured? |
| SRC-API | 02-public-api, 05-error-handling | Is every public endpoint/method/error documented accurately? |
| SRC-DATA | 03-data-model | Are entities, fields, types, constraints, and relations correct? |
| SRC-BIZ | 04-business-logic | Are every validation rule, edge case, and state transition documented? |
| SRC-ERR | 05-error-handling | Are all error types, codes, messages, and recovery paths captured? |
| SRC-DEP | 06-dependencies | Are all libraries, infrastructure, and external services listed? |
| SRC-TST | 07-test-coverage, 10-fidelity-baseline | Does every source test case map to a FID item? Are file:line references correct? |
| SRC-MAP | 08-concept-mapping | Is every source concept mapped to a target equivalent or [GAP]? Are mappings correct? |
| TGT-SURF | 11-target-surface | Is the target project's receiving surface fully analyzed? Are all integration points, structural diffs, and readiness items captured? Does every source gap in the structural diff have a plan? |
| CAP-BOUND | 12-capability-boundary | Are all 13 artifact dimensions filled in? Does every Source Function Inventory item appear in D1/D2? Does every structural diff gap have a boundary entry? Are all N/A dimensions justified? |
3. **Output**: Each reviewer writes a findings JSON conforming to
`core/schemas/port-analysis.schema.json` as a
wiki page `port-{name}/source-analysis/reviews/{dimension}`
with format:
```json
{
"dimension": "SRC-CMP",
"findings": [
{
"id": "SRC-CMP-001",
"severity": "BLOCKER|MAJOR|MINOR",
"description": "...",
"source_evidence": "file:line",
"recommendation": "..."
}
]
}
```
4. **Synthesis**: An Orchestrator (Worker) collects all 10 dimension reports,
deduplicates, and produces a synthesis:
wiki page `port-{name}/source-analysis/reviews/synthesis`
with summary counts per dimension and consolidated action items.
5. **Iterate until convergence**:
- Developer addresses all BLOCKER and MAJOR findings.
- Reviewer re-checks affected dimensions.
- Repeat until all dimensions show 0 BLOCKER and 0 MAJOR.
6. **GATE**: Phase B2 cannot start until synthesis shows ALL dimensions
converged (0 BLOCKER, 0 MAJOR). Developer prints:
`SOURCE ANALYSIS REVIEW CONVERGED — 10/10 dimensions pass`
---
## Phase B1.7 + B1.8 — Target Surface & Capability Boundary (Pipeline)
In pipeline mode, Phase A1.7 (Target Surface Analysis) and Phase A1.8
(Capability Boundary Definition) produce formal artifacts in the
source-analysis directory for peer review:
- **`11-target-surface.md`** — same process and format as Phase A1.7, using
`reference/target-surface-template.md`. Reviewed by the **TGT-SURF**
dimension in Phase B1.5.
- **`12-capability-boundary.md`** — same process and format as Phase A1.8,
using `reference/capability-boundary-template.md`. Reviewed by the
**CAP-BOUND** dimension in Phase B1.5.
### Roadmap decomposition link
The capability boundary directly feeds the DAG decomposition (Phase B3):
- Chunks are defined by grouping capability boundary artifacts by source
module / feature area.
- Cross-chunk dependencies are derived from dimension 13 (shared package
changes).
- The fidelity baseline (`10-fidelity-baseline.md`) is partitioned across
chunks based on which capability boundary artifacts implement each FID item.
**GATE**: Do NOT proceed to Phase B2 until the B1.5 review converges on ALL
10 dimensions including TGT-SURF and CAP-BOUND.
---
## Phase B2 — Self-Check Source Analysis
Run the port checklist (`core/checklists/port.md`) against the source
analysis:
- Section 0.5 (SRV — Source Analysis Review): 10-dimension review converged.
- Section 0.7 (TGT — Target Surface Analysis): target receiving surface
fully mapped, structural diff complete, integration points identified.
- Section 0.8 (CAP — Capability Boundary): all 13 artifact dimensions filled,
cross-checks passed.
- Section 1 (SRC — Source Understanding): every source file and test read.
- Section 2 (MAP — Concept Mapping): every source concept has a target
equivalent or `[GAP]`.
- Section 3 (GAP — Gap Analysis): all gaps have alternatives and decisions.
- Sections 47 (ADAPT, FID, TST, BEH): deferred to downstream stages —
marked as `[DEFERRED TO DESIGN]`, `[DEFERRED TO IMPLEMENT]`, etc.
---
## Phase B3 — Handoff to DAG Decomposition
> Legacy note ([org-internal #3072] phase 3, 2026-08-21): this handoff used to target the
> archived `roadmap` skill; it now targets `analyze-dag`.
Present the source analysis and request task-DAG decomposition:
```
Source analysis complete for port-{name}.
- {N} source files ({L} LOC) across {M} modules
- {K} public API endpoints / methods documented
- {B} business rules captured
- {F} fidelity baseline items (for end-to-end verify)
- {G} gaps identified with alternatives
Artifacts: wiki pages under `port-{name}/source-analysis/`
→ Approve and begin DAG decomposition? (yes / no / revise)
```
Upon approval, the Builder routes to
`core/skills/analyze-dag/SKILL.md` with:
- **Slug**: `port-{name}`
- **Scope**: the source modules and fidelity baseline from the source analysis.
analyze-dag decomposes the port into a task DAG by source module / feature
area. Each node is a self-contained porting unit (e.g. `N-auth`, `N-api`,
`N-models`); node ACs derive from the source analysis (`FID-*` items become
ACs tracing to source tests; concept-mapping and gap decisions become the
node spec; porting order follows source-file dependency order as edge
topology). After the `review-dag` single gate PASSes, each node ticket flows
`dag.task_route`:
```
core/skills/implement/SKILL.md
→ core/skills/review-code/SKILL.md
→ core/skills/verify/SKILL.md
```
(The legacy per-chunk `requirements-elicitation` → `design` →
`review-artifact(design-space)` → `plan-iterations` → `review-artifact(plan)`
front-end was archived 2026-08-21, [org-internal #3072] phase 3 — `<instance-root>/archive/`.)
### implement
The Developer ports code following the rules from Phase A5 (target
conventions, fidelity over aesthetics, no new dependencies, port comments).
Additionally:
- Each implementation report references the corresponding `FID-*` items
from the fidelity baseline.
- Ported tests reference source test file and line number.
### review-code
Standard code review. Additionally:
- Reviewer checks fidelity: does the ported code match the source behavior
as documented in the chunk's requirements?
- Reviewer checks convention compliance: does the new code look native to
the target project?
### Target-Side Refactoring in Port Pipeline
Large ports often require restructuring the target codebase to accommodate
ported code — extracting interfaces, renaming conflicting modules, removing
dead code, or adapting existing abstractions. These refactoring needs MUST
flow through the pipeline's quality gates, not as ad hoc changes.
**Identification**: The Architect identifies target-side refactoring needs
during design and documents them as design decisions. Each refactoring
decision references the gap that requires it (e.g. "Target's UserService must
be extracted to an interface before porting source AuthService to avoid
tight coupling").
**Planning**: The Planner creates refactoring work items alongside
implementation work items. A refactoring work item's description starts with
"Refactor" (or 重构) and its requirements coverage maps to a refactoring
requirement derived from the design decision. Dependencies are enforced:
- Refactoring work items that unblock port work items appear earlier in the
iteration order.
- No port work item depends on an incomplete refactoring.
**Execution**: The `implement` skill handles refactoring work items via its
Refactoring Mode (see
`core/skills/implement/SKILL.md` — Refactoring Mode). The refactoring
produces a standard implementation report and proceeds to code review.
**Quality**: Refactoring work items go through the full review gate —
code review is mandatory regardless of change size. The reviewer verifies:
- Behavioral fidelity: did the refactoring preserve existing behavior?
- Test baseline: did any existing tests break or change?
- Design alignment: does the refactoring match the design decision?
```
pipeline for refactoring work items (DAG node):
implement (refactoring mode) → review-code → verify
```
@@ -0,0 +1,102 @@
# Concept Mapping, Gap Analysis & Adaptation Design — Detailed Processes
> Extracted from `implement/SKILL.md` (Mode: port) Phase A2, A3, A4.
> Read this file when executing the Mapping and Design phases in standalone mode.
---
## Phase A2 — Map Concepts
For every source concept, identify the target project's equivalent. This is a
bidirectional mapping — every source entity, every source API call, every
source pattern must have a corresponding target concept.
### Mapping Table
| Source Concept | Target Equivalent | Notes |
| ------------------ | ------------------------------------------- | ---------------------------------------------------- |
| Express middleware | Fastify hook | Different signature — adapt order and error handling |
| Sequelize model | Drizzle schema | Different migration strategy — adapt CLI |
| bcrypt hash | argon2 | Target project's existing auth module uses argon2 |
| Redis cache | Memory cache (no Redis) | Compromise — simplify to in-memory with TTL |
| Pino logger | Existing logger module in `src/util/log.ts` | Reuse target's logger |
### Rules
- If a source concept has no clear target equivalent, pause and log `[GAP]`.
- If the target has a different pattern for the same concept (e.g. callbacks
vs. async/await), prefer the TARGET pattern, not the source's.
- If the source uses a library that exists in the target's ecosystem, use the
version already in the target's `package.json` — do not introduce a different
version.
---
## Phase A3 — Gap Analysis
For every `[GAP]` from Phase A2, analyze the impact:
### Gap Analysis Table
| Gap | Impact | Alternatives | Decision | Deferred To (slug) | Reactivation Trigger |
| ------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------- | ---------- | ------------------ | -------------------- |
| No Redis in target | Source uses Redis for session store | 1. Add Redis to target, 2. Use DB for sessions, 3. Use in-memory (not for production) | {decision} | | |
| No message queue | Source uses RabbitMQ for async tasks | 1. Add queue to target, 2. Make synchronous, 3. Use a simpler queue (e.g. database polling) | {decision} | | |
### Rules
- Do NOT add infrastructure to the target unless absolutely necessary — prefer
alternatives that use existing target infrastructure.
- If a gap forces a behavior change, mark it as `[FIDELITY DEVIATION]` — the
port will not behave identically. This must be explicitly approved.
### Deferral Rules
- A gap marked `Deferred` MUST populate both "Deferred To" and "Reactivation Trigger" columns.
- "Deferred To" must reference a concrete artifact slug (e.g. `port-{name}/chunk-http`).
- "Reactivation Trigger" must specify a condition (e.g. "After chunk-auth verification passes").
- Gaps without a reactivation path are treated as `[PORT GAP]` — a blocker for the current port.
---
## Phase A4 — Adaptation Design
Design how the source feature will fit into the target project:
1. **File structure** — where in the target project will the ported code live?
2. **Interface adaptations** — source API signatures must adapt to target
conventions (e.g. source uses `snake_case`, target uses `camelCase`).
3. **Dependency replacements** — for each source dependency, use the target
equivalent or the Gap decision from Phase A3.
4. **Test adaptation** — source test framework → target test framework mapping
(e.g. `describe`/`it``describe`/`it` if both use the same pattern, or
map to target's test DSL).
### Adaptation Design Template
````markdown
## Adaptation Design
### File Structure
```
src/
{module}/
{ported_file}.ts — (from source/src/{module}/{file}.js)
...
test/
{module}/
{ported_test}.test.ts — (from source/test/{module}/{file}.test.js)
```
### Interface Adaptations
| Source | Target | Reason |
|--------|--------|--------|
| `req.body.created_at` | `req.body.createdAt` | Target convention: camelCase |
| `throw new AppError(400, '...')` | `yield* new BadRequest('...')` | Target uses Effect errors |
### Fidelity Deviations
| What Changes | Why | Impact |
|-------------|-----|--------|
| Session store: Redis → DB | Target has no Redis | Slightly higher latency (~5ms), CAP consistency trade |
| Async queue: RabbitMQ → DB polling | Target has no queue broker | Higher latency, lower throughput — acceptable for < 100 ops/min |
````
@@ -0,0 +1,389 @@
> Extracted from implement/SKILL.md (Mode: port) — moved verbatim 2026-08-25, ticket [org-internal #3381].
### Mode: port
Port a feature from a source project into the current (target) project. Unlike
greenfield (no existing code), brownfield (new feature in same project), or
bugfix (restore intended behavior), porting requires preserving the source's
behavioral contract while adapting every implementation detail to the target's
tech stack and conventions.
#### Role & Responsibilities
The port is owned by the **Developer** (Worker).
The Developer is responsible for:
- Reading and understanding the source feature end-to-end.
- Mapping source concepts to target equivalents.
- Identifying gaps (missing infrastructure, incompatible libraries).
- Designing adaptations that preserve behavior.
- For small ports: implementing in the target project following target
conventions end-to-end.
- For large ports: producing a source analysis report, then handing off to
the DAG pipeline (analyze-dag → review-dag → per-node implement →
review-code → verify) with the single-gate review.
- Porting source tests to the target test framework.
- Verifying behavioral fidelity (same inputs → same outputs).
The Builder's role is to validate the port output and route to the next stage
(analyze-dag for large ports, code review for non-trivial small ports).
#### Tester focus for port
The Tester role in port writes **behavioral-fidelity tests** — verifying
that ported code behaves identically to the source, not just that it passes
its own assertions:
- **Fidelity-anchored tests** — every `FID-*` item in
`port-{name}/source-analysis/10-fidelity-baseline` (or `port-{name}/source-analysis/fid-raw` in standalone mode) MUST map
to at least one ported test. The Tester reads the source test for each
FID and rewrites it in the target test framework's assertion style,
preserving the behavioral contract (same inputs → same outputs).
- **Source-test porting** — port edge cases, error paths, and boundary
tests from the source, not just the happy path. Missing FID coverage is
the #1 cause of incomplete porting.
- **Fidelity-deviation tests** — for every `[FIDELITY DEVIATION]` in the
adaptation design (Phase A4), write a test that documents and locks the
divergent behavior so the deviation is intentional, not accidental drift.
The port orchestration follows the standard role-split flow (implement
before test): the Developer ports code in Phase A5 and runs test:changed
to green; the Tester then ports source tests and verifies fidelity in
Phase A6. In pipeline mode (Part 2), the role split applies within each
chunk's implement stage.
---
#### Phase 0 — Scope Assessment
Before starting, measure the source scope across **four dimensions** — file
count and LOC alone are insufficient because a 3-file port that spans 3
packages with implicit dependencies (schema, config, routes) is far more
complex than a 10-file port within a single self-contained module.
1. Count source files in the feature scope.
2. Count total source lines of code (exclude tests).
3. Count source modules / feature areas (distinct functional areas).
4. **Count source packages touched** — how many monorepo packages does the
feature span? (e.g. `packages/app`, `packages/sdk`, `packages/ui`,
`packages/core`).
5. **Count implicit dependency artifacts** — schema/migration files, config
entries, env vars, CLI flags, theme/style files, route definitions,
Provider/context hierarchy changes, build config changes. These are the
artifacts that are NOT source code files but are required for the feature
to function. See Phase A1.8 for the full artifact dimension list.
Determine the port path:
| Scope | Mode | Pipeline |
| ---------------------------------- | ------------- | ------------------------------------------------------ |
| ≤ 5 files, ≤ 200 LOC, 1 package, 0 implicit deps | **Standalone**| Standalone Developer flow (Phases A1A8, mandatory code review) |
| > 5 files or > 200 LOC | **Pipeline** | Full pipeline: source analysis → analyze-dag → per-node |
| Spans ≥ 3 source modules | **Pipeline** | Full pipeline (regardless of file count / LOC) |
| Spans ≥ 2 packages | **Pipeline** | Full pipeline (cross-package ports have hidden coupling) |
| ≥ 3 implicit dependency artifacts | **Pipeline** | Full pipeline (implicit deps require capability boundary analysis) |
Report the assessment:
```
Port scope assessment:
- Source files: {N}
- Source LOC: {L}
- Source modules: {M}
- Source packages touched: {P}
- Implicit dependency artifacts: {I}
- Path: A (standalone) / B (full pipeline)
→ Proceed? (yes / no / revise)
```
Do NOT proceed without explicit approval.
---
#### Preconditions
- [ ] Source project path or reference is specified.
- [ ] Source feature scope is specified (which files, module, or feature).
- [ ] Target project is the current working directory and has a clean
workspace.
- [ ] Target project has an existing test framework.
- [ ] **Target project structure is accessible** — the Developer can read all
target project packages, config files, and build configs. If the target
is a monorepo, all packages must be accessible.
- [ ] **Target project's existing capabilities are documented or discoverable**
— the Developer must be able to identify what the target already has
(existing modules, routes, providers, schemas) to avoid redundant porting
and to identify integration points. If not documented, the Developer
must run a structural survey (Phase A1.7) before proceeding.
- [ ] `core/checklists/port.md` is accessible.
If the target project has no test framework, warn:
```
Target project has no test framework. Porting without tests cannot verify
behavioral fidelity. Options:
1. Add a test framework to the target project first.
2. Proceed without tests — behavioral fidelity cannot be guaranteed.
```
---
#### Part 1 — Standalone Mode (Small Port)
For small ports (≤ 5 files, ≤ 200 LOC), the Developer executes the full
port lifecycle directly. No DAG decomposition, no per-stage review gates.
Code review is mandatory (regardless of size, per Phase A8).
| Phase | Summary | Detail |
|-------|---------|--------|
| A1 | Deeply analyze source: public API, data model, dependencies, function inventory, test-to-FID extraction | `reference/source-analysis.md` |
| A1.5 | Source Analysis Review GATE — 4-dimensional peer review of A1 deliverables | `reference/source-analysis.md` |
| A1.7 | Target Surface Analysis — map target receiving surface, structural diffs, integration points | `reference/source-analysis.md` |
| A1.8 | Capability Boundary Definition GATE — 13-dimension artifact inventory | `reference/source-analysis.md` |
| A2 | Map every source concept to a target equivalent or mark as `[GAP]` | `reference/mapping-and-design.md` |
| A3 | Gap Analysis — alternatives, decisions, deferral paths for every `[GAP]` | `reference/mapping-and-design.md` |
| A4 | Adaptation Design — file structure, interface adaptations, fidelity deviations | `reference/mapping-and-design.md` |
| A5 | **Implement** — port code following target conventions | *(inline below)* |
| A5.5 | **Self-Check Gate** — complete port checklist | *(inline below)* |
| A6 | Port Tests & Verify Fidelity — port every test, fidelity checklist | `reference/fidelity-verification.md` |
| A7 | **Report** — produce port report | `reference/port-report-template.md` |
| A8 | **Approval** — present report, route to code review | *(inline below)* |
##### Phase A1 — Understand Source
Deeply analyze the source feature: public API, data model, dependencies, and
function inventory. Extract every test case as a `FID-*` entry in `port-{name}/source-analysis/fid-raw` (wiki page).
See `reference/source-analysis.md` for the full process, templates, and
Function Inventory format.
##### Phase A1.5 — Source Analysis Review (GATE)
A reviewer (Explorer sub-agent) cross-checks all A1 deliverables against source
files across 4 dimensions (SRC-CMP, SRC-API, SRC-TST, SRC-DEP). All BLOCKER
findings must be resolved before Phase A2. See `reference/source-analysis.md`.
##### Phase A1.7 — Target Surface Analysis
Analyze the target project's receiving surface: directory tree, existing
capabilities, automated structural diffs, integration points, and readiness.
See `reference/source-analysis.md` for the full process.
Output follows `reference/target-surface-template.md`.
##### Phase A1.8 — Capability Boundary Definition (GATE)
Define the complete artifact boundary across all 13 dimensions (code, types,
schema, config, env, CLI, theme, routes, providers, build, deps, tests, shared
packages). Cross-reference with A1 inventory and A1.7 diffs.
See `reference/source-analysis.md` for the full process.
Output follows `reference/capability-boundary-template.md`.
##### Phase A2 — Map Concepts
Map every source concept to a target equivalent — bidirectional, complete.
Mark missing equivalents as `[GAP]`. See `reference/mapping-and-design.md`.
##### Phase A3 — Gap Analysis
Analyze every `[GAP]`: impact, alternatives, decision, deferral path with
reactivation trigger. See `reference/mapping-and-design.md`.
##### Phase A4 — Adaptation Design
Design file structure, interface adaptations, dependency replacements, and
document `[FIDELITY DEVIATION]` items. See `reference/mapping-and-design.md`.
##### Phase A5 — Implement
Port the code file by file, following these rules:
1. **One file at a time** — port completely, verify, then move to the next.
2. **Target conventions are law** — the ported code must follow target
conventions exactly. Use target's naming, patterns, and idioms.
3. **Fidelity over aesthetics** — do not "improve" the source logic. If the
source validates email with `/^.+@.+$/`, port that exact validation (then
log a `[NOTE: weak validation in source]`). If you want stricter validation,
that's a separate feature item, not part of the port.
4. **Port comments from source** (translated to target language) — they capture
the original author's intent.
5. **After each file** — run `bun typecheck` to catch type errors early.
6. **Do not introduce new dependencies** — if the source uses a library not in
the target's lockfile, use the alternative from the adaptation design or
reimplement the needed subset inline.
##### Phase A5.5 — Self-Check Gate (MANDATORY)
Before proceeding to test porting, the Developer MUST complete the port
checklist (`core/checklists/port.md`) in full:
1. **Run every checklist section** — all 12 sections, all items.
2. **Mark every item** — ☑ (pass) or ☐ (fail) with written justification.
3. **For any ☐ item** — document a fix plan inline in the remarks column.
If the item is a known deferral (e.g. infrastructure gap), the deferral
must include a reactivation path (chunk + trigger).
4. **Publish the completed checklist** as a Gitea wiki page:
`wiki 读写 API(见 TERMINOLOGY(owner="Octopus", repo="octopus", title="port-{name}/self-check", content="{checklist body}", message="Publish port self-check for {name}")`.
5. **GATE** — do NOT proceed to Phase A6 until:
- All items are ☑, OR
- All ☐ items have documented fix plans with reactivation paths,
AND the Developer has printed: `SELF-CHECK COMPLETE — {X} items passing, {Y} items deferred with plan`
##### Phase A6 — Port Tests & Verify Fidelity
Port every source test to the target test framework. Run all tests (ported +
existing), verify behavioral fidelity, and produce a fidelity verification
table. See `reference/fidelity-verification.md` for the full process and
fidelity checklist template.
##### Phase A7 — Report
Produce a port report following the template in
`reference/port-report-template.md`. The report covers: fidelity assessment
(fully ported / adapted / deferred / N/A), portfolio map (source → target file
mapping), gaps & deferred items, and verification results.
##### Phase A8 — Approval
Present the report:
```
Port complete: {one-line summary}
- {N} files ported ({L} lines)
- {M} tests ported, all pass
- Fidelity: {X}% fully ported, {Y}% adapted, {Z}% deferred
→ Run code review? (mandatory)
```
---
#### Part 2 — Pipeline Mode (Large Port)
For large ports, the Developer produces a source analysis report as the
foundation artifact, then the port flows through the full pipeline with
peer-review gates at every stage:
```
port (source analysis)
→ analyze-dag (decompose by source module into DAG nodes)
→ review-dag single gate (replaces the legacy design-space + plan reviews)
→ per node (dag.task_route):
implement (port code, target conventions) — see ### Mode: implement (default)
→ review-code
→ verify (integration + fidelity)
```
> Legacy diagram (archived [org-internal #3072] phase 3): the former per-chunk pipeline ran
> requirements-elicitation → design → review (target: design-space) →
> plan-iterations → review (target: plan) → implement → review-code. Those
> skills/targets are archived (`<instance-root>/archive/`); the live path is the
> analyze-dag → review-dag route above (see Phase B3/B4 below).
| Phase | Summary | Detail |
|-------|---------|--------|
| B1 | Produce comprehensive 12-document source analysis | `reference/large-port-pipeline.md` |
| B1.5 | **GATE** — 10-dimensional peer review of source analysis | `reference/large-port-pipeline.md` |
| B1.7+B1.8 | Target surface & capability boundary as formal artifacts for peer review | `reference/large-port-pipeline.md` |
| B2 | Self-check source analysis against port checklist | `reference/large-port-pipeline.md` |
| B3 | Handoff to analyze-dag — decompose by source module into DAG nodes (legacy: roadmap skill, archived [org-internal #3072] phase 3) | `reference/large-port-pipeline.md` |
| B4 | Per-node DAG task route: implement → review-code (the review-dag single gate replaces the legacy design-space + plan reviews at the Epic level) | `reference/large-port-pipeline.md` |
| B5 | Verify — integration + fidelity, every `FID-*` traced to a passing test | `reference/fidelity-verification.md` |
| B6 | **Final Approval** — consolidated port report | *(inline below)* |
##### Phase B1 — Source Analysis
Produce a comprehensive 12-document source analysis as Gitea wiki pages under
`port-{name}/source-analysis/`. Each document is a separate wiki page.
See `reference/large-port-pipeline.md` for the full document list, templates (in
`reference/source-analysis-templates.md`), and production process.
##### Phase B1.5 — Source Analysis Review (GATE)
Spawn 10 parallel Explorer reviewers across all dimensions (SRC-CMP, SRC-API,
SRC-DATA, SRC-BIZ, SRC-ERR, SRC-DEP, SRC-TST, SRC-MAP, TGT-SURF, CAP-BOUND),
synthesize findings, and iterate until convergence (0 BLOCKER, 0 MAJOR).
See `reference/large-port-pipeline.md` for the full process.
##### Phase B1.7 + B1.8 — Target Surface & Capability Boundary
Same processes as A1.7/A1.8, producing formal artifacts (`11-target-surface.md`,
`port-{name}/source-analysis/12-capability-boundary`) for peer review. The capability boundary feeds
directly into DAG node decomposition.
See `reference/large-port-pipeline.md`.
##### Phase B2 — Self-Check Source Analysis
Run the port checklist against the source analysis (sections 0.5, 0.7, 0.8, 1,
2, 3; sections 47 deferred to downstream stages).
See `reference/large-port-pipeline.md`.
##### Phase B3 — Handoff to DAG Decomposition
Present source analysis summary and request task-DAG decomposition. The
Builder routes to `core/skills/analyze-dag/SKILL.md` with slug
`port-{name}` (nodes per source module; the source analysis supplies node
ACs and edge contracts). (Legacy: this handed off to the archived `roadmap`
skill — `<instance-root>/archive/skills/roadmap/`, [org-internal #3072] phase 3.) See
`reference/large-port-pipeline.md` for the handoff format.
##### Phase B4 — Per-Node Pipeline
Each node ticket flows `dag.task_route`: `implement``review-code`
(the `review-dag` single gate replaces the legacy design-space + plan
reviews at the Epic level). Includes rules for target-side refactoring work
items routed through the refactor workflow (see Mode: refactor above).
See `reference/large-port-pipeline.md` for full per-stage details.
##### Phase B5 — Verify (Integration + Fidelity)
Run `core/skills/verify/SKILL.md` with the fidelity baseline as acceptance
criteria. Every `FID-*` must trace to a passing test. Produce a consolidated
Port Fidelity Report. See `reference/fidelity-verification.md`.
##### Phase B6 — Final Approval
Before declaring the port complete, verify:
1. **CI is configured** — Check `.gitea/workflows/` (this repo's CI location),
`.github/workflows/ci.yml`, or equivalent.
If absent, warn: `[GAP: no CI — no automated gate before merge]`.
The PR may be merged, but flag the gap in the port report.
2. **All review gates passed** — The `review-dag` single gate and every
node's code review have `converged: true`.
3. **Final typecheck + lint + tests pass** — Run all three commands fresh.
Present the consolidated port report:
```
Port complete: port-{name}
- {N} chunks implemented
- {F} files ported ({L} lines)
- {T} tests ported, all pass
- Fidelity: {X}/{Y} behaviors verified, {Z} deferred
- All peer-review gates passed
- CI: {configured / absent — manual gate required}
Artifacts: wiki pages under `port-{name}/`
→ Approve port? (yes / no)
```
##### Phase B7 — Post-Merge Cleanup
After the PR is merged and the port branch is no longer needed:
1. **Delete the remote branch**:
```
git push origin --delete workflow/port/{name}
```
2. **Delete the local branch**:
```
git branch -d workflow/port/{name}
```
3. **Remove associated worktrees**:
```
git worktree list | grep "workflow/port/{name}" | awk '{print $1}' | xargs git worktree remove
```
4. **Verify cleanup**: `git branch -a | grep workflow/port/{name}` should
return empty.
@@ -0,0 +1,43 @@
# Port Report Template
```markdown
# Port Report
**Source**: {project name} — {feature name}
**Target**: {current project}
**Files ported**: {N}
**Tests ported**: {M}
## Fidelity Assessment
| Category | Status | Count |
| ------------------------------------- | ------ | ------------- |
| Fully ported | ✅ | {N} behaviors |
| Adapted (minor change) | ⚠️ | {N} behaviors |
| Deferred (not ported) | ❌ | {N} behaviors |
| Not applicable (different tech stack) | N/A | {N} behaviors |
## Portfolio Map
| Source File | Target File | Lines | Status |
| -------------------------------- | -------------------------------- | --------- | ------ |
| `source/src/auth/login.js` | `target/src/auth/login.ts` | 45 → 52 | ✅ |
| `source/test/auth/login.test.js` | `target/test/auth/login.test.ts` | 120 → 118 | ✅ |
## Gaps & Deferred
| Item | Reason | Deferred to |
| ------------- | -------------------------- | ------------------------------------- |
| Rate limiting | Target has no rate limiter | Separate feature: "Add rate limiting" |
## Verification
- Ported tests: {M} passed, 0 failed
- Existing tests: {K} passed, 0 failed
- Typecheck: ✅
- Lint: ✅
---
**Handoff**: → Run `core/skills/review-code/SKILL.md` (mandatory for all ports, regardless of size)
```
@@ -0,0 +1,258 @@
> Extracted from implement/SKILL.md (Mode: refactor) — moved verbatim 2026-08-25, ticket [org-internal #3381].
### Mode: refactor
Restructure existing code to improve maintainability, readability, or
performance without changing observable behavior. The existing test suite
is the safety net — every refactoring step MUST be verified before proceeding.
#### Execution Modes
| Mode | Entry Point | Scope Source | Review Gate |
| ---------- | -------------------------------------------- | --------------------------- | ---------------------- |
| Standalone | User says "refactor {X}" | User specifies scope | Optional (>50 lines or ≥5 files) |
| Pipeline | `implement` skill dispatches refactoring WI | Node spec (DAG) or request | Mandatory |
In pipeline mode, the scope and target pattern come from the design document
and iteration plan, not from user input. The Developer must read the design
sections referenced by the work item before starting Phase 1. After completing
Phases 16, the Developer produces the standard implementation report (see
Mode: implement (default), Phase 5) and hands off to code review.
#### Role & Responsibilities
The refactoring is owned and executed by the **Developer** (Worker). The
Developer owns both implementation and refactoring — same role, same skill set.
The Developer is responsible for:
- Establishing a passing test baseline before any code change.
- Decomposing the refactoring into small, reversible, verifiable steps.
- Running the full test suite after every step — never skip a verification.
- Reverting immediately if any step causes a test failure.
- Comparing before/after coverage and complexity metrics.
The Builder's role is to present the refactor report and route to code review
if the change is non-trivial (> 50 lines or touches ≥ 5 files).
---
#### Preconditions
Before starting the refactoring, confirm:
- [ ] Scope is specified (which file, module, or pattern to refactor).
- [ ] An existing test suite covers the scope. If test coverage is unknown,
run the test suite with coverage first.
- [ ] No uncommitted changes in the working tree (`git status` is clean).
- [ ] `core/checklists/refactoring.md` is accessible.
##### No Test Coverage? Stop.
If the scope has **no existing tests**:
```
Cannot safely refactor {scope} — no existing test coverage.
Refactoring without tests is not restructuring, it's rewriting with unknown
side effects. Options:
1. Write characterization tests first (tests that capture current behavior),
then refactor.
2. Skip this module — refactor only modules with test coverage.
```
---
#### Phase 1 — Scope & Baseline
1. **Identify scope** — confirm the exact files, classes, or modules to
refactor. Use `glob` and `grep` to map all files and their dependents.
2. **Establish baseline**:
- Run `bun run test:parallel` (or project-equivalent) — all tests must pass.
- If any test fails before you start, stop and report: "Cannot begin
refactoring with failing tests. Fix them first."
- Capture test count as baseline: `tests: {N} total, {N} passed`.
- Capture coverage if available: run the package's coverage script from
the package dir (e.g. `cd <harness-package> && bun run test:coverage`) —
`bun run test:parallel` does not emit coverage (its runner script drops
positional args, so `test:parallel --coverage` silently ignores the
flag); use the package's `test:coverage` script instead.
3. **Capture complexity metrics** (optional but recommended):
- Lines of code in scope.
- Cyclomatic complexity or equivalent (if tooling exists).
- Dependencies (fan-in / fan-out).
```markdown
## Baseline
**Scope**: {list of files}
**Tests**: {N} passed, 0 failed
**Coverage**: {X}% lines, {Y}% branches
**LOC**: {N}
**Complexity**: {measured or "no metrics tool available"}
```
---
#### Phase 2 — Define Target Pattern
Define what "done" looks like. A refactoring without a target pattern is
code churn, not improvement.
The target pattern must be one of:
| Category | Examples |
| ------------ | -------------------------------------------------------------------------------------------------------------- |
| **Extract** | Extract class, extract function, extract module |
| **Inline** | Inline function, inline variable, inline class |
| **Rename** | Rename function, variable, class, file, module |
| **Move** | Move function/class to a more appropriate module |
| **Replace** | Replace callback with Promise/await, replace loop with functional style, replace conditional with polymorphism |
| **Simplify** | Remove dead code, collapse redundant logic, flatten nested conditionals |
| **Upgrade** | Migrate to new API, adopt new library version patterns |
```markdown
## Target Pattern
**Category**: {Extract | Inline | Rename | Move | Replace | Simplify | Upgrade}
**Goal**: {one sentence — e.g. "Extract UserRepository from UserController to
separate persistence logic from HTTP handling"}
**Success criteria**:
1. All existing tests pass unchanged.
2. {specific structural goal — e.g. "UserController no longer imports Database"}.
3. Coverage does not decrease.
4. {additional criteria if applicable}.
```
---
#### Phase 3 — Decompose into Steps
Break the refactoring into the smallest individually-verifiable steps.
Each step must:
- Be reversible (if tests break, revert and reassess).
- Pass the full test suite independently.
- Be one conceptual transformation (not "rename + extract + inline" in one step).
- Take ≤ 5 minutes to write.
```markdown
## Refactoring Steps
| Step | Action | Files Affected | Expected Outcome |
| ---- | ----------------------------------------------- | ------------------------------------------------ | ---------------------------------- |
| 1 | Extract `findById` method from controller | `user.controller.ts`, `user.repository.ts` (new) | Controller delegates to repository |
| 2 | Extract `create` method | `user.controller.ts`, `user.repository.ts` | Same pattern as step 1 |
| 3 | Inline `formatUser` helper (used once) | `user.controller.ts` | Remove one-line helper |
| 4 | Rename `user.controller.ts``user.handler.ts` | `user.controller.ts`, 3 imports | Naming consistency |
```
**Rules**:
- Present the step plan to the user before executing.
- If > 10 steps, the scope is too large — split into multiple refactoring
sessions.
- The user may approve, reorder, or reject individual steps.
---
#### Phase 4 — Incremental Execution
For each step, in order:
1. **Transform**: apply the single conceptual change.
2. **Verify**: run `bun run test:changed` — ALL affected tests must pass.
3. **If PASS**: commit the step with a message describing the transformation:
```
refactor: extract {what} from {where}
```
4. **If FAIL**: revert the change. Do NOT fix the test or the code. Assess
whether the step decomposition is wrong or the test was already flaky.
- If the test was flaky (fails non-deterministically), fix the test first
as a prerequisite step, then retry.
- If the step decomposition is wrong, re-decompose from Phase 3.
**Revert policy**: revert immediately on failure. Do not attempt to fix
within the same step — a failing test during refactoring means the step is
not behavior-preserving, and you must find a smaller decomposition.
---
#### Phase 5 — Final Validation
After all steps are complete:
1. **Full test suite**: `bun run test:parallel` — all tests must pass.
2. **Typecheck**: `bun typecheck` — zero errors.
3. **Lint**: `bun oxlint --deny-warnings` — zero errors.
4. **Coverage comparison**: compare post-refactor coverage to baseline.
Coverage MUST NOT decrease (within ±1% for measurement noise).
5. **Complexity comparison** (optional): confirm the refactoring improved
the target metric (e.g. lower cyclomatic complexity).
---
#### Phase 6 — Report
```markdown
# Refactor Report
**Scope**: {module/pattern}
**Target**: {one-sentence goal}
**Steps executed**: {N}
## Before / After
| Metric | Before | After | Delta |
| ---------------- | ------ | ----- | --------- |
| LOC in scope | {N} | {N} | {N} |
| Coverage (lines) | {X}% | {Y}% | {delta} |
| Complexity | {N} | {N} | {delta} |
| Files touched | — | {N} | — |
| Tests | {N} | {N} | 0 changed |
## Steps
| # | Action | Outcome |
| --- | ------------------ | ------------- |
| 1 | Extract `findById` | ✅ tests pass |
| 2 | Extract `create` | ✅ tests pass |
| ... | ... | ... |
## Verification
- `bun run test:parallel`: {N} passed, 0 failed
- `bun typecheck`: ✅
- `bun oxlint --deny-warnings`: ✅
- Coverage delta: {delta}
## Design Deviation
{If the refactoring changes the internal architecture in a way that merits an
ADR, reference the ADR. **Deprecated:** `.artifacts/{slug}/design/adr/{NNNN}-*.md`
→ ADRs now live on the Gitea wiki at page `{slug}/03-adr-{NNNN}-{title}`,
readable via `gitea_wiki__get_page`. Or "None".}
---
**Handoff**: {if > 50 lines or ≥ 5 files → run `core/skills/review-code/SKILL.md`
| otherwise → refactor complete, no review needed}
```
---
#### Phase 7 — Approval
Present the report:
```
Refactor complete: {one-line summary}
- {N} steps executed, all tests pass
- {before} → {after} ({delta} LOC)
- Coverage: {before}% → {after}%
→ {if review needed: "Run code review?" | else: "Refactor complete. Approve?"}
```
@@ -0,0 +1,105 @@
# Source Analysis — Document Templates
> Used by Phase B1 (pipeline mode).
> Read this file when producing the source-analysis document set.
> Each section below is the template for the corresponding numbered file
> under `port-{name}/source-analysis/` (wiki pages via `wiki 读写 API(见 TERMINOLOGY`).
## 01-source-overview.md
```markdown
## Source Overview
- **Project**: {name}
- **Language / Runtime**: {e.g. Python 3.11}
- **Framework**: {e.g. FastAPI}
- **Feature scope**: {description of what's being ported}
- **Source files**: {N}
- **Source LOC**: {L}
- **Source modules**: {list of distinct functional areas}
```
## 02-public-api.md
Document every public interface:
```markdown
## Public API
| Method / Endpoint | Input Schema | Output Schema | Errors | Notes |
| ----------------- | -------------- | --------------- | --------------------- | --------------- |
| POST /auth/login | `{email, pw}` | `{token, user}` | 400, 401, 429, 500 | Rate limited |
| GET /users/:id | path param | `User` object | 401, 403, 404 | Auth required |
```
## 03-data-model.md
```markdown
## Data Model
### Entity: User
| Field | Type | Constraints |
| ----------- | ---------- | ------------------- |
| id | UUID | PK, not null |
| email | string | unique, not null |
| password | string | hashed, not null |
| created_at | datetime | not null |
### Relationships
- User 1—N Session
- User N—M Role
```
## 04-business-logic.md
Capture every business rule, validation, edge case, and state transition
from the source. Write each rule as an executable assertion:
```markdown
## Business Logic
### Login
- RULE-01: Valid credentials → return JWT + user object
- RULE-02: Invalid password → 401 "Invalid credentials"
- RULE-03: Non-existent email → 401 "Invalid credentials" (same message, no enumeration)
- RULE-04: 5 failed attempts in 1 min → 429 + lock for 15 min
- RULE-05: Locked account + valid password → 423 "Account locked"
### Edge Cases
- Empty email → 400 "Email is required"
- Email > 254 chars → 400 "Email too long"
- Password < 8 chars → 400 "Password too short"
```
## 05-error-handling.md
```markdown
## Error Handling
| Error Code | HTTP Status | Message | Source Condition |
| ---------- | ----------- | -------------------- | --------------------- |
| AUTH_001 | 400 | Email is required | empty email |
| AUTH_002 | 401 | Invalid credentials | wrong email or pw |
| AUTH_003 | 429 | Too many attempts | rate limit exceeded |
| AUTH_004 | 423 | Account locked | locked out |
```
## 10-fidelity-baseline.md
This is the master inventory used by `verify` at the end. Every source
behavior is listed as a checkable item:
```markdown
## Fidelity Baseline
| ID | Behavior | Type | Source Test | Chunk |
| ---------- | ----------------------------------- | ------------ | -------------------- | ------------ |
| FID-001 | Login with valid credentials | happy path | test_login_ok | chunk-auth |
| FID-002 | Login with invalid password | error path | test_login_bad_pw | chunk-auth |
| FID-003 | Login with empty email | edge case | test_login_empty | chunk-auth |
| FID-004 | Rate limiting after 5 attempts | error path | test_rate_limit | chunk-auth |
```
Each `FID-*` item maps to a `Chunk` column — this drives the DAG node
decomposition. Behaviors in the same chunk are ported together. The
`Source Test` column traces back to the original test for the verify stage.
@@ -0,0 +1,301 @@
# Source Analysis & Review — Detailed Processes
> Extracted from `implement/SKILL.md` (Mode: port) Phase A1, A1.5, A1.7, A1.8.
> Read this file when executing the Source Analysis phases in standalone mode.
---
## Phase A1 — Understand Source
Read the source feature thoroughly — you must understand it well enough to
reimplement it from scratch in a different tech stack.
### Process
1. **Source code** — read every file in the source scope. Understand:
- Public API (method signatures, request/response schemas).
- Data model (entities, fields, relationships).
- Business logic (validation, business rules, edge cases).
- Error handling (exception types, error codes, error messages).
- Configuration (environment variables, feature flags, constants).
2. **Source tests** — read all tests for the source feature. Tests are the
authoritative specification of behavior. Pay attention to:
- Happy path assertions.
- Edge case and boundary condition tests.
- Error path tests.
- Mock/stub setup (external dependencies).
After reading all source tests, run an automated extraction to seed the
fidelity baseline:
1. **For each test file**, extract every test case name (e.g. `describe`/`it`
blocks, function names in test files).
2. **Generate a raw FID list** — one `FID-*` entry per test case:
| FID-* | Test Name | Source File:Line | Type |
| ----- | --------- | ---------------- | ---- |
3. **Do NOT skip** — every test case becomes a FID item. Missing FID items
are the #1 cause of incomplete porting.
4. **Save** the raw FID list to wiki page `port-{name}/source-analysis/fid-raw` (via `wiki 读写 API(见 TERMINOLOGY`).
In pipeline mode (Phase B1), this raw FID list feeds into `10-fidelity-baseline.md`.
3. **Source dependencies** — list every library, service, and infrastructure
the source feature depends on:
- Language runtime and version.
- Framework (web framework, ORM, etc.).
- Libraries (auth, logging, data parsing, etc.).
- Infrastructure (database, cache, message queue, file storage).
- External services (APIs, SaaS).
4. **Source Function Inventory** — produce a function-level catalog of every
public API, private helper, and configuration constant in the source scope.
This is the completeness audit trail — every unported function is visible.
### Output Templates
#### Source Analysis Document
```markdown
## Source Analysis: {source feature name}
### Public API
| Endpoint / Method | Input | Output | Error Cases |
| ----------------- | ----- | ------ | ----------- |
| ... | ... | ... | ... |
### Data Model
| Entity | Fields | Relations |
| ------ | ------ | --------- |
| ... | ... | ... |
### Dependencies
| Dep | Purpose | Available in Target? |
| ------ | --------- | ------------------------ |
| {name} | {purpose} | {yes / no / alternative} |
```
#### Source Function Inventory
```markdown
### Source Function Inventory
| Source File | Function / Symbol | Line | Type (public/private/config) | Ported? | Target Location |
| ----------- | ----------------- | ---- | ---------------------------- | ------- | --------------- |
| ... | ... | ... | ... | ☐ | |
```
After Phase A5 (or at the end), require the Developer to backfill the "Ported?"
and "Target Location" columns. Add a note: "Any ☐ remaining in the 'Ported?'
column is a port gap."
---
## Phase A1.5 — Source Analysis Review (GATE)
Before proceeding to concept mapping, a reviewer (Explorer sub-agent) MUST
cross-check the source analysis deliverables against the original source files.
This is a lightweight but mandatory gate — source misunderstandings are the #1
root cause of incomplete porting.
### Review Process
1. **Spawn a reviewer** (Explorer sub-agent) with access to:
- All source files in the original project (or their copies if offline).
- All A1 deliverables: Source Analysis doc, Source Function Inventory,
`fid-raw.md`.
2. **Reviewer checks (4 dimensions)**:
- **SRC-CMP** (Completeness — **automated, not manual**): Does the Source
Function Inventory list every public/private function, symbol, and config
constant found in source files? This dimension MUST be verified by an
automated symbol diff (see step 2.5), not by the reviewer reading source
files one-by-one. Manual "looks complete" judgments are the dominant
failure mode for port completeness — they are the reason functions get
silently dropped. Any symbol present in source but absent from the
inventory is a BLOCKER gap.
- **SRC-API** (API Accuracy): Does the Public API table correctly capture
every endpoint/method, its input/output schema, and all documented error
cases? Compare against source route/method definitions and error handling
code.
- **SRC-TST** (Test Coverage): Does `fid-raw.md` contain one FID entry for
every `describe`/`it`/`test` block in the source test files? Any test case
without a FID is a gap. Does every FID reference the correct source
file:line?
- **SRC-DEP** (Dependency Accuracy): Are all libraries, infrastructure
services, and external APIs the source depends on listed? Check source
package manager files (`package.json`, `Cargo.toml`, `requirements.txt`,
etc.) and imports.
2.5. **SRC-CMP automated symbol verification (mandatory)** — Enumerate every
symbol the source actually exports, then diff against the Source Function
Inventory. This converts "is the inventory complete?" from a subjective
judgment into an objective set difference. **Do NOT skip even if codegraph
is unavailable** — fall back to `grep`, never to a manual glance.
```bash
# Preferred: codegraph symbol enumeration (one call per source file in scope)
codegraph_node --symbolsOnly <source-file>
# Fallback: grep for declarations in the source language
grep -rEn '^\s*(export (async )?(function|const|class|interface|type|enum)|export \{|def |class |fn |public )' <source-dir>
# Then diff the enumerated source-symbol set against the inventory's
# "Function / Symbol" column. Every source-only symbol is a BLOCKER.
```
Record the command used and the resulting symbol-set delta under dimension
`SRC-CMP` in `source-analysis-review.md`. A review that omits this
automated delta is itself a BLOCKER — the gate was bypassed, not passed.
3. **Output**: Reviewer writes findings to wiki page `port-{name}/source-analysis/review` (via `wiki 读写 API(见 TERMINOLOGY`) with format:
| Dimension | Finding | Severity (BLOCKER/MAJOR/MINOR) | Source Evidence |
| --------- | ------- | ------------------------------ | --------------- |
| SRC-CMP | Missing function `validateSession` in `auth/middleware.js:45` | MAJOR | Source file line 45 |
4. **GATE**: All BLOCKER findings MUST be resolved (add missing items to
inventory/fid list) before proceeding to Phase A2. MAJOR findings require
documented justification if deferred.
5. **Pass condition**: Developer prints:
`SOURCE ANALYSIS REVIEW COMPLETE — {N} BLOCKER items fixed, {M} MAJOR items documented`
---
## Phase A1.7 — Target Surface Analysis
> **Root cause addressed**: Ports fail when the target project's receiving
> surface is not analyzed. The Developer knows the source inside-out but has
> no systematic picture of what the target already has, what it lacks, and
> what structural changes are needed to receive the port. This phase closes
> that gap.
Analyze the **target project's current state** to establish the receiving
surface for the port. This is the mirror image of Phase A1 — instead of
understanding the source, you understand the target.
### Process
1. **Target directory tree** — map the target project's package structure,
especially the packages that will receive ported code or that the source
feature depends on. For monorepos, list every package and its role.
2. **Target existing capabilities** — identify what the target project
already has that overlaps with or relates to the source feature:
- Existing modules, components, services in the same domain.
- Existing routes, providers, context hierarchy.
- Existing schemas, migrations, config entries.
- Existing CLI commands, flags.
- Existing theme/style files.
3. **Automated structural diff** — run a source vs target comparison across
multiple dimensions to surface gaps that manual reading misses:
```bash
# Directory structure diff (source feature scope vs target equivalent)
diff <(cd /source && find packages/app/src -name '*.tsx' | sort) \
<(cd /target && find packages/app/src -name '*.tsx' | sort)
# Dependency diff (package.json)
diff <(jq '.dependencies | keys' /source/packages/app/package.json) \
<(jq '.dependencies | keys' /target/packages/app/package.json)
# Export symbol diff (if codegraph is available)
diff <(codegraph exports @source-ai/app) \
<(codegraph exports @target-ai/app)
```
If `codegraph` is not available, use `grep` for exported symbols or
`glob` for file presence. The goal is **systematic, not manual** —
never rely on reading files one by one to discover what the target has.
4. **Integration point identification** — where in the target project will
the ported code connect?
- Route table changes (new routes, modified redirects).
- Provider/context hierarchy changes (new providers, insertion points).
- Schema/migration additions (new tables, new columns).
- Config/settings additions (new config entries, new setting keys).
- CLI command additions or flag additions.
- Build config changes (vite/webpack/tsconfig).
- Package.json dependency additions.
5. **Target readiness assessment** — does the target need structural
refactoring before it can receive the port?
- Does the target need a new package? (e.g. a new `packages/timeline/`)
- Does the target need an interface extraction? (e.g. extract
`ServerService` to an interface before porting a new implementation)
- Does the target need a migration to add tables/columns?
- Does the target need config schema changes?
### Output
Publish to wiki page `port-{name}/source-analysis/target-surface` (via `wiki 读写 API(见 TERMINOLOGY`) using the format
in `reference/target-surface-template.md` (read it when executing this phase).
---
## Phase A1.8 — Capability Boundary Definition (GATE)
> **Root cause addressed**: Ports fail because the porting unit is "files"
> rather than "capabilities". A single capability (e.g. "draft/tab system")
> spans code files, type definitions, schemas, config, routes, providers,
> themes, and tests. When the Developer ports only the files they see and
> misses the implicit artifacts, the port is incomplete. This phase enforces
> a complete artifact inventory per capability before any implementation.
Define the **complete boundary** of the capability being ported. A capability
is not a file — it is the full set of artifacts required for the feature to
function in the target project.
### Artifact Dimensions
Every capability MUST be analyzed across ALL 13 dimensions listed in
`reference/capability-boundary-template.md` (read it when executing this
phase). The 13 dimensions are: source code files, type definitions/interfaces,
database schema/migrations, configuration entries, environment variables, CLI
flags/commands, theme/style files, route definitions, provider/context
hierarchy, build config changes, package.json dependencies, test files, and
shared package changes. A dimension with no artifacts is explicitly marked
"N/A — none required" (not silently skipped).
### Process
1. **For each dimension**, list every artifact:
- **Source has**: what exists in the source project for this dimension.
- **Target already has**: what the target project already has (from
Phase A1.7 Target Surface Analysis).
- **Needs creation / modification**: what must be created or changed in
the target.
- **Status**: ☐ not ported / ☑ ported / ⏭ N/A (none required)
2. **Cross-reference with Phase A1 Source Function Inventory** — every
function/symbol in the inventory MUST appear in dimension 1 (source code
files) or dimension 2 (type definitions). Any orphan is a gap.
3. **Cross-reference with Phase A1.7 Target Surface Analysis** — every
"Gap" in the structural diff table MUST have a corresponding entry in
the capability boundary. Any orphan is a gap.
4. **GATE**: All 13 dimensions MUST be filled in. A dimension with artifacts
marked "☐ not ported" is acceptable ONLY if there is a documented deferral
with a reactivation path (same rules as Phase A3 Gap Analysis). Dimensions
that are "N/A — none required" must include a one-line justification.
### Output
Publish to wiki page `port-{name}/source-analysis/capability-boundary` (via `wiki 读写 API(见 TERMINOLOGY`) using the format in
`reference/capability-boundary-template.md` (includes the full 13-dimension
table and output template).
### Pass condition
Developer prints:
`CAPABILITY BOUNDARY COMPLETE — {N}/{13} dimensions have artifacts, {M} dimensions N/A, {K} items deferred with reactivation path`
**Do NOT proceed to Phase A2 until this gate passes.** The capability boundary
is the single source of truth for "what must be ported" — every downstream
phase references it.
@@ -0,0 +1,52 @@
# Target Surface Analysis — Output Template
> Used by Phase A1.7 (standalone) and Phase B1.7 (pipeline).
> Read this file when executing the Target Surface Analysis phase, then
> produce the output document following this format.
Publish the output to wiki page `port-{name}/source-analysis/target-surface`
(standalone) or `port-{name}/source-analysis/11-target-surface`
(pipeline) via `wiki 读写 API(见 TERMINOLOGY`.
```markdown
## Target Surface Analysis
### Target Project Structure
- Package map (package name → role)
- Relevant directory trees
### Existing Capabilities (overlapping with source)
| Target Module | Overlap with Source | Action (reuse / replace / extend) |
| ------------- | ------------------- | --------------------------------- |
| ... | ... | ... |
### Structural Diff Summary
| Dimension | Source has | Target has | Gap |
| --------------- | ---------- | ---------- | --- |
| Files (.tsx) | {N} files | {M} files | {N-M} new |
| Dependencies | {list} | {list} | {diff} |
| Export symbols | {list} | {list} | {diff} |
| Routes | {list} | {list} | {diff} |
| Providers | {list} | {list} | {diff} |
| Schemas | {list} | {list} | {diff} |
| CLI commands | {list} | {list} | {diff} |
| Theme files | {list} | {list} | {diff} |
| Config entries | {list} | {list} | {diff} |
| Env vars | {list} | {list} | {diff} |
| Build config | {list} | {list} | {diff} |
### Integration Points
| Integration Point | Change Required | Affected Target Files |
| ----------------- | --------------- | --------------------- |
| Route table | Add /new-session route | src/app.tsx |
| Provider hierarchy | Insert TabsProvider | src/app.tsx |
| ... | ... | ... |
### Target Readiness
| Readiness Item | Required? | Complexity | Blocking? |
| -------------- | --------- | ---------- | --------- |
| New package | No | — | No |
| Interface extraction | Yes | Medium | Yes |
| Migration | Yes | Low | Yes |
| Config schema | No | — | No |
```
@@ -0,0 +1,123 @@
> Extracted from implement/SKILL.md (Pipeline Work Item Detection) — moved verbatim 2026-08-25, ticket [org-internal #3381].
## Pipeline Work Item Detection
Not all work items in an iteration plan involve writing new code. Some require
fixing bugs, restructuring existing code, porting features, or building
frontend UI. These specialized work items use different execution workflows
(defined above) but flow through the same pipeline gates (review-code →
verify; DAG-routed work resolves its spec from `{epic-slug}/dag`).
### Refactoring Work Items
A work item is a refactoring work item when:
- Its description starts with "Refactor", "重构", "Restructure", "Extract",
"Inline", "Move", "Rename", "Simplify", "Upgrade", or "Remove dead code".
- It is explicitly tagged `[REFACTOR]` in the node ticket / request.
- The node spec identifies it as a structural change that preserves
behavior (no new capabilities, no bug fixes).
- Requirements coverage is a refactoring requirement (REQ-REFACTOR-*).
When a work item is a refactoring work item, follow the workflow defined in
Mode: refactor above with these adaptations:
1. **Scope & Baseline** (refactor Phase 1): The scope is the node spec /
work-item description, not free-form user input.
2. **Define Target Pattern** (refactor Phase 2): The target pattern must align
with the baseline's architecture decisions (node spec + contracts). If the
baseline does not prescribe a pattern, justify the choice in the refactor
report.
3. **Decompose into Steps** (refactor Phase 3): Present steps to the user for
approval per the refactor workflow. If the design document decides the target
pattern, the steps are not negotiable — they are derived from that decision.
4. **Incremental Execution** (refactor Phase 4): Same as standalone refactor.
Commit each step separately.
5. **Final Validation** (refactor Phase 5): Run `bun run test:parallel`, `bun typecheck`,
`bun oxlint --deny-warnings`. Coverage must not decrease.
6. **Produce the implementation report** using the standard Phase 5 (Report)
from ### Mode: implement (default). Embed the refactor report as the
report body. The file change table and acceptance criteria table follow the
standard format so the handoff to code review is seamless.
After the refactoring is complete, proceed to the standard Phase 5 (Report)
and Phase 6 (Handoff to Code Review) exactly as a standard implementation
work item would. The code review gate is mandatory for all refactoring work
items regardless of size — there is no "no review needed" bypass.
### Bugfix Work Items
A work item is a bugfix work item when:
- Its description starts with "Bugfix", "Fix", "修复", "Bug", or "Hotfix".
- It is explicitly tagged `[BUGFIX]` in the node ticket / request.
- The node spec identifies it as a correction of existing behavior
(no new capabilities).
- Its description references a bug report, stack trace, or root cause analysis
from the bugfix workflow's Phases 12.
When a work item is a bugfix work item, follow the workflow defined in
Mode: bugfix above with these adaptations:
1. **Understand & Reproduce** (bugfix Phase 1): The scope is the work item
description and the bug reproduction steps captured in requirements.
2. **Isolate Root Cause** (bugfix Phase 2): The root cause may already be
documented in the requirements; verify it against the current codebase.
If the root cause differs, flag a design gap and abort.
3. **Write Regression Test** (bugfix Phase 3): Before fixing, write a test
that fails with the bug's symptom. The test must exercise the exact
condition described in the acceptance criteria.
4. **Fix** (bugfix Phase 4): Apply the minimal surgical fix. The fix MUST
match the design document's component and interface decisions.
5. **Self-Check & Report** (bugfix Phase 5): Run `bun run test:changed`, `bun typecheck`,
`bun oxlint --deny-warnings`. Verify every item in `core/checklists/bugfix.md`.
6. **Produce the implementation report** using the standard Phase 5 (Report)
from ### Mode: implement (default). Embed the bugfix report as the report
body. The file change table and acceptance criteria table follow the
standard format so the handoff to code review is seamless.
After the bugfix is complete, proceed to the standard Phase 5 (Report) and
Phase 6 (Handoff to Code Review) exactly as a standard implementation work
item would. The code review gate is mandatory for all bugfix work items
regardless of size — there is no "no review needed" bypass for pipeline
bugfixes.
### Frontend Work Items
A work item is a frontend work item when:
- Its description starts with "Frontend", "UI", "Component", "Page", "Style",
"前端", "UI", "组件", "页面", or "样式".
- It is explicitly tagged `[FRONTEND]` in the node ticket / request.
- The node spec identifies it as a UI-layer change.
- The work item's component mapping (node `req_refs` + component field in
`{epic-slug}/dag`; historically `{slug}/03-design-08-traceability`) shows
components in `components/`, `pages/`, `views/`, `ui/`, or frontend
framework directories.
- The work item involves `.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`, `.css`,
or `.scss` files exclusively (no backend or data-layer files).
When a work item is a frontend work item, follow the workflow defined in
`core/skills/frontend/SKILL.md` with these adaptations:
1. **Parse Context** (frontend Phase 1): The scope is the work item
description, the design sections it references, and the project's
framework/styling conventions. Read neighboring frontend files to absorb
patterns before writing code.
2. **Plan UI Implementation** (frontend Phase 2): Produce a brief
implementation plan covering component structure, props, state variants
(loading/empty/error/edge), and accessibility requirements. Cross-
reference every design specification.
3. **Implement** (frontend Phase 3): Implement in layers — structure → style
→ state variants → interactivity → accessibility pass. Every component
MUST render gracefully in all states.
4. **Self-Check** (frontend Phase 4): Run `bun typecheck`, `bun oxlint --deny-warnings`,
`bun run test:changed`. Verify every item in `core/checklists/frontend.md`.
5. **Produce the implementation report** using the standard Phase 5 (Report)
from ### Mode: implement (default). Embed the frontend report as the report
body. Include state coverage and accessibility verification tables. The file
change table and acceptance criteria table follow the standard format so the
handoff to code review is seamless.
After the frontend implementation is complete, proceed to the standard Phase 5
(Report) and Phase 6 (Handoff to Code Review) exactly as a standard
implementation work item would. The code review gate is mandatory for all
frontend work items regardless of size — there is no "no review needed" bypass
for pipeline frontend work.
+203
View File
@@ -0,0 +1,203 @@
---
name: land-batch
description: >
Use ONLY when the orchestrator lands the finished task branches of one DAG
iteration as a single batch PR ([org-internal #3731] batch-landing pilot). Composes
path-disjoint member branches via the deterministic batch-compose script,
pre-validates locally, opens ONE PR for N tasks (one CI fan-out instead of
N on the single-runner pool), pastes each member's review synthesis into
the PR conversation, runs one delta-scoped review-code on the batch head,
then hands the labeled PR to the merge coordinator. NOT for hotfixes /
CI-infra fixes (fast lane: 1:1 PR), single tasks, or single-session
mono-branch batches (topology A — one session owns every ticket on one
branch from inception; hand-compose those, see Applicability).
stage: merge-pr
triggers:
- land batch
- batch land
- batch-compose
- 批量归并
- 批量提交 pr
role: Producer
---
# land-batch — batch PR composition for one DAG iteration
**Orchestrator-only.** The composing session is the epic's orchestrator
(single writer at iteration close — the same role that already owns uniform
merge/close). `workflow/batch/*` branches are NOT claim branches: no
`claim.sh` interaction, no assignee; the orchestrator is the only composer.
Pilot charter: the decision comment on [org-internal #3731] (scope, opt-in protocol, cap,
success criteria) — this skill implements that charter, not the epic's
frozen DAG v1 (which covers only the merge-coordinator nodes N-01..04).
## Applicability: post-hoc composition only (topology B)
Two batch shapes exist ([org-internal #3731] charter addendum, comment 39347):
- **Topology A — single-session mono-branch batch**: one session owns every
ticket from inception and writes them on ONE branch (instance: PR [org-internal #3861],
audit-h1). Hand composition is natural there — do NOT force this skill on
it; record the outcome on [org-internal #3731] as a manual-batch datum instead.
- **Topology B — parallel workers, post-hoc composition**: workers finish on
SEPARATE branches at different times; the orchestrator composes at
iteration close. This is the skill's ONLY applicability — cross-branch
conflict attribution, lockfile convergence, and composition ordering are
exactly the error-prone parts it mechanizes (path-disjoint admission,
fail-closed attribution, `--convergent` surgery, per-member commit
retention for revert granularity).
Quick discriminator: if every "member" would be a commit on a branch you are
already writing, that is topology A — keep going, skip this skill. If you
are landing OTHER workers' branches you never owned, that is topology B —
this skill applies.
## Preconditions (all mandatory, fail-closed)
1. **Pilot opt-in**: the epic runs in batch-landing mode — its workers were
seeded to report `status=done branch=<ref>` WITHOUT opening PRs (see the
pilot note in `headless-session-ops/reference/completion-report.md`).
Non-pilot epics keep the per-task 1:1 PR flow.
2. **Every member is done**: review-code PASS on its own branch, run bundle
archived (`gen-run-bundle.ts --slug <slug> --ticket <N>` — the bundle is
committed on the member branch, so composition carries it; the
merge-gate's presence check is per close-ref and expects it).
3. **2..7 members** — a single done task rides its own 1:1 PR; more than 7
(`batch-compose --max-members`, the pilot cap) splits into multiple
batches along the path-overlap report.
4. **No fast-lane cargo**: prod hotfixes and CI-infra fixes bypass batching
entirely (1:1 PR, land immediately) — batching latency must never gate
urgent work.
## Procedure
### 1. Compose (deterministic core)
```bash
bun .gitea/scripts/batch-compose.ts \
--branch workflow/task/<a> --branch workflow/task/<b> … \
--out workflow/batch/<epic-slug>-iter<N> --dry-run
```
For batches whose members ALL rewrite a regenerable shared file — the
rule, not the exception, for dependency-bump batches (every `bun install`
rewrites the root `bun.lock`) — pass `--convergent bun.lock`: the file is
exempt from the overlap admission, and a merge that conflicts ONLY on it is
surgically resolved to the running head's version (dropped from the index
outright when the head no longer carries the path), with the JSON
reporting `converged: ["bun.lock"]` and a regeneration hint. A conflict
report carrying shapes the tool cannot attribute (rename/delete etc.)
fails CLOSED — convergent exemption never widens into a blind pass.
Exit codes / handling: `1 usage / no-changes-over-base` → drop the named
member or fix the invocation; `3 path-overlap` → split the batch along the
reported pairs (or land the overlapping member 1:1) and re-run per group;
`4 merge conflict` / `5 transport/git error` → fall
back to 1:1 for this batch; `0` → drop `--dry-run`
and push the composed branch. The script never touches the worktree or
index (keeper-style plumbing) and each member keeps its original commits —
revert granularity stays per-task.
### 2. Pre-validate locally (never enter CI red)
```bash
git fetch origin workflow/batch/<slug>
git switch --detach origin/workflow/batch/<slug>
cd packages/octopus && bun run test:changed
cd ../.. && git switch - # back to the orchestrator branch
```
If the compose output carried a `regenerate convergent files (…)` hint —
regenerate them FIRST, on the detached batch branch, and push the result
(the composed branch deliberately carries a stale convergent file: either
the surgically-resolved running-head version, or a member's clean-merged
one; either way it is deterministic only after regeneration):
```bash
git switch --detach origin/workflow/batch/<slug>
bun install # regenerates bun.lock
git add bun.lock # NEVER `commit -am` here: -a stages the
# worktree's symlink stubs and trips the
# pre-commit symlink guard (core.symlinks=false)
git commit -m "[<epic-slug>][iter-N] chore(deps): regenerate bun.lock for batch <slug>"
git push origin HEAD:refs/heads/workflow/batch/<slug>
```
(On detached HEAD the commit-msg hook's format check is inert — the
`[slug][iter-N]` prefix above keeps the corpus contract anyway.)
Failure → fix on the offending member branch (it is the only place the
fault can live — paths are disjoint), re-compose, re-validate. Do NOT push
a known-red batch.
### 3. Open the batch PR (gitea-rest recipes)
Title: `[<epic-slug>][iter-N] batch: <task-a> / <task-b> / …`
Body constraints (each is a merge-gate requirement, not style):
- **English close keywords, one per member issue**: `Closes #A`, `Closes #B`,
… — Gitea auto-closes all of them; Chinese 关闭/修复 never auto-close.
- **Zero unchecked top-level `- [ ]` lines in the body** — the gate's regex
(`^-\s+\[ \]`) blocks on any unindented unchecked box, with no fence
stripping. Render deferred/pending items as
plain text (`· deferred: …`), never as checkboxes; the per-task live
checklists stay on their issues.
- Per-member summary (ticket, branch, one line) + bundle index.
### 4. Paste member syntheses into the PR conversation
Post one comment per member carrying its review-code verdict block
(`**Verdict**: PASS|WARN` + the fenced JSON synthesis, from the member
issue's review thread). **Why**: the merge-gate hunts FAIL verdicts only in
the PR's own comment stream — a batch PR without these comments passes
vacuously (a fake-green hole). Pasting restores FAIL visibility; a member
synthesis of FAIL blocks the gate exactly as a 1:1 PR's would.
### 5. One review-code pass on the batch head
Run review-code against the batch PR. Members already PASS'd individually —
scope the review delta to what composition added (merge seams, cross-member
integration the per-task reviews could not see). This posts the required
per-sha `pipeline/review-code` status; member statuses do NOT transfer to
the composed head.
### 6. Label and hand off
Label the PR `ready-to-merge`. From here the flow is stock: the merge
coordinator's double-green queue serially merges (CI/check +
pipeline/merge-gate on the batch head, bot approve, main-account merge).
Do NOT hand-merge, do NOT hand-sync main into the batch branch.
### 7. Post-merge expectations
Member issues auto-close via the English keywords; their `## 当前状态`
PR/代码评审/CI rows fan out to EVERY close ref (status-sync poller Wide
behavior — requires [org-internal #3867], merged 2026-08-31). `task done ≡ PR merged`
still holds, transitively
through the batch PR; verify milestones fire unchanged.
## Fallback matrix
| Failure | Action |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| path-overlap (exit 3) | split batch along overlap pairs / 1:1 for the odd member |
| merge conflict (exit 4) | 1:1 for the whole batch |
| local pre-validation red | fix on the offending member, re-compose |
| batch CI red after mergeable | coordinator dequeues with trail; fix on member branches, re-compose as a new batch PR |
Any fallback preserves today's behavior — batch landing is an optimization,
never a dependency.
## Measurement (pilot duty — revised per [org-internal #3731] charter addendum 39347)
Per-iteration attribution only; the global 7-day ratio is trend context,
never a pass/fail judge (same-window confounders: test-slim [org-internal #3855] /
test-dedupe [org-internal #3854] / the pilot's own PRs). On the server,
`bun .gitea/scripts/ci-run-stats.ts --days 7 --json /tmp/<slug>-ci-stats.json`
(the `--json` flag takes a filename — bare, it is a silent no-op; the human
summary still prints). Same methodology as the checklist-merge-gate.yml
2026-08-27 note. Record on [org-internal #3731]: (a) this batch iteration's per-workflow
trigger counts vs a historically similar 1:1 iteration, and (b) one
fallback drill (construct a path-overlap group → exit-3 split executed).
Baseline, trend only: 2026-08-31, 5,081 runs / TOTAL 2.75x single runner.
+132
View File
@@ -0,0 +1,132 @@
---
name: project-kickoff
description: >
Use ONLY when the user asks about project setup, kickoff, or project
initialization ("kickoff", "project setup", "椤圭洰鍒濆鍖?, "椤圭洰閰嶇疆").
Provides guidance on the octopus project kickoff workflow: checking readiness,
running /kickoff or octopus kickoff, configuring codegraph, and understanding
project scaffolding. Do not use for application-level configuration 鈥?
this skill covers the octopus project lifecycle, not user application code.
triggers:
# High-discrimination phrases only ([org-internal #4525]): bare "setup"/"init"/"閰嶇疆" fire
# on any configuration mention, and the skill gate folds the previous
# assistant turn on approval turns 鈥?one stray "閰嶇疆" in a reply re-fires
# the gate every turn. Keep triggers to phrases that name THIS workflow;
# generic setup/init questions still route via the description in the
# skills listing.
- kickoff
- project setup
- 椤圭洰鍒濆鍖?
- 椤圭洰閰嶇疆
role: Producer
---
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
# Project Kickoff
## 1. Overview
Project kickoff is the octopus project initialization workflow that gets a project from zero to fully configured in one command. It covers readiness detection 鈫?setup 鈫?scaffold 鈫?config 鈫?codegraph init 鈫?skills overlay pull, producing a project that is ready for the full SDLC pipeline without manual configuration steps.
## 2. When This Skill Activates
This skill activates when the user's text matches any of the trigger keywords:
- `kickoff` 鈥?asking about the `/kickoff` command or the kickoff workflow
- `project setup` 鈥?asking how to set up a project in octopus
- `椤圭洰鍒濆鍖朻 / `椤圭洰閰嶇疆` 鈥?asking how to initialize or configure an octopus project (Chinese)
Generic "setup"/"init"/"閰嶇疆" mentions are deliberately NOT triggers ([org-internal #4525]): they collide with everyday configuration talk, and the hard skill gate folds the previous assistant turn into its match text on approval turns 鈥?a passing "鎴栭厤缃?x: false" in a reply would re-fire the gate every turn. `/init` note: `/init` generates AGENTS.md via LLM; kickoff uses the managed template from `octopus setup` 鈥?the Builder distinguishes based on context.
Typical user intents include: encountering a readiness prompt at session start, wondering how to initialize a project, or wanting to understand the kickoff workflow steps.
## 3. Prerequisites
Before kickoff can run:
- `codegraph` CLI must be installed and on `$PATH` (covered by the `codegraph-setup` skill)
- Global octopus config (`<config-home>/octopus/octopus.jsonc`) must be merged and valid
- The project root must be identified (the directory octopus is running in)
### CI Readiness
After kickoff completes, check for CI configuration:
```bash
ls .gitea/workflows/ .github/workflows/ 2>/dev/null || echo "[GAP: no CI workflows]"
```
If no workflows exist, recommend creating `.gitea/workflows/ci.yml` (this repo
uses Gitea Actions) 鈥?or `.github/workflows/ci.yml` for a GitHub-hosted project
鈥?with at minimum: typecheck, lint, and test gates. Without CI, every merge is a
manual gate 鈥?type errors, lint regressions, and test failures can land on
main and go undetected until the next local checkout.
## 4. Workflow
The kickoff workflow runs in four automated steps:
1. **Check readiness** 鈥?Determine if the project needs setup. Run `checkReadinessWithGitea()` or `octopus kickoff --check-only` to inspect the current state.
2. **Run kickoff** 鈥?Execute `/kickoff` from the TUI or `octopus kickoff` from the terminal.
3. **What happens** 鈥?A Phase 0 gate, then five sequential phases:
- Phase 0 鈥?**Gitea auth check**: derives the `gitea` block from the git remote origin (persisting missing fields), then verifies the token (`GET /user`) and runs the auth wizard on failure; never aborts kickoff
- [1/5] **Setup** 鈥?Creates project directories and writes `AGENTS.md` from the managed template
- [2/5] **Scaffold** 鈥?Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); domain skills content arrives via the [5/5] overlay pull
- [3/5] **Config stub + MCP wiring** 鈥?Writes the `mcp.codegraph` block into `octopus.jsonc`
- [4/5] **Codegraph init** 鈥?Initializes and builds the per-project `.codegraph/` index
- [5/5] **Skills remote + overlay pull** 鈥?Opt-in semantics ([org-internal #4370]); branches by flags when no `overlay.remotes` entry is configured:
- no flags (interactive run) 鈥?**safe default**: nothing is written; a remediation hint is printed (pass `--skills-remote`, or edit `overlay.remotes`), and the readiness skills item stays not-ready
- `--yes` 鈥?legacy behavior kept: writes the default skills remote, then pulls domain skills content (`octopus skills pull` equivalent)
- `--skills-remote <owner/repo>` (+ optional `--skills-remote-base-url <url>`) 鈥?writes the specified remote instead (ref `main`; base URL defaults to `<instance-base-url>`), then pulls from it
- `overlay.remotes` already configured 鈥?the write is skipped (idempotent; an explicit `--skills-remote` is ignored with a warning)
4. **Post-kickoff verification** 鈥?Run `octopus kickoff --check-only` again. After a `--yes` or `--skills-remote` run it reports all 5 items as ready; after a no-flag interactive run it reports 4 of 5 鈥?the skills item stays not-ready until a remote is configured (`--skills-remote`, `--yes`, or a manual `overlay.remotes` edit + `octopus skills pull`).
### First-ticket hint ([org-internal #3061])
For a brand-new project still validating its concept, open the FIRST ticket as
`Kind/MVP` 鈥?the interactive no-pipeline mode (decision log + debt register in
the ticket body). It graduates by relabeling to `Kind/Feature` (analyze-dag
backfill mode picks up the decision log) when a second session, an external
contract, or a second consumer of a shared type appears. Do not route early
concept work into the full pipeline "to be safe" 鈥?the pipeline's coordination
machinery is cost, not quality, at zero coordination.
## 5. Re-kickoff / Idempotency
Re-running kickoff on an already-configured project is safe 鈥?every step is idempotent:
- **Setup** skips existing directories
- **Scaffold** skips existing files
- **Config mutation** is a no-op if the `mcp.codegraph` block already matches
- **Codegraph** re-indexes (rebuilds the graph, which is a safe refresh)
- **Skills remote** ([5/5]) skips the write when `overlay.remotes` is already configured
**Idempotent silence** (REQ-F-005): A fully configured project produces zero startup prompts. Users can run `/kickoff` or `octopus kickoff --check-only` at any time for explicit readiness checks without side effects.
## 6. Troubleshooting
Common issues and their resolutions:
- **`codegraph: command not found`** 鈥?Run the `codegraph-setup` skill, or install via `bun add -g @colbymchenry/codegraph`
- **Permission denied** 鈥?Check project directory ownership and write permissions
- **Partially complete state** 鈥?Re-run `/kickoff`; idempotent steps will complete the missing parts without affecting already-completed steps
- **MCP tools not available** 鈥?Verify `mcp.codegraph` exists in `octopus.jsonc`; re-run `octopus kickoff` to write it
## 7. Related Commands
- `/init` 鈥?Generates `AGENTS.md` using an LLM (custom, context-aware); kickoff uses the managed template from `octopus setup`
- `octopus setup` 鈥?Creates directories + `AGENTS.md` from the managed template; kickoff calls this internally
- `octopus scaffold` 鈥?Creates the `<instance-root>/` skeleton (`core/skills/` directory + default `octopus.jsonc` template if absent); run `octopus skills pull` to fetch domain skills content; kickoff calls this internally
- `octopus kickoff --check-only` 鈥?Readiness check only, no pipeline execution
- `octopus kickoff --skills-remote <owner/repo>` 鈥?Explicit opt-in: writes the given skills remote to `overlay.remotes` (ref `main`) and pulls it instead of the default
- `octopus kickoff --skills-remote-base-url <url>` 鈥?Base URL for `--skills-remote` (requires it); defaults to `<instance-base-url>` when omitted
- `octopus kickoff --yes` 鈥?Skips the opt-in confirmation and keeps the legacy behavior of writing the default skills remote (`Octopus/octopus-skills`)
- `codegraph-setup` skill 鈥?For installing the codegraph CLI and initializing the per-project index
## 8. Reference
- `<harness-package>/src/project-kickoff/readiness.ts` 鈥?`checkReadiness()` implementation (sync 4-item base)
- `<harness-package>/src/project-kickoff/readiness-gitea.ts` 鈥?`checkReadinessWithGitea()` composed 5-item check (adds gitea auth reachable)
- `<harness-package>/src/cli/cmd/kickoff.ts` 鈥?`/kickoff` command handler
- `core/skills/codegraph-setup/SKILL.md` 鈥?CodeGraph setup skill
- Project roadmap at `.artifacts/project-kickoff/` (deprecated 鈥?now stored as wiki page `project-kickoff/roadmap/` via `wiki 璇诲啓 API锛堣 TERMINOLOGY锛塦)
+99
View File
@@ -0,0 +1,99 @@
---
name: prototype
description: >
Use ONLY when the user asks to build a prototype, proof-of-concept, spike, or
exploratory implementation where the code's ultimate fate (keep vs discard)
is not yet decided by the standard pipeline. The Developer (Worker) first
classifies the work as THROWAWAY (spike) or EVOLUTIONARY (high-fidelity
prototype that will be promoted to production), then applies the matching
disposition contract. Do NOT use for standard feature work (use implement),
bug fixes (use implement, Mode: bugfix), or refactoring (use implement, Mode: refactor).
triggers:
- prototype
- spike
- proof of concept
- poc
- 原型
- 高保真原型
- 演进式原型
- 技术探索
- 技术验证
- throwaway
- evolutionary prototype
role: Producer
---
# Prototype / Spike
Classify the work BEFORE writing code, then apply the disposition contract.
The classification is a one-line decision recorded in the artifact header.
## Phase 0 — Disposition Decision (MANDATORY)
Answer this question in one sentence, citing the user's words as evidence:
> **Will this exact code be promoted to production (EVOLUTIONARY), or will it
> be discarded after we learn from it (THROWAWAY)?**
| Signal from user | Disposition |
|---|---|
| "spike", "validate", "prove it works", "throwaway", "探索", "验证" | THROWAWAY |
| "prototype then keep iterating", "evolve", "no rewrite", "MVP from this", "演进", "不重写" | EVOLUTIONARY |
| Conditional evolution: "if it works, we'll keep iterating", "if users like it, we'll build on it" | **ASK the user**: "Do you intend to evolve this exact codebase (EVOLUTIONARY), or rewrite cleanly if validated (THROWAWAY)?" |
| Ambiguous ("let's prototype and see") | **ASK the user.** Do not guess. |
Record the decision in the artifact you produce:
```
@disposition: THROWAWAY | EVOLUTIONARY
@evidence: "<the user's exact phrase that decided it>"
```
If you cannot cite the user's exact phrase, you have not decided — ask.
---
## THROWAWAY contract (spike)
Goal = **learning**, not code. Code is a means to a decision.
- **Time-box**: state a deadline (hours/days) up front. Stop when the box ends, even mid-sentence.
- **Artifacts**:
1. Throwaway code (no tests, no lint, no polish — label `@ai-artifact: spike`)
2. **Learning report** (wiki page `{slug}/spike-report`): what was validated, what failed, decision (go / pivot / stop)
- **Fencing**: spike code MUST NOT be importable from production code. Put it in `spike/` or a scratch worktree; delete it after the report is accepted.
- **Exit gate**: the learning report is the deliverable. Code without a report = incomplete.
- **Terminal artifact**: wiki page `{slug}/spike-report` is consumed by the human decision-maker (go/pivot/stop), not by a downstream skill. This is an intentional pipeline terminal, analogous to `release` and `retrospective` outputs.
## EVOLUTIONARY contract (high-fidelity prototype)
Goal = **seed code that becomes production**. Code is the product.
- **Debt register** (wiki page `{slug}/prototype-debt`): every shortcut is a line item with an owner and a promotion criterion. Example:
| Shortcut | Why | Promotion criterion |
|---|---|---|
| Hardcoded auth token | unblock UI flow | Replace with real auth before any external user |
| No tests on prototype UI | speed | Add source-verification tests before merging to main |
- **Quality floors that apply from day 1**: typecheck passes, no `any` without justification, no `console.log` left in committed code.
- **Quality floors explicitly deferred** (and tracked in the debt register): full test coverage, error states, observability, performance budgets.
- **Promotion gate**: the prototype is "production" only when the debt register is empty OR each remaining item has an explicit waiver from the reviewer. Route through `review-code` with `mode: "prototype-promotion"` before promotion; the debt register is a required review input in that mode. Waived items become `ACCEPTED_RISK` findings in the review report, which `verify` Phase 5.5 registers as `## TD 登记` rows — promoted to `tech-debt` labeled Gitea issues on claim (`.octopus/rules/ticket-lifecycle.md`); the register lives in the issue tracker, not a parallel file.
## Rationalization table
| Excuse | Reality |
|---|---|
| "The user said 'no rewrite', so I'll just skip design forever" | EVOLUTIONARY defers design DETAILS, not design ITSELF. The debt register IS the design surface; promotion requires review-code. |
| "This is just a spike, so no need to record anything" | The learning report IS the spike's deliverable. No report = no learning = wasted spike. |
| "The prototype works, let's ship it and clean up later" | "Later" never comes. The debt register is the cleanup plan; promote only when it's empty or waived. |
| "I'll decide throwaway vs evolutionary after I see how it goes" | Deciding after = throwaway by default (no debt register, no promotion path). If it might evolve, declare EVOLUTIONARY up front. |
| "'If it works, we'll keep iterating' — so EVOLUTIONARY" | "If it works" is conditional. The user has NOT committed to evolving this exact codebase. ASK: evolve-this-code or validate-then-rewrite? |
## Anti-patterns (do not do these)
- ❌ Building a "prototype" with full production quality "just in case" — that's not a prototype, that's slow feature work.
- ❌ Building a "spike" then quietly merging it because it happened to work — that's evolutionary without the debt register.
- ❌ Freezing an evolutionary prototype and rewriting it in `src/` — that's a throwaway; if the user said "no rewrite", honor it.
## References
- `.octopus/checklists/prototype.md` — Prototype self-check checklist
+447
View File
@@ -0,0 +1,447 @@
---
name: release
description: >
Use ONLY when ready to cut a release. The Release Manager (Worker) inspects
project state (git log, version files, build) to determine the semver bump,
generate the changelog, tag, and produce a release artifact. Works on any
project state 鈥?no pipeline artifacts required.
triggers:
- release
- cut release
- 鍙戠増
- publish release
- ship release
role: Producer
---
> Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛屽師 deferHard verbatimDir锛夈€傛満鍒躲€佺粨鏋勪笌 frontmatter 淇濇寔锛涘疄渚嬫湳璇紙宸ュ叿鍚嶃€佽矾寰勩€佸伐鍗曞彿锛夋寜 `core/adapters/TERMINOLOGY.md` 缁戝畾鍒板叿浣撳疄渚嬨€?
# Release
Prepare and tag a release based on project facts 鈥?git history since the last
tag, current version, build status, and dependency health. No dependency on `.artifacts/{slug}/`
(**deprecated** 鈥?SDLC artifacts now live in the Gitea wiki at `{slug}/...`;
see `_shared/gitea-read-patterns.md`) or any pipeline skill outputs.
**This SKILL does not deploy.** Deployment is project-specific (K8s, Docker,
npm publish, etc.) and varies too widely to template. The release artifact
produced here is the input to project-specific deployment workflows.
## Agent Role
The release is owned and executed by the **Release Manager** (Worker).
**Context compaction**: release is a pipeline stage boundary. The main session
compacts at this clean boundary ONLY when a capacity/projection trigger holds,
per `core/rules/compact.md` 搂"Stage-boundary compaction" (long multi-stage
runs 鈥?DAG Epic orchestration 鈥?keep the legacy every-boundary compaction;
short runs 鈥?bugfix / DAG task 鈥?and standalone runs default to NOT
compacting). The Release Manager itself is single-phase and
artifact-driven (version files, changelog, tag); a mid-run compaction loses
nothing 鈥?re-read the release checklist state and git log to resume.
---
## Preconditions
Before starting the release:
- [ ] Working tree is clean (`git status` has no uncommitted changes).
- [ ] `core/checklists/release.md` is accessible.
---
## Phase 1 鈥?Pre-release Gate
Run each check against the live project. Stop and report failures.
1. **Clean workspace**: `git status` must show nothing to commit.
2. **Branch**: confirm the current branch. Default assumption: release from
`main` or `master`. If on another branch, note it.
3. **Build**: run the project's build command. Must pass.
4. **Typecheck + Lint**: run the project's typecheck and lint. Must pass.
5. **Tests**: run the project's test suite. Must pass.
6. **Dependency audit**: run the project's vulnerability scanner
(e.g. `npm audit`, `bun audit`, `pip-audit`, `cargo audit`).
- No new HIGH or CRITICAL CVEs block the release.
- Pre-existing HIGH/CRITICAL CVEs do NOT block but **MUST be filed as
tech-debt** before the release lands. File them in TWO TIERS (batching
rule, [org-internal #3846] 鈥?a single release audit once fanned out to 14 TDs 鈫?14 PRs 鈫?
56-84 CI runs on a saturated runner pool): Tier A below is the general
mechanical-change batching rule; Tier B and the escape hatch follow it.
<a id="phase-1-tier-a"></a>
### Tier A 鈥?閫氱敤鏈烘鍙樻洿骞跺崟瑙勫垯 / Universal mechanical-change batching rule
Origin [org-internal #3846] (audit-batch pilot, generalized in place): the rule covers ANY
single-source fan-out of mechanical changes, not just dependency audits.
Two source types today: (a) dependency bump audits; (b) docs 鎵归噺淇
(batch docs revisions).
#### 瑙勫垯姝f枃 / Rule text
- **Core**: mechanical changes of ONE class fanning out from a single
source are filed as ONE batch issue + ONE batch PR 鈥?never N脳issue +
N脳PR. The batch body keeps one row per item (per-CVE / per-doc mapping),
so per-item traceability is unchanged; work the batch as a single PR.
- **(a) Dependency bump audits** (the [org-internal #3846] origin case): advisories whose
remediation is a plain version bump (lockfile-only diff, no semver-major
jump, no overrides/catalog surgery, no API or adapter fallout) are filed
together via `宸ュ崟 API锛堣 TERMINOLOGY锛塩reate` with
`title="[{origin}] audit-batch@{version}: mechanical bumps for {N} advisories (TD-{NNN})"`
and labels `[tech_debt_label_id, severity_label_id, kind_bug_label_id]`
(severity = highest in the batch). One row per advisory 鈥?CVE/GHSA ID,
affected package + from鈫抰o version, severity (CVSS), advisory URL,
recommended remediation, and a per-advisory Reactivation Trigger
("resolved when `npm audit` / `bun audit` reports no HIGH/CRITICAL for
this advisory") 鈥?so the release-notes `TD-NNN 鈫?#NNNN` mapping stays
per-advisory.
- **(b) Docs 鎵归噺淇 (batch docs revisions)**: many small same-class docs
corrections discovered in one pass (e.g. a terminology sweep) file as ONE
issue via the same flow,
`title="[{origin}] docs-batch: {class} revisions for {N} files"`, with one
row per file (path, correction, reason); same ONE-batch-PR landing.
- **Landing**: when members live on separate branches, compose the single
batch PR via the `land-batch` skill (`core/skills/land-batch/SKILL.md`
鈥?cross-branch batch composition, topology B). For dependency-bump
batches pass `batch-compose --convergent bun.lock` (script
`.gitea/scripts/batch-compose.ts`): the regenerable lockfile is exempt
from path-overlap admission and lockfile-only conflicts are surgically
resolved to the running head's version.
#### 閫傜敤杈圭晫 / Applicability boundary
- **闈?mechanical锛堝惈鍒ゆ柇鎴愬垎鐨勫彉鏇达紝涓嶅苟鍗曪級** 鈥?items requiring judgment
(wording decisions, behavior/API changes, review-dependent edits) are
never batched; file each as its own issue.
- **璺緞閲嶅彔锛坆atch 鎴愬憳瑙︾鐩稿悓鏂囦欢 鈫?涓嶅苟鍗曪紱鍞竴璞佸厤 =
鍙啀鐢熷叡浜枃浠?`bun.lock` 缁?batch-compose `--convergent` 鏀舵暃锛?* 鈥?the sole
path-overlap exemption is the regenerable shared lockfile under
`--convergent`; every other same-file collision stays un-batched.
- **semver-major 璺冲彉 / overrides路catalog 鎵嬫湳 / adapter路peer 鑱斿姩锛堚啋
Tier B 鐙珛鍗曪紝缁存寔 per-package锛?* 鈥?these stay per-package Tier B
issues. Tier B and the escape hatch (below) survive this generalization
unchanged.
#### 闄嶇骇璺緞 / Degradation path
- **骞跺崟 PR CI 澶辫触 鈫?鎸夐攣鏂囦欢 hunk 鎷嗗寘鍥為€€锛坆isect锛?*: for dependency
batches, bisect by splitting lockfile hunks per package back into
per-package PRs 鈥?the original Tier A bisect semantics carried over
verbatim (鍚岃涔夛紝娉涘寲鎺緸: every mechanical batch degrades the same way).
- **Docs batches**: split per file back into per-file PRs.
- **land-batch composition fallback** (exit codes per
`.gitea/scripts/batch-compose.ts`): `3` path-overlap 鈫?split the batch
along the reported pairs (or land the overlapping member 1:1) and re-run
per group; `4` merge conflict / `5` transport/git error 鈫?fall back to
1:1 PRs for the whole batch.
- **Preflight hedge ([org-internal #3846])**: run `bun install --dry-run` before opening
a dependency-batch PR 鈥?the resolver accepting the composed version set
is a cheap pre-CI rejection of impossible bump combinations.
Tier B and the escape hatch (unchanged by the generalization):
- **Tier B 鈥?surgery, one issue per package**: semver-major jumps,
adapter/peer fallout, or overrides/catalog surgery keep the
per-package issue: `title="[{origin}] {CVE-ID/GHSA-ID} in {package} (TD-{NNN})"`
with the same labels; body requirements match the Tier A rows.
- Escape hatch: either tier may carve a single advisory into its own
issue when same-day remediation is required (urgent HIGH/CRITICAL).
De-duplicate against open `tech-debt` issues (match by
CVE/GHSA ID) before creating. Record the `TD-NNN 鈫?#NNNN` mapping in the
release notes. See `verify` Phase 5.5 for the tech-debt promotion body
template and `_shared/gitea-write-patterns.md` Pattern 3.
```markdown
## Pre-release Gate
| Check | Status |
| ----------------- | ----------------------------------------------------- |
| Clean workspace | 鉁?/ 鉂? |
| Branch | {branch name} |
| Build | 鉁?/ 鉂? |
| Typecheck + Lint | 鉁?/ 鉂? |
| Tests | 鉁?/ 鉂?(N passed, M failed) |
| Dependency audit | 鉁?/ 鈿狅笍 N known CVEs (pre-existing) / 鉂?N new CVEs |
```
If any gate fails except known CVEs, stop and report what failed.
---
## Phase 2 鈥?Version Bump
Determine the new version by inspecting git history since the last tag.
1. **Find last tag**: `git describe --tags --abbrev=0` (or `git tag --sort=-v:refname | head -1`).
- **No previous tag?** This is the first tracked release. Use the initial commit as
baseline: `git rev-list --max-parents=0 HEAD`. After tagging this release, create
a retroactive baseline tag (`v{base-version}`) on the initial commit so future
cycles have a clean `{tag}..{tag}` range. Document the gap in the release report.
2. **Read commits since last tag**: `git log <last-tag>..HEAD --oneline`.
3. **Read current version** from the project's version file (`package.json`
`version`, `Cargo.toml`, `VERSION`, etc.).
4. **Categorize commits** by change type:
| Conventional prefix | Semver | Examples |
| -------------------- | ------- | ----------------------------- |
| `BREAKING CHANGE:` / `!:` | MAJOR | API removal, schema change |
| `feat:` | MINOR | New feature, new endpoint |
| `fix:` | PATCH | Bug fix only |
| `perf:` | PATCH | Performance improvement |
| `refactor:` | PATCH | Internal restructuring |
| `docs:` / `chore:` | (skip) | Not user-visible |
If no conventional prefix found, infer from the subject line:
- "add", "implement", "introduce" 鈫?MINOR
- "fix", "resolve", "correct" 鈫?PATCH
- "remove", "drop", "rename" (public API) 鈫?MAJOR
5. **Compute bump**:
- If any MAJOR commit 鈫?bump MAJOR.
- Else if any MINOR commit 鈫?bump MINOR.
- Else 鈫?bump PATCH.
```markdown
## Version Bump
**Last tag**: {tag}
**Current version**: {old version}
**New version**: {new version}
**Type**: MAJOR / MINOR / PATCH
**Commits since last tag**: {N}
**Reason**: {justification 鈥?e.g. "2 feat + 3 fix 鈫?MINOR"}
```
---
## Phase 3 鈥?Changelog
Generate the changelog from `git log <last-tag>..HEAD`:
1. **List all commits**. For each, extract:
- Type (from prefix or inferred).
- Scope (if present, e.g. `feat(auth):`).
- Description (the subject line, past tense, human-readable).
2. **Group by type**:
- **Added** 鈥?`feat:` commits.
- **Changed** 鈥?modifications to existing behavior (non-breaking).
- **Fixed** 鈥?`fix:` commits.
- **Breaking** 鈥?`BREAKING CHANGE:` or `!:` commits.
3. **Deduplicate**: squash multiple commits for the same change into one entry
where it makes narrative sense.
4. Read the existing root `CHANGELOG.md` (if any) and prepend this release.
Write to root `CHANGELOG.md` (prepend section).
```markdown
## [{version}] 鈥?{YYYY-MM-DD}
### Added
- {feature} ({commit hash short})
### Changed
- {change} ({hash})
### Fixed
- {bugfix} ({hash})
### Breaking
- {breaking change} ({hash}) 鈥?see migration notes above
```
---
## Phase 4 鈥?Tag & Finalize
1. **Update version file** 鈥?write the new version to the project's version
manifest.
2. **Commit**: `git add` version file + changelog file, commit with message:
```
release: {version}
```
3. **Tag**: `git tag v{version}` (adjust prefix per project convention 鈥?
check existing tags with `git tag -l`).
4. **Verify tag**: `git tag -l v{version}` confirms the tag exists.
**Do not push** 鈥?the user must explicitly request pushing to remote.
```markdown
## Release Artifact
- **Version**: {version}
- **Commit**: {commit hash}
- **Tag**: v{version}
- **Changelog**: CHANGELOG.md updated
### Files Changed
| File | Change |
| -------------- | ------------------------- |
| {version file} | {old} 鈫?{new} |
| CHANGELOG.md | Prepended {version} |
```
---
## Phase 4b 鈥?Publish Release Artifacts (octopus project, manual)
> Octopus-specific. Other projects: substitute your own artifact pipeline 鈥?
> the goal is identical (turn the tag into downloadable assets).
The CI publish pipeline (`.gitea/workflows/publish.yml`) was **retired by
[org-internal #2003]** (138/138 historical runs cancelled; the pipeline sat unused for 35+
days). Releasing octopus is now a **manual local process**. All former CI
steps live in repo scripts, runnable from a maintainer machine with the right
credentials in the environment.
### Prerequisites
- Clean checkout of the release commit (tag pushed or about to be pushed).
- Credentials in env:
- `GITEA_TOKEN` 鈥?PAT with `write:repository` (release create/undraft,
tag push) and `write:package` (container registry). The `CI_PAT` secret
value is the canonical token.
- `NODE_AUTH_TOKEN` 鈥?only if publishing to npm.
- `AUR_KEY` 鈥?only if pushing the AUR package.
- Docker logged in to the Gitea container registry:
`echo "$GITEA_TOKEN" | docker login <instance-registry-host> -u <user> --password-stdin`.
### Steps
1. **Version + draft release** (idempotent 鈥?skips if already published):
```bash
GITEA_TOKEN=<pat> GH_REPO=Octopus/octopus bun script/version.ts
```
Creates (or refines) the draft Gitea release for `v{version}`.
2. **Build the 12 platform binaries + archives** (longest step):
```bash
OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \
GH_REPO=Octopus/octopus GITEA_TOKEN=<pat> \
bun <harness-package>/script/build.ts
```
`build.ts` attaches the 12 release assets to the draft release.
3. **Publish npm / docker / AUR + git sync**:
```bash
OCTOPUS_VERSION={version} OCTOPUS_RELEASE={release-id} \
GITEA_TOKEN=<pat> GH_REPO=Octopus/octopus \
NODE_AUTH_TOKEN=<npm-token> \
bun script/publish.ts
```
Set `OCTOPUS_GITEA_ONLY=true` to skip npm/docker/AUR/homebrew.
4. **Undraft the release** (makes it public):
```bash
curl -fsS -X PATCH \
"<instance-base-url>/api/v1/repos/Octopus/octopus/releases/{release-id}" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" -d '{"draft": false}'
```
5. **Verify assets** 鈥?the release must carry 12 assets:
```bash
curl -sS "<instance-base-url>/api/v1/repos/Octopus/octopus/releases/tags/v{version}" \
-H "Authorization: token $GITEA_TOKEN" | jq '.assets | length'
```
6. **Sync the public mirror repo** (`Octopus/octopus-release`):
```bash
CI_PAT=<pat> VERSION={version} bash script/sync-public-install.sh
CI_PAT=<pat> VERSION={version} bash script/sync-public-assets.sh
CI_PAT=<pat> VERSION={version} bash script/verify-public-sync.sh
```
### Notes
- **Windows code-signing** is not provisioned (no Windows runner / Azure
Trusted Signing); the CLI ships unsigned, as before ([org-internal #252]).
- **Rollback**: if a step fails mid-release, the draft release + tag can be
deleted and re-run; every script above is idempotent or safely re-runnable.
- If a future CI pipeline replaces this manual flow, update this section and
reference [org-internal #2003] for the retirement rationale.
---
## Phase 5 鈥?Rollback Plan
Document how to undo this release:
1. **Git rollback**:
- `git tag -d v{version}` (if not yet pushed).
- `git revert {commit_hash}` (if already merged).
- If the release includes DB migrations, confirm the `down` migration exists.
2. **Data rollback** (if applicable):
- For each migration, confirm the `down` migration exists and has been tested.
- If the release changes data format without a reversible migration, mark
`鈿狅笍 IRREVERSIBLE DATA CHANGE`.
```markdown
## Rollback Plan
### Git Rollback
git tag -d v{version}
# or: git revert {hash}
### Data Rollback
- Migration `{name}`: down {exists / NOT FOUND}
- {additional risks}
### Rollback Triggers
| Condition | Threshold | Duration |
| ------------------ | ------------ | -------- |
| P99 latency spike | 2x baseline | 5 min |
| Error rate spike | 1% | 1 min |
| Critical bug | Data loss / security breach | immediate |
```
---
## Phase 6 鈥?Post-release Smoke Test
1. Run the project's build command on the tagged commit. Must pass.
2. Run the test suite. Must pass.
3. Return to the branch: `git checkout <original-branch>`.
---
## Phase 7 鈥?Report
```markdown
## Release Report
**Version**: {old} 鈫?{new} (MAJOR / MINOR / PATCH)
**Tag**: v{version}
**Commits**: {N} since last tag ({feat} features, {fix} fixes)
**Typecheck**: 鉁?| Lint: 鉁?| Tests: 鉁?
### Changelog
{paste changelog section}
### Release Artifacts
- Commit: {hash} 鈥?`release: {version}`
- Tag: v{version}
- Changelog: CHANGELOG.md updated
---
**Deploy**: {manual step 鈥?"merge to main triggers CI", etc.}
**Rollback**: see Phase 5 above
**Next**: `core/skills/retrospective/SKILL.md` (optional 鈥?run on any project state)
```
---
## References
- `core/checklists/release.md` 鈥?Release checklist
- Semver spec: https://semver.org
- Conventional Commits: https://www.conventionalcommits.org
+762
View File
@@ -0,0 +1,762 @@
---
name: retrospective
description: >
Use ONLY when running a retrospective at the end of any work cycle
(release, feature, bugfix, port). The Retrospective Lead (Worker) inspects project
facts — git log, file churn, test history, commit patterns — and generates
actionable improvement items. No pipeline artifacts required.
triggers:
- retrospective
- 复盘
- lessons learned
- post-mortem
- postmortem
role: Producer
---
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
# Retrospective
Inspect the project's recent work cycle to extract patterns and generate
concrete action items. Based purely on project facts: git history, code churn,
commit quality, test results, and build health.
**Purpose**: Continuous improvement of BOTH the project AND the SDLC pipeline
itself. Every retrospective may modify templates, checklists, or SKILLs.
## Agent Role
The retrospective is owned and executed by the **Retrospective Lead** (Worker).
**Context compaction**: retrospective is a pipeline stage boundary. The main
session compacts at this clean boundary ONLY when a capacity/projection trigger
holds, per `core/rules/compact.md` §"Stage-boundary compaction" (long multi-stage runs
— DAG Epic orchestration — keep the legacy every-boundary compaction; short
runs — bugfix / DAG task — and standalone runs default to NOT compacting). The Retrospective Lead itself is single-phase and
data-driven: all findings are written to the retrospective report as they are
produced, so a mid-run compaction loses nothing — re-read the report artifact
to resume.
---
## Preconditions
Before starting the retrospective:
- [ ] A work cycle has been completed (release was cut, feature merged,
bug fixed, or port landed).
- [ ] The project is a git repository with recent commits.
- [ ] `core/checklists/retrospective.md` is accessible.
No `.artifacts/{slug}/` files are required. (**Deprecated**: `.artifacts/` file-system access is being migrated to Gitea wiki. SDLC artifacts now live as wiki pages under `{slug}/`; use `wiki 读写 API(见 TERMINOLOGY` and `wiki 读写 API(见 TERMINOLOGY` to read them.) The retrospective reads the project's own state and git history.
---
## Phase 1 — Scope the Cycle
Determine the time range to analyze:
1. If a **release was just done**: use `git log <last-tag>..<new-tag>`.
2. If **no tag exists**: prompt the user for a time range or revision range
(e.g. `HEAD~20..HEAD`, or `--since="last work cycle"`).
3. If the user specifies a range, use that.
```markdown
## Cycle Scope
**Range**: {commit range or "last work cycle"}
**Date**: {start} → {end}
**Commits analyzed**: {N}
**Authors**: {names}
```
---
## Phase 2 — Collect Data
Run project-inspection commands and summarize findings:
### 2.1 Commit patterns
```bash
git log {range} --oneline --no-merges
git log {range} --format='%s' | grep -cE '^(BREAKING|feat|fix|perf|refactor|docs|chore)'
```
- Count commits by type (BREAKING, feat, fix, refactor, etc.).
- Flag commits with no conventional prefix — these are opaque and hard to
trace → log as `[SIGNAL: low commit hygiene]`.
- Count revert commits (`git log {range} --grep="Revert" --oneline`).
- High revert count → likely insufficient testing or review before merge.
### 2.2 File churn
```bash
git diff --stat {range}
git diff --numstat {range} | sort -k1 -rn | head -20
```
- Identify the most-churned files (top 10 by lines added + deleted).
- High churn in a single file (> 200 lines in one cycle) → possible
monolithic module, design issue, or scope creep.
- List new files vs. deleted files.
### 2.3 Test health
1. Run the test suite: confirm pass/fail count and duration.
2. Check if any test files changed during the cycle:
```bash
git diff --name-only {range} | grep -E 'test|spec|__tests__'
```
3. If test files were NOT changed but source files were → `[SIGNAL: untested changes]`.
**Architecture-A exemption**: If the cycle touches ONLY `<instance-root>/` and
`.gitea/` files (config, skills, templates, rules, checklists) with zero
`packages/*` source changes, the "untested changes" signal does NOT apply
— `<instance-root>/` files are validated by the audit-process review gate, not
by unit tests. Record this as `[NOTE: Architecture A — config-only cycle,
unit-test exemption applies]` in the test summary.
4. Check for skipped/flaky tests if the framework reports them.
### 2.4 Build health
1. Run `bun typecheck` (or project equivalent). Note first-time errors.
2. Run `bun oxlint --deny-warnings` (repo root; `bun lint` is the package-script alias). Note any first-time warnings.
3. If the project has CI, check the latest run status.
### 2.5 Dependency health
1. Run `bun audit` (or equivalent). Flag any new HIGH/CRITICAL CVEs.
2. Check if any dependency was added/removed/upgraded:
```bash
git diff {range} -- package.json bun.lockb
```
### 2.6 Process quality
Inspect the SDLC infrastructure around the project — not just the code, but
the factory that produces it. These checks are all file-existence and
configuration reads; they require zero prior pipeline artifacts.
1. **Pre-commit guards**:
- Does the project have pre-commit hooks? (Check `.husky/`, `lefthook.yml`,
`.pre-commit-config.yaml`, `package.json` `"lint-staged"` key.)
- If absent → `[GAP: no pre-commit guard — bad code can land]`.
- If present → what commands do they run? (lint? typecheck? test?)
2. **CI/CD pipeline**:
- Does CI exist? (Check `.gitea/workflows/` — this repo's CI location —
then `.github/workflows/`, `.gitlab-ci.yml`, `Jenkinsfile`, etc.)
- If absent → `[GAP: no CI — no automated gate before merge]`.
- If present → does it block merge on failure? Check branch protection
(GitHub: `gh api repos/{owner}/{repo}/branches/main/protection`).
3. **Code review practice**:
- Does the project have a review template or guideline? (Check
`.github/PULL_REQUEST_TEMPLATE.md`, `CONTRIBUTING.md`, etc.)
- Are PRs used? Look at merge commits: do they reference PR numbers?
- If direct pushes to main → `[SIGNAL: no review gate]`.
4. **SDLC artifacts**:
- Use `wiki 读写 API(见 TERMINOLOGY(owner="Octopus", repo="octopus")` to check
for `{slug}/` wiki pages (`.artifacts/` file-system access is retired —
SDLC artifacts now live as wiki pages under `{slug}/`).
- If no `{slug}/` pages exist → `[NOTE: no structured SDLC artifacts — decisions not traceable]`.
- If present but stale → `[SIGNAL: artifacts not maintained — process drift]`.
5. **Tooling consistency**:
- Does the project have a unified formatter config? (`.prettierrc`,
`biome.json`, etc.)
- Does it have a unified typecheck/lint/test command? (Check `package.json`
scripts.)
- Are there multiple competing tools for the same concern? (e.g. both
prettier AND biome, both jest AND vitest.)
6. **Documentation health**:
- Does `README.md` include setup, build, and test instructions?
- Does `AGENTS.md` or `<instance-root>/AGENTS.md` exist?
- Are there any outdated docs? (Check for files referencing removed
commands or directories.)
### 2.7 Token telemetry
Collect LLM token-consumption signals to assess workflow quality. Five
metrics: M1 from an inline check; M2M5 from the token-telemetry probe.
1. **M1 — Review convergence.** The number of rounds each review dimension
needed to converge is the strongest signal of upstream-stage quality.
Discover review rounds via `wiki 读写 API(见 TERMINOLOGY(owner="Octopus", repo="octopus")`
with prefix `{slug}/reviews/` (`.artifacts/` file-system listing is retired).
For each review, `max(roundN)` is its convergence round count.
- 🟢 12 / 🟡 34 / 🔴 ≥5.
- ≥5 rounds → `[SIGNAL: review convergence ≥5 — upstream stage quality
insufficient, rework deferred to review]`.
The remaining four metrics come from a single probe run:
```bash
bun run core/skills/retrospective/scripts/token-telemetry.ts
```
It scans `~/.local/share/octopus/octopus-*.db` (token usage) and review
rounds from two sources — the Tier 1 review-status file
`<runs-root>/{slug}/reviews/{stage}/status.json` (canonical — reads the
`history[]` field (legacy alias `rounds[]`) and `current_round`) with the
legacy `.artifacts/**/reviews/*/status.json` tree fallback, plus the
committed archive bundles `<runs-root>/archive/{slug}.json` for closed
runs — and prints M2M5.
2. **M2 — Stage distribution.** Token spend per pipeline stage (design,
review, implement, …). The probe reconstructs a per-session stage
timeline from `~/.local/share/octopus/token-stage-ledger.jsonl` —
written by the auto-discovered `<instance-root>/plugin/token-stage-ledger.ts`
plugin, which hooks `tool.execute.after` on the `skill` tool — and
attributes each message's tokens to the stage active at its creation.
- **Ledger-gated.** If the plugin was not active during the cycle the
ledger is absent and the probe prints
`[NOTE: token-stage-ledger.jsonl absent — M2 skipped]`. That is
"unavailable", not "failed" — proceed.
- Review-stage share: 🟢 <35% / 🟡 3560% / 🔴 >60%.
- > 60% → `[SIGNAL: review stage >60% of token spend — over-reviewing,
review findings not actionable upstream]`.
3. **M3 — Review rework.** Rework fraction — review rounds beyond the first
as a share of total review rounds.
The probe merges two sources, deduped by `{slug}/reviews/{stage}` (the
ACTIVE status.json wins): (a) the Tier 1 review-status file
`<runs-root>/{slug}/reviews/{stage}/status.json` (canonical — `history[]`
field, legacy alias `rounds[]`, and `current_round`; skips `_archive/`)
with the legacy `.artifacts/**/reviews/*/status.json` tree fallback —
in-flight runs only, since the active workspace is deleted at
archive-at-close; (b) the committed archive bundles
`<runs-root>/archive/{slug}.json`, where per-review round counts are
reconstructed from the documented `reviews/{stage}/round{N}/` layout in
`index.artifacts[].path` ([org-internal #2591] — the archive source is what makes M3
durable instead of structurally emptying as runs close). Available once
any run has closed.
- Rework fraction: 🟢 <15% / 🟡 1530% / 🔴 >30%.
- > 30% → `[SIGNAL: rework fraction >30% — review findings not actionable
or upstream design unclear]`.
4. **M4 — Context hygiene.** Per-session input-token growth and cache
efficiency, read from the octopus session database. The cache
read/input ratio measures context reuse.
- 🟢 cache_read/input > 10:1 / 🟡 310:1 / 🔴 < 3:1.
- < 3:1 → `[SIGNAL: cache hit <3:1 — context re-read, code-graph-first
not followed]`.
- **Compact frequency ([org-internal #2601] pilot data)**: report the token-telemetry
"Compactor Activity" line (compactor messages/tokens in window) and the
zero-compact share of short runs (bugfix / DAG task) — the capacity-driven compaction
pilot metrics. A rising zero-compact share with NO late-stage
degradation signal is the evidence that retires the pilot gate in
`rules/compact.md` § Stage-boundary compaction.
5. **M5 — Explore/execute ratio.** Token spend by agent type
(`data.agent` per message). A low ratio means workers are doing
explorers' job — context-gathering that should be delegated.
- 🟢 > 2:1 / 🟡 12:1 / 🔴 < 1:1.
- < 1:1 → `[SIGNAL: explore/execute <1:1 — workers doing explorers' job,
exploration skipped or under-delegated]`.
### 2.8 Gate defect-escape analysis (single metric, [org-internal #3061])
Gates earn their cost by what they CATCH, and the honest test is what slips
past them: a gate that runs clean while the same defect resurfaces downstream
is under-powered — the opposite of redundant. This probe computes ONE metric
per `gate_id` (the legacy per-(Kind × Size × gate) keying is retired with the
sizing subsystem — sub-5 cell counts produced noise, not evidence):
**`escape_rate` = clean runs with a downstream escape / clean runs**
- **Clean run**: the gate ran on a cycle ticket and passed round 1 with zero
INFO-or-worse findings.
- **Downstream escape**: within 14 days after the clean gate, either
(a) a verify FAIL/WARN finding in the same area (module/dimension) on the
same ticket, or (b) a post-merge `Kind/Bug` ticket whose body references
the area the gate covered.
**Data sources** (all read-only):
- **Review status**: commit-status context `pipeline/{review_type}`
(`_shared/gitea-write-patterns.md` Pattern 8), read via `octopus review
status` CLI or commit-status inspection. Final reports at
`{slug}/reviews/{stage}/final/report` record round count and highest
severity.
- **Downstream (a)**: verify reports `{slug}/05-verify-iteration-{N}` for the
same slug.
- **Downstream (b)**: `工单 API(见 TERMINOLOGYlist(labels="Kind/Bug", state=closed)`
within the window; match by module/path references in the body.
- **slug ↔ issue map**: the issue's `## 工件索引` comment (legacy:
`## Pipeline 工件追踪表`) or `Closes #N` in the PR body.
**Procedure**:
1. Enumerate closed tickets in the cycle with any review run; resolve slugs.
2. Per gate run: classify clean / not-clean; for clean runs, search both
downstream sources for an escape.
3. Tally per `gate_id`: `clean_runs`, `escapes`, `escape_rate`.
4. Reading (thresholds are this skill's own policy — the `gate_trim:` block
they once deferred to was retired 2026-08-21, [org-internal #3072] phase 3; nothing
trims gates anymore, so the trim-candidate branch below is gone):
- `escape_rate ≥ 0.3` → the gate is
**UNDER-POWERED**: route the escape causes into §2.9 pre-flight
(producer-side self-checks) and note the gate in the report. Do NOT
propose trimming it.
- `escape_rate < 0.3` → healthy; report the numbers, no further verdict.
**DAG single-gate exclusion ([org-internal #2267])**: `review-dag` is structurally
non-trimmable (`dag.route.single_gate.never_trim: true` — self-contained in
the `dag:` block); `verify` and `merge-pr` were `never_trim` in the retired
`gate_trim` block and stay untouchable by convention.
**keep_gates exclusion (TD-390, [org-internal #3061]) — RETIRED with gate_trim**: the
rule that a trim candidate's gate must not be a member of the target route's
`keep_gates` guarded a landing field (`sizing.tiers.*.additional_skip`)
that no longer exists. Historical record: wiki `rules/gate-trim`.
**Sample-sufficiency gate** (structural, not optional): fewer than 5 clean
runs for a gate → report the raw numbers with
`[NOTE: insufficient sample for gate {id}]` and emit no verdict for it.
```markdown
### Gate defect-escape (M6)
| gate | clean runs | escapes | escape_rate | verdict |
| ----------------------------------- | ---------- | ------- | ----------- | ------------------------- |
| review-code | 12 | 1 | 0.08 | healthy |
| review-design-space (sticky legacy) | 6 | 3 | 0.50 | UNDER-POWERED → feed §2.9 |
| ... | ... | ... | ... | ... |
- [NOTE: insufficient sample for gate X] where clean runs < 5.
- UNDER-POWERED gates (escape_rate ≥ 0.3) feed §2.9 pre-flight — never a
unilateral trim: the only exit is a furlough entry in
`<instance-root>/gate-ledger.yaml` (evidence + reopen condition, [org-internal #3607]).
```
### 2.9 Pre-flight defect-prevention analysis ([org-internal #2599])
First-round FAIL/WARN findings are the pipeline's cost multiplier: every
multi-round review pays for them twice (review round + revision round).
This probe tallies their root causes and proposes producer-side self-checks —
the defensive complement of the retired gate-trim meta-process: instead of
removing a gate that never catches anything, inject the causes that keep
costing rounds as a pre-flight checklist the Producer verifies BEFORE writing
code.
**Data sources** (read-only, same as 2.8): review status pages / final
reports for the cycle's tickets (`{slug}/reviews/{stage}/final/report` —
round-1 findings with severity FAIL/WARN, their dimension codes, and the
finding text for root-cause categorization) + the source issues'
`Kind/*` labels. For DAG-routed Epics ([org-internal #2905] 方案 3), two further
read-only sources feed the `REQ × late-discovery` root cause (step 1):
(a) **DAG oversize-signal events** — `node_split` / registry-row additions
recorded on the Epic's issue timeline / `## DAG 状态` whose trigger is a NEW
requirement rather than a refactor; (b) **demo-period feedback** —
stakeholder comments naming functionality the frozen DAG never covered. For browser-evidence
cycles ([org-internal #4499], contract `browser-evidence-4486/shared/evidence-ref-v1` §3), a
third read-only
source: (c) **browser session rows** — per browser-debug session
`{session_id, outcome: evidence-captured | env-unavailable | replay-failed,
replay summary, env.mode}`, derived from Tier-1 pack manifests (sanitized
transitively by the N-03 write boundary — this probe never touches raw
captures). Replay failure attribution rides the FIXED
`ReplayFailureCategory` enum (`browser-evidence-4486/shared/pack-manifest-v1`
§4); `env-unavailable` sessions default to the enum's own environment slot
(`env-binary-missing`) unless the row carries an explicit attribution;
browser-class
causes enter the step-2 route-class tally as `BROWSER × {category}` and a
qualifying cause becomes a PRE-FLIGHT PROPOSAL candidate landing on
`routes.{Kind}.preflight` (human-landed in Phase 6, never auto-applied).
Rows are currently Task-DAG cycle products by default (the helper's
`--route-class` override / row-level `route_class` field re-keys a future
Bug- or Feature-cycle session to its own landing slot). The replay
success-rate baseline (N-03 BENCH) is exposed as a consumable metric — a SEEDED CONVENTION (fixture-authored outcomes, channel health),
NOT a live-browser trend baseline. Mechanical helper:
`<harness-package>/scripts/browser-retro-tally.ts`. Boundary rules ([org-internal #4499]
AC-3): out-of-enum or empty categories fall into an explicit `other` bucket
with provenance (recorded, never silently swallowed, never a crash); rows
missing replay/attribution fields yield structured errors in the tally
output; a cycle with no browser session rows keeps this probe's current
behavior (empty tally, no synthetic proposal rows).
**Procedure**:
1. For each ticket with a multi-round review in the cycle, read the round-1
findings with severity FAIL or WARN. Categorize each by
(dimension × root-cause) — the dimension code is checklist-qualified
(checklist-local namespace; e.g. `code-review.md TST × missing-boundary-test`,
`code-review.md COR × unhandled-null`,
`code-review.md STY × wrong-import-order`).
Canonical root-cause category for DAG-routed Epics: `REQ ×
late-discovery` — 冻结后才被发现的需求 (a requirement that surfaced only
after the DAG froze). A late discovery usually manifests as a round-1
REQMAP FAIL on the re-run review, but the underlying event is an
oversize signal or demo comment — count it from sources (a)/(b) above,
not only from review findings.
2. Tally per **route-class** — `Bug`, `Feature-DAG`, `Epic-DAG-route`,
`Task-DAG` (Kind/Feature routes to the DAG pipeline since [org-internal #3061] Phase 2 /
TD-388 — `Feature-legacy` survives only as a historical bucket for tickets
closed before 2026-08-20; the Size dimension is retired from retro keying,
[org-internal #3061]: DAG-routed tickets derive depth instead of carrying `Size/*`
(spec-06), and `Kind/Bug` carries no ladder). First-round hit count
and distinct-ticket count per cause per class.
3. A cause is a **pre-flight candidate** when ALL hold (thresholds from
`workflow-routing.yaml` `preflight` — do NOT restate values here):
- hit count ≥ `preflight.min_sample`
- distinct tickets ≥ `preflight.consecutive_recur`
A qualifying `Epic × DAG-route` cause (e.g. `REQ × late-discovery`) lands
on `<instance-root>/workflow-routing.yaml` `dag.route.preflight` — NOT
`routes.Kind/Epic.preflight` (legacy-roadmap-era landing; the legacy route
was archived [org-internal #3072] phase 3) ([org-internal #2905] 方案 3). The analyze-dag skill reads
`dag.route.preflight` before decomposition.
4. Aging: for causes ALREADY landed in `routes.{Kind}.preflight` or
`dag.route.preflight`, count
consecutive clean first rounds since landing (from this and prior retro
data); at ≥ `preflight.aging_consecutive_clean`, emit a REMOVAL row.
Sample-sufficiency guard mirrors 2.8: zero qualifying tickets → emit
`[NOTE: insufficient sample for pre-flight proposals]` and skip this probe.
```markdown
### Pre-flight defect prevention (M7)
| route-class | Cause (checklist-qualified dim × root-cause) | 1st-round hits | tickets | qualify? |
| -------------- | ------------------------------------------- | -------------- | --------------------------- | -------------------------------- |
| Bug | code-review.md COR × unhandled-null | 5 | 4 ([org-internal #2400] [org-internal #2429] [org-internal #2471] [org-internal #2488]) | ✅ PROPOSE |
| Bug | code-review.md STY × wrong-import-order | 6 | 2 | ⛔ tickets <3 |
| Feature-DAG | code-review.md TST × missing-boundary-test | 3 | 3 | ⛔ hits <5 |
| Epic-DAG-route | REQ × late-discovery | 5 | 3 (#27xx #28xx #29xx) | ✅ PROPOSE → dag.route.preflight |
- [NOTE: no pre-flight proposals] if the table is empty or nothing clears threshold.
- ✅ PROPOSE rows feed Phase 5 PRE-FLIGHT PROPOSAL action items.
- Landed-item aging: `cor-unhandled-null clean streak 5 ≥ aging 5` → REMOVAL row.
```
### 2.10 Derived-ticket health ([org-internal #3061])
Derived tickets (TD promotions, BF umbrellas, FT tickets) are the pipeline's
exhaust. Unmanaged they accumulate into flush cycles (2026-08 evidence: 122
open tech-debt tickets, ~9.4/day creation, zero pre-August closures, a
115-ticket bulk flush). Under the registry-first regime (verify Phase 5.5)
this probe checks whether the system DIGESTS what it records:
**Metrics** (cycle window):
- **TD flow**: registry rows created / rows promoted to tickets / promoted
tickets closed-as-fixed vs closed-as-wontfix.
- **Median open age**: open TD rows + promoted tickets, by module/origin.
- **BF triage compliance**: % of verify-Phase-5.55 BF umbrellas triaged
(assigned or scheduled) within 1 day of filing.
- **FT expiry compliance**: % of verify-Phase-5.56 FT tickets fixed-or-
isolated within their N-day window.
- **Per-module open count** vs the promotion quota (verify Phase 5.5).
**Actions**:
- A category (module/origin) with creation ≥ fix across ≥2 consecutive
retros → mark it **register-only**: verify Phase 5.5 stops promoting rows
to tickets there until one retro shows net-negative backlog.
- Open TD rows unclaimed for >3 retro cycles → mark the row `[COLD]`
(revivable — a pull event clears the mark). Cold rows are excluded from
adjacency-quota pressure and do not count against the module quota.
- BF/FT compliance < 100% → name the untriaged/expired items in the report
(they are SLA breaches, not statistics).
```markdown
### Derived-ticket health (M8)
| metric | this cycle | last cycle | trend |
| ----------------------------------------- | ---------- | ---------- | ----- |
| TD rows created / promoted / closed-fixed | | | |
| median open-TD age (days) | | | |
| BF same-day triage % | | | |
| FT fix-or-isolate % | | | |
- register-only categories: {list or "none"}
- rows marked [COLD] this cycle: {list or "none"}
```
### 2.11 Threshold calibration tally ([org-internal #3380])
Process prose constants (round caps, D1D4 thresholds, preflight knobs,
escape-rate cutoffs, quotas — full index:
`docs/workflow-refactor/thresholds-ledger.md`) are only as good as the data
behind them. This probe keeps them honest, mirroring §2.9's propose-and-human-
lands pattern:
1. **Round-cap distribution** (per stage): read `{slug}/reviews/*/final/report`
headers (`**Rounds completed**: N`) for the cycle's slugs; tally per stage
(code / dag), report n / p50 / p90 / p95 / max, and how often the cap was
the binding stop (`Rounds completed` == cap with `Converged: false`).
2. **CALIBRATION PROPOSAL rows**: for any ledger constant whose data source
this retro tallied, emit a proposal row — `constant | current | observed |
proposed | evidence` — when the data contradicts the current value (cap
never binding AND p95 ≪ cap → propose lowering; cap binding with
converged-improving runs → propose raising). Proposals are **never
auto-landed**: a human lands them by editing the definition site AND the
ledger row in one PR citing this retro (preflight evidence-field pattern).
3. **Ledger sync**: if any constant's definition site changed since the last
retro (value or location), update the ledger row — drift between the two
is a TD-480-class double-source failure.
```markdown
### Threshold calibration (M9)
| stage | n | p50 | p90 | p95 | max | cap-binding runs |
| --------------------------------- | ------- | -------- | -------- | ------------------- | --- | ---------------- |
| code | | | | | | |
| dag | | | | | | |
| - CALIBRATION PROPOSAL: {constant | current | observed | proposed | evidence} or "none" |
- ledger sync: {rows updated or "none"}
```
```markdown
## Data Summary
### Commits
| Type | Count |
| ------------- | ------ |
| BREAKING | {N} |
| feat | {N} |
| fix | {N} |
| refactor | {N} |
| docs/chore | {N} |
| **no prefix** | {N} ⚠️ |
| **reverts** | {N} |
### Churn Top 10
| File | +lines | -lines | Concern |
| --------------------- | ------ | ------ | ----------- |
| src/{module}/large.ts | 200 | 150 | Monolithic? |
| ... | ... | ... | ... |
### Tests
- Suite: {N} passed, {M} failed, {S} skipped — {duration}s
- Test files changed: {N} / {M}
- Untested source changes: {list or "none"}
### Build
- Typecheck: ✅ / ❌ (N errors)
- Lint: ✅ / ⚠️ (N warnings)
### Dependencies
- Added: {list}
- Removed: {list}
- Upgraded: {list}
- Audit: ✅ / ⚠️ N CVEs
### Process
| Check | Status | Detail |
| ------------------- | ------------ | ---------------------------------- |
| Pre-commit hooks | ✅ / ❌ | {what runs / "none"} |
| CI/CD | ✅ / ❌ | {provider / "none"} |
| PR / review gate | ✅ / ❌ | {PR # pattern / direct push} |
| SDLC artifacts | ✅ / ⚠️ / ❌ | {present & fresh / stale / absent} |
| Formatter config | ✅ / ❌ | {tool / "none"} |
| Typecheck+Lint+Test | ✅ / ⚠️ | {unified scripts?} |
| README / AGENTS.md | ✅ / ⚠️ | {present / stale / absent} |
### Token
- M1 review convergence: max {N} rounds ({review name}) — 🟢/🟡/🔴
- M2 stage distribution: design {X}% / review {X}% / implement {X}% (review share {X}% — 🟢/🟡/🔴)
- M3 rework: {X}% rework fraction ({rework}/{total} rounds across {N} reviews) — 🟢/🟡/🔴
- M4 context: p50={N} / p90={N} / max={N} input tokens; cache {ratio}:1 — 🟢/🟡/🔴
- M5 explore/execute: {ratio}:1 (explorer {N} / worker {M} tokens) — 🟢/🟡/🔴
- M6 gate defect-escape: {N} gates scanned, {M} UNDER-POWERED (escape_rate at/above threshold, top: {gate}) — 🟢 all healthy / 🟡 {M} under-powered → feed §2.9 / 🔴 broad under-powering across gates
- [NOTE: M2 skipped if token-stage-ledger.jsonl absent]
```
---
## Phase 3 — What Went Well?
Identify positive patterns — things to repeat or amplify:
1. **High-quality commits**: commits with clear prefixes, atomic scope, good
descriptions → name specific examples.
2. **Low-churn modules**: files that were changed but had low +/ counts
(well-factored, easy to modify).
3. **Tests that caught bugs**: if any test was added before the fix commit,
that's TDD → highlight it.
4. **Fast turnaround**: if any commit → production cycle was unusually fast.
5. **Process safeguards active**: pre-commit hooks catching errors before push,
CI blocking broken builds, PR review catching design issues early.
6. **Fresh documentation**: README and AGENTS.md are up to date and referenceable.
```markdown
## What Went Well
1. {finding} — {evidence from data} — {why it worked, do again}
2. {finding}
3. {finding}
```
---
## Phase 4 — What Went Wrong?
Identify problems — focus on patterns in the data, not blame:
| Data signal | Root cause pattern | Example |
| ------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Revert commits > 0 | Bug slipped through review / testing | "Revert 'fix auth' — original fix broke login" |
| High churn in a single file (> 300) | Monolithic module, hard to change safely | `src/handler.ts` +400/-350 in one cycle |
| feat commits without test changes | New features landed untested | 3 feat commits, 0 test files changed |
| No conventional commit prefix | Low commit discipline, harder to auto-changelog | 8 of 12 commits have no prefix |
| Test suite growing slower | Test debt accumulating | Source +200 lines, tests +10 lines |
| Dependency added without audit check | Supply chain risk | New dep added, `audit` not run |
| Typecheck broke mid-cycle | No pre-commit / pre-push hooks | Type error landed on main, fixed later |
| No pre-commit hooks | Every developer must remember to run checks manually | Type errors and lint violations land on main |
| No CI/CD | No automated gate before merge | Broken build merged, discovered later |
| No PR template / direct pushes | No structured review process | Design flaws not caught until production |
| Stale SDLC artifacts | Process was followed once then abandoned | `.artifacts/` exists but empty for last 3 cycles (**deprecated**: check Gitea wiki `{slug}/` pages instead) |
| Multiple formatters / test frameworks | Tooling inconsistency slows onboarding | Both prettier and biome configured |
| Review rounds ≥5 | Upstream stage quality low — rework deferred to review | design converged only at round 5 |
| cache_read/input < 3:1 | Context re-read repeatedly — code-graph-first not followed | 2.8:1 across design stage |
| explore/execute < 1:1 | Workers doing explorers' job — exploration skipped or under-delegated | 0.7:1 — worker tokens exceed explorer |
| review stage >60% of token spend | Over-reviewing — review findings not actionable upstream | design 20% / review 65% / implement 15% |
| rework fraction >30% | Review findings not actionable or upstream design unclear | 62% rework — 30 of 48 rounds beyond first |
| retrospective skill modified in cycle range | Retrospective reviewing its own modification — potential self-review bias | `core/skills/retrospective/SKILL.md` changed in `git diff {range}` |
```markdown
## What Went Wrong
1. **{signal}** — {root cause} — {impact: what broke / slowed down}
2. **{signal}** — {root cause} — {impact}
3. **{signal}** — {root cause} — {impact}
```
---
## Phase 5 — Action Items
For each "what went wrong", generate a concrete, measurable action item.
Each item MUST specify:
- **What** — the change to make.
- **Where** — which template, checklist, SKILL, or project config to modify.
- **Who** — which role or agent is responsible.
- **When** — effective immediately or next cycle.
```markdown
## Action Items
| # | What | Where | Who | When |
| --- | -------------------------------------------------- | --------------------------------------- | ------------------ | ---------- |
| 1 | Add pre-commit hook: typecheck + lint on staged | `.husky/pre-commit` | Developer | immediate |
| 2 | Require test file changes for every feat commit | `core/checklists/implementation.md` | Retrospective Lead | next cycle |
| 3 | Add commit message template (conventional commits) | `.gitmessage` or `CONTRIBUTING.md` | Retrospective Lead | next cycle |
```
**Rules**:
- Maximum 5 action items per retrospective. If you have more, group by root
cause and take the top 5.
- Every action item MUST reference a specific file to modify.
- Action items that modify SKILLs or templates are applied immediately (the
Retrospective Lead can edit `<instance-root>/` files).
- Action items MUST NOT be vague ("try harder", "be more careful").
- **Layering question** (injection-budget ruling item 3, [org-internal #3547]): every action
item that encodes a lesson as a process constraint MUST answer **"which
layer carries this lesson?"** — options in ascending per-turn cost, pick
the cheapest that actually enforces it:
1. **L0 tool-enforced** — hook / bash guard / CI validation (zero prompt
bytes);
2. **Skill step** — inline in the phase skill that executes the work
(bytes paid only when that skill is loaded);
3. **L2 on-demand** — wiki / rule doc fetched when a task needs it;
4. **L1 per-turn injection** — an `core/rules/*.md` whitelist entry
(bytes paid by EVERY role on EVERY turn; item must cite the current
corpus bytes vs `bun run check:rule-budget` cap headroom).
Option 4 is **default-deny**: choosing it requires stating why 13 cannot
carry the lesson (2026-08 batch-1 cut builder -41% / explorer -69% per-turn
corpus — do not casually re-grow it; the rule-GC report in that same check
flags aging L1 rules for L2 retirement).
**TRIM PROPOSAL rows — RETIRED ([org-internal #3072] phase 3, 2026-08-21)**: the
gate-trim landing machinery (`gate_trim.action` →
`sizing.tiers.{Size}.additional_skip` / `routes.{Kind}.skip` /
`auto_approve.stages`) was removed with the `gate_trim:` and `sizing:`
blocks — nothing trims gates anymore, so Phase 5 emits NO trim proposals.
Gate health findings flow exclusively through §2.8's escape-rate verdict
(UNDER-POWERED → §2.9 pre-flight) and ordinary action items. Historical
spec: wiki `rules/gate-trim` (L2).
**PRE-FLIGHT PROPOSAL rows** (from Phase 2.9 defect-prevention, [org-internal #2599]): format
the What cell as
`Add pre-flight '{id}' to {Kind} ({hits} first-round FAIL/WARN hits, {tickets} tickets — {evidence})`
and the Where cell as
`<instance-root>/workflow-routing.yaml` `routes.{Kind}.preflight` — or, for causes
keyed `Epic × DAG-route` (e.g. `REQ × late-discovery`, [org-internal #2905] 方案 3),
`<instance-root>/workflow-routing.yaml` `dag.route.preflight` (consumed by
analyze-dag before decomposition).
Aging removal rows: `Remove pre-flight '{id}' from {Kind} (clean streak {N} ≥ aging threshold)`.
Same doctrine the retired TRIM proposals used: a PRE-FLIGHT PROPOSAL is a
recommendation, not an
auto-apply — landing (and removal) happens in Phase 6 only when the action
item survives the retrospective's own review. Landed checklists must stay
within `preflight.max_items` entries per route (drop-oldest by `added_cycle`).
---
## Phase 6 — Apply Improvements
For each action item that modifies a `<instance-root>/` or project-config file:
1. Read the current file.
2. Apply the change.
3. Note the change in the retrospective report.
```markdown
## Applied Improvements
1. Modified `{.file}`: {what was changed} — {commit hash}
2. Modified `{.file}`: {what was changed} — {commit hash}
```
---
## Phase 7 — Report
Publish the retrospective report as a Gitea wiki page `_retrospectives/{cycle-name}` via `wiki 读写 API(见 TERMINOLOGY`. The `_retrospectives/` namespace is an intentional cross-cycle, slug-less exception to the `{slug}/...` artifact-path convention (retrospectives aggregate multiple slugs and outlive any one pipeline run) — analogous to the audit `audit/{date}/` date-slug exception documented in the NAM 4.4 checklist item.
```markdown
# Retrospective: {cycle description}
**Date**: {YYYY-MM-DD}
**Range**: {commit range or time range}
**Commits**: {N}
**Overall**: 🟢 GREEN / 🟡 YELLOW / 🔴 RED
## What Went Well
{list}
## What Went Wrong
{list}
## Action Items
{table}
## Applied Improvements
{list}
```
---
## References
- `core/checklists/retrospective.md` — Retrospective self-check
@@ -0,0 +1,830 @@
import { Database } from "bun:sqlite"
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"
import { join, relative } from "node:path"
import { homedir } from "node:os"
const DBS_DIR = join(homedir(), ".local/share/octopus")
const LEDGER_PATH = join(DBS_DIR, "token-stage-ledger.jsonl")
// #2628: workspace containers (octopus-ws-*) persist their session store in
// named volumes octopus-sessions-<id> (bind added in
// packages/containers/src/runtime/docker.ts). Scan those alongside the host
// dir so telemetry no longer depends on which machine/container ran a session.
const SESSION_VOLUMES_ROOT = "/data/docker/volumes"
function collectSessionVolumeDbs(): string[] {
let vols: string[] = []
try {
vols = readdirSync(SESSION_VOLUMES_ROOT).filter((d) => d.startsWith("octopus-sessions-"))
} catch {
return [] // not on the docker host (e.g. a dev workstation) — fine
}
const out: string[] = []
for (const v of vols) {
const dir = join(SESSION_VOLUMES_ROOT, v, "_data")
try {
for (const e of readdirSync(dir, { withFileTypes: true })) {
if (e.isFile() && e.name.endsWith(".db") && (e.name === "octopus.db" || e.name.startsWith("octopus-"))) {
out.push(join(dir, e.name))
}
}
} catch {
// unreadable volume — skip it
}
}
return out.sort()
}
function dbLabel(dbPath: string): string {
const base = dbPath.split(/[/\\]/).pop()!.replace(".db", "")
const vol = dbPath.match(/octopus-sessions-([a-zA-Z0-9-]+)[/\\]_data/)
return vol ? `${base}@${vol[1]!.slice(0, 8)}` : base
}
// SINCE_DAYS=<N> env var scopes queries to messages from the last N days,
// avoiding full-table scans on multi-GB databases.
// message.time_created is MILLISECONDS (verified: raw values ~1.78e12). Keep
// `since` in ms — a seconds-based value is always smaller than every ms
// timestamp, so the filter would silently match everything (#2599).
const sinceDays = Number(process.env.SINCE_DAYS ?? "30")
const since = sinceDays > 0 ? Date.now() - sinceDays * 86_400_000 : 0
// Cycle-window filters (retro #4034 quick-wins): `--since <ISO-date>` and
// `--slug <slug>` constrain the cycle-window metrics (M2/M3/M5) to the
// window / matching run. M1/M4 keep the SINCE_DAYS env semantics. A filter
// that yields no data prints an explicit "no data in window" line for the
// metric — never a silent fallback to all-time numbers.
const argv = process.argv.slice(2)
const arg = (name: string): string | undefined => {
const i = argv.indexOf(`--${name}`)
return i >= 0 ? argv[i + 1] : undefined
}
const sinceArg = arg("since")
const slugArg = arg("slug")
const windowSince = sinceArg !== undefined ? Date.parse(sinceArg) : undefined
if (sinceArg !== undefined && Number.isNaN(windowSince)) {
console.error(`invalid --since "${sinceArg}" — use an ISO date (e.g. 2026-09-02)`)
process.exit(1)
}
const filtersActive = sinceArg !== undefined || slugArg !== undefined
const windowOrSince = windowSince ?? since
const m5Filter = filtersActive ? { since: windowOrSince, slug: slugArg } : undefined
function percentile(sorted: number[], p: number): number {
if (sorted.length === 0) return 0
const idx = Math.min(Math.floor((sorted.length * p) / 100), sorted.length - 1)
return sorted[idx] ?? 0
}
type AgentStats = Map<string, { msgCount: number; totalTokens: number }>
type SessionRow = {
id: string
parent_id: string | null
slug: string | null
title: string | null
directory: string | null
}
function loadSessionRows(db: Database): Map<string, SessionRow> | null {
const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session'").get()
if (!hasTable) return null
const rows = db.prepare("SELECT id, parent_id, slug, title, directory FROM session").all() as SessionRow[]
return new Map(rows.map((r) => [r.id, r]))
}
// Session→run matching for --slug: a session matches when its slug, title,
// or directory contains the run slug (workflow sessions live in worktrees
// named after the run). A match propagates to the whole subtree (subagents),
// so every message of the run's sessions is included.
function slugSessionIncludeSet(sessions: Map<string, SessionRow>, slug: string): Set<string> {
const needle = slug.toLowerCase()
const children = new Map<string, string[]>()
for (const r of sessions.values()) {
if (!r.parent_id) continue
const arr = children.get(r.parent_id) ?? []
arr.push(r.id)
children.set(r.parent_id, arr)
}
const include = new Set<string>()
const markSubtree = (id: string) => {
if (include.has(id)) return
include.add(id)
for (const c of children.get(id) ?? []) markSubtree(c)
}
for (const r of sessions.values()) {
const hay = [r.slug, r.title, r.directory].filter((x): x is string => typeof x === "string")
if (hay.some((x) => x.toLowerCase().includes(needle))) markSubtree(r.id)
}
return include
}
function processDb(dbPath: string, since = 0, m5Filter?: { since: number; slug?: string }) {
const dbName = dbLabel(dbPath)
const sessions: {
sessionId: string
msgCount: number
inputs: number[]
totalInput: number
totalCacheRead: number
}[] = []
const agents: AgentStats = new Map()
let db: Database | null = null
try {
db = new Database(dbPath, { readonly: true })
db.exec("PRAGMA busy_timeout = 5000")
} catch {
return { dbName, sessionCount: 0, messageCount: 0, sessions, agents }
}
try {
const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='message'").get()
if (!hasTable) return { dbName, sessionCount: 0, messageCount: 0, sessions, agents }
// Single query replaces the former N+1 pattern (one query per session).
// Grouping in JS avoids N full-table scans with json_extract.
const rows = db
.prepare(
`
SELECT
session_id,
CAST(json_extract(data, '$.tokens.input') AS REAL) as inp,
CAST(json_extract(data, '$.tokens.cache.read') AS REAL) as cr
FROM message
WHERE json_extract(data, '$.role') = 'assistant'
AND json_extract(data, '$.tokens.input') IS NOT NULL
${since > 0 ? "AND time_created >= ?" : ""}
ORDER BY session_id, time_created
`,
)
.all(...(since > 0 ? [since] : [])) as {
session_id: string
inp: number | null
cr: number | null
}[]
const sessionMap = new Map<string, { inputs: number[]; totalInput: number; totalCacheRead: number }>()
for (const r of rows) {
const inp = Number(r.inp ?? 0)
const cr = Number(r.cr ?? 0)
if (inp <= 0) continue
let s = sessionMap.get(r.session_id)
if (!s) {
s = { inputs: [], totalInput: 0, totalCacheRead: 0 }
sessionMap.set(r.session_id, s)
}
s.inputs.push(inp)
s.totalInput += inp
s.totalCacheRead += cr
}
for (const [sessionId, s] of sessionMap) {
if (s.inputs.length === 0) continue
sessions.push({
sessionId,
msgCount: s.inputs.length,
inputs: s.inputs,
totalInput: s.totalInput,
totalCacheRead: s.totalCacheRead,
})
}
if (m5Filter) {
let include: Set<string> | undefined
if (m5Filter.slug !== undefined) {
const sessions = loadSessionRows(db)
include = sessions ? slugSessionIncludeSet(sessions, m5Filter.slug) : new Set<string>()
}
// include.size === 0 (or a slug-less filter) → contribute nothing;
// the merged M5 output prints the explicit "no data in window" line.
if (include === undefined || include.size > 0) {
const rows = db
.prepare(
`
SELECT
session_id,
COALESCE(json_extract(data, '$.agent'), 'unknown') as agent,
CAST(json_extract(data, '$.tokens.input') AS REAL) as inp,
CAST(json_extract(data, '$.tokens.output') AS REAL) as outp,
CAST(json_extract(data, '$.tokens.reasoning') AS REAL) as rea
FROM message
WHERE json_extract(data, '$.role') = 'assistant'
AND json_extract(data, '$.tokens.input') IS NOT NULL
${m5Filter.since > 0 ? "AND time_created >= ?" : ""}
`,
)
.all(...(m5Filter.since > 0 ? [m5Filter.since] : [])) as {
session_id: string
agent: string
inp: number | null
outp: number | null
rea: number | null
}[]
for (const r of rows) {
if (include !== undefined && !include.has(r.session_id)) continue
const cur = agents.get(r.agent) ?? { msgCount: 0, totalTokens: 0 }
cur.msgCount++
cur.totalTokens += Number(r.inp ?? 0) + Number(r.outp ?? 0) + Number(r.rea ?? 0)
agents.set(r.agent, cur)
}
}
} else {
const agentRows = db
.prepare(
`
SELECT
COALESCE(json_extract(data, '$.agent'), 'unknown') as agent,
COUNT(*) as msg_count,
CAST(TOTAL(json_extract(data, '$.tokens.input')) AS REAL) as inp,
CAST(TOTAL(json_extract(data, '$.tokens.output')) AS REAL) as outp,
CAST(TOTAL(json_extract(data, '$.tokens.reasoning')) AS REAL) as rea
FROM message
WHERE json_extract(data, '$.role') = 'assistant'
AND json_extract(data, '$.tokens.input') IS NOT NULL
${since > 0 ? "AND time_created >= ?" : ""}
GROUP BY agent
`,
)
.all(...(since > 0 ? [since] : [])) as {
agent: string
msg_count: number
inp: number
outp: number
rea: number
}[]
for (const r of agentRows) {
agents.set(r.agent, {
msgCount: Number(r.msg_count ?? 0),
totalTokens: Number(r.inp ?? 0) + Number(r.outp ?? 0) + Number(r.rea ?? 0),
})
}
}
} finally {
db.close()
}
const messageCount = sessions.reduce((s, se) => s + se.msgCount, 0)
return { dbName, sessionCount: sessions.length, messageCount, sessions, agents }
}
function dbSummary(db: ReturnType<typeof processDb>) {
if (db.sessionCount === 0) return null
return ` ${db.dbName}: ${db.sessionCount} sessions, ${db.messageCount.toLocaleString()} messages`
}
function computeSessionMedians(dbs: ReturnType<typeof processDb>[]) {
const medians: number[] = []
for (const db of dbs) {
for (const s of db.sessions) {
const sorted = [...s.inputs].sort((a, b) => a - b)
medians.push(percentile(sorted, 50))
}
}
return medians
}
function computeSessionMaxes(dbs: ReturnType<typeof processDb>[]) {
const maxes: number[] = []
for (const db of dbs) {
for (const s of db.sessions) {
maxes.push(Math.max(...s.inputs))
}
}
return maxes
}
function computeSessionP90s(dbs: ReturnType<typeof processDb>[]) {
const p90s: number[] = []
for (const db of dbs) {
for (const s of db.sessions) {
const sorted = [...s.inputs].sort((a, b) => a - b)
p90s.push(percentile(sorted, 90))
}
}
return p90s
}
function computeOverallCacheRatio(dbs: ReturnType<typeof processDb>[]) {
let totalInput = 0
let totalCacheRead = 0
for (const db of dbs) {
for (const s of db.sessions) {
totalInput += s.totalInput
totalCacheRead += s.totalCacheRead
}
}
return totalInput > 0 ? totalCacheRead / totalInput : 0
}
function trafficLight(ratio: number): string {
if (ratio > 10) return "🟢"
if (ratio >= 3) return "🟡"
return "🔴"
}
const dbFiles = [
...readdirSync(DBS_DIR, { withFileTypes: true })
.filter((e) => e.isFile() && e.name.endsWith(".db") && (e.name === "octopus.db" || e.name.startsWith("octopus-")))
.map((e) => join(DBS_DIR, e.name)),
...collectSessionVolumeDbs(), // #2628
].sort()
for (const f of dbFiles) {
const size = statSync(f).size
if (size > 1_000_000_000 && since === 0) {
console.error(
`[WARN] ${f.split("/").pop()} is ${(size / 1e9).toFixed(1)} GB — query may be slow. Set SINCE_DAYS=<N> to scope to recent sessions.`,
)
}
}
const allDbs = dbFiles.map((p) => processDb(p, since, m5Filter))
console.log("# Token Telemetry: Context Hygiene (M4)")
console.log()
if (filtersActive) {
const parts = [sinceArg ? `--since ${sinceArg}` : "", slugArg ? `--slug ${slugArg}` : ""].filter(Boolean)
console.log(`Cycle-window filters active (${parts.join(" ")}): M2/M3/M5 constrained to the window; M1/M4 unchanged`)
console.log()
}
console.log("## Per-Database Summaries")
console.log()
for (const db of allDbs) {
const s = dbSummary(db)
if (s) console.log(s)
}
const totalSessions = allDbs.reduce((s, d) => s + d.sessionCount, 0)
const totalMessages = allDbs.reduce((s, d) => s + d.messageCount, 0)
console.log()
console.log(
`Total across ${allDbs.filter((d) => d.sessionCount > 0).length} databases: ${totalSessions} sessions, ${totalMessages.toLocaleString()} messages`,
)
const sessionMedians = computeSessionMedians(allDbs)
const sessionP90s = computeSessionP90s(allDbs)
const sessionMaxes = computeSessionMaxes(allDbs)
const cacheRatio = computeOverallCacheRatio(allDbs)
const sortedMedians = [...sessionMedians].sort((a, b) => a - b)
const sortedP90s = [...sessionP90s].sort((a, b) => a - b)
const sortedMaxes = [...sessionMaxes].sort((a, b) => a - b)
console.log()
console.log("## Aggregate Input Token Stats (per-session metrics)")
console.log()
console.log("| Metric | p50 | p90 | max |")
console.log("| ------ | --- | --- | --- |")
console.log(
`| Per-session median input | ${percentile(sortedMedians, 50).toLocaleString()} | ${percentile(sortedMedians, 90).toLocaleString()} | ${percentile(sortedMedians, 100).toLocaleString()} |`,
)
console.log(
`| Per-session p90 input | ${percentile(sortedP90s, 50).toLocaleString()} | ${percentile(sortedP90s, 90).toLocaleString()} | ${percentile(sortedP90s, 100).toLocaleString()} |`,
)
console.log(
`| Per-session max input | ${percentile(sortedMaxes, 50).toLocaleString()} | ${percentile(sortedMaxes, 90).toLocaleString()} | ${percentile(sortedMaxes, 100).toLocaleString()} |`,
)
console.log()
const ratioLabel = cacheRatio >= 1 ? `${cacheRatio.toFixed(1)}:1` : `1:${(1 / cacheRatio).toFixed(1)}`
const light = trafficLight(cacheRatio)
console.log(`## Cache Read / Input Ratio: ${ratioLabel} ${light}`)
console.log()
const desc =
cacheRatio > 10
? "Excellent — context reuse is very high, indicating effective caching"
: cacheRatio >= 3
? "Moderate — reasonable cache hits, room for improvement"
: "Low — consider strategies to increase context cache reuse"
console.log(` ${desc}`)
const mergedAgents: Map<string, { msgCount: number; totalTokens: number }> = new Map()
for (const db of allDbs) {
for (const [agent, stats] of db.agents) {
const existing = mergedAgents.get(agent)
if (existing) {
existing.msgCount += stats.msgCount
existing.totalTokens += stats.totalTokens
} else {
mergedAgents.set(agent, { ...stats })
}
}
}
let exploreTokens = 0
let workerTokens = 0
for (const [agent, stats] of mergedAgents) {
const lower = agent.toLowerCase()
if (lower.includes("explorer")) exploreTokens += stats.totalTokens
else if (lower.includes("worker")) workerTokens += stats.totalTokens
}
const m5Ratio = workerTokens > 0 ? exploreTokens / workerTokens : 0
function m5TrafficLight(ratio: number): string {
if (ratio > 2) return "🟢"
if (ratio >= 1) return "🟡"
return "🔴"
}
console.log()
console.log("## Explore / Execute Ratio (M5)")
console.log()
console.log("| Agent | Messages | Total tokens |")
console.log("| --------- | -------- | ------------ |")
const sortedAgents = [...mergedAgents.entries()].sort((a, b) => b[1].totalTokens - a[1].totalTokens)
for (const [agent, stats] of sortedAgents) {
console.log(
`| ${agent.padEnd(9)} | ${stats.msgCount.toLocaleString().padStart(7)} | ${stats.totalTokens.toLocaleString().padStart(12)} |`,
)
}
console.log()
if (filtersActive && mergedAgents.size === 0) {
console.log("[NOTE: no data in window for M5 — no messages match the requested window/slug]")
} else {
const m5Light = m5TrafficLight(m5Ratio)
console.log(`Explore/Execute: ${m5Ratio.toFixed(2)}:1 ${m5Light}`)
console.log()
const m5Desc =
m5Ratio > 2
? "Explorer-heavy — exploration dominates execution, good for discovery but may need more synthesis"
: m5Ratio >= 1
? "Balanced — reasonable split between exploration and execution"
: "Execution-heavy — workers are spending tokens on discovery work that explorers should handle"
console.log(` ${m5Desc}`)
}
// ---------------------------------------------------------------------------
// Compactor activity — compact-frequency proxy (#2601 pilot data).
// The compactor agent runs once per agent-initiated compaction, so its
// message count in the window approximates how often compaction fired.
// ---------------------------------------------------------------------------
console.log()
console.log("## Compactor Activity (#2601 compact-frequency proxy)")
console.log()
const compactor = mergedAgents.get("compactor")
if (compactor) {
console.log(
`compactor: ${compactor.msgCount.toLocaleString()} messages, ${compactor.totalTokens.toLocaleString()} tokens in window`,
)
console.log(" (per-run distribution = the #2601 pilot metric; rising zero-compact")
console.log(" share for short runs (bugfix / DAG task) with no late-stage degradation retires the pilot gate)")
} else {
console.log("[NOTE: no compactor messages in window — zero agent-initiated compactions recorded]")
}
// ---------------------------------------------------------------------------
// M2 — Stage token distribution (ledger-gated).
// Reads token-stage-ledger.jsonl (written by .octopus/plugin/token-stage-ledger.ts),
// reconstructs a per-root-session stage timeline, and attributes every
// assistant message's tokens to the stage that was active when the message
// was created. Skips cleanly when no ledger exists.
// ---------------------------------------------------------------------------
type LedgerEntry = { sessionID: string; stage: string; t: number }
function loadLedger(): Map<string, { stage: string; t: number }[]> | null {
if (!existsSync(LEDGER_PATH)) return null
const bySession = new Map<string, { stage: string; t: number }[]>()
let any = false
for (const line of readFileSync(LEDGER_PATH, "utf8").split("\n")) {
const trimmed = line.trim()
if (!trimmed) continue
try {
const e = JSON.parse(trimmed) as LedgerEntry
const arr = bySession.get(e.sessionID) ?? []
arr.push({ stage: e.stage, t: e.t })
bySession.set(e.sessionID, arr)
any = true
} catch {
// skip malformed lines
}
}
if (!any) return null
for (const arr of bySession.values()) arr.sort((a, b) => a.t - b.t)
return bySession
}
function stageTokensForDb(
dbPath: string,
ledger: Map<string, { stage: string; t: number }[]>,
since = 0,
slug?: string,
): Map<string, number> | null {
let db: Database | null = null
try {
db = new Database(dbPath, { readonly: true })
} catch {
return null
}
try {
const sessions = loadSessionRows(db)
if (!sessions) return null
// timelines keyed by root sessions present in this db
const timelines = new Map<string, { stage: string; t: number }[]>()
for (const [sid, entries] of ledger) {
if (sessions.has(sid)) timelines.set(sid, entries)
}
if (timelines.size === 0) return null
let include: Set<string> | undefined
if (slug !== undefined) {
include = slugSessionIncludeSet(sessions, slug)
if (include.size === 0) return null // no session matches the slug
}
const rootOf = (id: string): string => {
let cur = id
let guard = 0
while (guard++ < 100) {
const parent = sessions.get(cur)?.parent_id
if (!parent) break
cur = parent
}
return cur
}
const stageAt = (rootId: string, time: number): string | null => {
const tl = timelines.get(rootId)
if (!tl) return null
let stage: string | null = null
for (const e of tl) {
if (e.t <= time) stage = e.stage
else break
}
return stage
}
const byStage = new Map<string, number>()
const rows = db
.prepare(
`
SELECT session_id, time_created,
CAST(json_extract(data, '$.tokens.input') AS REAL) as inp,
CAST(json_extract(data, '$.tokens.output') AS REAL) as outp,
CAST(json_extract(data, '$.tokens.reasoning') AS REAL) as rea
FROM message
WHERE json_extract(data, '$.role') = 'assistant'
AND json_extract(data, '$.tokens.input') IS NOT NULL
${since > 0 ? "AND time_created >= ?" : ""}
`,
)
.all(...(since > 0 ? [since] : [])) as {
session_id: string
time_created: number
inp: number | null
outp: number | null
rea: number | null
}[]
for (const r of rows) {
if (include !== undefined && !include.has(r.session_id)) continue
const stage = stageAt(rootOf(r.session_id), r.time_created)
if (!stage) continue
const tokens = Number(r.inp ?? 0) + Number(r.outp ?? 0) + Number(r.rea ?? 0)
byStage.set(stage, (byStage.get(stage) ?? 0) + tokens)
}
return byStage
} finally {
db.close()
}
}
// ---------------------------------------------------------------------------
// M3 — Review rework fraction (#2591).
// Two sources, merged with dedup by review identity `{slug}/reviews/{stage}`
// (the ACTIVE status.json wins when both exist — it is canonical):
// 1. ACTIVE runs — status.json under the Tier 1 location
// .octopus/runs/{slug}/reviews/{stage}/status.json (reads history[],
// legacy alias rounds[], current_round) and the legacy
// .artifacts/**/reviews/*/status.json tree. In-flight runs only: the
// active workspace is deleted at archive-at-close, so this source alone
// structurally empties as runs close.
// 2. ARCHIVED runs — the committed archive bundle
// .octopus/runs/archive/{slug}.json. Bundles store digests, not
// status.json content, so the per-review round count is reconstructed
// from the documented Tier 1 layout `reviews/{stage}/round{N}/…`
// (templates/runs-layout.md) by counting distinct roundN path segments
// per stage across index.artifacts[].path. This archive source is what
// makes M3 durable.
// ---------------------------------------------------------------------------
type ReviewRounds = Map<string, number> // `${slug}/reviews/${stage}` -> rounds
function collectActiveReviewRounds(roots: string[]): { rounds: ReviewRounds; startedMs: Map<string, number> } {
const out: ReviewRounds = new Map()
const startedMs = new Map<string, number>()
const walk = (dir: string, top: string) => {
let entries: ReturnType<typeof readdirSync>
try {
entries = readdirSync(dir, { withFileTypes: true })
} catch {
return
}
for (const e of entries) {
if (e.name === "_archive" || e.name === "archive") continue
const full = join(dir, e.name)
if (e.isDirectory()) walk(full, top)
else if (e.name === "status.json" && dir.includes("/reviews/")) {
try {
const data = JSON.parse(readFileSync(full, "utf8")) as {
rounds?: unknown[]
history?: unknown[]
current_round?: number
started_at?: string
}
// Canonical field is `history[]` (per review-status.schema.json);
// `rounds[]` is a legacy alias that maps to it. Prefer the array
// forms; fall back to current_round.
let rounds = 0
if (Array.isArray(data.rounds) && data.rounds.length > 0) rounds = data.rounds.length
else if (Array.isArray(data.history) && data.history.length > 0) rounds = data.history.length
else if (typeof data.current_round === "number" && data.current_round > 0) rounds = data.current_round
if (rounds > 0) {
const key = relative(top, dir).split("\\").join("/")
out.set(key, rounds)
if (typeof data.started_at === "string") {
const t = Date.parse(data.started_at)
if (!Number.isNaN(t)) startedMs.set(key, t)
}
}
} catch {
// skip unreadable / malformed status files
}
}
}
}
for (const root of roots) walk(root, root)
return { rounds: out, startedMs }
}
function collectBundleReviewRounds(archiveDir: string): { rounds: ReviewRounds; closedMs: Map<string, number> } {
let files: string[] = []
try {
files = readdirSync(archiveDir).filter((f) => f.endsWith(".json"))
} catch {
return { rounds: new Map(), closedMs: new Map() } // no archive dir (e.g. a fresh checkout) — fine
}
const out: ReviewRounds = new Map()
const closedMs = new Map<string, number>()
for (const f of files) {
try {
const bundle = JSON.parse(readFileSync(join(archiveDir, f), "utf8")) as {
index?: { artifacts?: { path?: unknown }[] }
meta?: { closed_at?: string; updated_at?: string; created_at?: string }
}
// Identity = the bundle filename stem (= the archived run's workspace
// dir name). meta.slug is NOT unique — epic task-node bundles carry the
// parent epic slug while filenames stay per-node.
const slug = f.replace(/\.json$/, "")
const closedRaw = bundle.meta?.closed_at ?? bundle.meta?.updated_at ?? bundle.meta?.created_at
// Distinct roundN segments per review stage across artifact paths
// (paths may or may not carry the slug prefix — match the segment).
const byStage = new Map<string, Set<string>>()
for (const a of bundle.index?.artifacts ?? []) {
if (typeof a?.path !== "string") continue
const hit = a.path.match(/reviews\/([^/]+)\/(round\d+)\//)
if (!hit?.[1] || !hit[2]) continue
const set = byStage.get(hit[1]) ?? new Set<string>()
set.add(hit[2])
byStage.set(hit[1], set)
}
for (const [stage, rounds] of byStage) {
if (rounds.size > 0) out.set(`${slug}/reviews/${stage}`, rounds.size)
if (closedRaw !== undefined) {
const t = Date.parse(closedRaw)
if (!Number.isNaN(t)) closedMs.set(`${slug}/reviews/${stage}`, t)
}
}
} catch {
// skip unreadable / malformed bundles
}
}
return { rounds: out, closedMs }
}
// --- M2 output ---
const ledger = loadLedger()
console.log()
console.log("## Stage Token Distribution (M2)")
console.log()
if (!ledger) {
console.log("[NOTE: token-stage-ledger.jsonl absent — M2 skipped]")
console.log(" (enable the .octopus/plugin/token-stage-ledger plugin to populate)")
} else {
const mergedStages = new Map<string, number>()
for (const dbPath of dbFiles) {
const byStage = stageTokensForDb(dbPath, ledger, windowOrSince, slugArg)
if (!byStage) continue
for (const [stage, tokens] of byStage) mergedStages.set(stage, (mergedStages.get(stage) ?? 0) + tokens)
}
const grandTotal = [...mergedStages.values()].reduce((a, b) => a + b, 0)
if (grandTotal === 0) {
if (filtersActive) {
console.log("[NOTE: no data in window for M2 — no attributed tokens match the requested window/slug]")
} else {
console.log("[NOTE: ledger present but no sessions matched — M2 has no attributed data yet]")
}
} else {
const sortedStages = [...mergedStages.entries()].sort((a, b) => b[1] - a[1])
console.log("| Stage | Tokens | Share |")
console.log("| ------------- | ------ | ----- |")
for (const [stage, tokens] of sortedStages) {
const pct = ((tokens / grandTotal) * 100).toFixed(1)
console.log(`| ${stage.padEnd(13)} | ${tokens.toLocaleString().padStart(13)} | ${pct.padStart(5)}% |`)
}
const reviewTokens = mergedStages.get("review") ?? 0
const reviewShare = (reviewTokens / grandTotal) * 100
const m2Light = reviewShare > 60 ? "🔴" : reviewShare >= 35 ? "🟡" : "🟢"
console.log()
console.log(`Review-stage share: ${reviewShare.toFixed(1)}% ${m2Light}`)
console.log(
` ${
reviewShare > 60
? "Review dominates token spend — possible over-reviewing"
: reviewShare >= 35
? "Moderate review spend"
: "Review spend is proportionate"
}`,
)
}
}
// --- M3 output ---
const runsDir = join(process.cwd(), ".octopus", "runs")
const artifactsDir = join(process.cwd(), ".artifacts")
const archiveDir = join(runsDir, "archive")
const activeRoots = [runsDir, artifactsDir].filter((d) => existsSync(d))
console.log()
console.log("## Review Rework (M3)")
console.log()
const active = collectActiveReviewRounds(activeRoots)
const archived = collectBundleReviewRounds(archiveDir)
const activeRounds = active.rounds
const archivedRounds = archived.rounds
const mergedRounds: ReviewRounds = new Map(activeRounds)
let archivedOnly = 0
for (const [key, rounds] of archivedRounds) {
if (mergedRounds.has(key)) continue // active status.json is canonical
mergedRounds.set(key, rounds)
archivedOnly++
}
// Cycle-window filters: constrain to reviews whose run identity contains the
// slug and whose start (active) / close (archived) time falls in the window.
// Reviews without a parseable timestamp are excluded when a window is set —
// strict, so filtered numbers never silently fall back to all-time totals.
const windowRounds: ReviewRounds = new Map()
for (const [key, rounds] of mergedRounds) {
const identity = key.split("/reviews/")[0] ?? key
if (slugArg !== undefined && !identity.includes(slugArg)) continue
if (windowSince !== undefined) {
const t = active.startedMs.get(key) ?? archived.closedMs.get(key)
if (t === undefined || t < windowSince) continue
}
windowRounds.set(key, rounds)
}
if (mergedRounds.size === 0) {
console.log(
activeRoots.length === 0 && archivedRounds.size === 0
? "[NOTE: no .octopus/runs or .artifacts directory in cwd — M3 skipped]"
: "[NOTE: no review rounds found (active status.json or archive bundles) — M3 skipped]",
)
} else if (filtersActive && windowRounds.size === 0) {
console.log("[NOTE: no data in window for M3 — no reviews match the requested window/slug]")
} else {
const roundsMap = filtersActive ? windowRounds : mergedRounds
const reviews = roundsMap.size
const totalRounds = [...roundsMap.values()].reduce((a, b) => a + b, 0)
const reworkRounds = [...roundsMap.values()].reduce((a, b) => a + (b - 1), 0)
const nonFirstPass = [...roundsMap.values()].filter((r) => r > 1).length
const fraction = totalRounds > 0 ? reworkRounds / totalRounds : 0
const nonFirstPct = (nonFirstPass / reviews) * 100
const m3Light = fraction > 0.3 ? "🔴" : fraction >= 0.15 ? "🟡" : "🟢"
if (filtersActive)
console.log(`Window filter: ${windowRounds.size}/${mergedRounds.size} reviews match (--since/--slug)`)
console.log(`Reviews: ${reviews} | total rounds: ${totalRounds} | rework rounds: ${reworkRounds}`)
console.log(
`Sources: ${activeRounds.size} active status.json + ${archivedOnly} archive bundles (dedup by slug+stage)`,
)
console.log(`Non-first-pass reviews: ${nonFirstPass}/${reviews} (${nonFirstPct.toFixed(0)}%)`)
console.log()
console.log(`Rework fraction: ${(fraction * 100).toFixed(1)}% ${m3Light}`)
console.log(
` ${
fraction > 0.3
? "High rework — review findings not actionable or design unclear"
: fraction >= 0.15
? "Moderate rework — some review churn"
: "Low rework — reviews converge efficiently"
}`,
)
}
+519
View File
@@ -0,0 +1,519 @@
---
name: review-artifact
description: >-
Use ONLY after analyze-dag has produced the DAG artifact — review the task DAG (review-dag single gate) — OR when auditing the <instance-root>/ process infrastructure itself (audit-process). Spawns N parallel Explorer reviewers (1 dimension each) against the matching checklist, synthesizes findings with a Worker Synthesizer, and iterates with a Worker reviser until convergence. The legacy roadmap / design-space / iteration-plan targets were archived 2026-08-21 ([org-internal #3072] phase 3, <instance-root>/archive/). Do NOT use for code review — use the review-code skill for that.
triggers:
- audit-process
- review-dag
- dag review
- DAG 评审
- process audit
- parallel review
- reviewer
- checklist review
- mandatory gate
# `stage` is set to the primary gate `audit-process`; the second live target
# (review-dag) carries its own stage id in the Target Selection table below.
stage: audit-process
role: Reviewer
---
> Core 中立版(Increment 6a 改写,原 deferHard verbatimDir)。机制、结构与 frontmatter 保持;实例术语(工具名、路径、工单号)按 `core/adapters/TERMINOLOGY.md` 绑定到具体实例。
# Skill: review-artifact
## Tool Availability & Path Resolution
This skill's reviewers (parallel Explorer subagents) read two kinds of files.
Some runs lack the `gitea_wiki__*` MCP tools (cross-repo scenario, [org-internal #2115]); the
fallbacks below keep reviews working without them.
The Orchestrator probes availability **before dispatching reviewers** (Phase A
step 1, `_shared/review-pipeline-phases.md`) and fails fast if neither path is
usable, so every reviewer has a working read path by the time it runs.
### Wiki artifacts (DAG artifact / review reports)
- **Primary**: `wiki 读写 API(见 TERMINOLOGY(owner, repo, page_name)` — use when the
tool is available. `page_name` construction is in
`_shared/gitea-read-patterns.md` Pattern 1.
- **Fallback** (when `gitea_wiki__*` is unavailable): read the **local wiki
clone** at `<wiki-clone-root>/{repo}-wiki/` — a git checkout of the wiki repo,
kept current. Wiki filenames are URL-encoded (`/``%2F`, with a possible
`.-` before `.md`), so resolve the file with `glob` rather than constructing
one fixed path:
```
glob(pattern="*{slug}*{page-title}*.md", path="<wiki-clone-root>/{repo}-wiki/")
# then read the returned path
```
### Checklists (`core/checklists/*.md`)
`<instance-root>/` **is git-tracked** (skills, checklists, rules — only runtime
artifacts under `<instance-root>/locks/`, `entropy-baseline.json`, etc. are
gitignored). Therefore the relative paths referenced throughout this skill
(`core/checklists/dag-single-gate.md`, etc.) **resolve correctly inside
any git worktree** — no absolute path is needed.
- **Primary**: read `core/checklists/<name>.md` relative to the project /
worktree root (the reviewer subagent's CWD).
- **Fallback** (non-standard CWD only): read from the main project directory,
e.g. `/data/{repo}/core/checklists/<name>.md`.
## Target Selection
This skill reviews different artifact types. Identify the target from the pipeline stage that triggered the review:
| Target | Stage ID | Checklist | Dimensions | Revision Role | Output namespace |
|---|---|---|---|---|---|
| **process** | `audit-process` | `core/checklists/audit-process.md` | 10 | Remediator | `audit/{date}/` |
| **review-dag** | `review-dag` | `core/checklists/dag-single-gate.md` | 3 | Analyst | `{epic-slug}/reviews/review-dag/` |
> **Retired targets (historical)**: the `roadmap`, `design-space`, and `plan`
> targets were archived on 2026-08-21 per [org-internal #3072] phase 3 (ruling:
> `docs/workflow-refactor/phase3-retirement-ruling.md`) — the legacy
> production skills they gated no longer exist (see `<instance-root>/archive/`).
> Their stage ids (`review-roadmap`, `review-design-space`,
> `review-iteration-plan`) were retired from the stage registry in the same
> ruling. Historical review pages under `{slug}/reviews/roadmap|design-space|plan/`
> remain readable; no new reviews run against those targets.
The stage IDs remain as-is — they are used by the stage registry and auto-approve system. The skill name is `review-artifact`, but the stage IDs do not change.
> **Progressive disclosure ([org-internal #3734])**: this skill has two disjoint live
> targets — dispatch an auditor/reviewer with
> `skills: ["review-artifact:target-process"]` or
> `["review-artifact:target-review-dag"]` (preamble + that target only)
> instead of the full body. Tool availability, target selection, and shared
> pipeline mechanics are unmarked preamble and always inject. Full-body
> remains the default when no selector is passed.
## Shared Pipeline Mechanics
This skill follows the standardized parallel-review pipeline. The full mechanics are in:
- `<!-- inject: ../_shared/review-pipeline-phases.md -->` — Phase A through Phase E
- **Phase A step 0**: move the source issue to the `review` column on the
Pipeline Stages board per `_shared/gitea-write-patterns.md` Pattern 7.5
(see `_shared/review-pipeline-phases.md` for the canonical orchestrator
actions — step 0 is now part of the shared flow).
- `<!-- inject: ../_shared/review-orchestrator-rules.md -->` — mandatory orchestrator rules
- `<!-- inject: ../_shared/review-final-report-template.md -->` — final report format
- `<!-- inject: ../_shared/review-revision-prompt.md -->` — Phase C revision prompt template
- `<!-- inject: ../_shared/compaction-round-boundary.md -->` — round-boundary compaction procedure (Phase D)
**Context compaction**: this skill compacts at every review round ≥ 2. The
round-boundary procedure (`compaction-round-boundary.md`) is injected directly
above — that is the normative source; do not restate it here. At the stage
level, the main session also compacts after this skill converges, per
`core/rules/compact.md` §"Stage-boundary compaction".
Each target binds the canonical phase placeholders and defines target-specific overrides in its section below.
### JSON Schemas
| Schema File | Used For |
|---|---|
| `core/schemas/reviewer-output.schema.json` | Per-dimension reviewer findings |
| `core/schemas/synthesis.schema.json` | Orchestrator round synthesis |
| `core/schemas/review-status.schema.json` | Review lifecycle tracking |
### Reviewer Prompt
The Orchestrator MUST use the standardized reviewer prompt template at `core/skills/_shared/review-reviewer-prompt.md` when dispatching each reviewer. The template is the same for all reviewers; the Orchestrator fills in skill-level constants and per-dimension values. Each target defines its own constants in its section below.
### Agent Roles
| Role | Agent Type | Count | Responsibility |
|---|---|---|---|
| Orchestrator | Worker | 1 | Spawns reviewers, reads synthesis, drives revision loop |
| Reviewer | Explorer | N (per target) | Reviews ONE dimension, outputs structured JSON |
| Revision Role | Worker | 1 | Processes findings, revises artifact (role varies by target) |
All reviewers run in parallel. The orchestrator and revision role run sequentially.
### Preconditions
Before launching any review, confirm:
- [ ] The target artifact exists (source varies by target: wiki pages for
review-dag; the local `<instance-root>/` corpus for audit-process — no upstream
producer, self-authored).
- [ ] The matching checklist is accessible (see target selection table above).
- [ ] `core/schemas/` contains the shared review schemas (`reviewer-output`, `synthesis`, `review-status`).
- [ ] `slug` is known (for process audits, `slug` is the literal `audit`).
Each target adds specific preconditions listed in its section below. If any precondition is unmet, abort and inform the user.
> ## Retired targets (historical, [org-internal #3072] phase 3)
The three legacy production-review targets — **roadmap** (stage
`review-roadmap`, checklist `roadmap.md`, revision role Analyst),
**design-space** (stage `review-design-space`, checklists
`requirements-analysis.md` + `design.md`, revision role Architect), and
**plan** (stage `review-iteration-plan`, checklist `iteration-plan.md`,
revision role Planner) — were archived on 2026-08-21 together with the
production skills they gated (ruling:
`docs/workflow-refactor/phase3-retirement-ruling.md`; skills and exclusive
checklists/templates recoverable under `<instance-root>/archive/`). Their stage ids
are retired from the stage registry; no new reviews run against them, and
historical review pages under `{slug}/reviews/{roadmap|design-space|plan}/`
stay readable as produced. Live targets: **process** (below) and
**review-dag** (single gate).
---
<!-- section:target-process -->
## Target: process (audit)
The audit target is the **SDLC factory itself** — the set of skills, checklists, templates, and schemas under `<instance-root>/` — judged for internal consistency, cross-reference integrity, and conformance to audit standards (ISO 19011:2018, IEEE 1028-2008) and `AGENTS.md` conventions.
**Scope boundary**: This skill audits the *definition* of the pipeline. It does NOT audit whether a project *followed* the pipeline — that is the `retrospective` skill's responsibility.
### Audit Subject
| Area | Path | What is audited |
|---|---|---|
| Skills | `core/skills/**/SKILL.md` | Frontmatter, references, pipeline wiring |
| Checklists | `core/checklists/*.md` | Coverage, orphans, dimension count match |
| Templates | `core/templates/*.md` | Orphans, referenced existence |
| Schemas | `core/schemas/*.json` | Validity, field alignment with skill text |
| Conventions | `AGENTS.md` | Engineering-convention conformance |
### Preconditions
- [ ] `core/skills/` exists and contains at least one `SKILL.md`.
- [ ] `core/checklists/audit-process.md` is accessible.
- [ ] `core/schemas/` contains the shared review schemas (`reviewer-output`, `synthesis`, `review-status`).
No `.artifacts/{slug}/` inputs are required — the audit reads `<instance-root>/` itself. Audit outputs are published as Gitea wiki pages under `audit/{date}/`.
### Audit Dimensions
| # | Dimension | Label | Checklist Ref | Files to Read |
|---|---|---|---|---|
| 1 | INV | Inventory Completeness | INV 1.11.7 | `core/skills/` tree, `checklists/`, `templates/`, `schemas/` listings |
| 2 | XREF | Cross-Reference Integrity | XREF 2.12.7 | ALL `SKILL.md`; resolve every referenced path |
| 3 | FM | Frontmatter Conformance | FM 3.13.7 | Frontmatter block of every `SKILL.md` |
| 4 | NAM | Naming Convention | NAM 4.14.6 | ALL `SKILL.md`, checklist filenames, schema enums |
| 5 | FLOW | Pipeline Cohesion | FLOW 5.15.6 | Preconditions + output sections of every `SKILL.md` |
| 6 | SCH | Schema Contract | SCH 6.16.5 | `core/schemas/*.json` + skills that reference them |
| 7 | DUP | Duplication & Drift | DUP 7.17.4 | ALL `SKILL.md` + ALL `checklists/*.md` |
| 8 | STD | Audit Standards (ISO/IEEE) | STD 8.18.7 | `review-*` skills, `review-artifact` (target: `audit-process`) itself |
| 9 | AGT | AGENTS.md Conformance | AGT 9.19.5 | ALL `SKILL.md` + `AGENTS.md` |
| 10 | TRC | Traceability | TRC 10.110.5 | `review-*` skills, schemas, status artifacts |
### Phase A Threshold
The process target has 10 dimensions, so Phase A is satisfied when **≥8 of 10
dimension findings are produced after retries** (≥75% coverage). If fewer
than 8 dimension JSONs are available after
retries, abort the round with FAIL — an audit round must not proceed with an
underspecified fraction of dimensions covered. Crashed/timed-out dimensions are
marked `UNRESOLVED` and retried in the next round.
### Auditor Constants
In auditor output, the `dimension` field carries the audit dimension code (INV, XREF, FM, ...) and `slug` in the commit status is the literal `audit`.
The Orchestrator MUST use the standardized auditor prompt template from `core/skills/_shared/review-reviewer-prompt.md` with these constants:
- Model tier: default session reviewer model (`Reviewer` Explorer role, `_shared/roles/reviewer.yaml`) — the process target sets no `reviewer-tier` override; record any assignment change here so sibling review skills stay comparable (TRC 10.5)
- `{REVIEWER_ROLE}`: process auditor
- `{BASELINE_SECTION}`:
```
## Audit Subject
You are auditing the <instance-root>/ process infrastructure ITSELF — the SDLC factory of skills, checklists, templates, and schemas. You are NOT auditing any application code, and NOT auditing whether a project followed the process.
```
- `{READ_FIRST_STEPS}`:
1. Read every file listed above. Do NOT skip any file.
2. For XREF/SCH dimensions: for every path or field referenced in a skill, actually resolve it (Read/Glob) and confirm it exists and matches. A referenced-but-missing path or a field-name mismatch is a finding.
3. For all other dimensions, read every assigned file and flag any cross-file inconsistency (terminology, numbering, drift) you find.
- `{ARTIFACT_NOUN}`: process infrastructure
- `{FINDING_DETAILS}`: file path and line number, a summary, detailed explanation, and quoted evidence from the file
- `{FINDING_ID_EXAMPLE}`: INV-F001
- `{CONTRADICTION_RULE}`:
6. (not applicable — the audit is a self-contained inspection, not a baseline comparison)
- `{SEVERITY_DEFINITIONS}`:
- BLOCKER: broken cross-reference (skill references a non-existent checklist/template/schema/path), missing required frontmatter `description` (skill never triggers), a pipeline stage with no producer for its declared precondition, schema field mismatch that would make written artifacts invalid
- MAJOR: orphan checklist/template/schema, dimension count mismatch between a review skill and its checklist, naming inconsistency for a dimension code or role, missing convergence/approval gate, AGENTS.md command divergence (e.g. `bun test` instead of `bun run test:parallel`)
- MINOR: terminology drift, duplicated rule that should live in one place, inconsistent standard-number formatting, missing cross-reference comment
- INFO: observation or improvement suggestion beyond the checklist
- `{LOOKS_FINE_PHRASE}`: the pipeline looks fine
### Phase Bindings
| Placeholder | Value |
|---|---|
| `{DIMENSION_COUNT}` | 10 |
| `{REVISION_ROLE}` | Remediator |
| `{REVIEW_TYPE}` | audit-process |
| `{STAGE}` | audit-process |
| `{REVIEWER_OR_AUDITOR}` / `{REVIEWER_OR_AUDITOR_NAME}` | auditor / Audit |
| `{REVIEWER_OR_AUDIT_LOOP}` | Re-audit |
| Output mode | Tier 1 local files under `<runs-root>/audit/reviews/audit-process/round{N}/findings-{DIMENSION_CODE}.json` (validated by `core/schemas/reviewer-output.schema.json`); synthesis wiki page + commit status stay Tier 2 |
| Commit status context | `pipeline/audit-process` |
| Wiki namespace | `audit/{date}/` |
| Status tracking | Commit status `pipeline/audit-process` (via REST, `_shared/gitea-write-patterns.md` Pattern 8) — `slug: "audit"` |
| Date resolution | `{date}` resolved as `YYYY-MM-DD` |
| Task file names | `task-synthesizer`, `task-remediator` |
### Phase E — Close-actor (process target)
After the final report is written and the `## 工件索引` REVIEW row is
updated per the shared Phase E step 2, IF this route closes the source issue
(audit-only close, the common Kind/Enhancement process-improvement path that
does NOT run verify), execute archive-at-close per
`core/rules/artifact-index-guards.md` guard #3: edit the index comment
in place — add the canonical archive banner (defined in
`_shared/gitea-write-patterns.md` Pattern 10) above the table, and set
every row's 重读 column to `ARCHIVE`. Mechanism details (single-comment
invariant, no URL deletion, reversibility) per Pattern 10.
### Remediator Constraints
The Remediator revises `<instance-root>/` files. Constraints:
- Do NOT change the meaning of a skill's workflow to satisfy a finding; fix the inconsistency, not the intent.
- Preserve frontmatter `name`/`description` semantics; only correct conformance issues.
- When fixing a broken cross-reference, fix the reference OR create the missing file — whichever preserves the pipeline's intent; explain the choice.
### Output Artifacts
```
<runs-root>/audit/reviews/audit-process/
├── index.json # Tier 1 manifest (one row per findings/synthesis/task file; validated by runs-index.schema.json)
├── status.json # Review lifecycle tracking (shared Phase A step 0.5; validated by review-status.schema.json)
└── round{N}/
├── task-remediator.md # Remediator input prompt (Tier 1 copy; index row type `remediation-prompt`)
├── task-synthesizer.md # Synthesizer input prompt (Tier 1; index row type `synthesis-prompt`)
├── findings-{DIMENSION_CODE}.json # Auditor dimension findings (Tier 1 local, validated by reviewer-output.schema.json; index row type `review-findings`)
└── synthesis-return.md # Synthesizer return summary (index row type `other`)
audit/{date}/ # Wiki page hierarchy (Tier 2)
├── round{N}/
│ ├── task-remediator # Remediator input (wiki page)
│ ├── revision-summary # Remediator revision summary
│ └── synthesis # Orchestrator synthesis (```json block, wiki page — audits have no source issue comment)
├── round{N+1}/...
└── final/
└── report # Final human-readable report
```
> **Wiki namespace override**: the audit target writes `task-{ROLE}`,
> `revision-summary`, `synthesis`, and `final/report` under `audit/{date}/`
> (date-slug), NOT the shared `{slug}/reviews/{stage}/` formula. See the Phase
> Bindings `Wiki namespace` row above and `_shared/review-pipeline-phases.md`
> Phase C step 1 / Phase E step 1 for the audit carve-out.
### References
- `core/checklists/audit-process.md` — The authoritative checklist for all 10 audit dimensions
- `core/schemas/` — Shared JSON schemas (reviewer-output, synthesis, review-status) reused by this audit
- `retrospective` skill — Complementary: audits a project's *adherence* to the process; this skill audits the process *definition* itself
- ISO 19011:2018 — Guidelines for auditing management systems (independence, evidence-based, documented)
- IEEE 1028-2008 — Software Reviews and Audits (completeness, repeatability, severity classification)
---
<!-- section:target-review-dag -->
## Target: review-dag
The DAG review is a **single gate** (spec-02 §1 D-02) that replaces `review-design-space` +
`review-iteration-plan` for a DAG artifact. It reviews one artifact — the DAG
at wiki page `{epic-slug}/dag` (NOT per-size-tier pages) — against
`core/checklists/dag-single-gate.md` with three fixed dimensions. It saves
orchestration overhead, not the revision loop — the three dimensions each
review an independent risk surface.
### Preconditions
- [ ] The DAG artifact exists at wiki page `{epic-slug}/dag` (read via `wiki 读写 API(见 TERMINOLOGY`).
- [ ] The DAG's `dag_metrics.review_depth` is set (D1D4) — review depth, reviewer count, `dim_split`, and the Phase D round cap are all derived from this field, NOT from `Size/*` labels.
- [ ] `core/checklists/dag-single-gate.md` is accessible.
- [ ] `{epic-slug}` is known.
### Review Dimensions
Fixed dimension ids — do NOT rename:
| # | Dimension | Label | Checklist Ref | Files to Read |
|---|---|---|---|---|
| 1 | TOPO | Topology | `core/checklists/dag-single-gate.md` §TOPO | `{epic-slug}/dag` |
| 2 | REQMAP | Requirement Mapping | `core/checklists/dag-single-gate.md` §REQMAP | `{epic-slug}/dag` (+ `{epic-slug}/dag-nodes/{node-id}` subpages when AC detail is sunk) |
| 3 | RELEASE | Rolling Release | `core/checklists/dag-single-gate.md` §RELEASE | `{epic-slug}/dag` |
> **REQMAP reviews task nodes only** — milestone nodes carry no `req_refs` /
> `acceptance_criteria` (only a DoD) and are not part of the REQMAP surface.
### Review Depth (from `dag_metrics.review_depth`)
Review depth is derived from the DAG's `dag_metrics.review_depth` (D1D4), NOT
from `Size/*` labels.
| Depth | Reviewers | dim_split |
|---|---|---|
| D1 | 1 | `all` |
| D2 | 3 | `TOPO:1 REQMAP:1 RELEASE:1` |
| D3 | 5 | `TOPO:2 REQMAP:2 RELEASE:1` |
| D4 | 5 ([org-internal #3061] cap) | `TOPO:2 REQMAP:2 RELEASE:1` |
> **D4 cap ([org-internal #3061])**: D4 keeps its own rounds budget (4) but its fanout is
> capped at D3 level (5 reviewers) — under the 3-dimension + checklist-bound
> review the marginal coverage of 9 reviewers was redundancy, and round
> iteration is the backstop.
### Reviewer slots & findings files (dim_split > 1)
`dim_split` may assign MORE THAN ONE reviewer to a dimension (D3/D4
`TOPO:2`). Each reviewer within a dimension occupies a 1-based **slot**
and writes its OWN findings file — parallel reviewers must never share one
file (concurrent write race):
- 1 reviewer on the dimension → `findings-{DIM}.json` (unchanged shape).
- n > 1 reviewers → `findings-{DIM}-r{k}.json`, k = 1..n — e.g. a D3/D4 round
(dim_split `TOPO:2 REQMAP:2 RELEASE:1`) produces `findings-TOPO-r1.json` /
`-r2.json`, `findings-REQMAP-r1.json` / `-r2.json`, and a single
`findings-RELEASE.json` (RELEASE has one reviewer at every depth ≥ D2).
- Multi-slot findings JSONs MUST set `reviewer_slot` (their k) and
`reviewer_count` (n) — optional fields in
`core/schemas/reviewer-output.schema.json`. Finding ids inside slot
files MUST be slot-prefixed (`{DIM}R{k}-F{NNN}`, e.g. `TOPOR1-F001`) so ids
stay unique across a dimension's slot files. This preserves per-reviewer
attribution (retrospective overlap analysis, [org-internal #2996] step ①a) and makes the
write race impossible by construction.
- Synthesis input for a dimension = ALL `findings-{DIM}*.json` in the round
directory — the synthesizer already globs every `findings-*.json`; the slot
files of one dimension aggregate as that dimension's findings.
- Phase A's "dimension produced" check: a dimension counts as produced only
when EVERY assigned slot's file exists.
### Reviewer Constants
- Model tier: `reviewer-tier` (per `analyze-dag` §Exec-Resource Configuration — reviewer-tier's only allowed consumer; corresponds to the `Reviewer` Explorer role, `_shared/roles/reviewer.yaml`)
- `{REVIEWER_ROLE}`: DAG reviewer
- `{BASELINE_SECTION}`:
```
## Baseline
The artifact under review is the DAG at wiki page `{epic-slug}/dag` (read via `gitea_wiki__get_page`). Read it first for orientation. Its `dag_metrics.review_depth` (D1D4) sets this review's depth and round cap.
```
- `{READ_FIRST_STEPS}`:
1. Read the DAG artifact at `{epic-slug}/dag` first for orientation.
2. Read `core/checklists/dag-single-gate.md`.
3. For REQMAP when AC detail is sunk: follow the `{node-id} → {epic-slug}/dag-nodes/{node-id}` pointers to read the subpage ACs.
- `{ARTIFACT_NOUN}`: DAG artifact
- `{FINDING_DETAILS}`: location in the document, a summary, detailed explanation, quoted evidence from the document
- `{FINDING_ID_EXAMPLE}`: TOPO-F001
- `{CONTRADICTION_RULE}`:
6. When a DAG element contradicts its own declared topology, requirement mapping, or rolling-release plan, this is at minimum a MAJOR finding. When a required DAG section is entirely absent, this is a BLOCKER.
- `{SEVERITY_DEFINITIONS}`:
- BLOCKER: cycle in the DAG edge graph (TOPO 环检测), milestone missing at a `cross_session_in ≥ 2` convergence point (TOPO 里程碑位置), ≥3 task nodes below the granularity floor (`estimated_sessions < 0.25`, TOPO 粒度下限), a requirement with zero node coverage (REQMAP 需求覆盖)
- MAJOR: missing or reversed dependency edge (TOPO 依赖正确性), giant node (`estimated_sessions ≥ 2`) needing split (TOPO 大小均匀性), `estimated_hours`/`estimated_sessions` field inconsistency (`|estimated_hours 8 × estimated_sessions| > 2`), a single task node below the granularity floor, AC missing error/boundary-path coverage (REQMAP AC 路径覆盖), contract↔node AC inconsistency (REQMAP 契约↔节点一致性), task node with empty/invalid `req_refs` (REQMAP 节点溯源)
- MINOR: `1 < estimated_sessions < 2` (over-upper-limit, suggest split), formatting issue, missing cross-reference, terminology drift, revision record not initialized
- INFO: observation or recommendation beyond the checklist, including the TOPO page-size signal (see below) — does not affect any gate verdict
- `{LOOKS_FINE_PHRASE}`: the DAG looks fine
### Phase Bindings
| Placeholder | Value |
|---|---|
| `{DIMENSION_COUNT}` | 3 |
| `{REVISION_ROLE}` | Analyst |
| `{REVIEW_TYPE}` | review-dag |
| `{STAGE}` | review-dag |
| `{REVIEWER_OR_AUDITOR}` / `{REVIEWER_OR_AUDITOR_NAME}` | reviewer / Review |
| `{REVIEWER_OR_AUDIT_LOOP}` | Re-review |
| Output mode | Tier 1 local files under `<runs-root>/{epic-slug}/reviews/review-dag/round{N}/findings-{DIMENSION_CODE}.json` — one file per reviewer slot when dim_split > 1 (`findings-{DIM}-r{k}.json`, see §"Reviewer slots & findings files") — validated by `core/schemas/reviewer-output.schema.json`; synthesis comment + commit status stay Tier 2 |
| Commit status context | `pipeline/review-dag` |
| Wiki namespace | `{epic-slug}/reviews/review-dag/` |
| Task file names | `task-synthesizer`, `task-analyst` |
### Output (single gate)
The review produces a **single-gate synthesis**: the synthesis comment (reusing
`core/schemas/synthesis.schema.json`) uses `dimensions` key =
`TOPO` / `REQMAP` / `RELEASE`, and the commit status context is
`pipeline/review-dag`. Revision role = **Analyst** (the DAG author).
### Phase A Threshold
Unlike the process target (≥8 of 10 dimensions), review-dag has only 3 dimensions, so
Phase A is satisfied when **ALL THREE dimension findings are produced (3/3 = 100%)**.
- "3/3=100%" applies to a **full round** only (all 3 dimensions assigned and produced).
- In a **partial re-review round** (rounds ≥3 re-review only FAIL/WARN/UNRESOLVED dimensions), Phase A is satisfied when the assigned dimensions' findings are all produced.
- In a **full round where one dimension is UNRESOLVED** (e.g. a reviewer crashed, only 2/3 produced), that round proceeds with the produced dimensions and the UNRESOLVED dimension defers to the next partial re-review round — do NOT abort and do NOT apply the 8-file threshold.
### Phase D MAX_ROUNDS (per depth)
review-dag's Phase D convergence loop round cap is read from
`dag_metrics.review_depth`, NOT the shared `review-artifact` skill default of 3:
| Depth | MAX_ROUNDS |
|---|---|
| D1 | ≤2 |
| D2 | ≤3 |
| D3 | ≤3 |
| D4 | ≤4 |
**D4 override**: the shared `review-artifact` skill MAX_ROUNDS is 3 (see
`_shared/review-pipeline-phases.md` Phase D "`{MAX_ROUNDS}` binding"), but
review-dag overrides it per depth. D4 reaches round 4 (≤4), which exceeds the
shared default — the D4 round cap MUST take precedence for review-dag.
### Round-3 Escalation Guardrail interaction
The Round-3 Escalation Guardrail (see `_shared/review-pipeline-phases.md`
Phase D) applies to review-dag. Its interaction with the per-depth round cap:
- **D1D3** (cap ≤3): the guardrail and the per-depth cap coincide — no conflict.
- **D4** (cap ≤4): when the Round-3 guardrail triggers, it MUST offer the option "(b) 继续进入第 4 轮" (continue into round 4) rather than a hard stop. The guardrail means "escalate to human / give the user a choice", not "force-stop at round 3" — so D4's round 4 is reachable via that option. Independently of the guardrail, entering round 4 at all is gated by the shared **Round-4 user-choice escalation point** (`_shared/review-pipeline-phases.md` Phase D) — the choice is offered unconditionally at the round-3→4 transition, whether or not the guardrail's >50% condition fired.
### TOPO Page-Size Signal
The TOPO reviewer reads the `> 页尺寸自检: 超限` flag at the top of
`{epic-slug}/dag`. If present, the TOPO reviewer records it as an **INFO
finding** in the synthesis — summary = page-size over-limit, with the
acceptance-criteria detail sunk to the `{subpages}` of the DAG. This INFO
finding does NOT change any gate verdict and does NOT trigger re-derivation of
the DAG.
### Output Artifacts
```
<runs-root>/{epic-slug}/reviews/review-dag/
└── round{N}/
├── findings-{DIMENSION_CODE}.json # single-reviewer dimension (Tier 1 local, validated by reviewer-output.schema.json)
└── findings-{DIMENSION_CODE}-r{k}.json # one per reviewer slot when dim_split > 1 (D3/D4) — reviewer_slot/reviewer_count set
Wiki: {epic-slug}/reviews/review-dag/
├── round{N}/
│ ├── task-analyst # Phase C dispatch prompt (wiki page)
│ └── revision-summary # Analyst revision summary (wiki page)
└── final/
└── report # Final human-readable report (wiki page)
Ticket: #{issue_number}
└── synthesis comment # Orchestrator synthesis (```json block, Tier 2) — dimensions key = TOPO / REQMAP / RELEASE
Commit status: context "pipeline/review-dag"
```
### References
- `core/checklists/dag-single-gate.md` — The authoritative checklist for the 3 review dimensions (TOPO / REQMAP / RELEASE)
- `core/schemas/` — Shared JSON schemas for reviewer output, synthesis, and status
- IEEE 1028-2008 — Software Reviews and Audits
- ISO/IEC/IEEE 24765:2017 — Systems and software engineering vocabulary
---
## References
- `core/checklists/audit-process.md` — Process audit dimensions
- `core/checklists/dag-single-gate.md` — DAG single-gate review dimensions (TOPO / REQMAP / RELEASE)
- `core/schemas/reviewer-output.schema.json` — Per-dimension reviewer findings
- `core/schemas/synthesis.schema.json` — Orchestrator round synthesis
- `core/schemas/review-status.schema.json` — Review lifecycle tracking
- `core/templates/dag.md` — DAG artifact structure contract (review-dag target)
- IEEE 1028-2008 — Software Reviews and Audits
- ISO 19011:2018 — Guidelines for auditing management systems
- ISO/IEC/IEEE 24765:2017 — Systems and software engineering vocabulary
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
# Code Review Dimensions
> Reference data for the 10 review dimensions. Read on-demand by the Orchestrator
> when dispatching reviewers and filling per-dimension prompt values.
Each reviewer is assigned exactly **one** dimension at **L tier** (1 reviewer
per dimension). At S/M tiers the risk-tier table groups dimensions into a
single-reviewer sweep (S: all 10) or three grouped sweeps (M: COR+TST+DBT /
DGN+SEC+PERF / STY+A11Y+DOC+TRC) — a reviewer covering a group inspects each
dimension's checklist items listed below and nothing else (see
`review-code/SKILL.md` "Risk Tier Assessment").
| # | Dimension | Label | Checklist Ref | Code Files to Review | Design Reference Files |
|---|-----------|-------|---------------|----------------------|------------------------|
| 1 | COR | Correctness, Error Handling & Compatibility | COR 1.1-1.17 | All changed code files | `{slug}/03-design-03-component-design-*`, `{slug}/03-design-04-interface-design` (error paths) |
| 2 | DGN | Design Compliance & Dependencies | DGN 2.1-2.15 | All changed code files, package manifests | ALL design files |
| 3 | SEC | Security | SEC 3.1-3.10 | Auth, input, data-handling | `{slug}/03-design-06-non-functional-design` (security section) |
| 4 | PERF | Performance | PERF 4.1-4.10 | Loops, queries, I/O paths | `{slug}/03-design-06-non-functional-design` (performance section) |
| 5 | TST | Test Quality | TST 5.1-5.8 | All test files | `{slug}/03-design-03-component-design-*` (expected behavior) |
| 6 | STY | Style & Convention | STY 6.1-6.8 | All changed code files | (none — self-contained against project conventions) |
| 7 | DBT | Database & Data | DBT 7.1-7.7 | Migrations, query code | `{slug}/03-design-05-data-design` |
| 8 | A11Y | Accessibility & Browser Compatibility | A11Y 8.1-8.13 | UI components, markup | `{slug}/03-design-06-non-functional-design` (a11y section) |
| 9 | DOC | Documentation | DOC 9.1-9.6 | All changed code files | `{slug}/03-design-03-component-design-*` (for cross-ref accuracy) |
| 10 | TRC | Traceability | TRC 10.1-10.7 | All changed code files, tests | `{slug}/03-design-08-traceability`, work items (DAG task mode: the node `req_refs` + requirement registry rows in `{epic-slug}/dag` + node AC `test_id` declarations) |
> **DAG task mode**: the legacy `{slug}/03-design-*` Design Reference Files in
> the rows above (COR/SEC/PERF/TST/DBT/A11Y/DOC) resolve from the frozen DAG
> copy per the DAG-route read map — node spec + cross-session edge contracts
> in `{epic-slug}/dag` + node ticket body (same remap as the TRC row above;
> see `implement/SKILL.md` § Preconditions "DAG-route read map"d map").
**Rationale**: Single-dimension assignments prevent context explosion.
A reviewer inspecting one dimension examines fewer checklist items and can
read code files more deeply within the token budget. The 10 dimensions
consolidate the previous 13: COR absorbs error handling (ERR) and
compatibility (COR 1.15-1.17), DGN absorbs dependencies (DEP).
## Design Baseline
The approved design document at wiki page `{slug}/03-design-index` is the
**authoritative baseline** for code correctness. Every reviewer receives the
design `index.md` as a mandatory input for orientation.
Reviewers whose dimension involves design compliance (COR, DGN, SEC, PERF,
TST, DBT, A11Y, DOC) additionally receive the relevant design sub-files listed
above. Reviewers whose dimension is self-contained (STY) use the design baseline
index only. (TRC is NOT self-contained — it reads `{slug}/03-design-08-traceability`
and the work-item list to verify REQ references, per its row above; on the
DAG route that archived page does not exist — TRC's declared input remaps to
the node `req_refs` + requirement registry rows in `{epic-slug}/dag` and the
node AC `test_id` declarations, mirroring the DGN remap in
`review-code/SKILL.md` § DAG Task Mode. Historical `{slug}/03-design-*`
pages stay readable via `_shared/gitea-read-patterns.md`.)
@@ -0,0 +1,27 @@
# Code Review Output Artifacts
> Output tree for code review artifacts. Read on-demand for reference.
> Output model ([org-internal #1988] Tier 1 migration): Tier 1 local files (raw dimension findings) + Tier 2 wiki pages (task prompts, revision summaries, final reports) + Tier 2 synthesis comment. Status tracking via commit status `pipeline/review-code`.
```
<runs-root>/{slug}/reviews/code/
├── round{N}/
│ ├── findings-{DIMENSION_CODE}.json # Reviewer dimension findings (Tier 1 local, validated by reviewer-output.schema.json)
│ └── task-synthesizer.md # Synthesis prompt (Tier 1 local, passed to Synthesizer via prompt_file)
Ticket: #{issue_number}
└── synthesis comment # Orchestrator synthesis (```json block, Tier 2)
Wiki: {slug}/reviews/code/
├── round{N}/
│ ├── task-developer # Phase C dispatch prompt (wiki page)
│ └── revision-summary # Developer revision summary (wiki page)
└── final/
└── report # Final human-readable report (wiki page)
Commit status: context "pipeline/review-code"
```
**Dimensions (10)**: COR, DGN, SEC, PERF, TST, STY, DBT, A11Y, DOC, TRC
See `code-review-dimensions.md` for mapping. ERR/DEP/CMP were merged into COR/DGN per the 13→10 consolidation.
@@ -0,0 +1,510 @@
// Mechanical-green precondition gate for review-code (#2598).
//
// Runs the CI-aligned checks BEFORE any reviewer is dispatched — at Phase A
// entry and at every Phase D re-entry — so an LLM review round is never spent
// on a diff a deterministic check would reject:
// 1. oxlint — `bun oxlint --deny-warnings` (repo root)
// 2. typecheck — `bun turbo typecheck` (repo root)
// 3. test:changed — `cd packages/octopus && CI=true TEST_SHARDS=3 bun run
// test:changed` (mirrors .gitea/workflows/ci.yml exactly)
//
// `test:parallel` is deliberately NOT a gate check — the full suite belongs
// to verify (#2598 check-tiering).
//
// Known-failure waivers (#4380): a main-preexisting red can reach this gate
// through test:changed's transitive import closure with zero causal link to
// the diff under review (instance: #4296 N-01, TD-930 red via the `Config`
// edge). Waivers are declared in `.octopus/known-failures.json` (tracked —
// they ride PRs like code) as:
//
// {
// "schema_version": 1,
// "entries": [
// {
// "test": "packages/octopus/test/foo.test.ts::widget > broken",
// "fingerprint": "<sha256 below>",
// "evidence": "https://…/issues/4296#issuecomment-…", // REQUIRED
// "reason": "one-line attribution",
// "added_ts": "2026-09-07T00:00:00.000Z"
// }
// ]
// }
//
// - fingerprint = `--fingerprint` helper output = sha256("<file>\0<name>"),
// where <file> is the repo-relative test file and <name> is the bun test
// name exactly as printed in the `(fail)` line (describe chain joined
// with " > ", timing suffix stripped).
// - ALL failures of a `test:changed` run matched by fingerprint downgrade
// that check to WARN: the gate passes (`blocked: false`) and the record
// carries `waivers_applied` with the evidence URL. oxlint/typecheck
// failures, timeouts, and unparsable runs are NEVER waivable (fail-closed).
// - Invalidation is automatic — 指纹消失即失效: once the waived test no
// longer fails while its file still ran (fix landed), the entry matches
// nothing and the gate emits a `waiver-stale` warning + record entry;
// physical removal rides the fix PR (the gate never rewrites repo files).
//
// Result cache: each run is keyed on a tree fingerprint (HEAD sha + tracked
// diff + untracked file contents + the check-set). If the newest record for
// this slug with the same fingerprint is green, the mechanical run is
// skipped (cache hit) — the Developer pre-handoff gate (Phase C step 4) and
// the Phase A/D dispatch gate collapse into ONE execution per tree state.
// verify Phase 2.0 consumes the same green record as its mechanical-evidence
// reuse source (eff-gate-cache: run this script; a cache-hit GREEN transfers
// to verify's typecheck/lint DoD without a local re-run). Only GREEN results are ever reused; a red result always re-runs. The
// fingerprint does NOT cover the environment (bun version, node_modules
// state) — pass --no-cache to force a full re-run after an env change. A
// waived green is cacheable like any other green: the waiver file is part of
// the tree (tracked diff or untracked contents), so editing it changes the
// fingerprint and forces a re-run.
//
// Usage (from the workflow worktree root):
// bun .octopus/skills/review-code/scripts/precondition-gate.ts <slug> [--round N] [--only oxlint|typecheck|test:changed] [--no-cache]
// bun .octopus/skills/review-code/scripts/precondition-gate.ts --fingerprint <repo-relative-test-file> "<bun test name>"
//
// Harness mode (tests only, GATE_* env precedent — see GATE_ARCHIVE_DIR):
// GATE_CHECKS_JSON=<path> replace the check-set with a JSON array of
// { name, command, cwd, timeoutMs?, env? }
// GATE_WAIVER_FILE=<path> replace the default waiver file location
// (<repoRoot>/.octopus/known-failures.json)
//
// Exit codes: 0 = green (dispatch reviewers); 1 = RED — a PRECONDITION-BLOCK
// record has been appended to .octopus/runs/<slug>/reviews/code/precondition-gate.jsonl
// (Tier 1); do NOT dispatch reviewers, hand the output to the Developer
// (Phase C) and re-run after the fix. 2 = usage error.
//
// Every invocation (green, red, or cache hit) appends a record — per-check
// durations feed the gate-latency telemetry (#2598 DoD).
import { spawnSync } from "node:child_process"
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"
import { createHash } from "node:crypto"
import { join } from "node:path"
// ---- Waiver types & helpers (#4380) ----
interface WaiverEntry {
test: string // "<repo-relative file>::<bun test name>"
fingerprint: string // sha256("<file>\0<name>")
evidence: string // attribution URL (issue comment)
reason?: string
added_ts?: string
}
interface WaiverLoad {
entries: WaiverEntry[]
warnings: string[]
}
function testFingerprint(file: string, name: string): string {
return createHash("sha256").update(`${file}\0${name}`).digest("hex")
}
// Fail-closed loader: any malformation drops the affected entries (or the
// whole file) with a warning — never grants an exemption it cannot verify.
function loadWaivers(waiverPath: string): WaiverLoad {
const warnings: string[] = []
if (!existsSync(waiverPath)) return { entries: [], warnings }
let parsed: unknown
try {
parsed = JSON.parse(readFileSync(waiverPath, "utf8"))
} catch (e) {
return {
entries: [],
warnings: [
`malformed known-failures file (unparsable JSON) at ${waiverPath}: ${(e as Error).message} — treating as no waivers`,
],
}
}
const wf = parsed as { schema_version?: unknown; entries?: unknown }
if (typeof wf.schema_version !== "number" || wf.schema_version !== 1) {
return {
entries: [],
warnings: [`malformed known-failures file at ${waiverPath}: schema_version must be 1 — treating as no waivers`],
}
}
if (!Array.isArray(wf.entries)) {
return {
entries: [],
warnings: [`malformed known-failures file at ${waiverPath}: entries must be an array — treating as no waivers`],
}
}
const entries: WaiverEntry[] = []
for (const raw of wf.entries) {
const e = raw as Partial<WaiverEntry>
const desc = typeof e.test === "string" ? e.test : JSON.stringify(raw).slice(0, 120)
if (typeof e.test !== "string" || !e.test.includes("::")) {
warnings.push(`waiver entry ignored (test must be "<file>::<name>"): ${desc}`)
continue
}
if (typeof e.fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(e.fingerprint)) {
warnings.push(`waiver entry ignored (fingerprint must be 64-hex — run --fingerprint): ${desc}`)
continue
}
if (typeof e.evidence !== "string" || !/^https?:\/\//.test(e.evidence)) {
warnings.push(`waiver entry ignored (evidence must be an issue/comment URL): ${desc}`)
continue
}
entries.push({
test: e.test,
fingerprint: e.fingerprint,
evidence: e.evidence,
...(typeof e.reason === "string" ? { reason: e.reason } : {}),
...(typeof e.added_ts === "string" ? { added_ts: e.added_ts } : {}),
})
}
return { entries, warnings }
}
// ---- bun test output parsing (formats verified against bun 1.3.14 non-TTY) ----
//
// stdout carries the test:changed selection list BEFORE execution:
// test:changed: N test file(s) selected from M changed file(s):
// packages/octopus/test/foo.test.ts
// stderr carries per-file headers + failure lines (passing-only files and
// (pass) lines leave no trace):
// test/foo.test.ts:
// (fail) widget > known broken thing [0.08ms]
interface ParsedFailure {
file: string // repo-relative
name: string
fingerprint: string
}
function parseSelection(stdoutText: string): string[] {
const lines = stdoutText.split(/\r?\n/)
const selected: string[] = []
let inList = false
for (const line of lines) {
if (!inList) {
if (/^test:changed: \d+ test file\(s\) selected from /.test(line)) inList = true
continue
}
const m = line.match(/^ (\S+\.(?:test|spec)\.(?:ts|tsx))(?: \(isolated\))?$/)
if (!m) break // the list ends at the first non-entry line
selected.push(m[1]!)
}
return selected
}
// bun prints file headers exactly as the path was passed on the CLI;
// test:changed passes package-relative paths, so resolve against the
// repo-relative selection list by unique suffix. Ambiguous/unresolvable
// headers stay raw — a properly-authored waiver then misses, which is the
// fail-closed direction.
function resolveRepoRelative(header: string, selection: string[]): string {
if (selection.includes(header)) return header
const candidates = selection.filter((s) => s.endsWith(`/${header}`))
return candidates.length === 1 ? candidates[0]! : header
}
function parseFailures(outputText: string, selection: string[]): ParsedFailure[] {
const byFp = new Map<string, ParsedFailure>()
let header: string | null = null
for (const line of outputText.split(/\r?\n/)) {
const h = line.match(/^(\S+\.(?:test|spec)\.(?:ts|tsx)):\s*$/)
if (h) {
header = h[1]!
continue
}
const f = line.match(/^\(fail\) (.+) \[[0-9.]+ms\]$/)
if (f && header !== null) {
const file = resolveRepoRelative(header, selection)
const name = f[1]!
byFp.set(testFingerprint(file, name), { file, name, fingerprint: testFingerprint(file, name) })
}
}
return [...byFp.values()]
}
// ---- CLI ----
const args = process.argv.slice(2)
const fpIdx = args.indexOf("--fingerprint")
if (fpIdx >= 0) {
const file = args[fpIdx + 1]
const name = args[fpIdx + 2]
if (!file || !name) {
console.error(
'usage: bun .octopus/skills/review-code/scripts/precondition-gate.ts --fingerprint <repo-relative-test-file> "<bun test name>"',
)
process.exit(2)
}
console.log(testFingerprint(file, name))
process.exit(0)
}
let slug = ""
let round = 1
let only: string | null = null
let noCache = false
for (let i = 0; i < args.length; i++) {
if (args[i] === "--round") round = Number(args[++i] ?? 1)
else if (args[i] === "--no-cache") noCache = true
else if (args[i] === "--only") only = args[++i] ?? ""
else slug = args[i]
}
if (!slug || !/^[a-zA-Z0-9._-]+$/.test(slug) || !Number.isFinite(round) || round < 1) {
console.error(
"usage: bun .octopus/skills/review-code/scripts/precondition-gate.ts <slug> [--round N] [--only oxlint|typecheck|test:changed] [--no-cache]",
)
console.error(
' bun .octopus/skills/review-code/scripts/precondition-gate.ts --fingerprint <repo-relative-test-file> "<bun test name>"',
)
process.exit(2)
}
const root = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" })
if (root.status !== 0 || !root.stdout) {
console.error("precondition-gate: not inside a git worktree")
process.exit(2)
}
const repoRoot = root.stdout.trim()
type Check = { name: string; command: string; cwd: string; env?: Record<string, string>; timeoutMs: number }
const defaultChecks: Check[] = [
{ name: "oxlint", command: "bun oxlint --deny-warnings", cwd: repoRoot, timeoutMs: 5 * 60_000 },
{ name: "typecheck", command: "bun turbo typecheck", cwd: repoRoot, timeoutMs: 10 * 60_000 },
{
name: "test:changed",
command: "bun run test:changed",
cwd: join(repoRoot, "packages", "octopus"),
env: { CI: "true", TEST_SHARDS: "3" }, // mirror ci.yml Test step
timeoutMs: 20 * 60_000,
},
]
// Harness mode (#4380): GATE_CHECKS_JSON replaces the production check-set
// with fixture commands (GATE_* env precedent). Unset in production.
let allChecks: Check[] = defaultChecks
if (process.env.GATE_CHECKS_JSON) {
try {
const parsed = JSON.parse(readFileSync(process.env.GATE_CHECKS_JSON, "utf8")) as unknown
if (!Array.isArray(parsed) || parsed.length === 0) throw new Error("must be a non-empty JSON array")
for (const c of parsed) {
const chk = c as Partial<Check>
if (typeof chk.name !== "string" || typeof chk.command !== "string" || typeof chk.cwd !== "string") {
throw new Error("each check needs string name/command/cwd")
}
if (chk.timeoutMs !== undefined && typeof chk.timeoutMs !== "number")
throw new Error("timeoutMs must be a number")
if (chk.env !== undefined && typeof chk.env !== "object") throw new Error("env must be an object")
}
allChecks = parsed as Check[]
} catch (e) {
console.error(
`precondition-gate: invalid GATE_CHECKS_JSON (${process.env.GATE_CHECKS_JSON}): ${(e as Error).message}`,
)
process.exit(2)
}
}
const checks = only ? allChecks.filter((c) => c.name === only) : allChecks
if (checks.length === 0) {
console.error(`--only must be one of: ${allChecks.map((c) => c.name).join(", ")}`)
process.exit(2)
}
const runsDir = join(repoRoot, ".octopus", "runs", slug, "reviews", "code")
const gateLog = join(runsDir, "precondition-gate.jsonl")
const gitOut = (gitArgs: string[]): string => {
const r = spawnSync("git", gitArgs, { encoding: "utf8", cwd: repoRoot })
return r.status === 0 ? r.stdout : ""
}
// Fingerprint = check-set + HEAD + tracked diff (staged+unstaged) + untracked
// contents. Above the untracked cap the fingerprint is made unique so this
// run can never cache-hit (fail-safe: always re-run).
const UNTRACKED_CACHE_CAP = 500
function treeFingerprint(): string {
const h = createHash("sha256")
h.update("gate-fingerprint-v1\n")
h.update(checks.map((c) => c.name).join(",") + "\n")
h.update(gitOut(["rev-parse", "HEAD"]))
h.update("\0")
h.update(gitOut(["diff", "HEAD"]))
h.update("\0")
const untracked = gitOut(["ls-files", "--others", "--exclude-standard"]).split(/\r?\n/).filter(Boolean)
if (untracked.length > UNTRACKED_CACHE_CAP) return `nocache-${Date.now()}-${Math.random()}`
const hashes = spawnSync("git", ["hash-object", "--stdin-paths"], {
input: untracked.join("\n"),
encoding: "utf8",
cwd: repoRoot,
})
h.update(hashes.status === 0 ? hashes.stdout : "")
return h.digest("hex")
}
// Only a GREEN record with the identical fingerprint may skip a re-run; red
// always re-runs (an environment-caused red must not wedge the gate until
// the tree changes).
function lastGreenHit(fp: string): { round: number; ts: string } | null {
if (!existsSync(gateLog)) return null
const lines = readFileSync(gateLog, "utf8")
.split(/\r?\n/)
.filter((l) => l.trim() !== "")
for (let i = lines.length - 1; i >= 0; i--) {
try {
const r = JSON.parse(lines[i]!) as { fingerprint?: string; blocked?: boolean; round?: number; ts?: string }
if (r.fingerprint === fp && r.blocked === false && typeof r.ts === "string") {
return { round: r.round ?? 0, ts: r.ts }
}
} catch {
// tolerate a malformed/truncated line — keep scanning
}
}
return null
}
const fingerprint = treeFingerprint()
const hit = noCache ? null : lastGreenHit(fingerprint)
const waiverPath = process.env.GATE_WAIVER_FILE ?? join(repoRoot, ".octopus", "known-failures.json")
const waivers = loadWaivers(waiverPath)
const record: Record<string, unknown> = {
schema_version: 3,
gate: "precondition-gate",
ts: new Date().toISOString(),
slug,
round,
fingerprint,
cache_hit: hit !== null,
checks: [] as Array<Record<string, unknown>>,
blocked: false,
}
if (hit) {
record.checks = checks.map((c) => ({ name: c.name, skipped: true }))
console.log(`PRECONDITION-GATE GREEN (cache hit — slug=${slug} round=${round})`)
console.log(`Reusing green result from ${hit.ts} (round ${hit.round}) — identical tree fingerprint.`)
} else {
const tail = (s: string | null | undefined): string[] =>
(s ?? "")
.split(/\r?\n/)
.filter((l) => l.trim() !== "")
.slice(-15)
for (const w of waivers.warnings) console.log(`[waiver] WARN ${w}`)
let blocked = false
for (const c of checks) {
const started = Date.now()
const r = spawnSync(c.command, {
shell: true,
cwd: c.cwd,
encoding: "buffer",
env: { ...process.env, ...c.env },
timeout: c.timeoutMs,
})
const timedOut =
r.error?.name === "TimeoutError" || (r.error as NodeJS.ErrnoException | undefined)?.code === "ETIMEDOUT"
const exitCode = timedOut ? 124 : (r.status ?? 1)
const failed = exitCode !== 0
const outText = r.stdout?.toString() ?? ""
const errText = r.stderr?.toString() ?? ""
// ---- waiver pass (#4380): test:changed only ----
// A red is waivable only when every parsed failure matches a waiver
// fingerprint. Timeouts and zero-failure reds (crash, harness gap, output
// drift) stay blocking — no fingerprint to vouch for, fail closed.
let waived = false
let applied: WaiverEntry[] = []
let failures: ParsedFailure[] = []
let stale: WaiverEntry[] = []
if (c.name === "test:changed") {
const selection = parseSelection(outText)
failures = parseFailures(`${outText}\n${errText}`, selection)
if (failed && !timedOut && failures.length > 0 && waivers.entries.length > 0) {
const fpSet = new Set(failures.map((f) => f.fingerprint))
applied = waivers.entries.filter((e) => fpSet.has(e.fingerprint))
waived = applied.length === failures.length
}
// 指纹消失即失效: the entry's file ran in this selection yet its
// fingerprint is absent from the failures — the waiver is inert now.
// Report only; removal rides the fix PR (the gate never rewrites
// repo-tracked state).
if (waivers.entries.length > 0 && selection.length > 0) {
const failFps = new Set(failures.map((f) => f.fingerprint))
stale = waivers.entries.filter((e) => {
const file = e.test.split("::")[0]!
return selection.includes(file) && !failFps.has(e.fingerprint)
})
}
}
if (failed && !waived) blocked = true
const errorLines = failed ? tail(errText || outText) : []
;(record.checks as Array<Record<string, unknown>>).push({
name: c.name,
command: c.command,
cwd: c.cwd.replace(/\\/g, "/"),
exit_code: exitCode,
timed_out: timedOut,
...(failures.length > 0 ? { failures } : {}),
...(waived ? { waived: true } : {}),
duration_ms: Date.now() - started,
...(errorLines.length > 0 ? { error_tail: errorLines } : {}),
})
if (applied.length > 0) {
record.waivers_applied = [
...((record.waivers_applied as WaiverEntry[]) ?? []),
...applied.map((e) => ({ test: e.test, fingerprint: e.fingerprint, evidence: e.evidence })),
]
}
if (stale.length > 0) {
record.waivers_stale = [
...((record.waivers_stale as WaiverEntry[]) ?? []),
...stale.map((e) => ({ test: e.test, fingerprint: e.fingerprint })),
]
}
if (waived) {
console.log(
`[${c.name}] WARN (waived — ${applied.length} known failure(s) exempted) (${((Date.now() - started) / 1000).toFixed(1)}s)`,
)
for (const e of applied) console.log(` [waiver] ${e.test} — evidence: ${e.evidence}`)
} else if (failed && applied.length > 0) {
console.log(
`[${c.name}] FAIL (${applied.length}/${failures.length} failure(s) waived — unmatched failures block the gate) (${((Date.now() - started) / 1000).toFixed(1)}s)`,
)
for (const e of applied) console.log(` [waiver] ${e.test} — evidence: ${e.evidence}`)
} else {
console.log(`[${c.name}] ${failed ? "FAIL" : "ok"} (${((Date.now() - started) / 1000).toFixed(1)}s)`)
}
for (const e of stale) {
console.log(
` [waiver-stale] ${e.test} — fingerprint absent from this run; entry is inert, remove it in the fix PR`,
)
}
if (failed) for (const l of errorLines.slice(0, 8)) console.log(` ${l}`)
}
record.blocked = blocked
}
try {
mkdirSync(runsDir, { recursive: true })
appendFileSync(gateLog, JSON.stringify(record) + "\n")
} catch (e) {
// The gate verdict must not depend on Tier 1 logging succeeding; surface but proceed.
console.error(`[warn] could not append gate record: ${(e as Error).message}`)
}
console.log()
if (record.blocked) {
console.log("PRECONDITION-BLOCK — mechanically red. Do NOT dispatch reviewers.")
console.log("Route the failing check output above to the Developer (Phase C), fix, then re-run this gate.")
process.exit(1)
}
const appliedCount = ((record.waivers_applied as WaiverEntry[]) ?? []).length
if (hit) {
// cache-hit path already printed above
} else if (appliedCount > 0) {
console.log(
`PRECONDITION-GATE GREEN (slug=${slug} round=${round}) — ${appliedCount} known failure(s) waived; evidence in the gate record. Dispatch reviewers.`,
)
} else {
console.log(`PRECONDITION-GATE GREEN (slug=${slug} round=${round}) — dispatch reviewers.`)
}
File diff suppressed because it is too large Load Diff
+260
View File
@@ -0,0 +1,260 @@
---
name: writing-skills
description: Use ONLY when creating, editing, or evaluating an octopus skill — writing the SKILL.md body, deciding whether a skill needs baseline testing, or running a blind A/B eval of a behavior-shaping technique. Use BEFORE first deployment of any skill that must hold a process gate under pressure.
triggers:
# High-discrimination phrases only ([org-internal #4525]). We deliberately do NOT use:
# - bare "skill" / 技能 — collides with every skill discussion. Worse: the
# hard skill gate folds the previous assistant turn into its match text on
# approval turns, so any model reply containing 技能 (e.g. a gate
# disclaimer) re-fired the gate every turn — a self-sustaining loop.
# - this skill's own name — the gate reminder lists matched skill names, so
# a model echoing them re-seeds the trigger for the next turn.
# - "skill gate" — names the mechanism, not the task of authoring skills.
# Note matches() is a CONTIGUOUS substring, so verb phrases like
# "evaluate skill" miss real input "evaluate THIS skill". CJK recall is
# accepted as imperfect (写个技能 breaks 写技能 contiguity): the skills
# listing's description remains the model-side routing path for prose asks.
- blind A/B
- A/B eval
- skill eval
- 技能编写
- 编写技能
- 技能评估
- 评估技能
- 行为塑造
- SKILL.md
role: Producer
---
# Writing Skills
## Overview
**Writing a skill IS test-driven development applied to process documentation.**
You write test cases (blind pressure scenarios run through Explorer subagents),
watch them fail (baseline behavior without the skill), write the skill, watch
tests pass (agents comply), and refactor (close loopholes).
**Core principle:** If you didn't watch an agent fail without the skill, you
don't know if the skill teaches the right thing — or teaches anything at all.
This skill covers the *methodology*. For file locations, frontmatter fields,
and valid config shapes, load `customize-octopus` — a built-in skill
(`packages/octopus/src/skill/prompt/customize-octopus.md`), not a skill under
`.octopus/skills/`. The two co-trigger: mechanics + methodology.
## The Iron Law
```
NO BEHAVIOR-SHAPING SKILL WITHOUT A FAILING BASELINE FIRST.
```
This applies to NEW skills AND EDITS that change what a skill enforces. Write
the skill before testing the baseline? You no longer have a baseline — the
failure you needed to document is already contaminated by knowing the fix.
No exceptions for "simple additions", "just a new section", or "documentation
updates". If the edit changes agent behavior, it needs a baseline.
## When to eval — and when it is overkill (decide FIRST)
Not every skill needs a blind A/B eval. Classify before authoring:
| Skill type | Example | Eval required? |
|---|---|---|
| **Behavior-shaping / discipline** (must hold a gate under pressure) | `review-code` (Phase C), a new "never skip X" rule | **YES — full blind A/B** (RED-GREEN-REFACTOR below) |
| **Technique / how-to** (a method agents apply) | `condition-based-waiting`, `root-cause-tracing` port | Application scenarios (can the agent apply it correctly?) — baseline optional |
| **Reference / mechanical** (lookup, syntax, config shapes) | config-field docs, command references | **NO** — source-fidelity check + lint suffice |
| **Pure port of a proven technique** (verbatim, minimal localization) | a 1:1 port with wording unchanged | Fidelity check against source + spot-check; full re-eval only if localized substantially |
**The question that decides it:** "Could a capable agent comply with this skill
*without ever reading it*?" If yes (it restates convention, or the gate is
already procedurally explicit elsewhere), the skill is reference-class and eval
will show a **ceiling** — both control and treatment hold, and the eval carries
no signal. Do not run a blind eval expecting to prove a reference skill works;
run a fidelity check instead.
## RED-GREEN-REFACTOR for skills
### RED — write the failing test (baseline)
Run pressure scenarios through an Explorer subagent **WITHOUT** the skill (or
with a control SKILL that omits only the technique under test). Document, verbatim:
- What choices the agent made.
- The exact rationalizations it used to skip the gate.
- Which pressures (deadline, sunk cost, authority, exhaustion) triggered the violation.
This is "watch the test fail". You must see what agents naturally do before
writing the skill, or you are writing to an imagined failure.
### GREEN — write the minimal skill
Write the skill to address **the specific rationalizations you observed** — not
hypothetical ones. Run the same scenarios WITH the skill. The agent should now
comply. Do not add content for failures you did not see.
### REFACTOR — close loopholes
The agent found a new rationalization in GREEN? Add an explicit counter (see
Rationalization Table below). Re-test until bulletproof.
## Blind A/B eval methodology
For behavior-shaping skills, the eval is a **blind controlled experiment**:
control = SKILL without the technique; treatment = SKILL with it. The
Explorer subagents running the scenarios are unaware of the condition **or**
that they are being tested.
### Artifact layout (wiki pages under `_evals/{skill-name}/`)
> The `_evals/{skill-name}/` namespace is an intentional slug-less collection
> namespace — eval records aggregate across skills and outlive any one
> pipeline run — analogous to two documented slug-convention exceptions: the
> audit `audit/{date}/` date-slug exception (NAM 4.4,
> `core/checklists/audit-process.md`) and the retrospective
> `_retrospectives/` exception (`retrospective/SKILL.md` Phase 7 — Report).
```
README findings, conclusion, honest evidence strength,
and why you stopped (or didn't)
round-1-initial/control-SKILL baseline skill (technique omitted)
round-1-initial/treatment-SKILL skill with the technique
round-1-initial/scenarios/scenario-<id> blind pressure scenarios (3+ for discipline skills)
round-1-initial/cells/cell-<id>-{control,treatment} per-cell dispatch prompts
round-1-initial/outputs/output-<id>-{control,treatment} captured agent outputs (verbatim)
round-1-initial/rubric scoring rubric
round-1-initial/scoring scored results table
round-2-replication/* re-runnable half; same names, NEW scenarios
```
### Procedure
1. **Write 3+ pressure scenarios** that combine pressures (deadline + sunk cost;
authority + exhaustion). Each scenario tempts the agent to skip the gate the
skill enforces.
2. **Run each scenario twice, blind**, via an Explorer subagent: once seeded
with `control-SKILL.md`, once with `treatment-SKILL.md`. The subagent sees
only its assigned SKILL + the scenario — never the other condition, never
the word "test" or "experiment".
3. **Score outputs against the rubric** on a 3-level verdict: `HOLD` (kept the
gate), `PARTIAL` (hedged / delayed but technically held), `RATIONALIZE`
(skipped with justification).
4. **Treatment must win the *differentiating* cells** — cells where control and
treatment split. **Ceiling cells** (both HOLD) carry NO signal either way:
they are a positive finding (that gate is robust without the technique), not
noise to eliminate by cranking pressure.
5. **Replicate**: run a second round with NEW scenarios before concluding. A
single round is one data point.
6. **Record honest evidence strength** in the README — see calibration below.
### Worked example
The canonical blind A/B run (2 rounds, 17 cells, rationalization-anticipation
tables) is published at the `_evals/skill-compliance-framing/` wiki namespace
once its artifacts are promoted from internal storage. The experiment
demonstrated a ceiling finding: the tested gate was robust in both control
and treatment arms without the technique under evaluation.
## Honest evidence calibration (write this in the eval README)
An eval README that claims more than the data supports is worse than no eval.
State explicitly:
- **Supports**: the construct the data is consistent with (e.g. "*consistent
with* a real but **construct-specific** effect on redundancy-gates under
pressure").
- **Does NOT prove**: the limits — small differentiating N, ceiling cells,
stated-decision eval vs production compliance.
- **Ceiling IS a finding**: when both groups hold a gate, that gate is robust
without the technique. Do not manufacture pressure to break the control —
that is eval p-hacking.
- **Real arbiter**: for production skills, the honest next test is often a
measurement script over real iterations (e.g. `script/measure-review-blockers.ts`),
not more simulated cells.
**Stopping is a valid decision.** When the ceiling pattern is clear and
differentiating N stays small, further stated-decision cells have diminishing
returns. Say so and stop.
## Match the form to the failure
Before authoring guidance, classify the baseline failure. The form that
bulletproofs one failure type measurably backfires on another.
| Baseline failure | Right form | Wrong form |
|---|---|---|
| Skips/violates a rule under pressure (knows better, does it anyway) | Prohibition + rationalization table + red flags | Soft guidance ("prefer...", "consider...") |
| Complies, but output has the wrong shape (bloated prompt, buried verdict) | Positive recipe: state what the output IS — its parts, in order | Prohibition list ("don't restate", "never narrate") |
| Omits a required element from something they already produce | Structural: REQUIRED field or slot in the template | Prose reminders near the template |
| Behavior should depend on a condition | Conditional keyed to an observable predicate ("if the brief exists, reference it") | Unconditional rule + exemption clauses |
**Why prohibitions backfire on shaping problems:** under a competing incentive,
agents negotiate with "don't X". In head-to-head wording tests, the prohibition
arm produced more of the unwanted content than the recipe arm — and trended
worse than even the no-guidance control. A recipe leaves nothing to negotiate:
the output matches the stated shape or it doesn't.
**Rules for whichever form:**
- **No nuance clauses.** "Don't X unless it matters" reopens the negotiation.
Express a real exception as its own conditional on an observable predicate.
- **Exemption clauses don't scope.** "This limit doesn't apply to code blocks"
still suppresses code blocks. Restructure so the rule can't reach the exempt part.
## Rationalization table
Capture the rationalizations you observed in RED. Every excuse the baseline
agents used goes in the table, paired with the reality that defeats it:
```markdown
| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. The test takes 30 seconds. |
| "I'll test after" | Tests-after answers "what does this do?"; the baseline answers "what SHOULD it do?" |
```
This is the same rationalization-anticipation device already embedded in
`implement` (including its bugfix mode) — here it is an *authoring* tool: you build it from
your own baseline data, not from guesswork.
## Micro-test wording before full scenarios
Full blind A/B runs are expensive. Verify the wording itself first with
micro-tests:
1. **One fresh-context sample per call** — a single-shot Explorer subagent.
System prompt = the realistic context the guidance will live in; user
message = a task that tempts the failure.
2. **Always include a no-guidance control.** If the control doesn't exhibit the
failure, there is nothing to fix — stop, don't author the guidance.
3. **5+ reps per variant.** Single samples lie.
4. **Read every flagged match manually.** Template echoes and quoted
counter-examples masquerade as hits; automated counts overstate both failure
and success.
5. **Variance is a metric.** When wording lands, reps converge on the same
shape. Five different interpretations across five reps means the wording
isn't binding — tighten the form before adding words.
Micro-tests verify wording; they do not replace blind A/B for discipline skills.
## Authoring checklist
**RED — baseline:**
- [ ] Classified the skill (behavior-shaping / technique / reference / port)
- [ ] If behavior-shaping: wrote 3+ combined-pressure scenarios
- [ ] Ran baseline (control SKILL) blind via Explorer subagent; documented rationalizations verbatim
**GREEN — minimal skill:**
- [ ] Frontmatter `name` + `description` (description = WHEN to use, not what it does — see `customize-octopus`)
- [ ] Addresses the specific baseline failures observed (not hypothetical ones)
- [ ] Form matches the failure type (table above)
- [ ] Ran scenarios WITH skill; agent now complies
**REFACTOR — loopholes:**
- [ ] New rationalizations from testing have explicit counters
- [ ] Rationalization table built from real baseline data
- [ ] Re-tested until bulletproof
**Eval README (if blind A/B was run):**
- [ ] States what the data supports AND does not prove
- [ ] Records ceiling cells as a positive finding, not noise
- [ ] Names the real arbiter (measurement script / production data) if applicable