create-agdf 0.11.3 → 0.11.4

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 (35) hide show
  1. package/README.md +5 -4
  2. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +1 -1
  3. package/generated/.github/skills/contracts/control-scaffold.md +2 -0
  4. package/generated/.opencode/contracts/control-scaffold.md +2 -0
  5. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  6. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  7. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +1 -1
  8. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  9. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
  10. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  11. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +2 -2
  12. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +78 -14
  13. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +1 -0
  14. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +2 -11
  15. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +2 -11
  16. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
  17. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
  18. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +1 -0
  19. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  20. package/generated/plugins/agdf/runtime/create-agdf/package.json +3 -2
  21. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  22. package/lib/cli/application.js +17 -2
  23. package/lib/cli/command-registry.js +2 -2
  24. package/lib/cli/delivery-path-search-command.js +78 -14
  25. package/lib/control-evaluation/shared.js +1 -0
  26. package/lib/delivery-path-search/evaluators/claude.js +2 -11
  27. package/lib/delivery-path-search/evaluators/codex.js +2 -11
  28. package/lib/delivery-path-search/evaluators/opencode.js +234 -0
  29. package/lib/delivery-path-search/evaluators/prompt.js +13 -0
  30. package/lib/delivery-path-search/search-engine.js +1 -0
  31. package/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  32. package/lib/installers/opencode.js +214 -10
  33. package/lib/lifecycle/operations.js +4 -0
  34. package/opencode-plugin.js +19 -2
  35. package/package.json +3 -2
package/README.md CHANGED
@@ -115,7 +115,7 @@ it does not localize the CLI lifecycle card.
115
115
  - `claude` installs the AGDF plugin globally for Claude Code
116
116
  - `copilot` writes `AGENTS.md`, Copilot custom instructions under `.github/`, visible repository skills under `.github/skills/`, and AGDF control templates under `.agdf/control/`
117
117
  - `opencode` installs the AGDF npm plugin and ten native skills as a user-wide OpenCode surface
118
- - `opencode-status` reports OpenCode global config, package loadability, global native-skill completeness, durable repository activation, legacy compatibility and observable session signals
118
+ - `opencode-status` reports OpenCode global config, package loadability, global native-skill completeness, installed host/plugin-SDK versions, declaration-level support for AGDF's two experimental hooks, durable repository activation, legacy compatibility and observable session signals
119
119
  - `status` reports installation, repository activation and delivery separately without mutating state
120
120
  - `disable` writes a supported repository-local opt-out while retaining global capability and durable control state
121
121
  - `uninstall` previews and, only with `--confirm`, applies a selected global removal through supported native/owned operations
@@ -149,7 +149,7 @@ Then verify the visible installation state:
149
149
  npx --yes @agdf/cli@latest opencode-status --json
150
150
  ```
151
151
 
152
- The status command reports global configuration, package loadability, global native-skill completeness, durable repository activation, legacy local-surface compatibility and session signals. It does not infer an active OpenCode session from config alone.
152
+ The status command reports global configuration, package loadability, global native-skill completeness, installed host/plugin-SDK version divergence, declaration-level support for both experimental hooks, durable repository activation, legacy local-surface compatibility and session signals. SDK declarations are not proof that a live host invoked a hook, and host/SDK divergence is warning-only rather than auto-aligned. The command does not infer an active OpenCode session from config alone.
153
153
  In JSON schema version 1, `repository_surface.gate_check_agent` remains a deprecated compatibility alias for the native `gate_check_skill` path so existing status consumers keep working during the agent-to-skill migration.
154
154
 
155
155
  Use the `opencode-repo` target when a repository should opt into the globally installed AGDF OpenCode runtime:
@@ -235,7 +235,7 @@ Use `delivery-path-search` only for high-impact planning decisions with several
235
235
  agdf delivery-path-search --surface codex --json
236
236
  ```
237
237
 
238
- The runtime uses bounded best-first Delivery Path Search, not MCTS. It is read-only and advisory: the result must be checked by canonical `gate-check`. Codex and Claude Code are executable, tool-enforced evaluator adapters and support opt-in `--generate-candidates`; generated proposals supplement the deterministic baseline and are deterministically validated before evaluation. Copilot and OpenCode reuse the same skill and contracts as instruction-only surfaces until conforming executable adapters are available.
238
+ The runtime uses bounded best-first Delivery Path Search, not MCTS. It is read-only and advisory: the result must be checked by canonical `gate-check`. Codex and Claude Code are executable, tool-enforced evaluator adapters and support opt-in `--generate-candidates`; generated proposals supplement the deterministic baseline and are deterministically validated before evaluation. OpenCode has an executable evaluator through `opencode run --pure --agent agdf-evaluator` only after the current invocation's capability preflight proves the command flags, owned agent and effective deny permissions. A failed preflight or evaluator transport returns `evaluator_unavailable`, reports `instruction_only` and directs the user to the existing instruction-only workflow. Copilot remains instruction-only.
239
239
 
240
240
  Requirements and boundaries:
241
241
 
@@ -243,10 +243,11 @@ Requirements and boundaries:
243
243
  - the current control state must expose legal next actions
244
244
  - Codex CLI must be installed and authenticated for `--surface codex`
245
245
  - Claude Code CLI must be installed and authenticated for `--surface claude`
246
+ - OpenCode must pass the per-invocation command, agent and effective-permission preflight for `--surface opencode`
246
247
  - `--model <id>` optionally selects the Codex or Claude evaluator model
247
248
  - `--persist` writes redacted `DELIVERY_PATH_SEARCH.json` and `.md` evidence under the current scope
248
249
  - `--fixture <path>` is for deterministic contract tests, not a production evaluator
249
- - Copilot and OpenCode have shared workflow mappings but no executable native evaluator in this release
250
+ - OpenCode candidate generation is intentionally unavailable; Copilot has no executable native evaluator
250
251
  - cost units are rubric values used for bounded comparison, not measured provider currency
251
252
 
252
253
  The result is either one recommendation or `no_safe_recommendation`. In both cases run canonical `gate-check` afterwards.
@@ -15,7 +15,7 @@ This file is the living control pointer for active AGDF delivery work. Detailed
15
15
  9. Historical artefacts remain linkable but do not override the current pointer.
16
16
  10. If an item is superseded, mark the replacement.
17
17
  11. If active work grows beyond roughly ten items, triage the backlog.
18
- 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
18
+ 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Awaiting OR, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
19
19
  13. `Artefacts` link labels must be one of the labels normalized by the same parser module: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
20
20
  14. After writing or updating this file, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before treating the edit as done.
21
21
  15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/lib/control-evaluation/shared.js` is authoritative for the accepted values.
@@ -17,6 +17,8 @@ Delivery Path Search is an optional read-only planning step for high-impact deci
17
17
  - External generation receives only bounded normalized control summaries and references, never secrets, full artefacts, raw prompts, hidden reasoning or source snapshots.
18
18
  - Generator status, provenance, accepted/rejected counts, separate budget use and typed failure must remain visible. Failure retains the deterministic baseline; automatic provider fallback is forbidden.
19
19
  - Codex and Claude Code may provide tool-enforced generator transports. Copilot, OpenCode and generic surfaces remain instruction-only without conforming evidence.
20
+ - OpenCode may provide a tool-enforced evaluator only for an invocation whose capability preflight proves `opencode run --pure --agent`, the owned evaluator agent and effective executable-tool denies. Preflight or transport failure must return `evaluator_unavailable` with `instruction_only` enforcement and point to the existing instruction-only workflow; it must not continue through a weaker subprocess.
21
+ - OpenCode candidate generation remains unavailable.
20
22
 
21
23
  The field names above are the stable machine-readable contract used by JSON
22
24
  reports and automation. Human-facing Markdown must present the same projection
@@ -17,6 +17,8 @@ Delivery Path Search is an optional read-only planning step for high-impact deci
17
17
  - External generation receives only bounded normalized control summaries and references, never secrets, full artefacts, raw prompts, hidden reasoning or source snapshots.
18
18
  - Generator status, provenance, accepted/rejected counts, separate budget use and typed failure must remain visible. Failure retains the deterministic baseline; automatic provider fallback is forbidden.
19
19
  - Codex and Claude Code may provide tool-enforced generator transports. Copilot, OpenCode and generic surfaces remain instruction-only without conforming evidence.
20
+ - OpenCode may provide a tool-enforced evaluator only for an invocation whose capability preflight proves `opencode run --pure --agent`, the owned evaluator agent and effective executable-tool denies. Preflight or transport failure must return `evaluator_unavailable` with `instruction_only` enforcement and point to the existing instruction-only workflow; it must not continue through a weaker subprocess.
21
+ - OpenCode candidate generation remains unavailable.
20
22
 
21
23
  The field names above are the stable machine-readable contract used by JSON
22
24
  reports and automation. Human-facing Markdown must present the same projection
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdf",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "AI Governance & Delivery Framework (AGDF) brings an operating system for governed AI-assisted delivery to Claude Code, combining workflow skills, control templates and machine-readable repository checks for gate decisions, Brownfield analysis, task-plan review, QA decisions, durable run state and auditable closeout.",
5
5
  "keywords": [
6
6
  "agentic",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdf",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "description": "Operating system for governed AI-assisted delivery.",
5
5
  "author": {
6
6
  "name": "Arndt Gold",
@@ -15,7 +15,7 @@ This file is the living control pointer for active AGDF delivery work. Detailed
15
15
  9. Historical artefacts remain linkable but do not override the current pointer.
16
16
  10. If an item is superseded, mark the replacement.
17
17
  11. If active work grows beyond roughly ten items, triage the backlog.
18
- 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
18
+ 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Awaiting OR, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
19
19
  13. `Artefacts` link labels must be one of the labels normalized by the same parser module: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
20
20
  14. After writing or updating this file, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before treating the edit as done.
21
21
  15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/lib/control-evaluation/shared.js` is authoritative for the accepted values.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "agdf",
4
- "version": "0.11.3",
4
+ "version": "0.11.4",
5
5
  "displayName": "AI Governance & Delivery Framework",
6
6
  "developerName": "Arndt Gold",
7
7
  "description": "Operating system for governed AI-assisted delivery.",
@@ -59,6 +59,7 @@
59
59
  "opencode": {
60
60
  "skillPrefix": "agdf-",
61
61
  "globalSkillPrefix": "agdf-global-",
62
+ "evaluatorAgentName": "agdf-evaluator",
62
63
  "runtimeContractFileName": "agdf-runtime-contract.md",
63
64
  "instructionsFileName": "AGDF.md",
64
65
  "npmPackage": "create-agdf",
@@ -17,6 +17,8 @@ Delivery Path Search is an optional read-only planning step for high-impact deci
17
17
  - External generation receives only bounded normalized control summaries and references, never secrets, full artefacts, raw prompts, hidden reasoning or source snapshots.
18
18
  - Generator status, provenance, accepted/rejected counts, separate budget use and typed failure must remain visible. Failure retains the deterministic baseline; automatic provider fallback is forbidden.
19
19
  - Codex and Claude Code may provide tool-enforced generator transports. Copilot, OpenCode and generic surfaces remain instruction-only without conforming evidence.
20
+ - OpenCode may provide a tool-enforced evaluator only for an invocation whose capability preflight proves `opencode run --pure --agent`, the owned evaluator agent and effective executable-tool denies. Preflight or transport failure must return `evaluator_unavailable` with `instruction_only` enforcement and point to the existing instruction-only workflow; it must not continue through a weaker subprocess.
21
+ - OpenCode candidate generation remains unavailable.
20
22
 
21
23
  The field names above are the stable machine-readable contract used by JSON
22
24
  reports and automation. Human-facing Markdown must present the same projection
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "agdf",
4
- "version": "0.11.3",
4
+ "version": "0.11.4",
5
5
  "displayName": "AI Governance & Delivery Framework",
6
6
  "developerName": "Arndt Gold",
7
7
  "description": "Operating system for governed AI-assisted delivery.",
@@ -59,6 +59,7 @@
59
59
  "opencode": {
60
60
  "skillPrefix": "agdf-",
61
61
  "globalSkillPrefix": "agdf-global-",
62
+ "evaluatorAgentName": "agdf-evaluator",
62
63
  "runtimeContractFileName": "agdf-runtime-contract.md",
63
64
  "instructionsFileName": "AGDF.md",
64
65
  "npmPackage": "create-agdf",
@@ -27,8 +27,8 @@ export const commandRegistry = Object.freeze([
27
27
  command("gate-check", { local: [" --approval-envelope", " --json"], scaffold: [""], legacy: [" --json"] }),
28
28
  command("delivery-map", { local: [" --json"], scaffold: [""] }),
29
29
  command("delivery-path-search", {
30
- local: [" --surface codex --json", " --surface claude --json"],
31
- scaffold: [" --surface codex", " --surface claude"],
30
+ local: [" --surface codex --json", " --surface claude --json", " --surface opencode --json"],
31
+ scaffold: [" --surface codex", " --surface claude", " --surface opencode"],
32
32
  }),
33
33
  command("run-create", { local: [" --run <run_id>"] }),
34
34
  command("run-migrate", { local: [" [--run <run_id>]"] }),
@@ -3,6 +3,7 @@ import { searchInputFromControl } from '../delivery-path-search/state-adapter.js
3
3
  import { runDeliveryPathSearch } from '../delivery-path-search/search-engine.js';
4
4
  import { codexEvaluator } from '../delivery-path-search/evaluators/codex.js';
5
5
  import { claudeEvaluator } from '../delivery-path-search/evaluators/claude.js';
6
+ import { openCodeEvaluator, preflightOpenCodeEvaluator } from '../delivery-path-search/evaluators/opencode.js';
6
7
  import { fixtureEvaluator } from '../delivery-path-search/evaluators/protocol.js';
7
8
  import { codexGenerator } from '../delivery-path-search/generators/codex.js';
8
9
  import { claudeGenerator } from '../delivery-path-search/generators/claude.js';
@@ -10,12 +11,56 @@ import { fixtureGenerator } from '../delivery-path-search/generators/protocol.js
10
11
  import { enforcementForSurface } from '../delivery-path-search/surfaces/capabilities.js';
11
12
  import { persistSearchResult } from '../delivery-path-search/persistence.js';
12
13
 
13
- export async function executeDeliveryPathSearch(options, io = console) {
14
+ function unavailableOpenCodeResult({ options, preflight, input = null, error = null }) {
15
+ const mutation = error?.code === "OPENCODE_EVALUATOR_MUTATION_DETECTED";
16
+ const attempted = Boolean(input);
17
+ const nextAction = mutation
18
+ ? "Investigate and revert the detected repository mutation before any evaluator fallback or retry."
19
+ : "Repair the reported OpenCode capability and retry, or use the existing instruction-only Delivery Path Search workflow.";
20
+ return {
21
+ contract_version: "1",
22
+ surface: "opencode",
23
+ scope_key: input?.scope_key ?? options.runId ?? null,
24
+ current_gate: input?.current_gate ?? null,
25
+ status: mutation ? "evaluator_error" : "evaluator_unavailable",
26
+ recommendation: null,
27
+ enforcement: enforcementForSurface("opencode"),
28
+ evaluator: {
29
+ name: "opencode",
30
+ preflight: preflight?.code ?? "not_run",
31
+ failure_code: error?.code ?? preflight?.code,
32
+ detail: error?.message ?? preflight?.detail,
33
+ evidence: preflight?.evidence ?? [],
34
+ },
35
+ executable_evaluator: {
36
+ attempted,
37
+ preflight: preflight?.status ?? "failed",
38
+ failure_code: error?.code ?? preflight?.code,
39
+ evidence: preflight?.evidence ?? [],
40
+ },
41
+ stopping_reason: mutation ? "opencode_mutation_detected" : attempted ? "opencode_evaluator_failed" : "opencode_preflight_failed",
42
+ next_action: nextAction,
43
+ next_gate_action: nextAction,
44
+ };
45
+ }
46
+
47
+ export async function executeDeliveryPathSearch(options, io = console, dependencies = {}) {
14
48
  const fixture = options.fixture ? JSON.parse(readFileSync(options.fixture, "utf8")) : null;
15
- const enforcement = fixture?.input?.enforcement ?? enforcementForSurface(options.surface);
49
+ const preflight = !fixture && options.surface === "opencode"
50
+ ? (dependencies.preflightOpenCodeEvaluator ?? preflightOpenCodeEvaluator)({ cwd: options.dir })
51
+ : null;
52
+ if (preflight?.status === "failed") {
53
+ const result = unavailableOpenCodeResult({ options, preflight });
54
+ printDeliveryPathSearchResult(result, options, io);
55
+ return result;
56
+ }
57
+ const enforcement = fixture?.input?.enforcement
58
+ ?? (preflight
59
+ ? enforcementForSurface("opencode", preflight.evidence, { preflight })
60
+ : enforcementForSurface(options.surface));
16
61
  const fixtureGeneration = fixture?.generator_response;
17
62
  const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
18
- const baseInput = fixture?.input ?? searchInputFromControl(options.dir, {
63
+ const baseInput = fixture?.input ?? (dependencies.searchInputFromControl ?? searchInputFromControl)(options.dir, {
19
64
  scopeKey: options.runId,
20
65
  enforcement,
21
66
  generation: generationEnabled ? {
@@ -41,9 +86,11 @@ export async function executeDeliveryPathSearch(options, io = console) {
41
86
  ? codexEvaluator({ cwd: options.dir, model: options.model })
42
87
  : options.surface === "claude"
43
88
  ? claudeEvaluator({ cwd: options.dir, model: options.model })
44
- : null;
89
+ : options.surface === "opencode"
90
+ ? (dependencies.openCodeEvaluator ?? openCodeEvaluator)({ cwd: options.dir, model: options.model, preflight })
91
+ : null;
45
92
  if (!evaluator) {
46
- throw new Error(`${options.surface} has no executable evaluator in this release. Codex and Claude are the reference adapters; --fixture is available for deterministic contract testing.`);
93
+ throw new Error(`${options.surface} has no executable evaluator in this release. Codex, Claude and preflight-qualified OpenCode are the executable adapters; --fixture is available for deterministic contract testing.`);
47
94
  }
48
95
  const generator = fixtureGeneration
49
96
  ? fixtureGenerator(fixtureGeneration)
@@ -55,21 +102,38 @@ export async function executeDeliveryPathSearch(options, io = console) {
55
102
  if (options.generateCandidates && !generator) {
56
103
  throw new Error(`${options.surface} has no executable candidate generator; Codex and Claude are tool-enforced reference transports.`);
57
104
  }
58
- const result = await runDeliveryPathSearch(input, evaluator, { candidates: fixture?.candidates, generator });
59
- if (options.persist) result.persistence = persistSearchResult(options.dir, result);
105
+ let result;
106
+ try {
107
+ result = await (dependencies.runDeliveryPathSearch ?? runDeliveryPathSearch)(input, evaluator, { candidates: fixture?.candidates, generator });
108
+ if (!fixture && options.surface === "opencode" && result.budgets?.evaluations === 0) {
109
+ result.enforcement = enforcementForSurface("opencode");
110
+ }
111
+ } catch (error) {
112
+ if (!error?.fatalEvaluator || options.surface !== "opencode") throw error;
113
+ result = unavailableOpenCodeResult({ options, preflight, input, error });
114
+ }
115
+ if (options.persist && ["recommendation", "no_safe_recommendation"].includes(result.status)) {
116
+ result.persistence = (dependencies.persistSearchResult ?? persistSearchResult)(options.dir, result);
117
+ }
118
+ printDeliveryPathSearchResult(result, options, io);
119
+ return result;
120
+ }
121
+
122
+ function printDeliveryPathSearchResult(result, options, io) {
60
123
  if (options.json) {
61
124
  io.log(JSON.stringify(result, null, 2));
62
- return result;
125
+ return;
63
126
  }
64
127
  io.log(`AGDF Delivery Path Search: ${result.status}`);
65
- io.log(`Current gate: ${result.current_gate}`);
128
+ io.log(`Current gate: ${result.current_gate ?? "unknown"}`);
66
129
  io.log(`Enforcement: ${result.enforcement.level}`);
67
130
  io.log(`Recommendation: ${result.recommendation?.action ?? "none"}`);
68
- io.log(`Evaluations: ${result.budgets.evaluations}`);
69
- io.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
70
- io.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
71
- if (result.generation.failure_code) io.log(`Generation failure: ${result.generation.failure_code}`);
131
+ if (result.budgets) io.log(`Evaluations: ${result.budgets.evaluations}`);
132
+ if (result.generation) {
133
+ io.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
134
+ io.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
135
+ if (result.generation.failure_code) io.log(`Generation failure: ${result.generation.failure_code}`);
136
+ }
72
137
  io.log(`Stopping reason: ${result.stopping_reason}`);
73
138
  io.log(result.next_gate_action);
74
- return result;
75
139
  }
@@ -56,6 +56,7 @@ const backlogStatusLabels = new Map([
56
56
  ["blocked", "blocked"],
57
57
  ["awaiting qa", "awaiting_qa"],
58
58
  ["awaiting uat", "awaiting_uat"],
59
+ ["awaiting or", "awaiting_or"],
59
60
  ["completed", "completed"],
60
61
  ["superseded", "superseded"],
61
62
  ["abandoned", "abandoned"],
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { evaluatorOutputSchema, validateEvaluation } from "../contracts.js";
3
3
  import { guardedExecFileSync } from "../transports/read-only-guard.js";
4
+ import { buildEvaluatorPrompt } from "./prompt.js";
4
5
 
5
6
  export function claudeEvaluator(options = {}) {
6
7
  const claudeBin = options.claudeBin ?? "claude";
@@ -13,17 +14,7 @@ export function claudeEvaluator(options = {}) {
13
14
  name: "claude",
14
15
  metadata: { name: "claude", runtime, model: options.model ?? "configured-default" },
15
16
  async evaluate(input, candidate) {
16
- const prompt = [
17
- "You are an evaluation-only component. Do not use tools, execute commands, or modify files.",
18
- `Return JSON matching contract version ${input.contract_version}.`,
19
- `Objective: ${input.objective}`,
20
- `Current gate: ${input.current_gate}`,
21
- `Candidate id: ${candidate.id}`,
22
- `Candidate action: ${candidate.action}`,
23
- "Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
24
- "Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
25
- "Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
26
- ].join("\n");
17
+ const prompt = buildEvaluatorPrompt(input, candidate);
27
18
  const args = [
28
19
  "-p", "--disallowedTools", "Edit,Write,Bash",
29
20
  "--output-format", "json", "--json-schema", JSON.stringify(evaluatorOutputSchema()),
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { evaluatorOutputSchema, validateEvaluation } from "../contracts.js";
6
6
  import { guardedExecFileSync } from "../transports/read-only-guard.js";
7
+ import { buildEvaluatorPrompt } from "./prompt.js";
7
8
 
8
9
  export function codexEvaluator(options = {}) {
9
10
  const codexBin = options.codexBin ?? "codex";
@@ -21,17 +22,7 @@ export function codexEvaluator(options = {}) {
21
22
  const outputPath = join(temp, "output.json");
22
23
  try {
23
24
  writeFileSync(schemaPath, `${JSON.stringify(evaluatorOutputSchema(), null, 2)}\n`, "utf8");
24
- const prompt = [
25
- "You are an evaluation-only component. Do not use tools, execute commands, or modify files.",
26
- `Return JSON matching contract version ${input.contract_version}.`,
27
- `Objective: ${input.objective}`,
28
- `Current gate: ${input.current_gate}`,
29
- `Candidate id: ${candidate.id}`,
30
- `Candidate action: ${candidate.action}`,
31
- "Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
32
- "Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
33
- "Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
34
- ].join("\n");
25
+ const prompt = buildEvaluatorPrompt(input, candidate);
35
26
  const args = [
36
27
  "exec", "--sandbox", "read-only", "--ephemeral", "--ignore-user-config",
37
28
  "--output-schema", schemaPath, "--output-last-message", outputPath, "--color", "never",
@@ -0,0 +1,234 @@
1
+ import { execFileSync, spawnSync } from "node:child_process";
2
+ import process from "node:process";
3
+ import { validateEvaluation } from "../contracts.js";
4
+ import { guardedExecFileSync } from "../transports/read-only-guard.js";
5
+ import { buildEvaluatorPrompt } from "./prompt.js";
6
+
7
+ export const OPENCODE_EVALUATOR_AGENT = "agdf-evaluator";
8
+
9
+ export const OPENCODE_DENY_PERMISSIONS = Object.freeze({
10
+ "*": "deny",
11
+ read: "deny",
12
+ edit: "deny",
13
+ bash: "deny",
14
+ task: "deny",
15
+ webfetch: "deny",
16
+ websearch: "deny",
17
+ skill: "deny",
18
+ lsp: "deny",
19
+ todowrite: "deny",
20
+ question: "deny",
21
+ external_directory: "deny",
22
+ });
23
+
24
+ const executablePermissions = Object.freeze([
25
+ "edit", "bash", "task", "webfetch", "websearch", "skill", "lsp", "todowrite", "question",
26
+ ]);
27
+
28
+ export function openCodePermissionEnvironment(env = process.env) {
29
+ return {
30
+ ...env,
31
+ OPENCODE_PERMISSION: JSON.stringify(OPENCODE_DENY_PERMISSIONS),
32
+ };
33
+ }
34
+
35
+ function preflightFailure(code, detail, evidence = []) {
36
+ return {
37
+ surface: "opencode",
38
+ status: "failed",
39
+ code,
40
+ detail,
41
+ evidence,
42
+ };
43
+ }
44
+
45
+ function extractAgentPermissions(output, agentName) {
46
+ const marker = `${agentName} (`;
47
+ const lines = output.split(/\r?\n/);
48
+ const start = lines.findIndex((line) => line.startsWith(marker));
49
+ if (start < 0) return null;
50
+ const mode = lines[start].slice(marker.length).split(")")[0];
51
+ let jsonStart = -1;
52
+ let depth = 0;
53
+ for (let index = start + 1; index < lines.length; index += 1) {
54
+ const line = lines[index].trim();
55
+ if (jsonStart < 0 && line === "[") {
56
+ jsonStart = index;
57
+ depth = 1;
58
+ continue;
59
+ }
60
+ if (jsonStart >= 0) {
61
+ for (const character of line) {
62
+ if (character === "[") depth += 1;
63
+ if (character === "]") depth -= 1;
64
+ }
65
+ if (depth === 0) return { mode, permissions: JSON.parse(lines.slice(jsonStart, index + 1).join("\n")) };
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function terminalPermissionDecision(permissions, permissionName) {
72
+ let decision = null;
73
+ for (const entry of permissions) {
74
+ if (entry?.pattern === "*" && (entry.permission === "*" || entry.permission === permissionName)) {
75
+ decision = entry.action;
76
+ }
77
+ }
78
+ return decision;
79
+ }
80
+
81
+ export function preflightOpenCodeEvaluator(options = {}) {
82
+ const executable = options.openCodeBin ?? process.env.AGDF_OPENCODE_BIN ?? "opencode";
83
+ const run = options.execFileSync ?? execFileSync;
84
+ const cwd = options.cwd ?? process.cwd();
85
+ const agentName = options.agentName ?? OPENCODE_EVALUATOR_AGENT;
86
+ const env = openCodePermissionEnvironment(options.env ?? process.env);
87
+ let runtime;
88
+ let help;
89
+ let agents;
90
+ try {
91
+ runtime = run(executable, ["--version"], {
92
+ cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
93
+ }).trim();
94
+ } catch (error) {
95
+ return preflightFailure("opencode_host_unavailable", (error.stderr || error.message).toString().trim());
96
+ }
97
+ try {
98
+ if (options.execFileSync) {
99
+ help = run(executable, ["run", "--help"], {
100
+ cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
101
+ });
102
+ } else {
103
+ const helpProbe = (options.spawnSync ?? spawnSync)(executable, ["run", "--help"], {
104
+ cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
105
+ });
106
+ if (helpProbe.error || helpProbe.status !== 0) throw helpProbe.error ?? new Error(helpProbe.stderr || "OpenCode run help failed.");
107
+ help = `${helpProbe.stdout ?? ""}\n${helpProbe.stderr ?? ""}`;
108
+ }
109
+ } catch (error) {
110
+ return preflightFailure("opencode_run_unavailable", (error.stderr || error.message).toString().trim(), [`opencode ${runtime}`]);
111
+ }
112
+ const requiredFlags = ["--pure", "--agent", "--format", "--dir"];
113
+ const missingFlags = requiredFlags.filter((flag) => !help.includes(flag));
114
+ if (missingFlags.length) {
115
+ return preflightFailure("opencode_run_capability_missing", `Missing required flags: ${missingFlags.join(", ")}`, [`opencode ${runtime}`]);
116
+ }
117
+ try {
118
+ agents = run(executable, ["agent", "list", "--pure"], {
119
+ cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 10000,
120
+ });
121
+ } catch (error) {
122
+ return preflightFailure("opencode_agent_probe_failed", (error.stderr || error.message).toString().trim(), [`opencode ${runtime}`]);
123
+ }
124
+ let projection;
125
+ try {
126
+ projection = extractAgentPermissions(agents, agentName);
127
+ } catch (error) {
128
+ return preflightFailure("opencode_agent_permissions_unparseable", error.message, [`opencode ${runtime}`]);
129
+ }
130
+ if (!projection) {
131
+ return preflightFailure("opencode_evaluator_agent_missing", `OpenCode agent ${agentName} is not available.`, [`opencode ${runtime}`]);
132
+ }
133
+ if (projection.mode !== "primary") {
134
+ return preflightFailure("opencode_evaluator_agent_incompatible", `OpenCode agent ${agentName} must be primary for opencode run --agent; observed ${projection.mode || "unknown"}.`, [
135
+ `opencode ${runtime}`,
136
+ `agent:${agentName}`,
137
+ ]);
138
+ }
139
+ const unsafe = executablePermissions.filter((permission) => terminalPermissionDecision(projection.permissions, permission) !== "deny");
140
+ if (unsafe.length) {
141
+ return preflightFailure("opencode_evaluator_permissions_unsafe", `Terminal deny is not effective for: ${unsafe.join(", ")}`, [
142
+ `opencode ${runtime}`,
143
+ `agent:${agentName}`,
144
+ ]);
145
+ }
146
+ return {
147
+ surface: "opencode",
148
+ status: "passed",
149
+ code: "opencode_evaluator_ready",
150
+ invocation_id: `${process.pid}-${Date.now()}`,
151
+ runtime,
152
+ agent: agentName,
153
+ evidence: [
154
+ `opencode ${runtime}`,
155
+ "opencode run --pure --agent agdf-evaluator --format json",
156
+ "effective executable permissions terminal-deny",
157
+ ],
158
+ env,
159
+ };
160
+ }
161
+
162
+ export function parseOpenCodeEvaluatorOutput(raw) {
163
+ const text = [];
164
+ for (const line of raw.split(/\r?\n/).filter(Boolean)) {
165
+ const event = JSON.parse(line);
166
+ if (event?.type === "error") {
167
+ const status = event.error?.data?.statusCode;
168
+ const detail = event.error?.data?.message || event.error?.message || "unknown OpenCode evaluator error";
169
+ const error = new Error(`OpenCode evaluator error${status ? ` (${status})` : ""}: ${detail}`);
170
+ error.code = status === 401 || /auth|credential|provider available/i.test(detail)
171
+ ? "OPENCODE_EVALUATOR_AUTHENTICATION_FAILED"
172
+ : "OPENCODE_EVALUATOR_UNAVAILABLE";
173
+ throw error;
174
+ }
175
+ if (event?.part?.type === "text" && typeof event.part.text === "string") text.push(event.part.text);
176
+ else if (event?.type === "text" && typeof event.text === "string") text.push(event.text);
177
+ }
178
+ if (!text.length) throw new Error("OpenCode evaluator returned no text event.");
179
+ if (text.length !== 1) throw new Error(`OpenCode evaluator returned ${text.length} text events; exactly one final payload is required.`);
180
+ return JSON.parse(text[0]);
181
+ }
182
+
183
+ function fatalEvaluatorError(error) {
184
+ const wrapped = new Error(error.message);
185
+ wrapped.code = typeof error.code === "string" && error.code.startsWith("OPENCODE_EVALUATOR_")
186
+ ? error.code
187
+ : error.code === "GENERATOR_MUTATION_DETECTED"
188
+ ? "OPENCODE_EVALUATOR_MUTATION_DETECTED"
189
+ : error.code === "GENERATOR_TIMEOUT"
190
+ ? "OPENCODE_EVALUATOR_TIMEOUT"
191
+ : error.code === "ENOENT"
192
+ ? "OPENCODE_EVALUATOR_UNAVAILABLE"
193
+ : /auth|unauthorized|credential/i.test(`${error.message}\n${error.stderr ?? ""}`)
194
+ ? "OPENCODE_EVALUATOR_AUTHENTICATION_FAILED"
195
+ : "OPENCODE_EVALUATOR_OUTPUT_INVALID";
196
+ wrapped.fatalEvaluator = true;
197
+ return wrapped;
198
+ }
199
+
200
+ export function openCodeEvaluator(options = {}) {
201
+ if (options.preflight?.status !== "passed" || options.preflight.surface !== "opencode") {
202
+ throw new Error("OpenCode evaluator requires a successful current-invocation preflight.");
203
+ }
204
+ const executable = options.openCodeBin ?? process.env.AGDF_OPENCODE_BIN ?? "opencode";
205
+ const agentName = options.preflight.agent ?? OPENCODE_EVALUATOR_AGENT;
206
+ return {
207
+ name: "opencode",
208
+ metadata: {
209
+ name: "opencode",
210
+ runtime: options.preflight.runtime,
211
+ agent: agentName,
212
+ preflight: options.preflight.code,
213
+ },
214
+ async evaluate(input, candidate) {
215
+ const args = [
216
+ "run", "--pure", "--agent", agentName, "--format", "json", "--dir", options.cwd,
217
+ ];
218
+ if (options.model) args.push("--model", options.model);
219
+ args.push(buildEvaluatorPrompt(input, candidate));
220
+ try {
221
+ const raw = guardedExecFileSync(executable, args, {
222
+ cwd: options.cwd,
223
+ env: options.preflight.env,
224
+ encoding: "utf8",
225
+ stdio: ["ignore", "pipe", "pipe"],
226
+ timeout: options.timeoutMs ?? 120000,
227
+ });
228
+ return validateEvaluation(parseOpenCodeEvaluatorOutput(raw), candidate.id);
229
+ } catch (error) {
230
+ throw fatalEvaluatorError(error);
231
+ }
232
+ },
233
+ };
234
+ }
@@ -0,0 +1,13 @@
1
+ export function buildEvaluatorPrompt(input, candidate) {
2
+ return [
3
+ "You are an evaluation-only component. Do not use tools, execute commands, inspect files, delegate work or modify state.",
4
+ `Return only one JSON object matching contract version ${input.contract_version}.`,
5
+ `Objective: ${input.objective}`,
6
+ `Current gate: ${input.current_gate}`,
7
+ `Candidate id: ${candidate.id}`,
8
+ `Candidate action: ${candidate.action}`,
9
+ "Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
10
+ "Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
11
+ "Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
12
+ ].join("\n");
13
+ }
@@ -114,6 +114,7 @@ export async function runDeliveryPathSearch(inputValue, evaluator, options = {})
114
114
  }
115
115
  }
116
116
  } catch (error) {
117
+ if (error?.fatalEvaluator === true) throw error;
117
118
  rejected.push({ candidate_id: candidate.id, action: candidate.action, reason: `invalid_evaluation: ${error.message}` });
118
119
  }
119
120
  }