okstra 0.138.0 → 0.140.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/docs/architecture.md +4 -4
- package/docs/cli.md +4 -3
- package/docs/for-ai/README.md +2 -0
- package/docs/for-ai/skills/okstra-code-review.md +57 -0
- package/docs/project-structure-overview.md +5 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/coding-preflight/architectures/hexagonal.md +34 -0
- package/runtime/prompts/coding-preflight/clean-code.md +109 -0
- package/runtime/prompts/coding-preflight/languages/javascript-typescript.md +11 -0
- package/runtime/prompts/coding-preflight/overview.md +3 -2
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +10 -4
- package/runtime/prompts/profiles/_implementation-self-check.md +2 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +12 -3
- package/runtime/prompts/profiles/implementation-planning.md +1 -0
- package/runtime/prompts/profiles/release-handoff.md +2 -1
- package/runtime/python/okstra_ctl/code_review_paths.py +64 -0
- package/runtime/python/okstra_ctl/code_review_target.py +152 -0
- package/runtime/python/okstra_ctl/worktree.py +3 -2
- package/runtime/python/okstra_project/__init__.py +2 -0
- package/runtime/python/okstra_project/state.py +142 -0
- package/runtime/skills/okstra-code-review/SKILL.md +180 -0
- package/runtime/skills/okstra-code-review/references/census-rules.md +100 -0
- package/runtime/skills/okstra-code-review/references/review-calibration.md +86 -0
- package/src/cli-registry.mjs +7 -0
- package/src/commands/inspect/code-review.mjs +35 -0
- package/src/lib/skill-catalog.mjs +1 -0
package/README.md
CHANGED
|
@@ -175,6 +175,7 @@ Use these slash commands inside a Claude Code session:
|
|
|
175
175
|
| `/okstra-manager` | Coordinate cross-project okstra tasks through manager-owned plans, assignments, one-way project sync snapshots, status, and child launch context packets |
|
|
176
176
|
| `/okstra-pr-gen` | Register PR body templates under `~/.okstra/template/pr/` and generate a PR description from a branch diff (subcommands: `template` / `branches` / `gen`). Global skill—needs a Git repo, not a registered okstra project |
|
|
177
177
|
| `/okstra-user-response` | Answer a task's open clarification questions in-session and write the response sidecar. Each answer is dispositioned as `answer` or `reframe`; a `reframe` is carried into the next run as a re-scoped brief |
|
|
178
|
+
| `/okstra-code-review` | Review what a diff changed—one okstra `implementation` stage, or any branch—against this project's coding-preflight rules, and write the result to a file. The orchestrator censuses the diff into an explicit worklist once, four parallel reviewers return a verdict for every cell, and a coverage audit re-dispatches any gap. Result files land under `.okstra/tasks/<task-group>/<task-id>/code-reviews/` (stage mode) or `.project-docs/code-reviews/<branch>/` (branch mode) |
|
|
178
179
|
| `/okstra-setup` | Bootstrap a project as described in §3.2 |
|
|
179
180
|
|
|
180
181
|
The lead operating contract and support contracts—context loader, team contract, convergence, report writer, and the coding-preflight pack—are no longer installed as agent skills. They are installed as okstra runtime resources under `~/.okstra/prompts/` (`prompts/lead/*.md`, `prompts/coding-preflight/*`), and the generated launch prompt gives the lead their absolute paths. Reinstallation prunes legacy copies from agent skill homes, so they are not exposed as slash commands.
|
package/docs/architecture.md
CHANGED
|
@@ -150,9 +150,9 @@ Runtime entry points are consolidated in Python packages. Bash and skills only c
|
|
|
150
150
|
- Runtime selection and worker-provider assignment remain independent. Milestone 1 keeps `claude-execution-prompt.md`, existing manifest role labels, model defaults, and worker rosters for compatibility; provider registry and front-door separation are later milestones.
|
|
151
151
|
- [`skills/okstra-setup/SKILL.md`](../skills/okstra-setup/SKILL.md) — **first-run bootstrap**. Runs `okstra install` and creates `project.json`.
|
|
152
152
|
- [`skills/okstra-run/SKILL.md`](../skills/okstra-run/SKILL.md) — in-session entry point that **starts an okstra task in the current Claude session**. Calls `prepare_task_bundle` directly.
|
|
153
|
-
-
|
|
153
|
+
- Thirteen skills are user-invocable: `skills/okstra-setup/SKILL.md`, `skills/okstra-brief-gen/SKILL.md`, `skills/okstra-run/SKILL.md`, `skills/okstra-manager/SKILL.md`, `skills/okstra-memory/SKILL.md`, `skills/okstra-inspect/SKILL.md`, `skills/okstra-rollup/SKILL.md`, `skills/okstra-usage/SKILL.md`, `skills/okstra-schedule-gen/SKILL.md`, `skills/okstra-container-build/SKILL.md`, `skills/okstra-pr-gen/SKILL.md`, `skills/okstra-user-response/SKILL.md`, and `skills/okstra-code-review/SKILL.md`. Only these are copied into the agent skill home. They cover brief authoring, phase execution, cross-project manager task coordination, global Memory Book storage/search, read-side status/history/report/time/logs/cost/errors/recap, task-group-level aggregation of run results (rollup), project-wide historical resource usage, schedule support, local container deployment, PR description generation, clarification-response submission, and census-based code review of a stage or branch diff. `okstra-manager` uses `okstra manager` CLI JSON/launch packets as the source of truth, and stores manager-owned plans, assignments, directives, snapshots, and events under `~/.okstra/managers/<manager-id>/`. `okstra-rollup` is a read-side layer that fans the single-task aggregators from `okstra-inspect` (time/errors/recap) out to a task group or the whole project catalog. The `okstra rollup` CLI owns deterministic aggregation, while the skill (LLM) writes only the synthesized report summary. `okstra-usage` is a separate read-only resource snapshot grouped by lifecycle task type; it does not replace single-task `okstra-inspect` detail or the status/report digest from `okstra-rollup`. The canonical definition of `okstra-inspect` read-side facets is the subcommand table in `skills/okstra-inspect/SKILL.md`. `okstra-inspect logs` provides a read-only inventory and cleanup guidance for the live-log sidecars that the Codex/Antigravity wrappers write on every dispatch at the resolved `<run-dir>/prompts/<worker>-prompt-<phase>-<seq>.log`; for stage executions, the stage-qualified `run_dir` includes `stage-<N>/`. `okstra-inspect cost` summarizes `okstra context-cost`; `okstra-inspect errors` collects a task's okstra-run error logs into a timestamped Markdown error report and prints a summary; and `okstra-inspect recap` answers free-form questions about `.okstra` artifacts in addition to summarizing phases before and after each task run.
|
|
154
154
|
- Internal operating contracts—`context-loader` / `team-contract` / `convergence` / `report-writer` and the lead contract—have moved to `prompts/lead/*.md`. Language-specific coding preflight for implementation/verification workers has moved to `prompts/coding-preflight/*` (overview router + clean-code + three-stage language/framework/architecture selection). All are runtime resources installed under `~/.okstra/prompts/` and are not discoverable as skills. The generated launch prompt provides the lead with absolute paths, and reinstalling prunes the legacy exact-name skill directories `okstra-context-loader` / `okstra-team-contract` / `okstra-convergence` / `okstra-report-writer` / `okstra-coding-preflight` / `okstra`.
|
|
155
|
-
- Plugin manifest: [`../../.claude-plugin/plugin.json`](../.claude-plugin/plugin.json) — referenced by the supplementary `npx skills@latest add Devonshin/okstra` channel. Use `npx okstra@latest install` for normal setup. The plugin manifest exposes only the
|
|
155
|
+
- Plugin manifest: [`../../.claude-plugin/plugin.json`](../.claude-plugin/plugin.json) — referenced by the supplementary `npx skills@latest add Devonshin/okstra` channel. Use `npx okstra@latest install` for normal setup. The plugin manifest exposes only the thirteen user entry points (`okstra-setup`, `okstra-brief-gen`, `okstra-run`, `okstra-manager`, `okstra-memory`, `okstra-inspect`, `okstra-rollup`, `okstra-usage`, `okstra-schedule-gen`, `okstra-container-build`, `okstra-pr-gen`, `okstra-user-response`, `okstra-code-review`).
|
|
156
156
|
- Installation location: `~/.claude/skills/<name>/SKILL.md` or `~/.agents/skills/<name>/SKILL.md`.
|
|
157
157
|
- Release procedure: [`../../RELEASING.md`](../RELEASING.md) — npm publish flow and release-please / manual fallback.
|
|
158
158
|
|
|
@@ -298,7 +298,7 @@ The standard `okstra` workflow applies the following team contract consistently
|
|
|
298
298
|
- The main Claude is always the `Claude lead` and operates in synthesis-only mode.
|
|
299
299
|
- The default required worker roles are `Claude worker`, `Codex worker`, and `Report writer worker`. `Antigravity worker` is optional and is included as required only when explicitly named by `--workers` or the profile's `- Workers:` section.
|
|
300
300
|
- `Report writer worker` focuses on report structure and evidence organization, but `Claude lead` remains the final synthesis owner.
|
|
301
|
-
- The default model contract is computed from central defaults. Fallbacks are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`
|
|
301
|
+
- The default model contract is computed from central defaults. Fallbacks are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`gemini-3.1-pro` (when opted in). `Report writer worker` no longer inherits the `Claude lead` model — it resolves from its own catalog `ROLE_DEFAULTS` entry because it does not vote in convergence; `--report-writer-model opus` or `OKSTRA_DEFAULT_REPORT_WRITER_MODEL` restores the previous behavior.
|
|
302
302
|
- Because `Antigravity worker` is optional, it is attempted only in runs where it is explicitly included.
|
|
303
303
|
- Before the final judgment, each required role in the current run's worker roster must have either a result or an explicit terminal status (`completed`, `timeout`, `error`, `not-run`).
|
|
304
304
|
- Every attempted worker (`completed`, `timeout`, `error`) must have an assigned worker prompt history file under the current run's `prompts/` directory.
|
|
@@ -821,7 +821,7 @@ Each validator blocks the phase with a `contract-violated` exit code when a cont
|
|
|
821
821
|
- The run directory is organized into typed subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`; prompt snapshots are prepared under `prompts/` first.
|
|
822
822
|
- Claude creates workers and collects results.
|
|
823
823
|
- The standard workflow uses a `Claude lead` with default workers `Claude worker`, `Codex worker`, and `Report writer worker`; `Antigravity worker` is optional and included only when explicitly requested.
|
|
824
|
-
- Worker models can be overridden with `--lead-model`, `--claude-model`, `--codex-model`, `--antigravity-model`, and `--report-writer-model`; defaults are centrally managed through `OKSTRA_DEFAULT_*` environment variables. Fallback defaults are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`
|
|
824
|
+
- Worker models can be overridden with `--lead-model`, `--claude-model`, `--codex-model`, `--antigravity-model`, and `--report-writer-model`; defaults are centrally managed through `OKSTRA_DEFAULT_*` environment variables. Fallback defaults are `Claude lead`=`opus`, `Claude worker`=`opus`, `Codex worker`=`gpt-5.6-sol`, `Report writer worker`=`sonnet`, and `Antigravity worker`=`gemini-3.1-pro`.
|
|
825
825
|
- For `--task-type implementation`, select the provider that takes the Executor role with `--executor <claude|codex|antigravity>` (or `OKSTRA_DEFAULT_EXECUTOR`, fallback `claude`). Only the Executor may mutate project files. The other two providers and the Executor's own provider are each dispatched as verifiers in separate CLI sessions (session isolation preserves the self-review safeguard). The Executor's model reuses the selected provider's worker-model flag (`--claude-model` / `--codex-model` / `--antigravity-model`). Provider / displayName / workerAgent / model are recorded in the run-manifest `teamContract.executor` block.
|
|
826
826
|
- Worktree cwd injection by Executor: Codex / Antigravity executors pin cwd to the worktree at the CLI layer through wrappers (`okstra-codex-exec.sh -C` / `okstra-antigravity-exec.sh --include-directories`). Because the Bash tool has no per-call cwd argument, the Claude executor prefixes cwd-sensitive toolchain invocations (`cargo`/`npm`/`pnpm`/`bun`/`pytest`/`make`/`go`) with `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>` in the same Bash invocation. Wrapping in `bash -lc`/`bash -c` is prohibited because it hides the leading `cd` token and defeats permission auto-allow. Prefer working-directory flags such as `git -C` or `cargo --manifest-path` when available. See the *Executor Worktree* block in `prompts/profiles/implementation.md` and the Executor exception in `agents/workers/claude-worker.md` for details.
|
|
827
827
|
- The project-level current-task convenience pointer is `.okstra/discovery/latest-task.json`.
|
package/docs/cli.md
CHANGED
|
@@ -365,7 +365,7 @@ When omitted, it uses the central default `OKSTRA_DEFAULT_CODEX_MODEL`, falling
|
|
|
365
365
|
### `--antigravity-model`
|
|
366
366
|
|
|
367
367
|
Selects the model used by the `Antigravity worker`.
|
|
368
|
-
When omitted, it uses the central default `OKSTRA_DEFAULT_ANTIGRAVITY_MODEL`, falling back to `
|
|
368
|
+
When omitted, it uses the central default `OKSTRA_DEFAULT_ANTIGRAVITY_MODEL`, falling back to `gemini-3.1-pro`.
|
|
369
369
|
|
|
370
370
|
### `--report-writer-model`
|
|
371
371
|
|
|
@@ -387,7 +387,7 @@ Fallback defaults are:
|
|
|
387
387
|
- `Report writer worker`: `sonnet`
|
|
388
388
|
- `Claude worker`: `opus`
|
|
389
389
|
- `Codex worker`: `gpt-5.6-sol`
|
|
390
|
-
- `Antigravity worker`: `
|
|
390
|
+
- `Antigravity worker`: `gemini-3.1-pro`
|
|
391
391
|
- Implementation executor: `claude`, so the default is `Claude executor`.
|
|
392
392
|
|
|
393
393
|
### `--executor`
|
|
@@ -396,7 +396,7 @@ Selects the provider that performs the Executor role for `--task-type implementa
|
|
|
396
396
|
|
|
397
397
|
- Default: `OKSTRA_DEFAULT_EXECUTOR` → fallback `claude`.
|
|
398
398
|
- The Executor is the **only worker allowed to mutate project files** in this run. The other two providers are dispatched as strict read-only verifiers in the same run.
|
|
399
|
-
- The Executor reuses the provider's worker model flag. With `--executor codex`, its model comes from `--codex-model`, default `gpt-5.6-sol`; with `--executor antigravity`, it comes from `--antigravity-model`, default `
|
|
399
|
+
- The Executor reuses the provider's worker model flag. With `--executor codex`, its model comes from `--codex-model`, default `gpt-5.6-sol`; with `--executor antigravity`, it comes from `--antigravity-model`, default `gemini-3.1-pro`.
|
|
400
400
|
- All three Claude, Codex, and Antigravity verifiers are always dispatched regardless of the Executor provider. Even the verifier using the same provider runs in a separate CLI session with isolated context, preserving the self-review safeguard.
|
|
401
401
|
- Codex and Antigravity mutate files through each CLI's auto-edit mode, for example `codex exec --sandbox workspace-write`, without passing through Claude-side Edit/Write tools. Mutations occur in the task worktree described below. Both wrappers—`scripts/okstra-codex-exec.sh` and `scripts/okstra-antigravity-exec.sh`—receive the worktree path as their fourth positional argument and forward it through `--add-dir` for Codex or `--include-directories` for Antigravity. Without it, the Codex `workspace-write` sandbox rejects worktree writes with EPERM.
|
|
402
402
|
- **Claude Executor cwd handling**: Claude's Bash tool has no per-call cwd argument and inherits the lead session cwd. To run cwd-sensitive toolchains such as `cargo`, `npm`, `pnpm`, `bun`, `pytest`, `make`, or `go` inside the worktree, prefix the invocation with `cd {{EXECUTOR_WORKTREE_PATH}} && <cmd>`. Keep `cd` as the leading token in a single Bash call so Claude Code permission auto-allow works; do not wrap it in `bash -lc "..."` or `bash -c "..."`, which hides `cd` and causes a permission prompt on every call. Prefer a tool's working-directory option—such as `git -C <path>`, `cargo --manifest-path`, or `pytest --rootdir`—over a `cd && ` chain. Edit/Write/Read tools already use absolute paths and need no cwd handling. This rule applies only to the Claude Executor; the Codex and Antigravity wrappers inject cwd.
|
|
@@ -661,6 +661,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
661
661
|
| `okstra stage-map <task-key> [--cwd <dir>\|--project <dir>]` | Dump the task's implementation-planning Stage Map as JSON: `{ ok, taskKey, taskRoot, stages:[{stage_number,title,depends_on,step_count}], doneStages:[int] }`. `stages` is `[]` when the task has no implementation-planning Stage Map. `doneStages` is read from the implementation-planning stage consumer state (with carry recovery). This is the read-side source the `okstra-schedule-gen` skill uses to derive stage units and their dependency closure |
|
|
662
662
|
| `okstra incremental-scope <args…>` | Decide re-verify vs carry-forward scope for an `implementation-planning` clarification re-run. Thin shim into `scripts/okstra_ctl/incremental_scope.py` (deterministic pure function): it reads the dependency graph from the prior run `data.json`'s `implementationPlanning.stageMap` and returns `mode:"incremental"` only when the base-ref SHA is unchanged and the affected stages' `downstream_stage_closure` covers at most half of all stages; otherwise it signals a full re-run. Used to bound the cost of a clarification re-run |
|
|
663
663
|
| `okstra incremental-carry <args…>` | Merge carried-forward plan-item verdicts into an incremental re-run. Thin shim into `scripts/okstra_ctl/incremental_carry.py`: it takes the prior run's plan-item verdicts that the current run does not re-verify and merges them into the current `data.json` tagged with `carriedForwardFromSeq`. A `schemaVersion` drift raises `CarryError` and exits non-zero to force a full fallback. Runs after `incremental-scope` returns `mode:"incremental"` |
|
|
664
|
+
| `okstra code-review target --task-key <k> --stage <N> [--project-root <dir>] [--cwd <dir>] [--json]` / `okstra code-review target --branch <name> [--base <ref>] [--date <YYYY-MM-DD>] [--project-root <dir>] [--cwd <dir>] [--json]` | Resolve what a code review reads and where its result file goes. Output is always JSON, so `--json` only makes that explicit. `--project-root` and `--cwd` are shared pre-dispatch arguments and apply to both modes; `--cwd` is only consulted when `--project-root` is absent. Both modes return `{ ok, projectRoot, mode, worktreePath, branch, baseCommit, headCommit, reviewPath, round }`; stage mode additionally returns `taskKey`, `taskRoot`, and `stage`. Stage mode takes the diff base from the `base_ref` recorded on that stage's worktree-registry row when it was provisioned — not from a rule re-applied at review time — and names the result `.okstra/tasks/<task-group>/<task-id>/code-reviews/stage-<NN>.md`, where a re-review of the same stage becomes `-r2`, `-r3`, … (the `round` field). Only a legacy row provisioned before `base_ref` was recorded falls back to re-deriving the base through `stage_targets`, and a failure there is reported as `stage_base_unresolved`. `worktreePath` comes back empty whenever the stage worktree is not usable as a live checkout — the registry row is no longer `active` (whole-task final-verification released it), the row never carried a path, or the recorded directory is gone — and the review then reads the `branch` ref instead. Branch mode uses `--base` when given, otherwise the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`), and names the result `.project-docs/code-reviews/<branch>/<YYYY-MM-DD>-<NN>.md`, where `<NN>` (the `round` field) is the next sequence number for that date — the highest already on disk plus one. Read-only: it resolves paths and creates no directory and no file, so the review directory does not exist until the caller writes the report. Backend for the okstra-code-review skill |
|
|
664
665
|
| `okstra set-work-status <token> <todo\|in-progress\|blocked\|done> [--note <text>] [--task-group <g>] [--project-root <dir>]` | Update user-managed `workStatus` in task-manifest.json, along with `workStatusUpdatedAt` and, when `--note` is supplied, `workStatusNote`. `<token>` is a full task key or bare task ID. It uses the manifest renderer's serialization rules and returns `stage:"ambiguous"` plus `matches[]` when ambiguous |
|
|
665
666
|
| `okstra worktree-lookup <task-key>` | Return the `worktree_registry.lookup` result: reserved path, branch, base ref, and current status |
|
|
666
667
|
| `okstra plan-validate <plan-path>` | Run `_validate_approved_plan` and report frontmatter `approved` recognition plus unresolved Blocks=approval rows |
|
package/docs/for-ai/README.md
CHANGED
|
@@ -28,6 +28,7 @@ This directory is a compressed manual for an AI to quickly select and precisely
|
|
|
28
28
|
| Manage the implementation-task worktree-based docker compose user-test environment | `okstra-container-build` | [`skills/okstra-container-build.md`](skills/okstra-container-build.md) |
|
|
29
29
|
| Answer the unresolved clarification questions an okstra run left behind in-session and record the approval gate | `okstra-user-response` | [`skills/okstra-user-response.md`](skills/okstra-user-response.md) |
|
|
30
30
|
| Register a PR body template or generate a PR description from a branch diff (global, git repository) | `okstra-pr-gen` | [`skills/okstra-pr-gen.md`](skills/okstra-pr-gen.md) |
|
|
31
|
+
| Review the changed code of one okstra `implementation` stage or of any branch against the coding-preflight rules, and write the result to a file | `okstra-code-review` | [`skills/okstra-code-review.md`](skills/okstra-code-review.md) |
|
|
31
32
|
|
|
32
33
|
## Shared Execution Rules
|
|
33
34
|
|
|
@@ -62,3 +63,4 @@ The public skills listed in this AI manual are the following 13:
|
|
|
62
63
|
- `okstra-container-build`
|
|
63
64
|
- `okstra-user-response`
|
|
64
65
|
- `okstra-pr-gen`
|
|
66
|
+
- `okstra-code-review`
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# okstra-code-review AI Manual
|
|
2
|
+
|
|
3
|
+
## Sources
|
|
4
|
+
|
|
5
|
+
- Skill source: [`skills/okstra-code-review/SKILL.md`](../../../skills/okstra-code-review/SKILL.md)
|
|
6
|
+
- Census rules: [`skills/okstra-code-review/references/census-rules.md`](../../../skills/okstra-code-review/references/census-rules.md)
|
|
7
|
+
- Review calibration: [`skills/okstra-code-review/references/review-calibration.md`](../../../skills/okstra-code-review/references/review-calibration.md)
|
|
8
|
+
- Target core (CLI): [`scripts/okstra_ctl/code_review_target.py`](../../../scripts/okstra_ctl/code_review_target.py)
|
|
9
|
+
- Review path core: [`scripts/okstra_ctl/code_review_paths.py`](../../../scripts/okstra_ctl/code_review_paths.py)
|
|
10
|
+
- Node wrapper: [`src/commands/inspect/code-review.mjs`](../../../src/commands/inspect/code-review.mjs)
|
|
11
|
+
- Rules the review applies: [`prompts/coding-preflight/`](../../../prompts/coding-preflight/)
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
`okstra-code-review` reviews what a diff changed — one okstra `implementation` stage, or any branch unrelated to an okstra run — against this project's coding-preflight rules, and leaves the result as a file under the project.
|
|
16
|
+
|
|
17
|
+
**Core principle — the census is law.** The orchestrator turns the diff into an explicit worklist of cells **once, deterministically**, before any reviewer is dispatched. Reviewers receive their slice as input and never rebuild, reinterpret, or extend it. Each returns a verdict for **every** cell it was given (`clean` or findings), so a skipped cell is visible rather than silently absent, and the coverage audit re-dispatches every cell that came back without one.
|
|
18
|
+
|
|
19
|
+
**Second principle — the rules are not in the skill.** They live in `prompts/coding-preflight/` (`overview.md` router + `clean-code.md` + the routed `languages/` / `frameworks/` / `architectures/` resources). Briefs point at absolute pack paths; they never restate a rule.
|
|
20
|
+
|
|
21
|
+
Distinguish it from writing a PR body (`okstra-pr-gen`), starting a run (`okstra-run`), and inspecting a finished task (`okstra-inspect`).
|
|
22
|
+
|
|
23
|
+
## Modes
|
|
24
|
+
|
|
25
|
+
| Mode | Trigger | Diff range | Result file |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| stage | a task token (`DEV-9184`, or a full `project-id:task-group:task-id` key) | the stage's registry `base_ref` → the stage branch head | `.okstra/tasks/<task-group>/<task-id>/code-reviews/stage-<NN>.md` (re-review: `-r2`, `-r3`, …) |
|
|
28
|
+
| branch | a branch name | `--base` when given, otherwise the CLI's estimate against the default branch | `.project-docs/code-reviews/<branch>/<YYYY-MM-DD>-<NN>.md` |
|
|
29
|
+
|
|
30
|
+
The branch-mode result path is the one deliberate exception to the `.okstra/`-only artifact rule: a branch review belongs to no task bundle.
|
|
31
|
+
|
|
32
|
+
## Preflight
|
|
33
|
+
|
|
34
|
+
A single Bash call with the literal `okstra` token (not wrapped in `if` / `eval` / `export` / `$(...)` / `VAR=` / `||` / `&&` / `npx`):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
okstra preflight --runtime claude-code --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`ok:true` → carry `projectRoot` as a literal. `ok:false` → retry the intended directory with `--cwd <dir>`; if that also fails, point at `/okstra-setup` and stop. `unknown command: preflight` or `unknown command: code-review` means the `okstra` binary predates the skill — `npm i -g okstra@latest`, then stop.
|
|
41
|
+
|
|
42
|
+
## Flow
|
|
43
|
+
|
|
44
|
+
1. **Resolve the target.** Stage mode: `okstra resolve-task-key <token> --project-root <projectRoot> --json`, then `okstra stage-map <taskKey> --project <projectRoot> --json`, and pick the stage with a 3-option picker (recommendations first, `Enter directly` last). Branch mode: pick the branch the same way; a detached HEAD is refused.
|
|
45
|
+
2. **Call the target CLI**: `okstra code-review target --task-key <k> --stage <N> --project-root <dir> --json`, or `okstra code-review target --branch <name> [--base <ref>] --project-root <dir> --json`. It returns `{ok, projectRoot, mode, worktreePath, branch, baseCommit, headCommit, reviewPath, round}` (stage mode adds `taskKey`, `taskRoot`, `stage`). **Never derive the base** — the CLI owns it, and `baseCommit` may be a ref rather than a commit id, so pass it through verbatim. Run git in `worktreePath` when non-empty, otherwise in `projectRoot` against `branch`.
|
|
46
|
+
3. **Show the base and confirm it** with a 3-option picker before censusing anything — the returned `baseCommit` plus `git log -1 --oneline <baseCommit>` first, `Enter directly` last. Only an override calls the target CLI a second time, with `--base <ref>`.
|
|
47
|
+
4. **Census the diff** per `census-rules.md`: four axes (`structural`, `semantic`, `state-and-tests`, `general`), one cell per target per axis — the axis **is** the rule group, never one cell per individual rule. Membership is mechanical; judgment only ever decides a verdict. Route the coding-preflight packs exactly once here (`okstra paths --field home` → `<okstraHome>/prompts/coding-preflight/overview.md`), and fix the calibration path the briefs carry (`~/.claude/skills/okstra-code-review/references/review-calibration.md`). Print every cell table, every exclusion with its reason, the applied packs, and both completion criteria. Never truncate a large census — report the cell count and confirm.
|
|
48
|
+
5. **Dispatch four reviewers in parallel** — all four `Agent` calls in one message, `subagent_type: "general-purpose"`. Each brief carries the diff, the work directory, its own axis's cell list verbatim, its packs' absolute paths, and the absolute calibration path.
|
|
49
|
+
6. **Audit the coverage.** Diff each reviewer's returned cells against the slice it was handed; re-dispatch one gap-fill agent per axis for the missing cells, and repeat until every cell has a verdict. A missing verdict is unfinished work, never an implicit `clean`.
|
|
50
|
+
7. **Merge and write.** Dedupe across axes, never re-grade a severity, and write the report to `reviewPath` with `Write` (frontmatter `mode` / `taskKey` / `branch` / `stage` / `round` / `baseCommit` / `headCommit` / `packs` / `generatedAt`; body `## Coverage`, `## Must-fix`, `## Should-fix`, `## Nits`, `## Score`). An empty diff dispatches no reviewer and still writes the report — Coverage reading zero cells and a Score table totalling 0.
|
|
51
|
+
|
|
52
|
+
## Output Rules
|
|
53
|
+
|
|
54
|
+
- The file is the deliverable. In session, print only `reviewPath`, the count per severity, and the score total — do not replay the findings in chat.
|
|
55
|
+
- The report's prose is Korean; paths, identifiers, rule names, and quoted code stay verbatim.
|
|
56
|
+
- Every finding cites a line **this diff changed**, and carries a concrete fix (a pseudocode sketch for readability, an alternative name for naming, a destination for structural).
|
|
57
|
+
- Read-only against the repository: `okstra code-review target` creates no directory and no file, and the review never reconciles git history. A rewritten base is reported, not force-fixed.
|
|
@@ -199,6 +199,7 @@ Runtime/install asset changes follow this checklist:
|
|
|
199
199
|
| `rollup` | `src/commands/inspect/rollup.mjs` | `okstra rollup` thin shim into `scripts/okstra_ctl/rollup.py` — read-only cross-task roll-up backing the okstra-rollup skill |
|
|
200
200
|
| `usage-report` | `src/commands/inspect/usage-report.mjs` | `okstra usage-report` thin shim into `scripts/okstra_ctl/usage_report.py` — read-only project usage snapshot backing the okstra-usage skill |
|
|
201
201
|
| `container` | `src/commands/inspect/container.mjs` | `bin okstra container` thin shim into `scripts/okstra_ctl/container.py` for the okstra-container-build skill |
|
|
202
|
+
| `code-review` | `src/commands/inspect/code-review.mjs` | `okstra code-review target` thin shim into `scripts/okstra_ctl/code_review_target.py` — resolves what one implementation stage's or one branch's review reads (worktree, branch, base/head commits) and where its result file goes, for the okstra-code-review skill. Read-only; creates no directory or file |
|
|
202
203
|
| `manager` | `src/commands/manager.mjs` | Thin shim into `scripts/okstra_ctl/manager_cli.py` for cross-project manager state and child launch packets |
|
|
203
204
|
|
|
204
205
|
`src/lib/python-helper.mjs` centralizes Node → Python execution so command modules do not duplicate subprocess wiring.
|
|
@@ -269,6 +270,8 @@ Important modules:
|
|
|
269
270
|
| `json_registry.py` | shared flock + atomic JSON persistence for small okstra registries (`registry_lock`/`load_registry_json`/`save_registry_json`) — shared by `container_registry` and `worktree_registry` |
|
|
270
271
|
| `stage_integrate.py` | whole-task stage integration (merge) + worktree teardown core (`integrate_stages`) — shared by whole-task final-verification entry, `okstra integrate-stages`, and container up |
|
|
271
272
|
| `resolve_task_key.py` | shared skill helper resolving a bare task-id → `task-catalog.json` candidate entries (`okstra resolve-task-key`) |
|
|
273
|
+
| `code_review_paths.py` | filesystem-layout SSOT for code-review result files — `stage_review_dir` / `branch_review_dir` plus `next_stage_review` / `next_branch_review`, which read the existing files to derive the next round's name (`stage-<NN>.md`, then `-r2`, `-r3`, …) or the next same-day sequence (`<YYYY-MM-DD>-<NN>.md`), so skill markdown never re-derives a literal review path |
|
|
274
|
+
| `code_review_target.py` | `okstra code-review target` backend — argument validation and JSON shaping only. Stage mode delegates whole to `okstra_project.state.code_review_target_snapshot`; branch mode is resolved here, defaulting the diff base to the merge-base with the default branch (`refs/remotes/origin/HEAD`, else `main`/`master`). Read-only: it never creates the review directory |
|
|
272
275
|
| `session.py`, `tmux.py`, `seeding.py`, `locks.py`, `invocation.py`, `sequence.py`, `ids.py`, `material.py` | Supporting lifecycle helpers |
|
|
273
276
|
| `pane_reclaim.py` | decides which completed trace panes are reclaim targets; imports the in-progress status set from the `reconcile.NON_TERMINAL_RECENT_STATUSES` SSOT |
|
|
274
277
|
| `improvement_lenses.py` | lens enum SSOT + cap constants for the improvement-discovery phase (DEFAULT 8, ABSOLUTE 12, MIN/MAX PRIORITY 1/4, SOURCE_WORKERS) |
|
|
@@ -315,6 +318,7 @@ Project resolver and read-only state helpers:
|
|
|
315
318
|
|
|
316
319
|
- `resolver.py`: locate project root and upsert `project.json`
|
|
317
320
|
- `state.py`: read project metadata, task catalog, task manifest, and curated task read-side snapshots
|
|
321
|
+
- `code_review_target_snapshot()`: resolve one implementation stage's review target — worktree path (empty once the stage worktree is torn down), branch, `base_ref` from the stage's registry row, head commit, result path, and round — so callers never read the registry or the run-root layout themselves
|
|
318
322
|
|
|
319
323
|
### 4.5 `scripts/okstra_token_usage/`
|
|
320
324
|
|
|
@@ -393,6 +397,7 @@ Boilerplate shared by several skills (bash invocation rule, outdated-CLI preflig
|
|
|
393
397
|
| `okstra-manager` | yes | Coordinate cross-project okstra tasks through manager-owned plans, assignments, sync snapshots, status, and child launch context packets |
|
|
394
398
|
| `okstra-setup` | yes | Install/check runtime and register project |
|
|
395
399
|
| `okstra-user-response` | yes | Submit answers to a run's open clarification items through the installed response helper without hand-editing artifacts |
|
|
400
|
+
| `okstra-code-review` | yes | Census-based code review of a diff — one okstra `implementation` stage, or any branch — against this project's coding-preflight rules. The result file goes under the task bundle (`code-reviews/stage-<NN>.md`) in stage mode and under `.project-docs/code-reviews/<branch>/` in branch mode; `okstra code-review target` resolves both |
|
|
396
401
|
|
|
397
402
|
> The former internal skills — `context-loader`, `team-contract`, `convergence`, `report-writer`, `coding-preflight` — and the lead contract are no longer skills. They ship as runtime resources under `prompts/lead/*` and `prompts/coding-preflight/*` (installed to `~/.okstra/prompts/`); see §4.11.
|
|
398
403
|
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -107,6 +107,38 @@ The test: **is this type/class a thing the business talks about, or is it plumbi
|
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
110
|
+
## Rule H4 — Dependency direction: the domain imports nothing outward
|
|
111
|
+
|
|
112
|
+
The cardinal rule of ports and adapters: dependencies point inward. A file under the domain folder imports only from the domain itself and from pure utility code. Unlike H1–H3 this verdict is **mechanical** — read the import list of every changed domain file; no judgment about what the code means is required.
|
|
113
|
+
|
|
114
|
+
Violations — any import of:
|
|
115
|
+
|
|
116
|
+
- an ORM or DB layer (`sequelize`, `typeorm`, `prisma`, db model classes),
|
|
117
|
+
- a framework or its DI decorators (`@nestjs/*`, `express`),
|
|
118
|
+
- anything under adapters / repositories / infrastructure, DTO, or services.
|
|
119
|
+
|
|
120
|
+
Not a violation:
|
|
121
|
+
|
|
122
|
+
- imports from sibling domain files,
|
|
123
|
+
- pure utility libraries (`lodash`, `date-fns`),
|
|
124
|
+
- type-only imports of other *domain* types.
|
|
125
|
+
|
|
126
|
+
When the domain needs something the outside owns, invert it: declare the shape as a port and let the adapter implement it (H5).
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Rule H5 — A new boundary gets a port
|
|
131
|
+
|
|
132
|
+
When a change adds or modifies a service's dependency on a concrete adapter — constructor injection of a repository class, or direct model / DB access from a service — that boundary should be a port the adapter implements, so the caller depends on shape rather than on infrastructure.
|
|
133
|
+
|
|
134
|
+
The verdict is mechanical: this change adds or modifies such a dependency → finding; the injection sits entirely on lines this change did not touch → clean.
|
|
135
|
+
|
|
136
|
+
**An existing convention does not clear this one.** A codebase that injects concrete `*Repository` classes everywhere is precisely the debt this rule pays down, one touched injection at a time — matching the surrounding style is the condition being flagged, not a defence against it. Record it, say so in the note (*"matches existing convention — advisory"*), and propose the port: its name and the two or three method signatures it would declare. This is the single rule in this overlay where a project-local convention does not override the pack; every other conflict still resolves in the project's favour.
|
|
137
|
+
|
|
138
|
+
Severity: advisory. It is a direction-of-travel rule, not a correctness gate — it never blocks on its own.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
110
142
|
## Quick checklist before declaring a port/adapter change complete
|
|
111
143
|
|
|
112
144
|
- [ ] No `if` / `.filter` / validation / business math inside any port body.
|
|
@@ -114,3 +146,5 @@ The test: **is this type/class a thing the business talks about, or is it plumbi
|
|
|
114
146
|
- [ ] Adapter method names are neutral (`findAll`, `findByUserId`) unless the domain-flavored name passes the H2 judgment test.
|
|
115
147
|
- [ ] Every entity, value object, domain enum, and domain error is declared under `domain/`.
|
|
116
148
|
- [ ] Port files only declare shape and `import` domain types — they don't *define* them.
|
|
149
|
+
- [ ] No changed domain file imports an ORM, a framework, or anything under adapters / infrastructure / services.
|
|
150
|
+
- [ ] A service dependency this change adds or modifies goes through a port — or the advisory is recorded with the port sketch.
|
|
@@ -113,6 +113,20 @@ The test: if the name appeared in a stacktrace, an autocomplete list, or a grep
|
|
|
113
113
|
|
|
114
114
|
This rule applies most strongly to **names crossing module boundaries**: public methods, exported constants, port methods. Private helpers in a tight local scope get more slack.
|
|
115
115
|
|
|
116
|
+
### One identifier, one meaning per file
|
|
117
|
+
|
|
118
|
+
A name that means two things in one file makes every reader re-derive which one they are looking at. The common shape: a new parameter holding a key takes the name a sibling signature already gives to the whole entity.
|
|
119
|
+
|
|
120
|
+
Bad:
|
|
121
|
+
```ts
|
|
122
|
+
findFullAccount(account: string) // a primary key
|
|
123
|
+
update(idAccount: string, account: AccountRow) // the row — same word, four lines away
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Good: `findFullAccount(idAccount: string)` — the key takes the name the file already uses for keys.
|
|
127
|
+
|
|
128
|
+
The test: grep the identifier across the file. If two occurrences carry different types or different roles, rename the newer one to the distinguishing fact and follow whatever name the file already uses for that role.
|
|
129
|
+
|
|
116
130
|
## Functions do one thing
|
|
117
131
|
|
|
118
132
|
If the name contains "and", or you must scroll to read it, split it.
|
|
@@ -160,6 +174,56 @@ Crossing the cap is an extraction signal, not a style nit. Before declaring a fu
|
|
|
160
174
|
- Idiomatic framework patterns used clearly (a 30-line controller method obviously doing its job).
|
|
161
175
|
- Guard clauses at the top — they *help* readability, they don't count against you.
|
|
162
176
|
|
|
177
|
+
## Mutation and state boundaries
|
|
178
|
+
|
|
179
|
+
These defects survive a green suite because the test reads the same object the code mutated. State the fact you actually verified, at the moment you verified it.
|
|
180
|
+
|
|
181
|
+
### Decide on the direct fact, not a proxy
|
|
182
|
+
|
|
183
|
+
A status field, a flag, or a "was processed" marker is evidence *about* a decision, not the decision itself. When the decision turns on identity or a destination — did this row move, did ownership change, is this the same target — compare the source and destination identifiers directly. If the direct fact is unavailable, do not perform the change; surface it.
|
|
184
|
+
|
|
185
|
+
The test: construct the opposite case and ask whether your condition still reads true. `status === 'DONE'` holds both for "already moved here" and "moved somewhere else"; `row.parentId === target.parentId` does not.
|
|
186
|
+
|
|
187
|
+
### Capture before-state before the mutating boundary
|
|
188
|
+
|
|
189
|
+
When a value describes the state *before* processing, read it before anything that can mutate the object — a strategy call, a repository write, a reload, any `await` that hands the object to other code. Read every before-value feeding the same diagnostic in **one snapshot, at one moment**.
|
|
190
|
+
|
|
191
|
+
Never re-read a before-field off the original object after that boundary, and never reconstruct it from the post-state. Pass before-state and result-state as two explicit values.
|
|
192
|
+
|
|
193
|
+
### Update only the fields this work owns
|
|
194
|
+
|
|
195
|
+
Creating a new row and updating an existing one are not the same write authority. When reusing an existing row, update only the fields the current operation owns, and express them as an allowlist. Provenance, ownership, and externally-managed values are not overwritten without an explicit requirement saying so. Keep the create-vs-reuse answer available at the later write step — that is what tells the two authorities apart.
|
|
196
|
+
|
|
197
|
+
The test: for every field this change writes, grep the other sites that write the same field in the same flow — a later pass of the same loop, a final persist, a sibling handler — and put them in order. If a later write clears what an earlier one recorded, the clear needs a condition, not a default.
|
|
198
|
+
|
|
199
|
+
### Zero affected rows is a question, not an answer
|
|
200
|
+
|
|
201
|
+
A conditional write that changed no rows has not told you whether it succeeded. Re-read the current state and separate the three cases: already in the desired state, changed to a different conflicting state, target deleted. Each gets its own success/failure handling and its own message — collapsing conflict and deletion into one error hides the case the caller must act on.
|
|
202
|
+
|
|
203
|
+
### Priority between competing inputs is policy, not an if-chain
|
|
204
|
+
|
|
205
|
+
Which of several inputs wins is a business rule. It does not belong in a service's early return or inline condition, where the next caller re-derives it differently. The service collects inputs; a named domain function decides. The function's name alone should tell you the policy.
|
|
206
|
+
|
|
207
|
+
### An error message states only what you observed
|
|
208
|
+
|
|
209
|
+
Do not assert a cause the code never checked — "target not found" after a write that only reported zero rows is a guess. Failures with different remedies (deleted, state mismatch, concurrent change) get different messages, each covered by a test on that path.
|
|
210
|
+
|
|
211
|
+
## Wrapping a third-party call
|
|
212
|
+
|
|
213
|
+
### The wrapper must add something the library does not already do
|
|
214
|
+
|
|
215
|
+
Before writing recovery, retry, or fallback code around a library call, read that library's own handling of the same case in its installed source. A `catch` that repeats what the library already did — same read, same transaction, same conditions — recovers nothing; it only replaces a typed error with a weaker one.
|
|
216
|
+
|
|
217
|
+
The test: name the condition under which your branch runs and the library's does not. If you cannot name it from the library's source, the branch is dead. Delete it, or make it do something the library provably does not — a locking read where the library read without a lock, a different isolation level, a retry the library never attempts.
|
|
218
|
+
|
|
219
|
+
### A comment may not name a condition the code does not run under
|
|
220
|
+
|
|
221
|
+
Recovery documented as "under REPEATABLE READ this re-read succeeds" is false when every caller opens READ COMMITTED. Cite the call site that establishes the condition, or drop the claim — an unverifiable comment outlives the code it excuses.
|
|
222
|
+
|
|
223
|
+
### Rethrowing keeps the original error
|
|
224
|
+
|
|
225
|
+
Replacing a typed library error with a bare one throws away what the caller needs in order to act: which constraint fired, the driver's message, the original stack. Keep the original as the cause (`new Error(msg, { cause: err })`, `raise X from err`) and keep the type when a caller branches on it.
|
|
226
|
+
|
|
163
227
|
## Testing discipline
|
|
164
228
|
|
|
165
229
|
These principles apply to every test file regardless of language or framework. The mechanical detection patterns (which mock library, which spy API) are in each `languages/*.md` Tests section.
|
|
@@ -205,6 +269,51 @@ Legitimately behavioral (these are fine):
|
|
|
205
269
|
|
|
206
270
|
The judgment call: is the mocked thing an **outcome boundary** (port, external service, side-effecting adapter) or an **internal helper**? Asserting on boundaries is behavioral; asserting on helpers is implementation-coupled.
|
|
207
271
|
|
|
272
|
+
### Assert the last write, not the first
|
|
273
|
+
|
|
274
|
+
When one run writes the same record more than once — an update followed by a final persist, two passes of the same loop over sibling rows — asserting the earlier call proves nothing about what the record ends up holding. The later write can drop the very field the test asserted and the test stays green, so the suite certifies a broken end state.
|
|
275
|
+
|
|
276
|
+
The test: list every write in this flow that touches the field you assert, and confirm the one you assert is the last. If it is not, assert the last one too.
|
|
277
|
+
|
|
278
|
+
### An effect under its own mock is not evidence
|
|
279
|
+
|
|
280
|
+
When a mock replaces the code path that would produce an effect, a test above that mock has not verified the effect — it verified that the mock was reachable. Claim coverage for an effect only where the real path runs; the presence of a mock in the harness is not proof that the branch behind it works.
|
|
281
|
+
|
|
282
|
+
### Every branch you add gets a test that dies with it
|
|
283
|
+
|
|
284
|
+
A new `catch`, guard, early return, or `else` is a behavior claim. Coverage for it means one test that FAILS when the branch body is deleted — not a happy-path test that merely reaches the same method.
|
|
285
|
+
|
|
286
|
+
The test: delete the branch body and run the suite. Green means the branch is untested, and an untested recovery path is indistinguishable from one that cannot fire at all.
|
|
287
|
+
|
|
288
|
+
This binds hardest on error-recovery code, where the happy path is what the library already does and the branch is the only behavior your code adds.
|
|
289
|
+
|
|
290
|
+
### Shared fixtures keep the ordinary default
|
|
291
|
+
|
|
292
|
+
A shared fixture's defaults describe the normal path. Do not flip a default to exercise one exception — every existing caller silently changes meaning. Pass the exceptional condition explicitly in the test that needs it (`makeFamily({ familyEnabled: false })`), and search all call sites before changing any default.
|
|
293
|
+
|
|
294
|
+
The test: does the no-argument fixture call still describe the ordinary case?
|
|
295
|
+
|
|
296
|
+
### Test data must actually separate the paths
|
|
297
|
+
|
|
298
|
+
Two scenarios whose setup values and assertions are identical are one test with two names. Give source and destination, existing and incoming, success and conflict deliberately different values. The check: state each test's distinguishing input in one sentence, then delete that difference — if the test still passes, it never tested the path. A wrong implementation must not pass because the defaults happened to agree.
|
|
299
|
+
|
|
300
|
+
### A test title names the unit and the one condition it isolates
|
|
301
|
+
|
|
302
|
+
A test title is a name, so the truthful and stand-alone rules above apply to it. From a failure line alone the reader must know which unit ran and which single condition was under test.
|
|
303
|
+
|
|
304
|
+
- Name the unit when the file covers more than one. `'returns the primary-key lookup result'` fits every by-key read in the class; `'findFullAccount returns …'`, or a `describe('findFullAccount', …)` wrapper, fits one.
|
|
305
|
+
- Credit only the condition the case actually isolates. A parameterised case that trips two guards at once must not name one of them in the title — split it into one case per guard.
|
|
306
|
+
|
|
307
|
+
The test: delete from the setup the input the title credits. If the case still passes, the title describes a guard the test never reached — retitle it, or fix the setup so it does.
|
|
308
|
+
|
|
309
|
+
### Test tooling you add is used in the same change
|
|
310
|
+
|
|
311
|
+
A new mock, state setter, or repository branch that no test calls is unfinished work, not coverage. Grep every new test identifier for a use site before claiming done, and check that a repository mock does not only ever return success.
|
|
312
|
+
|
|
313
|
+
### Resource bounds need their own regression test
|
|
314
|
+
|
|
315
|
+
A change to memory use, concurrency, batching, or stream handling is not covered by a functional test that passes on a small input. Add a test that pins the bound the change claims to hold — peak size, concurrent count, chunk count.
|
|
316
|
+
|
|
208
317
|
## No magic numbers
|
|
209
318
|
|
|
210
319
|
Replace hardcoded values with named constants.
|
|
@@ -36,6 +36,7 @@ Airbnb JavaScript Style Guide is the common baseline. The project's `.eslintrc(.
|
|
|
36
36
|
- Empty literals where inference would default to `never` or `{}` — `const items: User[] = []`.
|
|
37
37
|
- Use `readonly` for arrays, properties, and parameters that must not mutate.
|
|
38
38
|
- Discriminated unions over loose objects with optional flags. Use a literal `kind` / `type` field.
|
|
39
|
+
- **Never re-declare an authoritative type.** When the domain or a dependency already exports the enum / union for a set of state values (`FontStatus`, `OrderState`), import it — do not hand-maintain a parallel string union or literal list. Before writing a new state type, grep the state literals *and* the intended type name for an existing declaration. A new type's name must say whose values it holds.
|
|
39
40
|
- Type guards (`function isX(v): v is X`) over casts (`as X`). Casts are a last resort.
|
|
40
41
|
- `interface` for object shapes that may be extended; `type` for unions / mapped / conditional types.
|
|
41
42
|
- Generics: meaningful names when single letters are unclear (`TUser`, `TResult`).
|
|
@@ -75,6 +76,16 @@ Airbnb JavaScript Style Guide is the common baseline. The project's `.eslintrc(.
|
|
|
75
76
|
|
|
76
77
|
What's fine: `jest.fn()` for collaborator stubs passed via constructor DI, `vi.mock('../mailer')` to fake a side-effecting module at the boundary, `expect(mailer.send).toHaveBeenCalledWith(...)` on an outcome boundary.
|
|
77
78
|
|
|
79
|
+
### Never read mock call arguments by position
|
|
80
|
+
|
|
81
|
+
`repository.save.mock.calls[0][2]` says nothing about what that argument means, and it keeps compiling after a signature change invalidates it. Assert the call with named intent:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
expect(repository.save).toHaveBeenCalledWith(id, versionId, expectedInfos, transaction);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When the *absence* of a property is the point, assert the exact object or that property's absence directly — a loose `expect.objectContaining` passes even when the property is present. If positional access is genuinely unavoidable, name the index once inside a test helper (`savedInfosOf(repository.save)`) so the meaning lives in one place. Detect with `rg 'mock\.calls'` over the changed test files.
|
|
88
|
+
|
|
78
89
|
## Formatter / linter to run
|
|
79
90
|
|
|
80
91
|
- `prettier --write .` (formatting).
|
|
@@ -48,9 +48,10 @@ If no Stage 1 language rule matches (an unlisted language), stop and ask the use
|
|
|
48
48
|
## Mandatory pre-write checks (every language)
|
|
49
49
|
|
|
50
50
|
- [ ] Language reference read for this turn.
|
|
51
|
-
- [ ] `clean-code.md` principles applied: DRY, KISS, SOLID, YAGNI, meaningful naming (truthful + standalone), single-purpose functions, plain-English summary test, 50-line cap, no magic numbers, shallow nesting, comments-explain-why.
|
|
51
|
+
- [ ] `clean-code.md` principles applied: DRY, KISS, SOLID, YAGNI, meaningful naming (truthful + standalone + one identifier one meaning per file), single-purpose functions, plain-English summary test, 50-line cap, no magic numbers, shallow nesting, comments-explain-why.
|
|
52
52
|
- [ ] Tests planned: which test(s) cover this change. New behaviour without a test is **incomplete** unless the user has explicitly opted out for this change.
|
|
53
|
-
- [ ] **Testing discipline:** the test does not stub/spy methods on the SUT itself (collaborators are fine), and assertions are on outcomes (return values, state, events, boundary calls) — not on which internal helper was called.
|
|
53
|
+
- [ ] **Testing discipline:** the test does not stub/spy methods on the SUT itself (collaborators are fine), and assertions are on outcomes (return values, state, events, boundary calls) — not on which internal helper was called. Each branch this change adds (`catch`, guard, early return, `else`) has a test that fails when the branch body is deleted; assertions land on the last write to a record, not an intermediate one; each test title names its unit and the single condition it isolates.
|
|
54
|
+
- [ ] **Third-party wrapper (when this change wraps a library call):** the wrapper adds behaviour the library does not already provide — read the installed library source before keeping a recovery branch — no comment names a condition the call site does not establish, and a rethrow keeps the original error as `cause`.
|
|
54
55
|
- [ ] **Hexagonal overlay (if loaded):** no business logic inside any port body, adapter methods are I/O only (no post-fetch JS filtering on domain state, no `findValid*`/`findActive*` adapter names hiding rules), all domain objects declared under `domain/`.
|
|
55
56
|
- [ ] Existing code searched: `grep` for the symbol / file / identifier you are about to add. Do not duplicate.
|
|
56
57
|
- [ ] Project conventions checked: `.editorconfig`, `CONTRIBUTING.md`, formatter config (`.prettierrc`, `rustfmt.toml`, `ktlint`, `google-java-format`, etc.). **Project rules override this resource pack on conflict.**
|
|
@@ -129,7 +129,7 @@ The table below documents those prep-time seed values **for reference only** —
|
|
|
129
129
|
| Report writer worker | sonnet | report-writer-worker | `agents/workers/report-writer-worker.md` |
|
|
130
130
|
| Claude worker | opus | claude-worker | `agents/workers/claude-worker.md` |
|
|
131
131
|
| Codex worker | gpt-5.6-sol | codex-worker | generated from `agents/workers/_cli-wrapper-template.md` + `codex-worker.params.json` |
|
|
132
|
-
| Antigravity worker |
|
|
132
|
+
| Antigravity worker | gemini-3.1-pro | antigravity-worker | generated from `agents/workers/_cli-wrapper-template.md` + `antigravity-worker.params.json` |
|
|
133
133
|
|
|
134
134
|
All three analysis workers use dedicated agent definitions; Codex/Antigravity wrappers handle external CLI invocation internally; Claude worker runs as an in-process subagent with explicitly registered MCP tools so it does not fall back to `claude --mcp-cli` Bash invocations.
|
|
135
135
|
|
|
@@ -27,16 +27,22 @@ Do not scan holistically and stop when it "looks fine". Work the matrix exhausti
|
|
|
27
27
|
|
|
28
28
|
1. **List every changed file:** `git diff --name-only <stage-base>..HEAD`. That list is your worklist — cover it to the end; no sampling.
|
|
29
29
|
2. **Classify each file and select its rule set** from the conventions the preflight already loaded (do NOT re-derive the rules here — read them from the routed pack):
|
|
30
|
-
- any source file → `clean-code.md`: truthful + standalone names, plain-English summary test, single-purpose ≤50-line functions, DRY (incl. scattered domain literals + documented forks), no magic numbers, shallow nesting, comments explain why.
|
|
31
|
-
-
|
|
32
|
-
-
|
|
30
|
+
- any source file → `clean-code.md`: truthful + standalone names, one identifier one meaning per file (a key parameter must not take the name a sibling signature gives the entity), plain-English summary test, single-purpose ≤50-line functions, DRY (incl. scattered domain literals + documented forks), no magic numbers, shallow nesting, comments explain why.
|
|
31
|
+
- a file that wraps a third-party / library call → `clean-code.md` "Wrapping a third-party call": the wrapper adds behaviour the library does not already provide (read the installed library source before keeping a recovery branch — a `catch` repeating the library's own retry recovers nothing), no comment names a condition the call site does not establish, and a rethrow keeps the original error as `cause`.
|
|
32
|
+
- a file that decides, mutates, or persists state → `clean-code.md` "Mutation and state boundaries": decide on the direct identifier rather than a status/flag proxy, capture before-state in one snapshot ahead of the mutating boundary, update only this work's owned fields on an existing row, re-read state before calling a zero-affected-rows write success or failure, put priority-between-inputs in a named domain function, and keep error messages to what was actually observed. Also check that no state union/enum was re-declared beside an authoritative one the domain or a dependency exports.
|
|
33
|
+
- test file (`*.spec.*` / `*.test.*` / `test_*.py` / `*_test.go` …) → `clean-code.md` "Testing discipline": no self-mocking of the SUT, behavioral (outcome) assertions not interaction-only, no tautological delegation assertion, no effect claimed under its own mock, shared-fixture defaults left on the ordinary path, setup values that actually separate the scenarios, every new test helper/mock used by a test in this same diff, no positional mock-argument access (`rg 'mock\.calls'`), every branch this diff adds covered by a test that fails when the branch body is deleted, assertions on the last write to a record rather than an intermediate one, and test titles naming their unit plus the single condition each case isolates.
|
|
34
|
+
- port / adapter / domain file, when the hexagonal overlay is loaded → `architectures/hexagonal.md`: no business logic in a port body, adapter methods are I/O only (no post-fetch filtering on domain state, no `findValid*`/`findActive*` names hiding a rule), domain objects declared under the domain boundary, no changed domain file importing an ORM / framework / adapter / service (read the import list — mechanical), and a service dependency you add or modify goes through a port rather than a concrete adapter (advisory: record it with the port sketch; the codebase already injecting concrete classes is the debt this pays down, not a reason to skip it).
|
|
33
35
|
A file can hold several roles — apply every rule set that fits it.
|
|
34
36
|
3. **Decide clean-or-finding for each cell.** Read the full file when a rule needs context (never judge a port/adapter/domain or a naming rule from the hunk alone).
|
|
35
37
|
4. **Fix each finding in place** before the commit. When a readability finding is real, the fix is a named helper or named intermediate value — sketch the cleaner shape (a few lines) in your audit note, then apply it. When the fix is genuinely out of this stage's scope, record it as an `Out-of-plan` note instead of silently leaving it.
|
|
36
38
|
|
|
37
39
|
## Coverage footer (BLOCKING deliverable)
|
|
38
40
|
|
|
39
|
-
End your audit-sidecar entry for this sweep with a one-line `Coverage:` footer naming each file you examined
|
|
41
|
+
End your audit-sidecar entry for this sweep with a one-line `Coverage:` footer naming each file you examined, the rule set(s) you applied to it, **and the outcome of each** — `clean` when you looked and found nothing, or `fixed <n>` when you fixed findings in place:
|
|
42
|
+
|
|
43
|
+
`Coverage: users.service.ts [names: clean, plain-english: fixed 1], users.repository.adapter.ts [hexagonal: fixed 1, names: clean], users.service.spec.ts [testing-discipline: clean]`
|
|
44
|
+
|
|
45
|
+
`clean` is a result you assert, not a blank you leave. Naming the rule set without its outcome reads identically whether you checked it or skipped it, which is exactly what this footer exists to distinguish — the completion self-check and any reviewer read it to confirm nothing in the diff was skipped. A sweep with no footer, or a footer whose entries carry no outcome, is an unfinished sweep.
|
|
40
46
|
|
|
41
47
|
## Graceful degradation
|
|
42
48
|
|
|
@@ -15,12 +15,13 @@ changed files (`git diff --name-only <stage-base>..HEAD`) and, for the per-file
|
|
|
15
15
|
items, write the confirming evidence per file, not one global "✓". If any item
|
|
16
16
|
fails, fix it or surface the violation — do not claim done on a failing item.
|
|
17
17
|
|
|
18
|
-
- diff-review done: the `Pre-commit diff review sweep` ran over the full diff and its `Coverage:` footer is in your audit sidecar
|
|
18
|
+
- diff-review done: the `Pre-commit diff review sweep` ran over the full diff and its `Coverage:` footer is in your audit sidecar — every changed file named, with each applied rule set carrying its outcome (`clean` or `fixed <n>`). No footer, or entries with no outcome → the sweep is unfinished; do not claim done.
|
|
19
19
|
- functions: every new/edited function ≤50 effective lines, single purpose — cite the longest function's actual line count, not "all under 50"
|
|
20
20
|
- conventions: applied the routed pack + project patterns (name which ones)
|
|
21
21
|
- names & comments: names say what, comments say why; no obvious-restatement comments; no truthful-name violations
|
|
22
22
|
- verification: ran the actual build/test — paste the exact command line and its real exit code / output tail, never a paraphrased "tests pass"
|
|
23
23
|
- acceptance: each implemented plan step's `Acceptance:` condition is observably met — cite the RED→GREEN test (or the command output) proving it per step, not one global "acceptance met"; and each `Test case (success|boundary|failure)` the plan declared for this stage has a matching test in the diff (name the test), so declared edge/failure cases are not silently uncovered
|
|
24
|
+
- mutation check: enumerate every branch this diff adds (`catch`, guard, early return, `else`), plus at least one test it added or changed. For each entry, break what it covers on purpose — delete the branch body, return the wrong value, skip the write — confirm a test FAILS, then restore; cite the branch (or test) and the observed failure per entry, not one global "mutation verified". A branch whose deletion leaves the suite green is untested, not covered — an untested recovery path is indistinguishable from one that cannot fire. "Verified" claimed without this check plus the real command output above is not a verified claim
|
|
24
25
|
- cleanup: no dead or commented-out code left behind
|
|
25
26
|
|
|
26
27
|
Close with a `Self-check coverage:` line naming the files you verified the
|
|
@@ -12,7 +12,7 @@ at Phase 5, BEFORE constructing the verifier worker dispatch prompts.
|
|
|
12
12
|
- **Verifier dispatch labelling.** The core functional role label is `<provider>-verifier` (here, and identically in `final-verification`). Provider, role, and model identity are owned by `prompts/lead/okstra-lead-contract.md` "Model assignments"; the selected runtime adapter owns provider-native dispatch-label mapping (including any `name` / `**Pane role:**` fields) and token-attribution wiring under its "Semantic operation mapping".
|
|
13
13
|
- The verifier slots are `Claude verifier` and `Codex verifier`, plus `Antigravity verifier` **only when `antigravity` is in the resolved `--workers` roster**. Every verifier in the resolved roster is dispatched regardless of which provider holds the executor role; the executor's own provider is run *separately* as a verifier (a fresh CLI session with no shared context) so that no verdict is produced from the same session that wrote the diff. Verifiers MUST NOT call Edit, Write, or any Bash command that mutates files outside the run's artifact directories. If a verifier wants a fix, it records the recommendation in its worker result; it does not apply the fix itself.
|
|
14
14
|
- Session isolation — not model-variant divergence — is the primary self-review safeguard: each verifier is a separate CLI invocation with its own context window, so reusing the same model variant for executor and same-provider verifier is acceptable. Different model variants (e.g. executor=opus / Claude verifier=sonnet) remain recommended when available.
|
|
15
|
-
- Phase-specific model defaults override the shared defaults: `Claude verifier`=`opus`, `Codex verifier`=`gpt-5.6-sol`, `Antigravity verifier`=`
|
|
15
|
+
- Phase-specific model defaults override the shared defaults: `Claude verifier`=`opus`, `Codex verifier`=`gpt-5.6-sol`, `Antigravity verifier`=`gemini-3.1-pro` (only when present in the roster). The `Executor`'s model is taken from the provider-specific worker model corresponding to `--executor`: claude→`--claude-model` (default `opus`), codex→`--codex-model` (default `gpt-5.6-sol`), antigravity→`--antigravity-model` (default `gemini-3.1-pro`).
|
|
16
16
|
- Verifiers read from the SAME working tree path the Executor used so they observe the exact diff the Executor produced. Verifiers remain strictly read-only there.
|
|
17
17
|
|
|
18
18
|
## Verifier QA duties (independent re-run mandate)
|
|
@@ -117,10 +117,19 @@ Re-running commands proves the diff *builds and passes*; it does NOT prove the d
|
|
|
117
117
|
- **Interaction-only assertion:** a test whose only/primary assertion is `toHaveBeenCalled*` / `toHaveBeenCalledTimes` on an internal helper or a non-side-effecting collaborator, with no assertion on the returned value / resulting state / persisted row / emitted event.
|
|
118
118
|
- **Tautological delegation assertion:** a test asserts the SUT result equals a direct call to the same pure helper/collaborator that the SUT delegates to, instead of asserting an independent literal value or observable state.
|
|
119
119
|
- **Untruthful name:** a read-named function (`get*` / `find*` / `load*`) that writes/inserts/mutates; an adapter or repository name encoding the caller's use-case (`*ForInit`) or hiding a domain rule (`findValid*` / `findActive*`).
|
|
120
|
-
- **Hexagonal (only when the overlay is loaded):** business logic inside a port body; an adapter method that is not pure I/O (post-fetch JS filtering on domain state, domain-rule evaluation); a domain object declared outside the `domain/` boundary.
|
|
120
|
+
- **Hexagonal (only when the overlay is loaded):** business logic inside a port body; an adapter method that is not pure I/O (post-fetch JS filtering on domain state, domain-rule evaluation); a domain object declared outside the `domain/` boundary; a changed file under the domain folder importing an ORM / DB layer, a framework or its DI decorators, or anything under adapters / infrastructure / services (read the import list — this verdict is mechanical, not a judgment).
|
|
121
121
|
- **gitignored file committed to the branch:** any path in the `git diff --name-only <base>...HEAD` enumeration that `.gitignore` excludes — enumerate them by piping that list through `git check-ignore --stdin --no-index`. A committed ignored file means the executor bulk-added (`git add .`/`-A`) or force-staged (`git add -f`) it, leaking build output, scratch files, or verification artifacts into the eventual PR. This explicitly includes `.okstra/` paths (and `.project-docs/` when the legacy symlink is present): `.okstra/**` is gitignored, so a committed okstra file (qa scripts, conformance results) is always this defect. Cite each path; recommend `git rm --cached <path>` to untrack it while keeping the file on disk. Conformance/qa evidence belongs in the carry sidecar / verifier result, never in git history.
|
|
122
122
|
- **Real-IO test in source tree:** a changed/added test under the project source test tree — `src/**`, `test/**`, `tests/**`, `**/__test__/**`, `**/__tests__/**`, `*.spec.*`, `*.test.*` — that opens a **real** DB connection / DSN, makes a real `fetch` / `axios` / `http` request, or otherwise hits real external IO without mocking the injected collaborator (a live handle, not a stub/spy). Real-IO tests MUST live under `<task_root>/qa/scripts/` per the executor's *Real-IO test isolation* rule — a live-IO test in source silently breaks the project's CI suite and violates the artifact-home rule. Cite the test file + the real-IO line; recommend moving it to `<task_root>/qa/scripts/` (or declaring it as a Tier 3 conformance script). Mock-only unit tests in source are NOT a hit.
|
|
123
|
-
- **
|
|
123
|
+
- **Proxy-based identity decision:** a move / ownership / re-parenting decision taken from a status field or flag while the source and destination identifiers were available and never compared. Cite the condition and the identifiers it should have compared, and show the opposite case the condition also reads true for.
|
|
124
|
+
- **Before-state read after a mutating boundary:** a value recorded as the "before" state (audit column, diagnostic field) that is read off the original object *after* a strategy call, repository write, reload, or intervening `await`, or reconstructed from the post-state. Cite the boundary and the field. Before-values feeding one diagnostic must come from a single pre-boundary snapshot.
|
|
125
|
+
- **Existing-row update outside its owned fields:** a reuse path that writes provenance, ownership, or externally-managed columns with no explicit requirement authorising it, or that loses the create-vs-reuse distinction before the write. The update field set must be an allowlist.
|
|
126
|
+
- **Zero affected rows treated as a verdict:** a conditional write whose zero-row result is reported as success, or as one undifferentiated failure, without re-reading state to separate already-done / conflicting / deleted.
|
|
127
|
+
- **Authoritative type re-declared:** a hand-maintained string union or state-literal list duplicating an enum / type the domain or a dependency already exports. Grep the state literals and the new type name; cite the existing declaration.
|
|
128
|
+
- **Shared fixture default flipped for an exception:** a common fixture's default inverted to exercise one exceptional path, silently changing the meaning of every existing no-argument caller. The exceptional condition belongs in the test that needs it.
|
|
129
|
+
- **Positional mock-argument access:** `mock.calls[<n>][<m>]` (or the framework equivalent) used to inspect arguments instead of an intent-revealing `toHaveBeenCalledWith` / explicit-absence assertion. Detect with `rg 'mock\.calls'` over the changed test files.
|
|
130
|
+
- **Non-separating test data:** two scenarios whose setup values and assertions are identical, so a wrong implementation passes both; or new test tooling added in this diff (mock, state setter, repository branch) that no test calls.
|
|
131
|
+
- **Effect claimed under its own mock:** a test presented as covering an effect whose producing path is replaced by a mock inside that same test. The mock's presence in the harness is not evidence the branch behind it works.
|
|
132
|
+
- **Advisory findings (recorded as recommendations; verdict MAY still PASS):** function >50 effective lines, a single body mixing read+write stages, weak readability, a missing-but-non-critical outcome assertion, newly orphaned private/public code that is safe to remove but not on a critical path, weak-but-not-misleading names, priority-between-inputs policy inlined in a service condition instead of a named domain function, an error message asserting a cause the code never observed, a memory / concurrency / batching change with no test pinning the bound it claims, or (hexagonal overlay only) a service dependency this diff adds or modifies that injects a concrete adapter instead of a port — record it with the port sketch; an existing convention of concrete injections does not convert this one to `clean`, it is the debt the rule pays down. These land in the verifier result as `should-fix` / `nit` recommendations, not as a `FAIL`.
|
|
124
133
|
- **Output.** Every finding — blocking or advisory — is a structured item in the verifier's worker result (`path:line`, rule, severity, suggested fix) so it carries into Phase 5.5 convergence and the final report. A blocking hit sets the verifier verdict to `FAIL` with the rule cited, using the same verdict machinery as the Discrepancy rule above. `Claude lead` MUST NOT silently downgrade a cited blocking finding to advisory during synthesis; an override requires a concrete cited reason, exactly as for the Discrepancy rule.
|
|
125
134
|
|
|
126
135
|
### Fix-run incremental scope (applies when the profile carries a "Fix-Run Carry" block)
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
- `Test case (boundary): <edge input → expected, the command/test name>` — an edge/boundary input (empty, min/max, missing, first/last, off-by-one). When the slice genuinely has no boundary, write `N/A — <reason>` so the value is non-empty and the omission is a conscious, reviewable decision — never leave it blank.
|
|
105
105
|
- `Test case (failure): <invalid input → error/rejection, the command/test name>` — a negative/failure path (invalid input, unauthorized, error propagation).
|
|
106
106
|
Validator S10d rejects a missing/empty line in any of the three categories (skipped only when a `TDD exemption:` line is present). The `RED:` step below must encode these cases, not a single assertion.
|
|
107
|
+
**Pick the cases by distinct outcome, not by line coverage.** When the stage writes or reconciles state, its meaningfully different outcomes are usually more than three — normal success, target already in the desired state (resume), existing data reused rather than created, a conflicting concurrent state, target absent, and mid-way failure with rollback. Enumerate the ones this stage actually implements and route them across the three lines (the `boundary` line is where resume / already-done / reuse belongs; `failure` carries conflict, absence, and rollback), naming each in the cell rather than collapsing them into "edge input". An implemented outcome with no declared case is a coverage gap the executor will not backfill.
|
|
107
108
|
- **Per-stage subsections** (`## 5.5.<i> Stage <i>: <title>` for each `i`), each containing the four required subsections:
|
|
108
109
|
- `### Carry-In` — for `depends-on (none)`: task-brief only. Otherwise: each depended-on stage's static exit contract + runtime sidecar path `runs/<impl-key>/carry/stage-<i>.json` placeholder.
|
|
109
110
|
- `### Stepwise Execution Order` — bite-sized table with `step | action | files | command | expected`. The `files` cell lists each touched path in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...`), which does not resolve and is rejected by plan-body verification as a kind-b path mismatch. **Effective row count ≤ 8** (excluding header / divider / blank). Each step is one cohesive, self-contained change (no lower time bound; it may span several files that change together); for code steps include actual code or diff sketch. **TDD ordering is MUST, not a preference:** the **first** effective step's `action` cell MUST start with the literal `RED:` and describe the failing test(s) that capture this stage's `Acceptance` **and the three declared `Test case (success|boundary|failure)` lines** (`expected` = FAIL) — the RED step encodes the case set, not a single happy-path assertion; at least one later `action` cell MUST start with the literal `GREEN:` and describe the minimal implementation that makes it pass (`expected` = PASS); an optional refactor step starts with `REFACTOR:`. **Exemption:** doc-only / config-only / pure-rename stages with no observable runtime behaviour may omit RED/GREEN by declaring one line `TDD exemption: <reason>` in the stage section (mirrors the executor's per-step exemption in `_implementation-executor.md`). Validator S10c enforces RED-first + GREEN **and** that the `RED:` step's `expected` reads FAIL / the `GREEN:` step's reads PASS; S10e rejects a `TDD exemption:` whose reason is not one of doc-only / config-only / pure-rename (both in `validators/validate-implementation-plan-stages.py`).
|