@pieai/pro-gov 0.3.3 → 0.3.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 +80 -15
- package/assets/docs/reference/adoption/adoption-playbook.md +42 -6
- package/assets/docs/reference/adoption/downstream-project-registry.md +24 -17
- package/assets/docs/reference/adoption/project-relationship.md +9 -8
- package/assets/docs/reference/adoption/public-release-checklist.md +34 -29
- package/assets/docs/reference/adoption/recommended-agent-tooling.md +106 -0
- package/assets/docs/reference/adoption/site-publication-brief.md +22 -14
- package/assets/integrations/ponytail.md +109 -0
- package/assets/integrations/superpowers.md +30 -2
- package/assets/starter/.gemini/settings.json +5 -0
- package/assets/starter/.github/workflows/docs-check.yml +4 -2
- package/assets/starter/docs/governance/agents-routing/doc-only-v0.9.md +3 -2
- package/assets/starter/docs/governance/agents-routing/engineering-runtime-v0.9.md +3 -2
- package/assets/starter/docs/governance/ssot-v0.9.md +2 -2
- package/assets/starter/lefthook.template.yml +2 -2
- package/cli-guide.md +25 -1
- package/dist/cli.js +1444 -18
- package/package.json +11 -10
package/dist/cli.js
CHANGED
|
@@ -30,6 +30,7 @@ function isValidProfile(profile) {
|
|
|
30
30
|
function listFiles(dir) {
|
|
31
31
|
const files = [];
|
|
32
32
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
33
|
+
if (isPlatformMetadata(entry.name)) continue;
|
|
33
34
|
const absolutePath = join(dir, entry.name);
|
|
34
35
|
if (entry.isDirectory()) {
|
|
35
36
|
files.push(...listFiles(absolutePath));
|
|
@@ -42,27 +43,1194 @@ function listFiles(dir) {
|
|
|
42
43
|
function toUnixPath(path) {
|
|
43
44
|
return path.replaceAll("\\", "/");
|
|
44
45
|
}
|
|
46
|
+
function isPlatformMetadata(name) {
|
|
47
|
+
return name === ".DS_Store" || name === "Thumbs.db" || name.startsWith("._");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/commands/assets.ts
|
|
51
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
52
|
+
import { dirname as dirname5 } from "node:path";
|
|
53
|
+
|
|
54
|
+
// src/asset-bundles/bundles.ts
|
|
55
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync } from "node:fs";
|
|
56
|
+
import { join as join2 } from "node:path";
|
|
57
|
+
function loadAgentAssetBundles(agentAssetsDir) {
|
|
58
|
+
const bundlesDir = join2(agentAssetsDir, "bundles");
|
|
59
|
+
if (!existsSync2(bundlesDir)) return [];
|
|
60
|
+
return readdirSync2(bundlesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => {
|
|
61
|
+
const bundlePath = join2(bundlesDir, entry.name);
|
|
62
|
+
return JSON.parse(readFileSync(bundlePath, "utf8"));
|
|
63
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/asset-npx/maintenance.ts
|
|
67
|
+
import { createHash } from "node:crypto";
|
|
68
|
+
import { spawnSync } from "node:child_process";
|
|
69
|
+
import { cpSync, existsSync as existsSync3, mkdirSync, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
70
|
+
import { join as join3, relative as relative2 } from "node:path";
|
|
71
|
+
import { tmpdir } from "node:os";
|
|
72
|
+
function createNpxSkillsMaintenancePlan(options) {
|
|
73
|
+
assertNativeNpxRoot(options.npxRoot);
|
|
74
|
+
if (options.operation === "add" && !options.source) {
|
|
75
|
+
throw new Error("npx skills add requires a source.");
|
|
76
|
+
}
|
|
77
|
+
const before = snapshotFiles(options.npxRoot);
|
|
78
|
+
const tempRoot = mkdtempSync(join3(tmpdir(), "pro-gov-npx-skills-"));
|
|
79
|
+
cpSync(options.npxRoot, tempRoot, { recursive: true, dereference: false });
|
|
80
|
+
const command2 = buildNpxCommand(options);
|
|
81
|
+
const runner = options.runner ?? defaultRunner;
|
|
82
|
+
const result = runner({ command: command2, cwd: tempRoot });
|
|
83
|
+
if (result.status !== 0) {
|
|
84
|
+
throw new Error(`npx skills ${options.operation} failed with exit code ${result.status}`);
|
|
85
|
+
}
|
|
86
|
+
if (options.operation === "update") {
|
|
87
|
+
assertNoReportedPartialUpdateFailure(result.stdout, result.stderr);
|
|
88
|
+
}
|
|
89
|
+
const after = snapshotFiles(tempRoot);
|
|
90
|
+
const changes = diffSnapshots(before, after);
|
|
91
|
+
return {
|
|
92
|
+
schemaVersion: 1,
|
|
93
|
+
operation: options.operation,
|
|
94
|
+
npxRoot: options.npxRoot,
|
|
95
|
+
tempRoot,
|
|
96
|
+
command: command2,
|
|
97
|
+
stdout: result.stdout,
|
|
98
|
+
stderr: result.stderr,
|
|
99
|
+
exitCode: result.status ?? 0,
|
|
100
|
+
changes,
|
|
101
|
+
summary: summarizeChanges(changes),
|
|
102
|
+
appliedToRealRoot: false
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function assertNoReportedPartialUpdateFailure(stdout, stderr) {
|
|
106
|
+
const output = `${stdout}
|
|
107
|
+
${stderr}`.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
108
|
+
const failure = output.match(/Failed to update\s+\d+\s+skill\(s\)/i);
|
|
109
|
+
if (failure) {
|
|
110
|
+
throw new Error(`npx skills update reported a partial failure: ${failure[0]}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function assertNativeNpxRoot(npxRoot) {
|
|
114
|
+
if (!existsSync3(join3(npxRoot, "skills-lock.json"))) {
|
|
115
|
+
throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync3(join3(npxRoot, ".agents/skills"))) {
|
|
118
|
+
throw new Error(`npx skills root is missing .agents/skills: ${npxRoot}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function buildNpxCommand(options) {
|
|
122
|
+
if (options.operation === "add") {
|
|
123
|
+
const command3 = ["npx", "--yes", "skills", "add", options.source ?? ""];
|
|
124
|
+
if (options.skill) command3.push("--skill", options.skill);
|
|
125
|
+
return command3;
|
|
126
|
+
}
|
|
127
|
+
const command2 = ["npx", "--yes", "skills", "update", "-p", "-y"];
|
|
128
|
+
if (options.skill) command2.push(options.skill);
|
|
129
|
+
return command2;
|
|
130
|
+
}
|
|
131
|
+
function defaultRunner({ command: command2, cwd }) {
|
|
132
|
+
const result = spawnSync(command2[0] ?? "npx", command2.slice(1), { cwd, encoding: "utf8" });
|
|
133
|
+
return {
|
|
134
|
+
status: result.status,
|
|
135
|
+
stdout: result.stdout,
|
|
136
|
+
stderr: result.stderr
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function snapshotFiles(root) {
|
|
140
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const filePath of listFiles2(root)) {
|
|
142
|
+
const relativePath = toUnixPath2(relative2(root, filePath));
|
|
143
|
+
snapshot.set(relativePath, hashFile(filePath));
|
|
144
|
+
}
|
|
145
|
+
return snapshot;
|
|
146
|
+
}
|
|
147
|
+
function listFiles2(root) {
|
|
148
|
+
const files = [];
|
|
149
|
+
collectFiles(root, root, files);
|
|
150
|
+
return files.sort();
|
|
151
|
+
}
|
|
152
|
+
function collectFiles(root, current, files) {
|
|
153
|
+
mkdirSync(root, { recursive: true });
|
|
154
|
+
for (const entry of readdirSync3(current, { withFileTypes: true })) {
|
|
155
|
+
const entryPath = join3(current, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
collectFiles(root, entryPath, files);
|
|
158
|
+
} else if (entry.isFile()) {
|
|
159
|
+
files.push(entryPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function hashFile(path) {
|
|
164
|
+
const hash = createHash("sha256");
|
|
165
|
+
const stats = statSync(path);
|
|
166
|
+
hash.update(String(stats.size));
|
|
167
|
+
hash.update("\0");
|
|
168
|
+
hash.update(readFileSync2(path));
|
|
169
|
+
return hash.digest("hex");
|
|
170
|
+
}
|
|
171
|
+
function diffSnapshots(before, after) {
|
|
172
|
+
const changes = [];
|
|
173
|
+
for (const [path, hash] of after) {
|
|
174
|
+
if (!before.has(path)) {
|
|
175
|
+
changes.push({ type: "added", path });
|
|
176
|
+
} else if (before.get(path) !== hash) {
|
|
177
|
+
changes.push({ type: "modified", path });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const path of before.keys()) {
|
|
181
|
+
if (!after.has(path)) changes.push({ type: "deleted", path });
|
|
182
|
+
}
|
|
183
|
+
return changes.sort((a, b) => a.path.localeCompare(b.path) || a.type.localeCompare(b.type));
|
|
184
|
+
}
|
|
185
|
+
function summarizeChanges(changes) {
|
|
186
|
+
const counts = /* @__PURE__ */ new Map();
|
|
187
|
+
for (const change of changes) counts.set(change.type, (counts.get(change.type) ?? 0) + 1);
|
|
188
|
+
return `added=${counts.get("added") ?? 0} modified=${counts.get("modified") ?? 0} deleted=${counts.get("deleted") ?? 0}`;
|
|
189
|
+
}
|
|
190
|
+
function toUnixPath2(path) {
|
|
191
|
+
return path.replaceAll("\\", "/");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/asset-registry/loader.ts
|
|
195
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
196
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
197
|
+
import { dirname as dirname2, join as join5, relative as relative3 } from "node:path";
|
|
198
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
199
|
+
|
|
200
|
+
// src/asset-registry/registry.ts
|
|
201
|
+
import { existsSync as existsSync4, lstatSync } from "node:fs";
|
|
202
|
+
import { isAbsolute, join as join4, posix } from "node:path";
|
|
203
|
+
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
204
|
+
"pie-skills",
|
|
205
|
+
"dokobot",
|
|
206
|
+
"npx-skills",
|
|
207
|
+
"pie-rules",
|
|
208
|
+
"pie-commands"
|
|
209
|
+
]);
|
|
210
|
+
var supportedKinds = /* @__PURE__ */ new Set(["skill", "rule", "command"]);
|
|
211
|
+
var supportedVisibilities = /* @__PURE__ */ new Set([
|
|
212
|
+
"public",
|
|
213
|
+
"private",
|
|
214
|
+
"third-party"
|
|
215
|
+
]);
|
|
216
|
+
var supportedSourceKinds = /* @__PURE__ */ new Set(["local", "local-pack", "npx"]);
|
|
217
|
+
var supportedHosts = /* @__PURE__ */ new Set([
|
|
218
|
+
"codex",
|
|
219
|
+
"claude-code",
|
|
220
|
+
"gemini-cli",
|
|
221
|
+
"antigravity"
|
|
222
|
+
]);
|
|
223
|
+
function validateAssetRegistry(registry, options = {}) {
|
|
224
|
+
const issues = [];
|
|
225
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
226
|
+
for (const asset of registry.assets) {
|
|
227
|
+
if (seenIds.has(asset.id)) {
|
|
228
|
+
issues.push({
|
|
229
|
+
type: "duplicate-id",
|
|
230
|
+
id: asset.id,
|
|
231
|
+
message: `Duplicate asset id: ${asset.id}`
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
seenIds.add(asset.id);
|
|
235
|
+
if (!supportedFamilies.has(asset.family)) {
|
|
236
|
+
issues.push({
|
|
237
|
+
type: "unsupported-enum",
|
|
238
|
+
id: asset.id,
|
|
239
|
+
message: `Unsupported asset family: ${asset.family}`
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (!supportedKinds.has(asset.kind)) {
|
|
243
|
+
issues.push({
|
|
244
|
+
type: "unsupported-enum",
|
|
245
|
+
id: asset.id,
|
|
246
|
+
message: `Unsupported asset kind: ${asset.kind}`
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (!supportedVisibilities.has(asset.visibility)) {
|
|
250
|
+
issues.push({
|
|
251
|
+
type: "unsupported-enum",
|
|
252
|
+
id: asset.id,
|
|
253
|
+
message: `Unsupported asset visibility: ${asset.visibility}`
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
if (!supportedSourceKinds.has(asset.sourceKind)) {
|
|
257
|
+
issues.push({
|
|
258
|
+
type: "unsupported-enum",
|
|
259
|
+
id: asset.id,
|
|
260
|
+
message: `Unsupported asset source kind: ${asset.sourceKind}`
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
for (const host of asset.hosts) {
|
|
264
|
+
if (!supportedHosts.has(host)) {
|
|
265
|
+
issues.push({
|
|
266
|
+
type: "unsupported-host",
|
|
267
|
+
id: asset.id,
|
|
268
|
+
message: `Unsupported asset host: ${host}`
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
273
|
+
issues.push({
|
|
274
|
+
type: "unsafe-source-path",
|
|
275
|
+
id: asset.id,
|
|
276
|
+
path: asset.sourcePath,
|
|
277
|
+
message: `Asset source path escapes agent-assets: ${asset.sourcePath}`
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (asset.visibility !== "public" && asset.publishable) {
|
|
281
|
+
issues.push({
|
|
282
|
+
type: "non-public-publishable",
|
|
283
|
+
id: asset.id,
|
|
284
|
+
message: `Only public assets may be publishable: ${asset.id}`
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (options.agentAssetsDir && isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
288
|
+
const sourceAbsolutePath = join4(options.agentAssetsDir, normalizeRegistrySourcePath(asset.sourcePath));
|
|
289
|
+
if (!existsSync4(sourceAbsolutePath)) {
|
|
290
|
+
issues.push({
|
|
291
|
+
type: "missing-source-path",
|
|
292
|
+
id: asset.id,
|
|
293
|
+
path: asset.sourcePath,
|
|
294
|
+
message: `Asset source path does not exist: ${asset.sourcePath}`
|
|
295
|
+
});
|
|
296
|
+
} else if (asset.kind === "skill" && !existsSync4(join4(sourceAbsolutePath, "SKILL.md"))) {
|
|
297
|
+
issues.push({
|
|
298
|
+
type: "missing-skill-file",
|
|
299
|
+
id: asset.id,
|
|
300
|
+
path: asset.sourcePath,
|
|
301
|
+
message: `Skill asset is missing SKILL.md: ${asset.sourcePath}`
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (options.agentAssetsDir) {
|
|
307
|
+
const npxCompatibilityLayer = join4(options.agentAssetsDir, "skills/npx-skills/skills");
|
|
308
|
+
if (pathExistsEvenIfDanglingSymlink(npxCompatibilityLayer)) {
|
|
309
|
+
issues.push({
|
|
310
|
+
type: "internal-npx-compatibility-layer",
|
|
311
|
+
id: "npx-skills",
|
|
312
|
+
path: "skills/npx-skills/skills",
|
|
313
|
+
message: "Do not create an internal npx compatibility symlink layer."
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return issues;
|
|
318
|
+
}
|
|
319
|
+
function isSafeRegistrySourcePath(sourcePath) {
|
|
320
|
+
if (!sourcePath || isAbsolute(sourcePath) || sourcePath.startsWith("/")) return false;
|
|
321
|
+
const normalized = normalizeRegistrySourcePath(sourcePath);
|
|
322
|
+
if (normalized === "." || normalized.startsWith("../") || normalized === "..") return false;
|
|
323
|
+
return !normalized.split("/").includes("..");
|
|
324
|
+
}
|
|
325
|
+
function normalizeRegistrySourcePath(sourcePath) {
|
|
326
|
+
return posix.normalize(sourcePath.replaceAll("\\", "/"));
|
|
327
|
+
}
|
|
328
|
+
function pathExistsEvenIfDanglingSymlink(path) {
|
|
329
|
+
try {
|
|
330
|
+
lstatSync(path);
|
|
331
|
+
return true;
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/asset-registry/loader.ts
|
|
338
|
+
function loadAgentAssetRegistry(options = {}) {
|
|
339
|
+
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
340
|
+
const registryPath = join5(agentAssetsDir, "registry.json");
|
|
341
|
+
if (!existsSync5(registryPath)) {
|
|
342
|
+
return {
|
|
343
|
+
registry: { schemaVersion: 1, assets: [] },
|
|
344
|
+
agentAssetsDir,
|
|
345
|
+
registryPath,
|
|
346
|
+
issues: []
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const registry = JSON.parse(readFileSync3(registryPath, "utf8"));
|
|
350
|
+
return {
|
|
351
|
+
registry,
|
|
352
|
+
agentAssetsDir,
|
|
353
|
+
registryPath,
|
|
354
|
+
issues: validateAssetRegistry(registry, { agentAssetsDir })
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
358
|
+
const wantedIds = assetIds ? new Set(assetIds) : void 0;
|
|
359
|
+
return registry.assets.filter((asset) => !wantedIds || wantedIds.has(asset.id)).map((asset) => ({
|
|
360
|
+
id: asset.id,
|
|
361
|
+
sourcePath: asset.sourcePath,
|
|
362
|
+
contentHash: hashAgentAssetContent(asset, agentAssetsDir)
|
|
363
|
+
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
364
|
+
}
|
|
365
|
+
function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
366
|
+
const sourceAbsolutePath = join5(agentAssetsDir, asset.sourcePath);
|
|
367
|
+
const hash = createHash2("sha256");
|
|
368
|
+
for (const filePath of listFiles3(sourceAbsolutePath)) {
|
|
369
|
+
const relativePath = toUnixPath3(relative3(sourceAbsolutePath, filePath));
|
|
370
|
+
hash.update(relativePath);
|
|
371
|
+
hash.update("\0");
|
|
372
|
+
hash.update(readFileSync3(filePath));
|
|
373
|
+
hash.update("\0");
|
|
374
|
+
}
|
|
375
|
+
return `sha256:${hash.digest("hex")}`;
|
|
376
|
+
}
|
|
377
|
+
function findDefaultAgentAssetsDir() {
|
|
378
|
+
const packageRoot2 = findPackageRoot(dirname2(fileURLToPath2(import.meta.url)));
|
|
379
|
+
const repoRoot = join5(packageRoot2, "..", "..");
|
|
380
|
+
const candidates = [join5(packageRoot2, "assets/agent-assets"), join5(repoRoot, "agent-assets")];
|
|
381
|
+
return candidates.find((candidate) => existsSync5(join5(candidate, "registry.json"))) ?? candidates[0];
|
|
382
|
+
}
|
|
383
|
+
function findPackageRoot(startDir) {
|
|
384
|
+
let current = startDir;
|
|
385
|
+
while (current !== dirname2(current)) {
|
|
386
|
+
const packageJsonPath = join5(current, "package.json");
|
|
387
|
+
if (existsSync5(packageJsonPath)) {
|
|
388
|
+
try {
|
|
389
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
|
|
390
|
+
if (packageJson.name === "@pieai/pro-gov") return current;
|
|
391
|
+
} catch {
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
current = dirname2(current);
|
|
395
|
+
}
|
|
396
|
+
return startDir;
|
|
397
|
+
}
|
|
398
|
+
function listFiles3(absolutePath) {
|
|
399
|
+
const stats = statSync2(absolutePath);
|
|
400
|
+
if (stats.isFile()) return [absolutePath];
|
|
401
|
+
const files = [];
|
|
402
|
+
for (const entry of readdirSync4(absolutePath, { withFileTypes: true })) {
|
|
403
|
+
const entryPath = join5(absolutePath, entry.name);
|
|
404
|
+
if (entry.isDirectory()) {
|
|
405
|
+
files.push(...listFiles3(entryPath));
|
|
406
|
+
} else if (entry.isFile()) {
|
|
407
|
+
files.push(entryPath);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return files.sort();
|
|
411
|
+
}
|
|
412
|
+
function toUnixPath3(path) {
|
|
413
|
+
return path.replaceAll("\\", "/");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/asset-targets/apply.ts
|
|
417
|
+
import { existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync2, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
418
|
+
import { dirname as dirname3, join as join6, resolve } from "node:path";
|
|
419
|
+
function applyAssetInstallPlan(plan) {
|
|
420
|
+
const appliedActions = [];
|
|
421
|
+
for (const action of plan.actions) {
|
|
422
|
+
applyAction(plan.targetDir, action);
|
|
423
|
+
appliedActions.push(action.type);
|
|
424
|
+
}
|
|
425
|
+
return { appliedActions };
|
|
426
|
+
}
|
|
427
|
+
function applyAction(targetDir, action) {
|
|
428
|
+
const targetAbsolutePath = join6(targetDir, action.targetPath);
|
|
429
|
+
if (action.type === "create-dir") {
|
|
430
|
+
mkdirSync2(targetAbsolutePath, { recursive: true });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (action.type === "write-file") {
|
|
434
|
+
mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
|
|
435
|
+
writeFileSync(targetAbsolutePath, action.content);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
mkdirSync2(dirname3(targetAbsolutePath), { recursive: true });
|
|
439
|
+
const sourceAbsolutePath = resolve(action.sourcePath);
|
|
440
|
+
if (action.type === "symlink") {
|
|
441
|
+
if (pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
442
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
443
|
+
}
|
|
444
|
+
symlinkSync(sourceAbsolutePath, targetAbsolutePath);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) {
|
|
448
|
+
symlinkSync(sourceAbsolutePath, targetAbsolutePath);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const stats = lstatSync2(targetAbsolutePath);
|
|
452
|
+
if (!stats.isSymbolicLink()) {
|
|
453
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
454
|
+
}
|
|
455
|
+
unlinkSync(targetAbsolutePath);
|
|
456
|
+
symlinkSync(sourceAbsolutePath, targetAbsolutePath);
|
|
457
|
+
}
|
|
458
|
+
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
459
|
+
try {
|
|
460
|
+
lstatSync2(path);
|
|
461
|
+
return true;
|
|
462
|
+
} catch {
|
|
463
|
+
return existsSync6(path);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/asset-targets/check.ts
|
|
468
|
+
import { existsSync as existsSync7, lstatSync as lstatSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
469
|
+
import { join as join7 } from "node:path";
|
|
470
|
+
function checkInstalledAssets(options) {
|
|
471
|
+
const lockfilePath = join7(options.targetDir, ".pro-gov/assets.lock.json");
|
|
472
|
+
if (!existsSync7(lockfilePath)) {
|
|
473
|
+
return {
|
|
474
|
+
targetDir: options.targetDir,
|
|
475
|
+
issues: [
|
|
476
|
+
{
|
|
477
|
+
type: "missing-lock",
|
|
478
|
+
message: "Missing .pro-gov/assets.lock.json"
|
|
479
|
+
}
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
484
|
+
const lockfile = JSON.parse(readFileSync4(lockfilePath, "utf8"));
|
|
485
|
+
const issues = [];
|
|
486
|
+
for (const entry of lockfile.assets ?? []) {
|
|
487
|
+
const asset = registryById.get(entry.id);
|
|
488
|
+
const targetAbsolutePath = join7(options.targetDir, entry.targetPath);
|
|
489
|
+
const sourceAbsolutePath = join7(options.agentAssetsDir, entry.sourcePath);
|
|
490
|
+
if (!asset) {
|
|
491
|
+
issues.push({
|
|
492
|
+
type: "unknown-asset",
|
|
493
|
+
id: entry.id,
|
|
494
|
+
targetPath: entry.targetPath,
|
|
495
|
+
message: `Lockfile references unknown asset: ${entry.id}`
|
|
496
|
+
});
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const hostFolderIssue = checkHostFolder(lockfile.host, asset.kind, entry.targetPath, entry.id);
|
|
500
|
+
if (hostFolderIssue) {
|
|
501
|
+
issues.push(hostFolderIssue);
|
|
502
|
+
}
|
|
503
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
504
|
+
issues.push({
|
|
505
|
+
type: "missing-target",
|
|
506
|
+
id: entry.id,
|
|
507
|
+
targetPath: entry.targetPath,
|
|
508
|
+
message: `Managed target is missing: ${entry.targetPath}`
|
|
509
|
+
});
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const targetStats = lstatSync3(targetAbsolutePath);
|
|
513
|
+
if (!targetStats.isSymbolicLink()) {
|
|
514
|
+
issues.push({
|
|
515
|
+
type: "unmanaged-conflict",
|
|
516
|
+
id: entry.id,
|
|
517
|
+
targetPath: entry.targetPath,
|
|
518
|
+
message: `Managed target is not a symlink: ${entry.targetPath}`
|
|
519
|
+
});
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
if (!existsSync7(targetAbsolutePath)) {
|
|
523
|
+
issues.push({
|
|
524
|
+
type: "dangling-symlink",
|
|
525
|
+
id: entry.id,
|
|
526
|
+
targetPath: entry.targetPath,
|
|
527
|
+
message: `Managed symlink is dangling: ${entry.targetPath}`
|
|
528
|
+
});
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (!existsSync7(sourceAbsolutePath)) {
|
|
532
|
+
issues.push({
|
|
533
|
+
type: "missing-source",
|
|
534
|
+
id: entry.id,
|
|
535
|
+
targetPath: entry.targetPath,
|
|
536
|
+
message: `Managed asset source is missing: ${entry.sourcePath}`
|
|
537
|
+
});
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const currentHash = hashAgentAssetContent(asset, options.agentAssetsDir);
|
|
541
|
+
if (currentHash !== entry.contentHash) {
|
|
542
|
+
issues.push({
|
|
543
|
+
type: "hash-drift",
|
|
544
|
+
id: entry.id,
|
|
545
|
+
targetPath: entry.targetPath,
|
|
546
|
+
message: `Managed asset hash drifted: ${entry.id}`
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return { targetDir: options.targetDir, issues };
|
|
551
|
+
}
|
|
552
|
+
function checkHostFolder(host, kind, targetPath, id) {
|
|
553
|
+
if (kind !== "skill") return void 0;
|
|
554
|
+
const expectedPrefix = expectedSkillTargetPrefix(host);
|
|
555
|
+
if (!expectedPrefix) {
|
|
556
|
+
return {
|
|
557
|
+
type: "unsupported-host-folder",
|
|
558
|
+
id,
|
|
559
|
+
targetPath,
|
|
560
|
+
message: `Lockfile host is unsupported for managed skill target: ${host ?? "missing"}`
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
if (!targetPath.startsWith(expectedPrefix)) {
|
|
564
|
+
return {
|
|
565
|
+
type: "unsupported-host-folder",
|
|
566
|
+
id,
|
|
567
|
+
targetPath,
|
|
568
|
+
message: `Managed skill target ${targetPath} does not match host ${host}; expected ${expectedPrefix}`
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return void 0;
|
|
572
|
+
}
|
|
573
|
+
function expectedSkillTargetPrefix(host) {
|
|
574
|
+
if (host === "claude-code") return ".claude/skills/";
|
|
575
|
+
if (host === "codex" || host === "gemini-cli" || host === "antigravity") {
|
|
576
|
+
return ".agents/skills/";
|
|
577
|
+
}
|
|
578
|
+
return void 0;
|
|
579
|
+
}
|
|
580
|
+
function pathExistsEvenIfDanglingSymlink3(path) {
|
|
581
|
+
try {
|
|
582
|
+
lstatSync3(path);
|
|
583
|
+
return true;
|
|
584
|
+
} catch {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/asset-targets/install-plan.ts
|
|
590
|
+
import { existsSync as existsSync8, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
591
|
+
import { basename, dirname as dirname4, join as join8 } from "node:path";
|
|
592
|
+
function createAssetInstallPlan(options) {
|
|
593
|
+
const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
594
|
+
const bundlesById = new Map(options.bundles.map((bundle) => [bundle.id, bundle]));
|
|
595
|
+
const assetIds = resolveBundleAssetIds(options.bundleIds, bundlesById);
|
|
596
|
+
const assets = assetIds.map((assetId) => {
|
|
597
|
+
const asset = assetsById.get(assetId);
|
|
598
|
+
if (!asset) throw new Error(`Unknown asset id in bundle: ${assetId}`);
|
|
599
|
+
if (asset.kind === "skill" && !asset.hosts.includes(options.host)) {
|
|
600
|
+
throw new Error(`Asset ${assetId} does not support host ${options.host}`);
|
|
601
|
+
}
|
|
602
|
+
return asset;
|
|
603
|
+
});
|
|
604
|
+
const lockEntries = createAgentAssetLockEntries(options.registry, options.agentAssetsDir, assetIds);
|
|
605
|
+
const managedTargets = readManagedTargets(options.targetDir);
|
|
606
|
+
const assetActions = assets.map(
|
|
607
|
+
(asset) => createAssetAction(asset, options.agentAssetsDir, options.targetDir, options.host, managedTargets)
|
|
608
|
+
);
|
|
609
|
+
const manifest = {
|
|
610
|
+
schemaVersion: 1,
|
|
611
|
+
host: options.host,
|
|
612
|
+
bundleIds: [...options.bundleIds],
|
|
613
|
+
assetIds
|
|
614
|
+
};
|
|
615
|
+
const lockfile = {
|
|
616
|
+
schemaVersion: 1,
|
|
617
|
+
host: options.host,
|
|
618
|
+
bundleIds: [...options.bundleIds],
|
|
619
|
+
assets: lockEntries.map((entry) => {
|
|
620
|
+
const action = assetActions.find((candidate) => "assetId" in candidate && candidate.assetId === entry.id);
|
|
621
|
+
return {
|
|
622
|
+
...entry,
|
|
623
|
+
targetPath: action && "targetPath" in action ? action.targetPath : ""
|
|
624
|
+
};
|
|
625
|
+
})
|
|
626
|
+
};
|
|
627
|
+
const writeActions = [
|
|
628
|
+
{
|
|
629
|
+
type: "write-file",
|
|
630
|
+
targetPath: ".pro-gov/assets.json",
|
|
631
|
+
content: `${JSON.stringify(manifest, null, 2)}
|
|
632
|
+
`
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
type: "write-file",
|
|
636
|
+
targetPath: ".pro-gov/assets.lock.json",
|
|
637
|
+
content: `${JSON.stringify(lockfile, null, 2)}
|
|
638
|
+
`
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
return {
|
|
642
|
+
schemaVersion: 1,
|
|
643
|
+
dryRun: true,
|
|
644
|
+
targetDir: options.targetDir,
|
|
645
|
+
host: options.host,
|
|
646
|
+
bundleIds: [...options.bundleIds],
|
|
647
|
+
assetIds,
|
|
648
|
+
actions: [...createDirectoryActions([...assetActions, ...writeActions]), ...assetActions, ...writeActions]
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function resolveBundleAssetIds(bundleIds, bundlesById) {
|
|
652
|
+
const ids = /* @__PURE__ */ new Set();
|
|
653
|
+
for (const bundleId of bundleIds) {
|
|
654
|
+
const bundle = bundlesById.get(bundleId);
|
|
655
|
+
if (!bundle) throw new Error(`Unknown bundle id: ${bundleId}`);
|
|
656
|
+
for (const assetId of bundle.assets) {
|
|
657
|
+
ids.add(assetId);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return [...ids].sort();
|
|
661
|
+
}
|
|
662
|
+
function createAssetAction(asset, agentAssetsDir, targetDir, host, managedTargets) {
|
|
663
|
+
const sourcePath = join8(agentAssetsDir, asset.sourcePath);
|
|
664
|
+
const targetPath = resolveHostTargetPath(asset, host);
|
|
665
|
+
const targetAbsolutePath = join8(targetDir, targetPath);
|
|
666
|
+
const targetExists = pathExistsEvenIfDanglingSymlink4(targetAbsolutePath);
|
|
667
|
+
if (targetExists) {
|
|
668
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
669
|
+
if (stats.isSymbolicLink() && managedTargets.has(targetPath)) {
|
|
670
|
+
return {
|
|
671
|
+
type: "update-symlink",
|
|
672
|
+
assetId: asset.id,
|
|
673
|
+
sourcePath,
|
|
674
|
+
targetPath
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
throw new Error(`Refusing to overwrite unmanaged target: ${targetPath}`);
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
type: "symlink",
|
|
681
|
+
assetId: asset.id,
|
|
682
|
+
sourcePath,
|
|
683
|
+
targetPath
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function resolveHostTargetPath(asset, host) {
|
|
687
|
+
if (asset.kind === "skill") {
|
|
688
|
+
if (host === "claude-code") {
|
|
689
|
+
return `.claude/skills/${basename(asset.sourcePath)}`;
|
|
690
|
+
}
|
|
691
|
+
return `.agents/skills/${basename(asset.sourcePath)}`;
|
|
692
|
+
}
|
|
693
|
+
if (asset.kind === "rule") {
|
|
694
|
+
return `.pro-gov/agent-assets/rules/${basename(asset.sourcePath)}`;
|
|
695
|
+
}
|
|
696
|
+
return `.pro-gov/agent-assets/commands/${basename(asset.sourcePath)}`;
|
|
697
|
+
}
|
|
698
|
+
function createDirectoryActions(actions) {
|
|
699
|
+
const directories = /* @__PURE__ */ new Set();
|
|
700
|
+
for (const action of actions) {
|
|
701
|
+
if (action.type === "create-dir") continue;
|
|
702
|
+
const directory = dirname4(action.targetPath);
|
|
703
|
+
if (directory !== ".") directories.add(directory);
|
|
704
|
+
}
|
|
705
|
+
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
706
|
+
}
|
|
707
|
+
function readManagedTargets(targetDir) {
|
|
708
|
+
const lockfilePath = join8(targetDir, ".pro-gov/assets.lock.json");
|
|
709
|
+
if (!existsSync8(lockfilePath)) return /* @__PURE__ */ new Set();
|
|
710
|
+
try {
|
|
711
|
+
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
712
|
+
return new Set((lockfile.assets ?? []).map((asset) => asset.targetPath).filter(Boolean));
|
|
713
|
+
} catch {
|
|
714
|
+
return /* @__PURE__ */ new Set();
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function pathExistsEvenIfDanglingSymlink4(path) {
|
|
718
|
+
try {
|
|
719
|
+
lstatSync4(path);
|
|
720
|
+
return true;
|
|
721
|
+
} catch {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// src/asset-targets/recommend.ts
|
|
727
|
+
import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
728
|
+
import { join as join9 } from "node:path";
|
|
729
|
+
var frontendPackages = /* @__PURE__ */ new Set([
|
|
730
|
+
"@vitejs/plugin-react",
|
|
731
|
+
"astro",
|
|
732
|
+
"next",
|
|
733
|
+
"nuxt",
|
|
734
|
+
"react",
|
|
735
|
+
"svelte",
|
|
736
|
+
"tailwindcss",
|
|
737
|
+
"vite",
|
|
738
|
+
"vue"
|
|
739
|
+
]);
|
|
740
|
+
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md", ".gemini/settings.json", "GEMINI.md"];
|
|
741
|
+
function discoverTargetSignals(targetDir) {
|
|
742
|
+
const packageJson = readJson(join9(targetDir, "package.json"));
|
|
743
|
+
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
744
|
+
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
745
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync9(join9(targetDir, file)));
|
|
746
|
+
const researchSignals = [
|
|
747
|
+
existsSync9(join9(targetDir, "docs/research")) ? "docs/research" : "",
|
|
748
|
+
existsSync9(join9(targetDir, "research")) ? "research" : "",
|
|
749
|
+
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
750
|
+
textFileIncludes(join9(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
751
|
+
].filter(Boolean);
|
|
752
|
+
const writingSignals = [
|
|
753
|
+
existsSync9(join9(targetDir, "chapters")) ? "chapters" : "",
|
|
754
|
+
existsSync9(join9(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
755
|
+
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
756
|
+
textFileIncludes(join9(targetDir, "AGENTS.md"), ["writing mode", "novel chapter", "book content"]) ? "AGENTS writing" : ""
|
|
757
|
+
].filter(Boolean);
|
|
758
|
+
return {
|
|
759
|
+
targetDir,
|
|
760
|
+
hasPackageJson: Boolean(packageJson),
|
|
761
|
+
hasAgentEntry,
|
|
762
|
+
frontendSignals,
|
|
763
|
+
researchSignals,
|
|
764
|
+
writingSignals
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function recommendBundlesForTarget(targetDir) {
|
|
768
|
+
const signals = discoverTargetSignals(targetDir);
|
|
769
|
+
const recommendations = [
|
|
770
|
+
{
|
|
771
|
+
bundleId: "base-governance",
|
|
772
|
+
confidence: signals.hasAgentEntry || signals.hasPackageJson ? "high" : "medium",
|
|
773
|
+
reasons: [
|
|
774
|
+
signals.hasAgentEntry ? "agent entry file present" : "",
|
|
775
|
+
signals.hasPackageJson ? "package.json present" : "",
|
|
776
|
+
!signals.hasAgentEntry && !signals.hasPackageJson ? "default project governance baseline" : ""
|
|
777
|
+
].filter(Boolean)
|
|
778
|
+
}
|
|
779
|
+
];
|
|
780
|
+
if (signals.frontendSignals.length > 0) {
|
|
781
|
+
recommendations.push({
|
|
782
|
+
bundleId: "frontend-app",
|
|
783
|
+
confidence: "high",
|
|
784
|
+
reasons: signals.frontendSignals.map((signal) => `frontend dependency: ${signal}`)
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (signals.researchSignals.length > 0) {
|
|
788
|
+
recommendations.push({
|
|
789
|
+
bundleId: "research-docs",
|
|
790
|
+
confidence: "high",
|
|
791
|
+
reasons: signals.researchSignals
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (signals.writingSignals.length > 0) {
|
|
795
|
+
recommendations.push({
|
|
796
|
+
bundleId: "novel-writing",
|
|
797
|
+
confidence: "high",
|
|
798
|
+
reasons: signals.writingSignals
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
return recommendations;
|
|
802
|
+
}
|
|
803
|
+
function readJson(path) {
|
|
804
|
+
if (!existsSync9(path)) return void 0;
|
|
805
|
+
try {
|
|
806
|
+
return JSON.parse(readFileSync6(path, "utf8"));
|
|
807
|
+
} catch {
|
|
808
|
+
return void 0;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function textFileIncludes(path, needles) {
|
|
812
|
+
if (!existsSync9(path)) return false;
|
|
813
|
+
const contents = readFileSync6(path, "utf8").toLowerCase();
|
|
814
|
+
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
815
|
+
}
|
|
816
|
+
function hasBookChildDirectory(targetDir, childName) {
|
|
817
|
+
const booksDir = join9(targetDir, "books");
|
|
818
|
+
if (!existsSync9(booksDir)) return false;
|
|
819
|
+
try {
|
|
820
|
+
return readdirSync5(booksDir, { withFileTypes: true }).some(
|
|
821
|
+
(entry) => entry.isDirectory() && existsSync9(join9(booksDir, entry.name, childName))
|
|
822
|
+
);
|
|
823
|
+
} catch {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
45
827
|
|
|
46
828
|
// src/commands/assets.ts
|
|
47
829
|
function runAssets(args) {
|
|
48
|
-
const [subcommand2] = args;
|
|
49
|
-
if (subcommand2
|
|
50
|
-
|
|
830
|
+
const [subcommand2, ...rest] = args;
|
|
831
|
+
if (subcommand2 === "list") {
|
|
832
|
+
return runAssetsList(rest);
|
|
833
|
+
}
|
|
834
|
+
if (subcommand2 === "recommend") {
|
|
835
|
+
return runAssetsRecommend(rest);
|
|
836
|
+
}
|
|
837
|
+
if (subcommand2 === "discover") {
|
|
838
|
+
return runAssetsDiscover(rest);
|
|
839
|
+
}
|
|
840
|
+
if (subcommand2 === "plan") {
|
|
841
|
+
return runAssetsPlan(rest);
|
|
842
|
+
}
|
|
843
|
+
if (subcommand2 === "apply") {
|
|
844
|
+
return runAssetsApply(rest);
|
|
845
|
+
}
|
|
846
|
+
if (subcommand2 === "check") {
|
|
847
|
+
return runAssetsCheck(rest);
|
|
848
|
+
}
|
|
849
|
+
if (subcommand2 === "npx") {
|
|
850
|
+
return runAssetsNpx(rest);
|
|
851
|
+
}
|
|
852
|
+
printUsage();
|
|
853
|
+
return 1;
|
|
854
|
+
}
|
|
855
|
+
function runAssetsNpx(args) {
|
|
856
|
+
const [operation, ...rest] = args;
|
|
857
|
+
if (!operation || operation === "--help" || operation === "-h" || rest.includes("--help")) {
|
|
858
|
+
printNpxUsage();
|
|
859
|
+
return 0;
|
|
860
|
+
}
|
|
861
|
+
if (operation !== "add" && operation !== "update") {
|
|
862
|
+
printNpxUsage();
|
|
863
|
+
return 1;
|
|
864
|
+
}
|
|
865
|
+
const options = parseNpxOptions(operation, rest);
|
|
866
|
+
if (!options.ok) {
|
|
867
|
+
console.error(options.error);
|
|
868
|
+
printNpxUsage();
|
|
869
|
+
return 1;
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const loaded = loadAgentAssetRegistry();
|
|
873
|
+
const plan = createNpxSkillsMaintenancePlan({
|
|
874
|
+
operation,
|
|
875
|
+
npxRoot: options.value.npxRoot ?? `${loaded.agentAssetsDir}/skills/npx-skills`,
|
|
876
|
+
source: options.value.source,
|
|
877
|
+
skill: options.value.skill
|
|
878
|
+
});
|
|
879
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
880
|
+
return 0;
|
|
881
|
+
} catch (error) {
|
|
882
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function runAssetsApply(args) {
|
|
887
|
+
const options = parseApplyOptions(args);
|
|
888
|
+
if (!options.ok) {
|
|
889
|
+
console.error(options.error);
|
|
890
|
+
printUsage();
|
|
891
|
+
return 1;
|
|
892
|
+
}
|
|
893
|
+
try {
|
|
894
|
+
const plan = JSON.parse(readFileSync7(options.value.planPath, "utf8"));
|
|
895
|
+
const result = applyAssetInstallPlan(plan);
|
|
896
|
+
console.log(`applied-actions: ${result.appliedActions.length}`);
|
|
897
|
+
return 0;
|
|
898
|
+
} catch (error) {
|
|
899
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
900
|
+
return 1;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function runAssetsCheck(args) {
|
|
904
|
+
const options = parseTargetJsonOptions(args);
|
|
905
|
+
if (!options.ok) {
|
|
906
|
+
console.error(options.error);
|
|
907
|
+
printUsage();
|
|
908
|
+
return 1;
|
|
909
|
+
}
|
|
910
|
+
const loaded = loadAgentAssetRegistry();
|
|
911
|
+
const result = checkInstalledAssets({
|
|
912
|
+
targetDir: options.value.targetDir,
|
|
913
|
+
agentAssetsDir: loaded.agentAssetsDir,
|
|
914
|
+
registry: loaded.registry
|
|
915
|
+
});
|
|
916
|
+
if (options.value.json) {
|
|
917
|
+
console.log(JSON.stringify(result, null, 2));
|
|
918
|
+
} else if (result.issues.length === 0) {
|
|
919
|
+
console.log("assets check passed");
|
|
920
|
+
} else {
|
|
921
|
+
for (const issue of result.issues) {
|
|
922
|
+
console.log(`${issue.type}: ${issue.message}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return result.issues.length === 0 ? 0 : 1;
|
|
926
|
+
}
|
|
927
|
+
function runAssetsList(args) {
|
|
928
|
+
const options = parseListOptions(args);
|
|
929
|
+
if (!options.ok) {
|
|
930
|
+
console.error(options.error);
|
|
931
|
+
printUsage();
|
|
51
932
|
return 1;
|
|
52
933
|
}
|
|
934
|
+
if (options.value.registryMode) {
|
|
935
|
+
return listRegistryAssets(options.value);
|
|
936
|
+
}
|
|
53
937
|
for (const asset of listAssets()) {
|
|
54
938
|
console.log(asset.path);
|
|
55
939
|
}
|
|
56
940
|
return 0;
|
|
57
941
|
}
|
|
942
|
+
function runAssetsRecommend(args) {
|
|
943
|
+
const options = parseTargetJsonOptions(args);
|
|
944
|
+
if (!options.ok) {
|
|
945
|
+
console.error(options.error);
|
|
946
|
+
printUsage();
|
|
947
|
+
return 1;
|
|
948
|
+
}
|
|
949
|
+
const recommendations = recommendBundlesForTarget(options.value.targetDir);
|
|
950
|
+
if (options.value.json) {
|
|
951
|
+
console.log(JSON.stringify({ targetDir: options.value.targetDir, recommendations }, null, 2));
|
|
952
|
+
} else {
|
|
953
|
+
for (const recommendation of recommendations) {
|
|
954
|
+
console.log(
|
|
955
|
+
`${recommendation.bundleId} ${recommendation.confidence} ${recommendation.reasons.join("; ")}`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return 0;
|
|
960
|
+
}
|
|
961
|
+
function runAssetsDiscover(args) {
|
|
962
|
+
const options = parseTargetJsonOptions(args);
|
|
963
|
+
if (!options.ok) {
|
|
964
|
+
console.error(options.error);
|
|
965
|
+
printUsage();
|
|
966
|
+
return 1;
|
|
967
|
+
}
|
|
968
|
+
const signals = discoverTargetSignals(options.value.targetDir);
|
|
969
|
+
if (options.value.json) {
|
|
970
|
+
console.log(JSON.stringify(signals, null, 2));
|
|
971
|
+
} else {
|
|
972
|
+
console.log(`target: ${signals.targetDir}`);
|
|
973
|
+
console.log(`package-json: ${signals.hasPackageJson ? "yes" : "no"}`);
|
|
974
|
+
console.log(`agent-entry: ${signals.hasAgentEntry ? "yes" : "no"}`);
|
|
975
|
+
console.log(`frontend: ${signals.frontendSignals.join(", ") || "none"}`);
|
|
976
|
+
console.log(`research: ${signals.researchSignals.join(", ") || "none"}`);
|
|
977
|
+
console.log(`writing: ${signals.writingSignals.join(", ") || "none"}`);
|
|
978
|
+
}
|
|
979
|
+
return 0;
|
|
980
|
+
}
|
|
981
|
+
function runAssetsPlan(args) {
|
|
982
|
+
const options = parsePlanOptions(args);
|
|
983
|
+
if (!options.ok) {
|
|
984
|
+
console.error(options.error);
|
|
985
|
+
printUsage();
|
|
986
|
+
return 1;
|
|
987
|
+
}
|
|
988
|
+
const loaded = loadAgentAssetRegistry();
|
|
989
|
+
if (loaded.issues.length > 0) {
|
|
990
|
+
for (const issue of loaded.issues) {
|
|
991
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
992
|
+
}
|
|
993
|
+
return 1;
|
|
994
|
+
}
|
|
995
|
+
try {
|
|
996
|
+
const plan = createAssetInstallPlan({
|
|
997
|
+
targetDir: options.value.targetDir,
|
|
998
|
+
agentAssetsDir: loaded.agentAssetsDir,
|
|
999
|
+
registry: loaded.registry,
|
|
1000
|
+
bundles: loadAgentAssetBundles(loaded.agentAssetsDir),
|
|
1001
|
+
bundleIds: options.value.bundleIds,
|
|
1002
|
+
host: options.value.host
|
|
1003
|
+
});
|
|
1004
|
+
if (options.value.json) {
|
|
1005
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1006
|
+
} else {
|
|
1007
|
+
console.log(`target: ${plan.targetDir}`);
|
|
1008
|
+
console.log(`host: ${plan.host}`);
|
|
1009
|
+
console.log(`bundles: ${plan.bundleIds.join(", ")}`);
|
|
1010
|
+
console.log(`assets: ${plan.assetIds.length}`);
|
|
1011
|
+
console.log(`actions: ${plan.actions.length}`);
|
|
1012
|
+
console.log("dry-run: true");
|
|
1013
|
+
}
|
|
1014
|
+
if (options.value.outPath) {
|
|
1015
|
+
mkdirSync3(dirname5(options.value.outPath), { recursive: true });
|
|
1016
|
+
writeFileSync2(options.value.outPath, `${JSON.stringify(plan, null, 2)}
|
|
1017
|
+
`);
|
|
1018
|
+
if (!options.value.json) {
|
|
1019
|
+
console.log(`plan: ${options.value.outPath}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return 0;
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1025
|
+
return 1;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
function parseListOptions(args) {
|
|
1029
|
+
const options = {
|
|
1030
|
+
json: false,
|
|
1031
|
+
registryMode: false,
|
|
1032
|
+
visibility: "all"
|
|
1033
|
+
};
|
|
1034
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1035
|
+
const arg = args[index];
|
|
1036
|
+
if (arg === "--json") {
|
|
1037
|
+
options.json = true;
|
|
1038
|
+
options.registryMode = true;
|
|
1039
|
+
} else if (arg === "--visibility") {
|
|
1040
|
+
const visibility = args[index + 1];
|
|
1041
|
+
if (!isVisibilityFilter(visibility)) {
|
|
1042
|
+
return { ok: false, error: "Expected --visibility public|private|third-party|all" };
|
|
1043
|
+
}
|
|
1044
|
+
options.visibility = visibility;
|
|
1045
|
+
options.registryMode = true;
|
|
1046
|
+
index += 1;
|
|
1047
|
+
} else if (arg === "--registry") {
|
|
1048
|
+
options.registryMode = true;
|
|
1049
|
+
} else {
|
|
1050
|
+
return { ok: false, error: `Unknown assets list option: ${arg}` };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return { ok: true, value: options };
|
|
1054
|
+
}
|
|
1055
|
+
function parseTargetJsonOptions(args) {
|
|
1056
|
+
const options = {
|
|
1057
|
+
targetDir: process.cwd(),
|
|
1058
|
+
json: false
|
|
1059
|
+
};
|
|
1060
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1061
|
+
const arg = args[index];
|
|
1062
|
+
if (arg === "--target") {
|
|
1063
|
+
const targetDir = args[index + 1];
|
|
1064
|
+
if (!targetDir) return { ok: false, error: "Expected --target <path>" };
|
|
1065
|
+
options.targetDir = targetDir;
|
|
1066
|
+
index += 1;
|
|
1067
|
+
} else if (arg === "--json") {
|
|
1068
|
+
options.json = true;
|
|
1069
|
+
} else {
|
|
1070
|
+
return { ok: false, error: `Unknown assets target option: ${arg}` };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return { ok: true, value: options };
|
|
1074
|
+
}
|
|
1075
|
+
function parsePlanOptions(args) {
|
|
1076
|
+
const options = {
|
|
1077
|
+
targetDir: process.cwd(),
|
|
1078
|
+
json: false,
|
|
1079
|
+
bundleIds: [],
|
|
1080
|
+
host: "codex"
|
|
1081
|
+
};
|
|
1082
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1083
|
+
const arg = args[index];
|
|
1084
|
+
if (arg === "--target") {
|
|
1085
|
+
const targetDir = args[index + 1];
|
|
1086
|
+
if (!targetDir) return { ok: false, error: "Expected --target <path>" };
|
|
1087
|
+
options.targetDir = targetDir;
|
|
1088
|
+
index += 1;
|
|
1089
|
+
} else if (arg === "--bundle") {
|
|
1090
|
+
const bundleId = args[index + 1];
|
|
1091
|
+
if (!bundleId) return { ok: false, error: "Expected --bundle <bundle-id>" };
|
|
1092
|
+
options.bundleIds.push(bundleId);
|
|
1093
|
+
index += 1;
|
|
1094
|
+
} else if (arg === "--host") {
|
|
1095
|
+
const host = args[index + 1];
|
|
1096
|
+
if (!isHost(host)) {
|
|
1097
|
+
return {
|
|
1098
|
+
ok: false,
|
|
1099
|
+
error: "Expected --host codex|claude-code|gemini-cli|antigravity"
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
options.host = host;
|
|
1103
|
+
index += 1;
|
|
1104
|
+
} else if (arg === "--out") {
|
|
1105
|
+
const outPath = args[index + 1];
|
|
1106
|
+
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
1107
|
+
options.outPath = outPath;
|
|
1108
|
+
index += 1;
|
|
1109
|
+
} else if (arg === "--json") {
|
|
1110
|
+
options.json = true;
|
|
1111
|
+
} else {
|
|
1112
|
+
return { ok: false, error: `Unknown assets plan option: ${arg}` };
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (options.bundleIds.length === 0) {
|
|
1116
|
+
return { ok: false, error: "Expected at least one --bundle <bundle-id>" };
|
|
1117
|
+
}
|
|
1118
|
+
return { ok: true, value: options };
|
|
1119
|
+
}
|
|
1120
|
+
function parseApplyOptions(args) {
|
|
1121
|
+
let planPath = "";
|
|
1122
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1123
|
+
const arg = args[index];
|
|
1124
|
+
if (arg === "--plan") {
|
|
1125
|
+
const value = args[index + 1];
|
|
1126
|
+
if (!value) return { ok: false, error: "Expected --plan <path>" };
|
|
1127
|
+
planPath = value;
|
|
1128
|
+
index += 1;
|
|
1129
|
+
} else {
|
|
1130
|
+
return { ok: false, error: `Unknown assets apply option: ${arg}` };
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (!planPath) return { ok: false, error: "Expected --plan <path>" };
|
|
1134
|
+
return { ok: true, value: { planPath } };
|
|
1135
|
+
}
|
|
1136
|
+
function parseNpxOptions(operation, args) {
|
|
1137
|
+
const options = {};
|
|
1138
|
+
const positional = [];
|
|
1139
|
+
let hasPlan = false;
|
|
1140
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1141
|
+
const arg = args[index];
|
|
1142
|
+
if (arg === "--plan") {
|
|
1143
|
+
hasPlan = true;
|
|
1144
|
+
} else if (arg === "--root") {
|
|
1145
|
+
const npxRoot = args[index + 1];
|
|
1146
|
+
if (!npxRoot) return { ok: false, error: "Expected --root <path>" };
|
|
1147
|
+
options.npxRoot = npxRoot;
|
|
1148
|
+
index += 1;
|
|
1149
|
+
} else if (arg === "--skill") {
|
|
1150
|
+
const skill = args[index + 1];
|
|
1151
|
+
if (!skill) return { ok: false, error: "Expected --skill <name>" };
|
|
1152
|
+
options.skill = skill;
|
|
1153
|
+
index += 1;
|
|
1154
|
+
} else if (arg.startsWith("-")) {
|
|
1155
|
+
return { ok: false, error: `Unknown assets npx option: ${arg}` };
|
|
1156
|
+
} else {
|
|
1157
|
+
positional.push(arg);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (!hasPlan) return { ok: false, error: "Expected --plan. Direct npx writes are not supported." };
|
|
1161
|
+
if (operation === "add") {
|
|
1162
|
+
const [source] = positional;
|
|
1163
|
+
if (!source) return { ok: false, error: "Expected pro-gov assets npx add <source> --plan" };
|
|
1164
|
+
options.source = source;
|
|
1165
|
+
} else if (positional.length > 0) {
|
|
1166
|
+
return { ok: false, error: `Unexpected assets npx update argument: ${positional[0]}` };
|
|
1167
|
+
}
|
|
1168
|
+
return { ok: true, value: options };
|
|
1169
|
+
}
|
|
1170
|
+
function listRegistryAssets(options) {
|
|
1171
|
+
const loaded = loadAgentAssetRegistry();
|
|
1172
|
+
if (loaded.issues.length > 0) {
|
|
1173
|
+
for (const issue of loaded.issues) {
|
|
1174
|
+
console.error(`${issue.type}: ${issue.message}`);
|
|
1175
|
+
}
|
|
1176
|
+
return 1;
|
|
1177
|
+
}
|
|
1178
|
+
const assets = filterAssetsByVisibility(loaded.registry.assets, options.visibility);
|
|
1179
|
+
if (options.json) {
|
|
1180
|
+
console.log(
|
|
1181
|
+
JSON.stringify(
|
|
1182
|
+
{
|
|
1183
|
+
count: assets.length,
|
|
1184
|
+
registryPath: loaded.registryPath,
|
|
1185
|
+
assets
|
|
1186
|
+
},
|
|
1187
|
+
null,
|
|
1188
|
+
2
|
|
1189
|
+
)
|
|
1190
|
+
);
|
|
1191
|
+
} else {
|
|
1192
|
+
for (const asset of assets) {
|
|
1193
|
+
console.log(asset.id);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return 0;
|
|
1197
|
+
}
|
|
1198
|
+
function filterAssetsByVisibility(assets, visibility) {
|
|
1199
|
+
if (visibility === "all") return [...assets];
|
|
1200
|
+
return assets.filter((asset) => asset.visibility === visibility);
|
|
1201
|
+
}
|
|
1202
|
+
function isVisibilityFilter(value) {
|
|
1203
|
+
return value === "public" || value === "private" || value === "third-party" || value === "all";
|
|
1204
|
+
}
|
|
1205
|
+
function isHost(value) {
|
|
1206
|
+
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
1207
|
+
}
|
|
1208
|
+
function printUsage() {
|
|
1209
|
+
console.error("Usage:");
|
|
1210
|
+
console.error(" pro-gov assets list [--registry] [--json] [--visibility public|private|third-party|all]");
|
|
1211
|
+
console.error(" pro-gov assets discover [--target <path>] [--json]");
|
|
1212
|
+
console.error(" pro-gov assets recommend [--target <path>] [--json]");
|
|
1213
|
+
console.error(" pro-gov assets plan --bundle <bundle-id> [--bundle <bundle-id>] [--target <path>] [--host codex|claude-code|gemini-cli|antigravity] [--out <path>] [--json]");
|
|
1214
|
+
console.error(" pro-gov assets apply --plan <path>");
|
|
1215
|
+
console.error(" pro-gov assets check [--target <path>] [--json]");
|
|
1216
|
+
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
1217
|
+
console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
1218
|
+
}
|
|
1219
|
+
function printNpxUsage() {
|
|
1220
|
+
console.log("Usage: pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
1221
|
+
console.log("Usage: pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
1222
|
+
console.log("");
|
|
1223
|
+
console.log("Runs npx skills only in a temporary copy and prints a reviewable plan.");
|
|
1224
|
+
}
|
|
58
1225
|
|
|
59
1226
|
// src/commands/doctor.ts
|
|
60
|
-
import { spawnSync } from "node:child_process";
|
|
61
|
-
import { existsSync as
|
|
1227
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1228
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
62
1229
|
import { createRequire } from "node:module";
|
|
63
|
-
import { dirname as
|
|
1230
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
64
1231
|
var REQUIRED_ASSETS = [
|
|
65
1232
|
"starter/AGENTS.template.md",
|
|
1233
|
+
"starter/.gemini/settings.json",
|
|
66
1234
|
"starter/docs/governance/ssot-v0.9.md",
|
|
67
1235
|
"starter/docs/governance/agents-routing/engineering-runtime-v0.9.md",
|
|
68
1236
|
"starter/docs/governance/agents-routing/doc-only-v0.9.md",
|
|
@@ -86,7 +1254,7 @@ function runDoctor(_args) {
|
|
|
86
1254
|
return missing.length > 0 ? 1 : 0;
|
|
87
1255
|
}
|
|
88
1256
|
function checkDocGov() {
|
|
89
|
-
const fromPath =
|
|
1257
|
+
const fromPath = spawnSync2("doc-gov", ["--help"], {
|
|
90
1258
|
encoding: "utf8",
|
|
91
1259
|
stdio: "ignore"
|
|
92
1260
|
});
|
|
@@ -97,7 +1265,7 @@ function checkDocGov() {
|
|
|
97
1265
|
if (!dependencyCli) {
|
|
98
1266
|
return "doc-gov: not found; install @pieai/doc-gov beside @pieai/pro-gov for validation.";
|
|
99
1267
|
}
|
|
100
|
-
const fromDependency =
|
|
1268
|
+
const fromDependency = spawnSync2(process.execPath, [dependencyCli, "--help"], {
|
|
101
1269
|
encoding: "utf8",
|
|
102
1270
|
stdio: "ignore"
|
|
103
1271
|
});
|
|
@@ -110,8 +1278,8 @@ function resolveDocGovDependencyCli() {
|
|
|
110
1278
|
try {
|
|
111
1279
|
const require2 = createRequire(import.meta.url);
|
|
112
1280
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
113
|
-
const cliPath =
|
|
114
|
-
return
|
|
1281
|
+
const cliPath = join10(dirname6(packageJsonPath), "dist/cli.js");
|
|
1282
|
+
return existsSync10(cliPath) ? cliPath : null;
|
|
115
1283
|
} catch {
|
|
116
1284
|
return null;
|
|
117
1285
|
}
|
|
@@ -176,9 +1344,256 @@ function readFlag(args, flag) {
|
|
|
176
1344
|
return value;
|
|
177
1345
|
}
|
|
178
1346
|
|
|
1347
|
+
// src/commands/lens.ts
|
|
1348
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1349
|
+
import { dirname as dirname7 } from "node:path";
|
|
1350
|
+
|
|
1351
|
+
// src/lens/report.ts
|
|
1352
|
+
function formatProjectLensInspection(report) {
|
|
1353
|
+
return [
|
|
1354
|
+
`target: ${report.targetDir}`,
|
|
1355
|
+
`ai-entry-files: ${formatList(report.aiEntryFiles)}`,
|
|
1356
|
+
`ai-config-files: ${formatList(report.aiConfigFiles)}`,
|
|
1357
|
+
`package-scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
1358
|
+
`dependencies: ${formatList(report.packageJson?.dependencies ?? [])}`,
|
|
1359
|
+
`dev-dependencies: ${formatList(report.packageJson?.devDependencies ?? [])}`,
|
|
1360
|
+
`docs-directory: ${report.docs.hasDocsDirectory ? "yes" : "no"}`,
|
|
1361
|
+
`markdown-files: ${report.docs.markdownFileCount}`,
|
|
1362
|
+
`governance-files: ${formatList(report.docs.governanceFiles)}`,
|
|
1363
|
+
`git: ${report.git.available ? "available" : "unavailable"}`,
|
|
1364
|
+
`git-branch: ${report.git.branch ?? "unknown"}`,
|
|
1365
|
+
`git-head: ${report.git.head ?? "unknown"}`,
|
|
1366
|
+
`large-files: ${report.largeFiles.length}`
|
|
1367
|
+
].join("\n");
|
|
1368
|
+
}
|
|
1369
|
+
function renderProjectLensMarkdownReport(report) {
|
|
1370
|
+
return `${[
|
|
1371
|
+
"# Project Lens Evidence Report",
|
|
1372
|
+
"",
|
|
1373
|
+
`- Target: \`${report.targetDir}\``,
|
|
1374
|
+
`- Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1375
|
+
"- Scope: local read-only evidence for AI-assisted project review",
|
|
1376
|
+
"",
|
|
1377
|
+
"## AI Entry Files",
|
|
1378
|
+
"",
|
|
1379
|
+
bulletList(report.aiEntryFiles),
|
|
1380
|
+
"",
|
|
1381
|
+
"## AI Config Adapters",
|
|
1382
|
+
"",
|
|
1383
|
+
bulletList(report.aiConfigFiles),
|
|
1384
|
+
"",
|
|
1385
|
+
"## Package",
|
|
1386
|
+
"",
|
|
1387
|
+
`- Scripts: ${formatList(report.packageJson?.scripts ?? [])}`,
|
|
1388
|
+
`- Dependencies: ${formatList(report.packageJson?.dependencies ?? [])}`,
|
|
1389
|
+
`- Dev dependencies: ${formatList(report.packageJson?.devDependencies ?? [])}`,
|
|
1390
|
+
"",
|
|
1391
|
+
"## Docs",
|
|
1392
|
+
"",
|
|
1393
|
+
`- Docs directory: ${report.docs.hasDocsDirectory ? "yes" : "no"}`,
|
|
1394
|
+
`- Markdown files: ${report.docs.markdownFileCount}`,
|
|
1395
|
+
`- Governance files: ${formatList(report.docs.governanceFiles)}`,
|
|
1396
|
+
"",
|
|
1397
|
+
"## Git",
|
|
1398
|
+
"",
|
|
1399
|
+
`- Available: ${report.git.available ? "yes" : "no"}`,
|
|
1400
|
+
`- Branch: ${report.git.branch ?? "unknown"}`,
|
|
1401
|
+
`- Head: ${report.git.head ?? "unknown"}`,
|
|
1402
|
+
"",
|
|
1403
|
+
"## Large Files",
|
|
1404
|
+
"",
|
|
1405
|
+
report.largeFiles.length === 0 ? "- none" : report.largeFiles.map((file) => `- \`${file.path}\` (${file.bytes} bytes)`).join("\n"),
|
|
1406
|
+
"",
|
|
1407
|
+
"## Review Notes",
|
|
1408
|
+
"",
|
|
1409
|
+
"- This report is evidence only; it does not replace human or AI judgement.",
|
|
1410
|
+
"- Use the ProjectLens skills for interpretation, tradeoff analysis, and recommendations."
|
|
1411
|
+
].join("\n")}
|
|
1412
|
+
`;
|
|
1413
|
+
}
|
|
1414
|
+
function formatList(values) {
|
|
1415
|
+
return values.length === 0 ? "none" : values.join(", ");
|
|
1416
|
+
}
|
|
1417
|
+
function bulletList(values) {
|
|
1418
|
+
if (values.length === 0) return "- none";
|
|
1419
|
+
return values.map((value) => `- \`${value}\``).join("\n");
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/lens/scan.ts
|
|
1423
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1424
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
|
|
1425
|
+
import { join as join11, relative as relative4 } from "node:path";
|
|
1426
|
+
var ignoredDirectories = /* @__PURE__ */ new Set([
|
|
1427
|
+
".git",
|
|
1428
|
+
".next",
|
|
1429
|
+
".turbo",
|
|
1430
|
+
"dist",
|
|
1431
|
+
"node_modules",
|
|
1432
|
+
"coverage"
|
|
1433
|
+
]);
|
|
1434
|
+
function scanProjectLensTarget(targetDir, options = {}) {
|
|
1435
|
+
const largeFileBytes = options.largeFileBytes ?? 5e4;
|
|
1436
|
+
const files = listProjectFiles(targetDir);
|
|
1437
|
+
const markdownFiles = files.filter((file) => file.endsWith(".md"));
|
|
1438
|
+
const packageJson = readPackageJson(targetDir);
|
|
1439
|
+
return {
|
|
1440
|
+
targetDir,
|
|
1441
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md", "GEMINI.md"].filter(
|
|
1442
|
+
(file) => existsSync11(join11(targetDir, file))
|
|
1443
|
+
),
|
|
1444
|
+
aiConfigFiles: [".gemini/settings.json"].filter((file) => existsSync11(join11(targetDir, file))),
|
|
1445
|
+
packageJson,
|
|
1446
|
+
docs: {
|
|
1447
|
+
hasDocsDirectory: existsSync11(join11(targetDir, "docs")),
|
|
1448
|
+
markdownFileCount: markdownFiles.length,
|
|
1449
|
+
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
1450
|
+
},
|
|
1451
|
+
git: readGitState(targetDir),
|
|
1452
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync3(join11(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function readPackageJson(targetDir) {
|
|
1456
|
+
const packageJsonPath = join11(targetDir, "package.json");
|
|
1457
|
+
if (!existsSync11(packageJsonPath)) return void 0;
|
|
1458
|
+
try {
|
|
1459
|
+
const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
|
|
1460
|
+
return {
|
|
1461
|
+
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
1462
|
+
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
1463
|
+
devDependencies: Object.keys(packageJson.devDependencies ?? {}).sort()
|
|
1464
|
+
};
|
|
1465
|
+
} catch {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
function readGitState(targetDir) {
|
|
1470
|
+
const branch = runGit(targetDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
1471
|
+
if (!branch.ok) return { available: false };
|
|
1472
|
+
const head = runGit(targetDir, ["log", "-1", "--format=%H %s"]);
|
|
1473
|
+
const status = runGit(targetDir, ["status", "-sb"]);
|
|
1474
|
+
return {
|
|
1475
|
+
available: true,
|
|
1476
|
+
branch: branch.stdout,
|
|
1477
|
+
head: head.ok ? head.stdout : void 0,
|
|
1478
|
+
statusShort: status.ok ? status.stdout : void 0
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
function runGit(targetDir, args) {
|
|
1482
|
+
const result = spawnSync3("git", ["-C", targetDir, ...args], {
|
|
1483
|
+
encoding: "utf8"
|
|
1484
|
+
});
|
|
1485
|
+
if (result.status !== 0) return { ok: false };
|
|
1486
|
+
return { ok: true, stdout: result.stdout.trim() };
|
|
1487
|
+
}
|
|
1488
|
+
function listProjectFiles(targetDir) {
|
|
1489
|
+
const files = [];
|
|
1490
|
+
collectFiles2(targetDir, targetDir, files);
|
|
1491
|
+
return files.sort();
|
|
1492
|
+
}
|
|
1493
|
+
function collectFiles2(rootDir, currentDir, files) {
|
|
1494
|
+
if (!existsSync11(currentDir)) return;
|
|
1495
|
+
for (const entry of readdirSync6(currentDir, { withFileTypes: true })) {
|
|
1496
|
+
if (entry.isDirectory()) {
|
|
1497
|
+
if (ignoredDirectories.has(entry.name)) continue;
|
|
1498
|
+
collectFiles2(rootDir, join11(currentDir, entry.name), files);
|
|
1499
|
+
} else if (entry.isFile()) {
|
|
1500
|
+
files.push(toUnixPath4(relative4(rootDir, join11(currentDir, entry.name))));
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
function toUnixPath4(path) {
|
|
1505
|
+
return path.replaceAll("\\", "/");
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// src/commands/lens.ts
|
|
1509
|
+
function runLens(args) {
|
|
1510
|
+
const [subcommand2, ...rest] = args;
|
|
1511
|
+
if (subcommand2 === "scan" || subcommand2 === "inspect") {
|
|
1512
|
+
return runLensInspect(rest, subcommand2);
|
|
1513
|
+
}
|
|
1514
|
+
if (subcommand2 === "report") {
|
|
1515
|
+
return runLensReport(rest);
|
|
1516
|
+
}
|
|
1517
|
+
printUsage2();
|
|
1518
|
+
return 1;
|
|
1519
|
+
}
|
|
1520
|
+
function runLensInspect(args, subcommand2) {
|
|
1521
|
+
const options = parseLensOptions(args, subcommand2);
|
|
1522
|
+
if (!options.ok) {
|
|
1523
|
+
console.error(options.error);
|
|
1524
|
+
printUsage2();
|
|
1525
|
+
return 1;
|
|
1526
|
+
}
|
|
1527
|
+
const report = scanProjectLensTarget(options.value.targetDir);
|
|
1528
|
+
if (options.value.json || options.value.format === "json") {
|
|
1529
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1530
|
+
} else {
|
|
1531
|
+
console.log(formatProjectLensInspection(report));
|
|
1532
|
+
}
|
|
1533
|
+
return 0;
|
|
1534
|
+
}
|
|
1535
|
+
function runLensReport(args) {
|
|
1536
|
+
const options = parseLensOptions(args, "report");
|
|
1537
|
+
if (!options.ok) {
|
|
1538
|
+
console.error(options.error);
|
|
1539
|
+
printUsage2();
|
|
1540
|
+
return 1;
|
|
1541
|
+
}
|
|
1542
|
+
if (!options.value.outPath) {
|
|
1543
|
+
console.error("Expected --out <path>");
|
|
1544
|
+
printUsage2();
|
|
1545
|
+
return 1;
|
|
1546
|
+
}
|
|
1547
|
+
const report = scanProjectLensTarget(options.value.targetDir);
|
|
1548
|
+
const markdown = renderProjectLensMarkdownReport(report);
|
|
1549
|
+
mkdirSync4(dirname7(options.value.outPath), { recursive: true });
|
|
1550
|
+
writeFileSync3(options.value.outPath, markdown);
|
|
1551
|
+
console.log(`report: ${options.value.outPath}`);
|
|
1552
|
+
return 0;
|
|
1553
|
+
}
|
|
1554
|
+
function parseLensOptions(args, subcommand2) {
|
|
1555
|
+
const options = {
|
|
1556
|
+
targetDir: process.cwd(),
|
|
1557
|
+
json: false,
|
|
1558
|
+
format: "text"
|
|
1559
|
+
};
|
|
1560
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1561
|
+
const arg = args[index];
|
|
1562
|
+
if (arg === "--target") {
|
|
1563
|
+
const targetDir = args[index + 1];
|
|
1564
|
+
if (!targetDir) return { ok: false, error: "Expected --target <path>" };
|
|
1565
|
+
options.targetDir = targetDir;
|
|
1566
|
+
index += 1;
|
|
1567
|
+
} else if (arg === "--json") {
|
|
1568
|
+
options.json = true;
|
|
1569
|
+
options.format = "json";
|
|
1570
|
+
} else if (arg === "--format") {
|
|
1571
|
+
const format = args[index + 1];
|
|
1572
|
+
if (format !== "text" && format !== "json") {
|
|
1573
|
+
return { ok: false, error: "Expected --format text|json" };
|
|
1574
|
+
}
|
|
1575
|
+
options.format = format;
|
|
1576
|
+
index += 1;
|
|
1577
|
+
} else if (arg === "--out") {
|
|
1578
|
+
const outPath = args[index + 1];
|
|
1579
|
+
if (!outPath) return { ok: false, error: "Expected --out <path>" };
|
|
1580
|
+
options.outPath = outPath;
|
|
1581
|
+
index += 1;
|
|
1582
|
+
} else {
|
|
1583
|
+
return { ok: false, error: `Unknown lens ${subcommand2} option: ${arg}` };
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return { ok: true, value: options };
|
|
1587
|
+
}
|
|
1588
|
+
function printUsage2() {
|
|
1589
|
+
console.error("Usage: pro-gov lens scan [--target <path>] [--json]");
|
|
1590
|
+
console.error("Usage: pro-gov lens inspect [--target <path>] [--format text|json]");
|
|
1591
|
+
console.error("Usage: pro-gov lens report --target <path> --out <path>");
|
|
1592
|
+
}
|
|
1593
|
+
|
|
179
1594
|
// src/commands/sync.ts
|
|
180
|
-
import { existsSync as
|
|
181
|
-
import { join as
|
|
1595
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
1596
|
+
import { join as join12 } from "node:path";
|
|
182
1597
|
function runSync(args) {
|
|
183
1598
|
if (!args.includes("--check")) {
|
|
184
1599
|
console.error("pro-gov sync requires --check in this first read-only release.");
|
|
@@ -187,14 +1602,14 @@ function runSync(args) {
|
|
|
187
1602
|
let differences = 0;
|
|
188
1603
|
console.log("pro-gov sync check");
|
|
189
1604
|
for (const file of planStarterFiles()) {
|
|
190
|
-
const targetPath =
|
|
191
|
-
if (!
|
|
1605
|
+
const targetPath = join12(process.cwd(), file.targetPath);
|
|
1606
|
+
if (!existsSync12(targetPath)) {
|
|
192
1607
|
console.log(`missing: ${file.targetPath}`);
|
|
193
1608
|
differences += 1;
|
|
194
1609
|
continue;
|
|
195
1610
|
}
|
|
196
|
-
const source =
|
|
197
|
-
const target =
|
|
1611
|
+
const source = readFileSync9(file.absoluteSourcePath, "utf8");
|
|
1612
|
+
const target = readFileSync9(targetPath, "utf8");
|
|
198
1613
|
if (source !== target) {
|
|
199
1614
|
console.log(`different: ${file.targetPath}`);
|
|
200
1615
|
differences += 1;
|
|
@@ -210,7 +1625,16 @@ function runSync(args) {
|
|
|
210
1625
|
|
|
211
1626
|
// src/cli.ts
|
|
212
1627
|
var COMMANDS = [
|
|
213
|
-
"assets list",
|
|
1628
|
+
"assets list [--json] [--visibility public|private|third-party|all]",
|
|
1629
|
+
"assets discover [--target <path>] [--json]",
|
|
1630
|
+
"assets recommend [--target <path>] [--json]",
|
|
1631
|
+
"assets plan --bundle <bundle-id> [--target <path>] [--json]",
|
|
1632
|
+
"assets apply --plan <path>",
|
|
1633
|
+
"assets check [--target <path>] [--json]",
|
|
1634
|
+
"assets npx add|update ... --plan",
|
|
1635
|
+
"lens scan [--target <path>] [--json]",
|
|
1636
|
+
"lens inspect [--target <path>] [--format text|json]",
|
|
1637
|
+
"lens report --target <path> --out <path>",
|
|
214
1638
|
"init --profile <engineering-runtime|doc-only> --dry-run",
|
|
215
1639
|
"sync --check",
|
|
216
1640
|
"doctor"
|
|
@@ -219,8 +1643,10 @@ var [command, subcommand] = process.argv.slice(2);
|
|
|
219
1643
|
if (!command || command === "--help" || command === "-h") {
|
|
220
1644
|
printHelp();
|
|
221
1645
|
process.exitCode = command ? 0 : 1;
|
|
222
|
-
} else if (command === "assets"
|
|
1646
|
+
} else if (command === "assets") {
|
|
223
1647
|
process.exitCode = runAssets(process.argv.slice(3));
|
|
1648
|
+
} else if (command === "lens") {
|
|
1649
|
+
process.exitCode = runLens(process.argv.slice(3));
|
|
224
1650
|
} else if (command === "init") {
|
|
225
1651
|
process.exitCode = runInit(process.argv.slice(3));
|
|
226
1652
|
} else if (command === "sync") {
|