sortie-dogs 0.9.8 → 0.9.10

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 CHANGED
@@ -1,13 +1,13 @@
1
1
  # Sortie-dogs
2
2
 
3
- **Add a bounded, cost-aware execution loop to OpenCode without taking OpenCode over.**
4
-
5
- Sortie-dogs is an opt-in overlay, not a replacement. It stays passive until you
6
- invoke `/sortie` or select `dog-coordinator`. Standard OpenCode agents and
7
- unrelated sessions remain available and unchanged.
8
-
9
- > **Project status: Beta.** v0.9.x is under active stabilization. Runtime
10
- > behavior, configuration, and runtime assets may still change before 1.0.
3
+ **Add a bounded, cost-aware execution loop to OpenCode without taking OpenCode over.**
4
+
5
+ Sortie-dogs is an opt-in overlay, not a replacement. It stays passive until you
6
+ invoke `/sortie` or select `dog-coordinator`. Standard OpenCode agents and
7
+ unrelated sessions remain available and unchanged.
8
+
9
+ > **Project status: Beta.** v0.9.x is under active stabilization. Runtime
10
+ > behavior, configuration, and runtime assets may still change before 1.0.
11
11
 
12
12
  [![npm](https://img.shields.io/npm/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
13
13
  [![license](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
@@ -15,63 +15,63 @@ unrelated sessions remain available and unchanged.
15
15
 
16
16
  ![Sortie-dogs coordinating a bounded implementation workflow](https://raw.githubusercontent.com/zufall-upon/Sortie-dogs/main/docs/assets/sortie-workflow.gif)
17
17
 
18
- Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
19
- bounded implementation, canonical validation, and evidence-backed completion.
18
+ Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
19
+ bounded implementation, canonical validation, and evidence-backed completion.
20
20
 
21
21
  Requirements: Node.js 22.6 or newer, npm, and OpenCode.
22
22
 
23
- Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
24
-
25
- Release: [v0.9.8](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.8)
26
-
27
- ## Why Sortie-dogs?
28
-
29
- ### Invisible until invited
30
-
31
- Use normal OpenCode normally. Sortie activates only for `/sortie` or
32
- `dog-coordinator`; it does not disable or replace OpenCode's standard agents.
33
-
34
- ### Spend strong models only where they matter
35
-
36
- Lower-cost models handle bounded retrieval and parallel volume work. Stronger
37
- models are reserved for implementation, escalation, and independent review.
38
-
39
- ### Return with proof
40
-
41
- Writes stay scoped, and completion requires validation evidence. Every completed
42
- run can return a concise Speed / Cost / Proof debrief.
43
-
44
- ## Designed to coexist with OpenCode
45
-
46
- Sortie-dogs adds a workflow to your existing setup rather than replacing it.
47
-
48
- - It does not disable OpenCode's native agents or replace standard roles such as
49
- `build`, `plan`, `explore`, or `general`.
50
- - Ordinary sessions are not automatically converted into Sortie workflows.
51
- - Project-local initialization is the recommended setup and does not change
52
- user settings.
53
- - Global runtime availability and cross-project reflection require separate,
54
- explicit opt-in. Reflection is disabled by default.
55
- - Unknown or user-owned runtime files are preserved rather than overwritten.
56
- - Manual removal targets only known Sortie-owned runtime assets.
57
-
58
- Use OpenCode normally. Invoke the pack only when you want it.
59
-
60
- ## Quick start
61
-
62
- Install the public npm package in the project and generate the project-local
63
- OpenCode runtime files:
23
+ Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
24
+
25
+ Release: [v0.9.10](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.10)
26
+
27
+ ## Why Sortie-dogs?
28
+
29
+ ### Invisible until invited
30
+
31
+ Use normal OpenCode normally. Sortie activates only for `/sortie` or
32
+ `dog-coordinator`; it does not disable or replace OpenCode's standard agents.
33
+
34
+ ### Spend strong models only where they matter
35
+
36
+ Lower-cost models handle bounded retrieval and parallel volume work. Stronger
37
+ models are reserved for implementation, escalation, and independent review.
38
+
39
+ ### Return with proof
40
+
41
+ Writes stay scoped, and completion requires validation evidence. Every completed
42
+ run can return a concise Speed / Cost / Proof debrief.
43
+
44
+ ## Designed to coexist with OpenCode
45
+
46
+ Sortie-dogs adds a workflow to your existing setup rather than replacing it.
47
+
48
+ - It does not disable OpenCode's native agents or replace standard roles such as
49
+ `build`, `plan`, `explore`, or `general`.
50
+ - Ordinary sessions are not automatically converted into Sortie workflows.
51
+ - Project-local initialization is the recommended setup and does not change
52
+ user settings.
53
+ - Global runtime availability and cross-project reflection require separate,
54
+ explicit opt-in. Reflection is disabled by default.
55
+ - Unknown or user-owned runtime files are preserved rather than overwritten.
56
+ - Manual removal targets only known Sortie-owned runtime assets.
57
+
58
+ Use OpenCode normally. Invoke the pack only when you want it.
59
+
60
+ ## Quick start
61
+
62
+ Install the public npm package in the project and generate the project-local
63
+ OpenCode runtime files:
64
64
 
65
65
  ```sh
66
66
  npm install --save-dev sortie-dogs
67
- npx sortie-dogs init .
68
- ```
69
-
70
- This is the recommended setup. Runtime assets stay project-local.
71
-
72
- Installing the runtime assets does not load the plugin, and without the plugin
73
- every role runs on whichever model the caller happened to use. Add the package
74
- to the `plugin` array in the project's `.opencode/opencode.json`:
67
+ npx sortie-dogs init .
68
+ ```
69
+
70
+ This is the recommended setup. Runtime assets stay project-local.
71
+
72
+ Installing the runtime assets does not load the plugin, and without the plugin
73
+ every role runs on whichever model the caller happened to use. Add the package
74
+ to the `plugin` array in the project's `.opencode/opencode.json`:
75
75
 
76
76
  ```json
77
77
  {
@@ -82,8 +82,8 @@ to the `plugin` array in the project's `.opencode/opencode.json`:
82
82
  Restart OpenCode afterwards. A `plugin` entry must name the package, not a
83
83
  subpath: `sortie-dogs/plugin` is an import specifier, not a plugin specifier.
84
84
 
85
- `dog-coordinator` defaults to `openai/gpt-5.6-terra` with the `high` variant; `dog-scout` defaults to
86
- `openai/gpt-5.6-luna`. To pin either role to another model, save this
85
+ `dog-coordinator` defaults to `openai/gpt-5.6-terra` with the `high` variant; `dog-scout` defaults to
86
+ `openai/gpt-5.6-luna`. To pin either role to another model, save this
87
87
  as `.opencode/sortie-dogs.json`:
88
88
 
89
89
  ```json
@@ -166,83 +166,83 @@ Optional settings in `.opencode/sortie-dogs.json`:
166
166
  {
167
167
  "operationManifestPath": "operation-manifest.json",
168
168
  "handoffPaths": ["handoff.json"],
169
- "readOnlyTools": ["my_mcp_search"],
170
- "dedicatedWorkerModel": { "model": "provider/model", "variant": "deep" },
171
- "continuation": { "enabled": true, "maxAutoContinues": 10 },
172
- "reflection": {
173
- "enabled": false,
174
- "layers": { "run": true, "project": true, "global": false },
175
- "maxInjectedTokens": 500
176
- }
177
- }
178
- ```
179
-
180
- The same schema may be saved globally as
181
- `~/.config/opencode/sortie-dogs.json` (on Windows,
182
- `%USERPROFILE%\.config\opencode\sortie-dogs.json`). Precedence is built-in
183
- defaults, global file, project file, `SORTIE_DOGS_CONFIG`, then plugin factory
184
- options. OpenCode plugin normalization may omit factory options, so use the
185
- global file for durable global settings.
169
+ "readOnlyTools": ["my_mcp_search"],
170
+ "dedicatedWorkerModel": { "model": "provider/model", "variant": "deep" },
171
+ "continuation": { "enabled": true, "maxAutoContinues": 10 },
172
+ "reflection": {
173
+ "enabled": false,
174
+ "layers": { "run": true, "project": true, "global": false },
175
+ "maxInjectedTokens": 500
176
+ }
177
+ }
178
+ ```
179
+
180
+ The same schema may be saved globally as
181
+ `~/.config/opencode/sortie-dogs.json` (on Windows,
182
+ `%USERPROFILE%\.config\opencode\sortie-dogs.json`). Precedence is built-in
183
+ defaults, global file, project file, `SORTIE_DOGS_CONFIG`, then plugin factory
184
+ options. OpenCode plugin normalization may omit factory options, so use the
185
+ global file for durable global settings.
186
186
 
187
187
  - `operationManifestPath` moves the manifest; the path is project-relative.
188
- - `handoffPaths` lists the handoff files the plugin inspects. A worker can only
188
+ - `handoffPaths` lists the handoff files the plugin inspects. A worker can only
189
189
  bind after one of these files passes inspection, so an empty list disables
190
190
  binding entirely. Relative entries are also candidate-relative in a nested
191
191
  repository: a child candidate may use its own `handoff.json` while OpenCode is
192
192
  opened at the parent workspace. For operational work the coordinator creates
193
193
  that valid handoff before dispatch and sends its exact absolute path; the
194
- binding child must use the built-in Read tool on it immediately before bind.
195
- New coordinator contracts are emitted under the candidate-relative
196
- `.sortie-dogs/contracts/` directory as `handoff.<id>.json` and
197
- `<id>.operation-manifest.json`. The directory is ignored by this repository's
198
- `.gitignore`; legacy root/scoped paths and configured custom paths remain
199
- readable and preflight-compatible, but are never moved or deleted.
200
- Remove the directory only when no Sortie run is active.
194
+ binding child must use the built-in Read tool on it immediately before bind.
195
+ New coordinator contracts are emitted under the candidate-relative
196
+ `.sortie-dogs/contracts/` directory as `handoff.<id>.json` and
197
+ `<id>.operation-manifest.json`. The directory is ignored by this repository's
198
+ `.gitignore`; legacy root/scoped paths and configured custom paths remain
199
+ readable and preflight-compatible, but are never moved or deleted.
200
+ Remove the directory only when no Sortie run is active.
201
201
  - `readOnlyTools` adds host-specific tool names that never change files, such as
202
202
  MCP tools. Unknown tools are denied for a bound session by default.
203
- - `dedicatedWorkerModel` selects the serial implementation target used by
204
- `implementation`, `remediation`, `blocker-resolution`, `sol-worker-mk2a2`, and
205
- `dog-worker`. It defaults to `openai/gpt-5.6-sol` with variant `medium`.
206
- The installed `dog-luna-worker` fabric route remains fixed to
207
- `openai/gpt-5.6-luna` with variant `max`; pointing the serial target at that
203
+ - `dedicatedWorkerModel` selects the serial implementation target used by
204
+ `implementation`, `remediation`, `blocker-resolution`, `sol-worker-mk2a2`, and
205
+ `dog-worker`. It defaults to `openai/gpt-5.6-sol` with variant `medium`.
206
+ The installed `dog-luna-worker` fabric route remains fixed to
207
+ `openai/gpt-5.6-luna` with variant `max`; pointing the serial target at that
208
208
  Luna model is invalid because it would collapse the two route identities. The
209
209
  coordinator dispatches this role only for a ready descriptor of a prepared
210
- `luna-fabric` run; a `sol-serial` run keeps `dog-worker`.
210
+ `luna-fabric` run; a `sol-serial` run keeps `dog-worker`.
211
211
  - `continuation` bounds the batch loop. After a terminal unit and its checkpoint,
212
212
  `dog-coordinator` calls `sortie_compact_and_continue`, which compacts the run
213
213
  and resumes the same root session on the next independent unit. Only a root
214
214
  `dog-coordinator` session is ever resumed: a child session is never promoted and
215
215
  another coordinator is never adopted. Set `enabled` to `false` to keep every
216
- batch manual, lower `maxAutoContinues` (default and maximum `10`) to change the
217
- ceiling, and set `summarizeModel` to override the latest coordinator
218
- model used for compaction. Normal OpenCode auto-compaction keeps the
219
- host's auto-continue behavior; Sortie suppresses it only while its own
220
- explicitly queued rollover owns the resume.
221
- Every terminal root-coordinator response that does not resume another unit
222
- compacts without auto-continuing, so completed tool output is not carried into
223
- the next user request.
224
- - `reflection` is an opt-in process-prevention companion for an activated root
225
- `dog-coordinator`. It is disabled by default. Run and project layers default
226
- to enabled after opt-in; the cross-project global storage layer remains
227
- disabled unless explicitly enabled. Child and non-coordinator sessions fail
228
- closed, and `SORTIE_REFLECTION=0` is an immediate kill switch. The coordinator
229
- injects the governing `REFLECTION_POLICY` only while reflection is enabled.
230
- `maxInjectedTokens` budgets the dynamic `SORTIE_PROCESS_REFLECTIONS` heading
231
- and persisted entry lines; the policy is outside that entry budget.
232
- The coordinator
233
- evaluates it only after a resolved blocker/review defect and at a terminal
234
- unit, with a maximum of three records per run; routine bugs and external
235
- failures are never journaled.
236
-
237
- ## Example run
216
+ batch manual, lower `maxAutoContinues` (default and maximum `10`) to change the
217
+ ceiling, and set `summarizeModel` to override the latest coordinator
218
+ model used for compaction. Normal OpenCode auto-compaction keeps the
219
+ host's auto-continue behavior; Sortie suppresses it only while its own
220
+ explicitly queued rollover owns the resume.
221
+ Every terminal root-coordinator response that does not resume another unit
222
+ compacts without auto-continuing, so completed tool output is not carried into
223
+ the next user request.
224
+ - `reflection` is an opt-in process-prevention companion for an activated root
225
+ `dog-coordinator`. It is disabled by default. Run and project layers default
226
+ to enabled after opt-in; the cross-project global storage layer remains
227
+ disabled unless explicitly enabled. Child and non-coordinator sessions fail
228
+ closed, and `SORTIE_REFLECTION=0` is an immediate kill switch. The coordinator
229
+ injects the governing `REFLECTION_POLICY` only while reflection is enabled.
230
+ `maxInjectedTokens` budgets the dynamic `SORTIE_PROCESS_REFLECTIONS` heading
231
+ and persisted entry lines; the policy is outside that entry budget.
232
+ The coordinator
233
+ evaluates it only after a resolved blocker/review defect and at a terminal
234
+ unit, with a maximum of three records per run; routine bugs and external
235
+ failures are never journaled.
236
+
237
+ ## Example run
238
238
 
239
239
  An illustrative low-risk run stays bounded and reports its gates:
240
240
 
241
241
  ```text
242
242
  You: /sortie Add the requested behavior
243
243
  dog-coordinator: manifest confirmed
244
- dog-scout: skipped — no concrete evidence gap
245
- dog-worker: implementation complete
244
+ dog-scout: skipped — no concrete evidence gap
245
+ dog-worker: implementation complete
246
246
  validation: npm test — PASS
247
247
  review: skipped — low risk
248
248
  dog-coordinator: completion evidence accepted
@@ -252,8 +252,8 @@ dog-coordinator: completion evidence accepted
252
252
 
253
253
  1. **Brief and plan** — `dog-coordinator` turns the request into acceptance
254
254
  criteria, a write manifest, and validation requirements.
255
- 2. **Optional scout** — one bounded, read-only investigation runs only for a
256
- concrete pre-worker evidence gap.
255
+ 2. **Optional scout** — one bounded, read-only investigation runs only for a
256
+ concrete pre-worker evidence gap.
257
257
  3. **Dedicated worker** — the dedicated worker implements only the approved
258
258
  manifest and also owns scoped remediation or blocker resolution.
259
259
  4. **Canonical validation** — the declared test or build command must produce
@@ -305,16 +305,16 @@ untouched.
305
305
 
306
306
  ## Model routing
307
307
 
308
- Default routes split work by required capability and repeated-context cost.
309
- Sortie-dogs keeps retrieval on Luna, coordinator routing on Terra, and independent
310
- review on Sol unless the host declares another target.
308
+ Default routes split work by required capability and repeated-context cost.
309
+ Sortie-dogs keeps retrieval on Luna, coordinator routing on Terra, and independent
310
+ review on Sol unless the host declares another target.
311
311
 
312
- `dog-coordinator` defaults to `openai/gpt-5.6-terra` with the `high` variant. Coordinator quality controls
313
- planning and forward progress, so Terra High is the default balance between capability and cost.
314
- Project or global `modelRouting` can override
315
- this default. If the host proves Terra unavailable,
316
- the existing availability policy uses a configured free-tier fallback when present
317
- and otherwise preserves the session model.
312
+ `dog-coordinator` defaults to `openai/gpt-5.6-terra` with the `high` variant. Coordinator quality controls
313
+ planning and forward progress, so Terra High is the default balance between capability and cost.
314
+ Project or global `modelRouting` can override
315
+ this default. If the host proves Terra unavailable,
316
+ the existing availability policy uses a configured free-tier fallback when present
317
+ and otherwise preserves the session model.
318
318
 
319
319
  `dog-scout` defaults to `openai/gpt-5.6-luna` with the `high` variant, since
320
320
  gathering bounded evidence is retrieval rather than reasoning and that tier is
@@ -322,46 +322,46 @@ where the curve gives the most per unit of cost. Nobody selects a model for a
322
322
  session the loop spawns, which is why delegated roles carry defaults and the
323
323
  coordinator does not. Project-local routing can override this default.
324
324
 
325
- The `implementation`, `remediation`, `blocker-resolution`, `sol-worker-mk2a2`,
326
- and `dog-worker` roles always use the stable serial target,
327
- `openai/gpt-5.6-sol` with the `medium` variant. `dedicatedWorkerModel` may move
328
- that serial target when a host cannot serve it. The installed `dog-luna-worker`
329
- route is separately fixed to `openai/gpt-5.6-luna` with the `max` variant. Its
325
+ The `implementation`, `remediation`, `blocker-resolution`, `sol-worker-mk2a2`,
326
+ and `dog-worker` roles always use the stable serial target,
327
+ `openai/gpt-5.6-sol` with the `medium` variant. `dedicatedWorkerModel` may move
328
+ that serial target when a host cannot serve it. The installed `dog-luna-worker`
329
+ route is separately fixed to `openai/gpt-5.6-luna` with the `max` variant. Its
330
330
  shared worker contract requires one validated fabric descriptor: the coordinator
331
- admits a v0.8 DAG contract with `sortie_admit_luna_fabric`, prepares it with
332
- `sortie_prepare_luna_fabric`, and materializes only the current ready wave, with
333
- at most five distinct Luna units. The complete DAG may contain up to 64 units.
334
- After every active artifact is verified, `sortie_advance_luna_fabric_wave`
335
- integrates them into a runtime-owned hidden candidate, cleans those worktrees,
336
- and creates fresh worktrees from that exact snapshot. After the final wave,
337
- `sortie_validate_luna_fabric_candidate` runs canonical validation once and
338
- `sortie_accept_luna_fabric_candidate` records review before one target CAS.
339
- Declared shared-path ownership serializes overlapping units
340
- across waves; unowned overlap or any admission defect routes the whole job back
341
- to one `dog-worker`. The fabric never duplicates one unit across lanes.
342
- `modelRouting` cannot replace either fixed
343
- route, and a serial override naming the Luna fabric model is invalid rather than
344
- silently collapsing both identities. Version 0.7.0 routed `dog-worker` to Luna
345
- Max; v0.8 intentionally preserves that history while splitting stable Sol and
346
- fabric Luna roles. Other explicit routes try the preferred target, then ordered
347
- fallbacks. Roles without a built-in default or explicit route keep OpenCode's
348
- already selected model.
331
+ admits a v0.8 DAG contract with `sortie_admit_luna_fabric`, prepares it with
332
+ `sortie_prepare_luna_fabric`, and materializes only the current ready wave, with
333
+ at most five distinct Luna units. The complete DAG may contain up to 64 units.
334
+ After every active artifact is verified, `sortie_advance_luna_fabric_wave`
335
+ integrates them into a runtime-owned hidden candidate, cleans those worktrees,
336
+ and creates fresh worktrees from that exact snapshot. After the final wave,
337
+ `sortie_validate_luna_fabric_candidate` runs canonical validation once and
338
+ `sortie_accept_luna_fabric_candidate` records review before one target CAS.
339
+ Declared shared-path ownership serializes overlapping units
340
+ across waves; unowned overlap or any admission defect routes the whole job back
341
+ to one `dog-worker`. The fabric never duplicates one unit across lanes.
342
+ `modelRouting` cannot replace either fixed
343
+ route, and a serial override naming the Luna fabric model is invalid rather than
344
+ silently collapsing both identities. Version 0.7.0 routed `dog-worker` to Luna
345
+ Max; v0.8 intentionally preserves that history while splitting stable Sol and
346
+ fabric Luna roles. Other explicit routes try the preferred target, then ordered
347
+ fallbacks. Roles without a built-in default or explicit route keep OpenCode's
348
+ already selected model.
349
349
 
350
350
  `dog-reviewer` and `dog-advisor` must never inherit the caller's model, because
351
351
  review and strategy lose their value when they run on the model that produced
352
352
  the candidate. Both default to `anthropic/claude-opus-5` when the catalog
353
353
  declares it, and otherwise fall back to `openai/gpt-5.6-sol` with the `xhigh`
354
- variant. That fallback uses higher effort than the Sol Medium worker because
355
- review has to be able to reject work the worker just produced. Moving
356
- `dedicatedWorkerModel` does not change consultation policy. Nothing here requires
357
- a particular vendor: both roles stay fully configurable, so declare whichever
358
- model you can actually serve.
354
+ variant. That fallback uses higher effort than the Sol Medium worker because
355
+ review has to be able to reject work the worker just produced. Moving
356
+ `dedicatedWorkerModel` does not change consultation policy. Nothing here requires
357
+ a particular vendor: both roles stay fully configurable, so declare whichever
358
+ model you can actually serve.
359
359
 
360
360
  ```json
361
361
  {
362
362
  "modelRouting": {
363
- "dog-coordinator": {
364
- "preferred": { "model": "openai/gpt-5.6-luna", "variant": "max" }
363
+ "dog-coordinator": {
364
+ "preferred": { "model": "openai/gpt-5.6-luna", "variant": "max" }
365
365
  },
366
366
  "dog-scout": {
367
367
  "preferred": { "model": "openai/gpt-5.6-luna", "variant": "high" }
@@ -375,8 +375,8 @@ model you can actually serve.
375
375
  }
376
376
  },
377
377
  "modelCatalog": {
378
- "project": [
379
- { "model": "openai/gpt-5.6-sol", "variants": ["medium", "xhigh"] },
378
+ "project": [
379
+ { "model": "openai/gpt-5.6-sol", "variants": ["medium", "xhigh"] },
380
380
  { "model": "openai/gpt-5.6-luna", "variants": ["max", "high"] },
381
381
  { "model": "anthropic/claude-opus-5" }
382
382
  ]
@@ -397,52 +397,52 @@ coordinator. `dog-reviewer` independently checks high-risk candidates after
397
397
  canonical validation. Neither role implements, stages, commits, or acts as a
398
398
  user-facing worker.
399
399
 
400
- ## Optional global availability
401
-
402
- If you intentionally want the Sortie roles available across projects, install
403
- the CLI and runtime assets globally:
404
-
405
- ```sh
406
- npm install --global sortie-dogs
407
- sortie-dogs init --global
408
- ```
409
-
410
- This writes canonical Sortie runtime assets to OpenCode's global configuration;
411
- it does not make project-local initialization global. Load the plugin from the
412
- global `~/.config/opencode/opencode.json` when using these assets:
413
-
414
- ```json
415
- {
416
- "plugin": ["sortie-dogs"]
417
- }
418
- ```
419
-
420
- Project-local configuration and plugin loading remain available when a project
421
- needs its own settings or dependency.
422
-
423
- ## Updates and migration
400
+ ## Optional global availability
401
+
402
+ If you intentionally want the Sortie roles available across projects, install
403
+ the CLI and runtime assets globally:
404
+
405
+ ```sh
406
+ npm install --global sortie-dogs
407
+ sortie-dogs init --global
408
+ ```
409
+
410
+ This writes canonical Sortie runtime assets to OpenCode's global configuration;
411
+ it does not make project-local initialization global. Load the plugin from the
412
+ global `~/.config/opencode/opencode.json` when using these assets:
413
+
414
+ ```json
415
+ {
416
+ "plugin": ["sortie-dogs"]
417
+ }
418
+ ```
419
+
420
+ Project-local configuration and plugin loading remain available when a project
421
+ needs its own settings or dependency.
422
+
423
+ ## Updates and migration
424
424
 
425
425
  After replacing the dependency with a newer release asset, run:
426
426
 
427
427
  ```sh
428
- npx sortie-dogs init .
429
- ```
430
-
431
- After initialization, fully restart the OpenCode host before resuming a coordinator or opening a
432
- fresh session. Plugin modules are process-scoped, so a new session alone cannot load the update.
433
-
434
- `init` is idempotent. It updates files owned by Sortie-dogs, migrates recognized
428
+ npx sortie-dogs init .
429
+ ```
430
+
431
+ After initialization, fully restart the OpenCode host before resuming a coordinator or opening a
432
+ fresh session. Plugin modules are process-scoped, so a new session alone cannot load the update.
433
+
434
+ `init` is idempotent. It updates files owned by Sortie-dogs, migrates recognized
435
435
  older runtime files, and records the installed version in
436
436
  `.opencode/sortie-dogs.version`. Conflicting or unrecognized files remain
437
437
  untouched and initialization stops safely. User-owned configuration—including
438
438
  `.opencode/sortie-dogs.json`—and standard OpenCode files are preserved.
439
439
 
440
- ## Maintainer releases
441
-
442
- The [release batch guide](docs/release-batch.md) covers fixed-tarball CLI verification,
443
- global application, resumable GitHub publication, and manual npm publication checks.
444
-
445
- ## Safe manual removal
440
+ ## Maintainer releases
441
+
442
+ The [release batch guide](docs/release-batch.md) covers fixed-tarball CLI verification,
443
+ global application, resumable GitHub publication, and manual npm publication checks.
444
+
445
+ ## Safe manual removal
446
446
 
447
447
  There is no supported Sortie-dogs uninstall command. Remove the npm dependency
448
448
  separately, then follow the [safe manual removal guide](docs/uninstall.md) to
@@ -2,5 +2,5 @@
2
2
  * Version of the installable runtime assets. Kept in its own module so the plugin can compare an
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
- export declare const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
5
+ export declare const RUNTIME_ASSET_VERSION = "0.3.86-codegen-proof-v1";
6
6
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
@@ -2,4 +2,4 @@
2
2
  * Version of the installable runtime assets. Kept in its own module so the plugin can compare an
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
- export const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
5
+ export const RUNTIME_ASSET_VERSION = "0.3.86-codegen-proof-v1";
@@ -119,7 +119,7 @@ export function reduceGoalFlight(records) {
119
119
  continue;
120
120
  requireState(instant(event.at), "invalid", "Goal event timestamp is invalid.");
121
121
  if (event.kind === "goal.accepted") {
122
- requireState(state.goal_id === null || state.phase === "terminal", "transition", "An active goal already owns this root.");
122
+ requireState(state.goal_id === null || state.phase === "terminal" || state.phase === "stopped", "transition", "An active goal already owns this root.");
123
123
  requireState(text(event.goal_id) && HASH.test(event.acceptance_fingerprint) && text(event.origin_user_message_id) &&
124
124
  text(event.origin_session_id) && text(event.selected_agent) && event.budget.max_units > 0 &&
125
125
  validAcceptanceContract(event.acceptance_contract), "invalid", "Accepted goal identity is incomplete.");
@@ -221,7 +221,7 @@ export function reduceGoalFlight(records) {
221
221
  event.consumed >= 0 && event.consumed <= event.limit && event.operation_id.length > 0 && event.evidence_key.length > 0, "invalid", "Validation admission is malformed.");
222
222
  if (event.decision === "ALLOW") {
223
223
  requireState(event.scope !== null && event.consumed === state.validation_budget.consumed + 1 &&
224
- (state.validation_budget.limit === null || state.validation_budget.limit === event.limit) &&
224
+ (state.validation_budget.limit === null || event.limit >= state.validation_budget.limit) &&
225
225
  !state.validation_budget.evidence_keys.includes(event.evidence_key) &&
226
226
  !state.validation_budget.reservations.some((entry) => entry.reservation_id === event.reservation_id), "budget", "Validation admission is stale, duplicated, or exhausted.");
227
227
  state = { ...state, validation_budget: { consumed: event.consumed, limit: event.limit,
@@ -2,38 +2,38 @@ import { goalFingerprint } from "./goal-bound.js";
2
2
  /** Shared planner guidance and admission enums; no inferred acceptance or dispatch authority. */
3
3
  export const GOAL_DELIVERY_INTENTS = ["design", "registration", "implementation", "repair", "controlled-change"];
4
4
  export const GOAL_DELIVERY_MODES = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
5
- export const GOAL_DECLARATION_FORMAT = `Declare a goal once, then reference it with goal_declaration_path in the Task prompt.
6
- The referenced JSON may contain shared defaults and a criteria array; the host expands them privately.
7
- An inline ext["sortie-dogs/goal-declaration"] in the registered handoff is also supported.
8
- Existing accepted goals need no repeated declaration; budget-only revisions retain their criteria.
9
- Legacy flat goal_* fields remain supported. No need to rewrite unrelated files or repeat common fields for each criterion.
10
- delivery_intent must be exactly one of: ${GOAL_DELIVERY_INTENTS.join(" | ")}. Never use prose or a boolean.
11
- delivery_mode is a separate optional enum: ${GOAL_DELIVERY_MODES.join(" | ")}.
12
- Use flat key: value lines. Each criterion begins with its own goal_criterion_id: line.
13
- For legacy flat input each criterion has a goal_criterion_id line. Prefer shared JSON defaults for multiple criteria.
14
- Replace every placeholder with the accepted task's actual value; preserve its acceptance and validation:
15
- goal_acceptance_fingerprint: <sha256: followed by exactly 64 lowercase hexadecimal characters>
16
- delivery_intent: implementation
17
- delivery_mode: mvp-first
18
- usable_path_established: false
19
- controlled_change: false
20
- goal_budget_units: <accepted positive integer>
21
- goal_criterion_id: <stable criterion id>
22
- goal_target: <requested behavior>
23
- goal_entrypoint: <real entrypoint>
24
- goal_workload: <requested workload>
25
- goal_oracle_coverage: ["<actual oracle>"]
26
- goal_build_boundary: <included | excluded | not-applicable>
27
- goal_source: <fixed source label>
28
- goal_candidate: <fixed candidate label>
29
- goal_source_binding: current-protected
30
- goal_candidate_binding: current-protected
31
- goal_fixture: <actual fixture identity>
32
- goal_proof_scope: requested-full
33
- goal_expected_outcome: pass
34
- goal_validation_command: <exact operation manifest validation command>
35
- The example's implementation/mvp-first selections apply only to an implementation without its usable path;
36
- select other enum values only from the accepted request. A declaration rejection launches no child.
5
+ export const GOAL_DECLARATION_FORMAT = `Declare a goal once, then reference it with goal_declaration_path in the Task prompt.
6
+ The referenced JSON may contain shared defaults and a criteria array; the host expands them privately.
7
+ An inline ext["sortie-dogs/goal-declaration"] in the registered handoff is also supported.
8
+ Existing accepted goals need no repeated declaration; budget-only revisions retain their criteria.
9
+ Legacy flat goal_* fields remain supported. No need to rewrite unrelated files or repeat common fields for each criterion.
10
+ delivery_intent must be exactly one of: ${GOAL_DELIVERY_INTENTS.join(" | ")}. Never use prose or a boolean.
11
+ delivery_mode is a separate optional enum: ${GOAL_DELIVERY_MODES.join(" | ")}.
12
+ Use flat key: value lines. Each criterion begins with its own goal_criterion_id: line.
13
+ For legacy flat input each criterion has a goal_criterion_id line. Prefer shared JSON defaults for multiple criteria.
14
+ Replace every placeholder with the accepted task's actual value; preserve its acceptance and validation:
15
+ goal_acceptance_fingerprint: <sha256: followed by exactly 64 lowercase hexadecimal characters>
16
+ delivery_intent: implementation
17
+ delivery_mode: mvp-first
18
+ usable_path_established: false
19
+ controlled_change: false
20
+ goal_budget_units: <accepted positive integer>
21
+ goal_criterion_id: <stable criterion id>
22
+ goal_target: <requested behavior>
23
+ goal_entrypoint: <real entrypoint>
24
+ goal_workload: <requested workload>
25
+ goal_oracle_coverage: ["<actual oracle>"]
26
+ goal_build_boundary: <included | excluded | not-applicable>
27
+ goal_source: <fixed source label>
28
+ goal_candidate: <fixed candidate label>
29
+ goal_source_binding: current-protected
30
+ goal_candidate_binding: current-protected
31
+ goal_fixture: <actual fixture identity>
32
+ goal_proof_scope: requested-full
33
+ goal_expected_outcome: pass
34
+ goal_validation_command: <exact operation manifest validation command>
35
+ The example's implementation/mvp-first selections apply only to an implementation without its usable path;
36
+ select other enum values only from the accepted request. A declaration rejection launches no child.
37
37
  Repair the Task prompt's named fields, not unrelated files; retry only with the corrected declaration.`;
38
38
  /** Normalize a shared declaration without guessing acceptance, coverage, delivery, or validation. */
39
39
  export function expandGoalDeclaration(value) {
@@ -824,6 +824,16 @@ export class RunFlightLedger {
824
824
  throw new RunFlightLedgerError("conflict", "Ledger lock remained busy.");
825
825
  try {
826
826
  const records = await this.#readGoalRecords();
827
+ if (event.kind === "goal.accepted") {
828
+ const prior = records.find(({ event: stored }) => stored.kind === "goal.accepted" &&
829
+ stored.origin_user_message_id === event.origin_user_message_id);
830
+ if (prior !== undefined) {
831
+ if (recordHash(1, null, { ...event, at: prior.event.at }) !== recordHash(1, null, prior.event)) {
832
+ throw new RunFlightLedgerError("transition", "Accepted goal conflicts with its durable user message identity.");
833
+ }
834
+ return reduceGoalFlight(records);
835
+ }
836
+ }
827
837
  if (event.kind === "goal.reported" && records.some(({ event: stored }) => stored.kind === "goal.reported" &&
828
838
  stored.goal_id === event.goal_id && stored.report?.terminal_key === event.report.terminal_key)) {
829
839
  return reduceGoalFlight(records);