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
@@ -35,7 +35,7 @@ import { CliUsageError, parseArgs } from "./parse-args.js";
35
35
  import { pluginDefinition } from "./runtime-context.js";
36
36
  import { createValidationHandlers } from "./validation-handlers.js";
37
37
 
38
- function createHandlers({ io, env, exec, prepare }) {
38
+ function createHandlers({ io, env, exec, prepare, openCodePackageSource }) {
39
39
  const installerAdapters = {
40
40
  ...(exec ? { exec } : {}),
41
41
  ...(prepare ? { prepare } : {}),
@@ -112,7 +112,7 @@ function createHandlers({ io, env, exec, prepare }) {
112
112
  ["opencode", (options) => {
113
113
  const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
114
114
  try {
115
- const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir));
115
+ const result = runLifecyclePhase("plugin_operation", () => installOpenCodeGlobalPlugin(configDir, { packageSource: openCodePackageSource }));
116
116
  runLifecyclePhase("global_surface", () => installOpenCodeGlobalSurface(configDir));
117
117
  const report = runLifecyclePhase("verification", () => evaluateOpenCodeStatus(options.dir, configDir, result.transition));
118
118
  const alignmentHealthy = ["already_matching", "aligned"].includes(result.sdk_alignment.status);
@@ -150,6 +150,7 @@ function createHandlers({ io, env, exec, prepare }) {
150
150
  `experimental_hooks=${report.experimental_hooks.aggregate}`,
151
151
  `host_sdk_version=${report.host_sdk_version.status};policy=${report.host_sdk_version.policy}`,
152
152
  `sdk_alignment=${result.sdk_alignment.status};target=${result.sdk_alignment.target_version || "unknown"};installed=${result.sdk_alignment.installed_version || "unknown"}`,
153
+ `package_source=${result.package_source.kind}${result.package_source.digest ? `;digest=${result.package_source.digest}` : ""}`,
153
154
  ],
154
155
  },
155
156
  restart: { required: true, reason: "host_reload" },
@@ -341,7 +342,7 @@ export async function runCli(argv = process.argv.slice(2), adapters = {}) {
341
342
  }
342
343
  const command = resolveCommand(options.target);
343
344
  if (!command) throw new Error(`No handler is registered for ${options.target}.`);
344
- const handler = createHandlers({ io, env, exec: adapters.exec, prepare: adapters.prepare }).get(command.handler);
345
+ const handler = createHandlers({ io, env, exec: adapters.exec, prepare: adapters.prepare, openCodePackageSource: adapters.openCodePackageSource }).get(command.handler);
345
346
  if (!handler) throw new Error(`No implementation is registered for ${command.name}.`);
346
347
  return await handler(options);
347
348
  }
package/lib/fs-swap.js ADDED
@@ -0,0 +1,26 @@
1
+ import { renameSync } from "node:fs";
2
+ import process from "node:process";
3
+
4
+ const RETRY_ATTEMPTS = 5;
5
+ const RETRY_BASE_DELAY_MS = 50;
6
+
7
+ function sleepSync(milliseconds) {
8
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
9
+ }
10
+
11
+ export function renameSyncWithRetry(source, destination, adapters = {}) {
12
+ const rename = adapters.rename ?? renameSync;
13
+ const sleep = adapters.sleep ?? sleepSync;
14
+ const platform = adapters.platform ?? process.platform;
15
+ for (let attempt = 1; ; attempt += 1) {
16
+ try {
17
+ rename(source, destination);
18
+ return;
19
+ } catch (error) {
20
+ // Windows antivirus/indexer handle locks surface as transient EPERM on
21
+ // renames of freshly written directory trees; retry briefly before failing.
22
+ if (platform !== "win32" || error?.code !== "EPERM" || attempt >= RETRY_ATTEMPTS) throw error;
23
+ sleep(RETRY_BASE_DELAY_MS * attempt);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,202 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFileSync } from "node:child_process";
3
+ import {
4
+ existsSync,
5
+ lstatSync,
6
+ mkdirSync,
7
+ mkdtempSync,
8
+ readFileSync,
9
+ renameSync,
10
+ rmSync,
11
+ statSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { join, resolve, win32 as pathWin32 } from "node:path";
15
+ import process from "node:process";
16
+
17
+ const LOCAL_PACKAGE_OWNER = "create-agdf";
18
+ const LOCAL_PACKAGE_KIND = "opencode_local_development_package";
19
+ const OWNERSHIP_FILE = ".agdf-owned.json";
20
+
21
+ function sha256File(path) {
22
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
23
+ }
24
+
25
+ function pathInside(root, candidate) {
26
+ const relativePath = candidate.slice(root.length);
27
+ return candidate === root || (candidate.startsWith(root) && ["/", "\\"].includes(relativePath[0]));
28
+ }
29
+
30
+ function readJson(path, label) {
31
+ try {
32
+ return JSON.parse(readFileSync(path, "utf8"));
33
+ } catch (error) {
34
+ throw new Error(`${label} is invalid: ${error.message}`);
35
+ }
36
+ }
37
+
38
+ function parsePackResult(output) {
39
+ const start = String(output).indexOf("[");
40
+ if (start < 0) throw new Error("AGDF local package build did not return npm pack JSON.");
41
+ const parsed = JSON.parse(String(output).slice(start));
42
+ const result = parsed?.[0];
43
+ const filename = result?.filename;
44
+ if (typeof filename !== "string" || !filename || filename.includes("/") || filename.includes("\\")) {
45
+ throw new Error("AGDF local package build returned an unsafe tarball name.");
46
+ }
47
+ if (!Array.isArray(result.files) || result.files.length === 0) {
48
+ throw new Error("AGDF local package build did not report its packed file inventory.");
49
+ }
50
+ return { filename, files: result.files };
51
+ }
52
+
53
+ export function localNpmExecutable(platform = process.platform, execPath = process.execPath) {
54
+ if (platform !== "win32") return { executable: "npm", prefix: [] };
55
+ return {
56
+ executable: execPath,
57
+ prefix: [pathWin32.join(pathWin32.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js")],
58
+ };
59
+ }
60
+
61
+ function digestPackedFiles(packageRoot, files) {
62
+ const root = resolve(packageRoot);
63
+ const paths = files.map((entry) => {
64
+ if (!entry
65
+ || typeof entry.path !== "string"
66
+ || !entry.path
67
+ || entry.path.includes("\\")
68
+ || !Number.isInteger(entry.mode)) {
69
+ throw new Error("AGDF local package build returned an invalid packed file path.");
70
+ }
71
+ const path = resolve(root, entry.path);
72
+ if (!pathInside(root, path) || path === root) throw new Error(`AGDF local package build returned an unsafe packed file path: ${entry.path}`);
73
+ const stats = lstatSync(path);
74
+ if (!stats.isFile() || stats.isSymbolicLink()) throw new Error(`AGDF local package build returned a non-file package entry: ${entry.path}`);
75
+ return { path, relative: entry.path.replaceAll("\\", "/"), mode: entry.mode };
76
+ }).sort((left, right) => left.relative.localeCompare(right.relative));
77
+ const hash = createHash("sha256");
78
+ for (const entry of paths) {
79
+ hash.update(entry.relative);
80
+ hash.update("\0");
81
+ hash.update(String(entry.mode));
82
+ hash.update("\0");
83
+ hash.update(readFileSync(entry.path));
84
+ hash.update("\0");
85
+ }
86
+ return hash.digest("hex");
87
+ }
88
+
89
+ function validateOwnedPackageDirectory(root, expected = {}) {
90
+ const rootStats = lstatSync(root);
91
+ if (!rootStats.isDirectory() || rootStats.isSymbolicLink()) throw new Error(`Refusing unsafe AGDF local package directory: ${root}`);
92
+ const marker = readJson(join(root, OWNERSHIP_FILE), "AGDF local package ownership marker");
93
+ if (marker.schema_version !== 1
94
+ || marker.owner !== LOCAL_PACKAGE_OWNER
95
+ || marker.kind !== LOCAL_PACKAGE_KIND
96
+ || !/^[a-f0-9]{64}$/.test(marker.digest ?? "")
97
+ || !/^[a-f0-9]{64}$/.test(marker.archive_digest ?? "")
98
+ || typeof marker.filename !== "string"
99
+ || !marker.filename) {
100
+ throw new Error(`Refusing unowned AGDF local package directory: ${root}`);
101
+ }
102
+ if (expected.version && marker.version !== expected.version) throw new Error(`AGDF local package version mismatch: ${root}`);
103
+ if (expected.digest && marker.digest !== expected.digest) throw new Error(`AGDF local package digest mismatch: ${root}`);
104
+ const tarball = join(root, marker.filename);
105
+ if (!existsSync(tarball)
106
+ || !lstatSync(tarball).isFile()
107
+ || lstatSync(tarball).isSymbolicLink()
108
+ || sha256File(tarball) !== marker.archive_digest) {
109
+ throw new Error(`AGDF local package tarball is missing or tampered: ${root}`);
110
+ }
111
+ return { marker, tarball };
112
+ }
113
+
114
+ export function validateLocalOpenCodePackageSource(source) {
115
+ if (!source
116
+ || source.kind !== "local_checkout"
117
+ || typeof source.root !== "string"
118
+ || typeof source.dataRoot !== "string"
119
+ || !/^[a-f0-9]{64}$/.test(source.digest ?? "")) {
120
+ throw new Error("Invalid AGDF OpenCode local package source.");
121
+ }
122
+ const root = resolve(source.root);
123
+ const dataRoot = resolve(source.dataRoot);
124
+ const expectedRoot = join(dataRoot, "packages", "local", source.digest);
125
+ if (root !== expectedRoot) throw new Error("AGDF OpenCode local package source is outside its owned data root.");
126
+ const { marker, tarball } = validateOwnedPackageDirectory(root, {
127
+ version: source.version,
128
+ digest: source.digest,
129
+ });
130
+ const specifier = tarball;
131
+ if (source.specifier && source.specifier !== specifier) throw new Error("AGDF OpenCode local package specifier does not match its owned tarball.");
132
+ return Object.freeze({
133
+ kind: "local_checkout",
134
+ dataRoot,
135
+ root,
136
+ markerPath: join(root, OWNERSHIP_FILE),
137
+ tarball,
138
+ specifier,
139
+ version: marker.version,
140
+ digest: marker.digest,
141
+ archiveDigest: marker.archive_digest,
142
+ });
143
+ }
144
+
145
+ export function prepareLocalOpenCodePackage({
146
+ dataRoot,
147
+ packageRoot,
148
+ expectedVersion,
149
+ exec = execFileSync,
150
+ } = {}) {
151
+ if (!dataRoot || !packageRoot || !expectedVersion) throw new Error("AGDF local package preparation requires dataRoot, packageRoot and expectedVersion.");
152
+ const localRoot = resolve(dataRoot, "packages", "local");
153
+ mkdirSync(localRoot, { recursive: true });
154
+ const stageRoot = mkdtempSync(join(localRoot, ".stage-"));
155
+ try {
156
+ const npm = localNpmExecutable();
157
+ const output = exec(npm.executable, [
158
+ ...npm.prefix,
159
+ "pack",
160
+ "--ignore-scripts",
161
+ "--json",
162
+ "--pack-destination",
163
+ stageRoot,
164
+ ], {
165
+ cwd: resolve(packageRoot),
166
+ encoding: "utf8",
167
+ stdio: "pipe",
168
+ });
169
+ const { filename, files } = parsePackResult(output);
170
+ const stagedTarball = join(stageRoot, filename);
171
+ if (!existsSync(stagedTarball) || !statSync(stagedTarball).isFile()) throw new Error("AGDF local package tarball was not created.");
172
+ const digest = digestPackedFiles(packageRoot, files);
173
+ const archiveDigest = sha256File(stagedTarball);
174
+ const stableRoot = join(localRoot, digest);
175
+ if (existsSync(stableRoot)) {
176
+ const existing = validateLocalOpenCodePackageSource({ kind: "local_checkout", dataRoot, root: stableRoot, version: expectedVersion, digest });
177
+ if (existing.archiveDigest !== archiveDigest) throw new Error(`AGDF local package archive is not deterministic for packed content: ${stableRoot}`);
178
+ rmSync(stageRoot, { recursive: true, force: true });
179
+ return existing;
180
+ }
181
+ writeFileSync(join(stageRoot, OWNERSHIP_FILE), `${JSON.stringify({
182
+ schema_version: 1,
183
+ owner: LOCAL_PACKAGE_OWNER,
184
+ kind: LOCAL_PACKAGE_KIND,
185
+ version: expectedVersion,
186
+ digest,
187
+ archive_digest: archiveDigest,
188
+ filename,
189
+ }, null, 2)}\n`, "utf8");
190
+ renameSync(stageRoot, stableRoot);
191
+ return validateLocalOpenCodePackageSource({ kind: "local_checkout", dataRoot, root: stableRoot, version: expectedVersion, digest });
192
+ } catch (error) {
193
+ if (existsSync(stageRoot)) rmSync(stageRoot, { recursive: true, force: true });
194
+ throw error;
195
+ }
196
+ }
197
+
198
+ export const localDevelopmentConstants = Object.freeze({
199
+ owner: LOCAL_PACKAGE_OWNER,
200
+ kind: LOCAL_PACKAGE_KIND,
201
+ ownershipFile: OWNERSHIP_FILE,
202
+ });