create-agdf 0.11.2 → 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 (48) hide show
  1. package/README.md +5 -4
  2. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +1 -1
  3. package/generated/.github/skills/agdf-gate-check/SKILL.md +9 -0
  4. package/generated/.github/skills/agdf-interaction-locales.json +38 -0
  5. package/generated/.github/skills/contracts/control-scaffold.md +2 -0
  6. package/generated/.github/skills/contracts/interaction.md +19 -0
  7. package/generated/.opencode/agdf-interaction-locales.json +38 -0
  8. package/generated/.opencode/contracts/control-scaffold.md +2 -0
  9. package/generated/.opencode/contracts/interaction.md +19 -0
  10. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +9 -0
  11. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  12. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  13. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +1 -1
  14. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +38 -0
  15. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  16. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
  17. package/generated/plugins/agdf/meta/contracts/interaction.md +19 -0
  18. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +38 -0
  19. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  20. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +2 -2
  21. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +78 -14
  22. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +1 -0
  23. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +2 -11
  24. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +2 -11
  25. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
  26. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
  27. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +1 -0
  28. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  29. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +63 -0
  30. package/generated/plugins/agdf/runtime/create-agdf/package.json +3 -2
  31. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  32. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +22 -0
  33. package/generated/plugins/agdf/skills/gate-check/SKILL.md +9 -0
  34. package/lib/cli/application.js +17 -2
  35. package/lib/cli/command-registry.js +2 -2
  36. package/lib/cli/delivery-path-search-command.js +78 -14
  37. package/lib/control-evaluation/shared.js +1 -0
  38. package/lib/delivery-path-search/evaluators/claude.js +2 -11
  39. package/lib/delivery-path-search/evaluators/codex.js +2 -11
  40. package/lib/delivery-path-search/evaluators/opencode.js +234 -0
  41. package/lib/delivery-path-search/evaluators/prompt.js +13 -0
  42. package/lib/delivery-path-search/search-engine.js +1 -0
  43. package/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  44. package/lib/installers/opencode.js +214 -10
  45. package/lib/interaction-presentation.js +63 -0
  46. package/lib/lifecycle/operations.js +4 -0
  47. package/opencode-plugin.js +19 -2
  48. package/package.json +3 -2
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  import { validateEnforcement } from "../contracts.js";
2
2
 
3
- export function enforcementForSurface(surface, evidence = []) {
3
+ export function enforcementForSurface(surface, evidence = [], context = {}) {
4
4
  const defaults = {
5
5
  codex: { level: "tool_enforced", evidence: ["codex exec --sandbox read-only --ephemeral"] },
6
6
  claude: { level: "tool_enforced", evidence: ["claude -p --disallowedTools Edit,Write,Bash"] },
@@ -9,6 +9,19 @@ export function enforcementForSurface(surface, evidence = []) {
9
9
  generic: { level: "instruction_only", evidence: [] },
10
10
  };
11
11
  const selected = structuredClone(defaults[surface] ?? defaults.generic);
12
- if (evidence.length) selected.evidence = evidence;
12
+ if (surface === "opencode") {
13
+ const preflight = context.preflight;
14
+ if (preflight?.surface === "opencode"
15
+ && preflight.status === "passed"
16
+ && typeof preflight.invocation_id === "string"
17
+ && preflight.invocation_id
18
+ && Array.isArray(preflight.evidence)
19
+ && preflight.evidence === evidence) {
20
+ selected.level = "tool_enforced";
21
+ selected.evidence = [...evidence];
22
+ }
23
+ } else if (evidence.length) {
24
+ selected.evidence = evidence;
25
+ }
13
26
  return validateEnforcement(selected);
14
27
  }
@@ -349,6 +349,69 @@ export function renderOperationalStatusCard(statusCard, {
349
349
  });
350
350
  }
351
351
 
352
+ export function renderScopeClassificationCard(classification, {
353
+ registry,
354
+ requestedLocale,
355
+ } = {}) {
356
+ if (!plainObject(classification)) return null;
357
+ const outcome = String(classification.outcome ?? "").trim();
358
+ const mode = String(classification.mode ?? "").trim();
359
+ const trivialBoundary = String(classification.trivial_boundary ?? "").trim();
360
+ const urTriggerEvaluation = String(classification.ur_trigger_evaluation ?? "").trim();
361
+ const allowedSummary = String(classification.allowed_summary ?? "").trim();
362
+ const forbiddenSummary = String(classification.forbidden_summary ?? "").trim();
363
+ const escalationTriggers = Array.isArray(classification.escalation_triggers)
364
+ ? classification.escalation_triggers.map((item) => String(item ?? "").trim()).filter(Boolean)
365
+ : [];
366
+ const challengePath = String(classification.challenge_path ?? "").trim();
367
+
368
+ if (outcome !== "ungated") return null;
369
+ if (!["quick_task", "verified_change"].includes(mode)) return null;
370
+ if (!["inside", "outside"].includes(trivialBoundary)) return null;
371
+ if (!urTriggerEvaluation || !allowedSummary || !forbiddenSummary || !challengePath) return null;
372
+ if (escalationTriggers.length === 0) return null;
373
+
374
+ let locale;
375
+ try {
376
+ locale = resolvePresentationLocale(registry, requestedLocale);
377
+ } catch {
378
+ return null;
379
+ }
380
+ if (!locale) return null;
381
+
382
+ const pack = localePack(registry, locale);
383
+ const labels = pack?.scopeClassification;
384
+ if (!plainObject(labels)) return null;
385
+
386
+ const modeLabel = labels.mode?.[mode] ?? mode;
387
+ const boundaryLabel = labels.boundary?.[trivialBoundary] ?? trivialBoundary;
388
+
389
+ const rows = [
390
+ [labels.classification, `${modeLabel} · ${boundaryLabel}`],
391
+ [labels.grounds, urTriggerEvaluation],
392
+ [labels.allowed, allowedSummary],
393
+ [labels.forbidden, forbiddenSummary],
394
+ [labels.escalation, escalationTriggers.join("; ")],
395
+ [labels.challenge, challengePath],
396
+ ];
397
+
398
+ const markdown = [
399
+ `## ${labels.title ?? "AGDF scope classification"}`,
400
+ "",
401
+ `| ${labels.field ?? "Field"} | ${labels.value ?? "Value"} |`,
402
+ "|---|---|",
403
+ ...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
404
+ ].join("\n");
405
+
406
+ return Object.freeze({
407
+ schema_version: "1",
408
+ semantic_block: "scope_classification_card",
409
+ presentation_language: locale,
410
+ markdown,
411
+ authorizes: false,
412
+ });
413
+ }
414
+
352
415
  export function gateOptions(registry, requestedLocale, gate, { includeCancel = false } = {}) {
353
416
  const pack = localePack(registry, requestedLocale);
354
417
  const options = [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agdf",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "description": "Bootstrap AGDF repository-local plugin files, Copilot/OpenCode instructions, skills, control files and machine-readable checks.",
5
5
  "type": "module",
6
6
  "files": [
@@ -24,6 +24,7 @@
24
24
  "test:delivery-path-search": "node ./scripts/delivery-path-search-test.js",
25
25
  "test:delivery-path-search-unit": "node ./scripts/delivery-path-search-unit-test.js",
26
26
  "test:delivery-path-search-generator": "node ./scripts/delivery-path-search-generator-test.js",
27
+ "test:opencode-hardening": "node ./scripts/opencode-hardening-test.js",
27
28
  "test:cli-modularization": "node ./scripts/cli-modularization-test.js",
28
29
  "test:local-validator": "node ./scripts/local-validator-test.js",
29
30
  "test:local-marketplace": "node ./scripts/local-marketplace-test.js",
@@ -40,7 +41,7 @@
40
41
  "eval:skills:record": "node ./scripts/record-skill-evals.js",
41
42
  "test:release-bootstrap": "node ./scripts/release-bootstrap-smoke-test.js",
42
43
  "test:routing": "npm run sync-package-assets && node ./scripts/test-routing.js",
43
- "smoke-test": "npm run sync-package-assets && npm run test:cli-modularization && npm run test:local-validator && npm run test:local-marketplace && npm run test:package-build && npm run test:package-contents && npm run test:lifecycle && npm run test:control-state && npm run test:interaction-presentation && npm run test:verified-change && npm run test:runtime-integrity-layout && npm run test:runtime-integrity-negative && npm run test:skill-evals && npm run eval:skills && npm run test:delivery-path-search && npm run test:delivery-path-search-unit && npm run test:delivery-path-search-generator && node ./scripts/smoke-test.js && node ./scripts/test-routing.js",
44
+ "smoke-test": "npm run sync-package-assets && npm run test:cli-modularization && npm run test:local-validator && npm run test:local-marketplace && npm run test:package-build && npm run test:package-contents && npm run test:lifecycle && npm run test:control-state && npm run test:interaction-presentation && npm run test:verified-change && npm run test:runtime-integrity-layout && npm run test:runtime-integrity-negative && npm run test:skill-evals && npm run eval:skills && npm run test:delivery-path-search && npm run test:delivery-path-search-unit && npm run test:delivery-path-search-generator && npm run test:opencode-hardening && node ./scripts/smoke-test.js && node ./scripts/test-routing.js",
44
45
  "prepack": "npm run sync-package-assets"
45
46
  },
46
47
  "keywords": [
@@ -2,7 +2,7 @@
2
2
  "schema_version": "1",
3
3
  "generated": true,
4
4
  "source": "create-agdf/cli",
5
- "version": "0.11.2",
5
+ "version": "0.11.4",
6
6
  "entrypoint": "create-agdf/bin/agdf-validator.js",
7
- "digest": "a4508a485d3954d88c3ba4cd12f007c046f803d181b40bf97ada2f81913224cc"
7
+ "digest": "a3dcdf6d1b66e4789ab10339253e69b967d2d4f2421b52a1d50212e9a8553ec1"
8
8
  }
@@ -400,6 +400,28 @@ for (const antiPattern of [
400
400
  if (!runtimeContract.includes("### Repository Activation Diagnosis Boundary")) {
401
401
  failures.push("control-scaffold contract must own the repository activation diagnosis boundary");
402
402
  }
403
+ if (!runtimeContract.includes("### Scope Classification Card")) {
404
+ failures.push("interaction contract must own the scope classification card section");
405
+ }
406
+ if (!gateCheckSkill.includes("scope_classification.markdown` verbatim")) {
407
+ failures.push("gate-check must consume the canonical scope classification projection verbatim");
408
+ }
409
+ if (gateCheckSkill.includes("## Scope Classification Card Template") || gateCheckSkill.includes("| Classification | Mode | Boundary |")) {
410
+ failures.push("gate-check must not maintain a skill-local scope classification card template");
411
+ }
412
+ const interactionPresentationPath = join(pluginRoot, "..", "create-agdf", "lib", "interaction-presentation.js");
413
+ if (sourceMode && isFile(interactionPresentationPath)) {
414
+ const interactionPresentation = read(interactionPresentationPath);
415
+ if (!interactionPresentation.includes("export function renderScopeClassificationCard")) {
416
+ failures.push("interaction-presentation.js must export renderScopeClassificationCard");
417
+ }
418
+ }
419
+ for (const locale of ["en", "de"]) {
420
+ const pack = interactionLocales?.locales?.[locale];
421
+ if (!pack?.scopeClassification?.title || !pack?.scopeClassification?.mode?.quick_task || !pack?.scopeClassification?.challenge) {
422
+ failures.push(`Interaction locale ${locale} missing scopeClassification section keys`);
423
+ }
424
+ }
403
425
  if (!runtimeContract.includes("not an agent-facing diagnosis proof")) {
404
426
  failures.push("control-scaffold contract must classify AGDF_* env vars as non-agent-facing diagnosis");
405
427
  }
@@ -169,6 +169,15 @@ If a status is not explicit, do not assume it is satisfied.
169
169
  11. 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.
170
170
  12. If branch, workspace and durable artefacts disagree, do not choose a scope silently; report the ambiguity and ask for the smallest clarifying gate action.
171
171
 
172
+ ### Scope Classification Output
173
+ When this skill classifies a fresh scope as ungated (Quick Task or Trivial Change Boundary), consume
174
+ `scope_classification.markdown` verbatim from `renderScopeClassificationCard` in
175
+ `../../meta/contracts/interaction.md` § Scope Classification Card. The card is a compact, localized,
176
+ non-authorizing projection: mode, boundary, UR-trigger evaluation, one allowed line, one forbidden
177
+ line, escalation triggers and the challenge path. Do not maintain or render a skill-local
178
+ classification card template. If the renderer returns `null`, fail closed to the existing ceremony
179
+ and do not model-reconstruct Markdown.
180
+
172
181
  ## Output
173
182
  Keep the result short and operational. For a status-only response, consume the Runtime Contract's
174
183
  code-owned `status_presentation.markdown` verbatim. It is the compact localized operational Run
@@ -117,7 +117,8 @@ function createHandlers({ io, env, exec, prepare }) {
117
117
  const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
118
118
  const verificationHealthy = report.status === "configured"
119
119
  && report.package.version_status === "current"
120
- && report.global_native_surface.complete;
120
+ && report.global_native_surface.complete
121
+ && report.experimental_hooks.aggregate === "declared_supported";
121
122
  printLifecycleResult(createLifecycleResult({
122
123
  operation: result.transition.status === "updated" ? "update" : "install",
123
124
  result: verificationHealthy ? "success" : "partial",
@@ -130,10 +131,24 @@ function createHandlers({ io, env, exec, prepare }) {
130
131
  status: report.package.version_status === "current" ? "verified" : "unknown",
131
132
  transition: result.transition.status,
132
133
  },
133
- verification: { status: verificationHealthy ? "healthy" : "degraded", evidence: [report.global_config.path, report.global_native_surface.path] },
134
+ verification: {
135
+ status: verificationHealthy ? "healthy" : "degraded",
136
+ evidence: [
137
+ report.global_config.path,
138
+ report.global_native_surface.path,
139
+ `opencode_host=${report.host.installed_version || "unknown"}`,
140
+ `plugin_sdk=${report.plugin_sdk.installed_version || "unknown"}`,
141
+ `experimental_hooks=${report.experimental_hooks.aggregate}`,
142
+ `host_sdk_version=${report.host_sdk_version.status};policy=${report.host_sdk_version.policy}`,
143
+ ],
144
+ },
134
145
  restart: { required: true, reason: "host_reload" },
135
146
  next_action: globalInstallRestartAction(options.target),
136
147
  }), { json: options.json, io });
148
+ if (!options.json) {
149
+ 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})`);
150
+ io.log(`Experimental hook declarations: ${report.experimental_hooks.aggregate} (SDK declaration evidence; live invocation not observed)`);
151
+ }
137
152
  return verificationHealthy ? 0 : 1;
138
153
  } catch (error) {
139
154
  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>]"] }),