create-agdf 0.14.3 → 0.14.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 (80) hide show
  1. package/README.md +34 -2
  2. package/generated/.opencode/contracts/interaction.md +2 -2
  3. package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
  4. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  5. package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
  6. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  7. package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
  8. package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
  9. package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
  10. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  11. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  12. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  13. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  14. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  15. package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  16. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  17. package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  18. package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  19. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  20. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  21. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  22. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +2 -0
  23. package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
  24. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  25. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  26. package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
  27. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  28. package/generated/plugins/copilot/agdf/.agdf-payload-inventory.json +55 -35
  29. package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
  30. package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
  31. package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
  32. package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
  33. package/generated/plugins/copilot/agdf/plugin.json +1 -1
  34. package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
  35. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
  36. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
  37. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
  38. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
  39. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
  40. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
  41. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
  42. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
  43. package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
  44. package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
  45. package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
  46. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  47. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
  48. package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
  49. package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
  50. package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
  51. package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
  52. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  53. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  54. package/generated/submissions/openai/agdf/submission/openai/inventory.json +16 -12
  55. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  56. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  57. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  58. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
  59. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  60. package/lib/cli/application.js +24 -6
  61. package/lib/cli/command-registry.js +8 -1
  62. package/lib/cli/parse-args.js +3 -0
  63. package/lib/control-evaluation/doctor.js +13 -0
  64. package/lib/control-evaluation/gate-check.js +64 -13
  65. package/lib/control-evaluation/run-state.js +6 -0
  66. package/lib/control-state/run-identity.js +9 -0
  67. package/lib/control-state/run-state-parser.js +4 -2
  68. package/lib/installers/claude-cache-recovery.js +76 -0
  69. package/lib/installers/copilot-settings.js +146 -16
  70. package/lib/installers/local-marketplace.js +169 -14
  71. package/lib/installers/plugin-installers.js +96 -9
  72. package/lib/interaction-presentation.js +57 -7
  73. package/lib/lifecycle/operations.js +28 -4
  74. package/lib/lifecycle/result.js +4 -4
  75. package/lib/release/profile-history.js +222 -0
  76. package/lib/release/version-bump.js +527 -0
  77. package/lib/release/version-coherence.js +92 -38
  78. package/lib/runtime/distribution-profile-history.js +173 -0
  79. package/lib/runtime/plugin-provenance.js +46 -3
  80. package/package.json +7 -3
@@ -4,10 +4,22 @@ import { join, resolve } from "node:path";
4
4
  import process from "node:process";
5
5
  import { pluginDefinition } from "../cli/runtime-context.js";
6
6
  import { digestNormalizedPluginSource } from "../runtime/plugin-provenance.js";
7
+ import { recoverClaudeCacheTemp } from "./claude-cache-recovery.js";
7
8
  import { CODEX_REGISTRATION_REVISION, classifyMarketplaceList, inspectLocalMarketplaceProjection, inspectOwnedSharedMarketplaceForCopilotMigration, isCodexLocalInstallVersion, prepareCopilotMarketplace, prepareLocalMarketplace } from "./local-marketplace.js";
8
9
 
9
10
  export const COPILOT_CLI_NPM_PACKAGE = "@github/copilot@1.0.80";
10
11
 
12
+ function historicalEvidenceEntries(transaction) {
13
+ const historical = transaction.historicalEvidence;
14
+ if (!historical) return [];
15
+ return [
16
+ `historical_release:${historical.releaseVersion}`,
17
+ `historical_contract:${historical.contractId}`,
18
+ `historical_contract_digest:${historical.contractDigest}`,
19
+ `historical_entry_digest:${historical.entryDigest}`,
20
+ ];
21
+ }
22
+
11
23
  export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
12
24
  const expectedVersion = pluginDefinition.version;
13
25
  const nativeOutput = [];
@@ -34,12 +46,15 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
34
46
  ...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
35
47
  ...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
36
48
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
49
+ ...(transaction.existingClassification === "owned_supported_historical_rebuild" ? ["marketplace_recovery:owned_supported_historical_rebuild", "loaded_session:fresh_session_required"] : []),
50
+ ...historicalEvidenceEntries(transaction),
37
51
  ...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
38
52
  ],
39
53
  pluginRoot: transaction.pluginRoot ?? null,
40
54
  digest: transaction.digest ?? null,
41
55
  runtimeDigest: transaction.runtimeDigest ?? null,
42
56
  sourceDigest: transaction.sourceDigest ?? null,
57
+ historicalEvidence: transaction.historicalEvidence ?? null,
43
58
  nativeOutput: nativeOutput.filter(Boolean).map(String),
44
59
  };
45
60
  } catch (error) {
@@ -48,11 +63,19 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
48
63
  }
49
64
  }
50
65
 
51
- export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
66
+ export function installClaudeGlobalPlugin({
67
+ exec = execFileSync,
68
+ prepare = prepareLocalMarketplace,
69
+ dataRoot,
70
+ recoverCache = recoverClaudeCacheTemp,
71
+ cacheRecoveryOptions = {},
72
+ } = {}) {
52
73
  const expectedVersion = pluginDefinition.version;
53
74
  const nativeOutput = [];
54
75
  const transaction = prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
55
76
  const migration = { state: "unknown", source: "", addedLocal: false, removedLegacy: false };
77
+ let previousPluginRemoved = false;
78
+ let pluginInstalled = false;
56
79
  try {
57
80
  const marketplaceOutput = runPluginPhase(exec, "claude", ["plugin", "marketplace", "list", "--json"], "marketplace", captureOptions());
58
81
  migrateMarketplace({ surface: "claude", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration });
@@ -63,11 +86,41 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
63
86
  // same-version local source change never reaches the host; reinstall replaces the content.
64
87
  if (alreadyInstalled) {
65
88
  nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "uninstall", "agdf@agdf"], "plugin_operation", captureOptions()));
89
+ previousPluginRemoved = true;
90
+ }
91
+ let cacheRecovery = null;
92
+ try {
93
+ nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
94
+ pluginInstalled = true;
95
+ } catch (installError) {
96
+ cacheRecovery = recoverCache({
97
+ error: installError,
98
+ expectedVersion,
99
+ ...cacheRecoveryOptions,
100
+ });
101
+ if (cacheRecovery.status !== "recovered") {
102
+ installError.evidence = {
103
+ ...(installError.evidence ?? {}),
104
+ claude_cache_recovery: cacheRecovery.reason,
105
+ };
106
+ throw installError;
107
+ }
108
+ try {
109
+ nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
110
+ pluginInstalled = true;
111
+ } catch (retryError) {
112
+ retryError.evidence = {
113
+ ...(retryError.evidence ?? {}),
114
+ claude_cache_recovery: "claude_cache_temp_retry_exhausted",
115
+ };
116
+ throw retryError;
117
+ }
66
118
  }
67
- nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
68
119
  const afterList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
69
120
  const installedVersion = pluginVersionFromList(afterList, "agdf@agdf");
70
- if (installedVersion && installedVersion !== expectedVersion) {
121
+ const historicalRebuild = transaction.existingClassification === "owned_supported_historical_rebuild";
122
+ if ((historicalRebuild && installedVersion !== expectedVersion)
123
+ || (installedVersion && installedVersion !== expectedVersion)) {
71
124
  throw lifecycleAdapterError("version", versionMismatchMessage("Claude Code", "agdf@agdf", expectedVersion, installedVersion, "npx --yes @agdf/cli@latest claude"));
72
125
  }
73
126
  transaction.commit();
@@ -85,16 +138,20 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
85
138
  ...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
86
139
  ...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
87
140
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
141
+ ...(transaction.existingClassification === "owned_supported_historical_rebuild" ? ["marketplace_recovery:owned_supported_historical_rebuild", "loaded_session:fresh_session_required"] : []),
142
+ ...historicalEvidenceEntries(transaction),
143
+ ...(cacheRecovery?.status === "recovered" ? ["claude_cache_temp_recovery:bounded_retry"] : []),
88
144
  ...(installedVersion ? [] : ["host_did_not_expose_version"]),
89
145
  ],
90
146
  pluginRoot: transaction.pluginRoot ?? null,
91
147
  digest: transaction.digest ?? null,
92
148
  runtimeDigest: transaction.runtimeDigest ?? null,
93
149
  sourceDigest: transaction.sourceDigest ?? null,
150
+ historicalEvidence: transaction.historicalEvidence ?? null,
94
151
  nativeOutput: nativeOutput.filter(Boolean).map(String),
95
152
  };
96
153
  } catch (error) {
97
- recoverMarketplace({ surface: "claude", exec, migration, transaction, error });
154
+ recoverMarketplace({ surface: "claude", exec, migration, transaction, error, previousPluginRemoved, pluginInstalled });
98
155
  throw error;
99
156
  }
100
157
  }
@@ -118,7 +175,7 @@ export function installCopilotGlobalPlugin({ exec = execFileSync, packagedCopilo
118
175
  try {
119
176
  before = activeExec("copilot", ["plugin", "list"], captureOptions());
120
177
  } catch (error) {
121
- if (error?.code !== "ENOENT") {
178
+ if (!copilotCliUnavailable(error)) {
122
179
  transaction?.rollback();
123
180
  throw lifecycleAdapterError("verification", commandErrorText(error), { executable: "copilot", args: ["plugin", "list"] });
124
181
  }
@@ -126,7 +183,10 @@ export function installCopilotGlobalPlugin({ exec = execFileSync, packagedCopilo
126
183
  activeExec = (_executable, args, options) => packagedCopilotExec(invocation.executable, [...invocation.args, ...args], options);
127
184
  try {
128
185
  before = activeExec("copilot", ["plugin", "list"], captureOptions());
129
- bootstrapEvidence.push("copilot_cli_not_found", `copilot_cli_npm_package:${COPILOT_CLI_NPM_PACKAGE}`);
186
+ bootstrapEvidence.push(
187
+ error?.code === "ENOENT" ? "copilot_cli_not_found" : "copilot_cli_launcher_unavailable",
188
+ `copilot_cli_npm_package:${COPILOT_CLI_NPM_PACKAGE}`,
189
+ );
130
190
  } catch (bootstrapError) {
131
191
  transaction?.commit();
132
192
  return {
@@ -302,7 +362,15 @@ function migrateMarketplace({ surface, exec, output, root, nativeOutput, migrati
302
362
  }
303
363
  }
304
364
 
305
- function recoverMarketplace({ surface, exec, migration, transaction, error }) {
365
+ function recoverMarketplace({
366
+ surface,
367
+ exec,
368
+ migration,
369
+ transaction,
370
+ error,
371
+ previousPluginRemoved = false,
372
+ pluginInstalled = false,
373
+ }) {
306
374
  const executable = surface === "claude" ? "claude" : "codex";
307
375
  const recovery = [];
308
376
  const attempt = (args) => {
@@ -316,13 +384,19 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
316
384
  }
317
385
  };
318
386
  if (migration?.refreshOwnedLocal) {
387
+ if (surface === "claude" && pluginInstalled) {
388
+ attempt(["plugin", "uninstall", "agdf@agdf"]);
389
+ }
319
390
  const refreshedRegistrationRemoved = migration.addedLocal
320
391
  ? attempt(["plugin", "marketplace", "remove", "agdf", "--json"])
321
392
  : true;
322
- rollbackMarketplaceFilesystem(transaction, recovery);
393
+ const filesystemRestored = rollbackMarketplaceFilesystem(transaction, recovery);
323
394
  if (migration.removedOwnedLocal && refreshedRegistrationRemoved) {
324
395
  attempt(["plugin", "marketplace", "add", migration.source || transaction.root, "--json"]);
325
396
  }
397
+ if (surface === "claude" && previousPluginRemoved && filesystemRestored) {
398
+ attempt(["plugin", "install", "agdf@agdf"]);
399
+ }
326
400
  error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
327
401
  return;
328
402
  }
@@ -336,7 +410,13 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
336
410
  ? ["plugin", "marketplace", "add", migration.source, "--scope", "user"]
337
411
  : ["plugin", "marketplace", "add", migration.source, "--json"]);
338
412
  }
339
- rollbackMarketplaceFilesystem(transaction, recovery);
413
+ if (surface === "claude" && pluginInstalled) {
414
+ attempt(["plugin", "uninstall", "agdf@agdf"]);
415
+ }
416
+ const filesystemRestored = rollbackMarketplaceFilesystem(transaction, recovery);
417
+ if (surface === "claude" && previousPluginRemoved && filesystemRestored) {
418
+ attempt(["plugin", "install", "agdf@agdf"]);
419
+ }
340
420
  error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
341
421
  }
342
422
 
@@ -344,8 +424,10 @@ function rollbackMarketplaceFilesystem(transaction, recovery) {
344
424
  try {
345
425
  transaction.rollback();
346
426
  recovery.push({ filesystem: transaction.root, status: "restored" });
427
+ return true;
347
428
  } catch (rollbackError) {
348
429
  recovery.push({ filesystem: transaction.root, status: "failed", message: rollbackError.message });
430
+ return false;
349
431
  }
350
432
  }
351
433
 
@@ -404,6 +486,11 @@ function commandErrorText(error) {
404
486
  return (error.stderr || error.stdout || error.message || "").toString().trim();
405
487
  }
406
488
 
489
+ function copilotCliUnavailable(error) {
490
+ return error?.code === "ENOENT"
491
+ || /^Cannot find GitHub Copilot CLI(?:\s|$|\()/i.test(commandErrorText(error));
492
+ }
493
+
407
494
  export function pluginListHasPlugin(output, pluginId) {
408
495
  const escapedPluginId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
409
496
  return output
@@ -1,3 +1,5 @@
1
+ import { RUN_ID_PATTERN } from "./control-state/run-identity.js";
2
+
1
3
  const REQUIRED_GATES = ["UR", "PRD", "SD", "TP", "QA", "UAT"];
2
4
  const ARTEFACT_ORDER = ["UR", "PRD", "SD", "TP"];
3
5
  const OUTCOMES = new Set(["approve", "revise", "decline", "cancel", "no_response", "timeout", "empty", "invalid", "stale"]);
@@ -313,17 +315,37 @@ function operationalBreadcrumb(value, labels) {
313
315
  .join(labels.breadcrumbSeparator);
314
316
  }
315
317
 
318
+ export function validateOperationalStatusCardPreconditions(statusCard, {
319
+ registry,
320
+ humanPresentation,
321
+ } = {}) {
322
+ const errors = [];
323
+ if (!plainObject(statusCard)) errors.push("status_card_missing");
324
+ if (humanPresentation !== undefined && !plainObject(humanPresentation)) errors.push("human_presentation_missing");
325
+ if (plainObject(statusCard)) {
326
+ if (!String(statusCard.run_id ?? "").trim()) errors.push("run_id_missing");
327
+ if (!String(statusCard.current_gate ?? "").trim()) errors.push("current_gate_missing");
328
+ let locale = "";
329
+ try {
330
+ locale = resolvePresentationLocale(registry, statusCard.presentation_language);
331
+ } catch {
332
+ locale = "";
333
+ }
334
+ if (!locale) errors.push("locale_unresolved");
335
+ }
336
+ return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
337
+ }
338
+
316
339
  export function renderOperationalStatusCard(statusCard, {
317
340
  registry,
318
341
  humanPresentation,
319
342
  revisionId = "",
320
343
  } = {}) {
321
- if (!plainObject(statusCard) || !plainObject(humanPresentation)) return null;
344
+ if (!validateOperationalStatusCardPreconditions(statusCard, { registry, humanPresentation: humanPresentation ?? null }).valid) return null;
322
345
  const runId = String(statusCard.run_id ?? "").trim();
323
346
  const currentGate = String(statusCard.current_gate ?? "").trim();
324
347
  const revision = String(revisionId ?? "").trim() || "unversioned";
325
348
  const locale = resolvePresentationLocale(registry, statusCard.presentation_language);
326
- if (!runId || !currentGate || !locale) return null;
327
349
 
328
350
  const pack = localePack(registry, locale);
329
351
  const labels = pack.statusCard;
@@ -537,6 +559,37 @@ export function normalizeInteractionOutcome(value, { expectedApproval, stale = f
537
559
  return "invalid";
538
560
  }
539
561
 
562
+ export function validateApprovalOrientationPreconditions({
563
+ statusCard,
564
+ humanPresentation,
565
+ registry,
566
+ requestedLocale,
567
+ } = {}) {
568
+ const errors = [];
569
+ if (!plainObject(statusCard)) return Object.freeze({ valid: false, errors: Object.freeze(["status_card_missing"]) });
570
+ if (humanPresentation !== undefined && !plainObject(humanPresentation)) errors.push("human_presentation_missing");
571
+ const gate = String(statusCard.current_gate ?? "").trim();
572
+ const runId = String(statusCard.run_id ?? "").trim();
573
+ if (!REQUIRED_GATES.includes(gate)) errors.push("gate_not_user_gate");
574
+ if (!RUN_ID_PATTERN.test(runId)) errors.push("run_id_invalid");
575
+ if (statusCard.status !== "open") errors.push("status_not_open");
576
+ if (statusCard.missing_approval !== `Approval: ${gate}`) errors.push("missing_approval_mismatch");
577
+ const nextGate = String(statusCard.next_gate_after_approval ?? "none");
578
+ const nextUserGate = String(statusCard.next_user_gate ?? "").trim();
579
+ const userActionRequired = String(statusCard.user_action_required ?? "").trim();
580
+ const userActionSemanticsValid = (userActionRequired === "no" && nextUserGate === "none")
581
+ || (userActionRequired === "yes" && REQUIRED_GATES.includes(nextUserGate) && nextGate === nextUserGate);
582
+ if (!userActionSemanticsValid) errors.push("user_action_semantics_invalid");
583
+ let locale = "";
584
+ try {
585
+ locale = resolvePresentationLocale(registry, requestedLocale);
586
+ } catch {
587
+ locale = "";
588
+ }
589
+ if (!locale) errors.push("locale_unresolved");
590
+ return Object.freeze({ valid: errors.length === 0, errors: Object.freeze(errors) });
591
+ }
592
+
540
593
  export function buildApprovalOrientationSnapshot({
541
594
  ready = false,
542
595
  statusCard,
@@ -545,11 +598,11 @@ export function buildApprovalOrientationSnapshot({
545
598
  registry,
546
599
  requestedLocale,
547
600
  } = {}) {
548
- if (!ready || !plainObject(statusCard) || !plainObject(humanPresentation)) return null;
601
+ if (!ready) return null;
602
+ if (!validateApprovalOrientationPreconditions({ statusCard, humanPresentation: humanPresentation ?? null, registry, requestedLocale }).valid) return null;
549
603
  const gate = String(statusCard.current_gate ?? "").trim();
550
604
  const runId = String(statusCard.run_id ?? "").trim();
551
605
  const expectedApproval = `Approval: ${gate}`;
552
- if (!REQUIRED_GATES.includes(gate) || !/^[A-Za-z0-9._-]+$/.test(runId) || statusCard.status !== "open" || statusCard.missing_approval !== expectedApproval) return null;
553
606
 
554
607
  const locale = resolvePresentationLocale(registry, requestedLocale);
555
608
  const pack = localePack(registry, locale);
@@ -560,9 +613,6 @@ export function buildApprovalOrientationSnapshot({
560
613
  const nextGate = String(statusCard.next_gate_after_approval ?? "none");
561
614
  const nextUserGate = String(statusCard.next_user_gate ?? "").trim();
562
615
  const userActionRequired = String(statusCard.user_action_required ?? "").trim();
563
- const userActionSemanticsValid = (userActionRequired === "no" && nextUserGate === "none")
564
- || (userActionRequired === "yes" && REQUIRED_GATES.includes(nextUserGate) && nextGate === nextUserGate);
565
- if (!userActionSemanticsValid) return null;
566
616
  const narration = pack.primary.narration.gates[gate] ?? {};
567
617
  const nextDecision = userActionRequired === "no"
568
618
  ? pack.primary.narration.noAction
@@ -2,6 +2,11 @@ import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { pluginDefinition } from "../cli/runtime-context.js";
5
+ import {
6
+ applyCopilotRepositoryDisable,
7
+ planCopilotRepositoryDisable,
8
+ verifyCopilotRepositoryDisabled,
9
+ } from "../installers/copilot-settings.js";
5
10
  import { evaluateOpenCodeStatus, openCodeNpmInvocation } from "../installers/opencode.js";
6
11
  import { inspectPluginSurface } from "../installers/plugin-installers.js";
7
12
  import { inspectGeneratedRepositoryMarketplace } from "../runtime/plugin-provenance.js";
@@ -19,8 +24,22 @@ function repositorySelector(targetDir, config = "") {
19
24
  return pluginSection(config, "agdf@agdf-repo") ? "agdf@agdf-repo" : "agdf@agdf";
20
25
  }
21
26
 
22
- export function planRepositoryDisable(targetDir, surface) {
23
- if (surface !== "codex") {
27
+ export function planRepositoryDisable(targetDir, surface, { shared = false, exec = execFileSync } = {}) {
28
+ if (surface === "copilot") {
29
+ const settings = planCopilotRepositoryDisable({ targetDir, shared, exec });
30
+ return Object.freeze({
31
+ operation: "disable",
32
+ surface,
33
+ scope: "repository",
34
+ audience: settings.audience,
35
+ mutations: Object.freeze(settings.changed
36
+ ? [{ kind: "copilot_settings", path: settings.path, settings }]
37
+ : []),
38
+ retained: Object.freeze([join(targetDir, ".agdf", "control"), "global AGDF plugin availability", "independent repository instructions"]),
39
+ expected: Object.freeze({ repository_status: "configured_disabled", activation_status: "pending_restart", settings_path: settings.path }),
40
+ });
41
+ }
42
+ if (surface !== "codex" || shared) {
24
43
  throw new Error(`Repository disable is not supported safely for ${surface}; no files were changed.`);
25
44
  }
26
45
  const path = join(targetDir, ".codex", "config.toml");
@@ -49,7 +68,9 @@ export function planRepositoryDisable(targetDir, surface) {
49
68
  });
50
69
  }
51
70
 
52
- export function verifyRepositoryDisabled(targetDir) {
71
+ export function verifyRepositoryDisabled(targetDir, surface = "codex", { shared = false } = {}) {
72
+ if (surface === "copilot") return verifyCopilotRepositoryDisabled(targetDir, { shared });
73
+ if (surface !== "codex") return { status: "failed", evidence: [`unsupported_surface:${surface}`] };
53
74
  const path = join(targetDir, ".codex", "config.toml");
54
75
  if (!existsSync(path)) return { status: "failed", evidence: [`missing:${path}`] };
55
76
  const config = readFileSync(path, "utf8");
@@ -165,7 +186,7 @@ function nativeUninstallPlan(surface, executable, args) {
165
186
  });
166
187
  }
167
188
 
168
- export function applyLifecyclePlan(plan, { exec = execFileSync } = {}) {
189
+ export function applyLifecyclePlan(plan, { exec = execFileSync, applyCopilotSettings = applyCopilotRepositoryDisable } = {}) {
169
190
  const completed = [];
170
191
  for (const mutation of plan.mutations) {
171
192
  try {
@@ -173,6 +194,9 @@ export function applyLifecyclePlan(plan, { exec = execFileSync } = {}) {
173
194
  mkdirSync(dirname(mutation.path), { recursive: true });
174
195
  writeFileSync(mutation.path, mutation.content, "utf8");
175
196
  completed.push({ kind: "write", path: mutation.path });
197
+ } else if (mutation.kind === "copilot_settings") {
198
+ const result = applyCopilotSettings(mutation.settings);
199
+ completed.push({ kind: "copilot_settings", path: mutation.path, status: result.status });
176
200
  } else if (mutation.kind === "remove") {
177
201
  rmSync(mutation.path);
178
202
  completed.push({ kind: "remove", path: mutation.path });
@@ -3,10 +3,10 @@ const RESULTS = new Set(["success", "partial", "failed", "preview"]);
3
3
  const SCOPES = new Set(["global", "repository"]);
4
4
  const SURFACES = new Set(["codex", "claude", "copilot", "opencode", "generic"]);
5
5
  const GLOBAL_INSTALL_RESTART_ACTIONS = Object.freeze({
6
- codex: "Restart Codex.",
7
- claude: "Restart Claude Code.",
8
- copilot: "Restart GitHub Copilot.",
9
- opencode: "Restart OpenCode.",
6
+ codex: "Fully restart Codex, then start a fresh session. Restoring the previous session can retain stale AGDF skills.",
7
+ claude: "Fully restart Claude Code, then start a fresh session. Restoring the previous session can retain stale AGDF skills.",
8
+ copilot: "Fully restart GitHub Copilot, then start a fresh session. Restoring the previous session can retain stale AGDF skills.",
9
+ opencode: "Fully restart OpenCode, then start a fresh session. Restoring the previous session can retain stale AGDF skills.",
10
10
  });
11
11
 
12
12
  function text(value) {
@@ -0,0 +1,222 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import {
5
+ classifyHistoricalDistributionProfile,
6
+ validateDistributionProfileHistory,
7
+ } from "../runtime/distribution-profile-history.js";
8
+
9
+ export const SUPPORTED_PROFILE_RELEASES = Object.freeze([
10
+ "0.13.6",
11
+ "0.13.7",
12
+ "0.13.8",
13
+ "0.14.1",
14
+ "0.14.2",
15
+ "0.14.3",
16
+ ]);
17
+
18
+ const HISTORY_PATH = "plugin/meta/distribution-profile-history.json";
19
+ const GENERATED_HISTORY_PATHS = [
20
+ "create-agdf/generated/plugins/agdf/meta/distribution-profile-history.json",
21
+ "create-agdf/generated/plugins/copilot/agdf/meta/distribution-profile-history.json",
22
+ ];
23
+
24
+ function fail(reason, detail) {
25
+ const error = new Error(`${reason}: ${detail}`);
26
+ error.code = reason;
27
+ throw error;
28
+ }
29
+
30
+ function parseJson(content, reason, label) {
31
+ try {
32
+ return JSON.parse(content);
33
+ } catch {
34
+ fail(reason, `${label} is not valid JSON`);
35
+ }
36
+ }
37
+
38
+ function defaultReadTagFile(repoRoot) {
39
+ return (tag, relativePath) => execFileSync(
40
+ "git",
41
+ ["show", `${tag}:${relativePath}`],
42
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
43
+ );
44
+ }
45
+
46
+ function defaultTagExists(repoRoot) {
47
+ return (tag) => {
48
+ try {
49
+ execFileSync(
50
+ "git",
51
+ ["rev-parse", "--verify", "--quiet", `refs/tags/${tag}`],
52
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
53
+ );
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ };
59
+ }
60
+
61
+ function assertTagRecord(catalogue, version, readTagFile) {
62
+ const tag = `agdf-v${version}`;
63
+ let definition;
64
+ let packageManifest;
65
+ let codexManifest;
66
+ try {
67
+ definition = parseJson(readTagFile(tag, "plugin/meta/agdf-plugin.definition.json"), "profile_history_tag_mismatch", `${tag} definition`);
68
+ packageManifest = parseJson(readTagFile(tag, "create-agdf/package.json"), "profile_history_tag_mismatch", `${tag} package`);
69
+ codexManifest = parseJson(readTagFile(tag, "plugin/.codex-plugin/plugin.json"), "profile_history_tag_mismatch", `${tag} Codex manifest`);
70
+ } catch (error) {
71
+ if (error.code === "profile_history_tag_mismatch") throw error;
72
+ fail("profile_history_tag_mismatch", `${tag} evidence is unavailable`);
73
+ }
74
+ if (definition.version !== version || packageManifest.version !== version || codexManifest.version !== version) {
75
+ fail("profile_history_tag_mismatch", `${tag} does not identify exact version ${version}`);
76
+ }
77
+ const classification = classifyHistoricalDistributionProfile({
78
+ catalogue,
79
+ version,
80
+ distributionProfiles: definition.distributionProfiles,
81
+ });
82
+ if (classification.status !== "matched") {
83
+ fail("profile_history_tag_mismatch", `${tag} does not match its catalogue contract`);
84
+ }
85
+ }
86
+
87
+ function assertIncoherentTagNegative(catalogue, readTagFile) {
88
+ if (Object.hasOwn(catalogue.releases, "0.14.0")) {
89
+ fail("profile_history_tag_mismatch", "incoherent agdf-v0.14.0 must not have a release record");
90
+ }
91
+ const tag = "agdf-v0.14.0";
92
+ let versions;
93
+ try {
94
+ versions = [
95
+ parseJson(readTagFile(tag, "plugin/meta/agdf-plugin.definition.json"), "profile_history_tag_mismatch", `${tag} definition`).version,
96
+ parseJson(readTagFile(tag, "create-agdf/package.json"), "profile_history_tag_mismatch", `${tag} package`).version,
97
+ parseJson(readTagFile(tag, "plugin/.codex-plugin/plugin.json"), "profile_history_tag_mismatch", `${tag} Codex manifest`).version,
98
+ ];
99
+ } catch (error) {
100
+ if (error.code === "profile_history_tag_mismatch") throw error;
101
+ fail("profile_history_tag_mismatch", `${tag} negative evidence is unavailable`);
102
+ }
103
+ if (versions.some((version) => version !== "0.13.8")) {
104
+ fail("profile_history_tag_mismatch", `${tag} must remain the explicit internally-0.13.8 negative`);
105
+ }
106
+ }
107
+
108
+ function baselineFromRepository(repoRoot) {
109
+ let mergeBase;
110
+ try {
111
+ mergeBase = execFileSync(
112
+ "git",
113
+ ["merge-base", "HEAD", "origin/main"],
114
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
115
+ ).trim();
116
+ } catch {
117
+ fail("profile_history_continuity_break", "merge-base evidence is unavailable");
118
+ }
119
+ try {
120
+ return execFileSync(
121
+ "git",
122
+ ["show", `${mergeBase}:${HISTORY_PATH}`],
123
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
124
+ );
125
+ } catch {
126
+ let matchingPaths;
127
+ try {
128
+ matchingPaths = execFileSync(
129
+ "git",
130
+ ["ls-tree", "--name-only", mergeBase, "--", HISTORY_PATH],
131
+ { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] },
132
+ ).trim();
133
+ } catch {
134
+ fail("profile_history_continuity_break", "baseline tree evidence is unavailable");
135
+ }
136
+ if (matchingPaths === "") return null;
137
+ fail("profile_history_continuity_break", "baseline catalogue cannot be read");
138
+ }
139
+ }
140
+
141
+ function assertContinuity(catalogue, baselineContent) {
142
+ if (baselineContent === null) return "initial_catalogue";
143
+ if (baselineContent === undefined) {
144
+ fail("profile_history_continuity_break", "baseline continuity evidence is unavailable");
145
+ }
146
+ const baseline = parseJson(baselineContent, "profile_history_continuity_break", "baseline catalogue");
147
+ if (validateDistributionProfileHistory(baseline).status !== "matched") {
148
+ fail("profile_history_continuity_break", "baseline catalogue is invalid");
149
+ }
150
+ for (const [version, priorRelease] of Object.entries(baseline.releases)) {
151
+ const currentRelease = catalogue.releases[version];
152
+ const priorContract = baseline.contracts[priorRelease.contract_id];
153
+ const currentContract = catalogue.contracts[currentRelease?.contract_id];
154
+ if (JSON.stringify(currentRelease) !== JSON.stringify(priorRelease)
155
+ || JSON.stringify(currentContract) !== JSON.stringify(priorContract)) {
156
+ fail("profile_history_continuity_break", `supported release ${version} was removed or changed`);
157
+ }
158
+ }
159
+ return "matched";
160
+ }
161
+
162
+ export function assertDistributionProfileHistory({
163
+ repoRoot,
164
+ catalogueContent,
165
+ generatedContents,
166
+ currentDefinition,
167
+ readTagFile,
168
+ tagExists,
169
+ baselineContent,
170
+ } = {}) {
171
+ repoRoot = repoRoot ? resolve(repoRoot) : null;
172
+ catalogueContent ??= readFileSync(join(repoRoot, HISTORY_PATH), "utf8");
173
+ const canonicalBytes = catalogueContent.replaceAll("\r\n", "\n");
174
+ const catalogue = parseJson(canonicalBytes, "profile_history_invalid", "distribution profile history");
175
+ if (validateDistributionProfileHistory(catalogue).status !== "matched") {
176
+ fail("profile_history_invalid", "catalogue schema or digest validation failed");
177
+ }
178
+
179
+ currentDefinition ??= JSON.parse(readFileSync(join(repoRoot, "plugin", "meta", "agdf-plugin.definition.json"), "utf8"));
180
+ const current = classifyHistoricalDistributionProfile({
181
+ catalogue,
182
+ version: currentDefinition.version,
183
+ distributionProfiles: currentDefinition.distributionProfiles,
184
+ });
185
+ if (current.status !== "matched") {
186
+ fail("profile_history_current_release_mismatch", `current definition ${currentDefinition.version} has no exact matching snapshot`);
187
+ }
188
+
189
+ generatedContents ??= Object.fromEntries(GENERATED_HISTORY_PATHS.map((path) => [
190
+ path,
191
+ existsSync(join(repoRoot, path)) ? readFileSync(join(repoRoot, path), "utf8") : null,
192
+ ]));
193
+ for (const path of GENERATED_HISTORY_PATHS) {
194
+ if (generatedContents[path] !== canonicalBytes) {
195
+ fail("profile_history_current_release_mismatch", `generated catalogue drift at ${path}`);
196
+ }
197
+ }
198
+
199
+ const suppliedReadTagFile = Boolean(readTagFile);
200
+ readTagFile ??= defaultReadTagFile(repoRoot);
201
+ tagExists ??= suppliedReadTagFile && !repoRoot ? () => true : defaultTagExists(repoRoot);
202
+ const supportedVersions = [...new Set([
203
+ ...SUPPORTED_PROFILE_RELEASES,
204
+ ...Object.keys(catalogue.releases),
205
+ ])];
206
+ for (const version of supportedVersions) {
207
+ const tag = `agdf-v${version}`;
208
+ if (version === currentDefinition.version && !tagExists(tag)) continue;
209
+ assertTagRecord(catalogue, version, readTagFile);
210
+ }
211
+ assertIncoherentTagNegative(catalogue, readTagFile);
212
+
213
+ if (baselineContent === undefined && repoRoot) baselineContent = baselineFromRepository(repoRoot);
214
+ const continuity = assertContinuity(catalogue, baselineContent);
215
+ return {
216
+ catalogue,
217
+ currentVersion: currentDefinition.version,
218
+ supportedVersions,
219
+ generatedPaths: [...GENERATED_HISTORY_PATHS],
220
+ continuity,
221
+ };
222
+ }