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.
- package/README.md +31 -7
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -4
- package/generated/plugins/agdf/hooks/copilot-hooks.json +15 -0
- package/generated/plugins/agdf/hooks/hooks.json +3 -2
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/plugin.json +22 -0
- package/generated/plugins/agdf/runtime/agdf-local.js +1 -1
- package/generated/plugins/agdf/runtime/agdf-session-check.js +97 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +10 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +17 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime-check-consent/contract.js +73 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +50 -5
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +12 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +23 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -2
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +44 -2
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +8 -1
- package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
- package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
- package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
- package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
- package/generated/submissions/openai/agdf/submission/openai/release-notes.md +12 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +23 -1
- package/lib/cli/application.js +291 -20
- package/lib/cli/command-registry.js +10 -2
- package/lib/cli/parse-args.js +17 -0
- package/lib/installers/copilot-precedence.js +34 -0
- package/lib/installers/copilot-settings.js +84 -0
- package/lib/installers/local-marketplace.js +2 -0
- package/lib/installers/opencode.js +21 -5
- package/lib/installers/plugin-installers.js +155 -4
- package/lib/lifecycle/operations.js +3 -0
- package/lib/lifecycle/presentation.js +43 -2
- package/lib/lifecycle/result.js +10 -0
- package/lib/lifecycle/status.js +7 -3
- package/lib/public-plugin/manifest.js +34 -2
- package/lib/public-plugin/validator.js +1 -1
- package/lib/release/version-coherence.js +1 -0
- package/lib/runtime-check-consent/adapters.js +42 -0
- package/lib/runtime-check-consent/claude-settings.js +71 -0
- package/lib/runtime-check-consent/contract.js +73 -0
- package/lib/runtime-check-consent/coordinator.js +38 -0
- package/lib/runtime-check-consent/service.js +181 -0
- package/lib/runtime-check-consent/state.js +90 -0
- package/lib/scaffold/plan.js +3 -89
- package/lib/scaffold/presentation.js +3 -22
- package/opencode-plugin.js +30 -15
- package/package.json +6 -3
- package/generated/.github/copilot-instructions.md +0 -16
- package/generated/.github/instructions/agdf-governance.instructions.md +0 -14
- package/generated/.github/skills/README.md +0 -20
- package/generated/AGENTS.md +0 -113
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-brownfield-analysis/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-clean-implementation-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-code-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-closeout/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-delivery-path-search/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-gate-check/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-interaction-locales.json +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-qa-gate/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-release-or/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-runtime-contract.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-task-plan-review/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/agdf-ux-intent-definition/SKILL.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/closeout.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/context-graph.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/control-scaffold.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/gate-transition.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/interaction.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/modes.md +0 -0
- /package/generated/{.github/skills → plugins/agdf/copilot-skills}/contracts/quality.md +0 -0
- /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 =
|
|
71
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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
|
-
|
|
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
|
}
|
package/lib/lifecycle/result.js
CHANGED
|
@@ -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",
|
package/lib/lifecycle/status.js
CHANGED
|
@@ -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"
|
|
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
|
+
}
|