sortie-dogs 0.9.12 → 0.10.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.
Files changed (66) hide show
  1. package/README.md +161 -70
  2. package/dist/asset-version.d.ts +2 -1
  3. package/dist/asset-version.js +1 -0
  4. package/dist/cli/main.js +12 -6
  5. package/dist/core/consultation.d.ts +1 -0
  6. package/dist/core/consultation.js +1 -0
  7. package/dist/core/contract-limits.d.ts +8 -0
  8. package/dist/core/contract-limits.js +2 -0
  9. package/dist/core/git-managed-state.d.ts +8 -0
  10. package/dist/core/git-managed-state.js +101 -0
  11. package/dist/core/goal-bound.d.ts +21 -0
  12. package/dist/core/goal-bound.js +44 -4
  13. package/dist/core/goal-declaration-format.d.ts +4 -0
  14. package/dist/core/goal-declaration-format.js +24 -9
  15. package/dist/core/initialize.d.ts +3 -3
  16. package/dist/core/initialize.js +51 -13
  17. package/dist/core/observed-goal-evidence.d.ts +16 -0
  18. package/dist/core/observed-goal-evidence.js +27 -0
  19. package/dist/core/operator-contract-repair.d.ts +19 -0
  20. package/dist/core/operator-contract-repair.js +8 -0
  21. package/dist/core/operator-proposal.d.ts +127 -0
  22. package/dist/core/operator-proposal.js +516 -0
  23. package/dist/core/operator-runtime.d.ts +312 -0
  24. package/dist/core/operator-runtime.js +1914 -0
  25. package/dist/core/refusal-codes.d.ts +4 -0
  26. package/dist/core/refusal-codes.js +26 -0
  27. package/dist/core/run-flight-ledger.d.ts +5 -1
  28. package/dist/core/run-flight-ledger.js +30 -1
  29. package/dist/core/runtime-profile.d.ts +29 -0
  30. package/dist/core/runtime-profile.js +50 -0
  31. package/dist/core/scout-contract.d.ts +2 -0
  32. package/dist/core/scout-contract.js +2 -0
  33. package/dist/core/unit-result-classification.d.ts +16 -0
  34. package/dist/core/unit-result-classification.js +11 -0
  35. package/dist/core/validate-schema.d.ts +1 -0
  36. package/dist/core/validate-schema.js +8 -6
  37. package/dist/core/validation-budget.d.ts +1 -1
  38. package/dist/core/worktree-commit-artifact.js +326 -326
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +3 -0
  41. package/dist/plugin/continuation.d.ts +3 -1
  42. package/dist/plugin/continuation.js +27 -9
  43. package/dist/plugin/fast-lane.d.ts +11 -0
  44. package/dist/plugin/fast-lane.js +83 -10
  45. package/dist/plugin/index.d.ts +3 -0
  46. package/dist/plugin/index.js +715 -83
  47. package/dist/plugin/legacy.d.ts +2 -0
  48. package/dist/plugin/legacy.js +2 -0
  49. package/dist/plugin/model-cost.d.ts +28 -0
  50. package/dist/plugin/model-cost.js +54 -0
  51. package/dist/plugin/opencode.d.ts +1 -1
  52. package/dist/plugin/opencode.js +1 -1
  53. package/dist/plugin/profiled.d.ts +5 -0
  54. package/dist/plugin/profiled.js +1213 -0
  55. package/dist/plugin/receipt-presentation.d.ts +5 -0
  56. package/dist/plugin/receipt-presentation.js +13 -0
  57. package/dist/plugin/run-metrics.js +31 -3
  58. package/dist/plugin/runtime-bridge.d.ts +99 -0
  59. package/dist/plugin/runtime-bridge.js +1 -0
  60. package/dist/plugin/sortie-debrief.d.ts +23 -0
  61. package/dist/plugin/sortie-debrief.js +54 -8
  62. package/dist/reflection/policy.js +82 -82
  63. package/dist/runtime-assets-v010.d.ts +4 -0
  64. package/dist/runtime-assets-v010.js +343 -0
  65. package/dist/runtime-assets.js +1465 -1465
  66. package/package.json +9 -4
package/README.md CHANGED
@@ -1,87 +1,172 @@
1
1
  # Sortie-dogs
2
2
 
3
- **Add a bounded, cost-aware execution loop to OpenCode without taking OpenCode over.**
3
+ **A goal-preserving, adaptive execution harness for OpenCode
4
+ that optimizes cost, time, and proof without taking your setup over.**
4
5
 
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.
6
+ Use OpenCode normally. Invoke Sortie only when you want
7
+ scoped implementation, validation, review, and model routing.
8
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.
9
+ ### Four design pillars
11
10
 
12
- [![npm](https://img.shields.io/npm/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
13
- [![license](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
14
- [![Node.js](https://img.shields.io/node/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
11
+ - **Goal invariance** — Accepted outcomes and proof requirements survive delegation, continuation, and remediation. A child agent cannot silently weaken the job to make it easier to finish.
12
+ - **Adaptive execution** — Small work stays small. Parallel workers, stronger models, and independent review are added only when task shape and risk justify them.
13
+ - **Coexistence and portability** — Sortie activates only when invited, preserves normal OpenCode agents and settings, and keeps project-local setup as the default.
14
+ - **Cost, time, and proof** — The objective is not maximum agent count. It is the lowest practical cost and effort for a verified outcome, with explicit evidence when work does or does not pass.
15
15
 
16
- ![Sortie-dogs coordinating a bounded implementation workflow](https://raw.githubusercontent.com/zufall-upon/Sortie-dogs/main/docs/assets/sortie-workflow.gif)
16
+ ### Direction for v0.10.x
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
+ The v0.10.x line is being developed around an **Astra operator / Terra dogs** split. Astra is the
19
+ top-level decision authority that protects the accepted goal, quality bar, escalation decisions,
20
+ and final acceptance. Because Astra is expensive, it should do only the small amount of work that
21
+ requires that level of judgment. Terra-based dogs handle most bounded planning, coordination, and
22
+ execution. The intended result is Astra-level judgment with Terra-level operating cost.
23
+
24
+ This is an architectural direction under active validation, not a demonstrated benchmark result.
25
+ Goal and quality authority remain centralized; implementation volume does not.
26
+
27
+ ## Try it
20
28
 
21
29
  Requirements: Node.js 22.6 or newer, npm, and OpenCode.
30
+ Start in your project directory. This block covers all four steps;
31
+ the JSON belongs in the configuration file, and `/sortie` runs inside OpenCode.
32
+ If the configuration already exists, add `sortie-dogs` to its `plugin` array
33
+ while keeping existing entries and settings.
22
34
 
23
- Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [テスト実行](docs/testing.md) · [CLI testing](docs/cli-testing.md)
35
+ ```text
36
+ 1. Install — run in your terminal
37
+ npm install --save-dev sortie-dogs
38
+ npx sortie-dogs init .
39
+
40
+ 2. Add plugin — save or merge into .opencode/opencode.json
41
+ {
42
+ "plugin": ["sortie-dogs"]
43
+ }
44
+
45
+ 3. Restart OpenCode
24
46
 
25
- Release: [v0.9.12](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.12)
47
+ 4. Start a task — enter in OpenCode
48
+ /sortie <task>
49
+ ```
50
+
51
+ Project-local setup is recommended. `init` installs runtime assets;
52
+ the plugin entry enables the plugin, including model routing.
53
+ See [configuration details](#configuration) for model selection and other setup options.
26
54
 
27
- ## Provisional quality–cost position
55
+ > **v0.10.x migration:** Give workflow instructions to **`dog-operator`**, not
56
+ > `dog-coordinator`. `dog-operator` is the user-facing authority in v0.10.x;
57
+ > coordinator roles are internal delegates and are not the task entry point.
28
58
 
29
- **Reference values, not a successful benchmark claim.** Quality and end-to-end completion
30
- problems remain. The latest completed qualification attempt ended at `IN_PROGRESS`, so its official
31
- verifier was not run. Further benchmarks are frozen while completion defects are repaired.
59
+ > **Project status: Beta.** v0.10.x is under active stabilization. Runtime
60
+ > behavior, configuration, and runtime assets may still change before 1.0.
32
61
 
33
- The last complete measured **Bare OpenCode vs Sortie** pair below used one frozen task,
34
- `datacurve/anko-typed-variable-bindings`, on 2026-09-10. It used Sortie **v0.9.5**, not the
35
- current release. Both candidates failed the official verifier.
62
+ [![npm](https://img.shields.io/npm/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
63
+ [![license](https://img.shields.io/npm/l/sortie-dogs)](LICENSE)
64
+ [![Node.js](https://img.shields.io/node/v/sortie-dogs)](https://www.npmjs.com/package/sortie-dogs)
36
65
 
37
- | Metric · one task, one trial per arm | Bare OpenCode | Sortie v0.9.5 |
38
- | --- | ---: | ---: |
39
- | Verified PASS | 0/1 | 0/1 |
40
- | Task-check completion · F2P | 55.6% · 5/9 | 88.9% · 8/9 |
41
- | Retained checks · P2P | 94/94 | 93/94 |
42
- | Estimated API-equivalent total cost | $5.42 | $1.46 |
43
- | Median agent wall · n=1 | 28.7 min | 10.0 min |
44
- | Premium-model token share · Sol | 100% | 20.1% |
66
+ ![Sortie-dogs coordinating a bounded implementation workflow](https://raw.githubusercontent.com/zufall-upon/Sortie-dogs/main/docs/assets/sortie-workflow.gif)
45
67
 
46
- Later Sortie-only evidence is weaker: the v0.9.9 recovery candidate passed **5/9** task
47
- checks with **0/1 Verified PASS**; `0.9.11-bench.2` did not reach a gradeable completion.
48
- Those attempts are not pooled into the historical pair above.
68
+ Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
69
+ bounded implementation, canonical validation, and evidence-backed completion.
49
70
 
50
- ![Historical quality–cost reference: Bare at $5.42 and 55.6% task-check completion; Sortie v0.9.5 at $1.46 and 88.9%. Neither achieved Verified PASS.](docs/assets/quality-cost-reference.svg)
71
+ Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [テスト実行](docs/testing.md) · [CLI testing](docs/cli-testing.md)
51
72
 
52
- The goal is **higher OpenCode task success with selective use of premium models**.
53
- These reference observations do not yet establish that success-rate claim: Sortie missed
54
- one task check and regressed one retained check. Codex, Pi, and Oh My OpenCode belong to
55
- separate methodologies and are not assigned comparable positions on this chart.
73
+ Release: [v0.10.0](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.10.0)
74
+
75
+ ## Latest local benchmark case study
76
+
77
+ **Completion-filtered reference values, not a successful benchmark or leaderboard claim.**
78
+ On 2026-09-14, Bare OpenCode and Sortie-dogs v0.9.12 each collected three completed runs of the
79
+ same frozen task, `datacurve/anko-typed-variable-bindings`. Bare needed three attempts; Sortie
80
+ needed five because two attempts returned `INTERRUPTED`. These were separate local trial batches,
81
+ not three matched pairs. Docker and Runta were intentionally unused.
82
+
83
+ Run configuration was fixed per product configuration:
84
+
85
+ - **Bare OpenCode:** standard `build` agent, `openai/gpt-5.6-sol` / `high`, with no Sortie plugin
86
+ or Sortie runtime assets in the effective configuration.
87
+ - **Sortie v0.9.12:** `dog-coordinator` on `openai/gpt-5.6-terra` / `high`; observed implementation
88
+ children on `openai/gpt-5.6-sol` / `medium`, with the pinned Sortie package and runtime assets.
89
+ No Luna, Astra, or Opus messages were observed in these trials.
90
+
91
+ | Metric · one frozen task | Bare OpenCode | Sortie v0.9.12 | Sortie v0.10.0 clean qualification |
92
+ | --- | ---: | ---: | ---: |
93
+ | Attempts needed | 3 | 5 | 1 |
94
+ | Completed runs compared | 3 | 3 | 1 |
95
+ | Verified PASS | 0/3 | 0/3 | 0/1 |
96
+ | Task checks · F2P | 11.1% · 3/27 | 85.2% · 23/27 | 88.9% · 8/9 |
97
+ | Retained checks · P2P | 282/282 | 282/282 | 94/94 |
98
+ | Median agent wall | 24.5 min | 25.7 min | 29.9 min · n=1 |
99
+ | Median model steps | 43 | 39 | 33 · CLI stream only |
100
+ | Implementation child sessions · total | 0 | 13 | 4 |
101
+ | Estimated API-equivalent cost · median completed run | $3.53 | $2.85 | **$3.94** · n=1 |
102
+ | Estimated cost · completed runs | $10.69 | $9.74 | **$3.94** |
103
+ | Additional interrupted-attempt cost | $0 | $6.20 | $0 |
104
+ | Total cost to acquire completed runs | $10.69 | $15.94 | **$3.94** |
105
+
106
+ All three Bare runs passed 1/9 task checks. The three completed Sortie runs passed 7/9, 8/9,
107
+ and 8/9. Every compared candidate retained 94/94 prior checks, but every official verifier still
108
+ returned reward 0. The two interrupted Sortie attempts are excluded from completed-run quality,
109
+ time, and cost aggregates; their attempt count and estimated cost remain visible above.
110
+
111
+ The v0.10 value is a standalone qualification-only reference, not a fourth matched run or a
112
+ FrontierHarness leaderboard result. It used no fresh Bare control and a localized Docker-free
113
+ verifier. Its estimated API-equivalent cost is **$3.94**: $3.26 from Sol and $0.68 from Terra,
114
+ calculated from the root and descendant session tokens with 100% pricing coverage. A preceding
115
+ debug run returned reward 1 (F2P 9/9, P2P 94/94), so that pass is not presented as reproducible.
116
+
117
+ ![Latest local case study: Bare completed 11.1 percent of task checks at a median estimated API-equivalent cost of $3.53; Sortie completed 85.2 percent at $2.85. Sortie needed five attempts and $15.94 to collect three completed runs. Neither configuration achieved a Verified PASS.](docs/assets/quality-cost-reference.svg)
118
+
119
+ Cost uses exported root and child session tokens, grouped by the model that produced each message,
120
+ with a fixed standard short-context rate schedule. Completed-run cost shows execution efficiency;
121
+ total acquisition cost includes the two interrupted Sortie attempts and shows reliability overhead.
122
+ These are API-equivalent estimates, not invoices.
123
+
124
+ The product objective is **more verified outcomes per unit of cost and time without weakening the
125
+ accepted goal**. This small, single-task local case study does not establish that claim, isolate
126
+ orchestration from model quality, or establish a cost advantage. Codex, Pi, and Oh My OpenCode use
127
+ separate methodologies and are not assigned quantitative positions from these observations.
56
128
 
57
129
  [Definitions, frozen inputs, current failure status, and limitations](docs/benchmark-reference.md)
58
130
  · [Machine-readable reference values](docs/benchmarks/provisional-reference.json)
59
131
 
60
- ## Why Sortie-dogs?
132
+ ## How the design is enforced
61
133
 
62
- ### Invisible until invited
134
+ The four pillars above become operational constraints rather than a second set
135
+ of product promises:
63
136
 
64
- Use normal OpenCode normally. Sortie activates only for `/sortie` or
65
- `dog-coordinator`; it does not disable or replace OpenCode's standard agents.
137
+ ### 1. Keep the goal invariant
66
138
 
67
- ### Spend strong models only where they matter
139
+ Sortie turns the requested outcome into explicit acceptance criteria and carries
140
+ them through planning, delegation, continuation, validation, and remediation.
141
+ Workers receive bounded units, but unit boundaries do not redefine success. A
142
+ child result, local process limit, or convenient partial implementation cannot
143
+ silently remove an unmet criterion. Only an explicit user-authorized revision
144
+ changes the accepted goal.
68
145
 
69
- Lower-cost models handle bounded retrieval and parallel volume work. Stronger
70
- models are reserved for implementation, escalation, and independent review.
146
+ ### 2. Adapt execution to the work
71
147
 
72
- ### Return with proof
148
+ Small changes can use one worker and targeted validation without paying for Scout,
149
+ parallel coordination, independent review, or a full-suite run. Larger work can be
150
+ split into bounded units; safely independent units may use a Luna fabric DAG.
151
+ Higher-risk candidates add stronger implementation or independent review. The
152
+ harness expands because the task requires it, not because more agents look better.
73
153
 
74
- Writes stay scoped, and completion requires validation evidence. Every completed
75
- run can return a concise Speed / Cost / Proof debrief.
154
+ ### 3. Coexist with OpenCode
155
+
156
+ Use normal OpenCode normally. Sortie activates only for `/sortie` or
157
+ `dog-coordinator`; it does not disable or replace standard agents. Project-local
158
+ installation is the default, existing settings remain authoritative, and unknown
159
+ user-owned runtime files are preserved. The same package can move with a project
160
+ without requiring users to surrender their global OpenCode environment.
76
161
 
77
- ### Use only as much harness as the task needs
162
+ ### 4. Optimize cost, time, and proof together
78
163
 
79
- Small changes can skip Scout and independent review when one worker and targeted
80
- validation are sufficient. Larger work can be decomposed into multiple units;
81
- units that are safely independent can use a Luna fabric DAG for bounded parallel
82
- execution. Higher-risk candidates add independent review, while full-suite and
83
- package verification are reserved for release work. Not every task pays the
84
- cost of the heaviest workflow.
164
+ Lower-cost models handle bounded retrieval and parallel volume work; stronger
165
+ models are reserved for implementation, escalation, and independent review where
166
+ their capability can change the outcome. Writes remain scoped, and completion
167
+ requires validation evidence rather than agent confidence. The returned Speed /
168
+ Cost / Proof debrief makes incomplete and failed outcomes visible too. Agent count
169
+ is an implementation detail, not the optimization target.
85
170
 
86
171
  ## Designed to coexist with OpenCode
87
172
 
@@ -99,17 +184,11 @@ Sortie-dogs adds a workflow to your existing setup rather than replacing it.
99
184
 
100
185
  Use OpenCode normally. Invoke the pack only when you want it.
101
186
 
102
- ## Quick start
187
+ ## Configuration
103
188
 
104
- Install the public npm package in the project and generate the project-local
105
- OpenCode runtime files:
106
-
107
- ```sh
108
- npm install --save-dev sortie-dogs
109
- npx sortie-dogs init .
110
- ```
111
-
112
- This is the recommended setup. Runtime assets stay project-local.
189
+ The [Try it](#try-it) steps above are the recommended project-local installation.
190
+ Runtime assets stay with the project. This section covers plugin behavior, model
191
+ selection, and alternative loading after installation.
113
192
 
114
193
  Installing the runtime assets does not load the plugin, and without the plugin
115
194
  every role runs on whichever model the caller happened to use. Add the package
@@ -130,6 +209,10 @@ as `.opencode/sortie-dogs.json`:
130
209
 
131
210
  ```json
132
211
  {
212
+ "dedicatedWorkerModel": {
213
+ "model": "openai/gpt-5.6-sol",
214
+ "variant": "medium"
215
+ },
133
216
  "modelRouting": {
134
217
  "dog-coordinator": {
135
218
  "preferred": { "model": "provider/model" }
@@ -292,18 +375,18 @@ dog-coordinator: completion evidence accepted
292
375
 
293
376
  ## The workflow
294
377
 
295
- 1. **Brief and plan** — `dog-coordinator` turns the request into acceptance
296
- criteria, a write manifest, and validation requirements.
378
+ 1. **Freeze the goal and plan** — `dog-coordinator` turns the request into invariant
379
+ acceptance criteria, a write manifest, and validation requirements.
297
380
  2. **Optional scout** — one bounded, read-only investigation runs only for a
298
381
  concrete pre-worker evidence gap.
299
- 3. **Dedicated worker** — the dedicated worker implements only the approved
300
- manifest and also owns scoped remediation or blocker resolution.
382
+ 3. **Adaptive execution** — the coordinator selects one worker or bounded parallel
383
+ units according to task shape; workers implement only their approved manifests.
301
384
  4. **Canonical validation** — the declared test or build command must produce
302
385
  acceptable evidence.
303
386
  5. **Risk-based review** — high-risk candidates receive independent review;
304
387
  low-risk candidates can skip that extra pass after validation.
305
- 6. **Coordinator completion** — only the coordinator closes the loop and owns
306
- any commit after manifest, validation, review, and evidence gates pass.
388
+ 6. **Goal-level completion** — only the coordinator closes the loop after every
389
+ accepted criterion has manifest, validation, review, and evidence coverage.
307
390
  7. **Bounded continuation** — restart recovery and compaction handoffs preserve
308
391
  progress; repeated batches remain bounded rather than becoming endless
309
392
  delegation.
@@ -413,6 +496,10 @@ model you can actually serve.
413
496
 
414
497
  ```json
415
498
  {
499
+ "dedicatedWorkerModel": {
500
+ "model": "openai/gpt-5.6-sol",
501
+ "variant": "medium"
502
+ },
416
503
  "modelRouting": {
417
504
  "dog-coordinator": {
418
505
  "preferred": { "model": "openai/gpt-5.6-luna", "variant": "max" }
@@ -438,6 +525,10 @@ model you can actually serve.
438
525
  }
439
526
  ```
440
527
 
528
+ `dog-worker` intentionally has no `modelRouting` entry. It uses
529
+ `dedicatedWorkerModel`, shared with the other stable serial implementation roles.
530
+ `dog-luna-worker` remains a separate fixed fabric route.
531
+
441
532
  Save project configuration as `.opencode/sortie-dogs.json`. `modelCatalog`
442
533
  declares provider models and named variants that are actually available;
443
534
  Sortie-dogs does not invent, probe, or translate variants. The built-in catalog
@@ -3,4 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION;
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref1-proposal1-review-remediation1-surface3";
7
+ export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
@@ -3,3 +3,4 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.10.0-v0912-language4-cost-rpt10-compaction-ref1-proposal1-review-remediation1-surface3";
package/dist/cli/main.js CHANGED
@@ -29,8 +29,9 @@ const USAGE = `Usage: sortie-dogs lint <handoff.json> [<handoff.json> ...]
29
29
  [--changed-paths-from <file|->]
30
30
  [--changed-path <path> ...]
31
31
  [--format text|json] [--quiet] [--strict]`;
32
- const INIT_USAGE = `Usage: sortie-dogs init [project-root]
33
- sortie-dogs init --global`;
32
+ const INIT_USAGE = `Usage: sortie-dogs init [project-root] [--profile stable|v010]
33
+ sortie-dogs init --global [--profile stable|v010]
34
+ This beta package defaults to the v010 profile.`;
34
35
  class InputFailure extends Error {
35
36
  safeMessage;
36
37
  constructor(safeMessage) {
@@ -229,16 +230,21 @@ export async function run(argv) {
229
230
  process.stdout.write(`${INIT_USAGE}\n`);
230
231
  return 0;
231
232
  }
232
- const global = argv[1] === "--global";
233
- if (argv.length > 2 || (argv[1]?.startsWith("-") === true && !global)) {
233
+ const initArgs = [...argv.slice(1)];
234
+ const profileIndex = initArgs.indexOf("--profile");
235
+ const profile = profileIndex < 0 ? "v010" : initArgs[profileIndex + 1];
236
+ if (profileIndex >= 0)
237
+ initArgs.splice(profileIndex, 2);
238
+ const global = initArgs[0] === "--global";
239
+ if ((profile !== "stable" && profile !== "v010") || initArgs.length > 1 || (initArgs[0]?.startsWith("-") === true && !global)) {
234
240
  process.stderr.write(`${INIT_USAGE}\n`);
235
241
  return 2;
236
242
  }
237
243
  try {
238
244
  const target = global ? await initializer.resolveGlobalConfigRoot() : undefined;
239
245
  const initialized = global
240
- ? await initializer.initializeGlobal(target)
241
- : await initializer.initializeProject(argv[1]);
246
+ ? await initializer.initializeGlobal(target, profile)
247
+ : await initializer.initializeProject(initArgs[0], profile);
242
248
  if (global) {
243
249
  process.stdout.write(initialized.status === "installed"
244
250
  ? `Initialized Sortie-dogs ${initialized.version} globally at ${target}.\n`
@@ -1,4 +1,5 @@
1
1
  export declare const CONSULTATION_CAPABILITIES: readonly ["strategy", "sourceReview"];
2
+ export declare const SOURCE_REVIEW_PHASES: readonly ["initial", "verification", "final"];
2
3
  export type ConsultationCapability = typeof CONSULTATION_CAPABILITIES[number];
3
4
  export declare const CONSULTATION_ROLE_POLICY: Readonly<{
4
5
  readonly strategy: "dog-advisor";
@@ -1,4 +1,5 @@
1
1
  export const CONSULTATION_CAPABILITIES = ["strategy", "sourceReview"];
2
+ export const SOURCE_REVIEW_PHASES = ["initial", "verification", "final"];
2
3
  export const CONSULTATION_ROLE_POLICY = Object.freeze({
3
4
  strategy: "dog-advisor",
4
5
  sourceReview: "dog-reviewer",
@@ -0,0 +1,8 @@
1
+ /** Common text bounds shared by handoff, manifest and goal evidence validation. */
2
+ export declare const CONTRACT_TEXT_LIMITS: Readonly<{
3
+ title: 160;
4
+ objective: 2000;
5
+ statement: 1000;
6
+ command: 1000;
7
+ path: 512;
8
+ }>;
@@ -0,0 +1,2 @@
1
+ /** Common text bounds shared by handoff, manifest and goal evidence validation. */
2
+ export const CONTRACT_TEXT_LIMITS = Object.freeze({ title: 160, objective: 2000, statement: 1000, command: 1000, path: 512 });
@@ -0,0 +1,8 @@
1
+ import { type RuntimeProfile } from "./runtime-profile.js";
2
+ /**
3
+ * Keep only the plugin-owned profile state invisible to normal Git status.
4
+ * The repository-local exclude file is Git metadata, not a source-tree policy.
5
+ * Callers invoke this only for an explicitly requested Git workflow; an
6
+ * enclosing repository is not the source root and therefore fails closed.
7
+ */
8
+ export declare function ensureGitManagedStateExcluded(sourceRoot: string, profile: RuntimeProfile, executable?: string): Promise<boolean>;
@@ -0,0 +1,101 @@
1
+ import { execFile } from "node:child_process";
2
+ import { open, lstat, mkdir, realpath } from "node:fs/promises";
3
+ import { dirname, join, relative, resolve, sep } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { RUNTIME_PROFILES } from "./runtime-profile.js";
6
+ const samePath = (left, right) => process.platform === "win32"
7
+ ? resolve(left).toLowerCase() === resolve(right).toLowerCase()
8
+ : resolve(left) === resolve(right);
9
+ function inside(root, candidate) {
10
+ const path = relative(root, candidate);
11
+ return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
12
+ }
13
+ async function git(root, executable, args) {
14
+ try {
15
+ return (await promisify(execFile)(executable, [...args], {
16
+ cwd: root, encoding: "utf8", timeout: 30_000, windowsHide: true, maxBuffer: 1024 * 1024,
17
+ })).stdout.trim();
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ async function safeDirectory(path) {
24
+ const metadata = await lstat(path);
25
+ if (!metadata.isDirectory() || metadata.isSymbolicLink() || !samePath(await realpath(path), path)) {
26
+ throw new Error("operator-git-managed-state-path-unsafe");
27
+ }
28
+ }
29
+ /**
30
+ * Keep only the plugin-owned profile state invisible to normal Git status.
31
+ * The repository-local exclude file is Git metadata, not a source-tree policy.
32
+ * Callers invoke this only for an explicitly requested Git workflow; an
33
+ * enclosing repository is not the source root and therefore fails closed.
34
+ */
35
+ export async function ensureGitManagedStateExcluded(sourceRoot, profile, executable = "git") {
36
+ const root = resolve(sourceRoot);
37
+ const known = Object.values(RUNTIME_PROFILES).some(candidate => candidate.id === profile.id && candidate.stateDirectory === profile.stateDirectory);
38
+ if (!known || !/^\.[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(profile.stateDirectory)) {
39
+ throw new Error("operator-git-managed-state-profile-unknown");
40
+ }
41
+ const top = await git(root, executable, ["rev-parse", "--show-toplevel"]);
42
+ if (top === undefined)
43
+ return false;
44
+ if (!samePath(top, root))
45
+ throw new Error("operator-git-managed-state-root-mismatch");
46
+ const commonValue = await git(root, executable, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
47
+ const excludeValue = await git(root, executable, ["rev-parse", "--path-format=absolute", "--git-path", "info/exclude"]);
48
+ if (commonValue === undefined || excludeValue === undefined) {
49
+ throw new Error("operator-git-managed-state-metadata-unavailable");
50
+ }
51
+ const common = resolve(root, commonValue);
52
+ const exclude = resolve(root, excludeValue);
53
+ if (!inside(common, exclude) || !samePath(exclude, join(common, "info", "exclude"))) {
54
+ throw new Error("operator-git-managed-state-path-unsafe");
55
+ }
56
+ await safeDirectory(common);
57
+ const info = dirname(exclude);
58
+ try {
59
+ await mkdir(info);
60
+ }
61
+ catch (error) {
62
+ if (error.code !== "EEXIST")
63
+ throw error;
64
+ }
65
+ await safeDirectory(info);
66
+ const pattern = `/${profile.stateDirectory}/`;
67
+ try {
68
+ const existing = await lstat(exclude);
69
+ if (!existing.isFile() || existing.isSymbolicLink())
70
+ throw new Error("operator-git-managed-state-path-unsafe");
71
+ }
72
+ catch (error) {
73
+ if (error.code !== "ENOENT")
74
+ throw error;
75
+ }
76
+ let handle;
77
+ try {
78
+ handle = await open(exclude, "a+");
79
+ }
80
+ catch (error) {
81
+ throw new Error("operator-git-managed-state-exclude-open-failed", { cause: error });
82
+ }
83
+ try {
84
+ const pathMetadata = await lstat(exclude);
85
+ const fileMetadata = await handle.stat();
86
+ if (!pathMetadata.isFile() || pathMetadata.isSymbolicLink() || !fileMetadata.isFile() ||
87
+ !samePath(await realpath(exclude), exclude)) {
88
+ throw new Error("operator-git-managed-state-path-unsafe");
89
+ }
90
+ const source = await handle.readFile();
91
+ if (source.toString("utf8").split(/\r?\n/u).includes(pattern))
92
+ return true;
93
+ const newline = source.includes(Buffer.from("\r\n")) ? "\r\n" : "\n";
94
+ const prefix = source.length === 0 || source.at(-1) === 0x0a ? "" : newline;
95
+ await handle.writeFile(`${prefix}${pattern}${newline}`);
96
+ return true;
97
+ }
98
+ finally {
99
+ await handle.close();
100
+ }
101
+ }
@@ -83,6 +83,20 @@ export interface GoalTerminalReceipt {
83
83
  interface GoalEventBase {
84
84
  readonly at: string;
85
85
  }
86
+ export interface GoalValidationRetryAuthorization {
87
+ readonly authority: "operator-repair-validation-retry";
88
+ readonly operator_run_id: string;
89
+ readonly unit_id: string;
90
+ readonly operator_generation: number;
91
+ readonly plan_hash: string;
92
+ readonly control_hash: string;
93
+ readonly goal_fingerprint: string;
94
+ readonly repair_fingerprint: string;
95
+ }
96
+ export interface GoalValidationReopen extends GoalValidationRetryAuthorization {
97
+ readonly prior_reservation_id: string;
98
+ readonly prior_operation_id: string;
99
+ }
86
100
  export type GoalFlightEvent = (GoalEventBase & {
87
101
  readonly kind: "goal.accepted";
88
102
  readonly goal_id: string;
@@ -151,6 +165,12 @@ export type GoalFlightEvent = (GoalEventBase & {
151
165
  readonly evidence: readonly GoalEvidence[];
152
166
  readonly elapsed_ms: number | null;
153
167
  readonly cost_usd: number | null;
168
+ }) | (GoalEventBase & {
169
+ readonly kind: "unit.evidence-reconciled";
170
+ readonly goal_id: string;
171
+ readonly unit_id: string;
172
+ readonly previous_receipt_id: string;
173
+ readonly evidence: readonly GoalEvidence[];
154
174
  }) | (GoalEventBase & {
155
175
  readonly kind: "validation.admission";
156
176
  readonly goal_id: string;
@@ -162,6 +182,7 @@ export type GoalFlightEvent = (GoalEventBase & {
162
182
  readonly reason: string;
163
183
  readonly consumed: number;
164
184
  readonly limit: number;
185
+ readonly reopen?: GoalValidationReopen;
165
186
  }) | (GoalEventBase & {
166
187
  readonly kind: "validation.settled";
167
188
  readonly goal_id: string;