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.
Files changed (80) hide show
  1. package/README.md +5 -4
  2. package/generated/.agdf/control/templates/MASTER_BACKLOG.md +1 -1
  3. package/generated/.agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +25 -0
  4. package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +26 -0
  5. package/generated/.github/skills/agdf-gate-check/SKILL.md +40 -18
  6. package/generated/.github/skills/agdf-interaction-locales.json +50 -0
  7. package/generated/.github/skills/agdf-runtime-contract.md +1 -0
  8. package/generated/.github/skills/contracts/control-scaffold.md +2 -0
  9. package/generated/.github/skills/contracts/gate-transition.md +7 -3
  10. package/generated/.github/skills/contracts/interaction.md +22 -0
  11. package/generated/.github/skills/contracts/modes.md +81 -0
  12. package/generated/.github/skills/contracts/task-target-resolution.md +102 -0
  13. package/generated/.opencode/AGDF.md +2 -0
  14. package/generated/.opencode/agdf-interaction-locales.json +50 -0
  15. package/generated/.opencode/agdf-runtime-contract.md +1 -0
  16. package/generated/.opencode/contracts/control-scaffold.md +2 -0
  17. package/generated/.opencode/contracts/gate-transition.md +7 -3
  18. package/generated/.opencode/contracts/interaction.md +22 -0
  19. package/generated/.opencode/contracts/modes.md +81 -0
  20. package/generated/.opencode/contracts/task-target-resolution.md +102 -0
  21. package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +26 -0
  22. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +40 -18
  23. package/generated/AGENTS.md +1 -0
  24. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  25. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  26. package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +1 -1
  27. package/generated/plugins/agdf/control/templates/artefacts/BROWNFIELD_REVIEW.md +25 -0
  28. package/generated/plugins/agdf/meta/agdf-agent-router.md +16 -0
  29. package/generated/plugins/agdf/meta/agdf-interaction-locales.json +50 -0
  30. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  31. package/generated/plugins/agdf/meta/agdf-runtime-contract.md +1 -0
  32. package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
  33. package/generated/plugins/agdf/meta/contracts/gate-transition.md +7 -3
  34. package/generated/plugins/agdf/meta/contracts/interaction.md +22 -0
  35. package/generated/plugins/agdf/meta/contracts/modes.md +81 -0
  36. package/generated/plugins/agdf/meta/contracts/task-target-resolution.md +102 -0
  37. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +50 -0
  38. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
  39. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +2 -2
  40. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +78 -14
  41. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +14 -7
  42. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +1 -0
  43. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +2 -11
  44. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +2 -11
  45. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
  46. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
  47. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +1 -0
  48. package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  49. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +85 -0
  50. package/generated/plugins/agdf/runtime/create-agdf/package.json +6 -2
  51. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  52. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +119 -3
  53. package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +26 -0
  54. package/generated/plugins/agdf/skills/gate-check/SKILL.md +40 -18
  55. package/lib/cli/application.js +29 -3
  56. package/lib/cli/command-registry.js +2 -2
  57. package/lib/cli/delivery-path-search-command.js +78 -14
  58. package/lib/control-evaluation/gate-policy.js +14 -7
  59. package/lib/control-evaluation/shared.js +1 -0
  60. package/lib/delivery-path-search/evaluators/claude.js +2 -11
  61. package/lib/delivery-path-search/evaluators/codex.js +2 -11
  62. package/lib/delivery-path-search/evaluators/opencode.js +234 -0
  63. package/lib/delivery-path-search/evaluators/prompt.js +13 -0
  64. package/lib/delivery-path-search/search-engine.js +1 -0
  65. package/lib/delivery-path-search/surfaces/capabilities.js +15 -2
  66. package/lib/installers/opencode.js +341 -10
  67. package/lib/interaction-presentation.js +85 -0
  68. package/lib/lifecycle/operations.js +4 -0
  69. package/lib/live-agent/read-only-structured.js +43 -0
  70. package/lib/proportionality-benchmark/blind-prompt.js +37 -0
  71. package/lib/proportionality-benchmark/contracts.js +120 -0
  72. package/lib/proportionality-benchmark/corpus-loader.js +110 -0
  73. package/lib/proportionality-benchmark/evaluator.js +250 -0
  74. package/lib/proportionality-benchmark/index.js +7 -0
  75. package/lib/proportionality-benchmark/live-recorder.js +100 -0
  76. package/lib/proportionality-benchmark/report.js +51 -0
  77. package/lib/proportionality-benchmark/source-fingerprint.js +30 -0
  78. package/lib/skill-evals/live-recorder.js +3 -26
  79. package/opencode-plugin.js +59 -3
  80. package/package.json +6 -2
@@ -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
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { homedir } from "node:os";
4
- import { dirname, join } from "node:path";
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import process from "node:process";
6
6
  import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
7
7
  import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
@@ -14,6 +14,7 @@ const globalOpenCodeSkillOwnershipMarker = "<!-- AGDF-GLOBAL-SKILL: ";
14
14
  const globalOpenCodeInstructionsOwnershipMarker = "<!-- AGDF-GLOBAL-INSTRUCTIONS -->";
15
15
  const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->";
16
16
  const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
17
+ const globalOpenCodeAgentOwnershipMarker = `<!-- AGDF-GLOBAL-AGENT: ${pluginDefinition.opencode.evaluatorAgentName} -->`;
17
18
  const globalOpenCodeValidatorPackageOwner = "create-agdf";
18
19
  const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
19
20
  const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
@@ -115,11 +116,13 @@ export function installOpenCodeGlobalPlugin(configDir) {
115
116
  throw openCodeLifecycleError("configuration", `Failed to write OpenCode config ${configPath}: ${error.message}`, { configPath });
116
117
  }
117
118
  const installedPackage = resolveOpenCodePackage(configDir);
119
+ const sdkAlignment = alignOpenCodePluginSdk(configDir);
118
120
 
119
121
  return {
120
122
  configPath,
121
123
  added: !alreadyInstalled,
122
124
  transition: openCodePackageTransition(previousPackage, installedPackage),
125
+ sdk_alignment: sdkAlignment,
123
126
  };
124
127
  }
125
128
 
@@ -131,6 +134,7 @@ function globalOpenCodeConfigPaths(configDir) {
131
134
  skills: join(configDir, "skills"),
132
135
  localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
133
136
  localValidatorPackage: join(configDir, "agdf", "package.json"),
137
+ evaluatorAgent: join(configDir, "agents", `${pluginDefinition.opencode.evaluatorAgentName}.md`),
134
138
  };
135
139
  }
136
140
 
@@ -179,6 +183,7 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
179
183
  assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
180
184
  assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
181
185
  assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
186
+ assertGlobalOpenCodeFileWritable(paths.evaluatorAgent, globalOpenCodeAgentOwnershipMarker, "after-frontmatter");
182
187
  assertGlobalOpenCodeValidatorPackageWritable(paths.localValidatorPackage);
183
188
  for (const moduleName of contractModules) {
184
189
  assertGlobalOpenCodeFileWritable(
@@ -212,6 +217,10 @@ function globalOpenCodeBoundary() {
212
217
  "Before applying AGDF gates, later artefacts or implementation guidance, inspect the current repository for valid `.agdf/control/config.json` durable control.",
213
218
  "If durable control is missing or invalid, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo` in this repository.",
214
219
  "When durable control is valid, use the global `agdf-global-*` skill surface; existing local OpenCode assets remain a compatibility path.",
220
+ "Only exact `Approval: <GateName>` values can advance AGDF gates; host permissions and plugin hooks never grant delivery authority.",
221
+ "Resolve and use the installed version-matched `agdf/bin/agdf-local.js` validator for deterministic checks.",
222
+ "If activation, approval, evidence or validator ownership is missing or unclear, stop before later artefacts or implementation.",
223
+ "Use `opencode-status --json` to inspect installed hook declarations, version divergence and evaluator availability without treating SDK declarations as live hook execution proof.",
215
224
  "",
216
225
  ].join("\n");
217
226
  }
@@ -255,10 +264,27 @@ export function installOpenCodeGlobalSurface(configDir) {
255
264
  ].join("\n");
256
265
  const globalRuntimeContract = `${globalOpenCodeRuntimeContractOwnershipMarker}\n${generatedRuntimeContract}`;
257
266
  const localValidator = `${globalOpenCodeValidatorOwnershipMarker}\nimport process from "node:process";\nimport { fileURLToPath } from "node:url";\nimport { runLocalValidator } from "../../node_modules/create-agdf/lib/runtime/local-validator.js";\n\nconst ownedPackageRoot = fileURLToPath(new URL("../../node_modules/create-agdf", import.meta.url));\nprocess.exitCode = runLocalValidator({ ownedPackageRoot, expectedVersion: ${JSON.stringify(pluginDefinition.version)}, surface: "opencode" }, process.argv.slice(2));\n`;
267
+ const evaluatorAgent = [
268
+ "---",
269
+ "description: Evaluate one AGDF Delivery Path Search candidate without tools or side effects.",
270
+ "mode: primary",
271
+ "permission:",
272
+ ' "*": deny',
273
+ "---",
274
+ globalOpenCodeAgentOwnershipMarker,
275
+ "",
276
+ "# AGDF Delivery Path Search evaluator",
277
+ "",
278
+ "Evaluate only the bounded candidate supplied in the user message.",
279
+ "Do not call tools, inspect files, delegate work or modify state.",
280
+ "Return only the requested JSON object; AGDF validates it independently.",
281
+ "",
282
+ ].join("\n");
258
283
 
259
284
  writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
260
285
  writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
261
286
  writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
287
+ writeOwnedGlobalOpenCodeFile(paths.evaluatorAgent, evaluatorAgent, globalOpenCodeAgentOwnershipMarker, "after-frontmatter");
262
288
  mkdirSync(dirname(paths.localValidatorPackage), { recursive: true });
263
289
  writeFileSync(paths.localValidatorPackage, `${JSON.stringify({
264
290
  name: "agdf-opencode-validator-runtime",
@@ -296,6 +322,7 @@ export function installOpenCodeGlobalSurface(configDir) {
296
322
  skills: paths.skills,
297
323
  localValidator: paths.localValidator,
298
324
  localValidatorPackage: paths.localValidatorPackage,
325
+ evaluatorAgent: paths.evaluatorAgent,
299
326
  };
300
327
  }
301
328
 
@@ -308,24 +335,64 @@ function readOpenCodeConfig(configPath) {
308
335
  }
309
336
  }
310
337
 
311
- function resolveOpenCodePackage(configDir) {
312
- const packageName = pluginDefinition.opencode.npmPackage;
338
+ export function resolveOpenCodeInstalledPackage(configDir, packageName, dependencies = {}) {
339
+ const run = dependencies.execFileSync ?? execFileSync;
313
340
  try {
314
- const resolvedPath = execFileSync(process.execPath, ["-e", `process.stdout.write(require.resolve(${JSON.stringify(packageName)}))`], {
341
+ const probe = [
342
+ 'const fs = require("node:fs");',
343
+ 'const path = require("node:path");',
344
+ `const packageName = ${JSON.stringify(packageName)};`,
345
+ 'let resolvedPath = "";',
346
+ 'try { resolvedPath = require.resolve(packageName); } catch {}',
347
+ 'let manifestPath = "";',
348
+ 'for (const root of require.resolve.paths(packageName) || []) {',
349
+ ' const candidate = path.join(root, packageName, "package.json");',
350
+ ' if (fs.existsSync(candidate)) { manifestPath = candidate; break; }',
351
+ '}',
352
+ 'if (!manifestPath) throw new Error(`installed package manifest not found: ${packageName}`);',
353
+ 'process.stdout.write(JSON.stringify({ resolvedPath, manifestPath }));',
354
+ ].join("\n");
355
+ const rawResolution = run(process.execPath, ["-e", probe], {
315
356
  cwd: configDir,
316
357
  encoding: "utf8",
317
358
  stdio: "pipe",
318
359
  });
319
- let installedVersion = "";
360
+ let resolvedPath = rawResolution;
361
+ let probedManifestPath = "";
320
362
  try {
321
- const packageManifest = JSON.parse(readFileSync(join(dirname(resolvedPath), "package.json"), "utf8"));
322
- installedVersion = typeof packageManifest.version === "string" && packageManifest.version.trim() ? packageManifest.version.trim() : "";
323
- } catch {
324
- installedVersion = "";
363
+ const parsed = JSON.parse(rawResolution);
364
+ resolvedPath = parsed.resolvedPath || parsed.manifestPath;
365
+ probedManifestPath = parsed.manifestPath;
366
+ } catch {}
367
+ let current = probedManifestPath ? dirname(probedManifestPath) : dirname(resolvedPath);
368
+ let manifestPath = "";
369
+ let packageRoot = "";
370
+ let manifest = null;
371
+ for (let depth = 0; depth < 20; depth += 1) {
372
+ const candidate = join(current, "package.json");
373
+ if (existsSync(candidate)) {
374
+ try {
375
+ const value = JSON.parse(readFileSync(candidate, "utf8"));
376
+ if (value?.name === packageName) {
377
+ manifestPath = candidate;
378
+ packageRoot = current;
379
+ manifest = value;
380
+ break;
381
+ }
382
+ } catch {}
383
+ }
384
+ const parent = dirname(current);
385
+ if (parent === current) break;
386
+ current = parent;
325
387
  }
388
+ if (!manifest) throw new Error(`resolved ${packageName} entry has no matching package manifest`);
389
+ const installedVersion = typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : "";
326
390
  return {
327
391
  loadable: true,
328
392
  path: resolvedPath,
393
+ manifest_path: manifestPath,
394
+ package_root: packageRoot,
395
+ manifest,
329
396
  installed_version: installedVersion,
330
397
  error: "",
331
398
  };
@@ -333,12 +400,252 @@ function resolveOpenCodePackage(configDir) {
333
400
  return {
334
401
  loadable: false,
335
402
  path: "",
403
+ manifest_path: "",
404
+ package_root: "",
405
+ manifest: null,
336
406
  installed_version: "",
337
407
  error: (error.stderr || error.message || "package not resolvable").toString().trim(),
338
408
  };
339
409
  }
340
410
  }
341
411
 
412
+ function resolveOpenCodePackage(configDir) {
413
+ return resolveOpenCodeInstalledPackage(configDir, pluginDefinition.opencode.npmPackage);
414
+ }
415
+
416
+ const requiredExperimentalHooks = [
417
+ "experimental.chat.system.transform",
418
+ "experimental.session.compacting",
419
+ ];
420
+
421
+ function packageLocalPath(packageRoot, candidate) {
422
+ if (!packageRoot || !candidate) return "";
423
+ const path = resolve(packageRoot, candidate);
424
+ const relation = relative(packageRoot, path);
425
+ return relation === "" || (relation !== ".." && !relation.startsWith(`..${sep}`) && !isAbsolute(relation)) ? path : "";
426
+ }
427
+
428
+ function openCodeHostProbe(dependencies = {}) {
429
+ const executable = dependencies.openCodeBin ?? process.env.AGDF_OPENCODE_BIN ?? "opencode";
430
+ const run = dependencies.execFileSync ?? execFileSync;
431
+ try {
432
+ return {
433
+ name: "opencode",
434
+ executable,
435
+ inspectable: true,
436
+ installed_version: run(executable, ["--version"], {
437
+ encoding: "utf8",
438
+ stdio: ["ignore", "pipe", "pipe"],
439
+ timeout: 5000,
440
+ }).trim(),
441
+ error: "",
442
+ };
443
+ } catch (error) {
444
+ return {
445
+ name: "opencode",
446
+ executable,
447
+ inspectable: false,
448
+ installed_version: "",
449
+ error: (error.stderr || error.message || "host version unavailable").toString().trim(),
450
+ };
451
+ }
452
+ }
453
+
454
+ function openCodeSdkProbe(configDir, dependencies = {}) {
455
+ const sdk = resolveOpenCodeInstalledPackage(configDir, "@opencode-ai/plugin", dependencies);
456
+ let declarationPath = "";
457
+ let declaration = "";
458
+ if (sdk.loadable) {
459
+ const declaredEntry = sdk.manifest?.types || sdk.manifest?.typings || "dist/index.d.ts";
460
+ declarationPath = packageLocalPath(sdk.package_root, declaredEntry);
461
+ if (declarationPath && existsSync(declarationPath)) {
462
+ try {
463
+ declaration = readFileSync(declarationPath, "utf8");
464
+ } catch (error) {
465
+ sdk.error = error.message;
466
+ }
467
+ } else {
468
+ sdk.error = `SDK declaration not found: ${declaredEntry}`;
469
+ }
470
+ }
471
+ const hooks = requiredExperimentalHooks.map((name) => ({
472
+ name,
473
+ state: !declaration
474
+ ? "uninspectable"
475
+ : declaration.includes(`"${name}"`)
476
+ ? "declared_supported"
477
+ : "declared_missing",
478
+ }));
479
+ const states = hooks.map((hook) => hook.state);
480
+ const aggregate = states.every((state) => state === "declared_supported")
481
+ ? "declared_supported"
482
+ : states.some((state) => state === "uninspectable")
483
+ ? "uninspectable"
484
+ : "degraded";
485
+ return {
486
+ package: {
487
+ name: "@opencode-ai/plugin",
488
+ loadable: sdk.loadable,
489
+ resolved_path: sdk.path,
490
+ manifest_path: sdk.manifest_path,
491
+ declaration_path: declarationPath,
492
+ installed_version: sdk.installed_version || null,
493
+ error: sdk.error,
494
+ },
495
+ hooks: {
496
+ evidence_level: "sdk_declaration",
497
+ aggregate,
498
+ hooks,
499
+ live_invocation_observed: false,
500
+ },
501
+ };
502
+ }
503
+
504
+ export function evaluateOpenCodeHostSdk(configDir, dependencies = {}) {
505
+ const host = openCodeHostProbe(dependencies);
506
+ const sdk = openCodeSdkProbe(configDir, dependencies);
507
+ return {
508
+ host,
509
+ plugin_sdk: sdk.package,
510
+ experimental_hooks: sdk.hooks,
511
+ host_sdk_version: {
512
+ status: !host.installed_version || !sdk.package.installed_version
513
+ ? "unknown"
514
+ : host.installed_version === sdk.package.installed_version
515
+ ? "matching"
516
+ : "divergent",
517
+ host_version: host.installed_version || null,
518
+ sdk_version: sdk.package.installed_version || null,
519
+ policy: "warn_only",
520
+ },
521
+ };
522
+ }
523
+
524
+ const exactOpenCodeVersionPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
525
+
526
+ function openCodeSdkAlignmentResult(status, initial, overrides = {}) {
527
+ return {
528
+ status,
529
+ attempted: false,
530
+ host_version: initial.host.installed_version || null,
531
+ previous_version: initial.plugin_sdk.installed_version || null,
532
+ target_version: null,
533
+ installed_version: initial.plugin_sdk.installed_version || null,
534
+ error: "",
535
+ ...overrides,
536
+ };
537
+ }
538
+
539
+ function openCodeRegistryVersion(raw) {
540
+ const value = String(raw ?? "").trim();
541
+ if (!value) return "";
542
+ try {
543
+ const parsed = JSON.parse(value);
544
+ return typeof parsed === "string" ? parsed.trim() : "";
545
+ } catch {
546
+ return value;
547
+ }
548
+ }
549
+
550
+ function openCodeSdkUnavailable(error) {
551
+ return /(?:ETARGET|E404|404 Not Found|No matching version)/i.test(
552
+ (error?.stderr || error?.message || "").toString(),
553
+ );
554
+ }
555
+
556
+ export function alignOpenCodePluginSdk(configDir, dependencies = {}) {
557
+ const initial = evaluateOpenCodeHostSdk(configDir, dependencies);
558
+ const hostVersion = initial.host.installed_version || "";
559
+ const sdkVersion = initial.plugin_sdk.installed_version || "";
560
+ if (!initial.host.inspectable || !initial.plugin_sdk.loadable || !exactOpenCodeVersionPattern.test(hostVersion)) {
561
+ return openCodeSdkAlignmentResult("not_attempted", initial, {
562
+ target_version: exactOpenCodeVersionPattern.test(hostVersion) ? hostVersion : null,
563
+ error: !initial.host.inspectable
564
+ ? initial.host.error || "OpenCode host version is not inspectable."
565
+ : !initial.plugin_sdk.loadable
566
+ ? initial.plugin_sdk.error || "OpenCode plugin SDK is not inspectable."
567
+ : `OpenCode host version is not an exact semantic version: ${hostVersion || "unknown"}`,
568
+ });
569
+ }
570
+ if (sdkVersion === hostVersion) {
571
+ return openCodeSdkAlignmentResult("already_matching", initial, {
572
+ target_version: hostVersion,
573
+ });
574
+ }
575
+
576
+ const run = dependencies.execFileSync ?? execFileSync;
577
+ const packageSpecifier = `@opencode-ai/plugin@${hostVersion}`;
578
+ const registryInvocation = openCodeNpmInvocation(["view", packageSpecifier, "version", "--json"]);
579
+ try {
580
+ const availableVersion = openCodeRegistryVersion(run(registryInvocation.executable, registryInvocation.args, {
581
+ cwd: configDir,
582
+ encoding: "utf8",
583
+ stdio: ["ignore", "pipe", "pipe"],
584
+ timeout: 15000,
585
+ }));
586
+ if (availableVersion !== hostVersion) {
587
+ return openCodeSdkAlignmentResult("unavailable", initial, {
588
+ target_version: hostVersion,
589
+ error: `Exact plugin SDK version is unavailable: ${packageSpecifier}`,
590
+ });
591
+ }
592
+ } catch (error) {
593
+ return openCodeSdkAlignmentResult(openCodeSdkUnavailable(error) ? "unavailable" : "failed", initial, {
594
+ target_version: hostVersion,
595
+ error: (error.stderr || error.message || "plugin SDK registry lookup failed").toString().trim(),
596
+ });
597
+ }
598
+
599
+ const installInvocation = openCodeNpmInvocation([
600
+ "install",
601
+ "--silent",
602
+ "--ignore-scripts",
603
+ "--no-audit",
604
+ "--no-fund",
605
+ "--save-prod",
606
+ "--save-exact",
607
+ packageSpecifier,
608
+ ]);
609
+ let installError = null;
610
+ try {
611
+ run(installInvocation.executable, installInvocation.args, {
612
+ cwd: configDir,
613
+ encoding: "utf8",
614
+ stdio: ["ignore", "pipe", "pipe"],
615
+ timeout: 60000,
616
+ });
617
+ } catch (error) {
618
+ installError = error;
619
+ }
620
+
621
+ const observed = evaluateOpenCodeHostSdk(configDir, dependencies);
622
+ const verified = observed.plugin_sdk.installed_version === hostVersion
623
+ && observed.experimental_hooks.aggregate === "declared_supported";
624
+ if (installError) {
625
+ return openCodeSdkAlignmentResult("failed", initial, {
626
+ attempted: true,
627
+ target_version: hostVersion,
628
+ installed_version: observed.plugin_sdk.installed_version || null,
629
+ error: (installError.stderr || installError.message || "plugin SDK installation failed").toString().trim(),
630
+ });
631
+ }
632
+ if (!verified) {
633
+ return openCodeSdkAlignmentResult("verification_failed", initial, {
634
+ attempted: true,
635
+ target_version: hostVersion,
636
+ installed_version: observed.plugin_sdk.installed_version || null,
637
+ error: observed.plugin_sdk.installed_version !== hostVersion
638
+ ? `Observed plugin SDK ${observed.plugin_sdk.installed_version || "unknown"} after targeting ${hostVersion}.`
639
+ : `Plugin SDK ${hostVersion} does not declare all required experimental hooks.`,
640
+ });
641
+ }
642
+ return openCodeSdkAlignmentResult("aligned", initial, {
643
+ attempted: true,
644
+ target_version: hostVersion,
645
+ installed_version: observed.plugin_sdk.installed_version,
646
+ });
647
+ }
648
+
342
649
  function openCodePackageVersionStatus(packageState) {
343
650
  if (!packageState.loadable) return "unloadable";
344
651
  if (!packageState.installed_version) return "unknown";
@@ -393,18 +700,24 @@ function evaluateGlobalOpenCodeSurface(configDir) {
393
700
  present: existsSync(paths.localValidator),
394
701
  ...validatorResolution.envelope,
395
702
  },
703
+ evaluator_agent: {
704
+ name: pluginDefinition.opencode.evaluatorAgentName,
705
+ path: paths.evaluatorAgent,
706
+ present: existsSync(paths.evaluatorAgent),
707
+ },
396
708
  present: existsSync(paths.skills),
397
709
  complete: existsSync(paths.instructions)
398
710
  && existsSync(paths.runtimeContract)
399
711
  && contractCount === contractModules.length
400
712
  && skillCount === openCodeSkillNames.length
401
713
  && existsSync(paths.localValidator)
714
+ && existsSync(paths.evaluatorAgent)
402
715
  && globalOpenCodeValidatorPackageIsValid(paths.localValidatorPackage)
403
716
  && validatorResolution.envelope.machine_validation === "owned_version_matched",
404
717
  };
405
718
  }
406
719
 
407
- export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(), transition = null) {
720
+ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeConfigDir(), transition = null, dependencies = {}) {
408
721
  const configPath = join(configDir, "opencode.json");
409
722
  const configState = readOpenCodeConfig(configPath);
410
723
  const plugins = Array.isArray(configState.config.plugin) ? configState.config.plugin : [];
@@ -412,6 +725,7 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
412
725
  const packageState = resolveOpenCodePackage(configDir);
413
726
  const packageVersionStatus = openCodePackageVersionStatus(packageState);
414
727
  const globalNativeSurface = evaluateGlobalOpenCodeSurface(configDir);
728
+ const hostSdk = evaluateOpenCodeHostSdk(configDir, dependencies);
415
729
  const sessionSignals = {
416
730
  active: process.env.AGDF_PLUGIN_ACTIVE === "1",
417
731
  version: process.env.AGDF_PLUGIN_VERSION || "",
@@ -434,6 +748,11 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
434
748
  `Global OpenCode native surface is incomplete (${globalNativeSurface.skill_count}/${globalNativeSurface.expected_skill_count} skills; ${globalNativeSurface.contract_count}/${globalNativeSurface.expected_contract_count} contract modules).`,
435
749
  );
436
750
  }
751
+ if (hostSdk.experimental_hooks.aggregate === "uninspectable") findings.push("Installed OpenCode plugin SDK hook declarations could not be inspected.");
752
+ if (hostSdk.experimental_hooks.aggregate === "degraded") findings.push("Installed OpenCode plugin SDK is missing one or more required experimental hook declarations.");
753
+ if (hostSdk.host_sdk_version.status === "divergent") {
754
+ findings.push(`OpenCode host ${hostSdk.host_sdk_version.host_version} and plugin SDK ${hostSdk.host_sdk_version.sdk_version} diverge; policy is warning-only.`);
755
+ }
437
756
  if (!sessionSignals.active) findings.push("No active AGDF OpenCode session signal is visible in this process.");
438
757
  if (!repositoryActivation.active) {
439
758
  findings.push(repositoryActivation.state === "invalid_control"
@@ -460,6 +779,7 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
460
779
  ...(transition ? { transition } : {}),
461
780
  error: packageState.error,
462
781
  },
782
+ ...hostSdk,
463
783
  global_native_surface: globalNativeSurface,
464
784
  session: sessionSignals,
465
785
  repository_activation: repositoryActivation.state,
@@ -482,6 +802,12 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
482
802
  ? "Run npx --yes @agdf/cli@latest opencode to install or repair the OpenCode package version."
483
803
  : !globalNativeSurface.complete
484
804
  ? "Run npx --yes @agdf/cli@latest opencode to install or repair the global native OpenCode skill surface."
805
+ : hostSdk.experimental_hooks.aggregate === "uninspectable"
806
+ ? "Repair the installed OpenCode plugin SDK so its declarations can be inspected; do not assume dynamic hook support."
807
+ : hostSdk.experimental_hooks.aggregate === "degraded"
808
+ ? "Use static AGDF instructions and review OpenCode compatibility before relying on dynamic hook injection."
809
+ : hostSdk.host_sdk_version.status === "divergent"
810
+ ? "Run npx --yes @agdf/cli@latest opencode to align the plugin SDK to the exact OpenCode host version; status itself remains read-only."
485
811
  : repositoryActivation.state === "invalid_control"
486
812
  ? "Repair .agdf/control/config.json so it contains valid artifact_language, chat_language and runtime_language values."
487
813
  : repositoryActivation.active
@@ -505,6 +831,11 @@ export function printOpenCodeStatus(report, json, io = console) {
505
831
  io.log(`Package version: ${report.package.installed_version || "unknown"}`);
506
832
  io.log(`Expected version: ${report.package.expected_version}`);
507
833
  io.log(`Version status: ${report.package.version_status}`);
834
+ io.log(`OpenCode host version: ${report.host.installed_version || "unknown"}`);
835
+ io.log(`Plugin SDK version: ${report.plugin_sdk.installed_version || "unknown"}`);
836
+ io.log(`Host/SDK version: ${report.host_sdk_version.status} (${report.host_sdk_version.policy})`);
837
+ io.log(`Experimental hook declarations: ${report.experimental_hooks.aggregate} (${report.experimental_hooks.evidence_level}; live invocation not observed)`);
838
+ for (const hook of report.experimental_hooks.hooks) io.log(`- ${hook.name}: ${hook.state}`);
508
839
  if (report.package.transition?.status === "updated") {
509
840
  io.log(`Version transition: ${report.package.transition.previous_version} -> ${report.package.transition.installed_version}`);
510
841
  } else if (report.package.transition?.status === "installed") {
@@ -349,6 +349,91 @@ export function renderOperationalStatusCard(statusCard, {
349
349
  });
350
350
  }
351
351
 
352
+ export function renderTaskTargetOrientation(resolution, {
353
+ registry,
354
+ requestedLocale,
355
+ } = {}) {
356
+ if (!plainObject(resolution)) return null;
357
+
358
+ const resolutionState = String(resolution.resolution_state ?? "").trim();
359
+ const reasonCode = String(resolution.reason_code ?? "").trim();
360
+ const primaryTarget = String(resolution.primary_target ?? "").trim();
361
+ const governanceTarget = String(resolution.governance_target ?? "").trim();
362
+ const workingDirectory = String(resolution.working_directory ?? "").trim();
363
+ const nextAction = String(resolution.next_action ?? "").trim();
364
+ if (typeof resolution.target_changed !== "boolean") return null;
365
+ const targetChanged = resolution.target_changed === true;
366
+ const evidenceSources = Array.isArray(resolution.evidence_sources)
367
+ ? resolution.evidence_sources.map((item) => String(item ?? "").trim()).filter(Boolean)
368
+ : null;
369
+
370
+ const resolvedReasons = new Set(["explicit_target", "continued_target"]);
371
+ const unresolvedReasons = new Set([
372
+ "multiple_plausible_targets",
373
+ "target_content_mismatch",
374
+ "target_unavailable",
375
+ "no_reliable_target",
376
+ ]);
377
+
378
+ if (!["resolved", "unresolved"].includes(resolutionState)) return null;
379
+ if (!workingDirectory || evidenceSources === null) return null;
380
+ if (resolutionState === "resolved") {
381
+ if (!resolvedReasons.has(reasonCode) || !primaryTarget || nextAction) return null;
382
+ } else if (!unresolvedReasons.has(reasonCode) || primaryTarget || governanceTarget || !nextAction || targetChanged) {
383
+ return null;
384
+ }
385
+
386
+ let locale;
387
+ try {
388
+ locale = resolvePresentationLocale(registry, requestedLocale);
389
+ } catch {
390
+ return null;
391
+ }
392
+ if (!locale) return null;
393
+
394
+ const pack = localePack(registry, locale);
395
+ const labels = pack?.taskTargetResolution;
396
+ if (!plainObject(labels) || !plainObject(labels.reasonCodes)) return null;
397
+ const reason = labels.reasonCodes[reasonCode];
398
+ if (!reason) return null;
399
+
400
+ const rows = resolutionState === "resolved"
401
+ ? [
402
+ [labels.state, labels.resolved],
403
+ [labels.primaryTarget, primaryTarget],
404
+ [labels.governanceTarget, governanceTarget || labels.none],
405
+ [labels.evidenceSources, evidenceSources.join("; ") || labels.none],
406
+ [labels.workingDirectory, workingDirectory],
407
+ [labels.reason, reason],
408
+ ...(targetChanged ? [[labels.targetChanged, labels.yes]] : []),
409
+ ]
410
+ : [
411
+ [labels.state, labels.unresolved],
412
+ [labels.reason, reason],
413
+ [labels.evidenceSources, evidenceSources.join("; ") || labels.none],
414
+ [labels.workingDirectory, workingDirectory],
415
+ [labels.nextAction, nextAction],
416
+ ];
417
+
418
+ const markdown = [
419
+ `## ${labels.title}`,
420
+ "",
421
+ `| ${labels.field} | ${labels.value} |`,
422
+ "|---|---|",
423
+ ...rows.map(([label, value]) => `| ${markdownCell(label)} | ${markdownCell(value)} |`),
424
+ ].join("\n");
425
+
426
+ return Object.freeze({
427
+ schema_version: "1",
428
+ semantic_block: "task_target_orientation",
429
+ presentation_language: locale,
430
+ resolution_state: resolutionState,
431
+ reason_code: reasonCode,
432
+ markdown,
433
+ authorizes: false,
434
+ });
435
+ }
436
+
352
437
  export function renderScopeClassificationCard(classification, {
353
438
  registry,
354
439
  requestedLocale,
@@ -131,6 +131,10 @@ function openCodeOwnedGlobalFiles(configDir) {
131
131
  const files = [
132
132
  { path: join(configDir, pluginDefinition.opencode.instructionsFileName), owned: firstLine("<!-- AGDF-GLOBAL-INSTRUCTIONS -->") },
133
133
  { path: join(configDir, pluginDefinition.opencode.runtimeContractFileName), owned: firstLine("<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->") },
134
+ {
135
+ path: join(configDir, "agents", `${pluginDefinition.opencode.evaluatorAgentName}.md`),
136
+ owned: afterFrontmatter(`<!-- AGDF-GLOBAL-AGENT: ${pluginDefinition.opencode.evaluatorAgentName} -->`),
137
+ },
134
138
  ];
135
139
  for (const moduleName of ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"]) {
136
140
  files.push({ path: join(configDir, "contracts", moduleName), owned: firstLine("<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->") });
@@ -0,0 +1,43 @@
1
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { guardedExecFileSync } from "../delivery-path-search/transports/read-only-guard.js";
5
+
6
+ export function executeStructuredAgent({ surface, cwd, prompt, model, outputSchema, timeoutMs = 120000 }) {
7
+ if (surface === "codex") {
8
+ const temp = mkdtempSync(join(tmpdir(), "agdf-live-codex-"));
9
+ try {
10
+ const schemaPath = join(temp, "schema.json");
11
+ const outputPath = join(temp, "output.json");
12
+ writeFileSync(schemaPath, `${JSON.stringify(outputSchema, null, 2)}\n`, "utf8");
13
+ const args = [
14
+ "exec", "--sandbox", "read-only", "--ephemeral", "--ignore-user-config",
15
+ "--skip-git-repo-check", "--output-schema", schemaPath,
16
+ "--output-last-message", outputPath, "--color", "never",
17
+ ];
18
+ if (model) args.push("--model", model);
19
+ args.push(prompt);
20
+ guardedExecFileSync("codex", args, {
21
+ cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs,
22
+ });
23
+ return readFileSync(outputPath, "utf8");
24
+ } finally {
25
+ rmSync(temp, { recursive: true, force: true });
26
+ }
27
+ }
28
+ if (surface === "claude") {
29
+ const args = [
30
+ "-p", "--disallowedTools", "Edit,Write,Bash", "--output-format", "json",
31
+ "--json-schema", JSON.stringify(outputSchema),
32
+ ];
33
+ if (model) args.push("--model", model);
34
+ args.push(prompt);
35
+ const raw = guardedExecFileSync("claude", args, {
36
+ cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: timeoutMs,
37
+ });
38
+ const envelope = JSON.parse(raw);
39
+ if (envelope.is_error) throw new Error(`claude live adapter failed: ${envelope.result ?? "unknown error"}`);
40
+ return envelope.result;
41
+ }
42
+ throw new Error(`unsupported live surface: ${surface}`);
43
+ }