create-agdf 0.13.7 → 0.14.1

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 (79) hide show
  1. package/README.md +31 -7
  2. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  3. package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -4
  4. package/generated/plugins/agdf/hooks/copilot-hooks.json +15 -0
  5. package/generated/plugins/agdf/hooks/hooks.json +3 -2
  6. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
  7. package/generated/plugins/agdf/plugin.json +22 -0
  8. package/generated/plugins/agdf/runtime/agdf-local.js +1 -1
  9. package/generated/plugins/agdf/runtime/agdf-session-check.js +97 -0
  10. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
  11. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +10 -2
  12. package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
  13. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +73 -0
  14. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  15. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  16. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +50 -5
  17. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  18. package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
  19. package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
  20. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
  21. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -2
  22. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +44 -2
  23. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  24. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
  25. package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
  26. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  27. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  28. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  29. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +12 -1
  30. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
  31. package/lib/cli/application.js +291 -20
  32. package/lib/cli/command-registry.js +10 -2
  33. package/lib/cli/parse-args.js +17 -0
  34. package/lib/installers/copilot-precedence.js +34 -0
  35. package/lib/installers/copilot-settings.js +84 -0
  36. package/lib/installers/local-marketplace.js +2 -0
  37. package/lib/installers/opencode.js +21 -5
  38. package/lib/installers/plugin-installers.js +155 -4
  39. package/lib/lifecycle/operations.js +3 -0
  40. package/lib/lifecycle/presentation.js +43 -2
  41. package/lib/lifecycle/result.js +10 -0
  42. package/lib/lifecycle/status.js +7 -3
  43. package/lib/public-plugin/manifest.js +34 -2
  44. package/lib/public-plugin/validator.js +1 -1
  45. package/lib/release/version-coherence.js +1 -0
  46. package/lib/runtime-check-consent/adapters.js +42 -0
  47. package/lib/runtime-check-consent/claude-settings.js +71 -0
  48. package/lib/runtime-check-consent/contract.js +73 -0
  49. package/lib/runtime-check-consent/coordinator.js +38 -0
  50. package/lib/runtime-check-consent/service.js +181 -0
  51. package/lib/runtime-check-consent/state.js +90 -0
  52. package/lib/scaffold/plan.js +3 -89
  53. package/lib/scaffold/presentation.js +3 -22
  54. package/opencode-plugin.js +30 -15
  55. package/package.json +6 -3
  56. package/generated/.github/copilot-instructions.md +0 -16
  57. package/generated/.github/instructions/agdf-governance.instructions.md +0 -14
  58. package/generated/.github/skills/README.md +0 -20
  59. package/generated/AGENTS.md +0 -113
  60. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-brownfield-analysis/SKILL.md +0 -0
  61. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-clean-implementation-review/SKILL.md +0 -0
  62. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-code-review/SKILL.md +0 -0
  63. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-closeout/SKILL.md +0 -0
  64. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-path-search/SKILL.md +0 -0
  65. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-gate-check/SKILL.md +0 -0
  66. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-interaction-locales.json +0 -0
  67. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-qa-gate/SKILL.md +0 -0
  68. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-release-or/SKILL.md +0 -0
  69. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-runtime-contract.md +0 -0
  70. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-task-plan-review/SKILL.md +0 -0
  71. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-ux-intent-definition/SKILL.md +0 -0
  72. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/closeout.md +0 -0
  73. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/context-graph.md +0 -0
  74. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/control-scaffold.md +0 -0
  75. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/gate-transition.md +0 -0
  76. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/interaction.md +0 -0
  77. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/modes.md +0 -0
  78. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/quality.md +0 -0
  79. /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/task-target-resolution.md +0 -0
@@ -398,6 +398,7 @@ export function prepareLocalMarketplace({
398
398
  codexInstallVersion,
399
399
  sourceDigest,
400
400
  digest: pluginDigest,
401
+ runtimeDigest: runtimeManifest.digest,
401
402
  existingClassification,
402
403
  changed: false,
403
404
  commit() {},
@@ -420,6 +421,7 @@ export function prepareLocalMarketplace({
420
421
  codexInstallVersion,
421
422
  sourceDigest,
422
423
  digest: pluginDigest,
424
+ runtimeDigest: runtimeManifest.digest,
423
425
  existingClassification,
424
426
  changed: true,
425
427
  commit() {
@@ -6,6 +6,7 @@ import process from "node:process";
6
6
  import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
7
7
  import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
8
8
  import { resolveLocalValidator } from "../runtime/local-validator.js";
9
+ import { digestDirectory } from "../runtime/plugin-provenance.js";
9
10
  import { validateLocalOpenCodePackageSource } from "./local-development.js";
10
11
 
11
12
  const contractModules = ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"];
@@ -17,6 +18,7 @@ const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-C
17
18
  const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
18
19
  const globalOpenCodeAgentOwnershipMarker = `<!-- AGDF-GLOBAL-AGENT: ${pluginDefinition.opencode.evaluatorAgentName} -->`;
19
20
  const globalOpenCodeValidatorPackageOwner = "create-agdf";
21
+ export const openCodePluginEntrypoint = `./node_modules/${pluginDefinition.opencode.npmPackage}/opencode-plugin.js`;
20
22
  const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
21
23
  const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
22
24
  const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
@@ -67,10 +69,14 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
67
69
  throw openCodeLifecycleError("configuration", `Refusing to update OpenCode config with non-array plugin field: ${configPath}`, { configPath });
68
70
  }
69
71
 
70
- const plugins = [...(config.plugin ?? [])];
71
- const alreadyInstalled = plugins.includes(pluginDefinition.opencode.npmPackage);
72
+ const plugins = (config.plugin ?? []).map((entry) => (
73
+ entry === pluginDefinition.opencode.npmPackage || entry.startsWith(`${pluginDefinition.opencode.npmPackage}@`)
74
+ ? openCodePluginEntrypoint
75
+ : entry
76
+ ));
77
+ const alreadyInstalled = plugins.includes(openCodePluginEntrypoint);
72
78
  if (!alreadyInstalled) {
73
- plugins.push(pluginDefinition.opencode.npmPackage);
79
+ plugins.push(openCodePluginEntrypoint);
74
80
  }
75
81
 
76
82
  const nextConfig = {
@@ -128,6 +134,10 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
128
134
  throw openCodeLifecycleError("configuration", `Failed to write OpenCode config ${configPath}: ${error.message}`, { configPath });
129
135
  }
130
136
  const installedPackage = resolveOpenCodePackage(configDir);
137
+ if (!installedPackage.loadable || !installedPackage.package_root) {
138
+ throw openCodeLifecycleError("verification", "Installed OpenCode AGDF package is not loadable after installation.", { configDir });
139
+ }
140
+ const installedPackageDigest = digestDirectory(installedPackage.package_root);
131
141
  const sdkAlignment = alignOpenCodePluginSdk(configDir);
132
142
 
133
143
  return {
@@ -135,6 +145,7 @@ export function installOpenCodeGlobalPlugin(configDir, dependencies = {}) {
135
145
  added: !alreadyInstalled,
136
146
  transition: openCodePackageTransition(previousPackage, installedPackage),
137
147
  sdk_alignment: sdkAlignment,
148
+ installed_package: { root: installedPackage.package_root, digest: installedPackageDigest },
138
149
  package_source: localPackageSource
139
150
  ? { kind: "local_checkout", path: localPackageSource.tarball, digest: localPackageSource.digest, version: localPackageSource.version }
140
151
  : { kind: "registry", path: "", digest: "", version: pluginDefinition.version },
@@ -736,7 +747,8 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
736
747
  const configPath = join(configDir, "opencode.json");
737
748
  const configState = readOpenCodeConfig(configPath);
738
749
  const plugins = Array.isArray(configState.config.plugin) ? configState.config.plugin : [];
739
- const globalConfigured = plugins.includes(pluginDefinition.opencode.npmPackage);
750
+ const globalConfigured = plugins.includes(openCodePluginEntrypoint);
751
+ const legacyPluginConfigured = plugins.some((entry) => entry === pluginDefinition.opencode.npmPackage || entry.startsWith(`${pluginDefinition.opencode.npmPackage}@`));
740
752
  const packageState = resolveOpenCodePackage(configDir);
741
753
  const packageVersionStatus = openCodePackageVersionStatus(packageState);
742
754
  const globalNativeSurface = evaluateGlobalOpenCodeSurface(configDir);
@@ -754,7 +766,9 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
754
766
  const findings = [];
755
767
  if (!configState.exists) findings.push("OpenCode global config not found.");
756
768
  if (configState.parseError) findings.push(`OpenCode global config is not valid JSON: ${configState.parseError}`);
757
- if (!globalConfigured) findings.push(`OpenCode global config does not include ${pluginDefinition.opencode.npmPackage}.`);
769
+ if (!globalConfigured) findings.push(legacyPluginConfigured
770
+ ? `OpenCode global config still uses the host-managed npm cache entry for ${pluginDefinition.opencode.npmPackage}; rerun installation to bind the verified local package entrypoint.`
771
+ : `OpenCode global config does not include ${openCodePluginEntrypoint}.`);
758
772
  if (!packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is not loadable from the OpenCode config directory.`);
759
773
  if (packageVersionStatus === "outdated") findings.push(`${pluginDefinition.opencode.npmPackage} version ${packageState.installed_version} is outdated; expected ${pluginDefinition.version}.`);
760
774
  if (packageVersionStatus === "unknown" && packageState.loadable) findings.push(`${pluginDefinition.opencode.npmPackage} is loadable but its installed version is unknown.`);
@@ -783,6 +797,8 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
783
797
  exists: configState.exists,
784
798
  parse_error: configState.parseError,
785
799
  plugin_configured: globalConfigured,
800
+ plugin_entrypoint: openCodePluginEntrypoint,
801
+ legacy_plugin_configured: legacyPluginConfigured,
786
802
  },
787
803
  package: {
788
804
  name: pluginDefinition.opencode.npmPackage,
@@ -1,7 +1,13 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import process from "node:process";
2
5
  import { pluginDefinition } from "../cli/runtime-context.js";
6
+ import { digestNormalizedPluginSource } from "../runtime/plugin-provenance.js";
3
7
  import { CODEX_REGISTRATION_REVISION, classifyMarketplaceList, inspectLocalMarketplaceProjection, isCodexLocalInstallVersion, prepareLocalMarketplace } from "./local-marketplace.js";
4
8
 
9
+ export const COPILOT_CLI_NPM_PACKAGE = "@github/copilot@1.0.80";
10
+
5
11
  export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
6
12
  const expectedVersion = pluginDefinition.version;
7
13
  const nativeOutput = [];
@@ -30,6 +36,10 @@ export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepar
30
36
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
31
37
  ...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
32
38
  ],
39
+ pluginRoot: transaction.pluginRoot ?? null,
40
+ digest: transaction.digest ?? null,
41
+ runtimeDigest: transaction.runtimeDigest ?? null,
42
+ sourceDigest: transaction.sourceDigest ?? null,
33
43
  nativeOutput: nativeOutput.filter(Boolean).map(String),
34
44
  };
35
45
  } catch (error) {
@@ -77,6 +87,10 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
77
87
  ...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
78
88
  ...(installedVersion ? [] : ["host_did_not_expose_version"]),
79
89
  ],
90
+ pluginRoot: transaction.pluginRoot ?? null,
91
+ digest: transaction.digest ?? null,
92
+ runtimeDigest: transaction.runtimeDigest ?? null,
93
+ sourceDigest: transaction.sourceDigest ?? null,
80
94
  nativeOutput: nativeOutput.filter(Boolean).map(String),
81
95
  };
82
96
  } catch (error) {
@@ -85,6 +99,142 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
85
99
  }
86
100
  }
87
101
 
102
+ export function installCopilotGlobalPlugin({ exec = execFileSync, packagedCopilotExec = execFileSync, prepare = prepareLocalMarketplace, dataRoot, pluginRoot } = {}) {
103
+ const expectedVersion = pluginDefinition.version;
104
+ const transaction = pluginRoot ? null : prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
105
+ const effectivePluginRoot = pluginRoot ?? transaction?.pluginRoot;
106
+ if (!effectivePluginRoot) throw lifecycleAdapterError("package", "AGDF Copilot plugin root is required.");
107
+ let runtimeManifest;
108
+ try {
109
+ runtimeManifest = JSON.parse(readFileSync(join(effectivePluginRoot, "runtime", "runtime-manifest.json"), "utf8"));
110
+ } catch (error) {
111
+ transaction?.rollback();
112
+ throw lifecycleAdapterError("package", `AGDF Copilot runtime manifest is unavailable: ${error.message}`);
113
+ }
114
+ const sourceDigest = transaction?.sourceDigest || digestNormalizedPluginSource(effectivePluginRoot, expectedVersion);
115
+ let before = "";
116
+ let activeExec = exec;
117
+ const bootstrapEvidence = [];
118
+ try {
119
+ before = activeExec("copilot", ["plugin", "list"], captureOptions());
120
+ } catch (error) {
121
+ if (error?.code !== "ENOENT") {
122
+ transaction?.rollback();
123
+ throw lifecycleAdapterError("verification", commandErrorText(error), { executable: "copilot", args: ["plugin", "list"] });
124
+ }
125
+ const invocation = copilotNpmInvocation();
126
+ activeExec = (_executable, args, options) => packagedCopilotExec(invocation.executable, [...invocation.args, ...args], options);
127
+ try {
128
+ before = activeExec("copilot", ["plugin", "list"], captureOptions());
129
+ bootstrapEvidence.push("copilot_cli_not_found", `copilot_cli_npm_package:${COPILOT_CLI_NPM_PACKAGE}`);
130
+ } catch (bootstrapError) {
131
+ transaction?.commit();
132
+ return {
133
+ surface: "copilot", operation: "install", expectedVersion, installedVersion: null,
134
+ verificationStatus: "unavailable", manualHandoff: true,
135
+ evidence: ["copilot_cli_not_found", `copilot_cli_npm_bootstrap_failed:${commandErrorText(bootstrapError)}`, "durable_local_plugin_stage", `local_plugin_root:${effectivePluginRoot}`, `source_digest:${sourceDigest}`],
136
+ pluginRoot: effectivePluginRoot, runtimeDigest: runtimeManifest.digest, sourceDigest, nativeOutput: [],
137
+ };
138
+ }
139
+ }
140
+ const directInstalled = pluginListHasPlugin(before, "agdf");
141
+ const marketplaceInstalled = pluginListHasPlugin(before, "agdf@agdf");
142
+ let marketplaceAdded = false;
143
+ let directRemoved = false;
144
+ try {
145
+ const nativeOutput = [];
146
+ const marketplaceOutput = runPluginPhase(activeExec, "copilot", ["plugin", "marketplace", "list"], "marketplace", captureOptions());
147
+ const marketplace = classifyCopilotMarketplaceList(marketplaceOutput, transaction?.root ?? resolve(effectivePluginRoot, "..", ".."));
148
+ if (marketplace.state === "conflict") {
149
+ throw lifecycleAdapterError("marketplace", `Refusing to replace non-AGDF Copilot marketplace registration agdf (${marketplace.source || "unknown source"}).`);
150
+ }
151
+ if (marketplace.state === "absent") {
152
+ nativeOutput.push(runPluginPhase(activeExec, "copilot", ["plugin", "marketplace", "add", transaction?.root ?? resolve(effectivePluginRoot, "..", "..")], "marketplace", captureOptions()));
153
+ marketplaceAdded = true;
154
+ }
155
+ if (directInstalled) {
156
+ nativeOutput.push(runPluginPhase(activeExec, "copilot", ["plugin", "uninstall", "agdf"], "plugin_operation", captureOptions()));
157
+ directRemoved = true;
158
+ }
159
+ nativeOutput.push(runPluginPhase(activeExec, "copilot", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
160
+ const after = runPluginPhase(activeExec, "copilot", ["plugin", "list"], "verification", captureOptions());
161
+ const installed = pluginListHasPlugin(after, "agdf@agdf");
162
+ const installedVersion = installed ? pluginVersionFromList(after, "agdf@agdf") : "";
163
+ if (!installed) throw lifecycleAdapterError("verification", "AGDF was not present in copilot plugin list after installation.");
164
+ if (installedVersion && installedVersion !== expectedVersion) {
165
+ throw lifecycleAdapterError("version", versionMismatchMessage("GitHub Copilot", "agdf", expectedVersion, installedVersion, "npx --yes @agdf/cli@latest copilot"));
166
+ }
167
+ transaction?.commit();
168
+ return {
169
+ surface: "copilot", operation: directInstalled || marketplaceInstalled ? "update" : "install", expectedVersion,
170
+ installedVersion: installedVersion || null, verificationStatus: installedVersion ? "healthy" : "degraded",
171
+ manualHandoff: false,
172
+ evidence: [...bootstrapEvidence, "durable_local_plugin_stage", "copilot plugin marketplace list", ...(marketplaceAdded ? ["copilot plugin marketplace add"] : ["marketplace:owned_local_current"]), ...(directRemoved ? ["direct_install_migrated"] : []), "copilot plugin install agdf@agdf", "copilot plugin list", `local_plugin_root:${effectivePluginRoot}`, `source_digest:${sourceDigest}`, ...(installedVersion ? [] : ["host_did_not_expose_version"])],
173
+ pluginRoot: effectivePluginRoot, runtimeDigest: runtimeManifest.digest, sourceDigest, nativeOutput: nativeOutput.filter(Boolean).map(String),
174
+ };
175
+ } catch (error) {
176
+ transaction?.rollback();
177
+ if (directRemoved) {
178
+ try {
179
+ activeExec("copilot", ["plugin", "install", effectivePluginRoot], captureOptions());
180
+ } catch (recoveryError) {
181
+ error.evidence = { ...(error.evidence ?? {}), direct_install_recovery: commandErrorText(recoveryError) };
182
+ }
183
+ }
184
+ if (marketplaceAdded) {
185
+ try {
186
+ activeExec("copilot", ["plugin", "marketplace", "remove", "agdf"], captureOptions());
187
+ } catch (recoveryError) {
188
+ error.evidence = { ...(error.evidence ?? {}), marketplace_recovery: commandErrorText(recoveryError) };
189
+ }
190
+ }
191
+ throw error;
192
+ }
193
+ }
194
+
195
+ export function classifyCopilotMarketplaceList(output, expectedRoot) {
196
+ const line = String(output || "").split(/\r?\n/).find((entry) => /^\s*[◆•]?\s*agdf\s+\(/.test(entry));
197
+ if (!line) return { state: "absent", source: "" };
198
+ const local = line.match(/\(Local:\s*(.+)\)\s*$/);
199
+ if (!local) return { state: "conflict", source: line.trim() };
200
+ return resolve(local[1]) === resolve(expectedRoot)
201
+ ? { state: "owned_local_current", source: local[1] }
202
+ : { state: "conflict", source: local[1] };
203
+ }
204
+
205
+ export function copilotNpmInvocation({ env = process.env, platform = process.platform, execPath = process.execPath } = {}) {
206
+ const args = ["exec", "--yes", `--package=${COPILOT_CLI_NPM_PACKAGE}`, "--", "copilot"];
207
+ if (env.npm_execpath) return { executable: execPath, args: [env.npm_execpath, ...args] };
208
+ return { executable: platform === "win32" ? "npm.cmd" : "npm", args };
209
+ }
210
+
211
+ export function setCopilotPluginEnabled({ enabled, exec = execFileSync } = {}) {
212
+ if (typeof enabled !== "boolean") throw lifecycleAdapterError("plugin_operation", "Copilot plugin enabled state must be boolean.");
213
+ const operation = enabled ? "enable" : "disable";
214
+ try {
215
+ const nativeOutput = runPluginPhase(exec, "copilot", ["plugin", operation, "agdf"], "plugin_operation", captureOptions());
216
+ const listOutput = runPluginPhase(exec, "copilot", ["plugin", "list"], "verification", captureOptions());
217
+ if (!pluginListHasPlugin(listOutput, "agdf") && !pluginListHasPlugin(listOutput, "agdf@agdf")) {
218
+ throw lifecycleAdapterError("verification", "AGDF was not present in copilot plugin list after the state change.");
219
+ }
220
+ return {
221
+ surface: "copilot",
222
+ requestedState: enabled ? "enabled" : "disabled",
223
+ status: "command_accepted_host_state_unverified",
224
+ evidence: [`copilot plugin ${operation} agdf`, "copilot plugin list", "fresh_session_required"],
225
+ nativeOutput: String(nativeOutput || ""),
226
+ };
227
+ } catch (error) {
228
+ if (/\bmanaged\b/i.test(commandErrorText(error))) {
229
+ return {
230
+ surface: "copilot", requestedState: enabled ? "enabled" : "disabled", status: "managed",
231
+ evidence: ["managed_policy_precedence", commandErrorText(error)], nativeOutput: "",
232
+ };
233
+ }
234
+ throw error;
235
+ }
236
+ }
237
+
88
238
  function migrateMarketplace({ surface, exec, output, root, nativeOutput, migration, refreshOwnedLocal = false }) {
89
239
  const classification = classifyMarketplaceList(surface, output, root);
90
240
  Object.assign(migration, classification);
@@ -168,10 +318,10 @@ function captureOptions() {
168
318
  }
169
319
 
170
320
  export function inspectPluginSurface(surface, exec = execFileSync, options = {}) {
171
- const executable = surface === "claude" ? "claude" : "codex";
172
- const pluginId = "agdf@agdf";
321
+ const executable = surface === "claude" ? "claude" : surface === "copilot" ? "copilot" : "codex";
173
322
  try {
174
323
  const output = exec(executable, ["plugin", "list"], { encoding: "utf8", stdio: "pipe" });
324
+ const pluginId = surface === "copilot" && !pluginListHasPlugin(output, "agdf@agdf") ? "agdf" : "agdf@agdf";
175
325
  const installed = pluginListHasPlugin(output, pluginId);
176
326
  const version = installed ? pluginVersionFromList(output, pluginId) : "";
177
327
  let localDevelopmentVersion = false;
@@ -219,12 +369,13 @@ function commandErrorText(error) {
219
369
  }
220
370
 
221
371
  export function pluginListHasPlugin(output, pluginId) {
372
+ const escapedPluginId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
222
373
  return output
223
374
  .split(/\r?\n/)
224
- .some((line) => line.includes(pluginId));
375
+ .some((line) => new RegExp(`(^|\\s)${escapedPluginId}(\\s|$)`).test(line));
225
376
  }
226
377
 
227
- const VERSION_PATTERN = "(\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?)";
378
+ const VERSION_PATTERN = "v?(\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?)";
228
379
 
229
380
  export function pluginVersionFromList(output, pluginId) {
230
381
  const escapedPluginId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -85,6 +85,9 @@ export function planGlobalUninstall(surface, { configDir } = {}) {
85
85
  if (surface === "claude") {
86
86
  return nativeUninstallPlan(surface, "claude", ["plugin", "uninstall", "agdf@agdf", "--scope", "user"]);
87
87
  }
88
+ if (surface === "copilot") {
89
+ return nativeUninstallPlan(surface, "copilot", ["plugin", "uninstall", "agdf"]);
90
+ }
88
91
  if (surface === "opencode") {
89
92
  if (!configDir) throw new Error("OpenCode uninstall requires its explicit config directory.");
90
93
  const configPath = join(configDir, "opencode.json");
@@ -9,6 +9,9 @@ const surfaceLabels = Object.freeze({
9
9
  function titleFor(report) {
10
10
  if (report.result === "failed") return "AGDF installation failed";
11
11
  if (report.result === "partial") return "AGDF installation partially completed";
12
+ if (report.result === "preview") return report.operation === "uninstall"
13
+ ? "AGDF uninstall preview"
14
+ : "AGDF installation cancelled";
12
15
  if (report.operation === "repository_setup") return "AGDF repository setup complete";
13
16
  return "AGDF installation complete";
14
17
  }
@@ -28,18 +31,55 @@ export function lifecycleCardLines(report) {
28
31
  `Installation: ${report.installation.status}`,
29
32
  `Activation: ${report.activation.status}`,
30
33
  `Repository delivery: ${report.delivery.status}`,
34
+ `Automatic runtime checks: requested=${report.runtime_checks.requested}; effective=${report.runtime_checks.effective}; reason=${report.runtime_checks.reason}`,
31
35
  `Verification: ${report.verification.status}`,
32
36
  `Restart required: ${report.restart.required ? "yes" : "no"}${report.restart.reason !== "none" ? ` (${report.restart.reason})` : ""}`,
33
37
  `Next action: ${report.next_action.text}`,
34
38
  ];
35
39
  }
36
40
 
37
- export function printLifecycleResult(report, { json = false, io = console } = {}) {
41
+ function friendlyRuntimeCheckState(report) {
42
+ if (report.runtime_checks.requested === "cancelled") return "Cancelled";
43
+ if (report.runtime_checks.effective === "enabled") return "On";
44
+ if (report.runtime_checks.effective === "manual") return "Manual";
45
+ if (report.runtime_checks.effective === "decision_required") return `Waiting for ${surfaceLabels[report.surface]} permission`;
46
+ if (report.runtime_checks.effective === "renewal_required") return "Needs your review";
47
+ if (["degraded", "failed", "unavailable"].includes(report.runtime_checks.effective)) return "Not available; use manual checks";
48
+ return report.runtime_checks.requested === "enabled" ? "Requested; verification pending" : "Not configured";
49
+ }
50
+
51
+ function compactVersionLine(report) {
52
+ if (report.version.transition === "updated" && report.version.previous && report.version.installed) {
53
+ return `Updated: ${report.version.previous} -> ${report.version.installed} (${report.version.status})`;
54
+ }
55
+ if (report.version.installed) return `Version: ${report.version.installed} (${report.version.status})`;
56
+ if (report.version.expected) return `Version: not verified (expected ${report.version.expected})`;
57
+ return "Version: not verified";
58
+ }
59
+
60
+ export function compactLifecycleCardLines(report) {
61
+ if (report.result === "preview") return [
62
+ titleFor(report),
63
+ "No changes were made.",
64
+ `Next: ${report.next_action.text}`,
65
+ ];
66
+ return [
67
+ report.result === "success"
68
+ ? `AGDF ${report.operation === "update" ? "updated" : "installed"} for ${surfaceLabels[report.surface]}`
69
+ : titleFor(report),
70
+ compactVersionLine(report),
71
+ `Installation: ${report.installation.status === "healthy" ? "Ready" : report.installation.status}`,
72
+ `Automatic checks: ${friendlyRuntimeCheckState(report)}`,
73
+ `Next: ${report.next_action.text}`,
74
+ ];
75
+ }
76
+
77
+ export function printLifecycleResult(report, { json = false, compact = false, io = console } = {}) {
38
78
  if (json) {
39
79
  io.log(JSON.stringify(report, null, 2));
40
80
  return;
41
81
  }
42
- for (const line of lifecycleCardLines(report)) io.log(line);
82
+ for (const line of compact ? compactLifecycleCardLines(report) : lifecycleCardLines(report)) io.log(line);
43
83
  if (report.failure) io.log(`Failure phase: ${report.failure.phase}: ${report.failure.message}`);
44
84
  if (report.retained.length) {
45
85
  io.log("Retained:");
@@ -56,5 +96,6 @@ export function printGeneralStatus(report, { json = false, io = console } = {})
56
96
  io.log(`Installation: ${report.installation.status}${report.installation.version ? ` (${report.installation.version})` : ""}`);
57
97
  io.log(`Repository: ${report.repository.status}`);
58
98
  io.log(`Delivery: ${report.delivery.status}${report.delivery.current_gate ? ` (${report.delivery.current_gate})` : ""}`);
99
+ io.log(`Automatic runtime checks: requested=${report.runtime_checks.requested}; effective=${report.runtime_checks.effective}; reason=${report.runtime_checks.reason}`);
59
100
  io.log(`Next action: ${report.next_action.text}`);
60
101
  }
@@ -5,6 +5,7 @@ const SURFACES = new Set(["codex", "claude", "copilot", "opencode", "generic"]);
5
5
  const GLOBAL_INSTALL_RESTART_ACTIONS = Object.freeze({
6
6
  codex: "Restart Codex.",
7
7
  claude: "Restart Claude Code.",
8
+ copilot: "Restart GitHub Copilot.",
8
9
  opencode: "Restart OpenCode.",
9
10
  });
10
11
 
@@ -39,6 +40,15 @@ export function createLifecycleResult(input = {}) {
39
40
  delivery: {
40
41
  status: text(input.delivery?.status) || "not_evaluated",
41
42
  },
43
+ runtime_checks: {
44
+ requested: text(input.runtime_checks?.requested) || "unknown",
45
+ effective: text(input.runtime_checks?.effective) || "unknown",
46
+ reason: text(input.runtime_checks?.reason) || "not_evaluated",
47
+ capability_identity: text(input.runtime_checks?.capability_identity) || null,
48
+ verification: text(input.runtime_checks?.verification) || "not_evaluated",
49
+ mutation: text(input.runtime_checks?.mutation) || "none",
50
+ rollback: text(input.runtime_checks?.rollback) || "none",
51
+ },
42
52
  restart: {
43
53
  required: Boolean(input.restart?.required),
44
54
  reason: text(input.restart?.reason) || "none",
@@ -6,6 +6,7 @@ import { evaluateGateCheck } from "../control-evaluation/gate-check.js";
6
6
  import { evaluateOpenCodeStatus } from "../installers/opencode.js";
7
7
  import { inspectPluginSurface } from "../installers/plugin-installers.js";
8
8
  import { inspectGeneratedRepositoryMarketplace } from "../runtime/plugin-provenance.js";
9
+ import { runtimeCheckStatus } from "../runtime-check-consent/service.js";
9
10
 
10
11
  function deliveryStatus(targetDir, selection, dependencies) {
11
12
  const controlDir = join(targetDir, ".agdf", "control");
@@ -62,7 +63,7 @@ function inspectInstallation(surface, targetDir, options, deps, exec) {
62
63
  evidence: [report.global_config.path],
63
64
  };
64
65
  }
65
- if (["codex", "claude"].includes(surface)) return deps.inspectPluginSurface(surface, exec);
66
+ if (["codex", "claude", "copilot"].includes(surface)) return deps.inspectPluginSurface(surface, exec);
66
67
  return { status: "unknown", surface, version: null, evidence: [`unsupported_status_probe:${surface}`] };
67
68
  }
68
69
 
@@ -78,7 +79,7 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
78
79
  if (surface) {
79
80
  installation = inspectInstallation(surface, targetDir, options, deps, dependencies.exec);
80
81
  } else {
81
- const probes = ["codex", "claude", "opencode"].map((candidate) => inspectInstallation(candidate, targetDir, options, deps, dependencies.exec));
82
+ const probes = ["codex", "claude", "copilot", "opencode"].map((candidate) => inspectInstallation(candidate, targetDir, options, deps, dependencies.exec));
82
83
  const observed = probes.filter((probe) => ["healthy", "degraded"].includes(probe.status));
83
84
  if (observed.length === 1) {
84
85
  installation = observed[0];
@@ -95,6 +96,9 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
95
96
  }
96
97
  const repository = repositoryStatus(targetDir, surface);
97
98
  const delivery = deliveryStatus(targetDir, { runId: options.runId }, deps);
99
+ const runtimeChecks = ["codex", "claude", "copilot", "opencode"].includes(surface)
100
+ ? runtimeCheckStatus(options.dataRoot, surface)
101
+ : { requested: "unknown", effective: "unavailable", reason: "unsupported_host_capability" };
98
102
  const nextText = surface === "multiple"
99
103
  ? "Multiple AGDF host surfaces were detected; rerun status with an explicit --surface."
100
104
  : surface === "generic"
@@ -112,5 +116,5 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
112
116
  : delivery.status === "not_configured"
113
117
  ? "Start a new task; status created no run and requires no approval. Initialize durable control only when the repository needs it."
114
118
  : "Continue with the reported delivery next step.";
115
- return { schema_version: 1, installation, repository, delivery, next_action: { kind: "action", text: nextText } };
119
+ return { schema_version: 1, installation, repository, delivery, runtime_checks: runtimeChecks, next_action: { kind: "action", text: nextText } };
116
120
  }
@@ -39,14 +39,12 @@ export function createCodexPluginManifest(definition, { publicCandidate = false
39
39
  websiteURL: distribution.urls.website,
40
40
  privacyPolicyURL: distribution.urls.privacy,
41
41
  termsOfServiceURL: distribution.urls.terms,
42
- supportURL: distribution.urls.support,
43
42
  composerIcon: definition.codex.composerIcon,
44
43
  logo: definition.codex.logo,
45
44
  defaultPrompt: pluginInterface.defaultPrompt,
46
45
  brandColor: definition.brandColor,
47
46
  },
48
47
  };
49
- if (!publicCandidate) manifest.hooks = `./${definition.codex.hooks}`;
50
48
  return manifest;
51
49
  }
52
50
 
@@ -70,3 +68,37 @@ export function createClaudePluginManifest(definition) {
70
68
  export function renderClaudePluginManifest(definition) {
71
69
  return `${JSON.stringify(createClaudePluginManifest(definition), null, 2)}\n`;
72
70
  }
71
+
72
+ function requireCopilotPath(value, field) {
73
+ if (typeof value !== "string" || !value || value.startsWith("/") || value.includes("..") || value.includes("\\")) {
74
+ throw new Error(`AGDF_COPILOT_PLUGIN_CONTRACT_INVALID: ${field} must be a relative POSIX path`);
75
+ }
76
+ return value;
77
+ }
78
+
79
+ export function createCopilotPluginManifest(definition) {
80
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(definition.id ?? "") || definition.id.length > 64) {
81
+ throw new Error("AGDF_COPILOT_PLUGIN_CONTRACT_INVALID: kebab-case name is required");
82
+ }
83
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(definition.version ?? "")) {
84
+ throw new Error("AGDF_COPILOT_PLUGIN_CONTRACT_INVALID: semantic version is required");
85
+ }
86
+ const copilot = definition.copilot ?? {};
87
+ return {
88
+ name: definition.id,
89
+ version: definition.version,
90
+ description: definition.description,
91
+ author: definition.author,
92
+ homepage: definition.homepage,
93
+ repository: definition.repository,
94
+ license: definition.license,
95
+ keywords: definition.keywords,
96
+ category: definition.category,
97
+ skills: requireCopilotPath(copilot.skills, "skills"),
98
+ hooks: requireCopilotPath(copilot.hooks, "hooks"),
99
+ };
100
+ }
101
+
102
+ export function renderCopilotPluginManifest(definition) {
103
+ return `${JSON.stringify(createCopilotPluginManifest(definition), null, 2)}\n`;
104
+ }
@@ -80,7 +80,7 @@ export function validateCandidate(root) {
80
80
  if (unicodeLength(manifest.interface?.developerName) > LISTING_LIMITS.developerName) throw new Error("AGDF_PUBLIC_PLUGIN_LISTING_LIMIT_EXCEEDED: developerName");
81
81
  const prompts = manifest.interface?.defaultPrompt ?? [];
82
82
  if (prompts.length !== LISTING_LIMITS.promptCount || prompts.some((prompt) => unicodeLength(prompt) > LISTING_LIMITS.promptLength)) throw new Error("AGDF_PUBLIC_PLUGIN_LISTING_LIMIT_EXCEEDED: defaultPrompt");
83
- for (const field of ["websiteURL", "privacyPolicyURL", "termsOfServiceURL", "supportURL"]) {
83
+ for (const field of ["websiteURL", "privacyPolicyURL", "termsOfServiceURL"]) {
84
84
  let url;
85
85
  try { url = new URL(manifest.interface?.[field]); } catch { throw new Error(`AGDF_PUBLIC_PLUGIN_CONTRACT_INVALID: ${field}`); }
86
86
  if (url.protocol !== "https:") throw new Error(`AGDF_PUBLIC_PLUGIN_CONTRACT_INVALID: ${field} must use HTTPS`);
@@ -32,6 +32,7 @@ const VERSION_SURFACES = [
32
32
  jsonVersion("create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json"),
33
33
  jsonVersion("create-agdf/generated/plugins/agdf/.codex-plugin/plugin.json"),
34
34
  jsonVersion("create-agdf/generated/plugins/agdf/.claude-plugin/plugin.json"),
35
+ jsonVersion("create-agdf/generated/plugins/agdf/plugin.json"),
35
36
  jsonVersion("create-agdf/generated/plugins/agdf/runtime/runtime-manifest.json"),
36
37
  jsonVersion("create-agdf/generated/plugins/agdf/runtime/create-agdf/package.json"),
37
38
  jsonVersion("create-agdf/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json"),
@@ -0,0 +1,42 @@
1
+ function fixedCommand(command) {
2
+ if (typeof command !== "string" || !command.trim() || /[\n\r;&|*?]/.test(command)) {
3
+ throw new Error("AGDF_RUNTIME_CHECK_COMMAND_NOT_EXACT");
4
+ }
5
+ return command.trim();
6
+ }
7
+
8
+ export function codexRuntimeCheckEvidence({ capabilityIdentity, observedIdentity, hookEnabled, reviewRequired = false }) {
9
+ if (hookEnabled && observedIdentity === capabilityIdentity) return { status: "enabled", capability_identity: capabilityIdentity, reason: "none" };
10
+ if (observedIdentity && observedIdentity !== capabilityIdentity) return { status: "renewal_required", reason: "capability_identity_changed" };
11
+ return { status: "decision_required", reason: reviewRequired ? "host_review_required" : "host_permission_unverified" };
12
+ }
13
+
14
+ export function claudePermissionRule({ platform, command }) {
15
+ const exact = fixedCommand(command);
16
+ return platform === "win32" ? `PowerShell(${exact})` : `Bash(${exact})`;
17
+ }
18
+
19
+ export function applyClaudeExactRule(settings, { rule, deny = [], ask = [] }) {
20
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("AGDF_CLAUDE_SETTINGS_INVALID");
21
+ if ([...deny, ...ask].includes(rule)) return { status: "degraded", reason: "host_permission_conflict", settings };
22
+ const next = structuredClone(settings);
23
+ next.permissions ??= {};
24
+ if (Array.isArray(next.permissions.allow) && next.permissions.allow.some((entry) => entry.includes("*") || entry !== entry.trim())) {
25
+ throw new Error("AGDF_CLAUDE_BROAD_OR_MALFORMED_RULE");
26
+ }
27
+ next.permissions.allow = [...new Set([...(next.permissions.allow ?? []), rule])];
28
+ return { status: "configured", reason: "none", settings: next };
29
+ }
30
+
31
+ export function revokeClaudeExactRule(settings, rule) {
32
+ const next = structuredClone(settings);
33
+ if (Array.isArray(next.permissions?.allow)) next.permissions.allow = next.permissions.allow.filter((entry) => entry !== rule);
34
+ return next;
35
+ }
36
+
37
+ export function openCodeRuntimeCheckEvidence({ capabilityIdentity, observedIdentity, packageLoadable, hookObserved }) {
38
+ if (!packageLoadable) return { status: "unavailable", reason: "unsupported_host_capability" };
39
+ if (observedIdentity !== capabilityIdentity) return { status: "renewal_required", reason: "installed_identity_mismatch" };
40
+ if (!hookObserved) return { status: "degraded", reason: "host_permission_unverified" };
41
+ return { status: "enabled", capability_identity: capabilityIdentity, reason: "none" };
42
+ }
@@ -0,0 +1,71 @@
1
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import process from "node:process";
5
+ import { renameSyncWithRetry } from "../fs-swap.js";
6
+ import { applyClaudeExactRule, revokeClaudeExactRule } from "./adapters.js";
7
+
8
+ export function defaultClaudeSettingsPath({ env = process.env, home = homedir() } = {}) {
9
+ return join(resolve(env.CLAUDE_CONFIG_DIR || join(home, ".claude")), "settings.json");
10
+ }
11
+
12
+ export function readClaudeSettings(path = defaultClaudeSettingsPath()) {
13
+ if (!existsSync(path)) return { exists: false, settings: {} };
14
+ const stats = lstatSync(path);
15
+ if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("AGDF_CLAUDE_SETTINGS_UNOWNED_PATH");
16
+ try {
17
+ const settings = JSON.parse(readFileSync(path, "utf8"));
18
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not an object");
19
+ return { exists: true, settings };
20
+ } catch (error) {
21
+ throw new Error(`AGDF_CLAUDE_SETTINGS_INVALID: ${error.message}`);
22
+ }
23
+ }
24
+
25
+ function atomicSettingsWrite(path, settings) {
26
+ mkdirSync(dirname(path), { recursive: true });
27
+ const temp = `${path}.agdf-tmp-${process.pid}`;
28
+ const backup = `${path}.agdf-backup-${process.pid}`;
29
+ const hadPrevious = existsSync(path);
30
+ const mode = hadPrevious ? lstatSync(path).mode & 0o777 : 0o600;
31
+ writeFileSync(temp, `${JSON.stringify(settings, null, 2)}\n`, { encoding: "utf8", flag: "wx", mode });
32
+ if (process.platform !== "win32") chmodSync(temp, mode);
33
+ try {
34
+ if (hadPrevious && process.platform === "win32") renameSyncWithRetry(path, backup);
35
+ renameSyncWithRetry(temp, path);
36
+ if (hadPrevious && process.platform === "win32") rmSync(backup, { force: true });
37
+ } catch (error) {
38
+ try { rmSync(temp, { force: true }); } catch {}
39
+ if (hadPrevious && process.platform === "win32" && existsSync(backup) && !existsSync(path)) renameSyncWithRetry(backup, path);
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ export function configureClaudeExactRuntimeRule({ path = defaultClaudeSettingsPath(), rule }) {
45
+ const before = readClaudeSettings(path);
46
+ const permissions = before.settings.permissions ?? {};
47
+ const applied = applyClaudeExactRule(before.settings, {
48
+ rule,
49
+ deny: Array.isArray(permissions.deny) ? permissions.deny : [],
50
+ ask: Array.isArray(permissions.ask) ? permissions.ask : [],
51
+ });
52
+ if (applied.status !== "configured") return { ...applied, path, rollback() {} };
53
+ atomicSettingsWrite(path, applied.settings);
54
+ return {
55
+ status: "configured",
56
+ reason: "host_permission_unverified",
57
+ path,
58
+ rollback() {
59
+ if (before.exists) atomicSettingsWrite(path, before.settings);
60
+ else rmSync(path, { force: true });
61
+ },
62
+ };
63
+ }
64
+
65
+ export function revokeClaudeRuntimeRule({ path = defaultClaudeSettingsPath(), rule }) {
66
+ const before = readClaudeSettings(path);
67
+ if (!before.exists) return { status: "manual", reason: "configuration_missing", path };
68
+ const next = revokeClaudeExactRule(before.settings, rule);
69
+ atomicSettingsWrite(path, next);
70
+ return { status: "manual", reason: "consent_not_provided", path };
71
+ }