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.
Files changed (38) hide show
  1. package/README.md +7 -1
  2. package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
  3. package/generated/plugins/agdf/.codex-plugin/plugin.json +2 -3
  4. package/generated/plugins/agdf/hooks/session-start.sh +31 -0
  5. package/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
  6. package/generated/plugins/agdf/runtime/agdf-local.js +8 -1
  7. package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +30 -9
  8. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +116 -31
  9. package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +206 -0
  10. package/generated/plugins/agdf/runtime/create-agdf/package.json +1 -1
  11. package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
  12. package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +64 -19
  13. package/generated/plugins/agdf/submission/openai/availability.md +1 -1
  14. package/generated/plugins/agdf/submission/openai/capability-matrix.json +1 -1
  15. package/generated/plugins/agdf/submission/openai/release-notes.md +1 -1
  16. package/generated/plugins/agdf/submission/openai/reviewer-cases.json +1 -1
  17. package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
  18. package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +30 -9
  19. package/generated/submissions/openai/agdf/submission/openai/availability.md +1 -1
  20. package/generated/submissions/openai/agdf/submission/openai/capability-matrix.json +1 -1
  21. package/generated/submissions/openai/agdf/submission/openai/inventory.json +9 -9
  22. package/generated/submissions/openai/agdf/submission/openai/listing.json +1 -1
  23. package/generated/submissions/openai/agdf/submission/openai/readiness.json +2 -2
  24. package/generated/submissions/openai/agdf/submission/openai/readiness.md +2 -2
  25. package/generated/submissions/openai/agdf/submission/openai/release-notes.md +1 -1
  26. package/generated/submissions/openai/agdf/submission/openai/reviewer-cases.json +1 -1
  27. package/lib/cli/application.js +4 -3
  28. package/lib/fs-swap.js +26 -0
  29. package/lib/installers/local-development.js +202 -0
  30. package/lib/installers/local-marketplace.js +253 -46
  31. package/lib/installers/opencode.js +17 -2
  32. package/lib/installers/plugin-installers.js +88 -22
  33. package/lib/lifecycle/operations.js +9 -5
  34. package/lib/lifecycle/status.js +10 -2
  35. package/lib/public-plugin/builder.js +5 -4
  36. package/lib/runtime/local-validator.js +116 -31
  37. package/lib/runtime/plugin-provenance.js +206 -0
  38. package/package.json +6 -2
@@ -0,0 +1,206 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+
5
+ export const INSTALLATION_PROVENANCE_FILE = ".agdf-installation.json";
6
+ export const LEGACY_LOCAL_INSTALL_FILE = ".agdf-local-install.json";
7
+
8
+ const EXPECTED_PROFILES = Object.freeze({
9
+ "source-development": Object.freeze({ runtime: "absent", installable: false, machineValidation: "unavailable" }),
10
+ "runtime-plugin": Object.freeze({ runtime: "required", installable: true, machineValidation: "local_exact_version_digest" }),
11
+ "opencode-config-local": Object.freeze({ runtime: "config_local_package", installable: true, machineValidation: "local_exact_version" }),
12
+ "portable-skills": Object.freeze({ runtime: "absent", installable: true, machineValidation: "unavailable_or_external_required" }),
13
+ });
14
+
15
+ function readJson(path) {
16
+ try {
17
+ return JSON.parse(readFileSync(path, "utf8"));
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function digestDirectory(root) {
24
+ const files = [];
25
+ function visit(directory) {
26
+ for (const name of readdirSync(directory).sort()) {
27
+ const path = join(directory, name);
28
+ const stats = statSync(path);
29
+ if (stats.isDirectory()) visit(path);
30
+ else if (stats.isFile()) files.push(path);
31
+ }
32
+ }
33
+ visit(root);
34
+ const hash = createHash("sha256");
35
+ for (const path of files) {
36
+ hash.update(relative(root, path).replaceAll("\\", "/"));
37
+ hash.update("\0");
38
+ hash.update(readFileSync(path));
39
+ hash.update("\0");
40
+ }
41
+ return hash.digest("hex");
42
+ }
43
+
44
+ export function digestNormalizedPluginSource(root, canonicalVersion) {
45
+ const files = [];
46
+ function visit(directory) {
47
+ for (const name of readdirSync(directory).sort()) {
48
+ const path = join(directory, name);
49
+ const stats = statSync(path);
50
+ if (stats.isDirectory()) visit(path);
51
+ else if (stats.isFile()) files.push(path);
52
+ }
53
+ }
54
+ visit(root);
55
+ const hash = createHash("sha256");
56
+ for (const path of files) {
57
+ const normalizedPath = relative(root, path).replaceAll("\\", "/");
58
+ if ([INSTALLATION_PROVENANCE_FILE, LEGACY_LOCAL_INSTALL_FILE].includes(normalizedPath)) continue;
59
+ let content = readFileSync(path);
60
+ if (normalizedPath === ".codex-plugin/plugin.json") {
61
+ const manifest = readJson(path);
62
+ if (!manifest) throw new Error(`Invalid Codex plugin manifest: ${path}`);
63
+ content = `${JSON.stringify({ ...manifest, version: canonicalVersion }, null, 2)}\n`;
64
+ }
65
+ hash.update(normalizedPath);
66
+ hash.update("\0");
67
+ hash.update(content);
68
+ hash.update("\0");
69
+ }
70
+ return hash.digest("hex");
71
+ }
72
+
73
+ export function validateDistributionProfiles(definition) {
74
+ const contract = definition?.distributionProfiles;
75
+ if (contract?.schemaVersion !== 1) return { status: "invalid", reason: "profile_invalid" };
76
+ if (contract.marketplaceIdentities?.durable !== "agdf"
77
+ || contract.marketplaceIdentities?.generatedRepository !== "agdf-repo") {
78
+ return { status: "invalid", reason: "profile_invalid" };
79
+ }
80
+ for (const [profileId, expected] of Object.entries(EXPECTED_PROFILES)) {
81
+ const observed = contract.profiles?.[profileId];
82
+ if (!observed
83
+ || Object.keys(observed).length !== Object.keys(expected).length
84
+ || Object.keys(expected).some((key) => observed[key] !== expected[key])) {
85
+ return { status: "invalid", reason: "profile_invalid" };
86
+ }
87
+ }
88
+ if (Object.keys(contract.profiles ?? {}).length !== Object.keys(EXPECTED_PROFILES).length) {
89
+ return { status: "invalid", reason: "profile_invalid" };
90
+ }
91
+ return { status: "matched", contract };
92
+ }
93
+
94
+ export function inspectGeneratedRepositoryMarketplace(targetDir) {
95
+ const marketplacePath = join(targetDir, ".agents", "plugins", "marketplace.json");
96
+ if (!existsSync(marketplacePath)) return { status: "absent", marketplacePath };
97
+ const marketplace = readJson(marketplacePath);
98
+ const plugin = marketplace?.plugins?.length === 1 ? marketplace.plugins[0] : null;
99
+ const pluginPath = plugin?.source?.source === "local" ? plugin.source.path : null;
100
+ if (marketplace?.name !== "agdf-repo" || plugin?.name !== "agdf" || pluginPath !== "./plugins/agdf") {
101
+ return { status: "invalid", reason: "repository_marketplace_invalid", marketplacePath };
102
+ }
103
+ const pluginRoot = resolve(targetDir, pluginPath);
104
+ const rel = relative(resolve(targetDir), pluginRoot);
105
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
106
+ return { status: "invalid", reason: "repository_marketplace_path_escape", marketplacePath };
107
+ }
108
+ const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"));
109
+ const runtimeManifest = readJson(join(pluginRoot, "runtime", "runtime-manifest.json"));
110
+ const codexManifest = readJson(join(pluginRoot, ".codex-plugin", "plugin.json"));
111
+ const claudeManifest = readJson(join(pluginRoot, ".claude-plugin", "plugin.json"));
112
+ const runtimeRoot = join(pluginRoot, "runtime");
113
+ const runtimePackageRoot = join(pluginRoot, "runtime", "create-agdf");
114
+ const runtimeEntrypoint = resolve(runtimeRoot, runtimeManifest?.entrypoint ?? "");
115
+ const runtimeEntrypointRel = relative(runtimeRoot, runtimeEntrypoint);
116
+ if (validateDistributionProfiles(definition).status !== "matched"
117
+ || definition?.version !== runtimeManifest?.version
118
+ || codexManifest?.version !== definition?.version
119
+ || claudeManifest?.version !== definition?.version
120
+ || !/^[a-f0-9]{64}$/.test(runtimeManifest?.digest ?? "")
121
+ || !existsSync(join(runtimeRoot, "agdf-local.js"))
122
+ || !runtimeManifest?.entrypoint
123
+ || runtimeEntrypointRel === ".."
124
+ || runtimeEntrypointRel.startsWith(`..${sep}`)
125
+ || isAbsolute(runtimeEntrypointRel)
126
+ || !existsSync(runtimeEntrypoint)
127
+ || !existsSync(runtimePackageRoot)) {
128
+ return { status: "invalid", reason: "repository_runtime_incomplete", marketplacePath, pluginRoot };
129
+ }
130
+ let runtimeDigest;
131
+ try { runtimeDigest = digestDirectory(runtimePackageRoot); } catch {
132
+ return { status: "invalid", reason: "repository_runtime_incomplete", marketplacePath, pluginRoot };
133
+ }
134
+ if (runtimeDigest !== runtimeManifest.digest) {
135
+ return { status: "invalid", reason: "runtime_digest_mismatch", marketplacePath, pluginRoot };
136
+ }
137
+ return {
138
+ status: "matched",
139
+ marketplacePath,
140
+ pluginRoot,
141
+ selector: `${plugin.name}@${marketplace.name}`,
142
+ runtimeDigest,
143
+ };
144
+ }
145
+
146
+ export function inspectInstallationProvenance(pluginRoot, {
147
+ definition,
148
+ runtimeManifest,
149
+ pluginVersion,
150
+ allowLegacy = false,
151
+ } = {}) {
152
+ const markerPath = join(pluginRoot, INSTALLATION_PROVENANCE_FILE);
153
+ const legacyPath = join(pluginRoot, LEGACY_LOCAL_INSTALL_FILE);
154
+ if (!existsSync(markerPath)) {
155
+ if (allowLegacy && existsSync(legacyPath)) {
156
+ const marker = readJson(legacyPath);
157
+ if (!marker
158
+ || marker.schema_version !== 1
159
+ || marker.owner !== "create-agdf"
160
+ || marker.kind !== "codex_local_development_projection"
161
+ || marker.canonical_version !== definition.version
162
+ || marker.codex_install_version !== pluginVersion
163
+ || !/^[a-f0-9]{64}$/.test(marker.source_digest ?? "")) {
164
+ return { status: "invalid", reason: "installation_provenance_invalid", marker };
165
+ }
166
+ let observedSourceDigest;
167
+ try { observedSourceDigest = digestNormalizedPluginSource(pluginRoot, definition.version); } catch {
168
+ return { status: "invalid", reason: "source_digest_mismatch", marker };
169
+ }
170
+ return observedSourceDigest === marker.source_digest
171
+ ? { status: "legacy", marker, observedSourceDigest, reason: "legacy_installation_provenance" }
172
+ : { status: "invalid", reason: "source_digest_mismatch", marker, observedSourceDigest };
173
+ }
174
+ const profile = validateDistributionProfiles(definition);
175
+ if (profile.status !== "matched") return profile;
176
+ return { status: "missing", reason: "installation_provenance_missing" };
177
+ }
178
+ const profile = validateDistributionProfiles(definition);
179
+ if (profile.status !== "matched") return profile;
180
+ const marker = readJson(markerPath);
181
+ if (!marker
182
+ || marker.schema_version !== 1
183
+ || marker.owner !== "create-agdf"
184
+ || marker.profile_id !== "runtime-plugin"
185
+ || marker.marketplace_id !== profile.contract.marketplaceIdentities.durable
186
+ || marker.canonical_version !== definition.version
187
+ || marker.codex_install_version !== pluginVersion
188
+ || marker.runtime_digest !== runtimeManifest?.digest
189
+ || !/^[a-f0-9]{64}$/.test(marker.source_digest ?? "")) {
190
+ return { status: "invalid", reason: "installation_provenance_invalid", marker };
191
+ }
192
+ let observedSourceDigest;
193
+ try {
194
+ observedSourceDigest = digestNormalizedPluginSource(pluginRoot, definition.version);
195
+ } catch {
196
+ return { status: "invalid", reason: "source_digest_mismatch", marker };
197
+ }
198
+ if (observedSourceDigest !== marker.source_digest) {
199
+ return { status: "invalid", reason: "source_digest_mismatch", marker, observedSourceDigest };
200
+ }
201
+ return { status: "matched", marker, observedSourceDigest };
202
+ }
203
+
204
+ export const distributionProfileContract = Object.freeze({
205
+ expectedProfiles: EXPECTED_PROFILES,
206
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-agdf",
3
- "version": "0.13.5",
3
+ "version": "0.13.7",
4
4
  "description": "Bootstrap AGDF repository-local plugin files, Copilot/OpenCode instructions, skills, control files and machine-readable checks.",
5
5
  "type": "module",
6
6
  "files": [
@@ -22,6 +22,9 @@
22
22
  "scripts": {
23
23
  "sync-package-assets": "node ./scripts/sync-package-assets.js",
24
24
  "build:public-plugin": "node ./scripts/build-public-plugin.js",
25
+ "install:claude": "node ./scripts/install-local-plugin.js claude",
26
+ "install:codex": "node ./scripts/install-local-plugin.js codex",
27
+ "install:opencode": "node ./scripts/install-local-plugin.js opencode",
25
28
  "test:release-version-coherence": "node ./scripts/release-version-coherence-test.js",
26
29
  "test:public-plugin": "node ./scripts/public-plugin-test.js",
27
30
  "release:prepare": "npm run sync-package-assets && npm run test:release-version-coherence && npm run test:public-plugin",
@@ -32,6 +35,7 @@
32
35
  "test:cli-modularization": "node ./scripts/cli-modularization-test.js",
33
36
  "test:local-validator": "node ./scripts/local-validator-test.js",
34
37
  "test:local-marketplace": "node ./scripts/local-marketplace-test.js",
38
+ "test:local-development-install": "npm run release:prepare && node ./scripts/local-development-install-test.js",
35
39
  "test:package-contents": "node ./scripts/package-contents-test.js",
36
40
  "test:package-build": "node ./scripts/package-build-test.js",
37
41
  "test:lifecycle": "node ./scripts/lifecycle-test.js",
@@ -50,7 +54,7 @@
50
54
  "test:proportionality": "node ./scripts/proportionality-benchmark-test.js",
51
55
  "test:release-bootstrap": "node ./scripts/release-bootstrap-smoke-test.js",
52
56
  "test:routing": "npm run sync-package-assets && node ./scripts/test-routing.js",
53
- "smoke-test": "npm run release:prepare && npm run test:cli-modularization && npm run test:local-validator && npm run test:local-marketplace && npm run test:package-build && npm run test:package-contents && npm run test:lifecycle && npm run test:control-state && npm run test:parent-reconciliation && npm run test:interaction-presentation && npm run test:verified-change && npm run test:runtime-integrity-layout && npm run test:runtime-integrity-negative && npm run test:agent-skills-conformance && npm run test:skill-evals && npm run eval:skills && npm run test:proportionality && npm run test:delivery-path-search && npm run test:delivery-path-search-unit && npm run test:delivery-path-search-generator && npm run test:opencode-hardening && node ./scripts/smoke-test.js && node ./scripts/test-routing.js",
57
+ "smoke-test": "npm run release:prepare && npm run test:cli-modularization && npm run test:local-validator && npm run test:local-marketplace && npm run test:local-development-install && npm run test:package-build && npm run test:package-contents && npm run test:lifecycle && npm run test:control-state && npm run test:parent-reconciliation && npm run test:interaction-presentation && npm run test:verified-change && npm run test:runtime-integrity-layout && npm run test:runtime-integrity-negative && npm run test:agent-skills-conformance && npm run test:skill-evals && npm run eval:skills && npm run test:proportionality && npm run test:delivery-path-search && npm run test:delivery-path-search-unit && npm run test:delivery-path-search-generator && npm run test:opencode-hardening && node ./scripts/smoke-test.js && node ./scripts/test-routing.js",
54
58
  "prepack": "npm run release:prepare"
55
59
  },
56
60
  "keywords": [