sortie-dogs 0.9.6 → 0.9.8

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
- **Give OpenCode a task; get a bounded, validated implementation loop instead of an open-ended agent run.**
3
+ **Add a bounded, cost-aware execution loop to OpenCode without taking OpenCode over.**
4
4
 
5
- Sortie-dogs is an execution harness for OpenCode, applying harness-engineering
6
- principles through mechanical write boundaries, validation, review, and bounded
7
- orchestration.
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
8
 
9
- > **Project status: Beta.** Stable in regular use. As a pre-1.0 release,
10
- > configuration and runtime assets may still change between releases.
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,47 +15,63 @@ orchestration.
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 is an opt-in OpenCode orchestration plugin. It turns a task into a
19
- scoped plan, optional evidence gathering, one bounded implementation unit, canonical
20
- validation, and evidence-backed completion—while preserving standard OpenCode
21
- agents and settings.
18
+ Sortie-dogs turns selected work into a scoped plan, optional evidence gathering,
19
+ bounded implementation, canonical validation, and evidence-backed completion.
22
20
 
23
21
  Requirements: Node.js 22.6 or newer, npm, and OpenCode.
24
22
 
25
23
  Guides: [日本語](docs/guide-ja.md) · [简体中文](docs/guide-zh-CN.md) · [CLI testing](docs/cli-testing.md)
26
24
 
27
- Release: [v0.9.6](https://github.com/zufall-upon/Sortie-dogs/releases/tag/v0.9.6)
28
-
29
- ## Quick start
30
-
31
- Install the public npm package in the project and generate the project-local
32
- OpenCode runtime files:
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:
33
64
 
34
65
  ```sh
35
66
  npm install --save-dev sortie-dogs
36
- npx sortie-dogs init .
37
- ```
38
-
39
- Alternatively, install the CLI globally and initialize OpenCode's global
40
- configuration:
41
-
42
- ```sh
43
- npm install --global sortie-dogs
44
- sortie-dogs init --global
45
- ```
46
-
47
- This installs the canonical runtime assets in OpenCode's global configuration,
48
- so `dog-coordinator` can be selected from other projects without project-local
49
- initialization. Global initialization and project-local initialization are
50
- separate: `sortie-dogs init .` still writes runtime files only into that
51
- project. Project-local configuration and the plugin bridge below remain
52
- available when a project needs its own settings or dependency.
53
-
54
- Installing the runtime assets does not load the plugin, and without the plugin
55
- every role runs on whichever model the caller happened to use. Add the package
56
- to the `plugin` array of the OpenCode configuration the agents run under —
57
- `~/.config/opencode/opencode.json` for the global assets, or the project's
58
- `.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`:
59
75
 
60
76
  ```json
61
77
  {
@@ -218,26 +234,7 @@ global file for durable global settings.
218
234
  unit, with a maximum of three records per run; routine bugs and external
219
235
  failures are never journaled.
220
236
 
221
- ## Why Sortie-dogs
222
-
223
- - **Focused when invited, invisible otherwise.** Activate it with `/sortie` or
224
- select `dog-coordinator`; ordinary OpenCode sessions remain unchanged.
225
- - **Evidence gathering without arbitrary dispatch ceilings.** A bounded scout resolves
226
- each concrete manifest, validation, or ownership gap whenever it appears; unchanged
227
- requests are not repeated.
228
- - **Writes stay inside the assignment.** Exact source or operation manifests
229
- gate edits and handoffs.
230
- - **Autonomous implementation routing.** An accepted scope with at least two safe independent
231
- units defaults to Luna fabric without opt-in. An explicit serial/no-parallel request wins;
232
- other work uses sequential workers. The separate parallel contract remains explicit.
233
- - **Runtime overlap protection.** Active equal or ancestor write scopes are rejected
234
- before mutation, and full validation waits until every parallel unit joins.
235
- - **Evidence before completion.** Canonical validation, risk-based review, and
236
- terminal evidence gate coordinator-owned completion and commits.
237
- - **Long work can recover.** Restart recovery and bounded compaction continue
238
- from retained handoff context rather than silently starting over.
239
-
240
- ## Example run
237
+ ## Example run
241
238
 
242
239
  An illustrative low-risk run stays bounded and reports its gates:
243
240
 
@@ -400,7 +397,30 @@ coordinator. `dog-reviewer` independently checks high-risk candidates after
400
397
  canonical validation. Neither role implements, stages, commits, or acts as a
401
398
  user-facing worker.
402
399
 
403
- ## 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
404
424
 
405
425
  After replacing the dependency with a newer release asset, run:
406
426
 
@@ -417,7 +437,12 @@ older runtime files, and records the installed version in
417
437
  untouched and initialization stops safely. User-owned configuration—including
418
438
  `.opencode/sortie-dogs.json`—and standard OpenCode files are preserved.
419
439
 
420
- ## 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
421
446
 
422
447
  There is no supported Sortie-dogs uninstall command. Remove the npm dependency
423
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.81-mission-debrief-v1";
5
+ export declare const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-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.81-mission-debrief-v1";
5
+ export const RUNTIME_ASSET_VERSION = "0.3.84-dispatch-recovery-v1";
@@ -243,7 +243,7 @@ export interface GoalFlightState {
243
243
  }
244
244
  export declare class GoalBoundError extends Error {
245
245
  readonly code: "invalid" | "transition" | "budget" | "ticket" | "evidence";
246
- constructor(code: "invalid" | "transition" | "budget" | "ticket" | "evidence", message: string);
246
+ constructor(code: GoalBoundError["code"], message: string);
247
247
  }
248
248
  export declare function goalFingerprint(value: unknown): string;
249
249
  export declare function selectGoalDelivery(input: {
@@ -2,3 +2,5 @@
2
2
  export declare const GOAL_DELIVERY_INTENTS: readonly ["design", "registration", "implementation", "repair", "controlled-change"];
3
3
  export declare const GOAL_DELIVERY_MODES: readonly ["planning-only", "mvp-first", "repair-first", "controlled-change"];
4
4
  export declare const GOAL_DECLARATION_FORMAT: string;
5
+ /** Normalize a shared declaration without guessing acceptance, coverage, delivery, or validation. */
6
+ export declare function expandGoalDeclaration(value: unknown): string;
@@ -1,12 +1,16 @@
1
+ import { goalFingerprint } from "./goal-bound.js";
1
2
  /** Shared planner guidance and admission enums; no inferred acceptance or dispatch authority. */
2
3
  export const GOAL_DELIVERY_INTENTS = ["design", "registration", "implementation", "repair", "controlled-change"];
3
4
  export const GOAL_DELIVERY_MODES = ["planning-only", "mvp-first", "repair-first", "controlled-change"];
4
- export const GOAL_DECLARATION_FORMAT = `Goal declaration belongs in the Task prompt, not in the handoff JSON or operation manifest.
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.
5
10
  delivery_intent must be exactly one of: ${GOAL_DELIVERY_INTENTS.join(" | ")}. Never use prose or a boolean.
6
11
  delivery_mode is a separate optional enum: ${GOAL_DELIVERY_MODES.join(" | ")}.
7
12
  Use flat key: value lines. Each criterion begins with its own goal_criterion_id: line.
8
- Repeat the entire flat criterion block for multiple criteria. Do not use goal_criteria, goal_acceptance.criteria,
9
- YAML object/list wrappers, inline { goal_criterion_id: ... }, or JSON objects for these blocks.
13
+ For legacy flat input each criterion has a goal_criterion_id line. Prefer shared JSON defaults for multiple criteria.
10
14
  Replace every placeholder with the accepted task's actual value; preserve its acceptance and validation:
11
15
  goal_acceptance_fingerprint: <sha256: followed by exactly 64 lowercase hexadecimal characters>
12
16
  delivery_intent: implementation
@@ -31,3 +35,48 @@ goal_validation_command: <exact operation manifest validation command>
31
35
  The example's implementation/mvp-first selections apply only to an implementation without its usable path;
32
36
  select other enum values only from the accepted request. A declaration rejection launches no child.
33
37
  Repair the Task prompt's named fields, not unrelated files; retry only with the corrected declaration.`;
38
+ /** Normalize a shared declaration without guessing acceptance, coverage, delivery, or validation. */
39
+ export function expandGoalDeclaration(value) {
40
+ if (value === null || typeof value !== "object" || Array.isArray(value))
41
+ throw new Error("goal declaration must be an object");
42
+ const object = value;
43
+ if (!Array.isArray(object.criteria) || object.criteria.length === 0)
44
+ throw new Error("goal declaration requires criteria");
45
+ const defaults = object.defaults !== null && typeof object.defaults === "object" && !Array.isArray(object.defaults)
46
+ ? object.defaults : {};
47
+ const fields = ["criterion_id", "target", "entrypoint", "workload", "oracle_coverage", "build_boundary", "source",
48
+ "candidate", "source_binding", "candidate_binding", "fixture", "proof_scope", "expected_outcome", "validation_command"];
49
+ const scalar = (value) => {
50
+ if (Array.isArray(value))
51
+ return JSON.stringify(value);
52
+ if (typeof value === "string" && !/[\r\n]/u.test(value))
53
+ return value;
54
+ if (typeof value === "boolean" || typeof value === "number")
55
+ return String(value);
56
+ throw new Error("goal declaration field must be a single-line scalar or array");
57
+ };
58
+ const normalizedFields = (values) => Object.fromEntries(fields.flatMap(field => {
59
+ const found = values[`goal_${field}`] ?? values[field];
60
+ return found === undefined ? [] : [[`goal_${field}`, found]];
61
+ }));
62
+ const criteria = object.criteria.map(raw => {
63
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
64
+ throw new Error("goal criterion must be an object");
65
+ return { ...normalizedFields(defaults), ...normalizedFields(raw) };
66
+ });
67
+ const identity = { criteria, delivery_intent: object.delivery_intent, delivery_mode: object.delivery_mode,
68
+ usable_path_established: object.usable_path_established, controlled_change: object.controlled_change };
69
+ const lines = [`goal_acceptance_fingerprint: ${scalar(object.goal_acceptance_fingerprint ?? goalFingerprint(identity))}`];
70
+ for (const field of ["delivery_intent", "delivery_mode", "usable_path_established", "controlled_change",
71
+ "goal_budget_units", "goal_budget_time_ms", "goal_budget_cost_usd"]) {
72
+ if (object[field] !== undefined)
73
+ lines.push(`${field}: ${scalar(object[field])}`);
74
+ }
75
+ for (const criterion of criteria) {
76
+ criterion.goal_criterion_id ??= `criterion-${goalFingerprint(criterion).slice(7, 31)}`;
77
+ for (const field of fields)
78
+ if (criterion[`goal_${field}`] !== undefined)
79
+ lines.push(`goal_${field}: ${scalar(criterion[`goal_${field}`])}`);
80
+ }
81
+ return lines.join("\n");
82
+ }
@@ -12,6 +12,7 @@
12
12
  * identity fails closed into "no automatic continuation" rather than into a guess.
13
13
  */
14
14
  import { openCodeModel } from "./model-routing-hook.js";
15
+ import { terminalRunOutcome } from "./run-metrics.js";
15
16
  /** Plugin tool name the coordinator asset names as the direct continuation capability. */
16
17
  export const CONTINUATION_CAPABILITY = "sortie_compact_and_continue";
17
18
  /** Fallback marker, used only when the direct capability is unavailable. */
@@ -445,8 +446,8 @@ export function createContinuationHooks(client, directory, policySource, timings
445
446
  return firstCheckpoint(text)?.status;
446
447
  }
447
448
  function terminalCheckpoint(text) {
448
- const status = checkpointStatus(text);
449
- return status === "DONE" || status === "NEED_DECISION" || (status === "BLOCKED" && trueBlockerReport(text));
449
+ // Use the same terminal vocabulary as the receipt/debrief path, including INTERRUPTED.
450
+ return terminalRunOutcome(text) !== undefined;
450
451
  }
451
452
  function trueBlockerReport(text) {
452
453
  const checkpoint = firstCheckpoint(text);
@@ -65,6 +65,8 @@ export interface OpenCodeHooks {
65
65
  /** Continuation observes the coordinator's completed final text to honour its fallback markers. */
66
66
  "experimental.text.complete"?: (input: {
67
67
  sessionID: string;
68
+ messageID?: string;
69
+ partID?: string;
68
70
  }, output: {
69
71
  text: string;
70
72
  }) => Promise<void>;