cowork-flow 1.9.2 → 2.0.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/CHANGELOG.md +23 -1
- package/docs/architecture.md +1 -34
- package/docs/direction.md +1 -1
- package/docs/hosts.md +13 -9
- package/package.json +1 -1
- package/presets/claude-code/.claude-plugin/plugin.json +1 -1
- package/presets/codex/.codex-plugin/plugin.json +1 -1
- package/presets/dsh/plugins/workflow-state.js +31 -16
- package/presets/kimi-code/.kimi-plugin/plugin.json +1 -1
- package/presets/opencode/cowork-flow/plugin-core.js +42 -87
- package/presets/opencode/plugins/cowork-flow.js +7 -9
- package/presets/qoder/.qoder-plugin/plugin.json +1 -1
- package/presets/qoder/hooks/hooks.json +1 -1
- package/presets/zcode/.zcode-plugin/plugin.json +1 -1
- package/src/commands/host.js +23 -2
- package/src/commands/registry.js +4 -2
- package/template/.cowork-flow/.version +1 -1
- package/template/.cowork-flow/adapters/claude-code/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/codex/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/dsh/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/opencode/adapter.yaml +1 -1
- package/template/.cowork-flow/adapters/qoder/adapter.yaml +1 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_lifecycle_commands.py +0 -1
- package/template/.cowork-flow/scripts/adapters/cli/task_parser.py +0 -7
- package/template/.cowork-flow/scripts/adapters/host/claude_code_policy.py +6 -4
- package/template/.cowork-flow/scripts/adapters/host/codex_policy.py +8 -6
- package/template/.cowork-flow/scripts/adapters/host/edit_scope.py +113 -0
- package/template/.cowork-flow/scripts/adapters/host/opencode_policy.py +12 -0
- package/template/.cowork-flow/scripts/adapters/host/qoder_policy.py +7 -5
- package/template/.cowork-flow/scripts/adapters/host/workflow_state_hook.py +0 -91
- package/template/.cowork-flow/scripts/adapters/host/zcode_policy.py +15 -77
- package/template/.cowork-flow/scripts/run.py +0 -2
- package/template/.cowork-flow/scripts/services/context_paths.py +2 -2
- package/template/.cowork-flow/scripts/services/fact_view.py +49 -21
- package/template/.cowork-flow/scripts/services/lifecycle_checks.py +1 -216
- package/template/.cowork-flow/scripts/services/lifecycle_policy.py +0 -5
- package/template/.cowork-flow/scripts/services/task_gate_profiles.py +1 -1
- package/template/.cowork-flow/scripts/services/task_lifecycle.py +14 -31
- package/template/.cowork-flow/spec/backend/error-handling.md +0 -14
- package/template/.cowork-flow/spec/contracts/context-injection.md +49 -22
- package/template/.cowork-flow/spec/contracts/decision-anchor.md +3 -3
- package/template/.cowork-flow/spec/contracts/error-codes.md +1 -3
- package/template/.cowork-flow/spec/contracts/evidence-completion.md +6 -8
- package/template/.cowork-flow/spec/contracts/index.md +0 -1
- package/template/.cowork-flow/spec/contracts/plan-binding.md +3 -2
- package/template/.cowork-flow/spec/contracts/subagent-dispatch.md +6 -21
- package/template/.cowork-flow/spec/contracts/task-adopt.md +1 -1
- package/template/.cowork-flow/spec/runtime/contract-registry.json +0 -13
- package/template/.cowork-flow/spec/runtime/host-assets.json +10 -7
- package/template/.cowork-flow/spec/runtime/scope-rules.json +2 -2
- package/template/.opencode/cowork-flow/plugin-core.js +42 -87
- package/template/.opencode/plugins/cowork-flow.js +7 -9
- package/template/AGENTS.md +0 -1
- package/template/skills/agent-dispatch/SKILL.md +0 -15
- package/template/skills/runtime-health/scripts/doctor.py +0 -49
- package/template/skills/spec-sync/SKILL.md +0 -1
- package/template/skills/task-planning/SKILL.md +1 -1
- package/template/skills/task-review/SKILL.md +3 -3
- package/template/.cowork-flow/scripts/adapters/cli/spec_check.py +0 -178
- package/template/.cowork-flow/scripts/services/spec_check.py +0 -573
- package/template/.cowork-flow/spec/contracts/spec-checks.md +0 -281
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# Spec Check Contract (spec-declared commands)
|
|
2
|
-
|
|
3
|
-
## What it does
|
|
4
|
-
|
|
5
|
-
User specs (the `backend/` and `frontend/` directories under `.cowork-flow/spec/`,
|
|
6
|
-
plus any project-owned directory) may declare check commands in frontmatter under
|
|
7
|
-
`checks:`. The mechanism only executes declarations and never parses spec prose —
|
|
8
|
-
so a rule change is a command change in the same file, and the two stay in sync by
|
|
9
|
-
construction.
|
|
10
|
-
|
|
11
|
-
## When to attach a declaration
|
|
12
|
-
|
|
13
|
-
A clause is worth a `checks:` declaration whenever a command exit code can decide
|
|
14
|
-
it (lint / tests / type check / schema validation). Two hard constraints:
|
|
15
|
-
|
|
16
|
-
- **A declared command that does not exist = `unchecked` = blocks `complete`**:
|
|
17
|
-
confirm the command runs in the current repository before declaring it.
|
|
18
|
-
- **edit-only is not a gate**: `when: edit` is a best-effort edit-time hint only —
|
|
19
|
-
Bash writes bypass PostToolUse, and hosts without an edit-time fast path
|
|
20
|
-
(see Appendix A) receive none of this feedback; the closeout phase does not
|
|
21
|
-
re-run it either. A check that must hard-block has to declare
|
|
22
|
-
`when: lifecycle`, or the default `both`.
|
|
23
|
-
|
|
24
|
-
## Syntax
|
|
25
|
-
|
|
26
|
-
```markdown
|
|
27
|
-
---
|
|
28
|
-
checks:
|
|
29
|
-
- cmd: npm run lint --silent
|
|
30
|
-
files: "src/" # optional: scope, a directory prefix or an extension ("*.ts"), comma-separated
|
|
31
|
-
timeout: 60 # optional: seconds, default 30, hard cap 120
|
|
32
|
-
when: both # optional: edit | lifecycle | both (default both)
|
|
33
|
-
cmd.win: npm run lint # optional: Windows override command
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
# Spec prose (the mechanism never reads it)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
- `files` accepts exactly two forms: a directory prefix (`src/`, ending in `/` or
|
|
40
|
-
auto-completed to it) and an extension (`*.ts`). Full glob forms (`**`, `[]`)
|
|
41
|
-
parse as an error, the declaration does not run, and doctor reports it — never a
|
|
42
|
-
fallback to "match everything"; for multi-value declarations
|
|
43
|
-
(`files: "src/", "lib/"`) each token is unquoted under the same quoting rule
|
|
44
|
-
before it takes effect.
|
|
45
|
-
- Quoting rule: a value is unquoted only when the whole value is a single quoted
|
|
46
|
-
pair; command values carrying several quoted pairs, such as
|
|
47
|
-
`"python" -c "..."`, are kept verbatim and split by the executor under shell
|
|
48
|
-
rules.
|
|
49
|
-
- `when: edit` runs at edit time (PostToolUse) on the changed files that hit
|
|
50
|
-
`files`, with the timeout clamped to 2.5 seconds (spawn budget for interpreter
|
|
51
|
-
startup and the cmd wrapper); slow commands should declare `when: lifecycle`.
|
|
52
|
-
- **The file must be UTF-8 without BOM**: frontmatter starting with a BOM is
|
|
53
|
-
silently ignored by the parser (zero declarations, no parse error) — the
|
|
54
|
-
declaration fails without a word. After declaring, run `spec-check` once to
|
|
55
|
-
confirm it was parsed.
|
|
56
|
-
|
|
57
|
-
### From zero to the first check (copy-paste)
|
|
58
|
-
|
|
59
|
-
1. Create a spec file under `.cowork-flow/spec/` (a project-owned subdirectory, or
|
|
60
|
-
directly at the root, e.g. `spec/team-gate.md`). `contracts/`, `runtime/`, and
|
|
61
|
-
`schemas/` are machine directories and are not scanned; every other level and
|
|
62
|
-
subdirectory is. Save the file as UTF-8 without BOM.
|
|
63
|
-
2. Copy the block below into it, replace `cmd` with a command that really runs in
|
|
64
|
-
this repository, and narrow `files` to the layer that spec governs:
|
|
65
|
-
|
|
66
|
-
```markdown
|
|
67
|
-
---
|
|
68
|
-
checks:
|
|
69
|
-
- cmd: npm run lint --silent
|
|
70
|
-
files: "src/"
|
|
71
|
-
when: both
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
# Team spec prose (the mechanism never reads it)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
3. Run `./.cowork-flow/run spec-check`: you should see
|
|
78
|
-
`1 passed, 0 violations, 0 unchecked` instead of `no checks declared`. Then
|
|
79
|
-
break the rule once on purpose and confirm it really reports a `violation` — a
|
|
80
|
-
declaration that always passes is decoration, not a check.
|
|
81
|
-
|
|
82
|
-
**The command prints violations only; the framework adds the
|
|
83
|
-
`spec-check[<spec>] violation:` prefix**: a `cmd` that prints that prefix itself
|
|
84
|
-
stacks on top of the framework's, producing
|
|
85
|
-
`spec-check[team-gate.md] violation: spec-check[team-gate.md] violation: ...`.
|
|
86
|
-
A command's only duties are an exit code (0 = pass, non-zero = violation) and
|
|
87
|
-
readable output; the prefix, the classification, the three-state verdict, and the
|
|
88
|
-
throttle all live on the framework side.
|
|
89
|
-
|
|
90
|
-
## Three states and the gate
|
|
91
|
-
|
|
92
|
-
| State | Meaning | complete gate |
|
|
93
|
-
| --- | --- | --- |
|
|
94
|
-
| `pass` | exit code 0 | passes |
|
|
95
|
-
| `violation` | non-zero exit code | **blocks** (status does not advance) |
|
|
96
|
-
| `unchecked` | command missing / interpreter missing / timeout | **blocks**; an explicit `--allow-unchecked` lets it through and records the exemption in task.json `meta.specCheckExempt` (item by item, see below) |
|
|
97
|
-
|
|
98
|
-
`unchecked` never masquerades as `pass` ("did not run" is not "passed"); malformed
|
|
99
|
-
frontmatter treats that spec as having no checks and lands in the doctor report
|
|
100
|
-
without breaking the flow. A parse failure is a signal separate from the three
|
|
101
|
-
states: declaration parse errors such as an illegal `files` form belong to
|
|
102
|
-
`parseErrors`, are not folded into `pass`/`unchecked`, and exit with code 2 like
|
|
103
|
-
`unchecked`. Text mode and `--json` must use the same exit code, and text mode
|
|
104
|
-
must print the parse-error lines (`spec-check: N parse error(s): <spec>: <error>`)
|
|
105
|
-
— printing only `0 passed, 0 violations, 0 unchecked` would make a malformed
|
|
106
|
-
declaration look like a clean pass.
|
|
107
|
-
|
|
108
|
-
Exit code contract: `0` everything passed; `1` a violation exists; `2` unchecked
|
|
109
|
-
entries, `parseErrors`, or an executor failure exist. `--allow-unchecked` waives
|
|
110
|
-
only the `unchecked` block, never a parse error.
|
|
111
|
-
|
|
112
|
-
**A whole-executor failure is a separate block**: when the spec-check module fails
|
|
113
|
-
to import or raises, the closeout gate degrades to `summary.executorError` set plus
|
|
114
|
-
`classification: executor-failure` plus exit code 2, and blocks `complete` with
|
|
115
|
-
`SPEC-CHECK-EXECUTOR-ERROR`. It does **not** accept `--allow-unchecked` — that flag
|
|
116
|
-
waives "a declared command could not run", not "the checker itself broke"; the
|
|
117
|
-
synthetic `unchecked: 1` in the degraded report is only a counting placeholder, is
|
|
118
|
-
not reported as its own entry, and the block message does not mention the flag.
|
|
119
|
-
|
|
120
|
-
**An exemption record must name what it waived**: `meta.specCheckExempt` =
|
|
121
|
-
`{"at", "flag": "--allow-unchecked", "by": <actor or null>, "unchecked": N,
|
|
122
|
-
"uncheckedItems": [{"spec", "cmd"}…], "truncated"?}`; `meta.evidenceExempt` =
|
|
123
|
-
`{"at", "flag": "--allow-missing-evidence", "by": <actor or null>,
|
|
124
|
-
"missing": [<AC id>…]}`. The item list is capped at 10 entries; beyond that
|
|
125
|
-
`truncated: true` is set. A single command is clipped at 80 characters and **never
|
|
126
|
-
splits the first token** (that token is the program name — which is precisely the
|
|
127
|
-
answer to "what went unverified"). `by` is the resolved session actor, or `null`
|
|
128
|
-
when none resolves — **not** a stand-in borrowed from `creator`/`assignee`. The
|
|
129
|
-
completion output and the archive output print **the same rows of the same record**
|
|
130
|
-
(the completion output reads the `task.json` just written), so "what was recorded"
|
|
131
|
-
and "what was printed" cannot tell two different stories; with no exemption, no
|
|
132
|
-
exemption rows are printed at all.
|
|
133
|
-
|
|
134
|
-
**Every run carries one classification fact**: `summary.classification` ∈
|
|
135
|
-
`parse-error` / `executor-failure` / `zero-declaration` / `violation` /
|
|
136
|
-
`unchecked` / `pass` / `empty`, queryable directly through `--json`. `empty` is a
|
|
137
|
-
legitimate state: declarations exist but none ran in this phase (for example
|
|
138
|
-
`when: edit` declarations under a lifecycle run). Reading a report older than that
|
|
139
|
-
field goes through `report_classification()`, which returns `legacy` — a missing
|
|
140
|
-
field is itself a fact, and is never reverse-engineered from the counts (that
|
|
141
|
-
would promote an old report into a guess).
|
|
142
|
-
|
|
143
|
-
**Zero declarations is a legitimate state, but it must be visible**: a repository
|
|
144
|
-
with no effective declarations exits with code `0`, and text mode prints the
|
|
145
|
-
standalone fact line `spec-check: no checks declared (<N> spec files scanned)`,
|
|
146
|
-
distinguishable from `spec-check: <n> passed, 0 violations, 0 unchecked`; `--json`
|
|
147
|
-
carries `specFiles` at the top level and `declarations` in the summary. Zero
|
|
148
|
-
declarations ≠ clean pass — it means the spec chain currently holds no
|
|
149
|
-
machine-checkable constraint, a fact that needs to be seen rather than a green
|
|
150
|
-
that can be claimed.
|
|
151
|
-
|
|
152
|
-
## Timing and output
|
|
153
|
-
|
|
154
|
-
- **Edit time** (PostToolUse): runs only for `when: edit`/`both` when the change
|
|
155
|
-
hits `files`, printing a single
|
|
156
|
-
`spec-check[<spec>] violation: <first violation line>`; a missing executor or a
|
|
157
|
-
timeout stays silent and never blocks the edit stream. Host differences and
|
|
158
|
-
throttle details are in Appendix A.
|
|
159
|
-
- **Closeout** (task complete): full run, results recorded in the
|
|
160
|
-
`meta.specCheckSummary` telemetry; the block message lists only a summary of the
|
|
161
|
-
failing entries. Exemptions must also be **visible**: while
|
|
162
|
-
`--allow-unchecked` / `--allow-missing-evidence` apply, the completion output
|
|
163
|
-
prints that record item by item (an `Exempted: …` header line plus
|
|
164
|
-
` - <spec>: <cmd>` / ` - <AC id>` detail rows, naming
|
|
165
|
-
`meta.specCheckExempt` / `meta.evidenceExempt` and the actor), and the archive
|
|
166
|
-
output prints the same rows again — an exemption written only into task.json is a
|
|
167
|
-
clean pass to whoever is watching the command.
|
|
168
|
-
- **Manual query**: `./.cowork-flow/run spec-check [--file <path>] [--json]
|
|
169
|
-
[--verbose]` (exit codes as above).
|
|
170
|
-
|
|
171
|
-
## Declarations belong to the consumer
|
|
172
|
-
|
|
173
|
-
- **The template ships no declarations**: the spec files the template carries
|
|
174
|
-
contain no effective `checks:` declaration — the template cannot know a
|
|
175
|
-
project's commands, and a missing command lands in `unchecked` and blocks
|
|
176
|
-
`complete`. Declare them in project-owned spec files (e.g. `spec/team-xxx.md` or
|
|
177
|
-
a project-owned subdirectory); sync never touches those, and the declarations are
|
|
178
|
-
maintained by the consumer alongside the spec prose.
|
|
179
|
-
- **sync-protected prefix and the named exception**: `spec/` as a whole is a
|
|
180
|
-
sync-protected prefix, so existing spec files are not overwritten by `sync`
|
|
181
|
-
(spec updates in the template only take effect for a fresh `init`); the single
|
|
182
|
-
named exception is the entries under `spec/contracts/` listed in
|
|
183
|
-
`spec/runtime/host-assets.json` `syncPolicy.safeFiles` (this file and
|
|
184
|
-
`workflow-state-templates.md`), which do track sync so contract corrections can
|
|
185
|
-
reach installed projects.
|
|
186
|
-
- **Recognizing clauses and attaching declarations is the main-session AI's job**:
|
|
187
|
-
declarations belong to the project. During review / spec maintenance, the main
|
|
188
|
-
session AI identifies machine-checkable clauses and adds the `checks:`
|
|
189
|
-
declaration (it does not rewrite the clause prose on its own — a conflict between
|
|
190
|
-
a clause and the implementation goes through the review flow for the user to
|
|
191
|
-
decide); only the main session writes specs, and a delegated subagent only
|
|
192
|
-
reports candidates without exceeding its scope. The full duty is in the
|
|
193
|
-
`task-review` and `spec-sync` Skills.
|
|
194
|
-
- **Four quality disciplines**: ① produce a `pass` on a clean tree; ② prove the
|
|
195
|
-
check really can fail with one negative case (not an always-passing decoration);
|
|
196
|
-
③ narrow the coverage with `files`; ④ run `spec-check` once after declaring to
|
|
197
|
-
confirm it was parsed (guards against silent BOM / `files`-form failures). The
|
|
198
|
-
review report lists the `clause → command → negativeProof` triple: `clause` (the
|
|
199
|
-
clause), `command` (the deciding command), `negativeProof` (what the command
|
|
200
|
-
reports when that clause is broken) — the same key set `task-review`'s Review
|
|
201
|
-
Report Format and `subagent check-review --finding` use. A clause no command can
|
|
202
|
-
decide must state why (`command`/`negativeProof` are `null`) instead of leaving
|
|
203
|
-
the row blank; a clause that is machine-checkable yet yields no command is itself
|
|
204
|
-
a finding.
|
|
205
|
-
|
|
206
|
-
## Appendix A: host capability matrix
|
|
207
|
-
|
|
208
|
-
| Host | digest injection | edit-time fast path | closeout enforcement |
|
|
209
|
-
| --- | --- | --- | --- |
|
|
210
|
-
| zcode | yes (Specs line h2 digest) | yes (PostToolUse short path; **main session only**) | yes |
|
|
211
|
-
| claude-code | yes (Python single source) | yes (PostToolUse, exit 2 feedback) | yes |
|
|
212
|
-
| codex | yes | yes (PostToolUse, `apply_patch\|Write\|Edit` → exit 2 stderr) | yes |
|
|
213
|
-
| opencode | yes | yes (plugin `tool.execute.after` appends one line to the tool result) | yes |
|
|
214
|
-
| dsh | yes (preset injection) | yes (preset plugin `tools/post-execute` → `additionalContexts`) | yes |
|
|
215
|
-
| kimi-code | yes (UserPromptSubmit hook → `inject.py --host kimi-code`) | no (no PostToolUse registered; observational-event stdout is discarded) | yes |
|
|
216
|
-
| qoder | yes (plugin hook → `inject.py --host qoder`) | yes (`PostToolUse` non-blocking event, warning via `additionalContext`, exit 0) | yes |
|
|
217
|
-
|
|
218
|
-
zcode / codex / claude-code share `run_edit_checks` through the Python single
|
|
219
|
-
source; opencode pulls the same executor from inside its plugin via the CLI
|
|
220
|
-
(`run spec-check --phase edit --throttled`), dsh calls the same Python protocol
|
|
221
|
-
through its preset plugin's `tools/post-execute`, and qoder calls the same executor
|
|
222
|
-
through its plugin hook's `PostToolUse` (that event is not blockable on Qoder, so
|
|
223
|
-
the warning comes back as `additionalContext` with exit 0 rather than
|
|
224
|
-
claude-code/codex's stderr + exit 2) — the six hosts with an edit-time fast path
|
|
225
|
-
share one throttle state and one three-state semantics instead of implementing
|
|
226
|
-
check logic each; kimi-code only gets closeout enforcement and the subagent
|
|
227
|
-
self-check.
|
|
228
|
-
|
|
229
|
-
Edit-time throttle: the same file is reported at most once per 10 seconds; the
|
|
230
|
-
throttle state lives in `.cowork-flow/.runtime/spec-edit-throttle.json`, and the
|
|
231
|
-
window is written only after the check ran to completion — a crashing executor
|
|
232
|
-
does not consume the window and a retry still executes. Delegated subagent sessions
|
|
233
|
-
receive spec-violation feedback too (subagents are the main code writers); scope
|
|
234
|
-
warnings stay main-only. The zcode main session's hook identity (the conversation's
|
|
235
|
-
own session id) is never bound — activation happens under an explicit identity on
|
|
236
|
-
the Bash side — so display and edit-time warnings fall back to the latest main
|
|
237
|
-
session; claude/codex keep a strict session identity (their Bash env carries the
|
|
238
|
-
same session id). zcode forwards through a shim to the single-source entrypoint
|
|
239
|
-
(`inject.py`), merging scope warnings and spec warnings into one additionalContext
|
|
240
|
-
payload; claude uses exit 2 stderr feedback.
|
|
241
|
-
|
|
242
|
-
## Appendix B: the delegated edit-time gap and its compensation
|
|
243
|
-
|
|
244
|
-
**Delegated subagent edit-time gap (measured on zcode, two rounds on 2026-09-09)**:
|
|
245
|
-
the ZCode plugin hook only dispatches on the main-session tool stream (all seven
|
|
246
|
-
event types attach to the main session; a subagent is a separate internal session
|
|
247
|
-
stream, `sess_subagent_agent_*`), so an Agent child's Edit/Write never receives the
|
|
248
|
-
`spec-check[...] violation` line — runtime binding and check logic are both fine;
|
|
249
|
-
what is missing is the host dispatch. The compensating mechanisms (frozen in the
|
|
250
|
-
subagent-dispatch.md contract):
|
|
251
|
-
|
|
252
|
-
1. Self-check before a subagent finishes: run `./.cowork-flow/run spec-check` in
|
|
253
|
-
full and fix everything (the pull path; verified to work);
|
|
254
|
-
2. A second check before the parent session accepts: violations count as acceptance
|
|
255
|
-
blockers (feedback moves from closeout up to return);
|
|
256
|
-
3. Implementation tasks' Verify commands include the spec-check self-check by
|
|
257
|
-
default;
|
|
258
|
-
4. The closeout hard gate still backstops everything unchanged (a violation blocks
|
|
259
|
-
`complete`).
|
|
260
|
-
|
|
261
|
-
Governance direction: ask the host to extend hook dispatch to the subagent tool
|
|
262
|
-
stream; once delivered, item 1 degrades automatically into a redundant safety net.
|
|
263
|
-
|
|
264
|
-
## Appendix C: digest injection
|
|
265
|
-
|
|
266
|
-
After entering `in_progress`, the stage-contract Specs line upgrades to
|
|
267
|
-
`Specs: <spec>(<h2 heading tree>); …`: at most 6 h2 entries, each truncated to 24
|
|
268
|
-
characters with `();` characters stripped; a missing file adds no annotation. The
|
|
269
|
-
digest is an entry-name index, not the prose, and is hard-capped by
|
|
270
|
-
scope-rules.json `stageContract.budget`; over budget, the whole line yields
|
|
271
|
-
(Scope/Gates lines are never dropped). The python/zcode output comes from a single
|
|
272
|
-
Python entrypoint, locked by the `test/stage-contract.test.js` matrix; opencode
|
|
273
|
-
transports that same entrypoint's output, so byte-exact equality holds by
|
|
274
|
-
construction.
|
|
275
|
-
|
|
276
|
-
## Appendix D: open decision
|
|
277
|
-
|
|
278
|
-
**Closeout aggregation budget**: a closeout runs everything with no aggregation
|
|
279
|
-
budget — repositories with many slow declarations get linearly slower at
|
|
280
|
-
`complete`. Whether to give closeout parallelism or a budget ceiling is left to a
|
|
281
|
-
later product decision; today it stays simple and serial.
|