sortie-dogs 0.9.11 → 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.
- package/README.md +174 -62
- package/dist/asset-version.d.ts +2 -1
- package/dist/asset-version.js +1 -0
- package/dist/cli/main.js +12 -6
- package/dist/core/consultation.d.ts +1 -0
- package/dist/core/consultation.js +1 -0
- package/dist/core/contract-limits.d.ts +8 -0
- package/dist/core/contract-limits.js +2 -0
- package/dist/core/git-managed-state.d.ts +8 -0
- package/dist/core/git-managed-state.js +101 -0
- package/dist/core/goal-bound.d.ts +21 -0
- package/dist/core/goal-bound.js +44 -4
- package/dist/core/goal-declaration-format.d.ts +4 -0
- package/dist/core/goal-declaration-format.js +24 -9
- package/dist/core/initialize.d.ts +3 -3
- package/dist/core/initialize.js +51 -13
- package/dist/core/observed-goal-evidence.d.ts +16 -0
- package/dist/core/observed-goal-evidence.js +27 -0
- package/dist/core/operator-contract-repair.d.ts +19 -0
- package/dist/core/operator-contract-repair.js +8 -0
- package/dist/core/operator-proposal.d.ts +127 -0
- package/dist/core/operator-proposal.js +516 -0
- package/dist/core/operator-runtime.d.ts +312 -0
- package/dist/core/operator-runtime.js +1914 -0
- package/dist/core/refusal-codes.d.ts +4 -0
- package/dist/core/refusal-codes.js +26 -0
- package/dist/core/run-flight-ledger.d.ts +5 -1
- package/dist/core/run-flight-ledger.js +30 -1
- package/dist/core/runtime-profile.d.ts +29 -0
- package/dist/core/runtime-profile.js +50 -0
- package/dist/core/scout-contract.d.ts +2 -0
- package/dist/core/scout-contract.js +2 -0
- package/dist/core/unit-result-classification.d.ts +16 -0
- package/dist/core/unit-result-classification.js +11 -0
- package/dist/core/validate-schema.d.ts +1 -0
- package/dist/core/validate-schema.js +8 -6
- package/dist/core/validation-budget.d.ts +1 -1
- package/dist/core/worktree-commit-artifact.js +326 -326
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/plugin/continuation.d.ts +4 -2
- package/dist/plugin/continuation.js +46 -13
- package/dist/plugin/fast-lane.d.ts +11 -0
- package/dist/plugin/fast-lane.js +83 -10
- package/dist/plugin/index.d.ts +8 -0
- package/dist/plugin/index.js +877 -172
- package/dist/plugin/legacy.d.ts +2 -0
- package/dist/plugin/legacy.js +2 -0
- package/dist/plugin/model-cost.d.ts +28 -0
- package/dist/plugin/model-cost.js +54 -0
- package/dist/plugin/opencode.d.ts +1 -1
- package/dist/plugin/opencode.js +1 -1
- package/dist/plugin/profiled.d.ts +5 -0
- package/dist/plugin/profiled.js +1213 -0
- package/dist/plugin/receipt-presentation.d.ts +5 -0
- package/dist/plugin/receipt-presentation.js +13 -0
- package/dist/plugin/run-metrics.d.ts +9 -1
- package/dist/plugin/run-metrics.js +95 -13
- package/dist/plugin/runtime-bridge.d.ts +99 -0
- package/dist/plugin/runtime-bridge.js +1 -0
- package/dist/plugin/sortie-career.js +18 -14
- package/dist/plugin/sortie-debrief.d.ts +27 -0
- package/dist/plugin/sortie-debrief.js +75 -20
- package/dist/reflection/policy.js +82 -82
- package/dist/runtime-assets-v010.d.ts +4 -0
- package/dist/runtime-assets-v010.js +343 -0
- package/dist/runtime-assets.js +1465 -1465
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -1,12 +1,62 @@
|
|
|
1
1
|
# Sortie-dogs
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**A goal-preserving, adaptive execution harness for OpenCode
|
|
4
|
+
that optimizes cost, time, and proof without taking your setup over.**
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
+
### Four design pillars
|
|
10
|
+
|
|
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
|
+
|
|
16
|
+
### Direction for v0.10.x
|
|
17
|
+
|
|
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
|
|
28
|
+
|
|
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.
|
|
34
|
+
|
|
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
|
|
46
|
+
|
|
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.
|
|
54
|
+
|
|
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.
|
|
58
|
+
|
|
59
|
+
> **Project status: Beta.** v0.10.x is under active stabilization. Runtime
|
|
10
60
|
> behavior, configuration, and runtime assets may still change before 1.0.
|
|
11
61
|
|
|
12
62
|
[](https://www.npmjs.com/package/sortie-dogs)
|
|
@@ -18,61 +68,105 @@ unrelated sessions remain available and unchanged.
|
|
|
18
68
|
Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
|
|
19
69
|
bounded implementation, canonical validation, and evidence-backed completion.
|
|
20
70
|
|
|
21
|
-
Requirements: Node.js 22.6 or newer, npm, and OpenCode.
|
|
22
|
-
|
|
23
71
|
Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [テスト実行](docs/testing.md) · [CLI testing](docs/cli-testing.md)
|
|
24
72
|
|
|
25
|
-
Release: [v0.
|
|
26
|
-
|
|
27
|
-
##
|
|
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
|
+

|
|
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.
|
|
28
128
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
verifier was not run. Further benchmarks are frozen while completion defects are repaired.
|
|
32
|
-
|
|
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.
|
|
129
|
+
[Definitions, frozen inputs, current failure status, and limitations](docs/benchmark-reference.md)
|
|
130
|
+
· [Machine-readable reference values](docs/benchmarks/provisional-reference.json)
|
|
36
131
|
|
|
37
|
-
|
|
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% |
|
|
132
|
+
## How the design is enforced
|
|
45
133
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
Those attempts are not pooled into the historical pair above.
|
|
134
|
+
The four pillars above become operational constraints rather than a second set
|
|
135
|
+
of product promises:
|
|
49
136
|
|
|
50
|
-
|
|
137
|
+
### 1. Keep the goal invariant
|
|
51
138
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
56
145
|
|
|
57
|
-
|
|
58
|
-
· [Machine-readable reference values](docs/benchmarks/provisional-reference.json)
|
|
146
|
+
### 2. Adapt execution to the work
|
|
59
147
|
|
|
60
|
-
|
|
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.
|
|
61
153
|
|
|
62
|
-
###
|
|
154
|
+
### 3. Coexist with OpenCode
|
|
63
155
|
|
|
64
156
|
Use normal OpenCode normally. Sortie activates only for `/sortie` or
|
|
65
|
-
`dog-coordinator`; it does not disable or replace
|
|
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.
|
|
66
161
|
|
|
67
|
-
###
|
|
162
|
+
### 4. Optimize cost, time, and proof together
|
|
68
163
|
|
|
69
|
-
Lower-cost models handle bounded retrieval and parallel volume work
|
|
70
|
-
models are reserved for implementation, escalation, and independent review
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
run can return a concise Speed / Cost / Proof debrief.
|
|
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.
|
|
76
170
|
|
|
77
171
|
## Designed to coexist with OpenCode
|
|
78
172
|
|
|
@@ -90,17 +184,11 @@ Sortie-dogs adds a workflow to your existing setup rather than replacing it.
|
|
|
90
184
|
|
|
91
185
|
Use OpenCode normally. Invoke the pack only when you want it.
|
|
92
186
|
|
|
93
|
-
##
|
|
94
|
-
|
|
95
|
-
Install the public npm package in the project and generate the project-local
|
|
96
|
-
OpenCode runtime files:
|
|
97
|
-
|
|
98
|
-
```sh
|
|
99
|
-
npm install --save-dev sortie-dogs
|
|
100
|
-
npx sortie-dogs init .
|
|
101
|
-
```
|
|
187
|
+
## Configuration
|
|
102
188
|
|
|
103
|
-
|
|
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.
|
|
104
192
|
|
|
105
193
|
Installing the runtime assets does not load the plugin, and without the plugin
|
|
106
194
|
every role runs on whichever model the caller happened to use. Add the package
|
|
@@ -121,6 +209,10 @@ as `.opencode/sortie-dogs.json`:
|
|
|
121
209
|
|
|
122
210
|
```json
|
|
123
211
|
{
|
|
212
|
+
"dedicatedWorkerModel": {
|
|
213
|
+
"model": "openai/gpt-5.6-sol",
|
|
214
|
+
"variant": "medium"
|
|
215
|
+
},
|
|
124
216
|
"modelRouting": {
|
|
125
217
|
"dog-coordinator": {
|
|
126
218
|
"preferred": { "model": "provider/model" }
|
|
@@ -283,22 +375,34 @@ dog-coordinator: completion evidence accepted
|
|
|
283
375
|
|
|
284
376
|
## The workflow
|
|
285
377
|
|
|
286
|
-
1. **
|
|
287
|
-
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.
|
|
288
380
|
2. **Optional scout** — one bounded, read-only investigation runs only for a
|
|
289
381
|
concrete pre-worker evidence gap.
|
|
290
|
-
3. **
|
|
291
|
-
|
|
382
|
+
3. **Adaptive execution** — the coordinator selects one worker or bounded parallel
|
|
383
|
+
units according to task shape; workers implement only their approved manifests.
|
|
292
384
|
4. **Canonical validation** — the declared test or build command must produce
|
|
293
385
|
acceptable evidence.
|
|
294
386
|
5. **Risk-based review** — high-risk candidates receive independent review;
|
|
295
387
|
low-risk candidates can skip that extra pass after validation.
|
|
296
|
-
6. **
|
|
297
|
-
|
|
388
|
+
6. **Goal-level completion** — only the coordinator closes the loop after every
|
|
389
|
+
accepted criterion has manifest, validation, review, and evidence coverage.
|
|
298
390
|
7. **Bounded continuation** — restart recovery and compaction handoffs preserve
|
|
299
391
|
progress; repeated batches remain bounded rather than becoming endless
|
|
300
392
|
delegation.
|
|
301
393
|
|
|
394
|
+
## Built to work on itself
|
|
395
|
+
|
|
396
|
+
Self-improvement keeps the same scoped manifests, worker ownership, validation,
|
|
397
|
+
and review gates as other work. A loaded plugin is not treated as hot-reloadable:
|
|
398
|
+
source changes are validated first, then packaged into an isolated `_testenv`
|
|
399
|
+
fixture and exercised through the real OpenCode CLI. Continuation and compaction
|
|
400
|
+
changes must demonstrate same-session recovery and terminal completion there.
|
|
401
|
+
|
|
402
|
+
`npm run test:full` is reserved for explicit release validation; ordinary
|
|
403
|
+
changes run targeted tests and `npm test`. The control plane coordinating a run
|
|
404
|
+
is not replaced while that run is in flight.
|
|
405
|
+
|
|
302
406
|
## A visual walkthrough
|
|
303
407
|
|
|
304
408
|
### Control complexity
|
|
@@ -392,6 +496,10 @@ model you can actually serve.
|
|
|
392
496
|
|
|
393
497
|
```json
|
|
394
498
|
{
|
|
499
|
+
"dedicatedWorkerModel": {
|
|
500
|
+
"model": "openai/gpt-5.6-sol",
|
|
501
|
+
"variant": "medium"
|
|
502
|
+
},
|
|
395
503
|
"modelRouting": {
|
|
396
504
|
"dog-coordinator": {
|
|
397
505
|
"preferred": { "model": "openai/gpt-5.6-luna", "variant": "max" }
|
|
@@ -417,6 +525,10 @@ model you can actually serve.
|
|
|
417
525
|
}
|
|
418
526
|
```
|
|
419
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
|
+
|
|
420
532
|
Save project configuration as `.opencode/sortie-dogs.json`. `modelCatalog`
|
|
421
533
|
declares provider models and named variants that are actually available;
|
|
422
534
|
Sortie-dogs does not invent, probe, or translate variants. The built-in catalog
|
package/dist/asset-version.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/asset-version.js
CHANGED
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
|
|
233
|
-
|
|
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(
|
|
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";
|
|
@@ -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;
|