create-agdf 0.11.3 → 0.12.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 +5 -4
- package/generated/.agdf/control/templates/MASTER_BACKLOG.md +1 -1
- package/generated/.agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +25 -0
- package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +26 -0
- package/generated/.github/skills/agdf-gate-check/SKILL.md +40 -18
- package/generated/.github/skills/agdf-interaction-locales.json +50 -0
- package/generated/.github/skills/agdf-runtime-contract.md +1 -0
- package/generated/.github/skills/contracts/control-scaffold.md +2 -0
- package/generated/.github/skills/contracts/gate-transition.md +7 -3
- package/generated/.github/skills/contracts/interaction.md +22 -0
- package/generated/.github/skills/contracts/modes.md +81 -0
- package/generated/.github/skills/contracts/task-target-resolution.md +102 -0
- package/generated/.opencode/AGDF.md +2 -0
- package/generated/.opencode/agdf-interaction-locales.json +50 -0
- package/generated/.opencode/agdf-runtime-contract.md +1 -0
- package/generated/.opencode/contracts/control-scaffold.md +2 -0
- package/generated/.opencode/contracts/gate-transition.md +7 -3
- package/generated/.opencode/contracts/interaction.md +22 -0
- package/generated/.opencode/contracts/modes.md +81 -0
- package/generated/.opencode/contracts/task-target-resolution.md +102 -0
- package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +26 -0
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +40 -18
- package/generated/AGENTS.md +1 -0
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +1 -1
- package/generated/plugins/agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +25 -0
- package/generated/plugins/agdf/meta/agdf-agent-router.md +16 -0
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +50 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
- package/generated/plugins/agdf/meta/agdf-runtime-contract.md +1 -0
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
- package/generated/plugins/agdf/meta/contracts/gate-transition.md +7 -3
- package/generated/plugins/agdf/meta/contracts/interaction.md +22 -0
- package/generated/plugins/agdf/meta/contracts/modes.md +81 -0
- package/generated/plugins/agdf/meta/contracts/task-target-resolution.md +102 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +50 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +2 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +78 -14
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +14 -7
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +1 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +2 -11
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +2 -11
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +1 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +15 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +85 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +6 -2
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +119 -3
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +26 -0
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +40 -18
- package/lib/cli/application.js +29 -3
- package/lib/cli/command-registry.js +2 -2
- package/lib/cli/delivery-path-search-command.js +78 -14
- package/lib/control-evaluation/gate-policy.js +14 -7
- package/lib/control-evaluation/shared.js +1 -0
- package/lib/delivery-path-search/evaluators/claude.js +2 -11
- package/lib/delivery-path-search/evaluators/codex.js +2 -11
- package/lib/delivery-path-search/evaluators/opencode.js +234 -0
- package/lib/delivery-path-search/evaluators/prompt.js +13 -0
- package/lib/delivery-path-search/search-engine.js +1 -0
- package/lib/delivery-path-search/surfaces/capabilities.js +15 -2
- package/lib/installers/opencode.js +341 -10
- package/lib/interaction-presentation.js +85 -0
- package/lib/lifecycle/operations.js +4 -0
- package/lib/live-agent/read-only-structured.js +43 -0
- package/lib/proportionality-benchmark/blind-prompt.js +37 -0
- package/lib/proportionality-benchmark/contracts.js +120 -0
- package/lib/proportionality-benchmark/corpus-loader.js +110 -0
- package/lib/proportionality-benchmark/evaluator.js +250 -0
- package/lib/proportionality-benchmark/index.js +7 -0
- package/lib/proportionality-benchmark/live-recorder.js +100 -0
- package/lib/proportionality-benchmark/report.js +51 -0
- package/lib/proportionality-benchmark/source-fingerprint.js +30 -0
- package/lib/skill-evals/live-recorder.js +3 -26
- package/opencode-plugin.js +59 -3
- package/package.json +6 -2
|
@@ -20,6 +20,7 @@ This skill must not create later artefacts such as PRD, SD, TP, CD, CR, QA, or U
|
|
|
20
20
|
## Runtime Contract
|
|
21
21
|
Use these focused runtime-contract modules:
|
|
22
22
|
|
|
23
|
+
- `../../meta/contracts/task-target-resolution.md`
|
|
23
24
|
- `../../meta/contracts/gate-transition.md`
|
|
24
25
|
- `../../meta/contracts/interaction.md`
|
|
25
26
|
- `../../meta/contracts/control-scaffold.md`
|
|
@@ -75,6 +76,12 @@ The CLI reports are validators and JSON evidence, not the primary user experienc
|
|
|
75
76
|
|
|
76
77
|
Plugin `AGDF_*` environment variables set by the OpenCode `shell.env` hook are convenience only and must not be used as the only proof of activation; host shell-env propagation to spawned tool shells is not guaranteed and is OpenCode-owned, not AGDF-owned. A relative glob or `grep` for `.agdf/control/config.json` must not be used as proof of presence or absence of `.agdf/control/config.json`; an absolute `read` or the canonical CLI probe is required.
|
|
77
78
|
|
|
79
|
+
## OpenCode Subagent Enforcement Boundary
|
|
80
|
+
|
|
81
|
+
OpenCode plugin hooks (`tool.execute.before`) do not intercept tool calls from subagents spawned via the `task` tool (anomalyco/opencode issue #5894, PR #36238 open). AGDF enforcement through plugin hooks applies to primary-agent tool calls only; in the subagent path, AGDF governance is auditing-only, not enforcement.
|
|
82
|
+
|
|
83
|
+
This is a host limitation, not an AGDF-owned defect. Do not claim that AGDF gates are technically enforced in the OpenCode subagent path. Do not route work to a subagent to bypass a primary-agent gate. When subagent work touches gate-relevant files, verify the result through the canonical validator (`doctor --json`, `gate-check --json`) after the subagent returns, not during.
|
|
84
|
+
|
|
78
85
|
## Native Interaction Path
|
|
79
86
|
|
|
80
87
|
`../../meta/contracts/interaction.md` is the complete normative owner for interaction kinds, locale,
|
|
@@ -84,12 +91,15 @@ do not restate or infer a surface matrix in this skill.
|
|
|
84
91
|
|
|
85
92
|
This skill owns only these operational responsibilities:
|
|
86
93
|
|
|
87
|
-
1.
|
|
88
|
-
2.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
1. Resolve or revalidate the primary task target before selecting repository control state.
|
|
95
|
+
2. Derive repository activation only from the resolved governance target; an evidence source or
|
|
96
|
+
working directory is not sufficient.
|
|
97
|
+
3. Select exactly one run and evaluate its current gate.
|
|
98
|
+
4. Confirm that the required durable artefact is present and ready.
|
|
99
|
+
5. Consume the canonical `approval_presentation` verbatim for the selected run, gate and revision.
|
|
100
|
+
6. Present that projection and obtain deliberate input through the contract-selected native or exact-text path.
|
|
101
|
+
7. Revalidate the same target, run, gate and revision immediately after the response and before persistence.
|
|
102
|
+
8. Persist only a currently valid exact approval through the existing control-state workflow.
|
|
93
103
|
|
|
94
104
|
For status, blocked, read-only or rationale interactions, consume the canonical
|
|
95
105
|
`status_presentation.markdown` verbatim and apply the locale contract without asking for approval.
|
|
@@ -156,18 +166,30 @@ Use what is available:
|
|
|
156
166
|
If a status is not explicit, do not assume it is satisfied.
|
|
157
167
|
|
|
158
168
|
## Workflow
|
|
159
|
-
1.
|
|
160
|
-
2.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
1. Resolve or revalidate the primary target through `task-target-resolution.md`.
|
|
170
|
+
2. If resolution is unresolved, consume the canonical target orientation, request only its
|
|
171
|
+
clarification/recovery action and stop before repository activation, gate evaluation or mutation.
|
|
172
|
+
3. Derive the governance target from the primary target; never from `cwd` or an evidence source alone.
|
|
173
|
+
4. Check exact approvals.
|
|
174
|
+
5. Check artefact status.
|
|
175
|
+
6. Determine the earliest blocking user gate or internal mandatory step.
|
|
176
|
+
7. Derive allowed and forbidden outputs.
|
|
177
|
+
8. Name the exact missing approval, if any.
|
|
178
|
+
9. If consent was only implicit, say it is not yet approval and provide the exact formula.
|
|
179
|
+
10. Ensure the next step follows the gate transition table. In particular, never jump from `Approval: UR` to implementation.
|
|
180
|
+
11. Treat a generic "start", "continue" or "leg los" request as a request to perform only the current next allowed action.
|
|
181
|
+
12. After Brownfield Review, choose the smallest safe process path before creating later artefacts.
|
|
182
|
+
13. If the selected path is not visibly recorded with scope reason and evidence, keep the run at `Mode/Slice Decision`.
|
|
183
|
+
14. When `.agdf/control/` is missing or incomplete, make the next allowed artefact action explicit. For a fresh request, that means draft the minimal UR in the response, then request `Approval: UR`. Do not write a full control scaffold unless durable control state was explicitly requested or is already the repository's live AGDF working state.
|
|
184
|
+
15. If branch, workspace and durable artefacts disagree, do not choose a scope silently; report the ambiguity and ask for the smallest clarifying gate action.
|
|
185
|
+
|
|
186
|
+
### Task Target Orientation
|
|
187
|
+
|
|
188
|
+
When target/context separation is material, the target changed, or resolution is unresolved, consume
|
|
189
|
+
`task_target_orientation.markdown` verbatim from `renderTaskTargetOrientation` in
|
|
190
|
+
`../../meta/contracts/interaction.md`. Do not reconstruct its fields or maintain a skill-local target
|
|
191
|
+
template. If the renderer returns `null`, fail closed to target clarification and do not proceed to
|
|
192
|
+
repository activation, Scope Classification or gate evaluation.
|
|
171
193
|
|
|
172
194
|
### Scope Classification Output
|
|
173
195
|
When this skill classifies a fresh scope as ungated (Quick Task or Trivial Change Boundary), consume
|
package/lib/cli/application.js
CHANGED
|
@@ -115,9 +115,19 @@ function createHandlers({ io, env, exec, prepare }) {
|
|
|
115
115
|
const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir));
|
|
116
116
|
runLifecyclePhase("global_surface", () => installOpenCodeGlobalSurface(configDir));
|
|
117
117
|
const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
|
|
118
|
+
const alignmentHealthy = ["already_matching", "aligned"].includes(result.sdk_alignment.status);
|
|
118
119
|
const verificationHealthy = report.status === "configured"
|
|
119
120
|
&& report.package.version_status === "current"
|
|
120
|
-
&& report.global_native_surface.complete
|
|
121
|
+
&& report.global_native_surface.complete
|
|
122
|
+
&& report.experimental_hooks.aggregate === "declared_supported"
|
|
123
|
+
&& report.host_sdk_version.status === "matching"
|
|
124
|
+
&& alignmentHealthy;
|
|
125
|
+
const nextAction = verificationHealthy
|
|
126
|
+
? globalInstallRestartAction(options.target)
|
|
127
|
+
: {
|
|
128
|
+
kind: "recovery",
|
|
129
|
+
text: `Retry the OpenCode installation to align @opencode-ai/plugin to ${result.sdk_alignment.target_version || "the exact host version"}; observed SDK: ${result.sdk_alignment.installed_version || "unknown"}.`,
|
|
130
|
+
};
|
|
121
131
|
printLifecycleResult(createLifecycleResult({
|
|
122
132
|
operation: result.transition.status === "updated" ? "update" : "install",
|
|
123
133
|
result: verificationHealthy ? "success" : "partial",
|
|
@@ -130,10 +140,26 @@ function createHandlers({ io, env, exec, prepare }) {
|
|
|
130
140
|
status: report.package.version_status === "current" ? "verified" : "unknown",
|
|
131
141
|
transition: result.transition.status,
|
|
132
142
|
},
|
|
133
|
-
verification: {
|
|
143
|
+
verification: {
|
|
144
|
+
status: verificationHealthy ? "healthy" : "degraded",
|
|
145
|
+
evidence: [
|
|
146
|
+
report.global_config.path,
|
|
147
|
+
report.global_native_surface.path,
|
|
148
|
+
`opencode_host=${report.host.installed_version || "unknown"}`,
|
|
149
|
+
`plugin_sdk=${report.plugin_sdk.installed_version || "unknown"}`,
|
|
150
|
+
`experimental_hooks=${report.experimental_hooks.aggregate}`,
|
|
151
|
+
`host_sdk_version=${report.host_sdk_version.status};policy=${report.host_sdk_version.policy}`,
|
|
152
|
+
`sdk_alignment=${result.sdk_alignment.status};target=${result.sdk_alignment.target_version || "unknown"};installed=${result.sdk_alignment.installed_version || "unknown"}`,
|
|
153
|
+
],
|
|
154
|
+
},
|
|
134
155
|
restart: { required: true, reason: "host_reload" },
|
|
135
|
-
next_action:
|
|
156
|
+
next_action: nextAction,
|
|
136
157
|
}), { json: options.json, io });
|
|
158
|
+
if (!options.json) {
|
|
159
|
+
io.log(`OpenCode host / plugin SDK: ${report.host.installed_version || "unknown"} / ${report.plugin_sdk.installed_version || "unknown"} (${report.host_sdk_version.status}; ${report.host_sdk_version.policy})`);
|
|
160
|
+
io.log(`Plugin SDK alignment: ${result.sdk_alignment.status} (target ${result.sdk_alignment.target_version || "unknown"}; installed ${result.sdk_alignment.installed_version || "unknown"})`);
|
|
161
|
+
io.log(`Experimental hook declarations: ${report.experimental_hooks.aggregate} (SDK declaration evidence; live invocation not observed)`);
|
|
162
|
+
}
|
|
137
163
|
return verificationHealthy ? 0 : 1;
|
|
138
164
|
} catch (error) {
|
|
139
165
|
printInstallFailure(options.target, error, options, io);
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
:
|
|
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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
}
|
|
@@ -29,8 +29,9 @@ export function isGateSatisfied(runState, gate) {
|
|
|
29
29
|
return isDurableGateArtefactSatisfied(runState, gate);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function
|
|
33
|
-
|
|
32
|
+
function qaNonPassDecision(runState) {
|
|
33
|
+
const status = gateArtefactStatus(runState, "QA").status;
|
|
34
|
+
return status === "revise" || status === "block" ? status : null;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function transitionDecisionForRunState(runState, verifiedChange = null) {
|
|
@@ -233,15 +234,21 @@ export function transitionDecisionForRunState(runState, verifiedChange = null) {
|
|
|
233
234
|
};
|
|
234
235
|
}
|
|
235
236
|
|
|
236
|
-
|
|
237
|
+
const qaNonPass = qaNonPassDecision(runState);
|
|
238
|
+
if (qaNonPass) {
|
|
239
|
+
const blocked = qaNonPass === "block";
|
|
237
240
|
return {
|
|
238
|
-
status: "open",
|
|
241
|
+
status: blocked ? "blocked" : "open",
|
|
239
242
|
current_gate: "QA",
|
|
240
|
-
blocking_reason: "qa_revise_required",
|
|
243
|
+
blocking_reason: blocked ? "qa_blocked" : "qa_revise_required",
|
|
241
244
|
missing_approval: "none",
|
|
242
|
-
allowed:
|
|
245
|
+
allowed: blocked
|
|
246
|
+
? ["route the blocking QA findings to their authoritative owner", "record the next permitted remediation or upstream decision", "rerun QA only after the blocker is resolved"]
|
|
247
|
+
: ["revise the implementation against the QA findings", "refresh CD+Tests and mandatory reviews", "rerun QA with refreshed evidence"],
|
|
243
248
|
forbidden: ["request QA approval", "request UAT approval", "release", "claim delivery readiness"],
|
|
244
|
-
next_allowed_action:
|
|
249
|
+
next_allowed_action: blocked
|
|
250
|
+
? "Resolve or route the blocking QA findings through their authoritative owner, then rerun the required delivery steps. Do not request Approval: QA from a block report."
|
|
251
|
+
: "Resolve the QA revise findings, refresh CD+Tests and reviews, then rerun QA. Do not request Approval: QA from a revise report.",
|
|
245
252
|
};
|
|
246
253
|
}
|
|
247
254
|
|
|
@@ -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
|
}
|