create-agdf 0.10.0 → 0.10.2
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 +30 -22
- package/bin/agdf-validator.js +11 -0
- package/generated/.agdf/control/README.md +3 -3
- package/generated/.github/skills/agdf-brownfield-analysis/SKILL.md +1 -1
- package/generated/.github/skills/agdf-delivery-path-search/SKILL.md +7 -6
- package/generated/.github/skills/agdf-gate-check/SKILL.md +28 -91
- package/generated/.github/skills/agdf-interaction-locales.json +2 -0
- package/generated/.github/skills/contracts/gate-transition.md +4 -2
- package/generated/.github/skills/contracts/modes.md +12 -1
- package/generated/.opencode/README.md +6 -8
- package/generated/.opencode/agdf-interaction-locales.json +2 -0
- package/generated/.opencode/contracts/gate-transition.md +4 -2
- package/generated/.opencode/contracts/modes.md +12 -1
- package/generated/.opencode/skills/agdf-brownfield-analysis/SKILL.md +1 -1
- package/generated/.opencode/skills/agdf-delivery-path-search/SKILL.md +7 -6
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +28 -91
- package/generated/plugins/agdf/.claude-plugin/plugin.json +19 -0
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/README.md +3 -3
- package/generated/plugins/agdf/hooks/session-start.sh +1 -1
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +2 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +1 -1
- package/generated/plugins/agdf/meta/contracts/gate-transition.md +4 -2
- package/generated/plugins/agdf/meta/contracts/modes.md +12 -1
- package/generated/plugins/agdf/runtime/agdf-local.js +11 -0
- package/generated/plugins/agdf/runtime/create-agdf/NOTICE +10 -0
- package/generated/plugins/agdf/runtime/create-agdf/bin/agdf-validator.js +11 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +418 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +220 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +136 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +75 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/parse-args.js +156 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/runtime-context.js +83 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/validation-handlers.js +45 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/delivery-map.js +291 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/doctor.js +265 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-check.js +459 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/gate-policy.js +283 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/required-files.js +9 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/run-state.js +217 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +344 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/verified-change.js +156 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/aggregate.js +18 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/gate-approval-validator.js +49 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/index.js +7 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/legacy-migration.js +95 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-parser.js +227 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-repository.js +68 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-resolver.js +45 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-state/run-state-writer.js +97 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/candidate-policy.js +99 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/contracts.js +237 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +44 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +53 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/protocol.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/claude.js +40 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/codex.js +38 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/generators/protocol.js +12 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/persistence.js +39 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/scoring.js +21 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +154 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/state-adapter.js +161 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +14 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/transports/read-only-guard.js +43 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +590 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/local-validator.js +142 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/runtime/validator-application.js +40 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +66 -0
- package/generated/plugins/agdf/runtime/runtime-manifest.json +8 -0
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +1040 -0
- package/generated/plugins/agdf/skills/brownfield-analysis/SKILL.md +1 -1
- package/generated/plugins/agdf/skills/delivery-path-search/SKILL.md +7 -6
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +28 -91
- package/lib/cli/application.js +21 -59
- package/lib/cli/command-registry.js +9 -11
- package/lib/cli/validation-handlers.js +45 -0
- package/lib/control-evaluation/doctor.js +1 -1
- package/lib/control-evaluation/gate-check.js +7 -7
- package/lib/control-evaluation/required-files.js +9 -0
- package/lib/installers/local-marketplace.js +322 -0
- package/lib/installers/opencode-activation.js +43 -0
- package/lib/installers/opencode.js +60 -18
- package/lib/installers/plugin-installers.js +102 -26
- package/lib/runtime/local-validator.js +142 -0
- package/lib/runtime/validator-application.js +40 -0
- package/lib/scaffold/plan.js +1 -30
- package/lib/scaffold/presentation.js +9 -14
- package/opencode-plugin.js +32 -24
- package/package.json +6 -2
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
statSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
15
|
+
import process from "node:process";
|
|
16
|
+
import { generatedRoot, packageRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
17
|
+
|
|
18
|
+
const MARKETPLACE_ID = "agdf";
|
|
19
|
+
const OWNERSHIP_FILE = ".agdf-owned.json";
|
|
20
|
+
const LEGACY_REPOSITORY = "arndtgold/ai-native-governance-delivery-framework";
|
|
21
|
+
|
|
22
|
+
function pathInside(root, candidate) {
|
|
23
|
+
const rel = relative(root, candidate);
|
|
24
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function digestDirectory(root) {
|
|
28
|
+
const files = [];
|
|
29
|
+
function visit(directory) {
|
|
30
|
+
for (const name of readdirSync(directory).sort()) {
|
|
31
|
+
const path = join(directory, name);
|
|
32
|
+
const stats = statSync(path);
|
|
33
|
+
if (stats.isDirectory()) visit(path);
|
|
34
|
+
else if (stats.isFile()) files.push(path);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
visit(root);
|
|
38
|
+
const hash = createHash("sha256");
|
|
39
|
+
for (const path of files) {
|
|
40
|
+
hash.update(relative(root, path).replaceAll("\\", "/"));
|
|
41
|
+
hash.update("\0");
|
|
42
|
+
hash.update(readFileSync(path));
|
|
43
|
+
hash.update("\0");
|
|
44
|
+
}
|
|
45
|
+
return hash.digest("hex");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readJson(path, label) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error(`${label} is invalid: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function defaultAgdfDataRoot({ env = process.env, platform = process.platform, home = homedir() } = {}) {
|
|
57
|
+
if (env.AGDF_DATA_DIR) return resolve(env.AGDF_DATA_DIR);
|
|
58
|
+
if (platform === "darwin") return join(home, "Library", "Application Support", "agdf");
|
|
59
|
+
if (platform === "win32") return join(env.LOCALAPPDATA || env.APPDATA || join(home, "AppData", "Local"), "agdf");
|
|
60
|
+
return join(env.XDG_DATA_HOME || join(home, ".local", "share"), "agdf");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function localMarketplaceRoot(options = {}) {
|
|
64
|
+
return join(defaultAgdfDataRoot(options), "marketplaces", MARKETPLACE_ID);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ownership(root, { allowBuilding = false } = {}) {
|
|
68
|
+
if (!existsSync(join(root, OWNERSHIP_FILE))) {
|
|
69
|
+
throw new Error(`Refusing marketplace path without AGDF ownership marker: ${root}`);
|
|
70
|
+
}
|
|
71
|
+
const marker = readJson(join(root, OWNERSHIP_FILE), "AGDF marketplace ownership marker");
|
|
72
|
+
if (marker.schema_version !== 1 || marker.owner !== "create-agdf" || marker.marketplace_id !== MARKETPLACE_ID) {
|
|
73
|
+
throw new Error(`Refusing marketplace path with invalid AGDF ownership marker: ${root}`);
|
|
74
|
+
}
|
|
75
|
+
if (marker.staging_state !== "ready" && !(allowBuilding && marker.staging_state === "building")) {
|
|
76
|
+
throw new Error(`Refusing marketplace path with invalid AGDF staging state: ${root}`);
|
|
77
|
+
}
|
|
78
|
+
return marker;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function removeOwnedRoot(root, parent, options = {}) {
|
|
82
|
+
if (!existsSync(root)) return;
|
|
83
|
+
if (!pathInside(parent, root) || dirname(root) !== parent) throw new Error(`Refusing unsafe marketplace cleanup path: ${root}`);
|
|
84
|
+
ownership(root, options);
|
|
85
|
+
rmSync(root, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeJson(path, value) {
|
|
89
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
90
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function codexMarketplace() {
|
|
94
|
+
return {
|
|
95
|
+
name: MARKETPLACE_ID,
|
|
96
|
+
interface: { displayName: pluginDefinition.displayName },
|
|
97
|
+
plugins: [{
|
|
98
|
+
name: pluginDefinition.id,
|
|
99
|
+
source: { source: "local", path: "./plugins/agdf" },
|
|
100
|
+
policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" },
|
|
101
|
+
category: pluginDefinition.category,
|
|
102
|
+
}],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function claudeMarketplace() {
|
|
107
|
+
return {
|
|
108
|
+
name: MARKETPLACE_ID,
|
|
109
|
+
owner: { name: pluginDefinition.displayName, email: "" },
|
|
110
|
+
metadata: { description: pluginDefinition.description },
|
|
111
|
+
plugins: [{
|
|
112
|
+
name: pluginDefinition.id,
|
|
113
|
+
source: "./plugins/agdf",
|
|
114
|
+
description: pluginDefinition.claudeDescription,
|
|
115
|
+
category: pluginDefinition.category,
|
|
116
|
+
}],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateBuiltPlugin(pluginRoot, expectedVersion) {
|
|
121
|
+
const definition = readJson(join(pluginRoot, "meta", "agdf-plugin.definition.json"), "built plugin definition");
|
|
122
|
+
const runtime = readJson(join(pluginRoot, "runtime", "runtime-manifest.json"), "built runtime manifest");
|
|
123
|
+
for (const required of [
|
|
124
|
+
join(pluginRoot, ".codex-plugin", "plugin.json"),
|
|
125
|
+
join(pluginRoot, ".claude-plugin", "plugin.json"),
|
|
126
|
+
join(pluginRoot, "runtime", "agdf-local.js"),
|
|
127
|
+
join(pluginRoot, "scripts", "check-runtime-integrity.mjs"),
|
|
128
|
+
]) {
|
|
129
|
+
if (!existsSync(required)) throw new Error(`Built plugin is incomplete: ${required}`);
|
|
130
|
+
}
|
|
131
|
+
if (definition.version !== expectedVersion || runtime.version !== expectedVersion) {
|
|
132
|
+
throw new Error(`Built plugin version mismatch: expected ${expectedVersion}, definition ${definition.version}, runtime ${runtime.version}`);
|
|
133
|
+
}
|
|
134
|
+
if (!/^[a-f0-9]{64}$/.test(runtime.digest ?? "")) throw new Error("Built plugin runtime digest is invalid.");
|
|
135
|
+
if (digestDirectory(join(pluginRoot, "runtime", "create-agdf")) !== runtime.digest) {
|
|
136
|
+
throw new Error("Built plugin runtime digest does not match its payload.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function validateMarketplaceRoot(root) {
|
|
141
|
+
const codex = readJson(join(root, ".agents", "plugins", "marketplace.json"), "Codex local marketplace manifest");
|
|
142
|
+
const claude = readJson(join(root, ".claude-plugin", "marketplace.json"), "Claude local marketplace manifest");
|
|
143
|
+
if (JSON.stringify(codex) !== JSON.stringify(codexMarketplace())) {
|
|
144
|
+
throw new Error(`Codex local marketplace manifest is not owned by ${MARKETPLACE_ID}: ${root}`);
|
|
145
|
+
}
|
|
146
|
+
if (JSON.stringify(claude) !== JSON.stringify(claudeMarketplace())) {
|
|
147
|
+
throw new Error(`Claude local marketplace manifest is not owned by ${MARKETPLACE_ID}: ${root}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot) {
|
|
152
|
+
const parent = dirname(stableRoot);
|
|
153
|
+
if (existsSync(backupRoot)) {
|
|
154
|
+
ownership(backupRoot);
|
|
155
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
156
|
+
if (existsSync(stableRoot)) {
|
|
157
|
+
ownership(stableRoot);
|
|
158
|
+
renameSync(stableRoot, failedRoot);
|
|
159
|
+
}
|
|
160
|
+
renameSync(backupRoot, stableRoot);
|
|
161
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
162
|
+
}
|
|
163
|
+
if (existsSync(stageRoot)) removeOwnedRoot(stageRoot, parent, { allowBuilding: true });
|
|
164
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function prepareLocalMarketplace({
|
|
168
|
+
dataRoot = defaultAgdfDataRoot(),
|
|
169
|
+
builtPluginRoot = join(generatedRoot, "plugins", "agdf"),
|
|
170
|
+
expectedVersion = pluginDefinition.version,
|
|
171
|
+
} = {}) {
|
|
172
|
+
dataRoot = resolve(dataRoot);
|
|
173
|
+
builtPluginRoot = resolve(builtPluginRoot);
|
|
174
|
+
const stableRoot = join(dataRoot, "marketplaces", MARKETPLACE_ID);
|
|
175
|
+
const parent = dirname(stableRoot);
|
|
176
|
+
const stageRoot = `${stableRoot}.stage`;
|
|
177
|
+
const backupRoot = `${stableRoot}.backup`;
|
|
178
|
+
const failedRoot = `${stableRoot}.failed`;
|
|
179
|
+
if (!pathInside(dataRoot, stableRoot) || dirname(stableRoot) !== parent) throw new Error(`Unsafe AGDF marketplace root: ${stableRoot}`);
|
|
180
|
+
validateBuiltPlugin(builtPluginRoot, expectedVersion);
|
|
181
|
+
mkdirSync(parent, { recursive: true });
|
|
182
|
+
recoverInterruptedTransaction(stableRoot, stageRoot, backupRoot, failedRoot);
|
|
183
|
+
|
|
184
|
+
const existing = existsSync(stableRoot) ? ownership(stableRoot) : null;
|
|
185
|
+
if (existing) {
|
|
186
|
+
const existingPluginRoot = join(stableRoot, "plugins", MARKETPLACE_ID);
|
|
187
|
+
validateBuiltPlugin(existingPluginRoot, existing.version);
|
|
188
|
+
validateMarketplaceRoot(stableRoot);
|
|
189
|
+
if (digestDirectory(existingPluginRoot) !== existing.plugin_digest) {
|
|
190
|
+
throw new Error(`Refusing tampered AGDF marketplace root: ${stableRoot}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
mkdirSync(stageRoot, { recursive: false });
|
|
194
|
+
try {
|
|
195
|
+
writeJson(join(stageRoot, OWNERSHIP_FILE), {
|
|
196
|
+
schema_version: 1,
|
|
197
|
+
owner: "create-agdf",
|
|
198
|
+
marketplace_id: MARKETPLACE_ID,
|
|
199
|
+
version: expectedVersion,
|
|
200
|
+
plugin_digest: null,
|
|
201
|
+
source_package_version: readJson(join(packageRoot, "package.json"), "create-agdf package manifest").version,
|
|
202
|
+
staging_state: "building",
|
|
203
|
+
});
|
|
204
|
+
const stagedPluginRoot = join(stageRoot, "plugins", MARKETPLACE_ID);
|
|
205
|
+
cpSync(builtPluginRoot, stagedPluginRoot, { recursive: true });
|
|
206
|
+
validateBuiltPlugin(stagedPluginRoot, expectedVersion);
|
|
207
|
+
writeJson(join(stageRoot, ".agents", "plugins", "marketplace.json"), codexMarketplace());
|
|
208
|
+
writeJson(join(stageRoot, ".claude-plugin", "marketplace.json"), claudeMarketplace());
|
|
209
|
+
validateMarketplaceRoot(stageRoot);
|
|
210
|
+
const pluginDigest = digestDirectory(stagedPluginRoot);
|
|
211
|
+
const marker = {
|
|
212
|
+
schema_version: 1,
|
|
213
|
+
owner: "create-agdf",
|
|
214
|
+
marketplace_id: MARKETPLACE_ID,
|
|
215
|
+
version: expectedVersion,
|
|
216
|
+
plugin_digest: pluginDigest,
|
|
217
|
+
source_package_version: readJson(join(packageRoot, "package.json"), "create-agdf package manifest").version,
|
|
218
|
+
staging_state: "ready",
|
|
219
|
+
};
|
|
220
|
+
writeJson(join(stageRoot, OWNERSHIP_FILE), marker);
|
|
221
|
+
|
|
222
|
+
if (existing?.version === expectedVersion && existing?.plugin_digest === pluginDigest) {
|
|
223
|
+
removeOwnedRoot(stageRoot, parent);
|
|
224
|
+
return Object.freeze({
|
|
225
|
+
root: stableRoot,
|
|
226
|
+
pluginRoot: join(stableRoot, "plugins", MARKETPLACE_ID),
|
|
227
|
+
version: expectedVersion,
|
|
228
|
+
digest: pluginDigest,
|
|
229
|
+
changed: false,
|
|
230
|
+
commit() {},
|
|
231
|
+
rollback() {},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (existsSync(stableRoot)) renameSync(stableRoot, backupRoot);
|
|
236
|
+
try {
|
|
237
|
+
renameSync(stageRoot, stableRoot);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (existsSync(backupRoot) && !existsSync(stableRoot)) renameSync(backupRoot, stableRoot);
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
let closed = false;
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
root: stableRoot,
|
|
245
|
+
pluginRoot: join(stableRoot, "plugins", MARKETPLACE_ID),
|
|
246
|
+
version: expectedVersion,
|
|
247
|
+
digest: pluginDigest,
|
|
248
|
+
changed: true,
|
|
249
|
+
commit() {
|
|
250
|
+
if (closed) return;
|
|
251
|
+
if (existsSync(backupRoot)) removeOwnedRoot(backupRoot, parent);
|
|
252
|
+
closed = true;
|
|
253
|
+
},
|
|
254
|
+
rollback() {
|
|
255
|
+
if (closed) return;
|
|
256
|
+
if (existsSync(backupRoot)) {
|
|
257
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
258
|
+
if (existsSync(stableRoot)) renameSync(stableRoot, failedRoot);
|
|
259
|
+
renameSync(backupRoot, stableRoot);
|
|
260
|
+
if (existsSync(failedRoot)) removeOwnedRoot(failedRoot, parent);
|
|
261
|
+
} else if (existsSync(stableRoot)) {
|
|
262
|
+
removeOwnedRoot(stableRoot, parent);
|
|
263
|
+
}
|
|
264
|
+
closed = true;
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (existsSync(stageRoot)) removeOwnedRoot(stageRoot, parent, { allowBuilding: true });
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizedLegacySource(value) {
|
|
274
|
+
if (typeof value !== "string") return "";
|
|
275
|
+
return value.trim().toLowerCase()
|
|
276
|
+
.replace(/^git\+/, "")
|
|
277
|
+
.replace(/^https?:\/\/github\.com\//, "")
|
|
278
|
+
.replace(/^github:/, "")
|
|
279
|
+
.replace(/\.git$/, "")
|
|
280
|
+
.replace(/\/$/, "");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function sourceForEntry(entry) {
|
|
284
|
+
const source = entry?.marketplaceSource?.source
|
|
285
|
+
?? entry?.source?.source
|
|
286
|
+
?? (entry?.source === "directory" ? entry?.path : undefined)
|
|
287
|
+
?? (entry?.source === "github" ? entry?.repo : undefined)
|
|
288
|
+
?? entry?.repo
|
|
289
|
+
?? entry?.path
|
|
290
|
+
?? entry?.source
|
|
291
|
+
?? entry?.url
|
|
292
|
+
?? "";
|
|
293
|
+
return typeof source === "string" ? source : "";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function classifyMarketplaceList(surface, output, stableRoot) {
|
|
297
|
+
let parsed;
|
|
298
|
+
try {
|
|
299
|
+
parsed = typeof output === "string" ? JSON.parse(output) : output;
|
|
300
|
+
} catch {
|
|
301
|
+
return { state: "unknown", source: "", reason: "invalid_json" };
|
|
302
|
+
}
|
|
303
|
+
const entries = surface === "codex" ? parsed?.marketplaces : parsed;
|
|
304
|
+
if (!Array.isArray(entries)) return { state: "unknown", source: "", reason: "invalid_shape" };
|
|
305
|
+
const matches = entries.filter((entry) => entry?.name === MARKETPLACE_ID);
|
|
306
|
+
if (matches.length === 0) return { state: "absent", source: "" };
|
|
307
|
+
if (matches.length !== 1) return { state: "unknown", source: "", reason: "duplicate_name" };
|
|
308
|
+
const entry = matches[0];
|
|
309
|
+
const source = sourceForEntry(entry);
|
|
310
|
+
const sourceType = entry?.marketplaceSource?.sourceType ?? entry?.source?.sourceType ?? "";
|
|
311
|
+
if ((sourceType === "local" || isAbsolute(source)) && resolve(source) === resolve(stableRoot)) {
|
|
312
|
+
return { state: "owned_local_current", source };
|
|
313
|
+
}
|
|
314
|
+
if (normalizedLegacySource(source) === LEGACY_REPOSITORY) return { state: "legacy_github", source };
|
|
315
|
+
return { state: "conflict", source, reason: source ? "foreign_source" : "source_missing" };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export const localMarketplaceConstants = Object.freeze({
|
|
319
|
+
id: MARKETPLACE_ID,
|
|
320
|
+
ownershipFile: OWNERSHIP_FILE,
|
|
321
|
+
legacyRepository: LEGACY_REPOSITORY,
|
|
322
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pluginDefinition } from "../cli/runtime-context.js";
|
|
4
|
+
|
|
5
|
+
const requiredConfigFields = ["artifact_language", "chat_language", "runtime_language"];
|
|
6
|
+
|
|
7
|
+
export function isOpenCodeLegacySurfacePresent(targetDir) {
|
|
8
|
+
return existsSync(join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName))
|
|
9
|
+
&& existsSync(join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function evaluateOpenCodeRepositoryActivation(targetDir) {
|
|
13
|
+
const controlDir = join(targetDir, ".agdf", "control");
|
|
14
|
+
const configPath = join(controlDir, "config.json");
|
|
15
|
+
const legacySurface = isOpenCodeLegacySurfacePresent(targetDir);
|
|
16
|
+
const base = {
|
|
17
|
+
control_dir: controlDir,
|
|
18
|
+
config_path: configPath,
|
|
19
|
+
legacy_surface: legacySurface,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (!existsSync(configPath)) {
|
|
23
|
+
return { ...base, state: "inactive", active: false, diagnostic: "missing_control_config" };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
28
|
+
const valid = config
|
|
29
|
+
&& typeof config === "object"
|
|
30
|
+
&& !Array.isArray(config)
|
|
31
|
+
&& requiredConfigFields.every((field) => typeof config[field] === "string" && config[field].trim());
|
|
32
|
+
if (!valid) return { ...base, state: "invalid_control", active: false, diagnostic: "invalid_control_config" };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return { ...base, state: "invalid_control", active: false, diagnostic: "invalid_control_json", error: error.message };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
state: legacySurface ? "legacy_compatible" : "active",
|
|
40
|
+
active: true,
|
|
41
|
+
diagnostic: "valid_control_config",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -4,6 +4,8 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { generatedRoot, pluginDefinition } from "../cli/runtime-context.js";
|
|
7
|
+
import { evaluateOpenCodeRepositoryActivation } from "./opencode-activation.js";
|
|
8
|
+
import { resolveLocalValidator } from "../runtime/local-validator.js";
|
|
7
9
|
|
|
8
10
|
const contractModules = ["gate-transition.md", "interaction.md", "modes.md", "quality.md", "context-graph.md", "control-scaffold.md", "closeout.md"];
|
|
9
11
|
const openCodeSkillNames = pluginDefinition.skillSet.map((skill) => pluginDefinition.opencode.skillPrefix + skill.slug);
|
|
@@ -11,6 +13,7 @@ const globalOpenCodeSkillNames = pluginDefinition.skillSet.map((skill) => plugin
|
|
|
11
13
|
const globalOpenCodeSkillOwnershipMarker = "<!-- AGDF-GLOBAL-SKILL: ";
|
|
12
14
|
const globalOpenCodeInstructionsOwnershipMarker = "<!-- AGDF-GLOBAL-INSTRUCTIONS -->";
|
|
13
15
|
const globalOpenCodeRuntimeContractOwnershipMarker = "<!-- AGDF-GLOBAL-RUNTIME-CONTRACT -->";
|
|
16
|
+
const globalOpenCodeValidatorOwnershipMarker = "// AGDF-GLOBAL-LOCAL-VALIDATOR";
|
|
14
17
|
const testNpmCliPath = process.env.NODE_ENV === "test" ? process.env.AGDF_TEST_NPM_CLI_PATH || "" : "";
|
|
15
18
|
const npmCommand = testNpmCliPath ? process.execPath : process.platform === "win32" ? process.execPath : "npm";
|
|
16
19
|
const npmPrefixArgs = testNpmCliPath ? [testNpmCliPath] : process.platform === "win32" ? [join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")] : [];
|
|
@@ -118,6 +121,7 @@ function globalOpenCodeConfigPaths(configDir) {
|
|
|
118
121
|
runtimeContract: join(configDir, pluginDefinition.opencode.runtimeContractFileName),
|
|
119
122
|
contracts: join(configDir, "contracts"),
|
|
120
123
|
skills: join(configDir, "skills"),
|
|
124
|
+
localValidator: join(configDir, "agdf", "bin", "agdf-local.js"),
|
|
121
125
|
};
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -140,6 +144,7 @@ function assertGlobalOpenCodeSurfaceWritable(configDir) {
|
|
|
140
144
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
141
145
|
assertGlobalOpenCodeFileWritable(paths.instructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
142
146
|
assertGlobalOpenCodeFileWritable(paths.runtimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
147
|
+
assertGlobalOpenCodeFileWritable(paths.localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
143
148
|
for (const moduleName of contractModules) {
|
|
144
149
|
assertGlobalOpenCodeFileWritable(
|
|
145
150
|
join(paths.contracts, moduleName),
|
|
@@ -169,9 +174,19 @@ function globalOpenCodeBoundary() {
|
|
|
169
174
|
"## Global OpenCode Surface Boundary",
|
|
170
175
|
"",
|
|
171
176
|
"This skill is globally discoverable, but global plugin or skill presence is not repository governance activation.",
|
|
172
|
-
"Before applying AGDF gates, later artefacts or implementation guidance, inspect the current repository for
|
|
173
|
-
"If
|
|
174
|
-
"When
|
|
177
|
+
"Before applying AGDF gates, later artefacts or implementation guidance, inspect the current repository for valid `.agdf/control/config.json` durable control.",
|
|
178
|
+
"If durable control is missing or invalid, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo` in this repository.",
|
|
179
|
+
"When durable control is valid, use the global `agdf-global-*` skill surface; existing local OpenCode assets remain a compatibility path.",
|
|
180
|
+
"",
|
|
181
|
+
].join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function globalOpenCodeActivationGuard() {
|
|
185
|
+
return [
|
|
186
|
+
"## Repository Activation Guard",
|
|
187
|
+
"",
|
|
188
|
+
"Apply AGDF governance only when this repository has valid `.agdf/control/config.json` durable control.",
|
|
189
|
+
"If it is missing or invalid, stop and direct the user to `npx --yes @agdf/cli@latest opencode-repo`; the global installation alone is not activation.",
|
|
175
190
|
"",
|
|
176
191
|
].join("\n");
|
|
177
192
|
}
|
|
@@ -198,14 +213,16 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
198
213
|
"AGDF native skills are globally discoverable through OpenCode.",
|
|
199
214
|
"",
|
|
200
215
|
globalOpenCodeBoundary(),
|
|
201
|
-
"
|
|
216
|
+
"Valid `.agdf/control/` is the repository source of truth for active governance; legacy `.opencode/` assets remain supported without being required.",
|
|
202
217
|
"",
|
|
203
218
|
toGlobalOpenCodeContent(generatedInstructions),
|
|
204
219
|
].join("\n");
|
|
205
220
|
const globalRuntimeContract = `${globalOpenCodeRuntimeContractOwnershipMarker}\n${generatedRuntimeContract}`;
|
|
221
|
+
const localValidator = `${globalOpenCodeValidatorOwnershipMarker}\nimport process from "node:process";\nimport { fileURLToPath } from "node:url";\nimport { runLocalValidator } from "../../node_modules/create-agdf/lib/runtime/local-validator.js";\n\nconst ownedPackageRoot = fileURLToPath(new URL("../../node_modules/create-agdf", import.meta.url));\nprocess.exitCode = runLocalValidator({ ownedPackageRoot, expectedVersion: ${JSON.stringify(pluginDefinition.version)}, surface: "opencode" }, process.argv.slice(2));\n`;
|
|
206
222
|
|
|
207
223
|
writeOwnedGlobalOpenCodeFile(paths.instructions, globalInstructions, globalOpenCodeInstructionsOwnershipMarker, "first-line");
|
|
208
224
|
writeOwnedGlobalOpenCodeFile(paths.runtimeContract, globalRuntimeContract, globalOpenCodeRuntimeContractOwnershipMarker, "first-line");
|
|
225
|
+
writeOwnedGlobalOpenCodeFile(paths.localValidator, localValidator, globalOpenCodeValidatorOwnershipMarker, "first-line");
|
|
209
226
|
for (const moduleName of contractModules) {
|
|
210
227
|
const generatedModule = readFileSync(join(generatedOpenCodeRoot, "contracts", moduleName), "utf8");
|
|
211
228
|
writeOwnedGlobalOpenCodeFile(
|
|
@@ -222,7 +239,7 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
222
239
|
const sourcePath = join(generatedOpenCodeRoot, "skills", sourceName, "SKILL.md");
|
|
223
240
|
const sourceContent = readFileSync(sourcePath, "utf8");
|
|
224
241
|
const marker = `${globalOpenCodeSkillOwnershipMarker}${skillName} -->`;
|
|
225
|
-
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${
|
|
242
|
+
const content = toGlobalOpenCodeContent(sourceContent).replace(/^(---[\s\S]*?\n---\n)/, `$1${marker}\n\n${globalOpenCodeActivationGuard()}`);
|
|
226
243
|
writeOwnedGlobalOpenCodeFile(join(paths.skills, skillName, "SKILL.md"), content, marker, "after-frontmatter");
|
|
227
244
|
}
|
|
228
245
|
|
|
@@ -231,6 +248,7 @@ export function installOpenCodeGlobalSurface(configDir) {
|
|
|
231
248
|
runtimeContract: paths.runtimeContract,
|
|
232
249
|
contracts: paths.contracts,
|
|
233
250
|
skills: paths.skills,
|
|
251
|
+
localValidator: paths.localValidator,
|
|
234
252
|
};
|
|
235
253
|
}
|
|
236
254
|
|
|
@@ -243,11 +261,6 @@ function readOpenCodeConfig(configPath) {
|
|
|
243
261
|
}
|
|
244
262
|
}
|
|
245
263
|
|
|
246
|
-
function isOpenCodeRepositorySurfacePresent(targetDir) {
|
|
247
|
-
return existsSync(join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName))
|
|
248
|
-
&& existsSync(join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md"));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
264
|
function resolveOpenCodePackage(configDir) {
|
|
252
265
|
const packageName = pluginDefinition.opencode.npmPackage;
|
|
253
266
|
try {
|
|
@@ -313,6 +326,12 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
313
326
|
const paths = globalOpenCodeConfigPaths(configDir);
|
|
314
327
|
const skillCount = globalOpenCodeSkillNames.filter((skillName) => existsSync(join(paths.skills, skillName, "SKILL.md"))).length;
|
|
315
328
|
const contractCount = contractModules.filter((moduleName) => existsSync(join(paths.contracts, moduleName))).length;
|
|
329
|
+
const validatorResolution = resolveLocalValidator({
|
|
330
|
+
ownedPackageRoot: join(configDir, "node_modules", pluginDefinition.opencode.npmPackage),
|
|
331
|
+
runtimeRoot: join(configDir, "agdf"),
|
|
332
|
+
expectedVersion: pluginDefinition.version,
|
|
333
|
+
surface: "opencode",
|
|
334
|
+
});
|
|
316
335
|
return {
|
|
317
336
|
path: paths.skills,
|
|
318
337
|
instructions: paths.instructions,
|
|
@@ -322,11 +341,18 @@ function evaluateGlobalOpenCodeSurface(configDir) {
|
|
|
322
341
|
skill_count: skillCount,
|
|
323
342
|
expected_contract_count: contractModules.length,
|
|
324
343
|
contract_count: contractCount,
|
|
344
|
+
local_validator: {
|
|
345
|
+
path: paths.localValidator,
|
|
346
|
+
present: existsSync(paths.localValidator),
|
|
347
|
+
...validatorResolution.envelope,
|
|
348
|
+
},
|
|
325
349
|
present: existsSync(paths.skills),
|
|
326
350
|
complete: existsSync(paths.instructions)
|
|
327
351
|
&& existsSync(paths.runtimeContract)
|
|
328
352
|
&& contractCount === contractModules.length
|
|
329
|
-
&& skillCount === openCodeSkillNames.length
|
|
353
|
+
&& skillCount === openCodeSkillNames.length
|
|
354
|
+
&& existsSync(paths.localValidator)
|
|
355
|
+
&& validatorResolution.envelope.machine_validation === "owned_version_matched",
|
|
330
356
|
};
|
|
331
357
|
}
|
|
332
358
|
|
|
@@ -343,8 +369,9 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
343
369
|
version: process.env.AGDF_PLUGIN_VERSION || "",
|
|
344
370
|
control_dir: process.env.AGDF_CONTROL_DIR || "",
|
|
345
371
|
repository_surface: process.env.AGDF_OPENCODE_REPOSITORY_SURFACE === "1",
|
|
372
|
+
repository_activation: process.env.AGDF_OPENCODE_REPOSITORY_ACTIVATION || "",
|
|
346
373
|
};
|
|
347
|
-
const
|
|
374
|
+
const repositoryActivation = evaluateOpenCodeRepositoryActivation(targetDir);
|
|
348
375
|
const gateCheckSkillPath = join(targetDir, ".opencode", "skills", `${pluginDefinition.opencode.skillPrefix}gate-check`, "SKILL.md");
|
|
349
376
|
|
|
350
377
|
const findings = [];
|
|
@@ -360,7 +387,11 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
360
387
|
);
|
|
361
388
|
}
|
|
362
389
|
if (!sessionSignals.active) findings.push("No active AGDF OpenCode session signal is visible in this process.");
|
|
363
|
-
if (!
|
|
390
|
+
if (!repositoryActivation.active) {
|
|
391
|
+
findings.push(repositoryActivation.state === "invalid_control"
|
|
392
|
+
? "Current repository has invalid AGDF durable control configuration."
|
|
393
|
+
: "Current repository does not contain valid AGDF durable control configuration.");
|
|
394
|
+
}
|
|
364
395
|
|
|
365
396
|
return {
|
|
366
397
|
schema_version: "1",
|
|
@@ -383,21 +414,30 @@ export function evaluateOpenCodeStatus(targetDir, configDir = defaultOpenCodeCon
|
|
|
383
414
|
},
|
|
384
415
|
global_native_surface: globalNativeSurface,
|
|
385
416
|
session: sessionSignals,
|
|
417
|
+
repository_activation: repositoryActivation.state,
|
|
418
|
+
repository_control: {
|
|
419
|
+
path: repositoryActivation.config_path,
|
|
420
|
+
diagnostic: repositoryActivation.diagnostic,
|
|
421
|
+
...(repositoryActivation.error ? { error: repositoryActivation.error } : {}),
|
|
422
|
+
},
|
|
386
423
|
repository_surface: {
|
|
387
424
|
path: targetDir,
|
|
388
|
-
present:
|
|
425
|
+
present: repositoryActivation.active,
|
|
426
|
+
legacy_present: repositoryActivation.legacy_surface,
|
|
389
427
|
instructions: join(targetDir, ".opencode", pluginDefinition.opencode.instructionsFileName),
|
|
390
428
|
gate_check_agent: gateCheckSkillPath,
|
|
391
429
|
gate_check_skill: gateCheckSkillPath,
|
|
392
430
|
},
|
|
393
|
-
visible_entrypoint:
|
|
431
|
+
visible_entrypoint: repositoryActivation.active ? `${pluginDefinition.opencode.globalSkillPrefix}gate-check (native skill)` : "none until durable AGDF control is configured for this repository",
|
|
394
432
|
findings,
|
|
395
433
|
next_step: packageVersionStatus !== "current"
|
|
396
434
|
? "Run npx --yes @agdf/cli@latest opencode to install or repair the OpenCode package version."
|
|
397
435
|
: !globalNativeSurface.complete
|
|
398
436
|
? "Run npx --yes @agdf/cli@latest opencode to install or repair the global native OpenCode skill surface."
|
|
399
|
-
:
|
|
400
|
-
?
|
|
437
|
+
: repositoryActivation.state === "invalid_control"
|
|
438
|
+
? "Repair .agdf/control/config.json so it contains valid artifact_language, chat_language and runtime_language values."
|
|
439
|
+
: repositoryActivation.active
|
|
440
|
+
? `Restart OpenCode if needed, then load ${pluginDefinition.opencode.globalSkillPrefix}gate-check through the native skill tool for new build/change intent.`
|
|
401
441
|
: "Run npx --yes @agdf/cli@latest opencode-repo in repositories where AGDF governance should be active and reviewable.",
|
|
402
442
|
};
|
|
403
443
|
}
|
|
@@ -431,7 +471,9 @@ export function printOpenCodeStatus(report, json, io = console) {
|
|
|
431
471
|
io.log(`Session active signal: ${report.session.active ? "yes" : "no"}`);
|
|
432
472
|
if (report.session.version) io.log(`Session plugin version: ${report.session.version}`);
|
|
433
473
|
if (report.session.control_dir) io.log(`Session control dir: ${report.session.control_dir}`);
|
|
434
|
-
io.log(`Repository
|
|
474
|
+
io.log(`Repository activation: ${report.repository_activation}`);
|
|
475
|
+
io.log(`Repository control: ${report.repository_control.path} (${report.repository_control.diagnostic})`);
|
|
476
|
+
io.log(`Legacy repository surface: ${report.repository_surface.legacy_present ? "present" : "absent"}`);
|
|
435
477
|
io.log(`Visible entrypoint: ${report.visible_entrypoint}`);
|
|
436
478
|
io.log(`Next step: ${report.next_step}`);
|
|
437
479
|
|