Initial publish v0.1.0: standalone workflow core (corpus + examples + guards)
This commit is contained in:
@@ -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.
|
||||
@@ -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 1–3 (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 铁律)"
|
||||
@@ -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)"
|
||||
@@ -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)"
|
||||
@@ -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)"
|
||||
@@ -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)"
|
||||
@@ -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.
|
||||
@@ -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 D1–D4
|
||||
```
|
||||
|
||||
- `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 | — | 1–3 | ≥4 |
|
||||
| `contract_change_surface` | none | — | additive | breaking |
|
||||
|
||||
- `cross_session_edge_count`: 0 → D1; 1–3 → 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.7–3.10` / `EST 6.3–6.12` / `EXE 8.7–8.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)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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 error,verify 字段回执);
|
||||
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 1–3 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 30–60 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:18–16: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
|
||||
30–60 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(见 TERMINOLOGY)get(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(见 TERMINOLOGY)search(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(见 TERMINOLOGY)get(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_async,body 携 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]).
|
||||
@@ -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.
|
||||
@@ -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; 1–3 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 1–6 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 1–3 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 B1–B4 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 1–2), 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 1–3 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(见 TERMINOLOGY)update`), 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 1–6 (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(见 TERMINOLOGY)get`.
|
||||
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(见 TERMINOLOGY)update` 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(见 TERMINOLOGY)create` → 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 01–05 and 10 are in
|
||||
`reference/source-analysis-templates.md` (read it when producing these
|
||||
documents). Documents 06–09 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 4–7 (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 A1–A8, 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 4–7 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 1–6, 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 1–2.
|
||||
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -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锛塦)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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; M2–M5 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.
|
||||
- 🟢 1–2 / 🟡 3–4 / 🔴 ≥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 M2–M5.
|
||||
|
||||
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% / 🟡 35–60% / 🔴 >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% / 🟡 15–30% / 🔴 >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 / 🟡 3–10: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 / 🟡 1–2: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(见 TERMINOLOGY)list(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, D1–D4 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 1–3 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"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
@@ -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.1–1.7 | `core/skills/` tree, `checklists/`, `templates/`, `schemas/` listings |
|
||||
| 2 | XREF | Cross-Reference Integrity | XREF 2.1–2.7 | ALL `SKILL.md`; resolve every referenced path |
|
||||
| 3 | FM | Frontmatter Conformance | FM 3.1–3.7 | Frontmatter block of every `SKILL.md` |
|
||||
| 4 | NAM | Naming Convention | NAM 4.1–4.6 | ALL `SKILL.md`, checklist filenames, schema enums |
|
||||
| 5 | FLOW | Pipeline Cohesion | FLOW 5.1–5.6 | Preconditions + output sections of every `SKILL.md` |
|
||||
| 6 | SCH | Schema Contract | SCH 6.1–6.5 | `core/schemas/*.json` + skills that reference them |
|
||||
| 7 | DUP | Duplication & Drift | DUP 7.1–7.4 | ALL `SKILL.md` + ALL `checklists/*.md` |
|
||||
| 8 | STD | Audit Standards (ISO/IEEE) | STD 8.1–8.7 | `review-*` skills, `review-artifact` (target: `audit-process`) itself |
|
||||
| 9 | AGT | AGENTS.md Conformance | AGT 9.1–9.5 | ALL `SKILL.md` + `AGENTS.md` |
|
||||
| 10 | TRC | Traceability | TRC 10.1–10.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 (D1–D4) — 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` (D1–D4), 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` (D1–D4) 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:
|
||||
|
||||
- **D1–D3** (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
@@ -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
|
||||
Reference in New Issue
Block a user