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

This commit is contained in:
octopus
2026-09-15 08:41:51 +08:00
commit bb35e661b2
114 changed files with 20240 additions and 0 deletions
+287
View File
@@ -0,0 +1,287 @@
> Core 中立版(Increment 6a 改写,原 deferHard verbatim)。编号与条目结构严格不变(C-2 不变量);实例术语按 `core/adapters/TERMINOLOGY.md` 绑定。
# Tier 1 Local Run Layout — `<runs-root>/{slug}/`
Normative layout for **Tier 1 local structured artifacts**. Authoritative
boundary rule: `core/rules/two-tier-artifacts.md`. Schemas that validate
the structured files below live in `core/schemas/runs-*.schema.json`.
This document is the **foundation** (MVP slice, [org-internal #1968]). It defines the
container, the manifest, and the metadata. Stage-specific artifact schemas
(design detail dumps, plan items, review dimension findings, synthesis
prompts, draft contracts) are added incrementally as each skill migrates its
transient output here — the follow-up iteration ([org-internal #1988]) has landed: `review-artifact` /
`audit-process` raw findings and producer working drafts are now Tier 1.
([org-internal #3072] phase 3, 2026-08-21: the legacy producer stages whose drafts are listed
below were archived — their rows and artifact dirs are kept for reading
historical run bundles.) See the migrated stages table below.
## Kind/MVP mode ([org-internal #3061])
`Kind/MVP` tickets write **nothing** to `<runs-root>/` by design — no stage
dirs, no `index.json`, no manifest. The ticket body is the doc (`## 决策日志`
+ `## Debt Register`). Optionally a single free-form
`<runs-root>/{slug}/working-notes.md` may capture scratch that does not
belong in the body; it has no schema and is not indexed. On graduation
(relabel `Kind/Feature`) the DAG route initializes the standard layout from
scratch; the MVP body remains the REQMAP baseline (analyze-dag backfill).
## Why a local tier
Agent-internal handoff (subagents sharing one worktree) currently pays the
Gitea tax (HTTP latency, wiki 409 conflicts, auth, Gitea-health dependency)
even though the consumer is a sibling subagent in the same process tree. The
embryo already proved local files viable (`/tmp/octopus/synthesis_task_*.md`
for `prompt_file`, see `core/skills/_shared/review-pipeline-phases.md` §Phase B step 1 — synthesis prompt).
This layout formalizes that embryo into a discoverable, schema-validated,
archive-at-close tier — without touching Gitea's role as the system of record
for human-visible decisions.
## Directory tree
```
<runs-root>/
├── .gitignore # ignores active-run workspaces; tracks archive/ + self
├── archive/ # TRACKED — closed-run bundles land here
│ ├── .gitkeep
│ └── {slug}.json # one bundle per closed run (meta + index digest)
└── {active-slug}/ # GITIGNORED — one subdir per active run
├── meta.json # run metadata (runs-meta.schema.json)
├── index.json # artifact manifest (runs-index.schema.json)
├── working-notes.md # cross-stage narrative memory ([org-internal #2600], schema-less md)
├── workers/ # worker final-return reports ([org-internal #2847], schema-less md —
│ └── ... # see _shared/worker-report-persistence.md)
└── {stage}/ # per-stage transient artifacts (added by skills)
└── ... # json (structured) / md (prompt files)
```
### `{active-slug}`
- One subdir per active pipeline run, named by the run slug (the same `{slug}`
used by the Gitea wiki namespace and the workflow branch tail).
- **Gitignored** while active — subagents share the worktree, not the git
history, so active-run content stays local (no repo bloat, no merge noise).
- **Initialized at run start** by `bun <harness-package>/script/runs-init.ts
--slug {slug} --ticket N [--branch …] [--worktree …]` ([org-internal #3642]) — the
producing role / Orchestrator runs the script (hand-writing the files is the
legacy path): it creates the `{active-slug}/` workspace, writes `meta.json`
with `state: "active"` and the run's slug/branch/worktree/ticket (branch
omitted / worktree null for ad-hoc runs without a worktree), and an empty
`index.json` (`artifacts: []`), schema-validated before write and idempotent
on re-run. Kind/MVP and Kind/Documentation tickets are skipped automatically
(label check, § Kind/MVP mode below). `script/claim-provision.sh` chains this
after claim + worktree in one command. Subsequent producing roles append to
`index.json` as they emit artifacts.
- Removed by the Verifier's archive step at close (its content is bundled into
`archive/{slug}.json` first).
### `meta.json` — run metadata
Schema: `core/schemas/runs-meta.schema.json`. Fields:
| field | type | notes |
|---|---|---|
| `schema_version` | integer | const `1` |
| `slug` | string | run slug; matches branch tail + wiki namespace |
| `ticket` | object\|null | `{ owner, repo, number }` of the source issue, or null for ad-hoc runs |
| `branch` | string | workflow branch, e.g. `workflow/enhancement/1968-two-tier-artifacts`; omitted for ad-hoc runs without a branch |
| `worktree` | string\|null | absolute path of the worktree, or null for ad-hoc runs without a worktree |
| `parent_epic` | object\|null | `{ number }` of the parent Epic, if any |
| `state` | enum | `active` → `archived` (set at close) |
| `created_at` | string | RFC 3339 timestamp |
| `updated_at` | string | RFC 3339 timestamp |
| `closed_at` | string\|null | RFC 3339; null while active |
| `close_commit_sha` | string\|null | git SHA of the archive commit; null while active |
### `index.json` — artifact manifest
Schema: `core/schemas/runs-index.schema.json`. This is the **compact
recovery entry point**: after `compact` (which never deletes local files), an
agent re-reads `index.json` to recover the run's artifact set without scanning
the tree. Each entry:
| field | type | notes |
|---|---|---|
| `schema_version` | integer | const `1` |
| `slug` | string | run slug |
| `artifacts` | array | one entry per Tier 1 artifact in this run |
| `artifacts[].path` | string | path relative to `{active-slug}/` |
| `artifacts[].type` | enum | `meta`, `index`, `design`, `plan`, `review-findings`, `review-status`, `review-synthesis`, `synthesis-prompt`, `precondition-gate`, `working-notes`, `analysis`, `browser-evidence`, `other` (authoritative enum: `core/schemas/runs-index.schema.json`) |
| `artifacts[].schema` | string\|null | `$id` of the validating schema, or null for schema-less (e.g. prompt `.md`) |
| `artifacts[].tier` | const `1` | local tier marker (Tier 2 is never listed here) |
| `artifacts[].stage` | enum\|null | producing pipeline stage (`requirements`/`design`/`plan-iterations`/`implement`/`review-code`/`verify`/…), or null for meta/index |
| `artifacts[].produced_by` | enum\|null | `Producer`/`Reviewer`/`Verifier`/`Tool`/`Coordinator` (canonical role names — the `name` field of `core/skills/_shared/roles/*.yaml`), plus fine-grained sub-roles mapped via `ROLE_ALIASES` in `<harness-package>/src/config/role.ts`: Producer sub-roles `Analyst`/`Architect`/`Planner`/`Developer`/`Synthesizer`/`Orchestrator`/`Remediator`, and the Reviewer sub-role `Auditor` |
| `artifacts[].sha` | string\|null | content digest (sha256), or null if not yet computed |
| `artifacts[].tier2_ref` | string\|null | cross-link to the Tier 2 mirror (e.g. issue comment URL / wiki page) when one exists |
`index.json` is **parallel, not replacement**, for the Tier 2 `## 工件索引`
issue comment: the former is the agent's compact-recovery entry; the latter is
the human-visible traceability hub. They are kept in sync by the producing
role but serve different consumers.
### `working-notes.md` — cross-stage narrative memory ([org-internal #2600])
`index.json` is a MANIFEST: it records WHAT artifacts exist. It does not
carry WHY code was written that way, which paths were tried and abandoned, or
what the reviewer's feedback actually changed — the tacit context every next
stage currently re-derives by re-reading formal artifacts. `working-notes.md`
is that narrative memory, in-file, append-only:
- **Format** (schema-less markdown): each entry is
`## [{stage}] {role} @ {RFC 3339 ts}` followed by **≤30 lines** covering:
decisions made (and why), dead ends (do NOT retry these), reviewer feedback
that changed the work, and one-line hints for the next stage.
- **Append cadence** — one entry per unit of ownership, never per subagent
dispatch: Producer sub-roles append at **stage exit**; the Reviewer side
appends **via the Synthesizer at review convergence** (one entry per
review, not per dimension — 9 dimension dispatches = 1 distilled entry);
the Verifier appends at **verify exit**; Tool agents append only when the
task produced a load-bearing finding (e.g. image evidence that changed a
decision).
- **Re-read rule**: the next stage reads `working-notes.md` FIRST — before
the formal artifacts — and may then skip full re-reads that the notes
already cover. Role yamls carry this line in `compact.preserve`
(`core/skills/_shared/roles/*.yaml`).
- **index.json row** (registered once by the first appending role):
`type: working-notes`, `schema: null` (markdown), `stage: null`
(cross-stage), `produced_by: null` (multi-role), `tier2_ref: null`.
- **Tier judgment**: consumed within one worktree/run ✓, never a human gate
decision ✓, never referenced cross-worktree ✓, archivable at close ✓ — all
four Tier 1 conditions hold (`core/rules/two-tier-artifacts.md` 判据).
### `workers/` — worker final-return reports ([org-internal #2847])
Every worker sub-agent dispatch (`agent: worker` roles — Producer
sub-roles, Verifier, Synthesizer) persists its **final return report**
verbatim to `workers/{chunk-id}-worker-{seq}.md` as its LAST action before
returning (lightweight fallback when no run workspace exists:
`/tmp/octopus/{chunk-id}-worker-{seq}.md`). The persisted copy is the report
of record — when the task completion notification is lost, the orchestrating
session recovers the worker's conclusion by reading the newest
`workers/{chunk-id}-worker-*.md` match. Skills that own a canonical stage
path for the dispatch (e.g. the review-code Synthesizer's
`reviews/{stage}/round{N}/synthesis-return.md`) use it instead of the
generic name. `index.json` row: `type: "other"`, `schema: null`, `stage` =
producing stage, `produced_by` = role. Full convention:
`core/skills/_shared/worker-report-persistence.md`.
### `browser/` — browser evidence packs ([org-internal #4497] N-03)
A browser-debug session writes one evidence pack per session under
`browser/{session-id}/` (contract `browser-evidence-4486/shared/pack-manifest-v1`,
frozen 2026-09-10 — `<harness-package>/src/browser/evidence-pack.ts` is the writer):
```
browser/{session-id}/
├── manifest.json # PackManifest: env probe (shared/env-probe-v1),
│ # replay metrics {attempted, succeeded, failures[]},
│ # artifact rows {kind, path, sha256_16}
├── trace/ # Playwright trace zips (recorded sessions)
├── screenshots/*.png # copies — the .playwright-mcp/ layer is additive-not-replaced
├── console-errors.jsonl # one sanitized entry per line
└── network-failures.json # sanitized failure summary
```
Write boundary invariants (the graph's single mandatory sanitize point,
`shared/sanitize-api-v1`): every textual capture passes `sanitizeForEvidence`
before hitting the disk; entries whose URL fails the navigation allowlist are
excluded from the pack; the write is non-blocking (the writer returns a handle
before the first byte lands). `index.json` row: `type: "browser-evidence"`,
`path: "browser/{session-id}/manifest.json"`, `stage` = producing session's
stage (`implement`/`verify`), `produced_by: "Tool"`.
### `{stage}/` — per-stage transient artifacts
Added incrementally by each skill as it migrates its transient output to Tier 1.
Conventions established by this foundation:
- Structured data → `.json` (schema-validated).
- Prompt files passed to subagents via `prompt_file` → `.md`.
- File naming mirrors the Gitea wiki namespace so a Tier 1 path and its (former
/ future) Tier 2 mirror are visually paired, e.g. on-disk
`<runs-root>/{slug}/reviews/{stage}/round{N}/findings-COR.json`. The
corresponding `index.json` `artifacts[].path` value strips the `{slug}/`
prefix (it is relative to `{active-slug}/`): `reviews/{stage}/round{N}/findings-COR.json`.
- A stage that has not yet migrated continues to write to Gitea (Tier 2) per its
existing skill body — the two tiers coexist during the migration.
**Migrated stages ([org-internal #1988]):**
| Stage | Skill | Tier 1 artifacts | Schema | Notes |
|---|---|---|---|---|
| `review-code` | review-code | `reviews/code/round{N}/findings-{DIM}.json` (raw dimension findings), `reviews/code/round{N}/task-synthesizer.md` (synthesis prompt) | `reviewer-output.schema.json` (findings) | synthesis comment + commit status stay Tier 2 |
| `review-roadmap` | review | `reviews/roadmap/round{N}/findings-{DIM}.json` | `reviewer-output.schema.json` | legacy target archived ([org-internal #3072] phase 3) — row kept for historical bundles |
| `review-design-space` | review | `reviews/design-space/round{N}/findings-{DIM}.json` | `reviewer-output.schema.json` | legacy target archived ([org-internal #3072] phase 3) — row kept for historical bundles |
| `review-iteration-plan` | review | `reviews/plan/round{N}/findings-{DIM}.json` | `reviewer-output.schema.json` | legacy target archived ([org-internal #3072] phase 3) — row kept for historical bundles |
| `review-dag` | review | `reviews/review-dag/round{N}/findings-{DIM}.json` | `reviewer-output.schema.json` | synthesis comment + commit status stay Tier 2 |
| `audit-process` | review | `reviews/audit-process/round{N}/findings-{DIM}.json` | `reviewer-output.schema.json` | synthesis wiki page + commit status stay Tier 2 |
| `requirements` (drafts) | requirements-elicitation | `requirements/` (schema-less `.md`/`.json`) | — | legacy skill archived ([org-internal #3072] phase 3) — historical bundles only |
| `design` (drafts) | design | `design/` (schema-less or `design-detail.schema.json`) | `design-detail.schema.json` (moved to `<instance-root>/archive/schemas/` [org-internal #3072] phase 3) | legacy skill archived ([org-internal #3072] phase 3) — historical bundles only |
| `plan-iterations` (drafts) | plan-iterations | `plan-iterations/` (schema-less or `plan-items.schema.json`) | `plan-items.schema.json` (moved to `<instance-root>/archive/schemas/` [org-internal #3072] phase 3) | legacy skill archived ([org-internal #3072] phase 3) — historical bundles only |
| `roadmap` (draft contracts) | roadmap | `roadmap/contracts/{name}.json` | `draft-contract.schema.json` (moved to `<instance-root>/archive/schemas/` [org-internal #3072] phase 3) | legacy skill archived ([org-internal #3072] phase 3) — historical bundles only |
Every review/audit stage additionally maintains a per-stage lifecycle file
`reviews/{stage}/status.json` (schema `review-status.schema.json`) — live
stages: `review-code`, `review-dag`, `audit-process`; legacy bundle stages
(`review-roadmap`, `review-design-space`, `review-iteration-plan`) maintain
the same file in historical bundles only. It is initialized by the
Orchestrator before round 1, appended by the Synthesizer each round, and
finalized with `converged` at Phase E. The Orchestrator appends an
`index.json` row for it (`type` = `review-status`, `stage` = `{REVIEW_TYPE}`,
`produced_by` = `Orchestrator`).
Stages NOT yet migrated (none — all target stages have been migrated or have opt-in Tier 1 affordances): reviser task prompts (wiki pages) remain Tier 2 in this slice; synthesis prompts for `review-artifact` / `audit-process` targets follow the shared Phase B mechanics (Tier 1 for all `tier1-local` skills).
## Archive-at-close
Closed by the **Verifier** at verify PASS (or by the Orchestrator for flows
without verify). The archive step is the ONLY point at which active-run content
enters git history:
1. Set `meta.json.state = "archived"`, record `closed_at` and `close_commit_sha`.
2. Write `archive/{slug}.json` — a compact bundle carrying `meta` + the
`index.json` manifest (the durable trace; bulk artifacts are summarized by
digest, not copied wholesale, to keep the archive small). One exception
([org-internal #2600]): when `working-notes.md` exists, its final content is inlined as
the bundle's top-level `working_notes` field — the narrative memory is the
single artifact whose content (not just digest) rides in the bundle,
feeding later retro probes and the notes-injection degradation path
([org-internal #2601]). The bundle is validated by `runs-bundle.schema.json`.
Offline review verifiability (TD/TRC-F006, [org-internal #2688]): for each review stage
the run executed, also embed a top-level `review_history` entry in the
bundle — `{stage, rounds, final_verdict, per_round[]}` (per-round:
round/overall_verdict/blockers/majors/minors) — derived from that stage's
live `reviews/{stage}/status.json` `history[]`. Closed-run probes can then
reconstruct round counts and verdicts from the bundle alone, without the
removed workspace or artifact-path reconstruction ([org-internal #2591]).
3. `git add archive/{slug}.json` and commit on the workflow branch (rides into
main via the `--no-ff` merge).
4. Remove the `{active-slug}/` workspace from the worktree (its durable record
now lives in the archive bundle).
Steps 14 collapse into ONE command ([org-internal #3642]): `bash script/archive-run.sh
{slug} [--pr N]` — run from the session worktree, it resolves the PR by branch
head, generates + validates the bundle via `gen-run-bundle.ts` (recording
`closed_at` / `close_commit_sha` and the `--branch` workflow branch), commits
it with a hook-conforming `[{chunk}][iter-N] chore(runs): …` message, removes
the workspace, and tears the worktree down unless invoked from inside it. The
`## 工件索引` archived remark (issue-checklist-sync.md § 归档动作) remains an
agent action — content judgment, not scripted.
Tier 2 (Gitea) remains the **system of record** for decisions (verdicts, issue
status, PRs, frozen contracts). The Tier 1 git archive is the **reproducibility
trace** — enough to reconstruct what a run produced, without re-paying the
Gitea tax.
## Relationship to existing mechanisms
- **`/tmp/octopus/` embryo** — `review-code` ([org-internal #1988] MVP) now writes its
synthesis prompt under `<runs-root>/{slug}/reviews/code/` instead of
`/tmp/octopus/synthesis_task_*`. Other skills still using the `/tmp/octopus/`
embryo coexist until their own migration iteration.
- **`## 工件索引` issue comment (Tier 2)** — unchanged; `index.json` (Tier 1) is
its agent-side parallel, not a replacement.
- **compact** — `compact` never deletes local files, so `index.json` survives
compaction and is the designated re-read entry for a compacted run.
- **worktree discipline** — all subagents of one workflow share its worktree, so
they share `<runs-root>/{slug}/` directly (no cross-worktree coordination
needed for Tier 1; that coordination lives in Tier 2).