create-agdf 0.14.3 → 0.14.4
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 +34 -2
- package/generated/.opencode/contracts/interaction.md +2 -2
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/contracts/interaction.md +2 -2
- package/generated/plugins/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/plugins/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- 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 +2 -0
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +6 -4
- 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/plugins/copilot/agdf/.agdf-payload-inventory.json +55 -35
- package/generated/plugins/copilot/agdf/copilot-skills/agdf-gate-check/SKILL.md +6 -4
- package/generated/plugins/copilot/agdf/copilot-skills/contracts/interaction.md +2 -2
- package/generated/plugins/copilot/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/plugins/copilot/agdf/plugin.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/command-registry.js +8 -1
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/cli/parse-args.js +3 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +13 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +64 -13
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +6 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-identity.js +9 -0
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +4 -2
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/interaction-presentation.js +57 -7
- package/generated/plugins/copilot/agdf/runtime/create-agdf/lib/runtime/plugin-provenance.js +46 -3
- package/generated/plugins/copilot/agdf/runtime/create-agdf/package.json +1 -1
- package/generated/plugins/copilot/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/submissions/openai/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/submissions/openai/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/submissions/openai/agdf/meta/contracts/interaction.md +2 -2
- package/generated/submissions/openai/agdf/meta/copilot-payload-baseline.json +3 -3
- package/generated/submissions/openai/agdf/meta/distribution-profile-history.json +125 -0
- package/generated/submissions/openai/agdf/skills/gate-check/SKILL.md +6 -4
- 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 +16 -12
- 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 +24 -6
- package/lib/cli/command-registry.js +8 -1
- package/lib/cli/parse-args.js +3 -0
- package/lib/control-evaluation/doctor.js +13 -0
- package/lib/control-evaluation/gate-check.js +64 -13
- package/lib/control-evaluation/run-state.js +6 -0
- package/lib/control-state/run-identity.js +9 -0
- package/lib/control-state/run-state-parser.js +4 -2
- package/lib/installers/claude-cache-recovery.js +76 -0
- package/lib/installers/copilot-settings.js +146 -16
- package/lib/installers/local-marketplace.js +169 -14
- package/lib/installers/plugin-installers.js +96 -9
- package/lib/interaction-presentation.js +57 -7
- package/lib/lifecycle/operations.js +28 -4
- package/lib/lifecycle/result.js +4 -4
- package/lib/release/profile-history.js +222 -0
- package/lib/release/version-bump.js +527 -0
- package/lib/release/version-coherence.js +92 -38
- package/lib/runtime/distribution-profile-history.js +173 -0
- package/lib/runtime/plugin-provenance.js +46 -3
- package/package.json +7 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { existsSync, lstatSync, realpathSync, rmSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { win32 } from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
|
|
6
|
+
function result(status, reason, removedPath = null, evidence = []) {
|
|
7
|
+
return Object.freeze({ status, reason, removed_path: removedPath, evidence });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function normalized(path) {
|
|
11
|
+
return win32.resolve(path).toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseRenameOperands(text) {
|
|
15
|
+
const matches = [...text.matchAll(/\brename\s+['"]([^'"]+)['"]\s*->\s*['"]([^'"]+)['"]/gi)];
|
|
16
|
+
return matches.length === 1 ? matches[0].slice(1, 3) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function recoverClaudeCacheTemp({
|
|
20
|
+
error,
|
|
21
|
+
expectedVersion,
|
|
22
|
+
platform = process.platform,
|
|
23
|
+
env = process.env,
|
|
24
|
+
home = homedir(),
|
|
25
|
+
fs = { existsSync, lstatSync, realpathSync, rmSync },
|
|
26
|
+
} = {}) {
|
|
27
|
+
const text = String(error?.message ?? error ?? "");
|
|
28
|
+
if (platform !== "win32" || !/\bEPERM\b/i.test(text) || !/\brename\b/i.test(text)) {
|
|
29
|
+
return result("not_applicable", "claude_cache_temp_recovery_not_applicable");
|
|
30
|
+
}
|
|
31
|
+
const operands = parseRenameOperands(text);
|
|
32
|
+
if (!operands || typeof expectedVersion !== "string" || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(expectedVersion)) {
|
|
33
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
34
|
+
}
|
|
35
|
+
const configRoot = win32.resolve(env.CLAUDE_CONFIG_DIR || win32.join(home, ".claude"));
|
|
36
|
+
const namespace = win32.join(configRoot, "plugins", "cache", "agdf", "agdf");
|
|
37
|
+
const absoluteOperands = operands.filter((path) => win32.isAbsolute(path));
|
|
38
|
+
if (absoluteOperands.length !== 2
|
|
39
|
+
|| absoluteOperands.some((path) => normalized(win32.dirname(path)) !== normalized(namespace))) {
|
|
40
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
41
|
+
}
|
|
42
|
+
const [candidate, destination] = absoluteOperands;
|
|
43
|
+
if (!/^temp_local_[0-9A-Za-z._-]+$/.test(win32.basename(candidate))
|
|
44
|
+
|| !fs.existsSync(candidate)) {
|
|
45
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
46
|
+
}
|
|
47
|
+
if (win32.basename(destination) !== expectedVersion || fs.existsSync(destination)) {
|
|
48
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
49
|
+
}
|
|
50
|
+
let candidateStats;
|
|
51
|
+
let realNamespace;
|
|
52
|
+
let realCandidate;
|
|
53
|
+
try {
|
|
54
|
+
candidateStats = fs.lstatSync(candidate);
|
|
55
|
+
realNamespace = fs.realpathSync(namespace);
|
|
56
|
+
realCandidate = fs.realpathSync(candidate);
|
|
57
|
+
} catch {
|
|
58
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
59
|
+
}
|
|
60
|
+
if (!candidateStats.isDirectory() || candidateStats.isSymbolicLink()
|
|
61
|
+
|| normalized(win32.dirname(realCandidate)) !== normalized(realNamespace)
|
|
62
|
+
|| normalized(realCandidate) !== normalized(candidate)) {
|
|
63
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
fs.rmSync(candidate, { recursive: true, force: false });
|
|
67
|
+
} catch {
|
|
68
|
+
return result("unsafe", "claude_cache_temp_recovery_unsafe");
|
|
69
|
+
}
|
|
70
|
+
return result(
|
|
71
|
+
"recovered",
|
|
72
|
+
"claude_cache_temp_recovery_bounded_retry",
|
|
73
|
+
candidate,
|
|
74
|
+
[`cache_entry:${win32.basename(candidate)}`, `target_version:${expectedVersion}`],
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -1,49 +1,179 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
6
7
|
|
|
8
|
+
const COPILOT_PLUGIN_SELECTOR = "agdf@agdf";
|
|
9
|
+
const COPILOT_LOCAL_SETTINGS = join(".github", "copilot", "settings.local.json");
|
|
10
|
+
const COPILOT_SHARED_SETTINGS = join(".github", "copilot", "settings.json");
|
|
11
|
+
|
|
7
12
|
export function defaultCopilotSettingsPath({ env = process.env, home = homedir() } = {}) {
|
|
8
13
|
return join(resolve(env.COPILOT_HOME || join(home, ".copilot")), "settings.json");
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
export function readCopilotSettings(path = defaultCopilotSettingsPath()) {
|
|
12
|
-
if (!existsSync(path)) return { exists: false, settings: {} };
|
|
17
|
+
if (!existsSync(path)) return { exists: false, settings: {}, content: null };
|
|
13
18
|
const stats = lstatSync(path);
|
|
14
19
|
if (!stats.isFile() || stats.isSymbolicLink()) throw new Error("AGDF_COPILOT_SETTINGS_UNOWNED_PATH");
|
|
15
20
|
try {
|
|
16
|
-
const
|
|
21
|
+
const content = readFileSync(path, "utf8");
|
|
22
|
+
const settings = JSON.parse(content);
|
|
17
23
|
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not an object");
|
|
18
24
|
if (settings.enabledPlugins !== undefined
|
|
19
25
|
&& (!settings.enabledPlugins || typeof settings.enabledPlugins !== "object" || Array.isArray(settings.enabledPlugins))) {
|
|
20
26
|
throw new Error("enabledPlugins must be an object");
|
|
21
27
|
}
|
|
22
|
-
return { exists: true, settings };
|
|
28
|
+
return { exists: true, settings, content };
|
|
23
29
|
} catch (error) {
|
|
24
30
|
throw new Error(`AGDF_COPILOT_SETTINGS_INVALID: ${error.message}`);
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
function
|
|
29
|
-
|
|
34
|
+
function atomicContentWrite(path, content, dependencies = {}) {
|
|
35
|
+
const makeDirectory = dependencies.mkdir ?? mkdirSync;
|
|
36
|
+
const write = dependencies.write ?? writeFileSync;
|
|
37
|
+
const rename = dependencies.rename ?? renameSyncWithRetry;
|
|
38
|
+
const remove = dependencies.remove ?? rmSync;
|
|
39
|
+
const changeMode = dependencies.chmod ?? chmodSync;
|
|
40
|
+
const exists = dependencies.exists ?? existsSync;
|
|
41
|
+
const stat = dependencies.lstat ?? lstatSync;
|
|
42
|
+
makeDirectory(dirname(path), { recursive: true });
|
|
30
43
|
const temp = `${path}.agdf-tmp-${process.pid}`;
|
|
31
44
|
const backup = `${path}.agdf-backup-${process.pid}`;
|
|
32
|
-
const hadPrevious =
|
|
33
|
-
const mode = hadPrevious ?
|
|
34
|
-
|
|
35
|
-
if (process.platform !== "win32")
|
|
45
|
+
const hadPrevious = exists(path);
|
|
46
|
+
const mode = hadPrevious ? stat(path).mode & 0o777 : 0o600;
|
|
47
|
+
write(temp, content, { encoding: "utf8", flag: "wx", mode });
|
|
48
|
+
if (process.platform !== "win32") changeMode(temp, mode);
|
|
36
49
|
try {
|
|
37
|
-
if (hadPrevious && process.platform === "win32")
|
|
38
|
-
|
|
39
|
-
if (hadPrevious && process.platform === "win32")
|
|
50
|
+
if (hadPrevious && process.platform === "win32") rename(path, backup);
|
|
51
|
+
rename(temp, path);
|
|
52
|
+
if (hadPrevious && process.platform === "win32") remove(backup, { force: true });
|
|
40
53
|
} catch (error) {
|
|
41
|
-
try {
|
|
42
|
-
if (hadPrevious && process.platform === "win32" &&
|
|
54
|
+
try { remove(temp, { force: true }); } catch {}
|
|
55
|
+
if (hadPrevious && process.platform === "win32" && exists(backup) && !exists(path)) rename(backup, path);
|
|
43
56
|
throw error;
|
|
44
57
|
}
|
|
45
58
|
}
|
|
46
59
|
|
|
60
|
+
export function atomicSettingsWrite(path, settings, dependencies = {}) {
|
|
61
|
+
atomicContentWrite(path, `${JSON.stringify(settings, null, 2)}\n`, dependencies);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function repositoryCopilotSettingsPath(targetDir, { shared = false } = {}) {
|
|
65
|
+
const root = resolve(targetDir);
|
|
66
|
+
if (!existsSync(root) || lstatSync(root).isSymbolicLink() || !lstatSync(root).isDirectory()) {
|
|
67
|
+
throw new Error(`AGDF_COPILOT_REPOSITORY_UNOWNED_PATH: ${root}`);
|
|
68
|
+
}
|
|
69
|
+
const path = resolve(root, shared ? COPILOT_SHARED_SETTINGS : COPILOT_LOCAL_SETTINGS);
|
|
70
|
+
const fromRoot = relative(root, path);
|
|
71
|
+
if (!fromRoot || fromRoot.startsWith("..") || isAbsolute(fromRoot)) {
|
|
72
|
+
throw new Error("AGDF_COPILOT_REPOSITORY_SETTINGS_OUTSIDE_ROOT");
|
|
73
|
+
}
|
|
74
|
+
for (const candidate of [root, join(root, ".github"), join(root, ".github", "copilot")]) {
|
|
75
|
+
if (existsSync(candidate) && lstatSync(candidate).isSymbolicLink()) {
|
|
76
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${candidate}`);
|
|
77
|
+
}
|
|
78
|
+
if (existsSync(candidate) && !lstatSync(candidate).isDirectory()) {
|
|
79
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${candidate}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(path) && (lstatSync(path).isSymbolicLink() || !lstatSync(path).isFile())) {
|
|
83
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_UNOWNED_PATH: ${path}`);
|
|
84
|
+
}
|
|
85
|
+
return path;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function assertCopilotLocalSettingsIgnored(targetDir, { exec = execFileSync } = {}) {
|
|
89
|
+
const root = resolve(targetDir);
|
|
90
|
+
try {
|
|
91
|
+
exec("git", ["check-ignore", "--quiet", "--", COPILOT_LOCAL_SETTINGS], {
|
|
92
|
+
cwd: root,
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error?.code === "ENOENT") {
|
|
98
|
+
throw new Error("AGDF_COPILOT_GIT_UNAVAILABLE: install Git and retry personal disable, or use --shared deliberately.");
|
|
99
|
+
}
|
|
100
|
+
if (error?.status === 1) {
|
|
101
|
+
throw new Error(`AGDF_COPILOT_LOCAL_SETTINGS_NOT_IGNORED: add ${COPILOT_LOCAL_SETTINGS} to an effective Git ignore source and retry, or use --shared deliberately.`);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`AGDF_COPILOT_GIT_IGNORE_UNVERIFIED: ${error?.message || "Git could not verify the repository ignore state."}`);
|
|
104
|
+
}
|
|
105
|
+
return { status: "ignored", path: COPILOT_LOCAL_SETTINGS };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function planCopilotRepositoryDisable({ targetDir, shared = false, exec = execFileSync }) {
|
|
109
|
+
const root = resolve(targetDir);
|
|
110
|
+
const path = repositoryCopilotSettingsPath(root, { shared });
|
|
111
|
+
if (!shared) assertCopilotLocalSettingsIgnored(root, { exec });
|
|
112
|
+
const before = readCopilotSettings(path);
|
|
113
|
+
const current = before.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR];
|
|
114
|
+
if (current !== undefined && typeof current !== "boolean") {
|
|
115
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_AMBIGUOUS_PLUGIN_STATE: ${path}`);
|
|
116
|
+
}
|
|
117
|
+
const next = structuredClone(before.settings);
|
|
118
|
+
next.enabledPlugins = { ...(next.enabledPlugins ?? {}), [COPILOT_PLUGIN_SELECTOR]: false };
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
path,
|
|
121
|
+
root,
|
|
122
|
+
shared,
|
|
123
|
+
audience: shared ? "shared" : "personal",
|
|
124
|
+
selector: COPILOT_PLUGIN_SELECTOR,
|
|
125
|
+
before: Object.freeze({ exists: before.exists, settings: structuredClone(before.settings), content: before.content }),
|
|
126
|
+
settings: Object.freeze(next),
|
|
127
|
+
changed: current !== false,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function applyCopilotRepositoryDisable(mutation, dependencies = {}) {
|
|
132
|
+
if (!mutation?.path || mutation.selector !== COPILOT_PLUGIN_SELECTOR || mutation.settings?.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] !== false) {
|
|
133
|
+
throw new Error("AGDF_COPILOT_SETTINGS_MUTATION_INVALID");
|
|
134
|
+
}
|
|
135
|
+
const read = dependencies.read ?? readCopilotSettings;
|
|
136
|
+
const write = dependencies.writeSettings ?? ((path, settings) => atomicSettingsWrite(path, settings, dependencies));
|
|
137
|
+
const writeContent = dependencies.writeContent ?? ((path, content) => atomicContentWrite(path, content, dependencies));
|
|
138
|
+
const remove = dependencies.remove ?? rmSync;
|
|
139
|
+
const resolvedPath = repositoryCopilotSettingsPath(mutation.root, { shared: mutation.shared });
|
|
140
|
+
if (resolvedPath !== mutation.path) throw new Error("AGDF_COPILOT_SETTINGS_MUTATION_PATH_MISMATCH");
|
|
141
|
+
const current = read(mutation.path);
|
|
142
|
+
if (current.exists !== mutation.before.exists || (current.exists && current.content !== mutation.before.content)) {
|
|
143
|
+
throw new Error(`AGDF_COPILOT_SETTINGS_CHANGED_DURING_APPLY: ${mutation.path}`);
|
|
144
|
+
}
|
|
145
|
+
if (!mutation.changed) return { status: "unchanged", path: mutation.path };
|
|
146
|
+
try {
|
|
147
|
+
write(mutation.path, mutation.settings);
|
|
148
|
+
const verified = read(mutation.path);
|
|
149
|
+
if (verified.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] !== false) {
|
|
150
|
+
throw new Error("AGDF_COPILOT_SETTINGS_VERIFICATION_FAILED");
|
|
151
|
+
}
|
|
152
|
+
return { status: "written", path: mutation.path };
|
|
153
|
+
} catch (error) {
|
|
154
|
+
try {
|
|
155
|
+
if (mutation.before.exists) writeContent(mutation.path, mutation.before.content);
|
|
156
|
+
else remove(mutation.path, { force: true });
|
|
157
|
+
} catch (rollbackError) {
|
|
158
|
+
error.rollbackError = rollbackError;
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function verifyCopilotRepositoryDisabled(targetDir, { shared = false } = {}) {
|
|
165
|
+
try {
|
|
166
|
+
const path = repositoryCopilotSettingsPath(targetDir, { shared });
|
|
167
|
+
if (!existsSync(path)) return { status: "failed", evidence: [`missing:${path}`] };
|
|
168
|
+
const state = readCopilotSettings(path);
|
|
169
|
+
return state.settings.enabledPlugins?.[COPILOT_PLUGIN_SELECTOR] === false
|
|
170
|
+
? { status: "healthy", evidence: [`${path}:${COPILOT_PLUGIN_SELECTOR}:enabled=false`, `audience:${shared ? "shared" : "personal"}`] }
|
|
171
|
+
: { status: "failed", evidence: [`postcondition_failed:${path}:${COPILOT_PLUGIN_SELECTOR}`] };
|
|
172
|
+
} catch (error) {
|
|
173
|
+
return { status: "failed", evidence: [`verification_failed:${resolve(targetDir)}:${error.message}`] };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
47
177
|
export function configureCopilotDeclarativePlugin({ path = defaultCopilotSettingsPath(), pluginRoot }) {
|
|
48
178
|
if (typeof pluginRoot !== "string" || !pluginRoot.trim() || !isAbsolute(pluginRoot)) {
|
|
49
179
|
throw new Error("AGDF_COPILOT_PLUGIN_SPEC_INVALID");
|
|
@@ -64,7 +194,7 @@ export function configureCopilotDeclarativePlugin({ path = defaultCopilotSetting
|
|
|
64
194
|
path,
|
|
65
195
|
spec,
|
|
66
196
|
rollback() {
|
|
67
|
-
if (before.exists)
|
|
197
|
+
if (before.exists) atomicContentWrite(path, before.content);
|
|
68
198
|
else rmSync(path, { force: true });
|
|
69
199
|
},
|
|
70
200
|
};
|
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
cpSync,
|
|
3
3
|
existsSync,
|
|
4
4
|
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
5
6
|
readFileSync,
|
|
6
7
|
rmSync,
|
|
7
8
|
writeFileSync,
|
|
8
9
|
} from "node:fs";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
11
|
import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
11
12
|
import process from "node:process";
|
|
12
13
|
import { generatedRoot, packageRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
13
14
|
import { renameSyncWithRetry } from "../fs-swap.js";
|
|
15
|
+
import { classifyHistoricalDistributionProfile } from "../runtime/distribution-profile-history.js";
|
|
14
16
|
import {
|
|
15
17
|
INSTALLATION_PROVENANCE_FILE,
|
|
16
18
|
LEGACY_LOCAL_INSTALL_FILE,
|
|
@@ -31,6 +33,14 @@ function pathInside(root, candidate) {
|
|
|
31
33
|
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
function readOptionalJson(path) {
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
export function digestPluginSource(root, canonicalVersion = pluginDefinition.version) {
|
|
35
45
|
return digestNormalizedPluginSource(root, canonicalVersion);
|
|
36
46
|
}
|
|
@@ -58,6 +68,59 @@ export function isCodexLocalInstallVersion(version, candidate, sourceDigest = ""
|
|
|
58
68
|
return new RegExp(`^${escaped}\\+codex\\.local-[a-f0-9]{12}$`).test(candidate);
|
|
59
69
|
}
|
|
60
70
|
|
|
71
|
+
function localInstallSourceUnstable(detail) {
|
|
72
|
+
const error = new Error(`AGDF local plugin source changed during snapshot capture: ${detail}`);
|
|
73
|
+
error.code = "local_install_source_unstable";
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function captureLocalPluginSnapshot({
|
|
78
|
+
builtPluginRoot,
|
|
79
|
+
expectedVersion = pluginDefinition.version,
|
|
80
|
+
profileId = "runtime-plugin",
|
|
81
|
+
adapters = {},
|
|
82
|
+
} = {}) {
|
|
83
|
+
if (!builtPluginRoot) throw new Error("AGDF local plugin source root is required.");
|
|
84
|
+
const sourceRoot = resolve(builtPluginRoot);
|
|
85
|
+
const digest = adapters.digest ?? digestPluginSource;
|
|
86
|
+
const createRoot = adapters.createRoot ?? (() => mkdtempSync(join(tmpdir(), "agdf-local-plugin-")));
|
|
87
|
+
const copy = adapters.copy ?? ((source, target) => cpSync(source, target, { recursive: true }));
|
|
88
|
+
const remove = adapters.remove ?? ((root) => rmSync(root, { recursive: true, force: true }));
|
|
89
|
+
const beforeDigest = digest(sourceRoot, expectedVersion);
|
|
90
|
+
let snapshotRoot = null;
|
|
91
|
+
try {
|
|
92
|
+
snapshotRoot = resolve(createRoot());
|
|
93
|
+
const pluginRoot = join(snapshotRoot, "agdf");
|
|
94
|
+
copy(sourceRoot, pluginRoot);
|
|
95
|
+
const snapshotDigest = digest(pluginRoot, expectedVersion);
|
|
96
|
+
const afterDigest = digest(sourceRoot, expectedVersion);
|
|
97
|
+
if (beforeDigest !== snapshotDigest || snapshotDigest !== afterDigest) {
|
|
98
|
+
throw localInstallSourceUnstable(
|
|
99
|
+
`before=${beforeDigest}, snapshot=${snapshotDigest}, after=${afterDigest}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const copilotProfile = profileId === "copilot-runtime-plugin";
|
|
103
|
+
return Object.freeze({
|
|
104
|
+
canonicalVersion: expectedVersion,
|
|
105
|
+
profileId,
|
|
106
|
+
pluginRoot,
|
|
107
|
+
sourceDigest: snapshotDigest,
|
|
108
|
+
codexInstallVersion: copilotProfile
|
|
109
|
+
? expectedVersion
|
|
110
|
+
: codexLocalInstallVersion(expectedVersion, snapshotDigest),
|
|
111
|
+
cleanup() {
|
|
112
|
+
if (!snapshotRoot) return;
|
|
113
|
+
const ownedRoot = snapshotRoot;
|
|
114
|
+
remove(ownedRoot);
|
|
115
|
+
snapshotRoot = null;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (snapshotRoot) remove(snapshotRoot);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
61
124
|
function readJson(path, label) {
|
|
62
125
|
try {
|
|
63
126
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -160,6 +223,9 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
160
223
|
requireInstallationProvenance = false,
|
|
161
224
|
allowLegacyProvenanceForMigration = false,
|
|
162
225
|
allowPreProvenanceShape = false,
|
|
226
|
+
allowHistoricalProfilesForMigration = false,
|
|
227
|
+
distributionProfileHistory = null,
|
|
228
|
+
classifyHistoricalProfile = classifyHistoricalDistributionProfile,
|
|
163
229
|
profileId = "runtime-plugin",
|
|
164
230
|
} = {}) {
|
|
165
231
|
const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"), "built plugin definition");
|
|
@@ -193,10 +259,18 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
193
259
|
runtimeManifest: runtime,
|
|
194
260
|
pluginVersion: expectedCodexInstallVersion,
|
|
195
261
|
allowLegacy: true,
|
|
262
|
+
allowHistoricalProfilesForMigration,
|
|
263
|
+
distributionProfileHistory,
|
|
264
|
+
classifyHistoricalProfile,
|
|
196
265
|
});
|
|
197
266
|
}
|
|
198
|
-
|
|
199
|
-
|
|
267
|
+
const supportedHistoricalProfile = provenance?.status === "matched"
|
|
268
|
+
&& provenance.profileClassification === "supported_historical";
|
|
269
|
+
if (profile.status !== "matched"
|
|
270
|
+
&& provenance?.status !== "legacy"
|
|
271
|
+
&& !supportedHistoricalProfile
|
|
272
|
+
&& !isPreProvenanceShape) {
|
|
273
|
+
throw new Error(`Built plugin distribution profile contract is invalid (${provenance?.reason ?? profile.reason}).`);
|
|
200
274
|
}
|
|
201
275
|
if (!copilotProfile && codex.version !== expectedCodexInstallVersion) {
|
|
202
276
|
throw new Error(`Built Codex plugin version mismatch: expected ${expectedCodexInstallVersion}, observed ${codex.version}`);
|
|
@@ -226,6 +300,9 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
226
300
|
runtimeManifest: runtime,
|
|
227
301
|
pluginVersion: expectedCodexInstallVersion,
|
|
228
302
|
allowLegacy: allowLegacyProvenanceForMigration,
|
|
303
|
+
allowHistoricalProfilesForMigration,
|
|
304
|
+
distributionProfileHistory,
|
|
305
|
+
classifyHistoricalProfile,
|
|
229
306
|
profileId,
|
|
230
307
|
inventoryDigest: copilotInventory?.inventoryDigest ?? null,
|
|
231
308
|
});
|
|
@@ -234,7 +311,11 @@ function validateBuiltPlugin(pluginRoot, expectedVersion, expectedCodexInstallVe
|
|
|
234
311
|
throw new Error(`Invalid AGDF installation provenance (${provenance.reason}): ${pluginRoot}`);
|
|
235
312
|
}
|
|
236
313
|
}
|
|
237
|
-
return
|
|
314
|
+
return {
|
|
315
|
+
definition,
|
|
316
|
+
provenance,
|
|
317
|
+
profileClassification: provenance?.profileClassification ?? "current",
|
|
318
|
+
};
|
|
238
319
|
}
|
|
239
320
|
|
|
240
321
|
function validateMarketplaceRoot(root, definition = pluginDefinition, { profileId = "runtime-plugin" } = {}) {
|
|
@@ -262,7 +343,10 @@ function validateMarketplaceRoot(root, definition = pluginDefinition, { profileI
|
|
|
262
343
|
return Object.freeze({ codexShape });
|
|
263
344
|
}
|
|
264
345
|
|
|
265
|
-
function classifyExistingMarketplace(stableRoot, marker, {
|
|
346
|
+
function classifyExistingMarketplace(stableRoot, marker, {
|
|
347
|
+
profileId = "runtime-plugin",
|
|
348
|
+
distributionProfileHistory = null,
|
|
349
|
+
} = {}) {
|
|
266
350
|
const existingPluginRoot = join(stableRoot, "plugins", MARKETPLACE_ID);
|
|
267
351
|
const copilotProfile = profileId === "copilot-runtime-plugin";
|
|
268
352
|
const existingCodexInstallVersion = copilotProfile ? marker.version : marker.codex_install_version ?? marker.version;
|
|
@@ -279,15 +363,32 @@ function classifyExistingMarketplace(stableRoot, marker, { profileId = "runtime-
|
|
|
279
363
|
const preProvenanceCandidate = !hasCurrentProvenance
|
|
280
364
|
&& !hasLegacyProvenance
|
|
281
365
|
&& !Object.hasOwn(definition, "distributionProfiles");
|
|
282
|
-
const
|
|
366
|
+
const existingValidation = validateBuiltPlugin(
|
|
283
367
|
existingPluginRoot,
|
|
284
368
|
marker.version,
|
|
285
369
|
existingCodexInstallVersion,
|
|
286
370
|
marker.source_digest ?? "",
|
|
287
371
|
preProvenanceCandidate
|
|
288
372
|
? { allowPreProvenanceShape: true }
|
|
289
|
-
: {
|
|
373
|
+
: {
|
|
374
|
+
requireInstallationProvenance: true,
|
|
375
|
+
allowLegacyProvenanceForMigration: true,
|
|
376
|
+
allowHistoricalProfilesForMigration: !copilotProfile,
|
|
377
|
+
distributionProfileHistory,
|
|
378
|
+
profileId,
|
|
379
|
+
},
|
|
290
380
|
);
|
|
381
|
+
if (!preProvenanceCandidate) {
|
|
382
|
+
if (!/^[a-f0-9]{64}$/.test(marker.source_digest ?? "")) {
|
|
383
|
+
throw new Error(`Refusing invalid AGDF marketplace ownership source digest: ${stableRoot}`);
|
|
384
|
+
}
|
|
385
|
+
const observedSourceDigest = existingValidation.provenance?.observedSourceDigest
|
|
386
|
+
?? digestPluginSource(existingPluginRoot, marker.version);
|
|
387
|
+
if (marker.source_digest !== observedSourceDigest) {
|
|
388
|
+
throw new Error(`Refusing AGDF marketplace ownership source digest mismatch: ${stableRoot}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const existingDefinition = existingValidation.definition;
|
|
291
392
|
const marketplace = validateMarketplaceRoot(stableRoot, existingDefinition, { profileId });
|
|
292
393
|
if (digestDirectory(existingPluginRoot) !== marker.plugin_digest) {
|
|
293
394
|
throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
|
|
@@ -295,9 +396,19 @@ function classifyExistingMarketplace(stableRoot, marker, { profileId = "runtime-
|
|
|
295
396
|
return Object.freeze({
|
|
296
397
|
classification: preProvenanceCandidate
|
|
297
398
|
? "owned_pre_provenance_rebuild"
|
|
298
|
-
: "
|
|
399
|
+
: existingValidation.profileClassification === "supported_historical"
|
|
400
|
+
? "owned_supported_historical_rebuild"
|
|
401
|
+
: "current_or_marker_migration",
|
|
299
402
|
definition: existingDefinition,
|
|
300
403
|
marketplace,
|
|
404
|
+
historicalEvidence: existingValidation.profileClassification === "supported_historical"
|
|
405
|
+
? {
|
|
406
|
+
releaseVersion: existingValidation.provenance.historicalReleaseVersion,
|
|
407
|
+
contractId: existingValidation.provenance.historicalContractId,
|
|
408
|
+
contractDigest: existingValidation.provenance.historicalContractDigest,
|
|
409
|
+
entryDigest: existingValidation.provenance.historicalEntryDigest,
|
|
410
|
+
}
|
|
411
|
+
: null,
|
|
301
412
|
});
|
|
302
413
|
}
|
|
303
414
|
|
|
@@ -322,13 +433,15 @@ function recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failed
|
|
|
322
433
|
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
323
434
|
}
|
|
324
435
|
|
|
325
|
-
|
|
436
|
+
function prepareLocalMarketplaceFromSource({
|
|
326
437
|
dataRoot = defaultAgdfDataRoot(),
|
|
327
438
|
builtPluginRoot,
|
|
328
439
|
expectedVersion = pluginDefinition.version,
|
|
329
440
|
codexInstallVersion = expectedVersion,
|
|
330
441
|
codexRegistrationRevision = null,
|
|
331
442
|
profileId = "runtime-plugin",
|
|
443
|
+
knownSourceDigest = "",
|
|
444
|
+
sourceStaged = () => {},
|
|
332
445
|
} = {}) {
|
|
333
446
|
dataRoot = resolve(dataRoot);
|
|
334
447
|
const copilotProfile = profileId === "copilot-runtime-plugin";
|
|
@@ -342,12 +455,15 @@ export function prepareLocalMarketplace({
|
|
|
342
455
|
const backupRoot = `${stableRoot}.backup`;
|
|
343
456
|
const failedRoot = `${stableRoot}.failed`;
|
|
344
457
|
if (!pathInside(dataRoot, stableRoot) || dirname(stableRoot) !== parent) throw new Error(`Unsafe AGDF marketplace root: ${stableRoot}`);
|
|
345
|
-
const sourceDigest = digestPluginSource(builtPluginRoot, expectedVersion);
|
|
458
|
+
const sourceDigest = knownSourceDigest || digestPluginSource(builtPluginRoot, expectedVersion);
|
|
346
459
|
if (!copilotProfile && codexInstallVersion !== expectedVersion
|
|
347
460
|
&& !isCodexLocalInstallVersion(expectedVersion, codexInstallVersion, sourceDigest)) {
|
|
348
461
|
throw new Error(`Invalid AGDF Codex local install version: ${codexInstallVersion}`);
|
|
349
462
|
}
|
|
350
|
-
const targetDefinition = validateBuiltPlugin(builtPluginRoot, expectedVersion, expectedVersion, "", { profileId });
|
|
463
|
+
const targetDefinition = validateBuiltPlugin(builtPluginRoot, expectedVersion, expectedVersion, "", { profileId }).definition;
|
|
464
|
+
const distributionProfileHistory = readOptionalJson(
|
|
465
|
+
join(builtPluginRoot, "meta", "distribution-profile-history.json"),
|
|
466
|
+
);
|
|
351
467
|
mkdirSync(parent, { recursive: true });
|
|
352
468
|
recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot);
|
|
353
469
|
|
|
@@ -365,15 +481,17 @@ export function prepareLocalMarketplace({
|
|
|
365
481
|
?? 0;
|
|
366
482
|
let existingMarketplace = null;
|
|
367
483
|
let existingClassification = "none";
|
|
484
|
+
let historicalEvidence = null;
|
|
368
485
|
if (existing) {
|
|
369
486
|
let classified;
|
|
370
487
|
try {
|
|
371
|
-
classified = classifyExistingMarketplace(stableRoot, existing, { profileId });
|
|
488
|
+
classified = classifyExistingMarketplace(stableRoot, existing, { profileId, distributionProfileHistory });
|
|
372
489
|
} catch (error) {
|
|
373
490
|
throw invalidExistingMarketplace(error);
|
|
374
491
|
}
|
|
375
492
|
existingClassification = classified.classification;
|
|
376
493
|
existingMarketplace = classified.marketplace;
|
|
494
|
+
historicalEvidence = classified.historicalEvidence;
|
|
377
495
|
}
|
|
378
496
|
mkdirSync(stageRoot, { recursive: false });
|
|
379
497
|
try {
|
|
@@ -392,6 +510,7 @@ export function prepareLocalMarketplace({
|
|
|
392
510
|
});
|
|
393
511
|
const stagedPluginRoot = join(stageRoot, "plugins", MARKETPLACE_ID);
|
|
394
512
|
cpSync(builtPluginRoot, stagedPluginRoot, { recursive: true });
|
|
513
|
+
sourceStaged();
|
|
395
514
|
validateBuiltPlugin(stagedPluginRoot, expectedVersion, expectedVersion, "", { profileId });
|
|
396
515
|
if (!copilotProfile && codexInstallVersion !== expectedVersion) {
|
|
397
516
|
const codexManifestPath = join(stagedPluginRoot, ".codex-plugin", "plugin.json");
|
|
@@ -450,6 +569,7 @@ export function prepareLocalMarketplace({
|
|
|
450
569
|
digest: pluginDigest,
|
|
451
570
|
runtimeDigest: runtimeManifest.digest,
|
|
452
571
|
existingClassification,
|
|
572
|
+
historicalEvidence,
|
|
453
573
|
changed: false,
|
|
454
574
|
commit() {},
|
|
455
575
|
rollback() {},
|
|
@@ -473,6 +593,7 @@ export function prepareLocalMarketplace({
|
|
|
473
593
|
digest: pluginDigest,
|
|
474
594
|
runtimeDigest: runtimeManifest.digest,
|
|
475
595
|
existingClassification,
|
|
596
|
+
historicalEvidence,
|
|
476
597
|
changed: true,
|
|
477
598
|
commit() {
|
|
478
599
|
if (closed) return;
|
|
@@ -498,8 +619,42 @@ export function prepareLocalMarketplace({
|
|
|
498
619
|
}
|
|
499
620
|
}
|
|
500
621
|
|
|
622
|
+
export function prepareLocalMarketplace(options = {}) {
|
|
623
|
+
if (!options.snapshotSource) return prepareLocalMarketplaceFromSource(options);
|
|
624
|
+
if (Object.hasOwn(options, "codexInstallVersion")) {
|
|
625
|
+
throw new Error("AGDF snapshot preparation owns the Codex local install version.");
|
|
626
|
+
}
|
|
627
|
+
const snapshot = captureLocalPluginSnapshot({
|
|
628
|
+
builtPluginRoot: options.builtPluginRoot,
|
|
629
|
+
expectedVersion: options.expectedVersion,
|
|
630
|
+
profileId: options.profileId,
|
|
631
|
+
adapters: options.snapshotAdapters,
|
|
632
|
+
});
|
|
633
|
+
try {
|
|
634
|
+
const { snapshotSource: _snapshotSource, snapshotAdapters: _snapshotAdapters, ...stableOptions } = options;
|
|
635
|
+
return prepareLocalMarketplaceFromSource({
|
|
636
|
+
...stableOptions,
|
|
637
|
+
builtPluginRoot: snapshot.pluginRoot,
|
|
638
|
+
expectedVersion: snapshot.canonicalVersion,
|
|
639
|
+
profileId: snapshot.profileId,
|
|
640
|
+
codexInstallVersion: snapshot.codexInstallVersion,
|
|
641
|
+
knownSourceDigest: snapshot.sourceDigest,
|
|
642
|
+
sourceStaged: snapshot.cleanup,
|
|
643
|
+
});
|
|
644
|
+
} finally {
|
|
645
|
+
snapshot.cleanup();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
501
649
|
export function prepareCopilotMarketplace(options = {}) {
|
|
502
|
-
|
|
650
|
+
const { codexInstallVersion, ...copilotOptions } = options;
|
|
651
|
+
return prepareLocalMarketplace({
|
|
652
|
+
...copilotOptions,
|
|
653
|
+
profileId: "copilot-runtime-plugin",
|
|
654
|
+
...(!options.snapshotSource
|
|
655
|
+
? { codexInstallVersion: codexInstallVersion ?? options.expectedVersion ?? pluginDefinition.version }
|
|
656
|
+
: {}),
|
|
657
|
+
});
|
|
503
658
|
}
|
|
504
659
|
|
|
505
660
|
export function inspectLocalMarketplaceProjection({
|
|
@@ -517,7 +672,7 @@ export function inspectLocalMarketplaceProjection({
|
|
|
517
672
|
codexInstallVersion,
|
|
518
673
|
marker.source_digest ?? "",
|
|
519
674
|
{ requireInstallationProvenance: true },
|
|
520
|
-
);
|
|
675
|
+
).definition;
|
|
521
676
|
validateMarketplaceRoot(root, definition);
|
|
522
677
|
if (marker.version !== expectedVersion || digestDirectory(pluginRoot) !== marker.plugin_digest) {
|
|
523
678
|
throw new Error(`Refusing stale or tampered AGDF marketplace projection: ${root}`);
|