create-agdf 0.13.5 → 0.13.7
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 +7 -1
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -3
- package/generated/plugins/agdf/hooks/session-start.sh +31 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/plugins/agdf/runtime/agdf-local.js +8 -1
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +116 -31
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +206 -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 +64 -19
- package/generated/plugins/agdf/submission/openai/availability.md +1 -1
- package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
- package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
- package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +30 -9
- package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
- package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -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 +1 -1
- package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
- package/lib/cli/application.js +4 -3
- package/lib/fs-swap.js +26 -0
- package/lib/installers/local-development.js +202 -0
- package/lib/installers/local-marketplace.js +253 -46
- package/lib/installers/opencode.js +17 -2
- package/lib/installers/plugin-installers.js +88 -22
- package/lib/lifecycle/operations.js +9 -5
- package/lib/lifecycle/status.js +10 -2
- package/lib/public-plugin/builder.js +5 -4
- package/lib/runtime/local-validator.js +116 -31
- package/lib/runtime/plugin-provenance.js +206 -0
- package/package.json +6 -2
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { pluginDefinition } from "../cli/runtime-context.js";
|
|
3
|
-
import { classifyMarketplaceList, prepareLocalMarketplace } from "./local-marketplace.js";
|
|
3
|
+
import { CODEX_REGISTRATION_REVISION, classifyMarketplaceList, inspectLocalMarketplaceProjection, isCodexLocalInstallVersion, prepareLocalMarketplace } from "./local-marketplace.js";
|
|
4
4
|
|
|
5
5
|
export function installCodexGlobalPlugin({ exec = execFileSync, prepare = prepareLocalMarketplace, dataRoot } = {}) {
|
|
6
6
|
const expectedVersion = pluginDefinition.version;
|
|
7
7
|
const nativeOutput = [];
|
|
8
|
-
const transaction = prepare({ expectedVersion, ...(dataRoot ? { dataRoot } : {}) });
|
|
9
|
-
const
|
|
8
|
+
const transaction = prepare({ expectedVersion, codexRegistrationRevision: CODEX_REGISTRATION_REVISION, ...(dataRoot ? { dataRoot } : {}) });
|
|
9
|
+
const expectedInstallVersion = transaction.codexInstallVersion ?? expectedVersion;
|
|
10
|
+
const migration = { state: "unknown", source: "", addedLocal: false, removedLegacy: false, refreshOwnedLocal: false, removedOwnedLocal: false };
|
|
10
11
|
try {
|
|
11
12
|
const marketplaceOutput = runPluginPhase(exec, "codex", ["plugin", "marketplace", "list", "--json"], "marketplace", captureOptions());
|
|
12
|
-
migrateMarketplace({ surface: "codex", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration });
|
|
13
|
-
nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "add", "agdf", "--
|
|
13
|
+
migrateMarketplace({ surface: "codex", exec, output: marketplaceOutput, root: transaction.root, nativeOutput, migration, refreshOwnedLocal: transaction.changed === true });
|
|
14
|
+
nativeOutput.push(runPluginPhase(exec, "codex", ["plugin", "add", "agdf@agdf", "--json"], "plugin_operation", captureOptions()));
|
|
14
15
|
const listOutput = runPluginPhase(exec, "codex", ["plugin", "list"], "verification", captureOptions());
|
|
15
16
|
const installedVersion = pluginVersionFromList(listOutput, "agdf@agdf");
|
|
16
|
-
if (installedVersion !==
|
|
17
|
-
throw lifecycleAdapterError("version", versionMismatchMessage("Codex", "agdf@agdf",
|
|
17
|
+
if (installedVersion !== expectedInstallVersion) {
|
|
18
|
+
throw lifecycleAdapterError("version", versionMismatchMessage("Codex", "agdf@agdf", expectedInstallVersion, installedVersion, "npx --yes @agdf/cli@latest codex"));
|
|
18
19
|
}
|
|
19
20
|
transaction.commit();
|
|
20
21
|
return {
|
|
21
|
-
surface: "codex", operation: migration.state === "owned_local_current" ? "update" : "install", expectedVersion, installedVersion, verificationStatus: "healthy",
|
|
22
|
-
evidence: [
|
|
22
|
+
surface: "codex", operation: migration.state === "owned_local_current" ? "update" : "install", expectedVersion: expectedInstallVersion, canonicalVersion: expectedVersion, installedVersion, verificationStatus: "healthy",
|
|
23
|
+
evidence: [
|
|
24
|
+
"durable_local_marketplace",
|
|
25
|
+
`marketplace:${migration.state}`,
|
|
26
|
+
"codex plugin list",
|
|
27
|
+
...(transaction.pluginRoot ? [`staged_plugin_root:${transaction.pluginRoot}`, "staged_installation_provenance:matched"] : []),
|
|
28
|
+
...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
|
|
29
|
+
...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
|
|
30
|
+
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
31
|
+
...(expectedInstallVersion === expectedVersion ? [] : [`canonical_version:${expectedVersion}`, `local_install_version:${expectedInstallVersion}`]),
|
|
32
|
+
],
|
|
23
33
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
24
34
|
};
|
|
25
35
|
} catch (error) {
|
|
@@ -39,7 +49,12 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
|
|
|
39
49
|
nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "marketplace", "update", "agdf"], "marketplace", captureOptions()));
|
|
40
50
|
const beforeList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
|
|
41
51
|
const alreadyInstalled = pluginListHasPlugin(beforeList, "agdf@agdf");
|
|
42
|
-
|
|
52
|
+
// `claude plugin update` keeps the cached copy when the version is unchanged, so a
|
|
53
|
+
// same-version local source change never reaches the host; reinstall replaces the content.
|
|
54
|
+
if (alreadyInstalled) {
|
|
55
|
+
nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "uninstall", "agdf@agdf"], "plugin_operation", captureOptions()));
|
|
56
|
+
}
|
|
57
|
+
nativeOutput.push(runPluginPhase(exec, "claude", ["plugin", "install", "agdf@agdf"], "plugin_operation", captureOptions()));
|
|
43
58
|
const afterList = runPluginPhase(exec, "claude", ["plugin", "list"], "verification", captureOptions());
|
|
44
59
|
const installedVersion = pluginVersionFromList(afterList, "agdf@agdf");
|
|
45
60
|
if (installedVersion && installedVersion !== expectedVersion) {
|
|
@@ -52,7 +67,16 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
|
|
|
52
67
|
expectedVersion,
|
|
53
68
|
installedVersion,
|
|
54
69
|
verificationStatus: installedVersion ? "healthy" : "degraded",
|
|
55
|
-
evidence: [
|
|
70
|
+
evidence: [
|
|
71
|
+
"durable_local_marketplace",
|
|
72
|
+
`marketplace:${migration.state}`,
|
|
73
|
+
"claude plugin list",
|
|
74
|
+
...(transaction.pluginRoot ? [`staged_plugin_root:${transaction.pluginRoot}`, "staged_installation_provenance:matched"] : []),
|
|
75
|
+
...(transaction.sourceDigest ? [`source_digest:${transaction.sourceDigest}`] : []),
|
|
76
|
+
...(transaction.digest ? [`plugin_digest:${transaction.digest}`] : []),
|
|
77
|
+
...(transaction.existingClassification === "owned_pre_provenance_rebuild" ? ["marketplace_recovery:owned_pre_provenance_rebuild", "loaded_session:restart_required"] : []),
|
|
78
|
+
...(installedVersion ? [] : ["host_did_not_expose_version"]),
|
|
79
|
+
],
|
|
56
80
|
nativeOutput: nativeOutput.filter(Boolean).map(String),
|
|
57
81
|
};
|
|
58
82
|
} catch (error) {
|
|
@@ -61,13 +85,21 @@ export function installClaudeGlobalPlugin({ exec = execFileSync, prepare = prepa
|
|
|
61
85
|
}
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
function migrateMarketplace({ surface, exec, output, root, nativeOutput, migration }) {
|
|
88
|
+
function migrateMarketplace({ surface, exec, output, root, nativeOutput, migration, refreshOwnedLocal = false }) {
|
|
65
89
|
const classification = classifyMarketplaceList(surface, output, root);
|
|
66
90
|
Object.assign(migration, classification);
|
|
67
91
|
if (["conflict", "unknown"].includes(classification.state)) {
|
|
68
92
|
throw lifecycleAdapterError("marketplace", `Refusing to replace non-AGDF marketplace registration agdf (${classification.reason || classification.state}).`, classification);
|
|
69
93
|
}
|
|
70
94
|
const executable = surface === "claude" ? "claude" : "codex";
|
|
95
|
+
if (surface === "codex" && classification.state === "owned_local_current" && refreshOwnedLocal) {
|
|
96
|
+
migration.refreshOwnedLocal = true;
|
|
97
|
+
nativeOutput.push(runPluginPhase(exec, executable, ["plugin", "marketplace", "remove", "agdf", "--json"], "marketplace", captureOptions()));
|
|
98
|
+
migration.removedOwnedLocal = true;
|
|
99
|
+
nativeOutput.push(runPluginPhase(exec, executable, ["plugin", "marketplace", "add", root, "--json"], "marketplace", captureOptions()));
|
|
100
|
+
migration.addedLocal = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
71
103
|
if (classification.state === "legacy_github") {
|
|
72
104
|
const args = surface === "claude"
|
|
73
105
|
? ["plugin", "marketplace", "remove", "agdf", "--scope", "user"]
|
|
@@ -91,10 +123,23 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
|
|
|
91
123
|
try {
|
|
92
124
|
exec(executable, args, captureOptions());
|
|
93
125
|
recovery.push({ args, status: "restored" });
|
|
126
|
+
return true;
|
|
94
127
|
} catch (rollbackError) {
|
|
95
128
|
recovery.push({ args, status: "failed", message: commandErrorText(rollbackError) });
|
|
129
|
+
return false;
|
|
96
130
|
}
|
|
97
131
|
};
|
|
132
|
+
if (migration?.refreshOwnedLocal) {
|
|
133
|
+
const refreshedRegistrationRemoved = migration.addedLocal
|
|
134
|
+
? attempt(["plugin", "marketplace", "remove", "agdf", "--json"])
|
|
135
|
+
: true;
|
|
136
|
+
rollbackMarketplaceFilesystem(transaction, recovery);
|
|
137
|
+
if (migration.removedOwnedLocal && refreshedRegistrationRemoved) {
|
|
138
|
+
attempt(["plugin", "marketplace", "add", migration.source || transaction.root, "--json"]);
|
|
139
|
+
}
|
|
140
|
+
error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
98
143
|
if (migration?.addedLocal) {
|
|
99
144
|
attempt(surface === "claude"
|
|
100
145
|
? ["plugin", "marketplace", "remove", "agdf", "--scope", "user"]
|
|
@@ -105,32 +150,44 @@ function recoverMarketplace({ surface, exec, migration, transaction, error }) {
|
|
|
105
150
|
? ["plugin", "marketplace", "add", migration.source, "--scope", "user"]
|
|
106
151
|
: ["plugin", "marketplace", "add", migration.source, "--json"]);
|
|
107
152
|
}
|
|
153
|
+
rollbackMarketplaceFilesystem(transaction, recovery);
|
|
154
|
+
error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rollbackMarketplaceFilesystem(transaction, recovery) {
|
|
108
158
|
try {
|
|
109
159
|
transaction.rollback();
|
|
110
160
|
recovery.push({ filesystem: transaction.root, status: "restored" });
|
|
111
161
|
} catch (rollbackError) {
|
|
112
162
|
recovery.push({ filesystem: transaction.root, status: "failed", message: rollbackError.message });
|
|
113
163
|
}
|
|
114
|
-
error.evidence = { ...(error.evidence ?? {}), rollback: recovery };
|
|
115
164
|
}
|
|
116
165
|
|
|
117
166
|
function captureOptions() {
|
|
118
167
|
return { encoding: "utf8", stdio: "pipe" };
|
|
119
168
|
}
|
|
120
169
|
|
|
121
|
-
export function inspectPluginSurface(surface, exec = execFileSync) {
|
|
170
|
+
export function inspectPluginSurface(surface, exec = execFileSync, options = {}) {
|
|
122
171
|
const executable = surface === "claude" ? "claude" : "codex";
|
|
123
172
|
const pluginId = "agdf@agdf";
|
|
124
173
|
try {
|
|
125
174
|
const output = exec(executable, ["plugin", "list"], { encoding: "utf8", stdio: "pipe" });
|
|
126
175
|
const installed = pluginListHasPlugin(output, pluginId);
|
|
127
176
|
const version = installed ? pluginVersionFromList(output, pluginId) : "";
|
|
177
|
+
let localDevelopmentVersion = false;
|
|
178
|
+
if (surface === "codex" && isCodexLocalInstallVersion(pluginDefinition.version, version)) {
|
|
179
|
+
try {
|
|
180
|
+
localDevelopmentVersion = inspectLocalMarketplaceProjection(options)?.codexInstallVersion === version;
|
|
181
|
+
} catch {
|
|
182
|
+
localDevelopmentVersion = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
128
185
|
return {
|
|
129
|
-
status: !installed ? "not_installed" : version === pluginDefinition.version ? "healthy" : "degraded",
|
|
186
|
+
status: !installed ? "not_installed" : version === pluginDefinition.version || localDevelopmentVersion ? "healthy" : "degraded",
|
|
130
187
|
surface,
|
|
131
188
|
version: version || null,
|
|
132
189
|
expected_version: pluginDefinition.version,
|
|
133
|
-
evidence: [`${executable} plugin list`, ...(installed && !version ? ["host_did_not_expose_version"] : [])],
|
|
190
|
+
evidence: [`${executable} plugin list`, ...(localDevelopmentVersion ? [`canonical_version:${pluginDefinition.version}`, `local_install_version:${version}`] : []), ...(installed && !version ? ["host_did_not_expose_version"] : [])],
|
|
134
191
|
};
|
|
135
192
|
} catch (error) {
|
|
136
193
|
return { status: "unknown", surface, version: null, expected_version: pluginDefinition.version, evidence: [commandErrorText(error)] };
|
|
@@ -167,14 +224,23 @@ export function pluginListHasPlugin(output, pluginId) {
|
|
|
167
224
|
.some((line) => line.includes(pluginId));
|
|
168
225
|
}
|
|
169
226
|
|
|
227
|
+
const VERSION_PATTERN = "(\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?)";
|
|
228
|
+
|
|
170
229
|
export function pluginVersionFromList(output, pluginId) {
|
|
171
230
|
const escapedPluginId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
231
|
+
const lines = output.split(/\r?\n/);
|
|
232
|
+
const entryIndex = lines.findIndex((entry) => new RegExp(`(^|\\s)${escapedPluginId}(\\s|$)`).test(entry));
|
|
233
|
+
if (entryIndex < 0) return "";
|
|
234
|
+
const sameLine = lines[entryIndex].match(new RegExp(`\\b${VERSION_PATTERN}\\b`));
|
|
235
|
+
if (sameLine) return sameLine[1];
|
|
236
|
+
// Current `claude plugin list` prints the plugin id and its `Version:` on separate
|
|
237
|
+
// lines; scan the entry's block until the next plugin id line.
|
|
238
|
+
for (let index = entryIndex + 1; index < lines.length; index += 1) {
|
|
239
|
+
if (/\S+@\S+/.test(lines[index])) break;
|
|
240
|
+
const blockMatch = lines[index].match(new RegExp(`\\bVersion:?\\s*${VERSION_PATTERN}\\b`, "i"));
|
|
241
|
+
if (blockMatch) return blockMatch[1];
|
|
242
|
+
}
|
|
243
|
+
return "";
|
|
178
244
|
}
|
|
179
245
|
|
|
180
246
|
function versionMismatchMessage(surface, pluginId, expectedVersion, installedVersion, correctiveCommand) {
|
|
@@ -4,6 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { pluginDefinition } from "../cli/runtime-context.js";
|
|
5
5
|
import { evaluateOpenCodeStatus, openCodeNpmInvocation } from "../installers/opencode.js";
|
|
6
6
|
import { inspectPluginSurface } from "../installers/plugin-installers.js";
|
|
7
|
+
import { inspectGeneratedRepositoryMarketplace } from "../runtime/plugin-provenance.js";
|
|
7
8
|
|
|
8
9
|
const CODEX_DISABLE_MARKER = "# AGDF-OWNED-REPOSITORY-PLUGIN-STATE";
|
|
9
10
|
|
|
@@ -12,8 +13,10 @@ function pluginSection(content, selector) {
|
|
|
12
13
|
return content.match(new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`))?.[0] ?? "";
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
function repositorySelector(targetDir) {
|
|
16
|
-
|
|
16
|
+
function repositorySelector(targetDir, config = "") {
|
|
17
|
+
const repository = inspectGeneratedRepositoryMarketplace(targetDir);
|
|
18
|
+
if (repository.status === "matched") return repository.selector;
|
|
19
|
+
return pluginSection(config, "agdf@agdf-repo") ? "agdf@agdf-repo" : "agdf@agdf";
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export function planRepositoryDisable(targetDir, surface) {
|
|
@@ -22,7 +25,7 @@ export function planRepositoryDisable(targetDir, surface) {
|
|
|
22
25
|
}
|
|
23
26
|
const path = join(targetDir, ".codex", "config.toml");
|
|
24
27
|
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
25
|
-
const selector = repositorySelector(targetDir);
|
|
28
|
+
const selector = repositorySelector(targetDir, existing);
|
|
26
29
|
const escaped = JSON.stringify(selector).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27
30
|
const sectionPattern = new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`);
|
|
28
31
|
const currentSection = pluginSection(existing, selector);
|
|
@@ -49,8 +52,9 @@ export function planRepositoryDisable(targetDir, surface) {
|
|
|
49
52
|
export function verifyRepositoryDisabled(targetDir) {
|
|
50
53
|
const path = join(targetDir, ".codex", "config.toml");
|
|
51
54
|
if (!existsSync(path)) return { status: "failed", evidence: [`missing:${path}`] };
|
|
52
|
-
const
|
|
53
|
-
const
|
|
55
|
+
const config = readFileSync(path, "utf8");
|
|
56
|
+
const selector = repositorySelector(targetDir, config);
|
|
57
|
+
const section = pluginSection(config, selector);
|
|
54
58
|
const matches = [...section.matchAll(/^enabled\s*=\s*(true|false)\s*$/gm)];
|
|
55
59
|
return matches.length === 1 && matches[0][1] === "false"
|
|
56
60
|
? { status: "healthy", evidence: [`${path}:${selector}:enabled=false`] }
|
package/lib/lifecycle/status.js
CHANGED
|
@@ -5,6 +5,7 @@ import { evaluateDoctor } from "../control-evaluation/doctor.js";
|
|
|
5
5
|
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
|
+
import { inspectGeneratedRepositoryMarketplace } from "../runtime/plugin-provenance.js";
|
|
8
9
|
|
|
9
10
|
function deliveryStatus(targetDir, selection, dependencies) {
|
|
10
11
|
const controlDir = join(targetDir, ".agdf", "control");
|
|
@@ -29,7 +30,8 @@ function deliveryStatus(targetDir, selection, dependencies) {
|
|
|
29
30
|
|
|
30
31
|
function repositoryStatus(targetDir, surface) {
|
|
31
32
|
if (surface === "codex") {
|
|
32
|
-
const
|
|
33
|
+
const repository = inspectGeneratedRepositoryMarketplace(targetDir);
|
|
34
|
+
const marketplace = repository.marketplacePath;
|
|
33
35
|
const disabled = join(targetDir, ".codex", "config.toml");
|
|
34
36
|
const config = existsSync(disabled) ? readFileSync(disabled, "utf8") : "";
|
|
35
37
|
const isDisabled = ["agdf@agdf-repo", "agdf@agdf"].some((selector) => {
|
|
@@ -37,7 +39,11 @@ function repositoryStatus(targetDir, surface) {
|
|
|
37
39
|
const section = config.match(new RegExp(`\\[plugins\\.${escaped}\\][\\s\\S]*?(?=\\n\\[|$)`))?.[0] ?? "";
|
|
38
40
|
return /^enabled\s*=\s*false\s*$/m.test(section);
|
|
39
41
|
});
|
|
40
|
-
return {
|
|
42
|
+
return {
|
|
43
|
+
status: isDisabled ? "disabled" : repository.status === "matched" ? "active" : repository.status === "invalid" ? "degraded" : "not_configured",
|
|
44
|
+
scope: "repository",
|
|
45
|
+
evidence: [marketplace, ...(repository.reason ? [repository.reason] : [])],
|
|
46
|
+
};
|
|
41
47
|
}
|
|
42
48
|
if (surface === "opencode") {
|
|
43
49
|
const instructions = join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName);
|
|
@@ -97,6 +103,8 @@ export function evaluateGeneralStatus(targetDir, options = {}, dependencies = {}
|
|
|
97
103
|
? `Run npx --yes @agdf/cli@latest ${surface} to install or repair AGDF.`
|
|
98
104
|
: repository.status === "disabled"
|
|
99
105
|
? "Restart the host to apply the repository-local disable; the healthy global AGDF installation remains available."
|
|
106
|
+
: repository.status === "degraded"
|
|
107
|
+
? "The repository marketplace is incomplete or invalid; use the supported repository scaffold or the healthy durable global AGDF installation."
|
|
100
108
|
: repository.status === "not_configured"
|
|
101
109
|
? `Run npx --yes @agdf/cli@latest ${surface === "codex" ? "codex-repo" : `${surface}-repo`} in this repository.`
|
|
102
110
|
: delivery.status === "blocked"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync,
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
4
5
|
import { assertPublicPluginContract, loadJson } from "./contract.js";
|
|
5
6
|
import { renderCodexPluginManifest } from "./manifest.js";
|
|
6
7
|
import { createReadinessReport, renderReadinessReport } from "./report.js";
|
|
@@ -66,11 +67,11 @@ export function buildPublicPluginCandidate({ repoRoot, outputRoot }) {
|
|
|
66
67
|
const backupRoot = `${outputRoot}.previous`;
|
|
67
68
|
if (existsSync(backupRoot)) throw new Error(`Refusing candidate swap while recovery backup exists: ${backupRoot}`);
|
|
68
69
|
const hadPrevious = existsSync(outputRoot);
|
|
69
|
-
if (hadPrevious)
|
|
70
|
+
if (hadPrevious) renameSyncWithRetry(outputRoot, backupRoot);
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
+
renameSyncWithRetry(temporaryRoot, outputRoot);
|
|
72
73
|
} catch (error) {
|
|
73
|
-
if (hadPrevious && existsSync(backupRoot) && !existsSync(outputRoot))
|
|
74
|
+
if (hadPrevious && existsSync(backupRoot) && !existsSync(outputRoot)) renameSyncWithRetry(backupRoot, outputRoot);
|
|
74
75
|
throw error;
|
|
75
76
|
}
|
|
76
77
|
if (hadPrevious) rmSync(backupRoot, { recursive: true, force: true });
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { existsSync, readFileSync,
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
4
3
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
4
|
import process from "node:process";
|
|
5
|
+
import {
|
|
6
|
+
INSTALLATION_PROVENANCE_FILE,
|
|
7
|
+
digestDirectory,
|
|
8
|
+
inspectGeneratedRepositoryMarketplace,
|
|
9
|
+
inspectInstallationProvenance,
|
|
10
|
+
validateDistributionProfiles,
|
|
11
|
+
} from "./plugin-provenance.js";
|
|
6
12
|
|
|
7
13
|
function readJson(path) {
|
|
8
14
|
try {
|
|
@@ -12,27 +18,12 @@ function readJson(path) {
|
|
|
12
18
|
}
|
|
13
19
|
}
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function visit(directory) {
|
|
18
|
-
for (const name of readdirSync(directory).sort()) {
|
|
19
|
-
const path = join(directory, name);
|
|
20
|
-
const stats = statSync(path);
|
|
21
|
-
if (stats.isDirectory()) visit(path);
|
|
22
|
-
else if (stats.isFile()) files.push(path);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
visit(root);
|
|
26
|
-
const hash = createHash("sha256");
|
|
27
|
-
for (const path of files) {
|
|
28
|
-
hash.update(relative(root, path).replaceAll("\\", "/"));
|
|
29
|
-
hash.update("\0");
|
|
30
|
-
hash.update(readFileSync(path));
|
|
31
|
-
hash.update("\0");
|
|
32
|
-
}
|
|
33
|
-
return hash.digest("hex");
|
|
21
|
+
function canonicalPath(path) {
|
|
22
|
+
try { return realpathSync(path); } catch { return resolve(path); }
|
|
34
23
|
}
|
|
35
24
|
|
|
25
|
+
export { digestDirectory };
|
|
26
|
+
|
|
36
27
|
function envelope(machineValidation, options, evidence = {}) {
|
|
37
28
|
return {
|
|
38
29
|
schema_version: "1",
|
|
@@ -42,6 +33,14 @@ function envelope(machineValidation, options, evidence = {}) {
|
|
|
42
33
|
observed_version: evidence.observedVersion ?? null,
|
|
43
34
|
source: evidence.source ?? null,
|
|
44
35
|
registry_access: false,
|
|
36
|
+
distribution_profile: evidence.distributionProfile ?? null,
|
|
37
|
+
evidence_plane: evidence.evidencePlane ?? null,
|
|
38
|
+
canonical_version: evidence.canonicalVersion ?? options.expectedVersion ?? null,
|
|
39
|
+
plugin_version: evidence.pluginVersion ?? null,
|
|
40
|
+
runtime_digest: evidence.runtimeDigest ?? null,
|
|
41
|
+
source_digest: evidence.sourceDigest ?? null,
|
|
42
|
+
plugin_root: evidence.pluginRoot ?? null,
|
|
43
|
+
provenance_status: evidence.provenanceStatus ?? null,
|
|
45
44
|
...(evidence.reason ? { reason: evidence.reason } : {}),
|
|
46
45
|
};
|
|
47
46
|
}
|
|
@@ -51,17 +50,17 @@ export function resolveLocalValidator(options) {
|
|
|
51
50
|
const packageManifest = readJson(join(options.ownedPackageRoot, "package.json"));
|
|
52
51
|
const observedVersion = packageManifest?.version ?? null;
|
|
53
52
|
if (!packageManifest) {
|
|
54
|
-
return { envelope: envelope("unavailable", options, { source: "config_local_package", reason: "package_missing" }) };
|
|
53
|
+
return { envelope: envelope("unavailable", options, { source: "config_local_package", evidencePlane: "opencode_config_local", distributionProfile: "opencode-config-local", reason: "package_missing" }) };
|
|
55
54
|
}
|
|
56
55
|
if (observedVersion !== options.expectedVersion) {
|
|
57
|
-
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "config_local_package" }) };
|
|
56
|
+
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "config_local_package", evidencePlane: "opencode_config_local", distributionProfile: "opencode-config-local" }) };
|
|
58
57
|
}
|
|
59
58
|
const entrypoint = join(options.ownedPackageRoot, "bin", "create-agdf.js");
|
|
60
59
|
if (!existsSync(entrypoint)) {
|
|
61
|
-
return { envelope: envelope("unavailable", options, { observedVersion, source: "config_local_package", reason: "entrypoint_missing" }) };
|
|
60
|
+
return { envelope: envelope("unavailable", options, { observedVersion, source: "config_local_package", evidencePlane: "opencode_config_local", distributionProfile: "opencode-config-local", reason: "entrypoint_missing" }) };
|
|
62
61
|
}
|
|
63
62
|
return {
|
|
64
|
-
envelope: envelope("owned_version_matched", options, { observedVersion, source: "config_local_package" }),
|
|
63
|
+
envelope: envelope("owned_version_matched", options, { observedVersion, source: "config_local_package", evidencePlane: "opencode_config_local", distributionProfile: "opencode-config-local", provenanceStatus: "matched" }),
|
|
65
64
|
executable: process.execPath,
|
|
66
65
|
prefixArgs: [entrypoint],
|
|
67
66
|
};
|
|
@@ -73,30 +72,116 @@ export function resolveLocalValidator(options) {
|
|
|
73
72
|
return { envelope: envelope("version_mismatch", options, { source: "plugin_bundle", reason: "manifest_invalid" }) };
|
|
74
73
|
}
|
|
75
74
|
if (manifest) {
|
|
75
|
+
const pluginRoot = canonicalPath(options.pluginRoot ?? join(options.runtimeRoot, ".."));
|
|
76
|
+
if (options.expectedPluginRoot && canonicalPath(options.expectedPluginRoot) !== pluginRoot) {
|
|
77
|
+
return { envelope: envelope("version_mismatch", options, { source: "plugin_bundle", pluginRoot, evidencePlane: "loaded_session", distributionProfile: "runtime-plugin", provenanceStatus: "mismatch", reason: "plugin_root_mismatch" }) };
|
|
78
|
+
}
|
|
79
|
+
const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"));
|
|
80
|
+
const profile = validateDistributionProfiles(definition);
|
|
81
|
+
if (profile.status !== "matched") {
|
|
82
|
+
return { envelope: envelope("version_mismatch", options, { source: "plugin_bundle", pluginRoot, distributionProfile: "runtime-plugin", provenanceStatus: "invalid", reason: "profile_invalid" }) };
|
|
83
|
+
}
|
|
84
|
+
const codexManifest = readJson(join(pluginRoot, ".codex-plugin", "plugin.json"));
|
|
85
|
+
const claudeManifest = readJson(join(pluginRoot, ".claude-plugin", "plugin.json"));
|
|
86
|
+
const pluginVersion = codexManifest?.version ?? null;
|
|
87
|
+
if (!codexManifest || !claudeManifest || claudeManifest.version !== definition.version) {
|
|
88
|
+
return { envelope: envelope("version_mismatch", options, { source: "plugin_bundle", pluginRoot, pluginVersion, canonicalVersion: definition.version, distributionProfile: "runtime-plugin", provenanceStatus: "invalid", reason: "manifest_invalid" }) };
|
|
89
|
+
}
|
|
76
90
|
const packageRoot = join(options.runtimeRoot, "create-agdf");
|
|
77
91
|
if (!existsSync(packageRoot)) {
|
|
78
|
-
return { envelope: envelope("unavailable", options, { source: "plugin_bundle", reason: "
|
|
92
|
+
return { envelope: envelope("unavailable", options, { source: "plugin_bundle", pluginRoot, pluginVersion, distributionProfile: "runtime-plugin", reason: "runtime_missing" }) };
|
|
79
93
|
}
|
|
80
94
|
const packageManifest = readJson(join(packageRoot, "package.json"));
|
|
81
95
|
const observedVersion = packageManifest?.version ?? manifest.version ?? null;
|
|
82
96
|
if (observedVersion !== options.expectedVersion || manifest.version !== options.expectedVersion) {
|
|
83
|
-
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle" }) };
|
|
97
|
+
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle", pluginRoot, pluginVersion, canonicalVersion: definition.version, distributionProfile: "runtime-plugin" }) };
|
|
84
98
|
}
|
|
85
99
|
let observedDigest = null;
|
|
86
100
|
try { observedDigest = digestDirectory(packageRoot); } catch {}
|
|
87
101
|
if (!observedDigest || manifest.digest !== observedDigest) {
|
|
88
|
-
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle", reason: "
|
|
102
|
+
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle", pluginRoot, pluginVersion, canonicalVersion: definition.version, runtimeDigest: observedDigest, distributionProfile: "runtime-plugin", reason: "runtime_digest_mismatch" }) };
|
|
89
103
|
}
|
|
90
104
|
const entrypoint = resolve(options.runtimeRoot, manifest.entrypoint ?? "");
|
|
91
105
|
const entrypointRelative = relative(options.runtimeRoot, entrypoint);
|
|
92
106
|
if (!manifest.entrypoint || entrypointRelative === ".." || entrypointRelative.startsWith(`..${sep}`) || isAbsolute(entrypointRelative)) {
|
|
93
|
-
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle", reason: "invalid_entrypoint" }) };
|
|
107
|
+
return { envelope: envelope("version_mismatch", options, { observedVersion, source: "plugin_bundle", pluginRoot, pluginVersion, canonicalVersion: definition.version, runtimeDigest: observedDigest, distributionProfile: "runtime-plugin", reason: "invalid_entrypoint" }) };
|
|
94
108
|
}
|
|
95
109
|
if (!existsSync(entrypoint)) {
|
|
96
|
-
return { envelope: envelope("unavailable", options, { observedVersion, source: "plugin_bundle", reason: "entrypoint_missing" }) };
|
|
110
|
+
return { envelope: envelope("unavailable", options, { observedVersion, source: "plugin_bundle", pluginRoot, pluginVersion, canonicalVersion: definition.version, runtimeDigest: observedDigest, distributionProfile: "runtime-plugin", reason: "entrypoint_missing" }) };
|
|
111
|
+
}
|
|
112
|
+
const installed = existsSync(join(pluginRoot, INSTALLATION_PROVENANCE_FILE));
|
|
113
|
+
let provenanceStatus = "not_applicable";
|
|
114
|
+
let sourceDigest = null;
|
|
115
|
+
let evidencePlane = "loaded_session";
|
|
116
|
+
if (installed) {
|
|
117
|
+
const provenance = inspectInstallationProvenance(pluginRoot, {
|
|
118
|
+
definition,
|
|
119
|
+
runtimeManifest: manifest,
|
|
120
|
+
pluginVersion,
|
|
121
|
+
});
|
|
122
|
+
provenanceStatus = provenance.status;
|
|
123
|
+
sourceDigest = provenance.marker?.source_digest ?? null;
|
|
124
|
+
if (provenance.status !== "matched") {
|
|
125
|
+
return { envelope: envelope("version_mismatch", options, {
|
|
126
|
+
observedVersion,
|
|
127
|
+
source: "plugin_bundle",
|
|
128
|
+
pluginRoot,
|
|
129
|
+
pluginVersion,
|
|
130
|
+
canonicalVersion: definition.version,
|
|
131
|
+
runtimeDigest: observedDigest,
|
|
132
|
+
sourceDigest,
|
|
133
|
+
distributionProfile: "runtime-plugin",
|
|
134
|
+
evidencePlane: "loaded_session",
|
|
135
|
+
provenanceStatus,
|
|
136
|
+
reason: provenance.reason,
|
|
137
|
+
}) };
|
|
138
|
+
}
|
|
139
|
+
} else if (existsSync(join(pluginRoot, ".agdf-local-install.json"))) {
|
|
140
|
+
return { envelope: envelope("unavailable", options, {
|
|
141
|
+
observedVersion,
|
|
142
|
+
source: "plugin_bundle",
|
|
143
|
+
pluginRoot,
|
|
144
|
+
pluginVersion,
|
|
145
|
+
canonicalVersion: definition.version,
|
|
146
|
+
runtimeDigest: observedDigest,
|
|
147
|
+
distributionProfile: "runtime-plugin",
|
|
148
|
+
evidencePlane: "loaded_session",
|
|
149
|
+
provenanceStatus: "missing",
|
|
150
|
+
reason: "installation_provenance_missing",
|
|
151
|
+
}) };
|
|
152
|
+
} else {
|
|
153
|
+
const generatedRepository = inspectGeneratedRepositoryMarketplace(resolve(pluginRoot, "..", ".."));
|
|
154
|
+
if (pluginVersion !== definition.version
|
|
155
|
+
|| generatedRepository.status !== "matched"
|
|
156
|
+
|| canonicalPath(generatedRepository.pluginRoot) !== pluginRoot) {
|
|
157
|
+
return { envelope: envelope("unavailable", options, {
|
|
158
|
+
observedVersion,
|
|
159
|
+
source: "plugin_bundle",
|
|
160
|
+
pluginRoot,
|
|
161
|
+
pluginVersion,
|
|
162
|
+
canonicalVersion: definition.version,
|
|
163
|
+
runtimeDigest: observedDigest,
|
|
164
|
+
distributionProfile: "runtime-plugin",
|
|
165
|
+
evidencePlane: "installed_plugin_root",
|
|
166
|
+
provenanceStatus: "missing",
|
|
167
|
+
reason: "installation_provenance_missing",
|
|
168
|
+
}) };
|
|
169
|
+
}
|
|
170
|
+
evidencePlane = "generated_bundle";
|
|
97
171
|
}
|
|
98
172
|
return {
|
|
99
|
-
envelope: envelope("owned_version_matched", options, {
|
|
173
|
+
envelope: envelope("owned_version_matched", options, {
|
|
174
|
+
observedVersion,
|
|
175
|
+
source: "plugin_bundle",
|
|
176
|
+
pluginRoot,
|
|
177
|
+
pluginVersion,
|
|
178
|
+
canonicalVersion: definition.version,
|
|
179
|
+
runtimeDigest: observedDigest,
|
|
180
|
+
sourceDigest,
|
|
181
|
+
distributionProfile: "runtime-plugin",
|
|
182
|
+
evidencePlane,
|
|
183
|
+
provenanceStatus,
|
|
184
|
+
}),
|
|
100
185
|
executable: process.execPath,
|
|
101
186
|
prefixArgs: [entrypoint],
|
|
102
187
|
};
|