okstra 0.167.0 → 0.169.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 +6 -5
- package/docs/architecture/storage-model.md +57 -1
- package/docs/architecture.md +70 -2
- package/docs/cli.md +8 -4
- package/docs/for-ai/skills/okstra-code-review.md +3 -2
- package/docs/for-ai/skills/okstra-schedule-gen.md +3 -1
- package/docs/pr-template-usage.md +10 -6
- package/docs/project-structure-overview.md +14 -11
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -5
- package/runtime/agents/workers/report-writer-worker.md +9 -4
- package/runtime/agents/workers/translator-worker.md +6 -4
- package/runtime/prompts/coding-preflight/architectures/hexagonal.md +27 -1
- package/runtime/prompts/coding-preflight/clean-code.md +13 -0
- package/runtime/prompts/duties/acceptance-critic.md +24 -0
- package/runtime/prompts/duties/acceptance-verifier.md +24 -0
- package/runtime/prompts/duties/analysis-worker.md +24 -0
- package/runtime/prompts/duties/code-reviewer.md +24 -0
- package/runtime/prompts/duties/common.md +35 -0
- package/runtime/prompts/duties/implementation-executor.md +24 -0
- package/runtime/prompts/duties/implementation-verifier.md +24 -0
- package/runtime/prompts/duties/lead.md +24 -0
- package/runtime/prompts/duties/report-writer.md +24 -0
- package/runtime/prompts/duties/reverification-worker.md +24 -0
- package/runtime/prompts/duties/schedule-verifier.md +24 -0
- package/runtime/prompts/duties/scope-critic.md +24 -0
- package/runtime/prompts/duties/translator.md +24 -0
- package/runtime/prompts/lead/convergence.md +51 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +10 -20
- package/runtime/prompts/lead/plan-body-verification.md +16 -1
- package/runtime/prompts/lead/report-writer.md +20 -5
- package/runtime/prompts/lead/team-contract.md +13 -13
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +3 -1
- package/runtime/prompts/profiles/_implementation-executor.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -1
- package/runtime/prompts/profiles/implementation.md +4 -2
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +8 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +33 -0
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +13 -12
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +2 -0
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -3
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/adapter.py +6 -0
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +2 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +1502 -0
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +788 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -107
- package/runtime/python/okstra_ctl/context_cost.py +46 -5
- package/runtime/python/okstra_ctl/dispatch_core.py +312 -37
- package/runtime/python/okstra_ctl/dispatch_state.py +461 -36
- package/runtime/python/okstra_ctl/doctor.py +150 -16
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +87 -9
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +214 -23
- package/runtime/python/okstra_ctl/path_hints.py +26 -0
- package/runtime/python/okstra_ctl/paths.py +20 -0
- package/runtime/python/okstra_ctl/ports/__init__.py +8 -0
- package/runtime/python/okstra_ctl/ports/host.py +3 -0
- package/runtime/python/okstra_ctl/ports/host_model.py +60 -0
- package/runtime/python/okstra_ctl/pr_template.py +3 -6
- package/runtime/python/okstra_ctl/registry/host_registry.py +5 -0
- package/runtime/python/okstra_ctl/render.py +217 -12
- package/runtime/python/okstra_ctl/report_finalize.py +44 -0
- package/runtime/python/okstra_ctl/run.py +368 -51
- package/runtime/python/okstra_ctl/session.py +16 -12
- package/runtime/python/okstra_ctl/team.py +11 -11
- package/runtime/python/okstra_ctl/worker_dispatch.py +104 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +5 -38
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +32 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +38 -1
- package/runtime/skills/okstra-code-review/SKILL.md +22 -3
- package/runtime/skills/okstra-run/SKILL.md +16 -1
- package/runtime/skills/okstra-schedule-gen/SKILL.md +15 -1
- package/runtime/templates/implementation-worker-preamble.md +0 -10
- package/runtime/templates/report-writer-prompt-preamble.md +0 -9
- package/runtime/templates/reports/settings.template.json +0 -11
- package/runtime/templates/worker-prompt-preamble.md +0 -10
- package/runtime/validators/lib/fixtures.sh +93 -0
- package/runtime/validators/lib/validate-assets.sh +0 -8
- package/runtime/validators/validate-run.py +182 -0
- package/src/cli-registry.mjs +14 -0
- package/src/commands/execute/agent-prompt.mjs +25 -0
- package/src/commands/execute/codex-dispatch.mjs +6 -63
- package/src/commands/execute/worker-dispatch.mjs +76 -0
- package/src/commands/lifecycle/doctor.mjs +18 -3
- package/src/commands/lifecycle/install.mjs +33 -15
- package/src/commands/lifecycle/uninstall.mjs +4 -3
- package/src/lib/install-assets.mjs +9 -0
- package/runtime/agents/workers/antigravity-worker.md +0 -259
- package/runtime/agents/workers/codex-worker.md +0 -259
- package/runtime/agents/workers/grok-worker.md +0 -259
- package/runtime/agents/workers/kimi-worker.md +0 -259
- package/runtime/prompts/coding-preflight/scripts/preedit-check.sh +0 -79
- package/runtime/templates/operating-standard.md +0 -22
- package/src/lib/worker-agent-render.mjs +0 -50
- /package/runtime/templates/{prd → pr}/pr-body.template.md +0 -0
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ okstra/ npm package = repo root
|
|
|
61
61
|
├── runtime/ gitignored install payload copied to ~/.okstra
|
|
62
62
|
├── scripts/ Python + Bash runtime sources
|
|
63
63
|
├── skills/ public skill Markdown sources (8 user-facing skills)
|
|
64
|
-
├── agents/
|
|
64
|
+
├── agents/ Claude host execution-adapter definitions
|
|
65
65
|
├── prompts/, schemas/, templates/, validators/
|
|
66
66
|
├── docs/ English manuals (architecture, CLI, storage, container, performance)
|
|
67
67
|
├── tests/, tests-e2e/
|
|
@@ -98,8 +98,8 @@ During `prepack`, `tools/build.mjs` rebuilds `runtime/` from `scripts/`, `skills
|
|
|
98
98
|
└── okstra-*/SKILL.md 8 user-facing skills only (setup/brief/run/memory/inspect/schedule/container/manager)
|
|
99
99
|
|
|
100
100
|
~/.claude/agents/ automatically discovered by Claude Code (when `~/.claude` exists)
|
|
101
|
-
└── {claude,
|
|
102
|
-
(
|
|
101
|
+
└── {claude,report-writer,translator}-worker.md native Claude execution adapters
|
|
102
|
+
(provider CLI workers use deterministic dispatch)
|
|
103
103
|
|
|
104
104
|
<project-root>/.okstra/
|
|
105
105
|
├── project.json {projectId, projectRoot, ...} (written by `/okstra-setup`)
|
|
@@ -225,9 +225,10 @@ Major workflow changes added to `main` after 0.8.0:
|
|
|
225
225
|
|
|
226
226
|
- **Automatic isolated worktrees for every task type** — During preparation, `okstra-ctl` runs `git worktree add ~/.okstra/worktrees/<project-id>/<task-group-segment>/<task-id-segment>` once per task key to create an isolated working tree and a `<work-category-namespace>/<task-id-segment>` branch (for example, `feature/dev-9436` or `fix/dev-7311`). The user chooses the base ref with `--base-ref`, using the same choices as the release-handoff PR base picker: `main`, `dev`, `staging`, `preprod`, `prod`, or a custom value. It is required in the first phase; the okstra-run skill collects it through `AskUserQuestion`, while non-interactive callers must pass `--base-ref` explicitly. Later **non-`implementation`** phases for the same task key (`requirements-discovery` → `error-analysis` → `implementation-planning` → `final-verification` → `release-handoff`) reuse the same path and branch. `implementation` runs are **stage-isolated**: each run executes one stage in its own `.../<task>/stage-<N>/` worktree on a `<work-category-namespace>/<task>-s<N>` branch, so independent stages with `depends-on (none)` can run concurrently without sharing a tree. The registry reserves both task keys and **stage keys** with flock. Provisioning is skipped when the caller is already in another worktree or project_root is not a Git repository; stage isolation degrades to a flat path in those cases. Manual cleanup: `git worktree remove <path>` → `git branch -D <branch>` plus release/removal of the registry entry. Details: [`docs/architecture.md`](docs/architecture.md), in the *Task type* section, and [`docs/cli.md#--executor`](docs/cli.md#--executor).
|
|
227
227
|
- **`release-handoff` lifecycle phase** — runs immediately after `final-verification` returns `verdict=accepted`. The current Okstra lead drafts the candidate messages and PR body inline, then uses the selected host adapter's user-prompt operation for the delivery choices. Only the Git/GitHub CLI commands selected through those menus are run. Force pushes, direct pushes to the base branch, hook bypasses (`--no-verify`), and release publication (`gh release`, `npm publish`, and similar commands) are prohibited. This phase does not edit source code. Profile: [`prompts/profiles/release-handoff.md`](prompts/profiles/release-handoff.md).
|
|
228
|
-
- **Configurable PR body template** (release-handoff) — PR bodies are populated from a Markdown template selected in this order: one-time override (`--pr-template-path` or the okstra-run Step 6 prompt) → `prTemplatePath` in `<project_root>/.okstra/project.json` → `prTemplatePath` in `~/.okstra/config.json` → the
|
|
228
|
+
- **Configurable PR body template** (release-handoff) — PR bodies are populated from a Markdown template selected in this order: one-time override (`--pr-template-path` or the okstra-run Step 6 prompt) → `prTemplatePath` in `<project_root>/.okstra/project.json` → `prTemplatePath` in `~/.okstra/config.json` → the installed default at `~/.okstra/templates/pr/pr-body.template.md`. Register a template with `okstra config set pr-template-path <path> [--scope project|global]`; project scope accepts a path relative to the project root, while global scope requires an absolute path or a path beginning with `~/`. `okstra config get pr-template-path --scope all` prints every scoped value and the effective winner. The default template contains `## Summary`, `## Changes`, `## Test plan`, and `## Linked issues`, plus HTML comment guidance that the lead removes immediately before PR creation.
|
|
229
229
|
- **Profile worker-roster validation** — `--workers <csv>` and the okstra-run Step 6 worker prompt accept only the worker IDs declared in the selected profile's `Required workers:` block. Requesting a worker absent from the profile—for example, `codex` or `antigravity` for `release-handoff`—fails with a clear error, and the interactive prompt shows only workers accepted by that profile.
|
|
230
|
-
- **Host-aware lead adapters** — `okstra-run` resolves the current harness through the same dynamic host registry used by the terminal front door. Claude Code, Codex, Antigravity, Grok, and Kimi keep their matching provider assignment native; `external` remains the explicit all-CLI host. Host and provider are separate axes, and every non-native worker assignment
|
|
230
|
+
- **Host-aware lead adapters** — `okstra-run` resolves the current harness through the same dynamic host registry used by the terminal front door. Claude Code, Codex, Antigravity, Grok, and Kimi keep their matching provider assignment native; `external` remains the explicit all-CLI host. Host and provider are separate axes, and every non-native worker assignment runs through the deterministic `okstra worker-dispatch` process boundary. `leadAssignment` and every `workerAssignments[]` row record provider, model, and `runner`. `okstra codex-dispatch` remains a compatibility alias for the provider-neutral dispatcher.
|
|
231
|
+
- **Per-invocation duty contracts** — every Okstra-owned LLM call composes three independently managed inputs: a persisted model assignment, a functional duty contract from `prompts/duties/`, and call-specific task instructions. The final prompt and adjacent metadata carry exactly five SHA-256 digests (catalog, assignment, duty, instruction, prompt). Code-owned process launches fail before execution when verification fails; host-native calls record a verified-specification link without claiming that Okstra observed the bytes delivered by the host. Standalone code-review and schedule-verification calls use the same contract under `.okstra/agent-invocations/`.
|
|
231
232
|
- **Multi-stage `implementation-planning` / `implementation`** — `implementation-planning` always produces a Stage Map and N stage sections. Each stage has no more than six steps, and stages with `depends-on (none)` can be implemented concurrently in separate `implementation` runs. Each `implementation` invocation runs a single stage, selected with `--stage <auto|N>`, and creates an evidence sidecar at `carry/stage-<N>.json` for automatic carry-in to the next stage. The `implementation-planning` run directory accumulates `consumers.jsonl` reverse links that record which run consumed each stage.
|
|
232
233
|
- **AI-prepared design preparation (implementation-planning → implementation)** — `implementation-planning` detects which stages need design input (domain contract, DB/table schema, external interface, transaction/consistency, transformation mapping, lifecycle, rollout/observability, manual user test) and has the AI draft a concrete proposal first, instead of handing the user an empty design document. Each item is assessed as `ready`, `provisional`, `blocked`, or `not-applicable`; a simple task may declare `no-design-inputs`. Phase 7 materializes an Okstra-owned request under `design-prep-requests/`, and `okstra design-prep <list|show|write>` or the okstra-run wizard records the confirmed answer as an **append-only** revision under `design-prep-inputs/`—neither path ever edits the approved planning snapshot. Before creating its worktree, `implementation` resolves only the items its selected stage cites in `stageRefs`: safe `provisional` assumptions are injected into the executor prompt so work proceeds, while an unsafe open decision makes only that stage wait or replan. A markerless legacy plan continues with a `legacy-unassessed` warning. Storage authorities: [`docs/architecture/storage-model.md`](docs/architecture/storage-model.md). CLI: [`docs/cli.md#okstra-design-prep`](docs/cli.md#okstra-design-prep).
|
|
233
234
|
- **Phase 6 plan-body verification (implementation-planning only)** — Immediately after the report-writer worker drafts the final report and before the user approval gate, the lead performs one post-verification round. It extracts `P-Opt-*`, `P-Step-*`, `P-Dep-*`, `P-Val-*`, and `P-Rb-*` plan items from the synthesized `## 5.5` implementation plan deliverables and asks every analyzer worker for an `AGREE`, `DISAGREE(a-e)`, or `SUPPLEMENT` verdict. The aggregate result is `passed`, `passed-with-dissent`, `blocked-by-disagreement`, or `aborted-non-result`. The frontmatter `approved` field is always published as `false`; a blocking result keeps it false and becomes a row in `## 1. Clarification Items`. For fast iteration, opt out with `--no-plan-verification`. Contract details: the "Plan-body verification mode" section of [`prompts/lead/convergence.md`](prompts/lead/convergence.md) and [`docs/cli.md#--no-plan-verification`](docs/cli.md#--no-plan-verification).
|
|
@@ -24,7 +24,8 @@ The task manifest, task index, instruction set, runs, and history are collected
|
|
|
24
24
|
- `runs/<task-type>/`
|
|
25
25
|
- `manifests/run-manifest-<task-type>-<seq>.json`
|
|
26
26
|
- `state/team-state-<task-type>-<seq>.json`
|
|
27
|
-
- `prompts/lead-execution-prompt-<task-type>-<seq>.md` and selected `<worker>-worker-prompt-<task-type>-<seq>.md` files
|
|
27
|
+
- `prompts/lead-execution-prompt-<task-type>-<seq>.md` and selected `<worker>-worker-prompt-<task-type>-<seq>.md` files, each with adjacent `.meta.json` invocation metadata
|
|
28
|
+
- `prompts/duty-contracts-<task-type>-<seq>/`, the immutable run-scoped duty catalog snapshot
|
|
28
29
|
- `reports/final-report-<task-type>-<seq>.{data.json,md,html}`
|
|
29
30
|
- implementation-planning-only `design-prep-requests/` and `design-prep-inputs/`
|
|
30
31
|
- `status/final-<task-type>-<seq>.status`
|
|
@@ -53,6 +54,8 @@ The representative files below are all relative to the resolved run directory (`
|
|
|
53
54
|
- `state/team-state-<task-type>-<seq>.json`
|
|
54
55
|
- `prompts/lead-execution-prompt-<task-type>-<seq>.md` *(canonical; the Claude-named snapshot remains a compatibility alias)*
|
|
55
56
|
- `prompts/<worker>-worker-prompt-<task-type>-<seq>.md`
|
|
57
|
+
- `prompts/<prompt>.md.meta.json` *(invocation identity, assignment, duty identity, source paths, and the five catalog/assignment/duty/instruction/prompt digests)*
|
|
58
|
+
- `prompts/duty-contracts-<task-type>-<seq>/` *(run-scoped snapshot; dispatch verification rejects drift from its catalog digest)*
|
|
56
59
|
|
|
57
60
|
After the host-native lead takes over, the lead and its assigned workers add the following result files to the current run.
|
|
58
61
|
- `sessions/claude-resume-<task-type>-<seq>.sh`
|
|
@@ -82,6 +85,59 @@ For the standalone Claude launcher, `sessions/claude-resume-<task-type>-<seq>.sh
|
|
|
82
85
|
The resolved run directory collects execution history. It divides its contents into type-specific subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`, then distinguishes each run-level artifact and result file with a `-<task-type>-<seq>` suffix (a three-digit, zero-padded per-category counter, such as `001` or `002`).
|
|
83
86
|
Worker prompt history is retained not under `/tmp`, but always as a canonical artifact under `prompts/` for the current run.
|
|
84
87
|
|
|
88
|
+
`state/team-state-<task-type>-<seq>.json` stores invocation-aware dispatch
|
|
89
|
+
records. Code-owned worker launches record the invocation fields directly in
|
|
90
|
+
`workerDispatches[]`. Host-native calls use `agentDispatches[]`, and
|
|
91
|
+
`agentResultLinks[]` associates an accepted result path with exactly one
|
|
92
|
+
dispatch ID. The inverse is also exclusive: one dispatch ID cannot authorize
|
|
93
|
+
multiple accepted paths. State mutation uses a run-state lock and unique
|
|
94
|
+
temporary files so parallel dispatch and result-link writers do not lose each
|
|
95
|
+
other's updates. `host-native-spec-link-gate` never means that the host attested the
|
|
96
|
+
delivered prompt bytes; `promptDeliveryVerified` remains false.
|
|
97
|
+
|
|
98
|
+
The complete run manifest is published before any run-backed prompt. Its
|
|
99
|
+
`agentContract`, `invocationAssignments`, run-scoped duty root, catalog digest,
|
|
100
|
+
and invocation reservation root are immutable for the lifetime of the run.
|
|
101
|
+
Every invocation metadata file refers back to that manifest and to one
|
|
102
|
+
`assignmentRef`. A reservation binds `invocationId`, `workerId`, assignment,
|
|
103
|
+
audience, dispatch kind, prompt path, and adjacent metadata path before the
|
|
104
|
+
prompt can be reused. This run-scoped state belongs under `runs/.../prompts/`,
|
|
105
|
+
not the task-wide `instruction-set/`, because consecutive runs and parallel
|
|
106
|
+
implementation stages must not share mutable invocation identity.
|
|
107
|
+
|
|
108
|
+
The manifest's `authorizedPaths` allowlists instruction, prompt, and result
|
|
109
|
+
roots. Materialization resolves each requested path to its real path, rejects a
|
|
110
|
+
path outside the corresponding root, rejects a symbolic-link escape, and
|
|
111
|
+
rejects an undeclared result path before writing. Persisted instruction
|
|
112
|
+
provenance is a non-empty list of `{kind: project|runtime, path: <relative POSIX
|
|
113
|
+
path>}` objects. The path is normalized POSIX text relative to its named
|
|
114
|
+
authority; installed runtime absolute paths are not durable metadata.
|
|
115
|
+
|
|
116
|
+
Prompt publication is recoverable rather than pretending that two independent
|
|
117
|
+
files can be replaced atomically. A create-only prompt is written first and its
|
|
118
|
+
adjacent metadata is written last as the completion marker. Under the
|
|
119
|
+
per-invocation publication lock, a retry recovers a byte-identical orphan
|
|
120
|
+
prompt, but rejects different existing bytes. A reservation lock prevents two
|
|
121
|
+
concurrent writers from mixing prompt and metadata identities.
|
|
122
|
+
|
|
123
|
+
LLM calls outside a task run use
|
|
124
|
+
`<project>/.okstra/agent-invocations/<purpose>/`. Identity is scoped by the
|
|
125
|
+
`(purpose, invocationId)` pair, and both values must be canonical slugs. For an
|
|
126
|
+
invocation ID `<invocation-id>`, the exact immutable paths are
|
|
127
|
+
`<invocation-id>.instructions.md`, `<invocation-id>.prompt.md`, adjacent
|
|
128
|
+
`<invocation-id>.prompt.md.meta.json`, `<invocation-id>.result.json`, and
|
|
129
|
+
`<invocation-id>.prompt.md.completion.json`, plus a private `.tmp/` capture
|
|
130
|
+
area. The result envelope binds purpose, invocation ID, prompt metadata path,
|
|
131
|
+
and `returnedBody`; the completion marker is published last and binds the
|
|
132
|
+
canonical result envelope digest. The same raw returned body may be used by two
|
|
133
|
+
independent invocations because identity comes from the envelope, while a
|
|
134
|
+
cross-invocation envelope link is rejected. Code review uses purpose
|
|
135
|
+
`code-review`; schedule narrative verification uses `schedule-verification`.
|
|
136
|
+
Consumers parse only the `returnedBody` emitted by
|
|
137
|
+
`agent-prompt verify-completion`. A branch review's final report may use the
|
|
138
|
+
documented `.project-docs/code-reviews/<branch>/` exception, but its invocation
|
|
139
|
+
specification, result envelope, and completion record remain under `.okstra/`.
|
|
140
|
+
|
|
85
141
|
The persisted prompt's `**Worker Preamble Path:**` records the selected functional audience contract: analysis uses `templates/worker-prompt-preamble.md`, implementation executor/verifier uses `templates/implementation-worker-preamble.md`, and report writing uses `templates/report-writer-prompt-preamble.md`. Every initial prompt also persists `**Worker Error Contract Path:** templates/worker-error-contract.md`. Active-run and run-context runtime resources expose the same audience map plus the shared error-contract path, so dispatch replay and context-cost accounting do not infer an audience from a provider/model name.
|
|
86
142
|
Unlike before, `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, skill copies, and `final-report-template.md` are not duplicated for every run.
|
|
87
143
|
These materials retain canonical copies in `instruction-set/` under the stable task root.
|
package/docs/architecture.md
CHANGED
|
@@ -615,6 +615,74 @@ Skill flow:
|
|
|
615
615
|
2. It calls `okstra_ctl.run.prepare_task_bundle(render_only=True)` with the user's input—directly invoking the same Python function without passing through `okstra.sh`.
|
|
616
616
|
3. After the same instruction-set artifacts are written to disk, the current host reads the canonical lead prompt and assumes the lead role.
|
|
617
617
|
|
|
618
|
+
### Agent invocation contract
|
|
619
|
+
|
|
620
|
+
An agent identity is functional, not provider-specific. Each LLM invocation is
|
|
621
|
+
composed from three independent inputs: the selected model assignment, a duty
|
|
622
|
+
contract from `prompts/duties/`, and call-specific task instructions. The
|
|
623
|
+
composer publishes the final prompt and adjacent metadata only after the
|
|
624
|
+
complete run manifest and its immutable `agentContract` and
|
|
625
|
+
`invocationAssignments` maps are on disk. Later manifest rewrites reject any
|
|
626
|
+
change to those fields or to the canonical lead-prompt path. This prevents a
|
|
627
|
+
prompt from being verified against state that appeared only after dispatch.
|
|
628
|
+
|
|
629
|
+
`invocationAssignments` is the assignment source of truth for the lead,
|
|
630
|
+
initial workers, critics, translators, reverification workers, and report
|
|
631
|
+
writer. Each `assignmentRef` resolves to exactly six fields: `provider`,
|
|
632
|
+
`model`, `modelExecutionValue`, `runner`, `hostRuntime`, and `hostModelValue`.
|
|
633
|
+
Invocation metadata repeats that resolved value under `modelAssignment`; the
|
|
634
|
+
verifier requires byte-equivalent canonical data rather than resolving the
|
|
635
|
+
model again. Initial and dynamic calls also reserve `invocationId`, `workerId`,
|
|
636
|
+
`assignmentRef`, audience, dispatch kind, prompt path, and metadata path under
|
|
637
|
+
the run's invocation-reservation root. Reusing an invocation ID with a
|
|
638
|
+
different identity fails as a conflict.
|
|
639
|
+
|
|
640
|
+
The adjacent metadata has one digest location, `digests`, containing exactly
|
|
641
|
+
`catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and
|
|
642
|
+
`promptDigest`. Assignment and instruction values use UTF-8 JSON with sorted
|
|
643
|
+
keys, compact separators, and no non-finite numbers before SHA-256 hashing.
|
|
644
|
+
Catalog and duty hashes use a versioned frame of sorted relative file names and
|
|
645
|
+
raw bytes. The prompt hash covers the final UTF-8 bytes. Instruction provenance
|
|
646
|
+
uses only `{kind: project|runtime, path: <relative POSIX path>}` entries under
|
|
647
|
+
`instruction.sourcePaths`; installed runtime absolute paths are never persisted.
|
|
648
|
+
|
|
649
|
+
Prompt publication uses an exclusive per-invocation lock and create-only file
|
|
650
|
+
publication. The prompt is published first and its metadata is the completion
|
|
651
|
+
marker published last. A retry may recover a matching prompt that has no
|
|
652
|
+
metadata; any differing prompt or metadata is an immutable conflict. The
|
|
653
|
+
reservation root has its own lock, so concurrent writers either converge on
|
|
654
|
+
the same specification or one fails without mixing artifacts from two writers.
|
|
655
|
+
|
|
656
|
+
`modelExecutionValue` is the provider-process value. `hostModelValue` is the
|
|
657
|
+
model argument understood by a host-native invocation primitive. They are
|
|
658
|
+
separate because a host may use a family token while the provider CLI requires
|
|
659
|
+
a concrete execution identifier. A code-owned provider launch goes through
|
|
660
|
+
`okstra worker-dispatch`, verifies the invocation immediately before process
|
|
661
|
+
creation, starts all selected analysis assignments before collecting any one
|
|
662
|
+
of them, and records `core-pre-dispatch`. Report writer is a Phase 6 dependency
|
|
663
|
+
and must run in its own explicit dispatch after convergence; the dispatcher
|
|
664
|
+
excludes it from default selection and rejects any mixed analysis/report batch.
|
|
665
|
+
Each completed result path must link to
|
|
666
|
+
exactly one dispatch, and one dispatch cannot authorize multiple accepted
|
|
667
|
+
results. A host-native launch records
|
|
668
|
+
`host-native-spec-link-gate` and links its accepted result through
|
|
669
|
+
`okstra agent-prompt link-result`; this proves association with the verified
|
|
670
|
+
specification, not observation of the prompt bytes delivered by the host.
|
|
671
|
+
Accordingly, host-native records retain `promptDeliveryVerified=false` unless
|
|
672
|
+
a future host supplies a verifiable receipt or transcript. The process-owned
|
|
673
|
+
lead launch performs the same pre-dispatch verification and records its lead
|
|
674
|
+
dispatch before process creation; an in-session lead can only apply the weaker
|
|
675
|
+
specification-link gate because Okstra cannot intercept the host primitive.
|
|
676
|
+
|
|
677
|
+
The same boundary covers the lead, initial and dynamic workers, critics,
|
|
678
|
+
report writing, translation, manager child leads, and the standalone code-review
|
|
679
|
+
and schedule-verification agents. `okstra codex-dispatch` is retained only as a
|
|
680
|
+
compatibility alias. Provider-specific LLM transport agents are not installed.
|
|
681
|
+
The implementation executor manifest has no `workerAgent` field: it records the
|
|
682
|
+
provider, both model values, runner, and dispatch mode, while `doctor` checks
|
|
683
|
+
either the native host adapter or deterministic dispatcher required by that
|
|
684
|
+
actual execution mode.
|
|
685
|
+
|
|
618
686
|
See [`skills/okstra-run/SKILL.md`](../skills/okstra-run/SKILL.md) for the detailed procedure.
|
|
619
687
|
|
|
620
688
|
### 5. Analyze errors if needed
|
|
@@ -870,8 +938,8 @@ Each validator blocks the phase with a `contract-violated` exit code when a cont
|
|
|
870
938
|
- The host adapter creates workers and collects results according to persisted runner assignments.
|
|
871
939
|
- The standard policy uses Claude and Codex analysers plus a report writer. Antigravity, Grok, and Kimi are optional and included only when the selected profile allows them.
|
|
872
940
|
- Worker models can be overridden through the legacy provider flags or generic `--worker-model provider=model`; lead and report-writer provider choices are explicit. Defaults are centrally managed through the provider registry and `OKSTRA_DEFAULT_*` environment variables.
|
|
873
|
-
- 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
|
|
874
|
-
- Worktree targeting is runner-based. A native-session executor uses its host adapter's edit and command primitives against `EXECUTOR_WORKTREE_PATH`; a CLI-
|
|
941
|
+
- 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 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, display name, model, `modelExecutionValue`, `hostModelValue`, runner, and dispatch mode are recorded in `teamContract.executor`; there is no provider-specific `workerAgent` field.
|
|
942
|
+
- Worktree targeting is runner-based. A native-session executor uses its host adapter's edit and command primitives against `EXECUTOR_WORKTREE_PATH`; a CLI-backed executor receives the worktree path through the deterministic provider process. Prefer explicit working-directory flags such as `git -C` or `cargo --manifest-path` when available.
|
|
875
943
|
- The project-level current-task convenience pointer is `.okstra/discovery/latest-task.json`.
|
|
876
944
|
- The project-level canonical task inventory is `.okstra/discovery/task-catalog.json`.
|
|
877
945
|
- At `okstra install` time, okstra skill assets are seeded to `~/.agents/skills/` by default. If `~/.claude` exists, `~/.claude/skills/` + `~/.claude/agents/` are seeded as well (per-project seeding is no longer performed).
|
package/docs/cli.md
CHANGED
|
@@ -388,7 +388,7 @@ Lead runtime independence boundary:
|
|
|
388
388
|
|
|
389
389
|
- `claude-code`: the current default execution path. Claude Code v2.1.178 removed `TeamCreate`, and the session owns an implicit team. Workers are dispatched with `Agent(name: ..., run_in_background: true)` without `team_name`. `teamName` is audit/display metadata; Claude session JSONL is used for usage accounting.
|
|
390
390
|
- `codex`: the runtime marker for the Codex lead adapter. `okstra codex-run` owns `--render-only --lead-runtime codex` to prepare a task bundle, and the prepared run manifest can be passed to `okstra codex-dispatch` for CLI-backed worker execution.
|
|
391
|
-
- `antigravity`: the runtime marker for the Antigravity CLI lead adapter. The current Antigravity session owns the native lead, keeps Antigravity assignments native, and routes every other provider through
|
|
391
|
+
- `antigravity`: the runtime marker for the Antigravity CLI lead adapter. The current Antigravity session owns the native lead, keeps Antigravity assignments native, and routes every other provider through the deterministic provider-process dispatcher.
|
|
392
392
|
- `grok` and `kimi`: registered native lead adapters for their corresponding CLIs. They can lead, analyse, and criticise, but they do not add executor, verifier, or report-writer worker roles.
|
|
393
393
|
- `external`: without Claude Code Teams, `--render-only --lead-runtime external` prepares the task bundle and external lead prompt. The lead manages the tmux-pane worker lifecycle with `okstra team dispatch`, `okstra team await`, and `okstra team teardown`. This path does not use `TeamCreate` / `Agent(...)` and uses artifact-only accounting.
|
|
394
394
|
|
|
@@ -422,7 +422,7 @@ worker roster contains Claude, Codex, or Antigravity.
|
|
|
422
422
|
|
|
423
423
|
For a Codex lead dry run, use `okstra codex-run <args...>`. It adds `--render-only --lead-runtime codex` itself and prints the prepared task bundle and lead prompt without dispatching workers.
|
|
424
424
|
The generated team-state, run manifest, and task manifest point `leadEventsPath` to `runs/<task-type>/state/lead-events-<task-type>-<seq>.jsonl`; rendering records a `bundle-prepared` event.
|
|
425
|
-
Then `okstra
|
|
425
|
+
Then `okstra worker-dispatch --project-root <dir> --run-manifest <run-manifest> [--workers <csv>]` reads each persisted assignment. `runner=native-session` rows stay with the current host; `runner=cli-wrapper` rows run through the registered provider process only after adjacent invocation metadata verifies. Without `--workers`, only CLI-backed analysis assignments are selected. Report writer is deferred to Phase 6 and must be dispatched explicitly with `--workers report-writer`; mixing it with analysis workers in one invocation is rejected before process creation. `okstra codex-dispatch` is a compatibility alias for this provider-neutral command. The report-writer provider and model still come from the manifest without a Codex-only opt-in flag; on success, postprocessing runs check-source → token-usage substitution → render-views → spawn-followups → validate-run in order.
|
|
426
426
|
|
|
427
427
|
The Codex worker (`--workers codex`, `--codex-model`) and Codex lead runtime are separate. The former creates a worker assignment whose runner depends on the host; the latter selects Codex as the native lead boundary. On Claude Code the Codex worker uses a CLI wrapper, while on Codex it uses the host-native worker/session primitive.
|
|
428
428
|
|
|
@@ -772,14 +772,18 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
|
|
|
772
772
|
| `okstra render-bundle <args…> [--stage <auto\|N>] [--stages <csv>]` | Thin shim over `prepare_task_bundle(render_only=True)` with the same signature as `python3 -m okstra_ctl.run --render-only`. `--stage` is for `implementation` and `final-verification`: for implementation, `auto` (default) selects the earliest incomplete stage with satisfied dependencies, while `<N>` forces a stage; for final-verification, `<N>` verifies one stage with artifacts under `runs/final-verification/stage-<N>/` and a `-fv-s<N>` team suffix, while an empty value performs whole-task verification with the flat layout. The separate `--stages <csv>` channel is for `release-handoff`: stage-group mode bundles the listed stage numbers into one PR, while an empty value selects whole-task mode. Preparation enforces eligibility—`done` + accepted `verified` + not yet `pr`—and automatically creates an input document that cites verification reports |
|
|
773
773
|
| `okstra profile show <task-type> [--resolved]` | Print a phase profile. `--resolved` expands its `{{INCLUDE:}}` targets and appends the lazy-read sidecars named in the profile body — transitively, because sidecars name sidecars of their own (`_implementation-executor.md` points at the coding-conventions preflight, the diff-review sweep, and the completion self-check). That matters because a profile is assembled from three places, so grepping only the top-level file returns false negatives: `grep clarification prompts/profiles/implementation.md` finds nothing while the assembled profile has many hits. One grep over this output answers whether a task-type covers a rule. The sidecar list is read from the profile body, never hard-coded, so a newly added sidecar is picked up without a code change. Read-only: it writes no manifest and registers no run, which is what separates it from `render-bundle` — `render-bundle` answers the same question but records a run in `recent.jsonl`, so it cannot be used to look something up. Exits 2 for an unknown task-type |
|
|
774
774
|
| `okstra codex-run <args…>` | Codex lead-adapter dry-run entry point. Accepts the same arguments as `render-bundle` but owns `--render-only --lead-runtime codex`. It prepares the task bundle and prints the prompt for the Codex lead without dispatching workers |
|
|
775
|
-
| `okstra
|
|
776
|
-
| `okstra
|
|
775
|
+
| `okstra worker-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Provider-neutral deterministic dispatcher for `runner=cli-wrapper` assignments. It verifies each adjacent invocation specification against the immutable run manifest immediately before process creation and records `core-pre-dispatch`; native-session rows stay with the host. The default selects CLI analysis assignments only. Phase 6 uses explicit `--workers report-writer`, and a mixed analysis/report batch is rejected. `--dry-run` performs the same verification and resolution without starting a provider process. |
|
|
776
|
+
| `okstra codex-dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--dry-run]` | Compatibility alias for `okstra worker-dispatch`; it no longer selects a Codex-only transport-agent path. |
|
|
777
|
+
| `okstra agent-prompt materialize\|verify\|record-dispatch\|link-result\|materialize-result\|complete\|verify-completion` | Internal invocation-contract CLI. `materialize` composes model assignment, functional duty, and task instructions; `verify` rejects identity, path, snapshot, assignment, source, or digest drift. Run-backed calls resolve `assignmentRef` from the manifest, enforce `authorizedPaths`, and reject real-path or symbolic-link escape. `record-dispatch` records a verified host-native specification before dispatch and `link-result` binds the accepted result. Standalone calls are identified by `(purpose, invocationId)` under `.okstra/agent-invocations/<purpose>/`; they publish a canonical result envelope and publish the completion marker last. Consumers use only the `returnedBody` from `verify-completion`. Metadata contains exactly `catalogDigest`, `assignmentDigest`, `dutyDigest`, `instructionDigest`, and `promptDigest`; JSON inputs use UTF-8, sorted keys, compact separators, and no non-finite values, while duty files use versioned sorted-name/byte framing. Instruction sources use `{kind: project\|runtime, path: <relative POSIX path>}` and never persist an installed absolute runtime path. |
|
|
778
|
+
| `okstra team dispatch --project-root <dir> --run-manifest <path> [--workers <csv>] [--jobs-file <path>] [--dry-run]` / `okstra team await --project-root <dir> --run-manifest <path> [--json]` / `okstra team teardown --project-root <dir> --run-manifest <path> [--dry-run] [--json]` | Read a `leadRuntime=external` run manifest and dispatch, await, or tear down tmux-pane workers. Default dispatch excludes report writer; Phase 6 selects it explicitly, and mixed analysis/report jobs are rejected. If a tmux pane cannot be created, gracefully degrade to the CLI wrapper and record the fallback in `workerDispatches[].degradedFrom` |
|
|
777
779
|
| `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run the whole Phase 7 post-report sequence in its contractual order: `token-usage` → `render-views` → `spawn-followups` → `validate-run`. Stops at the first non-zero exit and names the failing step, then prints a per-step `[ok]` / `[FAIL]` / `[skip]` summary on stderr so the outcome is legible without parsing the JSON payload. Every step is idempotent, so re-running after a fix is safe — but `--only <step>` (repeatable) reruns just the named steps in contractual order, which matters because `validate-run` is the step that usually fails and retrying it otherwise repeats the three steps before it at full token and wall-clock cost. This is the same code path (`scripts/okstra_ctl/report_finalize.py`) the Codex lead adapter runs automatically after its report-writer completes, so a Claude-led and a Codex-led run finalize identically. `--workspace-root` is owned by the Node wrapper. Prefer this over invoking the four steps individually |
|
|
778
780
|
| `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly (contract: `schemas/final-report-v2.0.schema.json`) into an always-generated, task-specific human HTML sibling while `templates/reports/final-report-v2.template.md` independently owns the AI handoff Markdown. Passing the Markdown sibling locates the same v2 data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, and Response ID parity |
|
|
779
781
|
| `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
|
|
780
782
|
| `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
|
|
781
783
|
| `okstra token-usage ...` | Wrap the installed `okstra-token-usage.py` to collect and substitute run token usage. Session JSONL is incrementally scanned by default through a byte-cursor cache at `$OKSTRA_HOME/cache/token-usage/`; `--no-cache` bypasses the cache and forces a full rescan as an accuracy fallback |
|
|
782
784
|
|
|
785
|
+
In prose, `okstra agent-prompt materialize|verify|record-dispatch|link-result|materialize-result|complete|verify-completion` denotes that internal command family; the vertical bars separate subcommands and are not literal shell arguments.
|
|
786
|
+
|
|
783
787
|
The convergence state lifecycle is `groups v1.0 → work v1.0 → final v1.3`; round-plan, round-results, and optional critic-results v1.0 artifacts provide the auditable transitions between those endpoints.
|
|
784
788
|
|
|
785
789
|
`okstra convergence apply-critic-gaps` is the only transition that may add verified coverage gaps to terminal main-queue state. `okstra plan-items extract` creates the complete plan queue, and `okstra plan-items validate` rejects any omission or drift before verifier dispatch.
|
|
@@ -45,8 +45,8 @@ okstra preflight --runtime claude-code --json
|
|
|
45
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
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
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. **
|
|
49
|
-
6. **
|
|
48
|
+
5. **Materialize and dispatch four reviewers in parallel.** Each reviewer is a separate standalone invocation under `.okstra/agent-invocations/code-review/`. Write `<invocation-id>.instructions.md`, run `okstra agent-prompt materialize --purpose code-review --audience code-reviewer ...`, and verify the returned `metadataPath` before dispatch. A native host call receives the verified prompt body and `hostModelValue`; a deterministic provider process receives the prompt path and `modelExecutionValue` through `okstra worker-dispatch`. 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. **Complete and audit the results.** Capture each raw return under the purpose directory's `.tmp/`, then run `agent-prompt materialize-result`, `complete`, and `verify-completion` in order. Parse only the verified `returnedBody`. Diff those cells against the assigned slice; re-dispatch one gap-fill invocation per axis for missing cells, using a new invocation ID and the same full materialize/verify/result/completion boundary. A missing or unverified verdict is unfinished work, never an implicit `clean`.
|
|
50
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
51
|
|
|
52
52
|
## Output Rules
|
|
@@ -55,3 +55,4 @@ okstra preflight --runtime claude-code --json
|
|
|
55
55
|
- The report's prose is Korean; paths, identifiers, rule names, and quoted code stay verbatim.
|
|
56
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
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.
|
|
58
|
+
- Branch mode keeps the final review at `.project-docs/code-reviews/<branch>/`, but its invocation prompts, result envelopes, and completion markers remain under `.okstra/agent-invocations/code-review/`.
|
|
@@ -217,7 +217,9 @@ Run both gates against the same draft and temporary selection contract.
|
|
|
217
217
|
python3 ~/.okstra/lib/validators/validate-schedule.py <draft> --selection-json <selection>
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
2. Only after that command passes,
|
|
220
|
+
2. Only after that command passes, create a new `.okstra/agent-invocations/schedule-verification/<invocation-id>.instructions.md` with the draft, selection JSON, and checks, but not the lead's reasoning. Run `okstra agent-prompt materialize --purpose schedule-verification --audience schedule-verifier ...` and verify the returned metadata before dispatch. A native host call uses the verified prompt body plus `hostModelValue`; a deterministic provider process uses `okstra worker-dispatch`, the prompt path, and `modelExecutionValue`. The verifier checks narrative coherence, phase rationale, executable order, engineering-only scope, and contradictions with the structured rows.
|
|
221
|
+
|
|
222
|
+
Capture the raw verifier return under the purpose directory's `.tmp/`, then run `okstra agent-prompt materialize-result`, `complete`, and `verify-completion` in order. Parse only the verified `returnedBody`; an inline or unverified response cannot pass the narrative gate.
|
|
221
223
|
|
|
222
224
|
If either gate finds a defect, revise the same draft in place and restart from the deterministic gate. Allow at most two revision rounds across both gates. Never publish a draft that has not passed both gates in that order.
|
|
223
225
|
|
|
@@ -13,22 +13,26 @@ Higher priority matches first. Once an upper step matches, the lower steps are n
|
|
|
13
13
|
| 1 | **per-run override** | `okstra render-bundle --pr-template-path <path>` or the wizard's one-time input | A relative path is resolved against the caller cwd (override) or against `project_root` (using the same function as project scope). |
|
|
14
14
|
| 2 | **project scope** | the `prTemplatePath` field in `<project_root>/.okstra/project.json` | A relative path is resolved against `project_root`. |
|
|
15
15
|
| 3 | **global scope** | the `prTemplatePath` field in `~/.okstra/config.json` | **Only an absolute path or a `~/`-prefixed path** is allowed. A relative path is ambiguous and rejected. |
|
|
16
|
-
| 4 | **default (
|
|
16
|
+
| 4 | **default (installed runtime)** | the first existing file among the candidate paths (§2 below) | The fallback path right after `npx okstra install`. |
|
|
17
17
|
|
|
18
18
|
If the file named in any of the 4 steps does not exist, it fails immediately with `PrTemplateError` (no silent fallback).
|
|
19
19
|
|
|
20
20
|
## 2. Default candidate paths
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
| # | Path | When it exists |
|
|
23
|
+
|---|------|----------------|
|
|
24
|
+
| 1 | `$OKSTRA_SKILLS_DIR/okstra-run/templates/pr-body.template.md` | Only when the `OKSTRA_SKILLS_DIR` environment variable is set. Nothing in okstra sets it for you — it is an escape hatch for a non-standard skill home. |
|
|
25
|
+
| 2 | `$OKSTRA_HOME/templates/pr/pr-body.template.md` — `~/.okstra/templates/pr/pr-body.template.md` unless `OKSTRA_HOME` overrides the home | The location `npx okstra install` writes to. This is the candidate that normally matches. |
|
|
24
26
|
|
|
25
|
-
The candidates are tried in priority order, and if all are absent it ends with an explicit error
|
|
27
|
+
The candidates are tried in priority order, and if all are absent it ends with an explicit error that names every path it searched:
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
```text
|
|
30
|
+
no PR template available: default template not found. Searched: <candidate paths>. Reinstall okstra (`npx okstra install`) or set prTemplatePath in project.json / ~/.okstra/config.json.
|
|
31
|
+
```
|
|
28
32
|
|
|
29
33
|
## 3. The original inside the source repository
|
|
30
34
|
|
|
31
|
-
- [`templates/pr/pr-body.template.md`](../templates/pr/pr-body.template.md) — the original that `npx okstra install` copies to
|
|
35
|
+
- [`templates/pr/pr-body.template.md`](../templates/pr/pr-body.template.md) — the original that `npx okstra install` copies to `~/.okstra/templates/pr/pr-body.template.md` (§2 candidate 2). To change the copy, edit this file and install again.
|
|
32
36
|
|
|
33
37
|
## 4. Configuration commands — persistence
|
|
34
38
|
|
|
@@ -59,8 +59,8 @@ okstra/
|
|
|
59
59
|
├── .claude/skills/ Claude Code project-only mirror-sync surface
|
|
60
60
|
├── .claude/settings.json Claude Code project-only mirror-sync surface
|
|
61
61
|
├── .codex/hooks.json Codex project lifecycle hooks
|
|
62
|
-
├── agents/
|
|
63
|
-
├── prompts/ launch
|
|
62
|
+
├── agents/ native Claude execution-adapter definitions
|
|
63
|
+
├── prompts/ launch/profile contracts, duty catalog, wizard prompt JSON
|
|
64
64
|
├── schemas/ JSON schema for final-report data.json
|
|
65
65
|
├── templates/ report, setup, PR, project-doc templates/assets
|
|
66
66
|
├── validators/ run / brief / schedule / view validators
|
|
@@ -139,7 +139,7 @@ Runtime/install asset changes follow this checklist:
|
|
|
139
139
|
- `runtime/templates/*` → `~/.okstra/templates/`
|
|
140
140
|
- `runtime/skills/<name>` (the thirteen user-facing skills only) → `~/.agents/skills` always, plus `~/.claude/skills` when `~/.claude` exists
|
|
141
141
|
- `runtime/prompts/*` → `~/.okstra/prompts/` (lead contracts under `prompts/lead/`, coding-preflight pack under `prompts/coding-preflight/`)
|
|
142
|
-
- `runtime/agents/workers
|
|
142
|
+
- the native Claude execution adapters in `runtime/agents/workers/` → `~/.claude/agents/` when `~/.claude` exists; retired provider transport-agent files are removed only when the prior install manifest owned them
|
|
143
143
|
- install manifests → `~/.okstra/installed-skills.json` (target-aware), `~/.okstra/installed-agents.json`
|
|
144
144
|
- version stamp → `~/.okstra/version`
|
|
145
145
|
|
|
@@ -184,7 +184,8 @@ Runtime/install asset changes follow this checklist:
|
|
|
184
184
|
| `render-bundle` | `src/commands/execute/render-bundle.mjs` | Preview `prepare_task_bundle(render_only=True)` |
|
|
185
185
|
| `profile` | `src/commands/inspect/profile-show.mjs` | Print a phase profile with `{{INCLUDE:}}` expanded and its lazy-read sidecars appended transitively, so one grep answers whether a task-type covers a rule — a top-level grep alone returns false negatives (Python: `okstra_ctl.profile_show`). Read-only, unlike `render-bundle` |
|
|
186
186
|
| `run` | `src/commands/execute/run.mjs` | Host-aware execution front door (`auto` → Claude/Codex/Antigravity/external path selection) |
|
|
187
|
-
| `codex-run`, `codex-dispatch` | `src/commands/execute/codex-*.mjs` | Codex lead dry-run bundle preparation
|
|
187
|
+
| `codex-run`, `codex-dispatch` | `src/commands/execute/codex-*.mjs` | Codex lead dry-run bundle preparation; `codex-dispatch` is the compatibility alias for provider-neutral worker dispatch |
|
|
188
|
+
| `agent-prompt`, `worker-dispatch` | `src/commands/execute/{agent-prompt,worker-dispatch}.mjs` | Materialize/verify invocation prompts, record host-native specification/result links, and launch verified CLI assignments through the provider-neutral dispatcher |
|
|
188
189
|
| `team` | `src/commands/execute/team.mjs` | External lead tmux-pane worker dispatch / await / teardown |
|
|
189
190
|
| `convergence` | `src/commands/execute/convergence.mjs` | Internal admin CLI for the deterministic Phase 5.5 convergence engine (`seed`/`plan-round`/`apply-round`/`apply-critic-gaps`/`finalize`/`validate`/`example`; Python: `okstra_ctl.convergence`) |
|
|
190
191
|
| `plan-items` | `src/commands/execute/plan-items.mjs` | Internal admin CLI for deterministic plan-body item extraction and exact-match validation (`extract`/`validate`; Python: `okstra_ctl.plan_items_cli`) |
|
|
@@ -294,9 +295,13 @@ Important modules:
|
|
|
294
295
|
| `manager_store.py` | Manager-owned state mutation — project membership, task planning, assignment, directives, event append |
|
|
295
296
|
| `manager_sync.py` | One-way child project `.okstra` snapshot reader; corrupt child state becomes row-level `error` so other children continue |
|
|
296
297
|
| `manager_launch.py` | Child launch packet and manager child context renderer; records `prepared` launch metadata/events without changing project-local task state |
|
|
297
|
-
| `
|
|
298
|
+
| `agent_invocation.py` | Deep invocation-contract module — composes model assignment, common/functional duty, and task instructions; publishes immutable prompt/metadata pairs; verifies five digests; owns standalone result/completion envelopes |
|
|
299
|
+
| `agent_prompt_cli.py` | CLI boundary for run-backed and standalone materialization/verification plus host-native dispatch and result-link records |
|
|
300
|
+
| `dispatch_state.py` | Provider-neutral `WorkerJob`, invocation metadata validation, immutable host-native dispatch/result-link recording, and shared team-state mutation helpers |
|
|
301
|
+
| `dispatch_core.py` | Backend-neutral worker dispatch core — verifies invocation metadata immediately before worker execution, then records and collects code-owned process/pane attempts shared by every lead runtime |
|
|
302
|
+
| `worker_dispatch.py` | Provider-neutral deterministic dispatcher for every `runner=cli-wrapper` assignment; it never composes or rewrites a prompt |
|
|
298
303
|
| `cmux.py` | cmux-pane worker backend — mirrors the tmux backend's contract (a worker that gets a pane frees the lead process; anything that stops a pane opening degrades quietly to the blocking wrapper, recording a surface UUID rather than a tmux pane id). Detects a usable cmux session before selecting the backend (CLI resolves + ping answers PONG + the lead's workspace is resolvable), derives placement from the workspace geometry each dispatch, relays lead/worker events to the cmux sidebar, and records the run's terminal backend in the manifest so both phases of a run land on one backend. A sandbox that hides cmux (`PermissionError` on the socket) stops dispatch with the remedy instead of degrading into the same broken fallback; a quit app (`FileNotFoundError`) still degrades |
|
|
299
|
-
| `codex_dispatch.py` |
|
|
304
|
+
| `codex_dispatch.py` | Compatibility adapter delegating `okstra codex-dispatch` to the provider-neutral `worker_dispatch` path |
|
|
300
305
|
| `analysis_packet.py` | assembles the compact analysis-worker input packet for a task run from worker-owned profile sections; report/lead procedure stays outside the packet |
|
|
301
306
|
| `analysis_inputs.py` | shared input boundary for `project-analysis`, `feature-analysis`, and `change-impact-analysis` — validates evidence-report identity and review status, enforces the type-to-type relation allowlist, computes `exact`/`stale` freshness, and resolves free-text or `PF-NNN` feature targets for both wizard and prepare paths |
|
|
302
307
|
| `user_response.py` | parses clarification/approval responses and the analysis-review sidecar; `parse_analysis_review` validates accepted, revision-requested, and rejected decisions plus their affected IDs and reason |
|
|
@@ -356,6 +361,7 @@ Token/cost accounting:
|
|
|
356
361
|
| Path | Role |
|
|
357
362
|
|---|---|
|
|
358
363
|
| `launch.template.md` | Lead prompt template rendered for each run |
|
|
364
|
+
| `duties/common.md`, `duties/<audience>.md` | Canonical common and functional duty contracts composed into every Okstra-owned LLM invocation; provider/model identity does not select the duty |
|
|
359
365
|
| `profiles/_common-contract.md` | Shared phase contract |
|
|
360
366
|
| `profiles/<task-type>.md` | Phase profiles (single language — runtime always loads from `profiles/`, never a translated mirror) |
|
|
361
367
|
| `project-analysis.md`, `feature-analysis.md`, `change-impact-analysis.md` | Read-only sidetrack profiles for project mapping, one-feature behavior tracing, and proposed-change impact mapping |
|
|
@@ -434,13 +440,10 @@ Boilerplate shared by several skills (bash invocation rule, outdated-CLI preflig
|
|
|
434
440
|
| File | Role |
|
|
435
441
|
|---|---|
|
|
436
442
|
| `agents/workers/claude-worker.md` | Claude analyzer/verifier/executor spec |
|
|
437
|
-
| `agents/workers/codex-worker.params.json` | Codex analyzer/verifier/executor wrapper params (build renders `.md` via `_cli-wrapper-template.md`) |
|
|
438
|
-
| `agents/workers/antigravity-worker.params.json` | Antigravity analyzer/verifier/executor wrapper params (build renders `.md` via `_cli-wrapper-template.md`) |
|
|
439
|
-
| `agents/workers/grok-worker.params.json` | Grok read-only analyser/critic wrapper params |
|
|
440
|
-
| `agents/workers/kimi-worker.params.json` | Kimi read-only analyser/critic wrapper params |
|
|
441
443
|
| `agents/workers/report-writer-worker.md` | data.json SSOT author and audit sidecar writer |
|
|
444
|
+
| `agents/workers/translator-worker.md` | Claude-native final-report translation execution adapter |
|
|
442
445
|
|
|
443
|
-
The neutral lead lifecycle contract lives at `prompts/lead/okstra-lead-contract.md`. Executable host strategies and their relay contracts live together under `scripts/okstra_ctl/adapters/hosts/<host-id>/`; `prompts/lead/adapters/cmux.md` remains the environment-selected cmux worker-backend contract. Lead resources are installed under `~/.okstra/prompts/lead/`, while executable host adapters are installed under `~/.okstra/lib/python/okstra_ctl/adapters/hosts/`. They are runtime resources, not agent skills.
|
|
446
|
+
These files are native Claude execution adapters, not provider-neutral LLM transport wrappers. Non-native providers execute through the deterministic `worker-dispatch` process boundary. The neutral lead lifecycle contract lives at `prompts/lead/okstra-lead-contract.md`. Executable host strategies and their relay contracts live together under `scripts/okstra_ctl/adapters/hosts/<host-id>/`; `prompts/lead/adapters/cmux.md` remains the environment-selected cmux worker-backend contract. Lead resources are installed under `~/.okstra/prompts/lead/`, while executable host adapters are installed under `~/.okstra/lib/python/okstra_ctl/adapters/hosts/`. They are runtime resources, not agent skills.
|
|
444
447
|
|
|
445
448
|
### 4.12 `tests/` and `tests-e2e/`
|
|
446
449
|
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -21,11 +21,12 @@ color: blue
|
|
|
21
21
|
tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch"]
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
This is the Claude host-native execution adapter for a materialized Okstra
|
|
25
|
+
invocation. The final prompt's duty contract owns the role boundary,
|
|
26
|
+
responsibility, and prohibited actions. This file owns Claude tool usage,
|
|
27
|
+
artifact persistence, and liveness procedure only. Do not shell out to another
|
|
28
|
+
model CLI, and refuse a dispatch whose prompt has no adjacent verified
|
|
29
|
+
invocation metadata.
|
|
29
30
|
|
|
30
31
|
## Execution Rules
|
|
31
32
|
|
|
@@ -14,7 +14,11 @@ model: inherit
|
|
|
14
14
|
tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch"]
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
This is the Claude host execution adapter for a materialized Okstra invocation.
|
|
18
|
+
The final prompt's `report-writer` duty contract owns the role boundary,
|
|
19
|
+
required responsibility, and prohibited actions. This file owns only how that
|
|
20
|
+
contract is executed with Claude host tools. Refuse a dispatch whose prompt has
|
|
21
|
+
no adjacent verified invocation metadata.
|
|
18
22
|
|
|
19
23
|
- The `**Report Language:**` header in your dispatch prompt is already
|
|
20
24
|
resolved to `en` or `ko` by the lead. Copy it verbatim into
|
|
@@ -28,9 +32,10 @@ tools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "TodoWrite", "WebFetch"
|
|
|
28
32
|
when the data.json's authored prose is not English. You can run that check
|
|
29
33
|
yourself before returning.
|
|
30
34
|
|
|
31
|
-
##
|
|
35
|
+
## Host file procedure
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
Use the assigned `runs/<task-type>/reports/final-report-<task-type>-<seq>.data.json`
|
|
38
|
+
path and the renderer commands supplied by the final prompt.
|
|
34
39
|
|
|
35
40
|
The data.json is the **single source of truth** for two audiences. The renderer (`scripts/okstra-render-final-report.py`) produces the AI handoff Markdown (`final-report-<task-type>-<seq>.md`) deterministically from it. Phase 7 produces the human HTML (`final-report-<task-type>-<seq>.html`) through the task-specific HTML renderer. HTML is rendered directly from the data.json; it is not a presentation of the Markdown. You do NOT hand-write either derived artifact. Both are regenerated whenever the data.json changes.
|
|
36
41
|
|
|
@@ -130,7 +135,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
|
|
|
130
135
|
- For `implementation-planning`, populate `implementationPlanning.variationPointAnalysis` — a `hasMultipleImplementations` judgement synthesized from the analysis workers' output, not a field filled in last. When it is `true`, write one `points[]` row per varying behavior carrying `behavior`, the two or more `implementations` that serve it, `evidence` (a `path:line`, or the sibling task / stage that already implements that behavior), and an `extractionDecision` of `extract` / `interfaceKind` / `coveredBy` (the Stage Map stage that builds the interface) / `rationale`; when it is `false`, write a non-empty `noVariationRationale` and leave `points` empty (the two branches are mutually exclusive). Do NOT pass a boilerplate rationale — `false` is the cheaper field to fill, and a `false` declaration the brief or the sibling code in the workers' evidence contradicts is a `P-Var` DISAGREE, not a saving. Also populate `implementationPlanning.recommendedOption.testSeams`: one row per boundary a test injects at and replaces, each carrying `boundary` / `injectedAs` / `replacedInTest`. An empty list is a conscious "no seam needed" claim, never a default for a field nobody filled. The schema excerpt enumerates both row shapes — author against it. (Maintainer SSOT for these two rules: the `Required deliverable shape` bullet in `prompts/profiles/implementation-planning.md` in the okstra repo; that path is not resolvable here, so it is provenance, not a file to open.) **Enforced:** `schemas/final-report-v2.0.schema.json` `$defs.VariationPointAnalysis` / `$defs.VariationPoint` (the block is in `implementationPlanning.required`) plus `testSeams` in `$defs.RecommendedOption`'s `required`; `validators/validate-run.py` `_validate_variation_point_analysis` rejects a rationale-less `false`, a `false` carrying points, a `true` with no point, an `extract: true` decision leaving `interfaceKind` or `coveredBy` empty, and a hexagonal project extracting as anything but a port; and every point becomes a `P-Var-*` plan item judged in §5.5.9.
|
|
131
136
|
- When the `Task Type` is `improvement-discovery`, populate `improvementDiscovery.candidates[]`, `improvementDiscovery.lensCoverage[]`, `improvementDiscovery.selectionLimit`, and `improvementDiscovery.userNarrative`. Each candidate carries the 11 logical fields enforced by `validators/validate_improvement_report.py`; each lens-coverage row records candidate IDs or an evidence-backed no-candidate rationale. Source IDs, lens names, and worker prefixes from `scripts/okstra_ctl/improvement_lenses.py`. The standard renderer derives the AI handoff Markdown; never author a free-form improvement report.
|
|
132
137
|
|
|
133
|
-
Write the three completion artifacts and the separate audit sidecar with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its
|
|
138
|
+
Write the three completion artifacts and the separate audit sidecar with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its seeded settings carry no `PreToolUse` entry at all — only the session/subagent lifecycle hooks `SessionStart` compact-reminder, `SessionEnd` trace-cleanup, and `SubagentStop` / `TaskCompleted` pane reclaim, none of which can intercept a tool call). A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. After writing data.json, invoke the renderer (`Bash`): `okstra render-final-report <data.json path>`, then write the Worker Result Path pointer. Confirm data.json, rendered Markdown, the pointer, and the audit sidecar exist before responding with a short status line prefixed by your model identity, per the preamble §"Return message to the lead". **Enforced:** dispatch `completionPaths` requires the first three files and `validators/validate_session_conformance.py` validates the audit sidecar.
|
|
134
139
|
|
|
135
140
|
```
|
|
136
141
|
**Model:** Report writer worker, <modelExecutionValue>
|
|
@@ -14,9 +14,11 @@ model: inherit
|
|
|
14
14
|
tools: ["Bash", "Read", "Write", "Glob", "Grep"]
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
This is the Claude host execution adapter for a materialized Okstra invocation.
|
|
18
|
+
The final prompt's `translator` duty contract owns the role boundary,
|
|
19
|
+
responsibility, and prohibited actions. This file owns only the extraction,
|
|
20
|
+
translation-sidecar, and verification tool procedure. Refuse a dispatch whose
|
|
21
|
+
prompt has no adjacent verified invocation metadata.
|
|
20
22
|
|
|
21
23
|
## Procedure
|
|
22
24
|
|
|
@@ -48,7 +50,7 @@ The data.json is the English SSOT that every later phase and validator reads. Yo
|
|
|
48
50
|
|
|
49
51
|
## How to translate
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
Judge every term on whether the translation or the original carries the meaning faster to a working developer in the target language, and pick that one. The goal is a reader who understands the report sooner, not a document with no English left in it.
|
|
52
54
|
|
|
53
55
|
- **Never touch**: code identifiers, file paths, CLI commands and flags, model names, commit SHAs, URLs, and anything already inside backticks. Reproduce them character for character.
|
|
54
56
|
- **Keep the English word** when that is what developers in the target language actually say. Forcing a native coinage onto `commit`, `worktree`, `merge`, `lint`, `diff`, `stage`, `rollback` or `PR` makes the sentence *slower* to read, not more local.
|
|
@@ -133,7 +133,33 @@ When a change adds or modifies a service's dependency on a concrete adapter —
|
|
|
133
133
|
|
|
134
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
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 — unless the project declares `architecture.style = hexagonal` in `.okstra/project.json`, where this item is blocking and that same port sketch is what you write rather than what you record: fix the injection before the write, never record-and-pass.
|
|
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 — unless the project declares `architecture.style = hexagonal` in `.okstra/project.json`, where this item is blocking and that same port sketch is what you write rather than what you record: fix the injection before the write, never record-and-pass.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Rule H6 — Business rules live in the domain, not in application services
|
|
141
|
+
|
|
142
|
+
A service orchestrates: fetch, delegate the decision to a domain function, persist, publish. Flag changed service code that *decides* a business outcome inline instead of calling into the domain.
|
|
143
|
+
|
|
144
|
+
Violations:
|
|
145
|
+
|
|
146
|
+
- An `if` / `else` chain over domain fields that decides an outcome — eligibility, validity, which branch of a business process runs.
|
|
147
|
+
- Arithmetic expressing a business formula: money, ranking, quota, date-based entitlement.
|
|
148
|
+
- A private service method whose name is a domain concept (`isEligible…`, `computeDowngrade…`, `resolve…Status`). The name is telling you where it belongs.
|
|
149
|
+
|
|
150
|
+
Not a violation:
|
|
151
|
+
|
|
152
|
+
- Orchestration control flow — early return on not-found, `try` / `finally` around a transaction, threading a transaction through repository calls.
|
|
153
|
+
- DTO → domain mapping.
|
|
154
|
+
- Calling a domain predicate and acting on its result. That IS the pattern.
|
|
155
|
+
|
|
156
|
+
The test: **if this decision changed, would the change be described as a business rule change or as a plumbing change?** A business rule change that lands in a service is the violation — the rule is now invisible to the domain's own tests, and the next caller that needs it writes its own copy.
|
|
157
|
+
|
|
158
|
+
Severity: `should-fix`, and blocking when the embedded rule is substantial — money, permissions, or a state machine. Under a declared `architecture.style = hexagonal` the substantial case is fixed before the write, never record-and-pass.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
**How far a project convention reaches.** H5 above is the one rule in this overlay a project-local convention cannot clear. Elsewhere here, a documented convention does resolve the conflict in the project's favour — but that latitude stops at okstra's own gates. It never clears a check the implementation verifier's blocking list declares **mechanical**: a changed file under the domain folder importing an ORM / DB layer, a framework or its DI decorators, or anything under adapters / infrastructure / services is decided by reading the import list, and no convention argument reaches that verdict (`prompts/profiles/_implementation-verifier.md` §"Static design & test-quality review" → Hexagonal). Resolving a declared-mechanical finding as "project convention" is the failure this paragraph exists to prevent — a real run did exactly that, returned `clean`, and the team's own PR review then flagged the same import.
|
|
137
163
|
|
|
138
164
|
Severity: advisory in a project that has not declared `architecture.style = hexagonal` — a direction-of-travel rule there, not a correctness gate, so it never blocks on its own. Under that declaration it is blocking: the injection is fixed before the write.
|
|
139
165
|
|