@pieai/pro-gov 0.9.2 → 0.9.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 +11 -0
- package/cli-guide.md +13 -0
- package/dist/cli.js +1688 -590
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -43,8 +43,8 @@ function isPlatformMetadata(name) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// src/commands/assets.ts
|
|
46
|
-
import { existsSync as
|
|
47
|
-
import { dirname as
|
|
46
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
47
|
+
import { dirname as dirname6, join as join12 } from "node:path";
|
|
48
48
|
|
|
49
49
|
// src/asset-bundles/bundles.ts
|
|
50
50
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync } from "node:fs";
|
|
@@ -58,27 +58,1001 @@ function loadAgentAssetBundles(agentAssetsDir) {
|
|
|
58
58
|
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
// src/asset-
|
|
61
|
+
// src/asset-catalog/catalog.ts
|
|
62
62
|
import { createHash } from "node:crypto";
|
|
63
|
-
import { spawnSync } from "node:child_process";
|
|
64
63
|
import {
|
|
65
|
-
cpSync,
|
|
66
64
|
existsSync as existsSync3,
|
|
65
|
+
lstatSync,
|
|
67
66
|
mkdirSync,
|
|
68
67
|
mkdtempSync,
|
|
69
|
-
readdirSync as readdirSync3,
|
|
70
68
|
readFileSync as readFileSync2,
|
|
71
|
-
|
|
69
|
+
readdirSync as readdirSync3,
|
|
70
|
+
renameSync,
|
|
71
|
+
rmSync,
|
|
72
|
+
statSync,
|
|
73
|
+
symlinkSync,
|
|
74
|
+
writeFileSync
|
|
72
75
|
} from "node:fs";
|
|
73
|
-
import { join as join3, relative as relative2 } from "node:path";
|
|
76
|
+
import { basename, dirname as dirname2, extname, isAbsolute, join as join3, relative as relative2, resolve } from "node:path";
|
|
77
|
+
var portableManifestName = ".asset-catalog-manifest.json";
|
|
78
|
+
var localLinksManifestName = ".asset-catalog-local-links.json";
|
|
79
|
+
var legacyManagedSentinel = ".managed-by-pro-gov-asset-catalog";
|
|
80
|
+
var legacyManagedDirectoryNames = ["generated", "native-links"];
|
|
81
|
+
var manifestGenerator = "@pieai/pro-gov asset-catalog";
|
|
82
|
+
var ignoredTraversalNames = /* @__PURE__ */ new Set([
|
|
83
|
+
".DS_Store",
|
|
84
|
+
"Thumbs.db",
|
|
85
|
+
"__pycache__",
|
|
86
|
+
".cache",
|
|
87
|
+
".pytest_cache",
|
|
88
|
+
".mypy_cache",
|
|
89
|
+
"node_modules",
|
|
90
|
+
"dist",
|
|
91
|
+
"build"
|
|
92
|
+
]);
|
|
93
|
+
var selectorFields = /* @__PURE__ */ new Set([
|
|
94
|
+
"id",
|
|
95
|
+
"title",
|
|
96
|
+
"family",
|
|
97
|
+
"kind",
|
|
98
|
+
"domain",
|
|
99
|
+
"tag",
|
|
100
|
+
"visibility",
|
|
101
|
+
"origin",
|
|
102
|
+
"sourcePath"
|
|
103
|
+
]);
|
|
104
|
+
var slugPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
105
|
+
function loadAssetCatalogConfig(catalogRoot) {
|
|
106
|
+
const configPath = join3(catalogRoot, "catalog.config.json");
|
|
107
|
+
if (!existsSync3(configPath)) {
|
|
108
|
+
throw new Error(`Missing asset catalog config: ${configPath}`);
|
|
109
|
+
}
|
|
110
|
+
const config = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
111
|
+
const issues = validateAssetCatalogConfig(config);
|
|
112
|
+
if (issues.length > 0) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Invalid asset catalog config:
|
|
115
|
+
${issues.map((issue) => `- ${issue}`).join("\n")}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
function buildAssetCatalog(options) {
|
|
121
|
+
const snapshot = createAssetCatalogSnapshot(options);
|
|
122
|
+
const warnings = [...writePortableCatalog(options.catalogRoot, snapshot.files)];
|
|
123
|
+
let nativeLinksRoot;
|
|
124
|
+
let nativeLinkCount = 0;
|
|
125
|
+
if (options.nativeLinks) {
|
|
126
|
+
nativeLinksRoot = join3(options.catalogRoot, "by-topic");
|
|
127
|
+
const nativeResult = writeNativeLinks({
|
|
128
|
+
agentAssetsDir: options.agentAssetsDir,
|
|
129
|
+
catalogRoot: options.catalogRoot,
|
|
130
|
+
entries: snapshot.entries,
|
|
131
|
+
platform: options.platform ?? process.platform
|
|
132
|
+
});
|
|
133
|
+
nativeLinkCount = nativeResult.linkCount;
|
|
134
|
+
warnings.push(...nativeResult.warnings);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
catalogRoot: options.catalogRoot,
|
|
138
|
+
nativeLinksRoot,
|
|
139
|
+
assetCount: snapshot.document.assetCount,
|
|
140
|
+
topicCount: snapshot.document.topicCount,
|
|
141
|
+
portableFileCount: snapshot.files.size + 1,
|
|
142
|
+
nativeLinkCount,
|
|
143
|
+
warnings
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function checkAssetCatalog(options) {
|
|
147
|
+
const snapshot = createAssetCatalogSnapshot(options);
|
|
148
|
+
const expected = new Map(snapshot.files);
|
|
149
|
+
expected.set(portableManifestName, renderPortableManifest(snapshot.files));
|
|
150
|
+
const issues = [];
|
|
151
|
+
for (const [relativePath, expectedContent] of expected) {
|
|
152
|
+
const targetPath = join3(options.catalogRoot, ...relativePath.split("/"));
|
|
153
|
+
if (!existsSync3(targetPath)) {
|
|
154
|
+
issues.push(`missing generated file: ${relativePath}`);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const stats = lstatSync(targetPath);
|
|
158
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
159
|
+
issues.push(`generated path is not a regular file: ${relativePath}`);
|
|
160
|
+
} else if (readFileSync2(targetPath, "utf8") !== expectedContent) {
|
|
161
|
+
issues.push(`stale generated file: ${relativePath}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const manifest = readPortableManifest(options.catalogRoot);
|
|
166
|
+
if (manifest) {
|
|
167
|
+
for (const relativePath of manifest.portableFiles) {
|
|
168
|
+
if (!snapshot.files.has(relativePath)) {
|
|
169
|
+
issues.push(`unexpected managed file: ${relativePath}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
issues.push(error instanceof Error ? error.message : String(error));
|
|
175
|
+
}
|
|
176
|
+
for (const name of legacyManagedDirectoryNames) {
|
|
177
|
+
if (existsSync3(join3(options.catalogRoot, name))) {
|
|
178
|
+
issues.push(`legacy catalog directory remains: ${name}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
catalogRoot: options.catalogRoot,
|
|
183
|
+
assetCount: snapshot.document.assetCount,
|
|
184
|
+
issues: issues.sort()
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function nativeLinkStrategy(platform, sourceType) {
|
|
188
|
+
if (platform !== "win32") return "relative-symlink";
|
|
189
|
+
return sourceType === "directory" ? "junction" : "file-symlink";
|
|
190
|
+
}
|
|
191
|
+
function createAssetCatalogSnapshot(options) {
|
|
192
|
+
const config = loadAssetCatalogConfig(options.catalogRoot);
|
|
193
|
+
const agentAssetsDir = resolve(options.agentAssetsDir);
|
|
194
|
+
const registryRaw = readFileSync2(join3(agentAssetsDir, "registry.json"), "utf8");
|
|
195
|
+
const configRaw = readFileSync2(join3(options.catalogRoot, "catalog.config.json"), "utf8");
|
|
196
|
+
const topicIds = new Set(config.topics.map((topic) => topic.id));
|
|
197
|
+
const bundleMembership = createBundleMembership(options.bundles);
|
|
198
|
+
const entries = [
|
|
199
|
+
...options.registry.assets.map(
|
|
200
|
+
(asset) => createRegisteredEntry(asset, agentAssetsDir, config, topicIds, bundleMembership)
|
|
201
|
+
),
|
|
202
|
+
...config.extraEntries.map(
|
|
203
|
+
(asset) => createExtraEntry(asset, agentAssetsDir, topicIds, bundleMembership)
|
|
204
|
+
)
|
|
205
|
+
].sort(compareCatalogEntries);
|
|
206
|
+
assertUniqueEntries(entries);
|
|
207
|
+
const uncategorized = entries.filter((entry) => entry.topics.length === 0);
|
|
208
|
+
if (uncategorized.length > 0) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Uncategorized catalog assets:
|
|
211
|
+
${uncategorized.map((entry) => `- ${entry.id}`).join("\n")}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
const uncovered = findUncoveredSourcePaths(
|
|
215
|
+
agentAssetsDir,
|
|
216
|
+
entries,
|
|
217
|
+
config.ignoredSourcePaths ?? []
|
|
218
|
+
);
|
|
219
|
+
if (uncovered.length > 0) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Agent asset sources are not represented in the catalog:
|
|
222
|
+
${uncovered.map((path) => `- ${path}`).join("\n")}`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const usedBundleIds = new Set(entries.flatMap((entry) => entry.bundles));
|
|
226
|
+
const document = {
|
|
227
|
+
schemaVersion: 1,
|
|
228
|
+
title: config.title,
|
|
229
|
+
description: config.description,
|
|
230
|
+
registryAssetCount: options.registry.assets.length,
|
|
231
|
+
extraEntryCount: config.extraEntries.length,
|
|
232
|
+
assetCount: entries.length,
|
|
233
|
+
topicCount: config.topics.length,
|
|
234
|
+
bundleCount: usedBundleIds.size,
|
|
235
|
+
registryDigest: sha256(registryRaw),
|
|
236
|
+
configDigest: sha256(configRaw),
|
|
237
|
+
topics: config.topics.map((topic) => ({
|
|
238
|
+
id: topic.id,
|
|
239
|
+
title: topic.title,
|
|
240
|
+
description: topic.description,
|
|
241
|
+
assetCount: entries.filter((entry) => entry.topics.includes(topic.id)).length
|
|
242
|
+
})),
|
|
243
|
+
entries
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
document,
|
|
247
|
+
entries,
|
|
248
|
+
files: renderPortableCatalog(document, config, options.catalogRoot, agentAssetsDir)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function createRegisteredEntry(asset, agentAssetsDir, config, topicIds, bundleMembership) {
|
|
252
|
+
const sourceAbsolutePath = resolveSafeSourcePath(agentAssetsDir, asset.sourcePath, asset.id);
|
|
253
|
+
const sourceType = statSync(sourceAbsolutePath).isDirectory() ? "directory" : "file";
|
|
254
|
+
const draft = {
|
|
255
|
+
id: asset.id,
|
|
256
|
+
title: asset.title,
|
|
257
|
+
description: readAssetDescription(sourceAbsolutePath, sourceType, asset.title),
|
|
258
|
+
family: asset.family,
|
|
259
|
+
kind: asset.kind,
|
|
260
|
+
domain: asset.domain,
|
|
261
|
+
visibility: asset.visibility,
|
|
262
|
+
sourceKind: asset.sourceKind,
|
|
263
|
+
sourcePath: normalizeRelativePath(asset.sourcePath),
|
|
264
|
+
sourceType,
|
|
265
|
+
tags: [...asset.tags],
|
|
266
|
+
bundles: [...bundleMembership.get(asset.id) ?? []],
|
|
267
|
+
origin: asset.origin,
|
|
268
|
+
registered: true
|
|
269
|
+
};
|
|
270
|
+
const topics = config.topics.filter((topic) => matchesTopic(draft, topic)).map((topic) => topic.id);
|
|
271
|
+
assertKnownTopics(asset.id, topics, topicIds);
|
|
272
|
+
return { ...draft, topics };
|
|
273
|
+
}
|
|
274
|
+
function createExtraEntry(asset, agentAssetsDir, topicIds, bundleMembership) {
|
|
275
|
+
const sourceAbsolutePath = resolveSafeSourcePath(agentAssetsDir, asset.sourcePath, asset.id);
|
|
276
|
+
const sourceType = statSync(sourceAbsolutePath).isDirectory() ? "directory" : "file";
|
|
277
|
+
assertKnownTopics(asset.id, asset.topics, topicIds);
|
|
278
|
+
return {
|
|
279
|
+
id: asset.id,
|
|
280
|
+
title: asset.title,
|
|
281
|
+
description: asset.description,
|
|
282
|
+
family: asset.family,
|
|
283
|
+
kind: asset.kind,
|
|
284
|
+
visibility: "private",
|
|
285
|
+
sourceKind: "local",
|
|
286
|
+
sourcePath: normalizeRelativePath(asset.sourcePath),
|
|
287
|
+
sourceType,
|
|
288
|
+
tags: [...asset.tags],
|
|
289
|
+
topics: [...new Set(asset.topics)].sort(),
|
|
290
|
+
bundles: [...bundleMembership.get(asset.id) ?? []],
|
|
291
|
+
origin: asset.origin,
|
|
292
|
+
registered: false
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function validateAssetCatalogConfig(config) {
|
|
296
|
+
const issues = [];
|
|
297
|
+
if (config.schemaVersion !== 1) issues.push("schemaVersion must be 1");
|
|
298
|
+
if (!config.title?.trim()) issues.push("title is required");
|
|
299
|
+
if (!config.description?.trim()) issues.push("description is required");
|
|
300
|
+
if (!Array.isArray(config.topics) || config.topics.length === 0) {
|
|
301
|
+
issues.push("topics must be a non-empty array");
|
|
302
|
+
}
|
|
303
|
+
if (!Array.isArray(config.extraEntries)) issues.push("extraEntries must be an array");
|
|
304
|
+
const topicIds = /* @__PURE__ */ new Set();
|
|
305
|
+
for (const topic of config.topics ?? []) {
|
|
306
|
+
if (!slugPattern.test(topic.id)) issues.push(`invalid topic id: ${topic.id}`);
|
|
307
|
+
if (topicIds.has(topic.id)) issues.push(`duplicate topic id: ${topic.id}`);
|
|
308
|
+
topicIds.add(topic.id);
|
|
309
|
+
if (!topic.title?.trim()) issues.push(`topic ${topic.id} is missing title`);
|
|
310
|
+
if (!topic.description?.trim()) issues.push(`topic ${topic.id} is missing description`);
|
|
311
|
+
if (!Array.isArray(topic.selectors))
|
|
312
|
+
issues.push(`topic ${topic.id} selectors must be an array`);
|
|
313
|
+
for (const selector of [...topic.selectors ?? [], ...topic.excludeSelectors ?? []]) {
|
|
314
|
+
const separator = selector.indexOf(":");
|
|
315
|
+
const field = separator > 0 ? selector.slice(0, separator) : "";
|
|
316
|
+
const pattern = separator > 0 ? selector.slice(separator + 1) : "";
|
|
317
|
+
if (!selectorFields.has(field) || !pattern) {
|
|
318
|
+
issues.push(`invalid selector in ${topic.id}: ${selector}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const extraIds = /* @__PURE__ */ new Set();
|
|
323
|
+
for (const entry of config.extraEntries ?? []) {
|
|
324
|
+
if (!entry.id?.includes("/")) issues.push(`invalid extra entry id: ${entry.id}`);
|
|
325
|
+
if (extraIds.has(entry.id)) issues.push(`duplicate extra entry id: ${entry.id}`);
|
|
326
|
+
extraIds.add(entry.id);
|
|
327
|
+
if (!entry.title?.trim()) issues.push(`extra entry ${entry.id} is missing title`);
|
|
328
|
+
if (!entry.family?.trim()) issues.push(`extra entry ${entry.id} is missing family`);
|
|
329
|
+
if (!entry.kind?.trim()) issues.push(`extra entry ${entry.id} is missing kind`);
|
|
330
|
+
if (!entry.description?.trim()) issues.push(`extra entry ${entry.id} is missing description`);
|
|
331
|
+
if (!Array.isArray(entry.tags)) issues.push(`extra entry ${entry.id} tags must be an array`);
|
|
332
|
+
if (!Array.isArray(entry.topics) || entry.topics.length === 0) {
|
|
333
|
+
issues.push(`extra entry ${entry.id} must name at least one topic`);
|
|
334
|
+
}
|
|
335
|
+
for (const topic of entry.topics ?? []) {
|
|
336
|
+
if (!topicIds.has(topic)) issues.push(`extra entry ${entry.id} has unknown topic: ${topic}`);
|
|
337
|
+
}
|
|
338
|
+
if (!isSafeSourcePath(entry.sourcePath)) {
|
|
339
|
+
issues.push(`extra entry ${entry.id} has unsafe sourcePath: ${entry.sourcePath}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (const path of config.ignoredSourcePaths ?? []) {
|
|
343
|
+
if (!isSafeSourcePath(path)) issues.push(`unsafe ignoredSourcePath: ${path}`);
|
|
344
|
+
}
|
|
345
|
+
return issues;
|
|
346
|
+
}
|
|
347
|
+
function matchesTopic(entry, topic) {
|
|
348
|
+
if (topic.excludeSelectors?.some((selector) => matchesSelector(entry, selector))) return false;
|
|
349
|
+
return topic.selectors.some((selector) => matchesSelector(entry, selector));
|
|
350
|
+
}
|
|
351
|
+
function matchesSelector(entry, selector) {
|
|
352
|
+
const separator = selector.indexOf(":");
|
|
353
|
+
const field = selector.slice(0, separator);
|
|
354
|
+
const pattern = selector.slice(separator + 1);
|
|
355
|
+
const values = field === "tag" ? entry.tags : [String(entry[field] ?? "")];
|
|
356
|
+
return values.some((value) => wildcardMatches(value, pattern));
|
|
357
|
+
}
|
|
358
|
+
function wildcardMatches(value, pattern) {
|
|
359
|
+
const expression = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
360
|
+
return new RegExp(`^${expression}$`, "iu").test(value);
|
|
361
|
+
}
|
|
362
|
+
function renderPortableCatalog(document, config, catalogRoot, agentAssetsDir) {
|
|
363
|
+
const files = /* @__PURE__ */ new Map();
|
|
364
|
+
files.set("catalog.json", `${JSON.stringify(document, null, 2)}
|
|
365
|
+
`);
|
|
366
|
+
files.set("INDEX.md", renderMainIndex(document));
|
|
367
|
+
files.set("index.html", renderHtmlCatalog(document, catalogRoot, agentAssetsDir));
|
|
368
|
+
for (const topic of config.topics) {
|
|
369
|
+
const entries = document.entries.filter((entry) => entry.topics.includes(topic.id));
|
|
370
|
+
const indexPath = join3(catalogRoot, "by-topic", topic.id, "README.md");
|
|
371
|
+
files.set(
|
|
372
|
+
`by-topic/${topic.id}/README.md`,
|
|
373
|
+
renderGroupIndex(topic.title, topic.description, entries, indexPath, agentAssetsDir)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return files;
|
|
377
|
+
}
|
|
378
|
+
function renderHtmlCatalog(document, catalogRoot, agentAssetsDir) {
|
|
379
|
+
const viewEntries = document.entries.map((entry) => ({
|
|
380
|
+
...entry,
|
|
381
|
+
sourceHref: encodeRelativeHref(
|
|
382
|
+
relative2(catalogRoot, join3(agentAssetsDir, ...entry.sourcePath.split("/")))
|
|
383
|
+
)
|
|
384
|
+
}));
|
|
385
|
+
const data = JSON.stringify({ ...document, entries: viewEntries }).replaceAll("<", "\\u003c");
|
|
386
|
+
return `<!doctype html>
|
|
387
|
+
<html lang="zh-CN">
|
|
388
|
+
<head>
|
|
389
|
+
<meta charset="utf-8">
|
|
390
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
391
|
+
<title>${escapeHtml(document.title)}</title>
|
|
392
|
+
<style>
|
|
393
|
+
:root { color-scheme: light; --ink:#17202a; --muted:#607080; --line:#dbe3ea; --paper:#fff; --wash:#f4f7fa; --accent:#1769aa; --chip:#eaf3fb; }
|
|
394
|
+
* { box-sizing:border-box; }
|
|
395
|
+
body { margin:0; background:var(--wash); color:var(--ink); font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif; }
|
|
396
|
+
header { padding:38px clamp(20px,5vw,72px) 28px; color:#fff; background:linear-gradient(125deg,#10283d,#1769aa 70%,#1e8b8a); }
|
|
397
|
+
header h1 { margin:0 0 8px; font-size:clamp(28px,4vw,44px); letter-spacing:-.03em; }
|
|
398
|
+
header p { max-width:800px; margin:0; opacity:.88; }
|
|
399
|
+
main { max-width:1440px; margin:auto; padding:24px clamp(16px,4vw,56px) 60px; }
|
|
400
|
+
.toolbar { position:sticky; top:0; z-index:2; display:grid; grid-template-columns:minmax(240px,2fr) repeat(3,minmax(150px,1fr)); gap:10px; padding:14px 0; background:color-mix(in srgb,var(--wash) 92%,transparent); backdrop-filter:blur(12px); }
|
|
401
|
+
input,select { width:100%; border:1px solid var(--line); border-radius:10px; background:var(--paper); padding:11px 12px; color:var(--ink); font:inherit; }
|
|
402
|
+
.summary { display:flex; justify-content:space-between; gap:16px; align-items:center; margin:10px 0 18px; color:var(--muted); }
|
|
403
|
+
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(310px,1fr)); gap:14px; }
|
|
404
|
+
article { min-width:0; padding:18px; border:1px solid var(--line); border-radius:14px; background:var(--paper); box-shadow:0 4px 18px rgba(28,55,75,.05); }
|
|
405
|
+
article h2 { margin:0 0 5px; font-size:18px; line-height:1.3; }
|
|
406
|
+
article p { min-height:3.1em; margin:8px 0 12px; color:#405160; }
|
|
407
|
+
.meta { color:var(--muted); font-size:13px; overflow-wrap:anywhere; }
|
|
408
|
+
.chips { display:flex; flex-wrap:wrap; gap:6px; margin:12px 0; }
|
|
409
|
+
.chip { padding:3px 8px; border-radius:999px; background:var(--chip); color:#155582; font-size:12px; }
|
|
410
|
+
.actions { display:flex; gap:12px; margin-top:13px; }
|
|
411
|
+
a { color:var(--accent); text-decoration:none; font-weight:600; }
|
|
412
|
+
a:hover { text-decoration:underline; }
|
|
413
|
+
.empty { padding:44px; text-align:center; color:var(--muted); border:1px dashed var(--line); border-radius:14px; background:var(--paper); }
|
|
414
|
+
@media (max-width:850px) { .toolbar { grid-template-columns:1fr 1fr; } }
|
|
415
|
+
@media (max-width:540px) { .toolbar { position:static; grid-template-columns:1fr; } }
|
|
416
|
+
</style>
|
|
417
|
+
</head>
|
|
418
|
+
<body>
|
|
419
|
+
<header>
|
|
420
|
+
<h1>${escapeHtml(document.title)}</h1>
|
|
421
|
+
<p>${escapeHtml(document.description)} \u5F53\u524D\u5171 ${document.assetCount} \u9879\uFF1B\u540C\u4E00\u8D44\u4EA7\u53EF\u4EE5\u5C5E\u4E8E\u591A\u4E2A\u4E3B\u9898\u3002</p>
|
|
422
|
+
</header>
|
|
423
|
+
<main>
|
|
424
|
+
<section class="toolbar" aria-label="\u76EE\u5F55\u7B5B\u9009">
|
|
425
|
+
<input id="query" type="search" placeholder="\u641C\u7D22\u540D\u79F0\u3001\u7528\u9014\u3001\u6807\u7B7E\u3001\u4F5C\u8005\u6216\u8DEF\u5F84\u2026" autofocus>
|
|
426
|
+
<select id="topic" aria-label="\u6309\u4E3B\u9898\u7B5B\u9009"><option value="">\u5168\u90E8\u4E3B\u9898</option></select>
|
|
427
|
+
<select id="family" aria-label="\u6309\u6765\u6E90\u7B5B\u9009"><option value="">\u5168\u90E8\u6765\u6E90</option></select>
|
|
428
|
+
<select id="kind" aria-label="\u6309\u7C7B\u578B\u7B5B\u9009"><option value="">\u5168\u90E8\u7C7B\u578B</option></select>
|
|
429
|
+
</section>
|
|
430
|
+
<div class="summary"><span id="count"></span><a href="INDEX.md">\u6253\u5F00 Markdown \u76EE\u5F55</a></div>
|
|
431
|
+
<section id="grid" class="grid"></section>
|
|
432
|
+
</main>
|
|
433
|
+
<script id="catalog-data" type="application/json">${data}</script>
|
|
434
|
+
<script>
|
|
435
|
+
const data = JSON.parse(document.getElementById('catalog-data').textContent);
|
|
436
|
+
const byId = (id) => document.getElementById(id);
|
|
437
|
+
const query = byId('query');
|
|
438
|
+
const topic = byId('topic');
|
|
439
|
+
const family = byId('family');
|
|
440
|
+
const kind = byId('kind');
|
|
441
|
+
const grid = byId('grid');
|
|
442
|
+
const count = byId('count');
|
|
443
|
+
const esc = (value) => String(value).replace(/[&<>"']/g, (char) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
|
444
|
+
const options = (select, values, labels = {}) => {
|
|
445
|
+
for (const value of [...new Set(values)].sort((a,b) => a.localeCompare(b))) {
|
|
446
|
+
const option = document.createElement('option'); option.value = value; option.textContent = labels[value] || value; select.append(option);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
options(topic, data.topics.map((item) => item.id), Object.fromEntries(data.topics.map((item) => [item.id, item.title])));
|
|
450
|
+
options(family, data.entries.map((item) => item.family));
|
|
451
|
+
options(kind, data.entries.map((item) => item.kind));
|
|
452
|
+
const render = () => {
|
|
453
|
+
const needle = query.value.trim().toLocaleLowerCase();
|
|
454
|
+
const matches = data.entries.filter((entry) => {
|
|
455
|
+
const haystack = [entry.title,entry.id,entry.description,entry.family,entry.kind,entry.origin,entry.sourceKind,entry.visibility,entry.sourcePath,...entry.tags,...entry.topics,...entry.bundles].join(' ').toLocaleLowerCase();
|
|
456
|
+
return (!needle || haystack.includes(needle)) && (!topic.value || entry.topics.includes(topic.value)) && (!family.value || entry.family === family.value) && (!kind.value || entry.kind === kind.value);
|
|
457
|
+
});
|
|
458
|
+
count.textContent = '\u663E\u793A ' + matches.length + ' / ' + data.entries.length + ' \u9879';
|
|
459
|
+
grid.innerHTML = matches.length ? matches.map((entry) => '<article><h2>' + esc(entry.title) + '</h2><div class="meta">' + esc(entry.id) + ' \xB7 ' + esc(entry.kind) + ' \xB7 ' + esc(entry.family) + '</div><p>' + esc(entry.description) + '</p><div class="chips">' + entry.topics.map((item) => '<span class="chip">' + esc(item) + '</span>').join('') + '</div><div class="meta">' + esc(entry.origin) + '</div><div class="actions"><a href="' + esc(entry.sourceHref) + '">\u6253\u5F00\u539F\u59CB\u8D44\u4EA7</a></div></article>').join('') : '<div class="empty">\u6CA1\u6709\u5339\u914D\u9879\u3002\u8BD5\u8BD5\u66F4\u77ED\u7684\u5173\u952E\u8BCD\uFF0C\u6216\u6E05\u7A7A\u4E00\u4E2A\u7B5B\u9009\u6761\u4EF6\u3002</div>';
|
|
460
|
+
};
|
|
461
|
+
for (const control of [query,topic,family,kind]) control.addEventListener('input', render);
|
|
462
|
+
render();
|
|
463
|
+
</script>
|
|
464
|
+
</body>
|
|
465
|
+
</html>
|
|
466
|
+
`;
|
|
467
|
+
}
|
|
468
|
+
function renderMainIndex(document) {
|
|
469
|
+
const lines = [
|
|
470
|
+
`# ${document.title}`,
|
|
471
|
+
"",
|
|
472
|
+
document.description,
|
|
473
|
+
"",
|
|
474
|
+
`\u5F53\u524D\u6536\u5F55 **${document.assetCount}** \u9879\uFF1A\u6CE8\u518C\u8868\u8D44\u4EA7 ${document.registryAssetCount} \u9879\uFF0C\u8865\u5145\u96C6\u5408 ${document.extraEntryCount} \u9879\u3002`,
|
|
475
|
+
"",
|
|
476
|
+
"## \u6309\u4E3B\u9898\u627E",
|
|
477
|
+
"",
|
|
478
|
+
"| \u4E3B\u9898 | \u6570\u91CF | \u9002\u7528\u8303\u56F4 |",
|
|
479
|
+
"| --- | ---: | --- |"
|
|
480
|
+
];
|
|
481
|
+
for (const topic of document.topics) {
|
|
482
|
+
lines.push(
|
|
483
|
+
`| [${escapeTable(topic.title)}](<by-topic/${topic.id}/README.md>) | ${topic.assetCount} | ${escapeTable(topic.description)} |`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
lines.push(
|
|
487
|
+
"",
|
|
488
|
+
"## \u600E\u4E48\u7528",
|
|
489
|
+
"",
|
|
490
|
+
"- \u53CC\u51FB [\u672C\u5730\u641C\u7D22\u9875](<index.html>)\uFF0C\u53EF\u6309\u5173\u952E\u8BCD\u3001\u4E3B\u9898\u3001\u6765\u6E90\u548C\u7C7B\u578B\u7B5B\u9009\u3002",
|
|
491
|
+
"- \u76F4\u63A5\u8FDB\u5165 `by-topic/`\uFF0C\u6309\u7528\u9014\u6D4F\u89C8\uFF1B\u6BCF\u4E2A\u4E3B\u9898\u7684 README \u90FD\u76F4\u63A5\u94FE\u63A5\u539F\u59CB\u8D44\u4EA7\u3002",
|
|
492
|
+
"- \u6765\u6E90\u3001\u4F5C\u8005\u3001\u7C7B\u578B\u548C Bundle \u662F\u8D44\u4EA7\u5C5E\u6027\uFF0C\u53EA\u4FDD\u7559\u5728\u641C\u7D22\u4E0E\u6570\u636E\u4E2D\uFF0C\u4E0D\u518D\u590D\u5236\u6210\u591A\u5957\u6587\u4EF6\u5939\u3002",
|
|
493
|
+
"",
|
|
494
|
+
"\u672C\u673A\u6267\u884C\u5E26 `--native-links` \u7684\u6784\u5EFA\u540E\uFF0C\u5FEB\u6377\u94FE\u63A5\u4F1A\u76F4\u63A5\u51FA\u73B0\u5728\u5404\u4E3B\u9898\u6587\u4EF6\u5939\u4E2D\uFF0C\u5E76\u4EE5 `.asset-link` \u7ED3\u5C3E\u3002\u94FE\u63A5\u662F\u53EF\u5220\u9664\u3001\u53EF\u91CD\u5EFA\u7684\u672C\u673A\u7F13\u5B58\uFF1BREADME\u3001HTML \u548C JSON \u624D\u662F\u8DE8\u5E73\u53F0\u4E8B\u5B9E\u5C42\u3002",
|
|
495
|
+
""
|
|
496
|
+
);
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
function renderGroupIndex(title, description, entries, indexAbsolutePath, agentAssetsDir) {
|
|
500
|
+
const lines = [
|
|
501
|
+
`# ${title}`,
|
|
502
|
+
"",
|
|
503
|
+
description,
|
|
504
|
+
"",
|
|
505
|
+
`\u5171 ${entries.length} \u9879\u3002`,
|
|
506
|
+
"",
|
|
507
|
+
"| \u540D\u79F0 | \u7C7B\u578B | \u6765\u6E90 | \u8BF4\u660E |",
|
|
508
|
+
"| --- | --- | --- | --- |"
|
|
509
|
+
];
|
|
510
|
+
for (const entry of entries) {
|
|
511
|
+
const sourcePath = normalizeRelativePath(
|
|
512
|
+
relative2(dirname2(indexAbsolutePath), join3(agentAssetsDir, ...entry.sourcePath.split("/")))
|
|
513
|
+
);
|
|
514
|
+
lines.push(
|
|
515
|
+
`| [${escapeTable(entry.title)}](<${sourcePath}>) | ${escapeTable(entry.kind)} | ${escapeTable(entry.family)} | ${escapeTable(entry.description)} |`
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
lines.push(
|
|
519
|
+
"",
|
|
520
|
+
"\u672C\u673A\u5FEB\u6377\u94FE\u63A5\uFF08\u5982\u5DF2\u751F\u6210\uFF09\u5C31\u5728\u672C\u76EE\u5F55\uFF0C\u6587\u4EF6\u540D\u4EE5 `.asset-link` \u7ED3\u5C3E\uFF1B\u6CA1\u6709\u94FE\u63A5\u65F6\uFF0C\u4E0A\u8868\u4ECD\u53EF\u76F4\u63A5\u6253\u5F00\u539F\u59CB\u8D44\u4EA7\u3002",
|
|
521
|
+
"",
|
|
522
|
+
"[\u8FD4\u56DE\u603B\u7D22\u5F15](<../../INDEX.md>)",
|
|
523
|
+
""
|
|
524
|
+
);
|
|
525
|
+
return lines.join("\n");
|
|
526
|
+
}
|
|
527
|
+
function writeNativeLinks(options) {
|
|
528
|
+
const specs = createNativeLinkSpecs(options.agentAssetsDir, options.entries);
|
|
529
|
+
const previous = readLocalLinksManifest(options.catalogRoot);
|
|
530
|
+
const previousLinks = new Set(previous?.links ?? []);
|
|
531
|
+
for (const relativePath of previousLinks) {
|
|
532
|
+
assertManagedLocalLinkPath(relativePath);
|
|
533
|
+
const linkPath = resolveCatalogPath(options.catalogRoot, relativePath);
|
|
534
|
+
if (pathEntryExists(linkPath) && !lstatSync(linkPath).isSymbolicLink()) {
|
|
535
|
+
throw new Error(
|
|
536
|
+
`Refusing to replace a managed link that is no longer a symlink: ${relativePath}`
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
for (const spec of specs) {
|
|
541
|
+
assertManagedLocalLinkPath(spec.relativePath);
|
|
542
|
+
assertCatalogPathParents(options.catalogRoot, spec.relativePath);
|
|
543
|
+
const linkPath = resolveCatalogPath(options.catalogRoot, spec.relativePath);
|
|
544
|
+
if (pathEntryExists(linkPath) && !previousLinks.has(spec.relativePath)) {
|
|
545
|
+
throw new Error(`Refusing to replace unmanaged catalog path: ${spec.relativePath}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const warnings = [];
|
|
549
|
+
const stagedLinks = [];
|
|
550
|
+
const stageRoot = mkdtempSync(join3(dirname2(options.catalogRoot), ".asset-catalog-links-stage-"));
|
|
551
|
+
const backupRoot = mkdtempSync(
|
|
552
|
+
join3(dirname2(options.catalogRoot), ".asset-catalog-links-backup-")
|
|
553
|
+
);
|
|
554
|
+
const installed = [];
|
|
555
|
+
const backedUp = [];
|
|
556
|
+
try {
|
|
557
|
+
for (const spec of specs) {
|
|
558
|
+
const stagedPath = resolveCatalogPath(stageRoot, spec.relativePath);
|
|
559
|
+
const finalPath = resolveCatalogPath(options.catalogRoot, spec.relativePath);
|
|
560
|
+
mkdirSync(dirname2(stagedPath), { recursive: true });
|
|
561
|
+
const strategy = nativeLinkStrategy(options.platform, spec.sourceType);
|
|
562
|
+
try {
|
|
563
|
+
if (strategy === "relative-symlink") {
|
|
564
|
+
const target = relative2(dirname2(finalPath), spec.sourceAbsolutePath);
|
|
565
|
+
symlinkSync(target, stagedPath, spec.sourceType === "directory" ? "dir" : "file");
|
|
566
|
+
} else if (strategy === "junction") {
|
|
567
|
+
symlinkSync(spec.sourceAbsolutePath, stagedPath, "junction");
|
|
568
|
+
} else {
|
|
569
|
+
symlinkSync(spec.sourceAbsolutePath, stagedPath, "file");
|
|
570
|
+
}
|
|
571
|
+
stagedLinks.push(spec.relativePath);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
574
|
+
warnings.push(`native link skipped (${spec.relativePath}): ${reason}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const localManifest = renderLocalLinksManifest(stagedLinks);
|
|
578
|
+
writeFileSync(join3(stageRoot, localLinksManifestName), localManifest);
|
|
579
|
+
for (const relativePath of [...previousLinks, localLinksManifestName]) {
|
|
580
|
+
const sourcePath = resolveCatalogPath(options.catalogRoot, relativePath);
|
|
581
|
+
if (!pathEntryExists(sourcePath)) continue;
|
|
582
|
+
const backupPath = resolveCatalogPath(backupRoot, relativePath);
|
|
583
|
+
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
584
|
+
renameSync(sourcePath, backupPath);
|
|
585
|
+
backedUp.push(relativePath);
|
|
586
|
+
}
|
|
587
|
+
for (const relativePath of [...stagedLinks, localLinksManifestName]) {
|
|
588
|
+
const sourcePath = resolveCatalogPath(stageRoot, relativePath);
|
|
589
|
+
const targetPath = resolveCatalogPath(options.catalogRoot, relativePath);
|
|
590
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
591
|
+
renameSync(sourcePath, targetPath);
|
|
592
|
+
installed.push(relativePath);
|
|
593
|
+
}
|
|
594
|
+
for (const relativePath of previousLinks) {
|
|
595
|
+
pruneEmptyCatalogParents(options.catalogRoot, dirname2(relativePath));
|
|
596
|
+
}
|
|
597
|
+
} catch (error) {
|
|
598
|
+
for (const relativePath of installed.reverse()) {
|
|
599
|
+
const targetPath = resolveCatalogPath(options.catalogRoot, relativePath);
|
|
600
|
+
if (pathEntryExists(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
601
|
+
}
|
|
602
|
+
for (const relativePath of backedUp.reverse()) {
|
|
603
|
+
const backupPath = resolveCatalogPath(backupRoot, relativePath);
|
|
604
|
+
const targetPath = resolveCatalogPath(options.catalogRoot, relativePath);
|
|
605
|
+
if (!pathEntryExists(backupPath)) continue;
|
|
606
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
607
|
+
renameSync(backupPath, targetPath);
|
|
608
|
+
}
|
|
609
|
+
throw error;
|
|
610
|
+
} finally {
|
|
611
|
+
removeTemporaryTree(stageRoot, warnings);
|
|
612
|
+
removeTemporaryTree(backupRoot, warnings);
|
|
613
|
+
}
|
|
614
|
+
return { linkCount: stagedLinks.length, warnings };
|
|
615
|
+
}
|
|
616
|
+
function createNativeLinkSpecs(agentAssetsDir, entries) {
|
|
617
|
+
const specs = [];
|
|
618
|
+
const add = (topic, entry) => {
|
|
619
|
+
specs.push({
|
|
620
|
+
relativePath: `by-topic/${safePathSegment(topic)}/${nativeLinkName(entry)}`,
|
|
621
|
+
sourceAbsolutePath: join3(agentAssetsDir, ...entry.sourcePath.split("/")),
|
|
622
|
+
sourceType: entry.sourceType
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
for (const entry of entries) {
|
|
626
|
+
for (const topic of entry.topics) add(topic, entry);
|
|
627
|
+
}
|
|
628
|
+
const sorted = specs.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
629
|
+
for (let index = 1; index < sorted.length; index += 1) {
|
|
630
|
+
if (sorted[index - 1].relativePath === sorted[index].relativePath) {
|
|
631
|
+
throw new Error(`Duplicate native catalog link path: ${sorted[index].relativePath}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return sorted;
|
|
635
|
+
}
|
|
636
|
+
function writePortableCatalog(catalogRoot, files) {
|
|
637
|
+
mkdirSync(catalogRoot, { recursive: true });
|
|
638
|
+
const warnings = [];
|
|
639
|
+
const previous = readPortableManifest(catalogRoot);
|
|
640
|
+
const previousFiles = new Set(previous?.portableFiles ?? []);
|
|
641
|
+
const expectedFiles = [...files.keys()].sort();
|
|
642
|
+
for (const relativePath of previousFiles) {
|
|
643
|
+
assertManagedPortablePath(relativePath);
|
|
644
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
645
|
+
if (pathEntryExists(targetPath)) {
|
|
646
|
+
const stats = lstatSync(targetPath);
|
|
647
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
`Refusing to replace a managed file that is no longer regular: ${relativePath}`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
for (const relativePath of expectedFiles) {
|
|
655
|
+
assertManagedPortablePath(relativePath);
|
|
656
|
+
assertCatalogPathParents(catalogRoot, relativePath);
|
|
657
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
658
|
+
if (pathEntryExists(targetPath) && !previousFiles.has(relativePath)) {
|
|
659
|
+
throw new Error(`Refusing to replace unmanaged catalog path: ${relativePath}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const legacyDirectories = findLegacyManagedDirectories(catalogRoot);
|
|
663
|
+
const stageRoot = mkdtempSync(join3(dirname2(catalogRoot), ".asset-catalog-stage-"));
|
|
664
|
+
const backupRoot = mkdtempSync(join3(dirname2(catalogRoot), ".asset-catalog-backup-"));
|
|
665
|
+
const installed = [];
|
|
666
|
+
const backedUp = [];
|
|
667
|
+
const legacyBackedUp = [];
|
|
668
|
+
try {
|
|
669
|
+
for (const [relativePath, content] of files) {
|
|
670
|
+
const stagedPath = resolveCatalogPath(stageRoot, relativePath);
|
|
671
|
+
mkdirSync(dirname2(stagedPath), { recursive: true });
|
|
672
|
+
writeFileSync(stagedPath, content);
|
|
673
|
+
}
|
|
674
|
+
writeFileSync(join3(stageRoot, portableManifestName), renderPortableManifest(files));
|
|
675
|
+
for (const relativePath of [...previousFiles, portableManifestName]) {
|
|
676
|
+
const sourcePath = resolveCatalogPath(catalogRoot, relativePath);
|
|
677
|
+
if (!pathEntryExists(sourcePath)) continue;
|
|
678
|
+
const backupPath = resolveCatalogPath(backupRoot, `portable/${relativePath}`);
|
|
679
|
+
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
680
|
+
renameSync(sourcePath, backupPath);
|
|
681
|
+
backedUp.push(relativePath);
|
|
682
|
+
}
|
|
683
|
+
for (const name of legacyDirectories) {
|
|
684
|
+
const sourcePath = join3(catalogRoot, name);
|
|
685
|
+
const backupPath = resolveCatalogPath(backupRoot, `legacy/${name}`);
|
|
686
|
+
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
687
|
+
renameSync(sourcePath, backupPath);
|
|
688
|
+
legacyBackedUp.push(name);
|
|
689
|
+
}
|
|
690
|
+
for (const relativePath of [...expectedFiles, portableManifestName]) {
|
|
691
|
+
const sourcePath = resolveCatalogPath(stageRoot, relativePath);
|
|
692
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
693
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
694
|
+
renameSync(sourcePath, targetPath);
|
|
695
|
+
installed.push(relativePath);
|
|
696
|
+
}
|
|
697
|
+
for (const relativePath of previousFiles) {
|
|
698
|
+
pruneEmptyCatalogParents(catalogRoot, dirname2(relativePath));
|
|
699
|
+
}
|
|
700
|
+
} catch (error) {
|
|
701
|
+
for (const relativePath of installed.reverse()) {
|
|
702
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
703
|
+
if (pathEntryExists(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
704
|
+
}
|
|
705
|
+
for (const relativePath of backedUp.reverse()) {
|
|
706
|
+
const backupPath = resolveCatalogPath(backupRoot, `portable/${relativePath}`);
|
|
707
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
708
|
+
if (!pathEntryExists(backupPath)) continue;
|
|
709
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
710
|
+
renameSync(backupPath, targetPath);
|
|
711
|
+
}
|
|
712
|
+
for (const name of legacyBackedUp.reverse()) {
|
|
713
|
+
const backupPath = resolveCatalogPath(backupRoot, `legacy/${name}`);
|
|
714
|
+
const targetPath = join3(catalogRoot, name);
|
|
715
|
+
if (pathEntryExists(backupPath)) renameSync(backupPath, targetPath);
|
|
716
|
+
}
|
|
717
|
+
throw error;
|
|
718
|
+
} finally {
|
|
719
|
+
removeTemporaryTree(stageRoot, warnings);
|
|
720
|
+
removeTemporaryTree(backupRoot, warnings);
|
|
721
|
+
}
|
|
722
|
+
return warnings;
|
|
723
|
+
}
|
|
724
|
+
function renderPortableManifest(files) {
|
|
725
|
+
const manifest = {
|
|
726
|
+
schemaVersion: 1,
|
|
727
|
+
generator: manifestGenerator,
|
|
728
|
+
portableFiles: [...files.keys()].sort()
|
|
729
|
+
};
|
|
730
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
731
|
+
`;
|
|
732
|
+
}
|
|
733
|
+
function renderLocalLinksManifest(links) {
|
|
734
|
+
const manifest = {
|
|
735
|
+
schemaVersion: 1,
|
|
736
|
+
generator: manifestGenerator,
|
|
737
|
+
links: [...links].sort()
|
|
738
|
+
};
|
|
739
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
740
|
+
`;
|
|
741
|
+
}
|
|
742
|
+
function readPortableManifest(catalogRoot) {
|
|
743
|
+
const path = join3(catalogRoot, portableManifestName);
|
|
744
|
+
if (!existsSync3(path)) return void 0;
|
|
745
|
+
const value = readManifest(path, "portableFiles");
|
|
746
|
+
for (const relativePath of value.items) assertManagedPortablePath(relativePath);
|
|
747
|
+
return {
|
|
748
|
+
schemaVersion: 1,
|
|
749
|
+
generator: manifestGenerator,
|
|
750
|
+
portableFiles: value.items
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function readLocalLinksManifest(catalogRoot) {
|
|
754
|
+
const path = join3(catalogRoot, localLinksManifestName);
|
|
755
|
+
if (!existsSync3(path)) return void 0;
|
|
756
|
+
const value = readManifest(path, "links");
|
|
757
|
+
for (const relativePath of value.items) assertManagedLocalLinkPath(relativePath);
|
|
758
|
+
return { schemaVersion: 1, generator: manifestGenerator, links: value.items };
|
|
759
|
+
}
|
|
760
|
+
function readManifest(path, listKey) {
|
|
761
|
+
const stats = lstatSync(path);
|
|
762
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
763
|
+
throw new Error(`Invalid asset catalog manifest file: ${path}`);
|
|
764
|
+
}
|
|
765
|
+
let parsed;
|
|
766
|
+
try {
|
|
767
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
768
|
+
} catch {
|
|
769
|
+
throw new Error(`Invalid asset catalog manifest JSON: ${path}`);
|
|
770
|
+
}
|
|
771
|
+
const items = parsed[listKey];
|
|
772
|
+
if (parsed.schemaVersion !== 1 || parsed.generator !== manifestGenerator || !Array.isArray(items) || items.some((item) => typeof item !== "string") || new Set(items).size !== items.length) {
|
|
773
|
+
throw new Error(`Invalid asset catalog manifest shape: ${path}`);
|
|
774
|
+
}
|
|
775
|
+
return { items: [...items].sort() };
|
|
776
|
+
}
|
|
777
|
+
function findLegacyManagedDirectories(catalogRoot) {
|
|
778
|
+
const result = [];
|
|
779
|
+
for (const name of legacyManagedDirectoryNames) {
|
|
780
|
+
const root = join3(catalogRoot, name);
|
|
781
|
+
if (!existsSync3(root)) continue;
|
|
782
|
+
const stats = lstatSync(root);
|
|
783
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
784
|
+
throw new Error(`Refusing to migrate non-directory legacy catalog output: ${root}`);
|
|
785
|
+
}
|
|
786
|
+
const sentinel = join3(root, legacyManagedSentinel);
|
|
787
|
+
if (!existsSync3(sentinel) || !lstatSync(sentinel).isFile()) {
|
|
788
|
+
throw new Error(`Refusing to migrate unmanaged legacy catalog directory: ${root}`);
|
|
789
|
+
}
|
|
790
|
+
result.push(name);
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
function assertManagedPortablePath(relativePath) {
|
|
795
|
+
if (relativePath !== "INDEX.md" && relativePath !== "index.html" && relativePath !== "catalog.json" && !/^by-topic\/[a-z0-9]+(?:-[a-z0-9]+)*\/README\.md$/u.test(relativePath)) {
|
|
796
|
+
throw new Error(`Unsafe managed portable catalog path: ${relativePath}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function assertManagedLocalLinkPath(relativePath) {
|
|
800
|
+
if (!/^by-topic\/[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9][a-z0-9._-]*\.asset-link$/u.test(relativePath)) {
|
|
801
|
+
throw new Error(`Unsafe managed local catalog link path: ${relativePath}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
function resolveCatalogPath(root, relativePath) {
|
|
805
|
+
const absoluteRoot = resolve(root);
|
|
806
|
+
const absolutePath = resolve(absoluteRoot, ...normalizeRelativePath(relativePath).split("/"));
|
|
807
|
+
const back = normalizeRelativePath(relative2(absoluteRoot, absolutePath));
|
|
808
|
+
if (back === ".." || back.startsWith("../") || isAbsolute(back)) {
|
|
809
|
+
throw new Error(`Catalog path escapes its managed root: ${relativePath}`);
|
|
810
|
+
}
|
|
811
|
+
return absolutePath;
|
|
812
|
+
}
|
|
813
|
+
function assertCatalogPathParents(catalogRoot, relativePath) {
|
|
814
|
+
const absoluteRoot = resolve(catalogRoot);
|
|
815
|
+
let current = dirname2(resolveCatalogPath(catalogRoot, relativePath));
|
|
816
|
+
while (current !== absoluteRoot) {
|
|
817
|
+
if (existsSync3(current)) {
|
|
818
|
+
const stats = lstatSync(current);
|
|
819
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`Refusing to write through a non-directory catalog parent: ${normalizeRelativePath(relative2(absoluteRoot, current))}`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const parent = dirname2(current);
|
|
826
|
+
if (parent === current) throw new Error(`Catalog parent escapes its root: ${relativePath}`);
|
|
827
|
+
current = parent;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function pruneEmptyCatalogParents(catalogRoot, relativeDirectory) {
|
|
831
|
+
const absoluteRoot = resolve(catalogRoot);
|
|
832
|
+
let current = resolveCatalogPath(catalogRoot, relativeDirectory);
|
|
833
|
+
while (current !== absoluteRoot && existsSync3(current)) {
|
|
834
|
+
const stats = lstatSync(current);
|
|
835
|
+
if (!stats.isDirectory() || stats.isSymbolicLink() || readdirSync3(current).length > 0) return;
|
|
836
|
+
rmSync(current, { recursive: true, force: true });
|
|
837
|
+
current = dirname2(current);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function pathEntryExists(path) {
|
|
841
|
+
try {
|
|
842
|
+
lstatSync(path);
|
|
843
|
+
return true;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
if (error.code === "ENOENT") return false;
|
|
846
|
+
throw error;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function removeTemporaryTree(path, warnings) {
|
|
850
|
+
try {
|
|
851
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
852
|
+
} catch (error) {
|
|
853
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
854
|
+
warnings.push(`temporary catalog cleanup pending (${path}): ${reason}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function findUncoveredSourcePaths(agentAssetsDir, entries, ignoredSourcePaths) {
|
|
858
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
859
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, "skills"))) {
|
|
860
|
+
if (basename(filePath) === "SKILL.md") {
|
|
861
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, dirname2(filePath))));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
for (const root of ["rules", "commands", "prompts"]) {
|
|
865
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, root))) {
|
|
866
|
+
if (extname(filePath).toLowerCase() === ".md") {
|
|
867
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, filePath)));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, "tooling"))) {
|
|
872
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, filePath)));
|
|
873
|
+
}
|
|
874
|
+
return [...candidates].filter((candidate) => !isCoveredByAnySource(candidate, entries, ignoredSourcePaths)).sort();
|
|
875
|
+
}
|
|
876
|
+
function isCoveredByAnySource(candidate, entries, ignoredSourcePaths) {
|
|
877
|
+
const covers = (sourcePath) => candidate === sourcePath || candidate.startsWith(`${sourcePath}/`);
|
|
878
|
+
return entries.some((entry) => covers(entry.sourcePath)) || ignoredSourcePaths.some(covers);
|
|
879
|
+
}
|
|
880
|
+
function listFiles2(root) {
|
|
881
|
+
if (!existsSync3(root)) return [];
|
|
882
|
+
const stats = lstatSync(root);
|
|
883
|
+
if (stats.isSymbolicLink()) return [];
|
|
884
|
+
if (stats.isFile()) return [root];
|
|
885
|
+
const files = [];
|
|
886
|
+
for (const entry of readdirSync3(root, { withFileTypes: true })) {
|
|
887
|
+
if (ignoredTraversalNames.has(entry.name) || entry.name.startsWith("._")) continue;
|
|
888
|
+
const path = join3(root, entry.name);
|
|
889
|
+
if (entry.isDirectory()) files.push(...listFiles2(path));
|
|
890
|
+
else if (entry.isFile()) files.push(path);
|
|
891
|
+
}
|
|
892
|
+
return files.sort();
|
|
893
|
+
}
|
|
894
|
+
function readAssetDescription(sourceAbsolutePath, sourceType, fallback) {
|
|
895
|
+
const documentPath = sourceType === "directory" ? join3(sourceAbsolutePath, "SKILL.md") : sourceAbsolutePath;
|
|
896
|
+
if (!existsSync3(documentPath)) return fallback;
|
|
897
|
+
const text = readFileSync2(documentPath, "utf8");
|
|
898
|
+
const frontmatter = text.match(/^---\r?\n([\s\S]*?)\r?\n---/u)?.[1] ?? "";
|
|
899
|
+
const description = extractFrontmatterDescription(frontmatter);
|
|
900
|
+
if (description) return compactDescription(description, fallback);
|
|
901
|
+
const withoutFrontmatter = text.replace(/^---\r?\n[\s\S]*?\r?\n---\s*/u, "");
|
|
902
|
+
const paragraph = withoutFrontmatter.split(/\r?\n\r?\n/u).map((part) => part.replace(/\r?\n/g, " ").trim()).find((part) => part && !part.startsWith("#") && !part.startsWith("```"));
|
|
903
|
+
return compactDescription(paragraph ?? fallback, fallback);
|
|
904
|
+
}
|
|
905
|
+
function extractFrontmatterDescription(frontmatter) {
|
|
906
|
+
const lines = frontmatter.split(/\r?\n/u);
|
|
907
|
+
const index = lines.findIndex((line) => /^description\s*:/u.test(line));
|
|
908
|
+
if (index < 0) return void 0;
|
|
909
|
+
const inlineValue = lines[index].replace(/^description\s*:\s*/u, "").trim();
|
|
910
|
+
if (!/^[|>][+-]?$/u.test(inlineValue)) {
|
|
911
|
+
return inlineValue ? stripWrappingQuotes(inlineValue) : void 0;
|
|
912
|
+
}
|
|
913
|
+
const block = [];
|
|
914
|
+
for (const line of lines.slice(index + 1)) {
|
|
915
|
+
if (line.trim() && !/^\s/u.test(line)) break;
|
|
916
|
+
block.push(line.replace(/^\s+/u, "").trim());
|
|
917
|
+
}
|
|
918
|
+
const separator = inlineValue.startsWith("|") ? "\n" : " ";
|
|
919
|
+
const value = block.join(separator).trim();
|
|
920
|
+
return value || void 0;
|
|
921
|
+
}
|
|
922
|
+
function compactDescription(value, fallback) {
|
|
923
|
+
const compact = value.replace(/\[([^\]]+)]\([^)]*\)/g, "$1").replace(/[`*_>#]/g, "").replace(/\s+/g, " ").trim();
|
|
924
|
+
if (!compact) return fallback;
|
|
925
|
+
return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
|
|
926
|
+
}
|
|
927
|
+
function stripWrappingQuotes(value) {
|
|
928
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
929
|
+
return value.slice(1, -1);
|
|
930
|
+
}
|
|
931
|
+
return value;
|
|
932
|
+
}
|
|
933
|
+
function resolveSafeSourcePath(agentAssetsDir, sourcePath, id) {
|
|
934
|
+
if (!isSafeSourcePath(sourcePath)) throw new Error(`Unsafe sourcePath for ${id}: ${sourcePath}`);
|
|
935
|
+
const absolutePath = resolve(agentAssetsDir, ...normalizeRelativePath(sourcePath).split("/"));
|
|
936
|
+
const relativePath = normalizeRelativePath(relative2(resolve(agentAssetsDir), absolutePath));
|
|
937
|
+
if (relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath)) {
|
|
938
|
+
throw new Error(`Source path escapes agent-assets for ${id}: ${sourcePath}`);
|
|
939
|
+
}
|
|
940
|
+
if (!existsSync3(absolutePath)) throw new Error(`Missing catalog source for ${id}: ${sourcePath}`);
|
|
941
|
+
return absolutePath;
|
|
942
|
+
}
|
|
943
|
+
function isSafeSourcePath(sourcePath) {
|
|
944
|
+
if (!sourcePath || isAbsolute(sourcePath) || sourcePath.includes("\\")) return false;
|
|
945
|
+
const normalized = normalizeRelativePath(sourcePath);
|
|
946
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith("../");
|
|
947
|
+
}
|
|
948
|
+
function createBundleMembership(bundles) {
|
|
949
|
+
const membership = /* @__PURE__ */ new Map();
|
|
950
|
+
for (const bundle of bundles) {
|
|
951
|
+
for (const assetId of bundle.assets) {
|
|
952
|
+
membership.set(assetId, [...membership.get(assetId) ?? [], bundle.id]);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
for (const [id, bundleIds] of membership) membership.set(id, uniqueSorted(bundleIds));
|
|
956
|
+
return membership;
|
|
957
|
+
}
|
|
958
|
+
function assertUniqueEntries(entries) {
|
|
959
|
+
const ids = /* @__PURE__ */ new Set();
|
|
960
|
+
for (const entry of entries) {
|
|
961
|
+
if (ids.has(entry.id)) throw new Error(`Duplicate catalog entry id: ${entry.id}`);
|
|
962
|
+
ids.add(entry.id);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function assertKnownTopics(id, topics, topicIds) {
|
|
966
|
+
for (const topic of topics) {
|
|
967
|
+
if (!topicIds.has(topic)) throw new Error(`Unknown topic ${topic} on catalog entry ${id}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function nativeLinkName(entry) {
|
|
971
|
+
const base = safePathSegment(entry.id.replace("/", "--"));
|
|
972
|
+
return `${base}.asset-link`;
|
|
973
|
+
}
|
|
974
|
+
function safePathSegment(value) {
|
|
975
|
+
const safe = value.normalize("NFKD").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
976
|
+
if (!safe || safe === "." || safe === "..")
|
|
977
|
+
throw new Error(`Unsafe catalog path segment: ${value}`);
|
|
978
|
+
return safe;
|
|
979
|
+
}
|
|
980
|
+
function normalizeRelativePath(path) {
|
|
981
|
+
return path.replaceAll("\\", "/");
|
|
982
|
+
}
|
|
983
|
+
function encodeRelativeHref(path) {
|
|
984
|
+
return normalizeRelativePath(path).split("/").map((part) => part === "." || part === ".." ? part : encodeURIComponent(part)).join("/");
|
|
985
|
+
}
|
|
986
|
+
function uniqueSorted(values) {
|
|
987
|
+
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
988
|
+
}
|
|
989
|
+
function compareCatalogEntries(left, right) {
|
|
990
|
+
return `${left.family}:${left.title}:${left.id}`.localeCompare(
|
|
991
|
+
`${right.family}:${right.title}:${right.id}`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
function escapeTable(value) {
|
|
995
|
+
return value.replaceAll("|", "\\|").replace(/\r?\n/g, " ");
|
|
996
|
+
}
|
|
997
|
+
function escapeHtml(value) {
|
|
998
|
+
return value.replace(/[&<>"']/g, (character) => {
|
|
999
|
+
const entities = {
|
|
1000
|
+
"&": "&",
|
|
1001
|
+
"<": "<",
|
|
1002
|
+
">": ">",
|
|
1003
|
+
'"': """,
|
|
1004
|
+
"'": "'"
|
|
1005
|
+
};
|
|
1006
|
+
return entities[character] ?? character;
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
function sha256(value) {
|
|
1010
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/asset-npx/maintenance.ts
|
|
1014
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1015
|
+
import {
|
|
1016
|
+
cpSync,
|
|
1017
|
+
existsSync as existsSync4,
|
|
1018
|
+
mkdirSync as mkdirSync2,
|
|
1019
|
+
mkdtempSync as mkdtempSync2,
|
|
1020
|
+
readdirSync as readdirSync4,
|
|
1021
|
+
readFileSync as readFileSync3,
|
|
1022
|
+
statSync as statSync2
|
|
1023
|
+
} from "node:fs";
|
|
1024
|
+
import { join as join4, relative as relative3 } from "node:path";
|
|
74
1025
|
import { tmpdir } from "node:os";
|
|
1026
|
+
|
|
1027
|
+
// src/command-runner.ts
|
|
1028
|
+
import {
|
|
1029
|
+
spawnSync
|
|
1030
|
+
} from "node:child_process";
|
|
1031
|
+
function spawnPlatformSync(command2, args, options) {
|
|
1032
|
+
if (process.platform !== "win32") return spawnSync(command2, args, options);
|
|
1033
|
+
const commandLine = [command2, ...args].map(quoteWindowsArgument).join(" ");
|
|
1034
|
+
return spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `"${commandLine}"`], {
|
|
1035
|
+
...options,
|
|
1036
|
+
// The command line above is intentionally assembled for cmd.exe. Without
|
|
1037
|
+
// this flag Node escapes its quotes before cmd.exe can parse them.
|
|
1038
|
+
windowsVerbatimArguments: true
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
function quoteWindowsArgument(value) {
|
|
1042
|
+
if (value.length === 0 || /[\s"&|<>^]/.test(value)) {
|
|
1043
|
+
return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
1044
|
+
}
|
|
1045
|
+
return value;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// src/asset-npx/maintenance.ts
|
|
75
1049
|
function createNpxSkillsMaintenancePlan(options) {
|
|
76
1050
|
assertNativeNpxRoot(options.npxRoot);
|
|
77
1051
|
if (options.operation === "add" && !options.source) {
|
|
78
1052
|
throw new Error("npx skills add requires a source.");
|
|
79
1053
|
}
|
|
80
1054
|
const before = snapshotFiles(options.npxRoot);
|
|
81
|
-
const tempRoot =
|
|
1055
|
+
const tempRoot = mkdtempSync2(join4(tmpdir(), "pro-gov-npx-skills-"));
|
|
82
1056
|
cpSync(options.npxRoot, tempRoot, { recursive: true, dereference: false });
|
|
83
1057
|
const command2 = buildNpxCommand(options);
|
|
84
1058
|
const runner = options.runner ?? defaultRunner;
|
|
@@ -122,10 +1096,10 @@ ${stderr}`.replace(ansiEscape, "");
|
|
|
122
1096
|
}
|
|
123
1097
|
}
|
|
124
1098
|
function assertNativeNpxRoot(npxRoot) {
|
|
125
|
-
if (!
|
|
1099
|
+
if (!existsSync4(join4(npxRoot, "skills-lock.json"))) {
|
|
126
1100
|
throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
|
|
127
1101
|
}
|
|
128
|
-
if (!
|
|
1102
|
+
if (!existsSync4(join4(npxRoot, ".agents/skills"))) {
|
|
129
1103
|
throw new Error(`npx skills root is missing .agents/skills: ${npxRoot}`);
|
|
130
1104
|
}
|
|
131
1105
|
}
|
|
@@ -140,7 +1114,8 @@ function buildNpxCommand(options) {
|
|
|
140
1114
|
return command2;
|
|
141
1115
|
}
|
|
142
1116
|
function defaultRunner({ command: command2, cwd, timeoutMs }) {
|
|
143
|
-
const
|
|
1117
|
+
const commandName = command2[0] ?? "npx";
|
|
1118
|
+
const result = spawnPlatformSync(commandName, command2.slice(1), {
|
|
144
1119
|
cwd,
|
|
145
1120
|
encoding: "utf8",
|
|
146
1121
|
timeout: timeoutMs
|
|
@@ -155,21 +1130,21 @@ function defaultRunner({ command: command2, cwd, timeoutMs }) {
|
|
|
155
1130
|
}
|
|
156
1131
|
function snapshotFiles(root) {
|
|
157
1132
|
const snapshot = /* @__PURE__ */ new Map();
|
|
158
|
-
for (const filePath of
|
|
159
|
-
const relativePath = toUnixPath2(
|
|
1133
|
+
for (const filePath of listFiles3(root)) {
|
|
1134
|
+
const relativePath = toUnixPath2(relative3(root, filePath));
|
|
160
1135
|
snapshot.set(relativePath, hashFile(filePath));
|
|
161
1136
|
}
|
|
162
1137
|
return snapshot;
|
|
163
1138
|
}
|
|
164
|
-
function
|
|
1139
|
+
function listFiles3(root) {
|
|
165
1140
|
const files = [];
|
|
166
1141
|
collectFiles(root, root, files);
|
|
167
1142
|
return files.sort();
|
|
168
1143
|
}
|
|
169
1144
|
function collectFiles(root, current, files) {
|
|
170
|
-
|
|
171
|
-
for (const entry of
|
|
172
|
-
const entryPath =
|
|
1145
|
+
mkdirSync2(root, { recursive: true });
|
|
1146
|
+
for (const entry of readdirSync4(current, { withFileTypes: true })) {
|
|
1147
|
+
const entryPath = join4(current, entry.name);
|
|
173
1148
|
if (entry.isDirectory()) {
|
|
174
1149
|
collectFiles(root, entryPath, files);
|
|
175
1150
|
} else if (entry.isFile()) {
|
|
@@ -178,11 +1153,11 @@ function collectFiles(root, current, files) {
|
|
|
178
1153
|
}
|
|
179
1154
|
}
|
|
180
1155
|
function hashFile(path) {
|
|
181
|
-
const hash =
|
|
182
|
-
const stats =
|
|
1156
|
+
const hash = createHash2("sha256");
|
|
1157
|
+
const stats = statSync2(path);
|
|
183
1158
|
hash.update(String(stats.size));
|
|
184
1159
|
hash.update("\0");
|
|
185
|
-
hash.update(
|
|
1160
|
+
hash.update(readFileSync3(path));
|
|
186
1161
|
return hash.digest("hex");
|
|
187
1162
|
}
|
|
188
1163
|
function diffSnapshots(before, after) {
|
|
@@ -209,14 +1184,14 @@ function toUnixPath2(path) {
|
|
|
209
1184
|
}
|
|
210
1185
|
|
|
211
1186
|
// src/asset-registry/loader.ts
|
|
212
|
-
import { createHash as
|
|
213
|
-
import { existsSync as
|
|
214
|
-
import { dirname as
|
|
1187
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1188
|
+
import { existsSync as existsSync6, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
1189
|
+
import { dirname as dirname3, join as join6, relative as relative5 } from "node:path";
|
|
215
1190
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
216
1191
|
|
|
217
1192
|
// src/asset-registry/registry.ts
|
|
218
|
-
import { existsSync as
|
|
219
|
-
import { isAbsolute, join as
|
|
1193
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync5, realpathSync } from "node:fs";
|
|
1194
|
+
import { isAbsolute as isAbsolute2, join as join5, posix, relative as relative4, sep } from "node:path";
|
|
220
1195
|
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
221
1196
|
"pie-skills",
|
|
222
1197
|
"npx-skills",
|
|
@@ -260,7 +1235,7 @@ function assetSkillInstallName(asset) {
|
|
|
260
1235
|
return asset.installName ?? posix.basename(asset.sourcePath);
|
|
261
1236
|
}
|
|
262
1237
|
function isValidAssetProjectTargetPath(kind, projectTargetPath) {
|
|
263
|
-
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 ||
|
|
1238
|
+
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 || isAbsolute2(projectTargetPath) || projectTargetPath.includes("\\")) {
|
|
264
1239
|
return false;
|
|
265
1240
|
}
|
|
266
1241
|
const segments = projectTargetPath.split("/");
|
|
@@ -416,11 +1391,11 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
416
1391
|
});
|
|
417
1392
|
}
|
|
418
1393
|
if (options.agentAssetsDir && isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
419
|
-
const sourceAbsolutePath =
|
|
1394
|
+
const sourceAbsolutePath = join5(
|
|
420
1395
|
options.agentAssetsDir,
|
|
421
1396
|
normalizeRegistrySourcePath(asset.sourcePath)
|
|
422
1397
|
);
|
|
423
|
-
if (!
|
|
1398
|
+
if (!existsSync5(sourceAbsolutePath)) {
|
|
424
1399
|
issues.push({
|
|
425
1400
|
type: "missing-source-path",
|
|
426
1401
|
id: asset.id,
|
|
@@ -444,7 +1419,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
444
1419
|
message: `Local skill pack must contain .codex-plugin/plugin.json and at least one skills/*/SKILL.md: ${asset.sourcePath}`
|
|
445
1420
|
});
|
|
446
1421
|
}
|
|
447
|
-
} else if (!
|
|
1422
|
+
} else if (!existsSync5(join5(sourceAbsolutePath, "SKILL.md"))) {
|
|
448
1423
|
issues.push({
|
|
449
1424
|
type: "missing-skill-file",
|
|
450
1425
|
id: asset.id,
|
|
@@ -456,7 +1431,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
456
1431
|
}
|
|
457
1432
|
}
|
|
458
1433
|
if (options.agentAssetsDir) {
|
|
459
|
-
const npxCompatibilityLayer =
|
|
1434
|
+
const npxCompatibilityLayer = join5(options.agentAssetsDir, "skills/npx-skills/skills");
|
|
460
1435
|
if (pathExistsEvenIfDanglingSymlink(npxCompatibilityLayer)) {
|
|
461
1436
|
issues.push({
|
|
462
1437
|
type: "internal-npx-compatibility-layer",
|
|
@@ -469,20 +1444,20 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
469
1444
|
return issues;
|
|
470
1445
|
}
|
|
471
1446
|
function isLocalSkillPack(sourceAbsolutePath) {
|
|
472
|
-
const skillsRoot =
|
|
473
|
-
if (!
|
|
1447
|
+
const skillsRoot = join5(sourceAbsolutePath, "skills");
|
|
1448
|
+
if (!existsSync5(join5(sourceAbsolutePath, ".codex-plugin/plugin.json")) || !existsSync5(skillsRoot)) {
|
|
474
1449
|
return false;
|
|
475
1450
|
}
|
|
476
1451
|
try {
|
|
477
|
-
return
|
|
478
|
-
(entry) => entry.isDirectory() &&
|
|
1452
|
+
return lstatSync2(skillsRoot).isDirectory() && readdirSync5(skillsRoot, { withFileTypes: true }).some(
|
|
1453
|
+
(entry) => entry.isDirectory() && existsSync5(join5(skillsRoot, entry.name, "SKILL.md"))
|
|
479
1454
|
);
|
|
480
1455
|
} catch {
|
|
481
1456
|
return false;
|
|
482
1457
|
}
|
|
483
1458
|
}
|
|
484
1459
|
function isSafeRegistrySourcePath(sourcePath) {
|
|
485
|
-
if (!sourcePath ||
|
|
1460
|
+
if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return false;
|
|
486
1461
|
const normalized = normalizeRegistrySourcePath(sourcePath);
|
|
487
1462
|
if (normalized === "." || normalized.startsWith("../") || normalized === "..") return false;
|
|
488
1463
|
return !normalized.split("/").includes("..");
|
|
@@ -494,15 +1469,15 @@ function isWithinAgentAssetsDir(agentAssetsDir, sourceAbsolutePath) {
|
|
|
494
1469
|
try {
|
|
495
1470
|
const agentAssetsRealPath = realpathSync(agentAssetsDir);
|
|
496
1471
|
const sourceRealPath = realpathSync(sourceAbsolutePath);
|
|
497
|
-
const relativePath =
|
|
498
|
-
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !
|
|
1472
|
+
const relativePath = relative4(agentAssetsRealPath, sourceRealPath);
|
|
1473
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute2(relativePath);
|
|
499
1474
|
} catch {
|
|
500
1475
|
return false;
|
|
501
1476
|
}
|
|
502
1477
|
}
|
|
503
1478
|
function pathExistsEvenIfDanglingSymlink(path) {
|
|
504
1479
|
try {
|
|
505
|
-
|
|
1480
|
+
lstatSync2(path);
|
|
506
1481
|
return true;
|
|
507
1482
|
} catch {
|
|
508
1483
|
return false;
|
|
@@ -519,7 +1494,7 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
519
1494
|
(left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
|
|
520
1495
|
)
|
|
521
1496
|
});
|
|
522
|
-
const hash =
|
|
1497
|
+
const hash = createHash3("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
|
|
523
1498
|
return {
|
|
524
1499
|
schema: "agent-assets-registry",
|
|
525
1500
|
version: registry.schemaVersion,
|
|
@@ -529,8 +1504,8 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
529
1504
|
}
|
|
530
1505
|
function loadAgentAssetRegistry(options = {}) {
|
|
531
1506
|
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
532
|
-
const registryPath =
|
|
533
|
-
if (!
|
|
1507
|
+
const registryPath = join6(agentAssetsDir, "registry.json");
|
|
1508
|
+
if (!existsSync6(registryPath)) {
|
|
534
1509
|
return {
|
|
535
1510
|
registry: { schemaVersion: 1, assets: [] },
|
|
536
1511
|
agentAssetsDir,
|
|
@@ -538,7 +1513,7 @@ function loadAgentAssetRegistry(options = {}) {
|
|
|
538
1513
|
issues: []
|
|
539
1514
|
};
|
|
540
1515
|
}
|
|
541
|
-
const registry = JSON.parse(
|
|
1516
|
+
const registry = JSON.parse(readFileSync4(registryPath, "utf8"));
|
|
542
1517
|
return {
|
|
543
1518
|
registry,
|
|
544
1519
|
agentAssetsDir,
|
|
@@ -556,15 +1531,15 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
556
1531
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
557
1532
|
}
|
|
558
1533
|
function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
559
|
-
return hashAssetPathContent(
|
|
1534
|
+
return hashAssetPathContent(join6(agentAssetsDir, asset.sourcePath));
|
|
560
1535
|
}
|
|
561
1536
|
function hashAssetPathContent(sourceAbsolutePath) {
|
|
562
|
-
const hash =
|
|
563
|
-
for (const filePath of
|
|
564
|
-
const relativePath = toUnixPath3(
|
|
1537
|
+
const hash = createHash3("sha256");
|
|
1538
|
+
for (const filePath of listFiles4(sourceAbsolutePath)) {
|
|
1539
|
+
const relativePath = toUnixPath3(relative5(sourceAbsolutePath, filePath));
|
|
565
1540
|
hash.update(relativePath);
|
|
566
1541
|
hash.update("\0");
|
|
567
|
-
hash.update(
|
|
1542
|
+
hash.update(readFileSync4(filePath));
|
|
568
1543
|
hash.update("\0");
|
|
569
1544
|
}
|
|
570
1545
|
return `sha256:${hash.digest("hex")}`;
|
|
@@ -579,40 +1554,40 @@ function canonicalizeValue(value) {
|
|
|
579
1554
|
return value;
|
|
580
1555
|
}
|
|
581
1556
|
function findDefaultAgentAssetsDir() {
|
|
582
|
-
const packageRoot2 = findPackageRoot(
|
|
583
|
-
const repoRoot =
|
|
1557
|
+
const packageRoot2 = findPackageRoot(dirname3(fileURLToPath2(import.meta.url)));
|
|
1558
|
+
const repoRoot = join6(packageRoot2, "..", "..");
|
|
584
1559
|
const candidates = [
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1560
|
+
join6(packageRoot2, "assets/agent-assets"),
|
|
1561
|
+
join6(repoRoot, "agent-assets"),
|
|
1562
|
+
join6(packageRoot2, "assets/public-agent-assets"),
|
|
1563
|
+
join6(repoRoot, "public-agent-assets")
|
|
589
1564
|
];
|
|
590
|
-
return candidates.find((candidate) =>
|
|
1565
|
+
return candidates.find((candidate) => existsSync6(join6(candidate, "registry.json"))) ?? candidates[0];
|
|
591
1566
|
}
|
|
592
1567
|
function findPackageRoot(startDir) {
|
|
593
1568
|
let current = startDir;
|
|
594
|
-
while (current !==
|
|
595
|
-
const packageJsonPath =
|
|
596
|
-
if (
|
|
1569
|
+
while (current !== dirname3(current)) {
|
|
1570
|
+
const packageJsonPath = join6(current, "package.json");
|
|
1571
|
+
if (existsSync6(packageJsonPath)) {
|
|
597
1572
|
try {
|
|
598
|
-
const packageJson = JSON.parse(
|
|
1573
|
+
const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
599
1574
|
if (packageJson.name === "@pieai/pro-gov") return current;
|
|
600
1575
|
} catch {
|
|
601
1576
|
}
|
|
602
1577
|
}
|
|
603
|
-
current =
|
|
1578
|
+
current = dirname3(current);
|
|
604
1579
|
}
|
|
605
1580
|
return startDir;
|
|
606
1581
|
}
|
|
607
|
-
function
|
|
608
|
-
const stats =
|
|
1582
|
+
function listFiles4(absolutePath) {
|
|
1583
|
+
const stats = statSync3(absolutePath);
|
|
609
1584
|
if (stats.isFile()) return [absolutePath];
|
|
610
1585
|
const files = [];
|
|
611
|
-
for (const entry of
|
|
1586
|
+
for (const entry of readdirSync6(absolutePath, { withFileTypes: true })) {
|
|
612
1587
|
if (shouldIgnoreAssetHashEntry(entry.name)) continue;
|
|
613
|
-
const entryPath =
|
|
1588
|
+
const entryPath = join6(absolutePath, entry.name);
|
|
614
1589
|
if (entry.isDirectory()) {
|
|
615
|
-
files.push(...
|
|
1590
|
+
files.push(...listFiles4(entryPath));
|
|
616
1591
|
} else if (entry.isFile()) {
|
|
617
1592
|
files.push(entryPath);
|
|
618
1593
|
}
|
|
@@ -627,8 +1602,8 @@ function toUnixPath3(path) {
|
|
|
627
1602
|
}
|
|
628
1603
|
|
|
629
1604
|
// src/asset-registry/public-promotion.ts
|
|
630
|
-
import { existsSync as
|
|
631
|
-
import { isAbsolute as
|
|
1605
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1606
|
+
import { isAbsolute as isAbsolute3, join as join7, posix as posix2 } from "node:path";
|
|
632
1607
|
function checkPublicAssetPromotions(options) {
|
|
633
1608
|
const issues = [];
|
|
634
1609
|
let checked = 0;
|
|
@@ -666,7 +1641,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
666
1641
|
});
|
|
667
1642
|
continue;
|
|
668
1643
|
}
|
|
669
|
-
if (!
|
|
1644
|
+
if (!existsSync7(privatePathResult.path)) {
|
|
670
1645
|
issues.push({
|
|
671
1646
|
type: "missing-private-source",
|
|
672
1647
|
id: asset.id,
|
|
@@ -686,7 +1661,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
686
1661
|
});
|
|
687
1662
|
}
|
|
688
1663
|
}
|
|
689
|
-
if (!
|
|
1664
|
+
if (!existsSync7(publicPathResult.path)) {
|
|
690
1665
|
issues.push({
|
|
691
1666
|
type: "missing-public-source",
|
|
692
1667
|
id: asset.id,
|
|
@@ -713,32 +1688,39 @@ function needsPromotionCheck(asset) {
|
|
|
713
1688
|
return asset.visibility === "public" && asset.publishable;
|
|
714
1689
|
}
|
|
715
1690
|
function resolveSafePath(root, sourcePath) {
|
|
716
|
-
if (!sourcePath ||
|
|
1691
|
+
if (!sourcePath || isAbsolute3(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
|
|
717
1692
|
const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
|
|
718
1693
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
719
1694
|
return { ok: false };
|
|
720
1695
|
}
|
|
721
1696
|
if (normalized.split("/").includes("..")) return { ok: false };
|
|
722
|
-
return { ok: true, path:
|
|
1697
|
+
return { ok: true, path: join7(root, normalized) };
|
|
723
1698
|
}
|
|
724
1699
|
|
|
725
1700
|
// src/asset-targets/apply.ts
|
|
726
|
-
import { createHash as
|
|
1701
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
727
1702
|
import {
|
|
728
|
-
existsSync as
|
|
729
|
-
lstatSync as
|
|
730
|
-
mkdirSync as
|
|
1703
|
+
existsSync as existsSync9,
|
|
1704
|
+
lstatSync as lstatSync4,
|
|
1705
|
+
mkdirSync as mkdirSync3,
|
|
731
1706
|
readlinkSync as readlinkSync2,
|
|
732
1707
|
realpathSync as realpathSync3,
|
|
733
|
-
symlinkSync,
|
|
1708
|
+
symlinkSync as symlinkSync2,
|
|
734
1709
|
unlinkSync,
|
|
735
|
-
writeFileSync
|
|
1710
|
+
writeFileSync as writeFileSync2
|
|
736
1711
|
} from "node:fs";
|
|
737
|
-
import { dirname as
|
|
1712
|
+
import { dirname as dirname5, join as join9, relative as relative6, resolve as resolve3 } from "node:path";
|
|
1713
|
+
|
|
1714
|
+
// src/asset-targets/install-plan.ts
|
|
1715
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
1716
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve2 } from "node:path";
|
|
1717
|
+
|
|
1718
|
+
// src/symlinks.ts
|
|
1719
|
+
function normalizeSymlinkTarget(target) {
|
|
1720
|
+
return target.replaceAll("\\", "/");
|
|
1721
|
+
}
|
|
738
1722
|
|
|
739
1723
|
// src/asset-targets/install-plan.ts
|
|
740
|
-
import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
|
|
741
|
-
import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
|
|
742
1724
|
function createAssetInstallPlan(options) {
|
|
743
1725
|
const placement = options.placement ?? "registry";
|
|
744
1726
|
const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
@@ -858,9 +1840,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
858
1840
|
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
859
1841
|
);
|
|
860
1842
|
}
|
|
861
|
-
const sourcePath =
|
|
1843
|
+
const sourcePath = join8(agentAssetsDir, asset.sourcePath);
|
|
862
1844
|
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
863
|
-
const targetAbsolutePath =
|
|
1845
|
+
const targetAbsolutePath = join8(targetDir, targetPath);
|
|
864
1846
|
const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
|
|
865
1847
|
const managedEntry = managedEntries.find(
|
|
866
1848
|
(entry) => entry.id === asset.id && entry.targetPath === targetPath
|
|
@@ -877,7 +1859,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
877
1859
|
});
|
|
878
1860
|
}
|
|
879
1861
|
if (targetExists) {
|
|
880
|
-
const stats =
|
|
1862
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
881
1863
|
if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
|
|
882
1864
|
return {
|
|
883
1865
|
type: "update-symlink",
|
|
@@ -889,7 +1871,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
889
1871
|
if (managedEntry && managedEntry.delivery === "snapshot") {
|
|
890
1872
|
throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
|
|
891
1873
|
}
|
|
892
|
-
if (stats.isSymbolicLink() &&
|
|
1874
|
+
if (stats.isSymbolicLink() && existsSync8(targetAbsolutePath) && realpathSync2(targetAbsolutePath) === realpathSync2(sourcePath)) {
|
|
893
1875
|
return {
|
|
894
1876
|
type: "adopt-existing-symlink",
|
|
895
1877
|
assetId: asset.id,
|
|
@@ -912,10 +1894,10 @@ function createSnapshotAction(options) {
|
|
|
912
1894
|
`Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
|
|
913
1895
|
);
|
|
914
1896
|
}
|
|
915
|
-
if (!
|
|
1897
|
+
if (!statSync4(options.sourcePath).isFile()) {
|
|
916
1898
|
throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
|
|
917
1899
|
}
|
|
918
|
-
const content =
|
|
1900
|
+
const content = readFileSync5(options.sourcePath);
|
|
919
1901
|
const contentBase64 = content.toString("base64");
|
|
920
1902
|
const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
|
|
921
1903
|
const managedDelivery = options.managedEntry?.delivery ?? "symlink";
|
|
@@ -928,9 +1910,9 @@ function createSnapshotAction(options) {
|
|
|
928
1910
|
contentHash
|
|
929
1911
|
};
|
|
930
1912
|
}
|
|
931
|
-
const stats =
|
|
1913
|
+
const stats = lstatSync3(options.targetAbsolutePath);
|
|
932
1914
|
if (stats.isSymbolicLink()) {
|
|
933
|
-
if (
|
|
1915
|
+
if (existsSync8(options.targetAbsolutePath) && realpathSync2(options.targetAbsolutePath) === realpathSync2(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
|
|
934
1916
|
return {
|
|
935
1917
|
type: "migrate-symlink-to-snapshot",
|
|
936
1918
|
assetId: options.asset.id,
|
|
@@ -986,9 +1968,9 @@ function resolveHostTargetPath(asset, _host, placement) {
|
|
|
986
1968
|
return `.agents/skills/${assetSkillInstallName(asset)}`;
|
|
987
1969
|
}
|
|
988
1970
|
if (asset.kind === "rule") {
|
|
989
|
-
return `.pro-gov/agent-assets/rules/${
|
|
1971
|
+
return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
|
|
990
1972
|
}
|
|
991
|
-
return `.pro-gov/agent-assets/commands/${
|
|
1973
|
+
return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
|
|
992
1974
|
}
|
|
993
1975
|
function resolveSkillPlacement(asset, placement) {
|
|
994
1976
|
if (placement !== "registry") return placement;
|
|
@@ -998,16 +1980,16 @@ function createDirectoryActions(actions) {
|
|
|
998
1980
|
const directories = /* @__PURE__ */ new Set();
|
|
999
1981
|
for (const action of actions) {
|
|
1000
1982
|
if (action.type === "create-dir") continue;
|
|
1001
|
-
const directory =
|
|
1983
|
+
const directory = dirname4(action.targetPath);
|
|
1002
1984
|
if (directory !== ".") directories.add(directory);
|
|
1003
1985
|
}
|
|
1004
1986
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
1005
1987
|
}
|
|
1006
1988
|
function readManagedLock(targetDir) {
|
|
1007
|
-
const lockfilePath =
|
|
1008
|
-
if (!
|
|
1989
|
+
const lockfilePath = join8(targetDir, ".pro-gov/assets.lock.json");
|
|
1990
|
+
if (!existsSync8(lockfilePath)) return { entries: [] };
|
|
1009
1991
|
try {
|
|
1010
|
-
const lockfile = JSON.parse(
|
|
1992
|
+
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
1011
1993
|
return {
|
|
1012
1994
|
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
1013
1995
|
entries: (lockfile.assets ?? []).filter(
|
|
@@ -1045,10 +2027,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1045
2027
|
throw new Error(`Legacy Claude lock entry cannot be safely normalized: ${entry.targetPath}`);
|
|
1046
2028
|
}
|
|
1047
2029
|
const targetPath = `.agents/skills/${skillName}`;
|
|
1048
|
-
const targetAbsolutePath =
|
|
1049
|
-
const compatibilityRootPath =
|
|
1050
|
-
const canonicalRootPath =
|
|
1051
|
-
if (!
|
|
2030
|
+
const targetAbsolutePath = join8(options.targetDir, targetPath);
|
|
2031
|
+
const compatibilityRootPath = join8(options.targetDir, ".claude/skills");
|
|
2032
|
+
const canonicalRootPath = join8(options.targetDir, ".agents/skills");
|
|
2033
|
+
if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync(compatibilityRootPath)) !== "../.agents/skills" || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
|
|
1052
2034
|
throw new Error(
|
|
1053
2035
|
`Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`
|
|
1054
2036
|
);
|
|
@@ -1057,10 +2039,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1057
2039
|
consumedTargetPaths.add(entry.targetPath);
|
|
1058
2040
|
continue;
|
|
1059
2041
|
}
|
|
1060
|
-
const targetStat =
|
|
1061
|
-
const legacyAbsolutePath =
|
|
1062
|
-
const legacyStat =
|
|
1063
|
-
const expectedSourcePath =
|
|
2042
|
+
const targetStat = lstatSync3(targetAbsolutePath);
|
|
2043
|
+
const legacyAbsolutePath = join8(options.targetDir, entry.targetPath);
|
|
2044
|
+
const legacyStat = lstatSync3(legacyAbsolutePath);
|
|
2045
|
+
const expectedSourcePath = join8(options.agentAssetsDir, entry.sourcePath);
|
|
1064
2046
|
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || realpathSync2(targetAbsolutePath) !== realpathSync2(expectedSourcePath)) {
|
|
1065
2047
|
throw new Error(`Legacy Claude skill target cannot be safely adopted: ${entry.targetPath}`);
|
|
1066
2048
|
}
|
|
@@ -1090,9 +2072,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1090
2072
|
`Refusing to remove managed asset outside supported roots: ${entry.targetPath}`
|
|
1091
2073
|
);
|
|
1092
2074
|
}
|
|
1093
|
-
const targetAbsolutePath =
|
|
2075
|
+
const targetAbsolutePath = join8(targetDir, entry.targetPath);
|
|
1094
2076
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
|
|
1095
|
-
const stats =
|
|
2077
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
1096
2078
|
if ((entry.delivery ?? "symlink") === "snapshot") {
|
|
1097
2079
|
if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1098
2080
|
throw new Error(
|
|
@@ -1121,9 +2103,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1121
2103
|
`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
|
|
1122
2104
|
);
|
|
1123
2105
|
}
|
|
1124
|
-
const expectedSourcePath =
|
|
1125
|
-
const actualSourcePath =
|
|
1126
|
-
if (actualSourcePath !==
|
|
2106
|
+
const expectedSourcePath = join8(agentAssetsDir, entry.sourcePath);
|
|
2107
|
+
const actualSourcePath = resolve2(dirname4(targetAbsolutePath), readlinkSync(targetAbsolutePath));
|
|
2108
|
+
if (actualSourcePath !== resolve2(expectedSourcePath)) {
|
|
1127
2109
|
throw new Error(
|
|
1128
2110
|
`Refusing to remove managed symlink with changed target: ${entry.targetPath}`
|
|
1129
2111
|
);
|
|
@@ -1147,7 +2129,7 @@ function isLegacyClaudeSkillTargetPath(path) {
|
|
|
1147
2129
|
}
|
|
1148
2130
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
1149
2131
|
try {
|
|
1150
|
-
|
|
2132
|
+
lstatSync3(path);
|
|
1151
2133
|
return true;
|
|
1152
2134
|
} catch {
|
|
1153
2135
|
return false;
|
|
@@ -1171,7 +2153,7 @@ function applyAssetInstallPlan(plan) {
|
|
|
1171
2153
|
return { appliedActions };
|
|
1172
2154
|
}
|
|
1173
2155
|
function applyAction(targetDir, action) {
|
|
1174
|
-
const targetAbsolutePath =
|
|
2156
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1175
2157
|
if (action.type === "remove-symlink") {
|
|
1176
2158
|
removeManagedSymlink(targetAbsolutePath, action);
|
|
1177
2159
|
return;
|
|
@@ -1182,41 +2164,41 @@ function applyAction(targetDir, action) {
|
|
|
1182
2164
|
return;
|
|
1183
2165
|
}
|
|
1184
2166
|
if (action.type === "create-dir") {
|
|
1185
|
-
|
|
2167
|
+
mkdirSync3(targetAbsolutePath, { recursive: true });
|
|
1186
2168
|
return;
|
|
1187
2169
|
}
|
|
1188
2170
|
if (action.type === "write-file") {
|
|
1189
|
-
|
|
1190
|
-
|
|
2171
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2172
|
+
writeFileSync2(targetAbsolutePath, action.content);
|
|
1191
2173
|
return;
|
|
1192
2174
|
}
|
|
1193
|
-
|
|
1194
|
-
const sourceAbsolutePath =
|
|
1195
|
-
const symlinkTarget =
|
|
2175
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2176
|
+
const sourceAbsolutePath = resolve3(action.sourcePath);
|
|
2177
|
+
const symlinkTarget = relative6(realpathSync3(dirname5(targetAbsolutePath)), realpathSync3(sourceAbsolutePath)) || ".";
|
|
1196
2178
|
if (action.type === "symlink") {
|
|
1197
2179
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1198
2180
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1199
2181
|
}
|
|
1200
|
-
|
|
2182
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1201
2183
|
return;
|
|
1202
2184
|
}
|
|
1203
2185
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1204
|
-
|
|
2186
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1205
2187
|
return;
|
|
1206
2188
|
}
|
|
1207
|
-
const stats =
|
|
2189
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1208
2190
|
if (!stats.isSymbolicLink()) {
|
|
1209
2191
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1210
2192
|
}
|
|
1211
2193
|
unlinkSync(targetAbsolutePath);
|
|
1212
|
-
|
|
2194
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1213
2195
|
}
|
|
1214
2196
|
function validateExistingSymlink(targetDir, action) {
|
|
1215
2197
|
if (!isManagedAssetTargetPath(action.targetPath)) {
|
|
1216
2198
|
throw new Error(`Refusing unsafe existing symlink adoption: ${action.targetPath}`);
|
|
1217
2199
|
}
|
|
1218
|
-
const targetAbsolutePath =
|
|
1219
|
-
if (!
|
|
2200
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
2201
|
+
if (!lstatSync4(targetAbsolutePath).isSymbolicLink() || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
1220
2202
|
throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
|
|
1221
2203
|
}
|
|
1222
2204
|
}
|
|
@@ -1227,7 +2209,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1227
2209
|
if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
|
|
1228
2210
|
throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
|
|
1229
2211
|
}
|
|
1230
|
-
const targetAbsolutePath =
|
|
2212
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1231
2213
|
if (action.type === "snapshot") {
|
|
1232
2214
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1233
2215
|
throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
|
|
@@ -1243,7 +2225,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1243
2225
|
return;
|
|
1244
2226
|
}
|
|
1245
2227
|
if (action.type === "migrate-symlink-to-snapshot") {
|
|
1246
|
-
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !
|
|
2228
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync4(targetAbsolutePath).isSymbolicLink() || !existsSync9(targetAbsolutePath) || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
|
|
1247
2229
|
throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
|
|
1248
2230
|
}
|
|
1249
2231
|
return;
|
|
@@ -1251,30 +2233,30 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1251
2233
|
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1252
2234
|
}
|
|
1253
2235
|
function applySnapshotAction(targetDir, action) {
|
|
1254
|
-
const targetAbsolutePath =
|
|
2236
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1255
2237
|
if (action.type === "adopt-snapshot") return;
|
|
1256
2238
|
if (action.type === "remove-snapshot") {
|
|
1257
2239
|
unlinkSync(targetAbsolutePath);
|
|
1258
2240
|
return;
|
|
1259
2241
|
}
|
|
1260
2242
|
if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
|
|
1261
|
-
|
|
2243
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
1262
2244
|
if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1263
2245
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1264
2246
|
}
|
|
1265
|
-
|
|
2247
|
+
writeFileSync2(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
|
|
1266
2248
|
}
|
|
1267
2249
|
function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
|
|
1268
2250
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1269
2251
|
throw new Error(`Snapshot target changed before apply: ${targetPath}`);
|
|
1270
2252
|
}
|
|
1271
|
-
const stats =
|
|
2253
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1272
2254
|
if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
|
|
1273
2255
|
throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
|
|
1274
2256
|
}
|
|
1275
2257
|
}
|
|
1276
2258
|
function hashSnapshotBytes(content) {
|
|
1277
|
-
const hash =
|
|
2259
|
+
const hash = createHash4("sha256");
|
|
1278
2260
|
hash.update("");
|
|
1279
2261
|
hash.update("\0");
|
|
1280
2262
|
hash.update(content);
|
|
@@ -1285,17 +2267,17 @@ function validateAdoptedSymlink(targetDir, action) {
|
|
|
1285
2267
|
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
1286
2268
|
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
1287
2269
|
}
|
|
1288
|
-
const canonicalRootPath =
|
|
1289
|
-
const compatibilityRootPath =
|
|
1290
|
-
const targetAbsolutePath =
|
|
1291
|
-
const legacyAbsolutePath =
|
|
1292
|
-
if (!
|
|
2270
|
+
const canonicalRootPath = join9(targetDir, ".agents/skills");
|
|
2271
|
+
const compatibilityRootPath = join9(targetDir, action.compatibilityRootPath);
|
|
2272
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
2273
|
+
const legacyAbsolutePath = join9(targetDir, action.legacyTargetPath);
|
|
2274
|
+
if (!lstatSync4(canonicalRootPath).isDirectory() || !lstatSync4(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync2(compatibilityRootPath)) !== action.expectedCompatibilityRawTarget || realpathSync3(compatibilityRootPath) !== realpathSync3(canonicalRootPath)) {
|
|
1293
2275
|
throw new Error(
|
|
1294
2276
|
`Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`
|
|
1295
2277
|
);
|
|
1296
2278
|
}
|
|
1297
|
-
const targetStat =
|
|
1298
|
-
const legacyStat =
|
|
2279
|
+
const targetStat = lstatSync4(targetAbsolutePath);
|
|
2280
|
+
const legacyStat = lstatSync4(legacyAbsolutePath);
|
|
1299
2281
|
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || targetStat.dev !== action.expectedDevice || targetStat.ino !== action.expectedInode || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
1300
2282
|
throw new Error(`Legacy Claude skill target changed before apply: ${action.targetPath}`);
|
|
1301
2283
|
}
|
|
@@ -1307,33 +2289,33 @@ function removeManagedSymlink(targetAbsolutePath, action) {
|
|
|
1307
2289
|
);
|
|
1308
2290
|
}
|
|
1309
2291
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) return;
|
|
1310
|
-
const stats =
|
|
2292
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1311
2293
|
if (!stats.isSymbolicLink()) {
|
|
1312
2294
|
throw new Error(
|
|
1313
2295
|
`Refusing to remove path that is no longer a managed symlink: ${action.targetPath}`
|
|
1314
2296
|
);
|
|
1315
2297
|
}
|
|
1316
|
-
const actualSourcePath =
|
|
1317
|
-
if (actualSourcePath !==
|
|
2298
|
+
const actualSourcePath = resolve3(dirname5(targetAbsolutePath), readlinkSync2(targetAbsolutePath));
|
|
2299
|
+
if (actualSourcePath !== resolve3(action.expectedSourcePath)) {
|
|
1318
2300
|
throw new Error(`Refusing to remove managed symlink with changed target: ${action.targetPath}`);
|
|
1319
2301
|
}
|
|
1320
2302
|
unlinkSync(targetAbsolutePath);
|
|
1321
2303
|
}
|
|
1322
2304
|
function pathExistsEvenIfDanglingSymlink3(path) {
|
|
1323
2305
|
try {
|
|
1324
|
-
|
|
2306
|
+
lstatSync4(path);
|
|
1325
2307
|
return true;
|
|
1326
2308
|
} catch {
|
|
1327
|
-
return
|
|
2309
|
+
return existsSync9(path);
|
|
1328
2310
|
}
|
|
1329
2311
|
}
|
|
1330
2312
|
|
|
1331
2313
|
// src/asset-targets/check.ts
|
|
1332
|
-
import { existsSync as
|
|
1333
|
-
import { join as
|
|
2314
|
+
import { existsSync as existsSync10, lstatSync as lstatSync5, readFileSync as readFileSync6, realpathSync as realpathSync4 } from "node:fs";
|
|
2315
|
+
import { join as join10 } from "node:path";
|
|
1334
2316
|
function checkInstalledAssets(options) {
|
|
1335
|
-
const lockfilePath =
|
|
1336
|
-
if (!
|
|
2317
|
+
const lockfilePath = join10(options.targetDir, ".pro-gov/assets.lock.json");
|
|
2318
|
+
if (!existsSync10(lockfilePath)) {
|
|
1337
2319
|
return {
|
|
1338
2320
|
targetDir: options.targetDir,
|
|
1339
2321
|
issues: [
|
|
@@ -1345,7 +2327,7 @@ function checkInstalledAssets(options) {
|
|
|
1345
2327
|
};
|
|
1346
2328
|
}
|
|
1347
2329
|
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
1348
|
-
const lockfile = JSON.parse(
|
|
2330
|
+
const lockfile = JSON.parse(readFileSync6(lockfilePath, "utf8"));
|
|
1349
2331
|
const issues = [];
|
|
1350
2332
|
const strictRegistry = options.strictRegistry ?? false;
|
|
1351
2333
|
const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
|
|
@@ -1374,7 +2356,7 @@ function checkInstalledAssets(options) {
|
|
|
1374
2356
|
continue;
|
|
1375
2357
|
}
|
|
1376
2358
|
const asset = registryById.get(entry.id);
|
|
1377
|
-
const targetAbsolutePath =
|
|
2359
|
+
const targetAbsolutePath = join10(options.targetDir, entry.targetPath);
|
|
1378
2360
|
const delivery = entry.delivery ?? "symlink";
|
|
1379
2361
|
const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
|
|
1380
2362
|
if (delivery !== "symlink" && delivery !== "snapshot") {
|
|
@@ -1436,7 +2418,7 @@ function checkInstalledAssets(options) {
|
|
|
1436
2418
|
});
|
|
1437
2419
|
continue;
|
|
1438
2420
|
}
|
|
1439
|
-
const targetStats =
|
|
2421
|
+
const targetStats = lstatSync5(targetAbsolutePath);
|
|
1440
2422
|
if (delivery === "snapshot" && !targetStats.isFile()) {
|
|
1441
2423
|
issues.push({
|
|
1442
2424
|
type: "snapshot-not-regular-file",
|
|
@@ -1455,7 +2437,7 @@ function checkInstalledAssets(options) {
|
|
|
1455
2437
|
});
|
|
1456
2438
|
continue;
|
|
1457
2439
|
}
|
|
1458
|
-
if (delivery === "symlink" && !
|
|
2440
|
+
if (delivery === "symlink" && !existsSync10(targetAbsolutePath)) {
|
|
1459
2441
|
if (portableDeferredSkill) continue;
|
|
1460
2442
|
issues.push({
|
|
1461
2443
|
type: "dangling-symlink",
|
|
@@ -1476,8 +2458,8 @@ function checkInstalledAssets(options) {
|
|
|
1476
2458
|
});
|
|
1477
2459
|
}
|
|
1478
2460
|
if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
|
|
1479
|
-
const sourceAbsolutePath =
|
|
1480
|
-
if (!
|
|
2461
|
+
const sourceAbsolutePath = join10(options.agentAssetsDir, asset.sourcePath);
|
|
2462
|
+
if (!existsSync10(sourceAbsolutePath)) {
|
|
1481
2463
|
issues.push({
|
|
1482
2464
|
type: "missing-source",
|
|
1483
2465
|
id: entry.id,
|
|
@@ -1541,7 +2523,7 @@ function checkDuplicateSkillPlacements(targetDir, registry) {
|
|
|
1541
2523
|
const skillName = assetSkillInstallName(asset);
|
|
1542
2524
|
const autoPath = `.agents/skills/${skillName}`;
|
|
1543
2525
|
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
1544
|
-
if (pathExistsEvenIfDanglingSymlink4(
|
|
2526
|
+
if (pathExistsEvenIfDanglingSymlink4(join10(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink4(join10(targetDir, manualPath))) {
|
|
1545
2527
|
issues.push({
|
|
1546
2528
|
type: "duplicate-skill-placement",
|
|
1547
2529
|
id: asset.id,
|
|
@@ -1587,7 +2569,7 @@ function expectedRegistrySkillTargetPath(host, skillName, placement) {
|
|
|
1587
2569
|
}
|
|
1588
2570
|
function pathExistsEvenIfDanglingSymlink4(path) {
|
|
1589
2571
|
try {
|
|
1590
|
-
|
|
2572
|
+
lstatSync5(path);
|
|
1591
2573
|
return true;
|
|
1592
2574
|
} catch {
|
|
1593
2575
|
return false;
|
|
@@ -1598,8 +2580,8 @@ function isProjectSkillTarget(targetPath) {
|
|
|
1598
2580
|
}
|
|
1599
2581
|
|
|
1600
2582
|
// src/asset-targets/recommend.ts
|
|
1601
|
-
import { existsSync as
|
|
1602
|
-
import { join as
|
|
2583
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
2584
|
+
import { join as join11 } from "node:path";
|
|
1603
2585
|
var frontendPackages = /* @__PURE__ */ new Set([
|
|
1604
2586
|
"@vitejs/plugin-react",
|
|
1605
2587
|
"astro",
|
|
@@ -1613,21 +2595,21 @@ var frontendPackages = /* @__PURE__ */ new Set([
|
|
|
1613
2595
|
]);
|
|
1614
2596
|
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
|
|
1615
2597
|
function discoverTargetSignals(targetDir) {
|
|
1616
|
-
const packageJson = readJson(
|
|
2598
|
+
const packageJson = readJson(join11(targetDir, "package.json"));
|
|
1617
2599
|
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
1618
2600
|
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
1619
|
-
const hasAgentEntry = agentEntryCandidates.some((file) =>
|
|
2601
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync11(join11(targetDir, file)));
|
|
1620
2602
|
const researchSignals = [
|
|
1621
|
-
|
|
1622
|
-
|
|
2603
|
+
existsSync11(join11(targetDir, "docs/research")) ? "docs/research" : "",
|
|
2604
|
+
existsSync11(join11(targetDir, "research")) ? "research" : "",
|
|
1623
2605
|
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
1624
|
-
textFileIncludes(
|
|
2606
|
+
textFileIncludes(join11(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
1625
2607
|
].filter(Boolean);
|
|
1626
2608
|
const writingSignals = [
|
|
1627
|
-
|
|
1628
|
-
|
|
2609
|
+
existsSync11(join11(targetDir, "chapters")) ? "chapters" : "",
|
|
2610
|
+
existsSync11(join11(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
1629
2611
|
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
1630
|
-
textFileIncludes(
|
|
2612
|
+
textFileIncludes(join11(targetDir, "AGENTS.md"), [
|
|
1631
2613
|
"writing mode",
|
|
1632
2614
|
"novel chapter",
|
|
1633
2615
|
"book content"
|
|
@@ -1672,24 +2654,24 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
1672
2654
|
return recommendations;
|
|
1673
2655
|
}
|
|
1674
2656
|
function readJson(path) {
|
|
1675
|
-
if (!
|
|
2657
|
+
if (!existsSync11(path)) return void 0;
|
|
1676
2658
|
try {
|
|
1677
|
-
return JSON.parse(
|
|
2659
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1678
2660
|
} catch {
|
|
1679
2661
|
return void 0;
|
|
1680
2662
|
}
|
|
1681
2663
|
}
|
|
1682
2664
|
function textFileIncludes(path, needles) {
|
|
1683
|
-
if (!
|
|
1684
|
-
const contents =
|
|
2665
|
+
if (!existsSync11(path)) return false;
|
|
2666
|
+
const contents = readFileSync7(path, "utf8").toLowerCase();
|
|
1685
2667
|
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
1686
2668
|
}
|
|
1687
2669
|
function hasBookChildDirectory(targetDir, childName) {
|
|
1688
|
-
const booksDir =
|
|
1689
|
-
if (!
|
|
2670
|
+
const booksDir = join11(targetDir, "books");
|
|
2671
|
+
if (!existsSync11(booksDir)) return false;
|
|
1690
2672
|
try {
|
|
1691
|
-
return
|
|
1692
|
-
(entry) => entry.isDirectory() &&
|
|
2673
|
+
return readdirSync7(booksDir, { withFileTypes: true }).some(
|
|
2674
|
+
(entry) => entry.isDirectory() && existsSync11(join11(booksDir, entry.name, childName))
|
|
1693
2675
|
);
|
|
1694
2676
|
} catch {
|
|
1695
2677
|
return false;
|
|
@@ -1723,9 +2705,67 @@ function runAssets(args) {
|
|
|
1723
2705
|
if (subcommand2 === "npx") {
|
|
1724
2706
|
return runAssetsNpx(rest);
|
|
1725
2707
|
}
|
|
2708
|
+
if (subcommand2 === "catalog") {
|
|
2709
|
+
return runAssetsCatalog(rest);
|
|
2710
|
+
}
|
|
1726
2711
|
printUsage();
|
|
1727
2712
|
return 1;
|
|
1728
2713
|
}
|
|
2714
|
+
function runAssetsCatalog(args) {
|
|
2715
|
+
const [operation, ...rest] = args;
|
|
2716
|
+
if (!operation || operation === "--help" || operation === "-h") {
|
|
2717
|
+
printCatalogUsage();
|
|
2718
|
+
return operation ? 0 : 1;
|
|
2719
|
+
}
|
|
2720
|
+
if (operation !== "build" && operation !== "check") {
|
|
2721
|
+
printCatalogUsage();
|
|
2722
|
+
return 1;
|
|
2723
|
+
}
|
|
2724
|
+
const options = parseCatalogOptions(operation, rest);
|
|
2725
|
+
if (!options.ok) {
|
|
2726
|
+
console.error(options.error);
|
|
2727
|
+
printCatalogUsage();
|
|
2728
|
+
return 1;
|
|
2729
|
+
}
|
|
2730
|
+
try {
|
|
2731
|
+
const loaded = loadAgentAssetRegistry(
|
|
2732
|
+
options.value.agentAssetsDir ? { agentAssetsDir: options.value.agentAssetsDir } : void 0
|
|
2733
|
+
);
|
|
2734
|
+
if (loaded.issues.length > 0) {
|
|
2735
|
+
for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
2736
|
+
return 1;
|
|
2737
|
+
}
|
|
2738
|
+
const catalogRoot = options.value.catalogRoot ?? join12(loaded.agentAssetsDir, "#catalogs");
|
|
2739
|
+
const common = {
|
|
2740
|
+
agentAssetsDir: loaded.agentAssetsDir,
|
|
2741
|
+
catalogRoot,
|
|
2742
|
+
registry: loaded.registry,
|
|
2743
|
+
bundles: loadAgentAssetBundles(loaded.agentAssetsDir)
|
|
2744
|
+
};
|
|
2745
|
+
if (operation === "check") {
|
|
2746
|
+
const result2 = checkAssetCatalog(common);
|
|
2747
|
+
if (options.value.json) console.log(JSON.stringify(result2, null, 2));
|
|
2748
|
+
else if (result2.issues.length === 0)
|
|
2749
|
+
console.log(`asset catalog check passed (${result2.assetCount} assets)`);
|
|
2750
|
+
else for (const issue of result2.issues) console.log(issue);
|
|
2751
|
+
return result2.issues.length === 0 ? 0 : 1;
|
|
2752
|
+
}
|
|
2753
|
+
const result = buildAssetCatalog({ ...common, nativeLinks: options.value.nativeLinks });
|
|
2754
|
+
if (options.value.json) console.log(JSON.stringify(result, null, 2));
|
|
2755
|
+
else {
|
|
2756
|
+
console.log(`catalog: ${result.catalogRoot}`);
|
|
2757
|
+
console.log(`assets: ${result.assetCount}`);
|
|
2758
|
+
console.log(`topics: ${result.topicCount}`);
|
|
2759
|
+
console.log(`portable-files: ${result.portableFileCount}`);
|
|
2760
|
+
console.log(`native-links: ${result.nativeLinkCount}`);
|
|
2761
|
+
for (const warning of result.warnings) console.warn(`warning: ${warning}`);
|
|
2762
|
+
}
|
|
2763
|
+
return 0;
|
|
2764
|
+
} catch (error) {
|
|
2765
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2766
|
+
return 1;
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
1729
2769
|
function runAssetsPublicCheck(args) {
|
|
1730
2770
|
const options = parsePublicCheckOptions(args);
|
|
1731
2771
|
if (!options.ok) {
|
|
@@ -1805,7 +2845,7 @@ function runAssetsApply(args) {
|
|
|
1805
2845
|
return 1;
|
|
1806
2846
|
}
|
|
1807
2847
|
try {
|
|
1808
|
-
const plan = JSON.parse(
|
|
2848
|
+
const plan = JSON.parse(readFileSync8(options.value.planPath, "utf8"));
|
|
1809
2849
|
const result = applyAssetInstallPlan(plan);
|
|
1810
2850
|
console.log(`applied-actions: ${result.appliedActions.length}`);
|
|
1811
2851
|
return 0;
|
|
@@ -1930,8 +2970,8 @@ function runAssetsPlan(args) {
|
|
|
1930
2970
|
console.log("dry-run: true");
|
|
1931
2971
|
}
|
|
1932
2972
|
if (options.value.outPath) {
|
|
1933
|
-
|
|
1934
|
-
|
|
2973
|
+
mkdirSync4(dirname6(options.value.outPath), { recursive: true });
|
|
2974
|
+
writeFileSync3(options.value.outPath, `${JSON.stringify(plan, null, 2)}
|
|
1935
2975
|
`);
|
|
1936
2976
|
if (!options.value.json) {
|
|
1937
2977
|
console.log(`plan: ${options.value.outPath}`);
|
|
@@ -1943,6 +2983,33 @@ function runAssetsPlan(args) {
|
|
|
1943
2983
|
return 1;
|
|
1944
2984
|
}
|
|
1945
2985
|
}
|
|
2986
|
+
function parseCatalogOptions(operation, args) {
|
|
2987
|
+
const options = { nativeLinks: false, json: false };
|
|
2988
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2989
|
+
const arg = args[index];
|
|
2990
|
+
if (arg === "--agent-assets") {
|
|
2991
|
+
const value = args[index + 1];
|
|
2992
|
+
if (!value) return { ok: false, error: "Expected --agent-assets <path>" };
|
|
2993
|
+
options.agentAssetsDir = value;
|
|
2994
|
+
index += 1;
|
|
2995
|
+
} else if (arg === "--catalog-root") {
|
|
2996
|
+
const value = args[index + 1];
|
|
2997
|
+
if (!value) return { ok: false, error: "Expected --catalog-root <path>" };
|
|
2998
|
+
options.catalogRoot = value;
|
|
2999
|
+
index += 1;
|
|
3000
|
+
} else if (arg === "--native-links") {
|
|
3001
|
+
if (operation !== "build") {
|
|
3002
|
+
return { ok: false, error: "--native-links is only valid with assets catalog build" };
|
|
3003
|
+
}
|
|
3004
|
+
options.nativeLinks = true;
|
|
3005
|
+
} else if (arg === "--json") {
|
|
3006
|
+
options.json = true;
|
|
3007
|
+
} else {
|
|
3008
|
+
return { ok: false, error: `Unknown assets catalog option: ${arg}` };
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return { ok: true, value: options };
|
|
3012
|
+
}
|
|
1946
3013
|
function parseListOptions(args) {
|
|
1947
3014
|
const options = {
|
|
1948
3015
|
json: false,
|
|
@@ -2129,10 +3196,10 @@ function parsePublicCheckOptions(args) {
|
|
|
2129
3196
|
return { ok: false, error: `Unknown assets public-check option: ${arg}` };
|
|
2130
3197
|
}
|
|
2131
3198
|
}
|
|
2132
|
-
if (!
|
|
3199
|
+
if (!existsSync12(options.publicRoot)) {
|
|
2133
3200
|
return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
|
|
2134
3201
|
}
|
|
2135
|
-
if (!
|
|
3202
|
+
if (!existsSync12(options.privateRoot)) {
|
|
2136
3203
|
return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
|
|
2137
3204
|
}
|
|
2138
3205
|
return { ok: true, value: options };
|
|
@@ -2141,13 +3208,13 @@ function getDefaultPublicCheckRoots() {
|
|
|
2141
3208
|
const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
|
|
2142
3209
|
if (defaultRegistryRoot.endsWith("public-agent-assets")) {
|
|
2143
3210
|
return {
|
|
2144
|
-
privateRoot:
|
|
3211
|
+
privateRoot: join12(dirname6(defaultRegistryRoot), "agent-assets"),
|
|
2145
3212
|
publicRoot: defaultRegistryRoot
|
|
2146
3213
|
};
|
|
2147
3214
|
}
|
|
2148
3215
|
return {
|
|
2149
3216
|
privateRoot: defaultRegistryRoot,
|
|
2150
|
-
publicRoot:
|
|
3217
|
+
publicRoot: join12(dirname6(defaultRegistryRoot), "public-agent-assets")
|
|
2151
3218
|
};
|
|
2152
3219
|
}
|
|
2153
3220
|
function listRegistryAssets(options) {
|
|
@@ -2208,6 +3275,9 @@ function printUsage() {
|
|
|
2208
3275
|
);
|
|
2209
3276
|
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
2210
3277
|
console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
3278
|
+
console.error(
|
|
3279
|
+
" pro-gov assets catalog build|check [--agent-assets <path>] [--catalog-root <path>] [--native-links] [--json]"
|
|
3280
|
+
);
|
|
2211
3281
|
}
|
|
2212
3282
|
function printNpxUsage() {
|
|
2213
3283
|
console.log("Usage: pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
@@ -2215,12 +3285,19 @@ function printNpxUsage() {
|
|
|
2215
3285
|
console.log("");
|
|
2216
3286
|
console.log("Runs npx skills only in a temporary copy and prints a reviewable plan.");
|
|
2217
3287
|
}
|
|
3288
|
+
function printCatalogUsage() {
|
|
3289
|
+
console.log(
|
|
3290
|
+
"Usage: pro-gov assets catalog build [--agent-assets <path>] [--catalog-root <path>] [--native-links] [--json]"
|
|
3291
|
+
);
|
|
3292
|
+
console.log(
|
|
3293
|
+
"Usage: pro-gov assets catalog check [--agent-assets <path>] [--catalog-root <path>] [--json]"
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
2218
3296
|
|
|
2219
3297
|
// src/commands/doctor.ts
|
|
2220
|
-
import {
|
|
2221
|
-
import { existsSync as existsSync12 } from "node:fs";
|
|
3298
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2222
3299
|
import { createRequire } from "node:module";
|
|
2223
|
-
import { dirname as
|
|
3300
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
2224
3301
|
var REQUIRED_ASSETS = [
|
|
2225
3302
|
"starter/.agents/skills/.gitkeep",
|
|
2226
3303
|
"starter/AGENTS.template.md",
|
|
@@ -2252,10 +3329,12 @@ function runDoctor(args) {
|
|
|
2252
3329
|
return runPackageDoctor(args);
|
|
2253
3330
|
}
|
|
2254
3331
|
function checkDocGov(options = {}) {
|
|
2255
|
-
const run = options.run ?? ((command2, args) =>
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
3332
|
+
const run = options.run ?? ((command2, args) => {
|
|
3333
|
+
return spawnPlatformSync(command2, args, {
|
|
3334
|
+
encoding: "utf8",
|
|
3335
|
+
stdio: "ignore"
|
|
3336
|
+
});
|
|
3337
|
+
});
|
|
2259
3338
|
const dependencyCli = (options.resolveDependencyCli ?? resolveDocGovDependencyCli)();
|
|
2260
3339
|
if (!dependencyCli) {
|
|
2261
3340
|
const fromPath = run("doc-gov", ["--help"]);
|
|
@@ -2283,16 +3362,16 @@ function resolveDocGovDependencyCli() {
|
|
|
2283
3362
|
try {
|
|
2284
3363
|
const require2 = createRequire(import.meta.url);
|
|
2285
3364
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
2286
|
-
const cliPath =
|
|
2287
|
-
return
|
|
3365
|
+
const cliPath = join13(dirname7(packageJsonPath), "dist/cli.js");
|
|
3366
|
+
return existsSync13(cliPath) ? cliPath : null;
|
|
2288
3367
|
} catch {
|
|
2289
3368
|
return null;
|
|
2290
3369
|
}
|
|
2291
3370
|
}
|
|
2292
3371
|
|
|
2293
3372
|
// src/commands/init.ts
|
|
2294
|
-
import { lstatSync as
|
|
2295
|
-
import { basename as
|
|
3373
|
+
import { lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, symlinkSync as symlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3374
|
+
import { basename as basename3, dirname as dirname8, join as join14 } from "node:path";
|
|
2296
3375
|
|
|
2297
3376
|
// src/commands/shared.ts
|
|
2298
3377
|
function planStarterFiles(profile) {
|
|
@@ -2379,7 +3458,7 @@ function runInit(args) {
|
|
|
2379
3458
|
function applyStarterFiles(files, profile) {
|
|
2380
3459
|
const root = process.cwd();
|
|
2381
3460
|
const conflicts = files.filter((file) => {
|
|
2382
|
-
const targetPath =
|
|
3461
|
+
const targetPath = join14(root, file.targetPath);
|
|
2383
3462
|
const stat = safeLstat(targetPath);
|
|
2384
3463
|
if (!stat) return false;
|
|
2385
3464
|
return file.kind !== "directory" || !stat.isDirectory();
|
|
@@ -2391,19 +3470,19 @@ function applyStarterFiles(files, profile) {
|
|
|
2391
3470
|
return 1;
|
|
2392
3471
|
}
|
|
2393
3472
|
for (const file of files) {
|
|
2394
|
-
const targetPath =
|
|
2395
|
-
|
|
3473
|
+
const targetPath = join14(root, file.targetPath);
|
|
3474
|
+
mkdirSync5(dirname8(targetPath), { recursive: true });
|
|
2396
3475
|
if (file.kind === "directory") {
|
|
2397
|
-
|
|
3476
|
+
mkdirSync5(targetPath, { recursive: true });
|
|
2398
3477
|
continue;
|
|
2399
3478
|
}
|
|
2400
3479
|
if (file.kind === "symlink") {
|
|
2401
|
-
|
|
3480
|
+
symlinkSync3(file.linkTarget, targetPath);
|
|
2402
3481
|
continue;
|
|
2403
3482
|
}
|
|
2404
|
-
const source =
|
|
2405
|
-
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"),
|
|
2406
|
-
|
|
3483
|
+
const source = readFileSync9(file.absoluteSourcePath);
|
|
3484
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
3485
|
+
writeFileSync4(targetPath, content);
|
|
2407
3486
|
}
|
|
2408
3487
|
console.log("pro-gov init APPLIED");
|
|
2409
3488
|
console.log(`profile: ${profile}`);
|
|
@@ -2420,7 +3499,7 @@ function renderAgentsTemplate(template, projectName, profile) {
|
|
|
2420
3499
|
}
|
|
2421
3500
|
function safeLstat(path) {
|
|
2422
3501
|
try {
|
|
2423
|
-
return
|
|
3502
|
+
return lstatSync6(path);
|
|
2424
3503
|
} catch {
|
|
2425
3504
|
return void 0;
|
|
2426
3505
|
}
|
|
@@ -2434,22 +3513,22 @@ function readFlag(args, flag) {
|
|
|
2434
3513
|
}
|
|
2435
3514
|
|
|
2436
3515
|
// src/commands/host-lens.ts
|
|
2437
|
-
import { mkdirSync as
|
|
2438
|
-
import { dirname as
|
|
3516
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3517
|
+
import { dirname as dirname11, resolve as resolve6 } from "node:path";
|
|
2439
3518
|
|
|
2440
3519
|
// src/host-lens.ts
|
|
2441
3520
|
import { execFileSync } from "node:child_process";
|
|
2442
3521
|
import {
|
|
2443
3522
|
cpSync as cpSync2,
|
|
2444
|
-
existsSync as
|
|
2445
|
-
lstatSync as
|
|
2446
|
-
mkdirSync as
|
|
2447
|
-
readdirSync as
|
|
2448
|
-
statSync as
|
|
2449
|
-
writeFileSync as
|
|
3523
|
+
existsSync as existsSync14,
|
|
3524
|
+
lstatSync as lstatSync7,
|
|
3525
|
+
mkdirSync as mkdirSync6,
|
|
3526
|
+
readdirSync as readdirSync8,
|
|
3527
|
+
statSync as statSync5,
|
|
3528
|
+
writeFileSync as writeFileSync5
|
|
2450
3529
|
} from "node:fs";
|
|
2451
3530
|
import { homedir } from "node:os";
|
|
2452
|
-
import { dirname as
|
|
3531
|
+
import { dirname as dirname9, join as join15, relative as relative7, resolve as resolve4 } from "node:path";
|
|
2453
3532
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2454
3533
|
var ROOT_DEFINITIONS = [
|
|
2455
3534
|
{
|
|
@@ -2504,36 +3583,36 @@ var ROOT_DEFINITIONS = [
|
|
|
2504
3583
|
{
|
|
2505
3584
|
id: "pnpm-store",
|
|
2506
3585
|
label: "pnpm Store",
|
|
2507
|
-
relativePath: "Library/pnpm/store",
|
|
3586
|
+
relativePath: process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store",
|
|
2508
3587
|
kind: "package-cache",
|
|
2509
3588
|
note: "pnpm \u5185\u5BB9\u5BFB\u5740\u4ED3\u5E93\uFF1B\u6E05\u7406\u65F6\u53EA\u5EFA\u8BAE pnpm store prune\u3002"
|
|
2510
3589
|
},
|
|
2511
3590
|
{
|
|
2512
3591
|
id: "playwright",
|
|
2513
3592
|
label: "Playwright \u6D4F\u89C8\u5668",
|
|
2514
|
-
relativePath: "Library/Caches/ms-playwright",
|
|
3593
|
+
relativePath: process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright",
|
|
2515
3594
|
kind: "browser-cache",
|
|
2516
3595
|
note: "\u6D4B\u8BD5\u6D4F\u89C8\u5668\u4E8C\u8FDB\u5236\u7F13\u5B58\uFF1B\u7248\u672C\u5806\u79EF\u53EF\u80FD\u9020\u6210\u663E\u8457\u78C1\u76D8\u5360\u7528\u3002"
|
|
2517
3596
|
},
|
|
2518
3597
|
{
|
|
2519
3598
|
id: "claude-desktop",
|
|
2520
3599
|
label: "Claude Desktop",
|
|
2521
|
-
relativePath: "Library/Application Support/Claude",
|
|
3600
|
+
relativePath: process.platform === "win32" ? "AppData/Roaming/Claude" : "Library/Application Support/Claude",
|
|
2522
3601
|
kind: "ai-host",
|
|
2523
3602
|
note: "\u684C\u9762\u5E94\u7528\u8FD0\u884C\u6570\u636E\uFF0C\u9ED8\u8BA4\u53D7\u4FDD\u62A4\u3002"
|
|
2524
3603
|
},
|
|
2525
3604
|
{
|
|
2526
3605
|
id: "cursor-desktop",
|
|
2527
3606
|
label: "Cursor Desktop",
|
|
2528
|
-
relativePath: "Library/Application Support/Cursor",
|
|
3607
|
+
relativePath: process.platform === "win32" ? "AppData/Roaming/Cursor" : "Library/Application Support/Cursor",
|
|
2529
3608
|
kind: "ai-host",
|
|
2530
3609
|
note: "\u684C\u9762\u5E94\u7528\u8FD0\u884C\u6570\u636E\uFF0C\u9ED8\u8BA4\u53D7\u4FDD\u62A4\u3002"
|
|
2531
3610
|
}
|
|
2532
3611
|
];
|
|
2533
3612
|
function inspectHost(options = {}) {
|
|
2534
|
-
const homePath =
|
|
3613
|
+
const homePath = resolve4(options.homeDir ?? homedir());
|
|
2535
3614
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
2536
|
-
const rootPaths = ROOT_DEFINITIONS.map((definition) =>
|
|
3615
|
+
const rootPaths = ROOT_DEFINITIONS.map((definition) => join15(homePath, definition.relativePath));
|
|
2537
3616
|
const rootSizes = measurePaths(rootPaths);
|
|
2538
3617
|
const findings = inspectFindings(homePath);
|
|
2539
3618
|
const rootFindingIds = /* @__PURE__ */ new Map();
|
|
@@ -2550,7 +3629,7 @@ function inspectHost(options = {}) {
|
|
|
2550
3629
|
label: definition.label,
|
|
2551
3630
|
path: displayPath(path, homePath),
|
|
2552
3631
|
kind: definition.kind,
|
|
2553
|
-
exists:
|
|
3632
|
+
exists: existsSync14(path),
|
|
2554
3633
|
bytes: rootSizes.get(path) ?? 0,
|
|
2555
3634
|
status: rootFindings.length > 0 ? "attention" : "healthy",
|
|
2556
3635
|
note: definition.note
|
|
@@ -2593,7 +3672,7 @@ function inspectHost(options = {}) {
|
|
|
2593
3672
|
function inspectAgentBrowser(options) {
|
|
2594
3673
|
const command2 = options.command ?? "agent-browser";
|
|
2595
3674
|
try {
|
|
2596
|
-
const
|
|
3675
|
+
const result = spawnPlatformSync(command2, ["--version"], {
|
|
2597
3676
|
encoding: "utf8",
|
|
2598
3677
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2599
3678
|
// Host scans can run alongside the full portfolio test matrix. Keep the
|
|
@@ -2601,7 +3680,9 @@ function inspectAgentBrowser(options) {
|
|
|
2601
3680
|
// spawn the pinned CLI instead of misclassifying a slow start as an
|
|
2602
3681
|
// unparseable version.
|
|
2603
3682
|
timeout: 1e4
|
|
2604
|
-
})
|
|
3683
|
+
});
|
|
3684
|
+
if (result.error || result.status !== 0) throw result.error ?? new Error("command failed");
|
|
3685
|
+
const output = result.stdout.trim();
|
|
2605
3686
|
const installedVersion = output.match(/(?:^|\s)(\d+\.\d+\.\d+)(?:\s|$)/)?.[1];
|
|
2606
3687
|
if (!installedVersion) {
|
|
2607
3688
|
return {
|
|
@@ -2655,25 +3736,25 @@ function createHostLensCleanupPlan(report) {
|
|
|
2655
3736
|
};
|
|
2656
3737
|
}
|
|
2657
3738
|
function writeHostLensReport(report, outDir) {
|
|
2658
|
-
|
|
3739
|
+
mkdirSync6(outDir, { recursive: true });
|
|
2659
3740
|
const assets = findHostDashboardAssets();
|
|
2660
3741
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
2661
|
-
const source =
|
|
2662
|
-
if (!
|
|
2663
|
-
cpSync2(source,
|
|
3742
|
+
const source = join15(assets, file);
|
|
3743
|
+
if (!existsSync14(source)) throw new Error(`HostLens dashboard asset is missing: ${source}`);
|
|
3744
|
+
cpSync2(source, join15(outDir, file));
|
|
2664
3745
|
}
|
|
2665
|
-
const jsonPath =
|
|
2666
|
-
const htmlPath =
|
|
2667
|
-
|
|
3746
|
+
const jsonPath = join15(outDir, "host-lens.json");
|
|
3747
|
+
const htmlPath = join15(outDir, "index.html");
|
|
3748
|
+
writeFileSync5(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
2668
3749
|
`);
|
|
2669
|
-
|
|
3750
|
+
writeFileSync5(join15(outDir, "data.js"), `window.__HOST_LENS__ = ${safeJavaScriptJson(report)};
|
|
2670
3751
|
`);
|
|
2671
3752
|
return { jsonPath, htmlPath };
|
|
2672
3753
|
}
|
|
2673
3754
|
function inspectFindings(homePath) {
|
|
2674
3755
|
const candidates = [];
|
|
2675
3756
|
const addGroup = (value) => {
|
|
2676
|
-
const paths = value.paths.filter((path) =>
|
|
3757
|
+
const paths = value.paths.filter((path) => existsSync14(path));
|
|
2677
3758
|
if (paths.length === 0) return;
|
|
2678
3759
|
candidates.push({ ...value, paths: paths.map((path) => displayPath(path, homePath)) });
|
|
2679
3760
|
};
|
|
@@ -2681,7 +3762,7 @@ function inspectFindings(homePath) {
|
|
|
2681
3762
|
id: "codex-backups",
|
|
2682
3763
|
rootId: "codex",
|
|
2683
3764
|
label: "Codex \u5386\u53F2\u5907\u4EFD",
|
|
2684
|
-
paths: matchingChildren(
|
|
3765
|
+
paths: matchingChildren(join15(homePath, ".codex"), (name) => /^backup-/i.test(name)),
|
|
2685
3766
|
category: "backup",
|
|
2686
3767
|
confidence: "medium",
|
|
2687
3768
|
disposition: "manual-review",
|
|
@@ -2692,7 +3773,7 @@ function inspectFindings(homePath) {
|
|
|
2692
3773
|
id: "codex-cache",
|
|
2693
3774
|
rootId: "codex",
|
|
2694
3775
|
label: "Codex \u53EF\u518D\u751F\u6210\u7F13\u5B58",
|
|
2695
|
-
paths: [
|
|
3776
|
+
paths: [join15(homePath, ".codex/cache")],
|
|
2696
3777
|
category: "cache",
|
|
2697
3778
|
confidence: "high",
|
|
2698
3779
|
disposition: "report-only",
|
|
@@ -2703,7 +3784,7 @@ function inspectFindings(homePath) {
|
|
|
2703
3784
|
id: "claude-temporary",
|
|
2704
3785
|
rootId: "claude-code",
|
|
2705
3786
|
label: "Claude Code \u4E34\u65F6\u76EE\u5F55",
|
|
2706
|
-
paths: matchingChildren(
|
|
3787
|
+
paths: matchingChildren(join15(homePath, ".claude"), (name) => /^(temp|tmp)[-_]/i.test(name)),
|
|
2707
3788
|
category: "temporary",
|
|
2708
3789
|
confidence: "high",
|
|
2709
3790
|
disposition: "manual-review",
|
|
@@ -2714,7 +3795,7 @@ function inspectFindings(homePath) {
|
|
|
2714
3795
|
id: "claude-cache",
|
|
2715
3796
|
rootId: "claude-code",
|
|
2716
3797
|
label: "Claude Code \u7F13\u5B58",
|
|
2717
|
-
paths: [
|
|
3798
|
+
paths: [join15(homePath, ".claude/cache")],
|
|
2718
3799
|
category: "cache",
|
|
2719
3800
|
confidence: "high",
|
|
2720
3801
|
disposition: "report-only",
|
|
@@ -2725,7 +3806,7 @@ function inspectFindings(homePath) {
|
|
|
2725
3806
|
id: "copilot-session-history",
|
|
2726
3807
|
rootId: "copilot",
|
|
2727
3808
|
label: "Copilot \u4F1A\u8BDD\u72B6\u6001",
|
|
2728
|
-
paths: [
|
|
3809
|
+
paths: [join15(homePath, ".copilot/session-state")],
|
|
2729
3810
|
category: "session-history",
|
|
2730
3811
|
confidence: "low",
|
|
2731
3812
|
disposition: "manual-review",
|
|
@@ -2736,7 +3817,7 @@ function inspectFindings(homePath) {
|
|
|
2736
3817
|
id: "npm-npx-cache",
|
|
2737
3818
|
rootId: "npm-cache",
|
|
2738
3819
|
label: "npx \u4E34\u65F6\u5B89\u88C5\u7F13\u5B58",
|
|
2739
|
-
paths: [
|
|
3820
|
+
paths: [join15(homePath, ".npm/_npx")],
|
|
2740
3821
|
category: "cache",
|
|
2741
3822
|
confidence: "high",
|
|
2742
3823
|
disposition: "native-tool",
|
|
@@ -2748,7 +3829,7 @@ function inspectFindings(homePath) {
|
|
|
2748
3829
|
id: "npm-content-cache",
|
|
2749
3830
|
rootId: "npm-cache",
|
|
2750
3831
|
label: "npm \u5185\u5BB9\u7F13\u5B58",
|
|
2751
|
-
paths: [
|
|
3832
|
+
paths: [join15(homePath, ".npm/_cacache")],
|
|
2752
3833
|
category: "cache",
|
|
2753
3834
|
confidence: "high",
|
|
2754
3835
|
disposition: "native-tool",
|
|
@@ -2760,7 +3841,12 @@ function inspectFindings(homePath) {
|
|
|
2760
3841
|
id: "pnpm-store",
|
|
2761
3842
|
rootId: "pnpm-store",
|
|
2762
3843
|
label: "pnpm \u672A\u5F15\u7528\u5305\u5019\u9009",
|
|
2763
|
-
paths: [
|
|
3844
|
+
paths: [
|
|
3845
|
+
join15(
|
|
3846
|
+
homePath,
|
|
3847
|
+
process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store"
|
|
3848
|
+
)
|
|
3849
|
+
],
|
|
2764
3850
|
category: "cache",
|
|
2765
3851
|
confidence: "high",
|
|
2766
3852
|
disposition: "native-tool",
|
|
@@ -2772,7 +3858,13 @@ function inspectFindings(homePath) {
|
|
|
2772
3858
|
id: "playwright-browsers",
|
|
2773
3859
|
rootId: "playwright",
|
|
2774
3860
|
label: "Playwright \u6D4F\u89C8\u5668\u7248\u672C",
|
|
2775
|
-
paths: [
|
|
3861
|
+
paths: [
|
|
3862
|
+
join15(
|
|
3863
|
+
homePath,
|
|
3864
|
+
process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright"
|
|
3865
|
+
),
|
|
3866
|
+
join15(homePath, ".cache/ms-playwright")
|
|
3867
|
+
],
|
|
2776
3868
|
category: "duplicate-runtime",
|
|
2777
3869
|
confidence: "medium",
|
|
2778
3870
|
disposition: "native-tool",
|
|
@@ -2794,6 +3886,8 @@ function inspectFindings(homePath) {
|
|
|
2794
3886
|
});
|
|
2795
3887
|
}
|
|
2796
3888
|
function buildProtections(homePath) {
|
|
3889
|
+
const claudeDesktopPath = process.platform === "win32" ? "~/AppData/Roaming/Claude" : "~/Library/Application Support/Claude";
|
|
3890
|
+
const cursorDesktopPath = process.platform === "win32" ? "~/AppData/Roaming/Cursor" : "~/Library/Application Support/Cursor";
|
|
2797
3891
|
return [
|
|
2798
3892
|
{
|
|
2799
3893
|
label: "\u914D\u7F6E\u4E0E\u8EAB\u4EFD\u8FB9\u754C",
|
|
@@ -2812,7 +3906,7 @@ function buildProtections(homePath) {
|
|
|
2812
3906
|
},
|
|
2813
3907
|
{
|
|
2814
3908
|
label: "\u684C\u9762\u5BBF\u4E3B\u8FD0\u884C\u6570\u636E",
|
|
2815
|
-
paths: [
|
|
3909
|
+
paths: [claudeDesktopPath, cursorDesktopPath],
|
|
2816
3910
|
reason: "\u5E94\u7528\u6570\u636E\u5E93\u4E0E\u8FD0\u884C\u72B6\u6001\u6DF7\u5728\u5176\u4E2D\uFF0C\u4E0D\u80FD\u628A\u6574\u4E2A\u76EE\u5F55\u5F53\u7F13\u5B58\u3002"
|
|
2817
3911
|
}
|
|
2818
3912
|
].map((protection) => ({
|
|
@@ -2821,16 +3915,16 @@ function buildProtections(homePath) {
|
|
|
2821
3915
|
}));
|
|
2822
3916
|
}
|
|
2823
3917
|
function matchingChildren(root, predicate) {
|
|
2824
|
-
if (!
|
|
3918
|
+
if (!existsSync14(root)) return [];
|
|
2825
3919
|
try {
|
|
2826
|
-
return
|
|
3920
|
+
return readdirSync8(root, { withFileTypes: true }).filter((entry) => predicate(entry.name)).map((entry) => join15(root, entry.name));
|
|
2827
3921
|
} catch {
|
|
2828
3922
|
return [];
|
|
2829
3923
|
}
|
|
2830
3924
|
}
|
|
2831
3925
|
function measurePaths(paths) {
|
|
2832
|
-
const uniquePaths = [...new Set(paths.map((path) =>
|
|
2833
|
-
(path) =>
|
|
3926
|
+
const uniquePaths = [...new Set(paths.map((path) => resolve4(path)))].filter(
|
|
3927
|
+
(path) => existsSync14(path)
|
|
2834
3928
|
);
|
|
2835
3929
|
const result = /* @__PURE__ */ new Map();
|
|
2836
3930
|
if (uniquePaths.length === 0) return result;
|
|
@@ -2843,7 +3937,7 @@ function measurePaths(paths) {
|
|
|
2843
3937
|
for (const line of output.split(/\r?\n/)) {
|
|
2844
3938
|
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
2845
3939
|
if (!match) continue;
|
|
2846
|
-
result.set(
|
|
3940
|
+
result.set(resolve4(match[2]), Number(match[1]) * 1024);
|
|
2847
3941
|
}
|
|
2848
3942
|
return result;
|
|
2849
3943
|
} catch {
|
|
@@ -2853,7 +3947,7 @@ function measurePaths(paths) {
|
|
|
2853
3947
|
}
|
|
2854
3948
|
function fallbackMeasure(root) {
|
|
2855
3949
|
try {
|
|
2856
|
-
const rootStats =
|
|
3950
|
+
const rootStats = lstatSync7(root);
|
|
2857
3951
|
if (!rootStats.isDirectory()) return rootStats.size;
|
|
2858
3952
|
} catch {
|
|
2859
3953
|
return 0;
|
|
@@ -2866,17 +3960,17 @@ function fallbackMeasure(root) {
|
|
|
2866
3960
|
if (!current) continue;
|
|
2867
3961
|
let entries;
|
|
2868
3962
|
try {
|
|
2869
|
-
entries =
|
|
3963
|
+
entries = readdirSync8(current, { withFileTypes: true });
|
|
2870
3964
|
} catch {
|
|
2871
3965
|
continue;
|
|
2872
3966
|
}
|
|
2873
3967
|
for (const entry of entries) {
|
|
2874
3968
|
visited += 1;
|
|
2875
|
-
const path =
|
|
3969
|
+
const path = join15(current, entry.name);
|
|
2876
3970
|
if (entry.isDirectory()) pending.push(path);
|
|
2877
3971
|
else if (entry.isFile()) {
|
|
2878
3972
|
try {
|
|
2879
|
-
bytes +=
|
|
3973
|
+
bytes += statSync5(path).size;
|
|
2880
3974
|
} catch {
|
|
2881
3975
|
}
|
|
2882
3976
|
}
|
|
@@ -2887,35 +3981,35 @@ function fallbackMeasure(root) {
|
|
|
2887
3981
|
}
|
|
2888
3982
|
function countImmediateEntries(path) {
|
|
2889
3983
|
try {
|
|
2890
|
-
return
|
|
3984
|
+
return lstatSync7(path).isDirectory() ? readdirSync8(path).length : 1;
|
|
2891
3985
|
} catch {
|
|
2892
3986
|
return 0;
|
|
2893
3987
|
}
|
|
2894
3988
|
}
|
|
2895
3989
|
function displayPath(path, homePath) {
|
|
2896
|
-
const absolute =
|
|
2897
|
-
const withinHome =
|
|
3990
|
+
const absolute = resolve4(path);
|
|
3991
|
+
const withinHome = relative7(homePath, absolute);
|
|
2898
3992
|
if (withinHome === "") return "~";
|
|
2899
|
-
if (!withinHome.startsWith("..")) return `~/${withinHome}`;
|
|
3993
|
+
if (!withinHome.startsWith("..")) return `~/${withinHome.replaceAll("\\", "/")}`;
|
|
2900
3994
|
return absolute;
|
|
2901
3995
|
}
|
|
2902
3996
|
function undisplayPath(path, homePath) {
|
|
2903
3997
|
if (path === "~") return homePath;
|
|
2904
|
-
if (path.startsWith("~/")) return
|
|
2905
|
-
return
|
|
3998
|
+
if (path.startsWith("~/")) return join15(homePath, path.slice(2));
|
|
3999
|
+
return resolve4(path);
|
|
2906
4000
|
}
|
|
2907
4001
|
function findHostDashboardAssets() {
|
|
2908
|
-
const packageRoot2 =
|
|
4002
|
+
const packageRoot2 = dirname9(dirname9(fileURLToPath3(import.meta.url)));
|
|
2909
4003
|
const candidates = [
|
|
2910
4004
|
process.env.PGS_HOST_DASHBOARD_ASSETS_DIR,
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
4005
|
+
join15(packageRoot2, ".host-dashboard-build"),
|
|
4006
|
+
join15(packageRoot2, "assets/host-dashboard"),
|
|
4007
|
+
join15(process.cwd(), ".host-dashboard-build"),
|
|
4008
|
+
join15(process.cwd(), "assets/host-dashboard"),
|
|
4009
|
+
join15(process.cwd(), "packages/pro-gov/.host-dashboard-build"),
|
|
4010
|
+
join15(process.cwd(), "packages/pro-gov/assets/host-dashboard")
|
|
2917
4011
|
].filter((value) => Boolean(value));
|
|
2918
|
-
const match = candidates.find((path) =>
|
|
4012
|
+
const match = candidates.find((path) => existsSync14(join15(path, "index.html")));
|
|
2919
4013
|
if (!match)
|
|
2920
4014
|
throw new Error(
|
|
2921
4015
|
"HostLens dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -2927,12 +4021,12 @@ function safeJavaScriptJson(value) {
|
|
|
2927
4021
|
}
|
|
2928
4022
|
|
|
2929
4023
|
// src/portfolio/manifest.ts
|
|
2930
|
-
import { existsSync as
|
|
2931
|
-
import { dirname as
|
|
4024
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
|
|
4025
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, resolve as resolve5 } from "node:path";
|
|
2932
4026
|
function loadPortfolioManifest(configPath) {
|
|
2933
4027
|
let parsed;
|
|
2934
4028
|
try {
|
|
2935
|
-
parsed = JSON.parse(
|
|
4029
|
+
parsed = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2936
4030
|
} catch (error) {
|
|
2937
4031
|
return {
|
|
2938
4032
|
configPath,
|
|
@@ -2944,7 +4038,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
2944
4038
|
]
|
|
2945
4039
|
};
|
|
2946
4040
|
}
|
|
2947
|
-
const normalized = resolveManifestPaths(parsed,
|
|
4041
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve5(configPath)));
|
|
2948
4042
|
const issues = validatePortfolioManifest(normalized);
|
|
2949
4043
|
return {
|
|
2950
4044
|
configPath,
|
|
@@ -2955,16 +4049,16 @@ function loadPortfolioManifest(configPath) {
|
|
|
2955
4049
|
function resolveManifestPaths(value, configDir) {
|
|
2956
4050
|
if (!isRecord(value)) return value;
|
|
2957
4051
|
const resolveEndpoint = (endpoint) => {
|
|
2958
|
-
if (!isRecord(endpoint) || typeof endpoint.path !== "string" ||
|
|
4052
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute4(endpoint.path)) {
|
|
2959
4053
|
return endpoint;
|
|
2960
4054
|
}
|
|
2961
|
-
return { ...endpoint, path:
|
|
4055
|
+
return { ...endpoint, path: resolve5(configDir, endpoint.path) };
|
|
2962
4056
|
};
|
|
2963
4057
|
return {
|
|
2964
4058
|
...value,
|
|
2965
|
-
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !
|
|
4059
|
+
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !isAbsolute4(value.technologyGovernance.strategySource) ? {
|
|
2966
4060
|
...value.technologyGovernance,
|
|
2967
|
-
strategySource:
|
|
4061
|
+
strategySource: resolve5(configDir, value.technologyGovernance.strategySource)
|
|
2968
4062
|
} : value.technologyGovernance,
|
|
2969
4063
|
controlPlane: resolveEndpoint(value.controlPlane),
|
|
2970
4064
|
executionEngine: resolveEndpoint(value.executionEngine),
|
|
@@ -3149,7 +4243,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
|
3149
4243
|
});
|
|
3150
4244
|
return;
|
|
3151
4245
|
}
|
|
3152
|
-
if (!
|
|
4246
|
+
if (!existsSync15(value.path)) {
|
|
3153
4247
|
issues.push({
|
|
3154
4248
|
type: "missing-path",
|
|
3155
4249
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3580,13 +4674,13 @@ function isExactVersion(value) {
|
|
|
3580
4674
|
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
|
|
3581
4675
|
}
|
|
3582
4676
|
function isRepositoryRelativePath(value) {
|
|
3583
|
-
if (value.length === 0 ||
|
|
4677
|
+
if (value.length === 0 || isAbsolute4(value)) return false;
|
|
3584
4678
|
const segments = value.replaceAll("\\", "/").split("/");
|
|
3585
4679
|
return !segments.includes("..");
|
|
3586
4680
|
}
|
|
3587
4681
|
function isExactRepositoryRelativePath(value) {
|
|
3588
4682
|
if (value.length === 0) return false;
|
|
3589
|
-
if (
|
|
4683
|
+
if (isAbsolute4(value)) return false;
|
|
3590
4684
|
if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\") || value.startsWith("//"))
|
|
3591
4685
|
return false;
|
|
3592
4686
|
if (value.includes("\\")) return false;
|
|
@@ -3884,7 +4978,7 @@ function runScan(options) {
|
|
|
3884
4978
|
const inspection = resolveInspectionOptions(options);
|
|
3885
4979
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3886
4980
|
const report = inspectHost(inspection.value);
|
|
3887
|
-
const written = writeHostLensReport(report,
|
|
4981
|
+
const written = writeHostLensReport(report, resolve6(options.outPath));
|
|
3888
4982
|
if (options.json) {
|
|
3889
4983
|
console.log(JSON.stringify({ ok: true, ...written, summary: report.summary }, null, 2));
|
|
3890
4984
|
} else {
|
|
@@ -3904,9 +4998,9 @@ function runPlan(options) {
|
|
|
3904
4998
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3905
4999
|
const report = inspectHost(inspection.value);
|
|
3906
5000
|
const plan = createHostLensCleanupPlan(report);
|
|
3907
|
-
const outPath =
|
|
3908
|
-
|
|
3909
|
-
|
|
5001
|
+
const outPath = resolve6(options.outPath);
|
|
5002
|
+
mkdirSync7(dirname11(outPath), { recursive: true });
|
|
5003
|
+
writeFileSync6(outPath, `${JSON.stringify(plan, null, 2)}
|
|
3910
5004
|
`);
|
|
3911
5005
|
if (options.json)
|
|
3912
5006
|
console.log(JSON.stringify({ ok: true, outPath, actions: plan.actions.length }, null, 2));
|
|
@@ -3979,7 +5073,7 @@ function parseOptions(args) {
|
|
|
3979
5073
|
if (arg === "--home") {
|
|
3980
5074
|
const value = args[index + 1];
|
|
3981
5075
|
if (!value) return { ok: false, error: "Expected value after --home" };
|
|
3982
|
-
options.homeDir =
|
|
5076
|
+
options.homeDir = resolve6(value);
|
|
3983
5077
|
index += 1;
|
|
3984
5078
|
continue;
|
|
3985
5079
|
}
|
|
@@ -3993,7 +5087,7 @@ function parseOptions(args) {
|
|
|
3993
5087
|
if (arg === "--config") {
|
|
3994
5088
|
const value = args[index + 1];
|
|
3995
5089
|
if (!value) return { ok: false, error: "Expected value after --config" };
|
|
3996
|
-
options.configPath =
|
|
5090
|
+
options.configPath = resolve6(value);
|
|
3997
5091
|
index += 1;
|
|
3998
5092
|
continue;
|
|
3999
5093
|
}
|
|
@@ -4019,8 +5113,8 @@ function printUsage2() {
|
|
|
4019
5113
|
}
|
|
4020
5114
|
|
|
4021
5115
|
// src/learning/recall.ts
|
|
4022
|
-
import { existsSync as
|
|
4023
|
-
import { basename as
|
|
5116
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync11 } from "node:fs";
|
|
5117
|
+
import { basename as basename4, join as join16, relative as relative8 } from "node:path";
|
|
4024
5118
|
function recallLearnings(root, options) {
|
|
4025
5119
|
const query = options.query.trim();
|
|
4026
5120
|
const terms = tokenize(query);
|
|
@@ -4043,16 +5137,16 @@ function recallLearnings(root, options) {
|
|
|
4043
5137
|
function loadLearningRecords(root) {
|
|
4044
5138
|
const recordsByTitle = /* @__PURE__ */ new Map();
|
|
4045
5139
|
for (const relativeDir of ["docs/reference/learnings", "docs/solutions"]) {
|
|
4046
|
-
const learningDir =
|
|
4047
|
-
if (!
|
|
5140
|
+
const learningDir = join16(root, relativeDir);
|
|
5141
|
+
if (!existsSync16(learningDir)) continue;
|
|
4048
5142
|
for (const path of listMarkdownFiles(learningDir)) {
|
|
4049
5143
|
const record = readLearningRecord(root, path);
|
|
4050
5144
|
const key = record.title.trim().toLowerCase();
|
|
4051
5145
|
if (!recordsByTitle.has(key)) recordsByTitle.set(key, record);
|
|
4052
5146
|
}
|
|
4053
5147
|
}
|
|
4054
|
-
const conceptsPath =
|
|
4055
|
-
if (
|
|
5148
|
+
const conceptsPath = join16(root, "CONCEPTS.md");
|
|
5149
|
+
if (existsSync16(conceptsPath)) {
|
|
4056
5150
|
const record = readLearningRecord(root, conceptsPath);
|
|
4057
5151
|
recordsByTitle.set(`concepts:${record.title.toLowerCase()}`, record);
|
|
4058
5152
|
}
|
|
@@ -4060,8 +5154,8 @@ function loadLearningRecords(root) {
|
|
|
4060
5154
|
}
|
|
4061
5155
|
function listMarkdownFiles(dir) {
|
|
4062
5156
|
const files = [];
|
|
4063
|
-
for (const entry of
|
|
4064
|
-
const absolutePath =
|
|
5157
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
5158
|
+
const absolutePath = join16(dir, entry.name);
|
|
4065
5159
|
if (entry.isDirectory()) {
|
|
4066
5160
|
files.push(...listMarkdownFiles(absolutePath));
|
|
4067
5161
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -4071,11 +5165,11 @@ function listMarkdownFiles(dir) {
|
|
|
4071
5165
|
return files.sort();
|
|
4072
5166
|
}
|
|
4073
5167
|
function readLearningRecord(root, absolutePath) {
|
|
4074
|
-
const content =
|
|
5168
|
+
const content = readFileSync11(absolutePath, "utf8");
|
|
4075
5169
|
const parsed = splitFrontmatter(content);
|
|
4076
5170
|
const body = parsed.body;
|
|
4077
5171
|
return {
|
|
4078
|
-
relativePath: normalizePath(
|
|
5172
|
+
relativePath: normalizePath(relative8(root, absolutePath)),
|
|
4079
5173
|
title: findTitle(parsed.frontmatter, body) ?? titleFromPath(absolutePath),
|
|
4080
5174
|
metadata: parsed.frontmatter,
|
|
4081
5175
|
body
|
|
@@ -4103,7 +5197,7 @@ function findTitle(frontmatter, body) {
|
|
|
4103
5197
|
return heading ? heading.slice(2).trim() : void 0;
|
|
4104
5198
|
}
|
|
4105
5199
|
function titleFromPath(path) {
|
|
4106
|
-
return
|
|
5200
|
+
return basename4(path, ".md").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
4107
5201
|
}
|
|
4108
5202
|
function scoreRecord(record, terms) {
|
|
4109
5203
|
const title = record.title.toLowerCase();
|
|
@@ -4156,8 +5250,8 @@ function cleanMarkdownLine(input) {
|
|
|
4156
5250
|
}
|
|
4157
5251
|
|
|
4158
5252
|
// src/learning/capture.ts
|
|
4159
|
-
import { existsSync as
|
|
4160
|
-
import { basename as
|
|
5253
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5254
|
+
import { basename as basename5, join as join17, relative as relative9 } from "node:path";
|
|
4161
5255
|
function captureLearning(root, options) {
|
|
4162
5256
|
const title = options.title.trim();
|
|
4163
5257
|
const summary = options.summary.trim();
|
|
@@ -4165,13 +5259,13 @@ function captureLearning(root, options) {
|
|
|
4165
5259
|
if (!summary) throw new Error("summary is required");
|
|
4166
5260
|
const category = slugify(options.category ?? "workflow-issues") || "workflow-issues";
|
|
4167
5261
|
const moduleName = options.module?.trim() || "PGS learning capture";
|
|
4168
|
-
const dir =
|
|
4169
|
-
|
|
5262
|
+
const dir = join17(root, "docs/reference/learnings", category);
|
|
5263
|
+
mkdirSync8(dir, { recursive: true });
|
|
4170
5264
|
const path = uniquePath(dir, slugify(title) || "learning");
|
|
4171
|
-
const idSlug =
|
|
4172
|
-
|
|
5265
|
+
const idSlug = basename5(path, ".md");
|
|
5266
|
+
writeFileSync7(path, renderLearning({ title, summary, category, moduleName, idSlug }));
|
|
4173
5267
|
return {
|
|
4174
|
-
relativePath: normalizePath2(
|
|
5268
|
+
relativePath: normalizePath2(relative9(root, path)),
|
|
4175
5269
|
title,
|
|
4176
5270
|
captureMode: "pgs-native"
|
|
4177
5271
|
};
|
|
@@ -4213,10 +5307,10 @@ function renderLearning(options) {
|
|
|
4213
5307
|
}
|
|
4214
5308
|
function uniquePath(dir, slug) {
|
|
4215
5309
|
let index = 1;
|
|
4216
|
-
let candidate =
|
|
4217
|
-
while (
|
|
5310
|
+
let candidate = join17(dir, `${slug}.md`);
|
|
5311
|
+
while (existsSync17(candidate)) {
|
|
4218
5312
|
index += 1;
|
|
4219
|
-
candidate =
|
|
5313
|
+
candidate = join17(dir, `${slug}-${index}.md`);
|
|
4220
5314
|
}
|
|
4221
5315
|
return candidate;
|
|
4222
5316
|
}
|
|
@@ -4401,12 +5495,12 @@ function printUsage3() {
|
|
|
4401
5495
|
}
|
|
4402
5496
|
|
|
4403
5497
|
// src/commands/lens.ts
|
|
4404
|
-
import { mkdirSync as
|
|
4405
|
-
import { dirname as
|
|
5498
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5499
|
+
import { dirname as dirname14 } from "node:path";
|
|
4406
5500
|
|
|
4407
5501
|
// src/lens/audit.ts
|
|
4408
|
-
import { existsSync as
|
|
4409
|
-
import { basename as
|
|
5502
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5503
|
+
import { basename as basename6, dirname as dirname12, join as join18 } from "node:path";
|
|
4410
5504
|
var REQUIRED_ARTIFACTS = [
|
|
4411
5505
|
"manifest.md",
|
|
4412
5506
|
"raw/project-lens/architecture-lens.md",
|
|
@@ -4424,20 +5518,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
4424
5518
|
version: 1,
|
|
4425
5519
|
target: {
|
|
4426
5520
|
path: targetDir,
|
|
4427
|
-
name:
|
|
5521
|
+
name: basename6(targetDir) || "target"
|
|
4428
5522
|
},
|
|
4429
5523
|
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
4430
5524
|
};
|
|
4431
|
-
|
|
4432
|
-
writeJson(
|
|
5525
|
+
mkdirSync9(auditDir, { recursive: true });
|
|
5526
|
+
writeJson(join18(auditDir, "audit.contract.json"), contract);
|
|
4433
5527
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4434
|
-
writeTemplate(
|
|
5528
|
+
writeTemplate(join18(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
4435
5529
|
}
|
|
4436
5530
|
return contract;
|
|
4437
5531
|
}
|
|
4438
5532
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
4439
|
-
const contractPath =
|
|
4440
|
-
if (!
|
|
5533
|
+
const contractPath = join18(auditDir, "audit.contract.json");
|
|
5534
|
+
if (!existsSync18(contractPath)) {
|
|
4441
5535
|
return {
|
|
4442
5536
|
ok: false,
|
|
4443
5537
|
auditDir,
|
|
@@ -4451,7 +5545,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4451
5545
|
}
|
|
4452
5546
|
let contract;
|
|
4453
5547
|
try {
|
|
4454
|
-
contract = JSON.parse(
|
|
5548
|
+
contract = JSON.parse(readFileSync12(contractPath, "utf8"));
|
|
4455
5549
|
} catch (error) {
|
|
4456
5550
|
return {
|
|
4457
5551
|
ok: false,
|
|
@@ -4484,12 +5578,12 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4484
5578
|
}
|
|
4485
5579
|
}
|
|
4486
5580
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4487
|
-
const absolutePath =
|
|
4488
|
-
if (!
|
|
5581
|
+
const absolutePath = join18(auditDir, artifactPath);
|
|
5582
|
+
if (!existsSync18(absolutePath)) {
|
|
4489
5583
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
4490
5584
|
continue;
|
|
4491
5585
|
}
|
|
4492
|
-
const content =
|
|
5586
|
+
const content = readFileSync12(absolutePath, "utf8");
|
|
4493
5587
|
if (isPendingArtifact(content)) {
|
|
4494
5588
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
4495
5589
|
} else if (hasTemplateBody(content)) {
|
|
@@ -4505,13 +5599,13 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4505
5599
|
};
|
|
4506
5600
|
}
|
|
4507
5601
|
function writeJson(path, value) {
|
|
4508
|
-
|
|
4509
|
-
|
|
5602
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5603
|
+
writeFileSync8(path, `${JSON.stringify(value, null, 2)}
|
|
4510
5604
|
`);
|
|
4511
5605
|
}
|
|
4512
5606
|
function writeTemplate(path, content) {
|
|
4513
|
-
|
|
4514
|
-
|
|
5607
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5608
|
+
writeFileSync8(path, content);
|
|
4515
5609
|
}
|
|
4516
5610
|
function renderArtifactTemplate(artifactPath, contract) {
|
|
4517
5611
|
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
@@ -4789,14 +5883,14 @@ function formatLink(link) {
|
|
|
4789
5883
|
}
|
|
4790
5884
|
|
|
4791
5885
|
// src/lens/scan.ts
|
|
4792
|
-
import { spawnSync as
|
|
4793
|
-
import { existsSync as
|
|
5886
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5887
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14, statSync as statSync7 } from "node:fs";
|
|
4794
5888
|
import { homedir as homedir3 } from "node:os";
|
|
4795
|
-
import { join as
|
|
5889
|
+
import { join as join23 } from "node:path";
|
|
4796
5890
|
|
|
4797
5891
|
// src/host-ssot.ts
|
|
4798
|
-
import { lstatSync as
|
|
4799
|
-
import { dirname as
|
|
5892
|
+
import { lstatSync as lstatSync8, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "node:fs";
|
|
5893
|
+
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join19, resolve as resolve7 } from "node:path";
|
|
4800
5894
|
function inspectProjectHostSsot(root) {
|
|
4801
5895
|
const agentsEntry = inspectCanonicalPath(root, "AGENTS.md");
|
|
4802
5896
|
const canonicalSkills = inspectCanonicalPath(root, ".agents/skills");
|
|
@@ -4840,7 +5934,7 @@ function inspectUserSkillsSsot(homeDir) {
|
|
|
4840
5934
|
};
|
|
4841
5935
|
}
|
|
4842
5936
|
function inspectCanonicalPath(root, path) {
|
|
4843
|
-
const absolutePath =
|
|
5937
|
+
const absolutePath = join19(root, path);
|
|
4844
5938
|
const stat = safeLstat2(absolutePath);
|
|
4845
5939
|
if (!stat) return { path, status: "missing" };
|
|
4846
5940
|
if (stat.isSymbolicLink()) {
|
|
@@ -4856,7 +5950,7 @@ function inspectCanonicalPath(root, path) {
|
|
|
4856
5950
|
return { path, status: "other" };
|
|
4857
5951
|
}
|
|
4858
5952
|
function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
4859
|
-
const absolutePath =
|
|
5953
|
+
const absolutePath = join19(root, path);
|
|
4860
5954
|
const stat = safeLstat2(absolutePath);
|
|
4861
5955
|
const base = { path, expectedRawTarget, compliant: false };
|
|
4862
5956
|
if (!stat) return { ...base, status: "missing" };
|
|
@@ -4865,9 +5959,9 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4865
5959
|
if (stat.isDirectory()) return { ...base, status: "directory" };
|
|
4866
5960
|
return { ...base, status: "other" };
|
|
4867
5961
|
}
|
|
4868
|
-
const rawTarget = readlinkSync3(absolutePath);
|
|
4869
|
-
const resolvedTarget =
|
|
4870
|
-
const expectedPath =
|
|
5962
|
+
const rawTarget = normalizeSymlinkTarget(readlinkSync3(absolutePath));
|
|
5963
|
+
const resolvedTarget = resolve7(dirname13(absolutePath), rawTarget);
|
|
5964
|
+
const expectedPath = resolve7(dirname13(absolutePath), expectedRawTarget);
|
|
4871
5965
|
const targetStat = safeLstat2(resolvedTarget);
|
|
4872
5966
|
if (!targetStat) {
|
|
4873
5967
|
return {
|
|
@@ -4886,7 +5980,7 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4886
5980
|
if (!targetMatches) {
|
|
4887
5981
|
return { ...base, rawTarget, resolvedTarget, status: "wrong-target" };
|
|
4888
5982
|
}
|
|
4889
|
-
if (
|
|
5983
|
+
if (isAbsolute5(rawTarget)) {
|
|
4890
5984
|
return { ...base, rawTarget, resolvedTarget, status: "absolute-symlink" };
|
|
4891
5985
|
}
|
|
4892
5986
|
if (rawTarget !== expectedRawTarget) {
|
|
@@ -4907,16 +6001,16 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4907
6001
|
}
|
|
4908
6002
|
function safeLstat2(path) {
|
|
4909
6003
|
try {
|
|
4910
|
-
return
|
|
6004
|
+
return lstatSync8(path);
|
|
4911
6005
|
} catch {
|
|
4912
6006
|
return void 0;
|
|
4913
6007
|
}
|
|
4914
6008
|
}
|
|
4915
6009
|
|
|
4916
6010
|
// src/portfolio/redundancy.ts
|
|
4917
|
-
import { existsSync as
|
|
6011
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync6 } from "node:fs";
|
|
4918
6012
|
import { homedir as homedir2 } from "node:os";
|
|
4919
|
-
import { join as
|
|
6013
|
+
import { join as join20 } from "node:path";
|
|
4920
6014
|
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
4921
6015
|
var MAX_CACHE_ENTRIES = 2e4;
|
|
4922
6016
|
function inspectHostRedundancy(options = {}) {
|
|
@@ -4947,8 +6041,8 @@ function inspectProjectRedundancy(root, options = {}) {
|
|
|
4947
6041
|
}
|
|
4948
6042
|
function inspectLegacyDirectories(root) {
|
|
4949
6043
|
const relativePath = ".agent";
|
|
4950
|
-
const path =
|
|
4951
|
-
if (!
|
|
6044
|
+
const path = join20(root, relativePath);
|
|
6045
|
+
if (!existsSync19(path)) return [];
|
|
4952
6046
|
const stats = collectDirectoryStats(path);
|
|
4953
6047
|
return [
|
|
4954
6048
|
{
|
|
@@ -4963,16 +6057,16 @@ function inspectLegacyDirectories(root) {
|
|
|
4963
6057
|
function getPlaywrightCachePaths(homeDir, configuredPath) {
|
|
4964
6058
|
const candidates = [
|
|
4965
6059
|
configuredPath && configuredPath !== "0" ? configuredPath : void 0,
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
6060
|
+
join20(homeDir, "Library/Caches/ms-playwright"),
|
|
6061
|
+
join20(homeDir, ".cache/ms-playwright"),
|
|
6062
|
+
join20(homeDir, "AppData/Local/ms-playwright")
|
|
4969
6063
|
].filter((path) => Boolean(path));
|
|
4970
6064
|
return [...new Set(candidates)];
|
|
4971
6065
|
}
|
|
4972
6066
|
function inspectPlaywrightCache(path, cache) {
|
|
4973
6067
|
const cached = cache?.get(path);
|
|
4974
6068
|
if (cached) return cached;
|
|
4975
|
-
if (!
|
|
6069
|
+
if (!existsSync19(path)) {
|
|
4976
6070
|
const missing = {
|
|
4977
6071
|
path,
|
|
4978
6072
|
exists: false,
|
|
@@ -4987,7 +6081,7 @@ function inspectPlaywrightCache(path, cache) {
|
|
|
4987
6081
|
const stats = collectDirectoryStats(path);
|
|
4988
6082
|
let revisionCount = 0;
|
|
4989
6083
|
try {
|
|
4990
|
-
revisionCount =
|
|
6084
|
+
revisionCount = readdirSync10(path, { withFileTypes: true }).filter(
|
|
4991
6085
|
(entry) => entry.isDirectory()
|
|
4992
6086
|
).length;
|
|
4993
6087
|
} catch {
|
|
@@ -5014,7 +6108,7 @@ function collectDirectoryStats(root) {
|
|
|
5014
6108
|
if (!current) continue;
|
|
5015
6109
|
let entries;
|
|
5016
6110
|
try {
|
|
5017
|
-
entries =
|
|
6111
|
+
entries = readdirSync10(current, { withFileTypes: true });
|
|
5018
6112
|
} catch {
|
|
5019
6113
|
continue;
|
|
5020
6114
|
}
|
|
@@ -5023,13 +6117,13 @@ function collectDirectoryStats(root) {
|
|
|
5023
6117
|
truncated = true;
|
|
5024
6118
|
break;
|
|
5025
6119
|
}
|
|
5026
|
-
const path =
|
|
6120
|
+
const path = join20(current, entry.name);
|
|
5027
6121
|
if (entry.isDirectory()) {
|
|
5028
6122
|
pending.push(path);
|
|
5029
6123
|
} else if (entry.isFile()) {
|
|
5030
6124
|
fileCount += 1;
|
|
5031
6125
|
try {
|
|
5032
|
-
bytes +=
|
|
6126
|
+
bytes += statSync6(path).size;
|
|
5033
6127
|
} catch {
|
|
5034
6128
|
}
|
|
5035
6129
|
}
|
|
@@ -5040,11 +6134,11 @@ function collectDirectoryStats(root) {
|
|
|
5040
6134
|
}
|
|
5041
6135
|
|
|
5042
6136
|
// src/portfolio/verification.ts
|
|
5043
|
-
import { existsSync as
|
|
5044
|
-
import { join as
|
|
6137
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "node:fs";
|
|
6138
|
+
import { join as join21 } from "node:path";
|
|
5045
6139
|
var REQUIRED_PROJECT_SCRIPTS = ["typecheck", "lint", "format:check", "verify"];
|
|
5046
6140
|
function inspectProjectVerification(root) {
|
|
5047
|
-
const packageJson = readPackageJson(
|
|
6141
|
+
const packageJson = readPackageJson(join21(root, "package.json"));
|
|
5048
6142
|
const scripts = Object.fromEntries(
|
|
5049
6143
|
REQUIRED_PROJECT_SCRIPTS.map((name) => [
|
|
5050
6144
|
name,
|
|
@@ -5062,18 +6156,18 @@ function inspectProjectVerification(root) {
|
|
|
5062
6156
|
};
|
|
5063
6157
|
}
|
|
5064
6158
|
function readPackageJson(path) {
|
|
5065
|
-
if (!
|
|
6159
|
+
if (!existsSync20(path)) return void 0;
|
|
5066
6160
|
try {
|
|
5067
|
-
return JSON.parse(
|
|
6161
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
5068
6162
|
} catch {
|
|
5069
6163
|
return void 0;
|
|
5070
6164
|
}
|
|
5071
6165
|
}
|
|
5072
6166
|
|
|
5073
6167
|
// src/repository-files.ts
|
|
5074
|
-
import { spawnSync as
|
|
5075
|
-
import { existsSync as
|
|
5076
|
-
import { isAbsolute as
|
|
6168
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6169
|
+
import { existsSync as existsSync21, readdirSync as readdirSync11 } from "node:fs";
|
|
6170
|
+
import { isAbsolute as isAbsolute6, join as join22, posix as posix3, relative as relative10 } from "node:path";
|
|
5077
6171
|
var gitMaxBufferBytes = 64 * 1024 * 1024;
|
|
5078
6172
|
var RepositoryFileDiscoveryError = class extends Error {
|
|
5079
6173
|
constructor(message) {
|
|
@@ -5084,7 +6178,7 @@ var RepositoryFileDiscoveryError = class extends Error {
|
|
|
5084
6178
|
function discoverRepositoryFiles(root, options = {}) {
|
|
5085
6179
|
const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
5086
6180
|
if (!probe.ok) {
|
|
5087
|
-
if (!probe.notRepository ||
|
|
6181
|
+
if (!probe.notRepository || existsSync21(join22(root, ".git"))) {
|
|
5088
6182
|
throw new RepositoryFileDiscoveryError(probe.message);
|
|
5089
6183
|
}
|
|
5090
6184
|
return {
|
|
@@ -5115,7 +6209,7 @@ function discoverRepositoryFiles(root, options = {}) {
|
|
|
5115
6209
|
`Git returned a path outside the repository boundary: ${path}`
|
|
5116
6210
|
);
|
|
5117
6211
|
}
|
|
5118
|
-
if (
|
|
6212
|
+
if (existsSync21(join22(root, normalized))) files.add(normalized);
|
|
5119
6213
|
}
|
|
5120
6214
|
return { source: "git", files: [...files].sort() };
|
|
5121
6215
|
}
|
|
@@ -5127,21 +6221,21 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5127
6221
|
const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
|
|
5128
6222
|
const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
|
|
5129
6223
|
const visit = (directory, depth) => {
|
|
5130
|
-
if (depth > maxDepth || !
|
|
6224
|
+
if (depth > maxDepth || !existsSync21(directory)) return;
|
|
5131
6225
|
let entries;
|
|
5132
6226
|
try {
|
|
5133
|
-
entries =
|
|
6227
|
+
entries = readdirSync11(directory, { withFileTypes: true });
|
|
5134
6228
|
} catch {
|
|
5135
6229
|
return;
|
|
5136
6230
|
}
|
|
5137
6231
|
for (const entry of entries) {
|
|
5138
|
-
const absolutePath =
|
|
6232
|
+
const absolutePath = join22(directory, entry.name);
|
|
5139
6233
|
if (entry.isDirectory()) {
|
|
5140
6234
|
if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
|
|
5141
6235
|
continue;
|
|
5142
6236
|
}
|
|
5143
6237
|
if (!entry.isFile()) continue;
|
|
5144
|
-
const relativePath = normalizeRepositoryRelativePath(
|
|
6238
|
+
const relativePath = normalizeRepositoryRelativePath(relative10(root, absolutePath));
|
|
5145
6239
|
if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
|
|
5146
6240
|
files.add(relativePath);
|
|
5147
6241
|
}
|
|
@@ -5151,10 +6245,10 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5151
6245
|
return [...files].sort();
|
|
5152
6246
|
}
|
|
5153
6247
|
function isSafeRepositoryRelativePath(path) {
|
|
5154
|
-
return path !== "" && path !== "." && !
|
|
6248
|
+
return path !== "" && path !== "." && !isAbsolute6(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
|
|
5155
6249
|
}
|
|
5156
6250
|
function runGit(root, args) {
|
|
5157
|
-
const result =
|
|
6251
|
+
const result = spawnSync2("git", ["-C", root, ...args], {
|
|
5158
6252
|
encoding: "utf8",
|
|
5159
6253
|
maxBuffer: gitMaxBufferBytes,
|
|
5160
6254
|
env: { ...process.env, LANG: "C", LC_ALL: "C" }
|
|
@@ -5191,7 +6285,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5191
6285
|
includedFileCount: files.length,
|
|
5192
6286
|
excludedFileCount: candidateFiles.length - files.length
|
|
5193
6287
|
},
|
|
5194
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) =>
|
|
6288
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync22(join23(targetDir, file))),
|
|
5195
6289
|
aiConfigFiles: [],
|
|
5196
6290
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
5197
6291
|
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
|
|
@@ -5202,19 +6296,19 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5202
6296
|
}),
|
|
5203
6297
|
packageJson,
|
|
5204
6298
|
docs: {
|
|
5205
|
-
hasDocsDirectory:
|
|
6299
|
+
hasDocsDirectory: existsSync22(join23(targetDir, "docs")),
|
|
5206
6300
|
markdownFileCount: markdownFiles.length,
|
|
5207
6301
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
5208
6302
|
},
|
|
5209
6303
|
git: readGitState(targetDir),
|
|
5210
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
6304
|
+
largeFiles: files.map((file) => ({ path: file, bytes: statSync7(join23(targetDir, file)).size })).filter((file) => file.bytes >= largeFileBytes).sort((a, b) => b.bytes - a.bytes || a.path.localeCompare(b.path)).slice(0, 25)
|
|
5211
6305
|
};
|
|
5212
6306
|
}
|
|
5213
6307
|
function readPackageJson2(targetDir) {
|
|
5214
|
-
const packageJsonPath =
|
|
5215
|
-
if (!
|
|
6308
|
+
const packageJsonPath = join23(targetDir, "package.json");
|
|
6309
|
+
if (!existsSync22(packageJsonPath)) return void 0;
|
|
5216
6310
|
try {
|
|
5217
|
-
const packageJson = JSON.parse(
|
|
6311
|
+
const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf8"));
|
|
5218
6312
|
return {
|
|
5219
6313
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
5220
6314
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -5237,7 +6331,7 @@ function readGitState(targetDir) {
|
|
|
5237
6331
|
};
|
|
5238
6332
|
}
|
|
5239
6333
|
function runGit2(targetDir, args) {
|
|
5240
|
-
const result =
|
|
6334
|
+
const result = spawnSync3("git", ["-C", targetDir, ...args], {
|
|
5241
6335
|
encoding: "utf8",
|
|
5242
6336
|
maxBuffer: 64 * 1024 * 1024
|
|
5243
6337
|
});
|
|
@@ -5305,8 +6399,8 @@ function runLensReport(args) {
|
|
|
5305
6399
|
}
|
|
5306
6400
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
5307
6401
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
5308
|
-
|
|
5309
|
-
|
|
6402
|
+
mkdirSync10(dirname14(options.value.outPath), { recursive: true });
|
|
6403
|
+
writeFileSync9(options.value.outPath, markdown);
|
|
5310
6404
|
console.log(`report: ${options.value.outPath}`);
|
|
5311
6405
|
return 0;
|
|
5312
6406
|
}
|
|
@@ -5411,19 +6505,18 @@ function printUsage4() {
|
|
|
5411
6505
|
}
|
|
5412
6506
|
|
|
5413
6507
|
// src/commands/portfolio.ts
|
|
5414
|
-
import { existsSync as
|
|
5415
|
-
import { join as
|
|
6508
|
+
import { existsSync as existsSync34, readFileSync as readFileSync19 } from "node:fs";
|
|
6509
|
+
import { join as join35 } from "node:path";
|
|
5416
6510
|
|
|
5417
6511
|
// src/portfolio/doctor.ts
|
|
5418
|
-
import { spawnSync as
|
|
5419
|
-
import { existsSync as
|
|
6512
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
6513
|
+
import { existsSync as existsSync25, readFileSync as readFileSync17 } from "node:fs";
|
|
5420
6514
|
import { createRequire as createRequire2 } from "node:module";
|
|
5421
6515
|
import { homedir as homedir4 } from "node:os";
|
|
5422
|
-
import { dirname as
|
|
6516
|
+
import { dirname as dirname16, join as join26 } from "node:path";
|
|
5423
6517
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5424
6518
|
|
|
5425
6519
|
// src/host-tooling/inventory.ts
|
|
5426
|
-
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5427
6520
|
function inspectHostTooling(requirements, runner = defaultRunner2) {
|
|
5428
6521
|
const hosts = [];
|
|
5429
6522
|
const issues = [];
|
|
@@ -5497,7 +6590,7 @@ function parseHostPlugins(host, value) {
|
|
|
5497
6590
|
});
|
|
5498
6591
|
}
|
|
5499
6592
|
function defaultRunner2({ command: command2 }) {
|
|
5500
|
-
const result =
|
|
6593
|
+
const result = spawnPlatformSync(command2[0] ?? "", command2.slice(1), {
|
|
5501
6594
|
encoding: "utf8",
|
|
5502
6595
|
timeout: 1e4
|
|
5503
6596
|
});
|
|
@@ -5512,8 +6605,8 @@ function isRecord2(value) {
|
|
|
5512
6605
|
}
|
|
5513
6606
|
|
|
5514
6607
|
// src/portfolio/asset-state.ts
|
|
5515
|
-
import { existsSync as
|
|
5516
|
-
import { join as
|
|
6608
|
+
import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
|
|
6609
|
+
import { join as join24 } from "node:path";
|
|
5517
6610
|
function comparePortfolioAssetState(options) {
|
|
5518
6611
|
const expectedManifest = readPlanDocument(
|
|
5519
6612
|
options.expectedPlan,
|
|
@@ -5524,10 +6617,10 @@ function comparePortfolioAssetState(options) {
|
|
|
5524
6617
|
".pro-gov/assets.lock.json"
|
|
5525
6618
|
);
|
|
5526
6619
|
const currentManifest = readJsonFile(
|
|
5527
|
-
|
|
6620
|
+
join24(options.targetDir, ".pro-gov/assets.json")
|
|
5528
6621
|
);
|
|
5529
6622
|
const currentLock = readJsonFile(
|
|
5530
|
-
|
|
6623
|
+
join24(options.targetDir, ".pro-gov/assets.lock.json")
|
|
5531
6624
|
);
|
|
5532
6625
|
const issues = [];
|
|
5533
6626
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
@@ -5555,7 +6648,7 @@ function comparePortfolioAssetState(options) {
|
|
|
5555
6648
|
(action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
|
|
5556
6649
|
))
|
|
5557
6650
|
continue;
|
|
5558
|
-
const targetAbsolutePath =
|
|
6651
|
+
const targetAbsolutePath = join24(options.targetDir, entry.targetPath);
|
|
5559
6652
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
5560
6653
|
issues.push({
|
|
5561
6654
|
type: "orphaned-managed-symlink",
|
|
@@ -5577,9 +6670,9 @@ function readPlanDocument(plan, targetPath) {
|
|
|
5577
6670
|
}
|
|
5578
6671
|
}
|
|
5579
6672
|
function readJsonFile(path) {
|
|
5580
|
-
if (!
|
|
6673
|
+
if (!existsSync23(path)) return void 0;
|
|
5581
6674
|
try {
|
|
5582
|
-
return JSON.parse(
|
|
6675
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
5583
6676
|
} catch {
|
|
5584
6677
|
return void 0;
|
|
5585
6678
|
}
|
|
@@ -5605,16 +6698,16 @@ function normalizeLock(lock) {
|
|
|
5605
6698
|
}
|
|
5606
6699
|
function pathIsSymlink(path) {
|
|
5607
6700
|
try {
|
|
5608
|
-
return
|
|
6701
|
+
return lstatSync9(path).isSymbolicLink();
|
|
5609
6702
|
} catch {
|
|
5610
6703
|
return false;
|
|
5611
6704
|
}
|
|
5612
6705
|
}
|
|
5613
6706
|
|
|
5614
6707
|
// src/portfolio/version-policy.ts
|
|
5615
|
-
import { spawnSync as
|
|
5616
|
-
import { existsSync as
|
|
5617
|
-
import { dirname as
|
|
6708
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6709
|
+
import { existsSync as existsSync24, lstatSync as lstatSync10, readFileSync as readFileSync16 } from "node:fs";
|
|
6710
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
5618
6711
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
5619
6712
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
5620
6713
|
const packageManifests = collectPackageManifests(root);
|
|
@@ -5704,7 +6797,7 @@ function inspectRuntime(expectedName, expectedVersion) {
|
|
|
5704
6797
|
function readRuntimeVersion(name) {
|
|
5705
6798
|
if (name === "node") return process.versions.node;
|
|
5706
6799
|
if (name !== "deno") return void 0;
|
|
5707
|
-
const result =
|
|
6800
|
+
const result = spawnSync4("deno", ["--version"], { encoding: "utf8" });
|
|
5708
6801
|
if (result.status !== 0) return void 0;
|
|
5709
6802
|
return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
|
|
5710
6803
|
}
|
|
@@ -5723,10 +6816,10 @@ function findDeclaredVersion(packageJson, name) {
|
|
|
5723
6816
|
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
5724
6817
|
let current = fromDirectory;
|
|
5725
6818
|
while (true) {
|
|
5726
|
-
const packageJson = readJson2(
|
|
6819
|
+
const packageJson = readJson2(join25(current, "node_modules", name, "package.json"));
|
|
5727
6820
|
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
5728
6821
|
if (current === root) return void 0;
|
|
5729
|
-
const parent =
|
|
6822
|
+
const parent = dirname15(current);
|
|
5730
6823
|
if (parent === current) return void 0;
|
|
5731
6824
|
current = parent;
|
|
5732
6825
|
}
|
|
@@ -5761,15 +6854,15 @@ function collectPackageManifests(root) {
|
|
|
5761
6854
|
if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
|
|
5762
6855
|
return;
|
|
5763
6856
|
}
|
|
5764
|
-
const path =
|
|
6857
|
+
const path = join25(root, relativePath);
|
|
5765
6858
|
try {
|
|
5766
|
-
if (!
|
|
6859
|
+
if (!lstatSync10(path).isFile()) return;
|
|
5767
6860
|
} catch {
|
|
5768
6861
|
return;
|
|
5769
6862
|
}
|
|
5770
6863
|
const packageJson = readJson2(path);
|
|
5771
6864
|
if (!packageJson) return;
|
|
5772
|
-
manifests.set(relativePath, { path: relativePath, directory:
|
|
6865
|
+
manifests.set(relativePath, { path: relativePath, directory: dirname15(path), packageJson });
|
|
5773
6866
|
};
|
|
5774
6867
|
const discovery = discoverRepositoryFiles(root, {
|
|
5775
6868
|
gitPathspecs: ["package.json", ":(glob)**/package.json"],
|
|
@@ -5784,9 +6877,9 @@ function unique(values) {
|
|
|
5784
6877
|
return [...new Set(values)];
|
|
5785
6878
|
}
|
|
5786
6879
|
function readJson2(path) {
|
|
5787
|
-
if (!
|
|
6880
|
+
if (!existsSync24(path)) return void 0;
|
|
5788
6881
|
try {
|
|
5789
|
-
return JSON.parse(
|
|
6882
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
5790
6883
|
} catch {
|
|
5791
6884
|
return void 0;
|
|
5792
6885
|
}
|
|
@@ -5819,12 +6912,12 @@ function inspectTarget(options) {
|
|
|
5819
6912
|
const { target } = options;
|
|
5820
6913
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
5821
6914
|
const issues = [];
|
|
5822
|
-
const packageJson = readJson3(
|
|
6915
|
+
const packageJson = readJson3(join26(target.path, "package.json"));
|
|
5823
6916
|
const packages = {};
|
|
5824
6917
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
5825
6918
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
5826
6919
|
const installedPackage = readJson3(
|
|
5827
|
-
|
|
6920
|
+
join26(target.path, "node_modules", packageName, "package.json")
|
|
5828
6921
|
);
|
|
5829
6922
|
const installed = installedPackage?.version;
|
|
5830
6923
|
const expected = options.expectedPackageVersions[packageName];
|
|
@@ -5884,7 +6977,7 @@ function inspectTarget(options) {
|
|
|
5884
6977
|
type: "asset-lock-drift",
|
|
5885
6978
|
message: error instanceof Error ? error.message : String(error)
|
|
5886
6979
|
});
|
|
5887
|
-
if (!
|
|
6980
|
+
if (!existsSync25(join26(target.path, ".pro-gov/assets.json"))) {
|
|
5888
6981
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
5889
6982
|
}
|
|
5890
6983
|
}
|
|
@@ -5902,15 +6995,15 @@ function inspectTarget(options) {
|
|
|
5902
6995
|
};
|
|
5903
6996
|
}
|
|
5904
6997
|
function readTargetAssetHost(targetDir) {
|
|
5905
|
-
const lockfile = readJson3(
|
|
6998
|
+
const lockfile = readJson3(join26(targetDir, ".pro-gov/assets.lock.json"));
|
|
5906
6999
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
5907
7000
|
}
|
|
5908
7001
|
function isAssetRegistryHost(value) {
|
|
5909
7002
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5910
7003
|
}
|
|
5911
7004
|
function runTargetChecks(target) {
|
|
5912
|
-
const proGovCli =
|
|
5913
|
-
const docGovCli =
|
|
7005
|
+
const proGovCli = join26(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
7006
|
+
const docGovCli = join26(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
5914
7007
|
const commands = [
|
|
5915
7008
|
{
|
|
5916
7009
|
name: "pro-gov doctor",
|
|
@@ -5921,8 +7014,8 @@ function runTargetChecks(target) {
|
|
|
5921
7014
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
5922
7015
|
];
|
|
5923
7016
|
return commands.map((command2) => {
|
|
5924
|
-
if (!
|
|
5925
|
-
const result =
|
|
7017
|
+
if (!existsSync25(command2.cli)) return { name: command2.name, status: null };
|
|
7018
|
+
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
5926
7019
|
cwd: target.path,
|
|
5927
7020
|
encoding: "utf8",
|
|
5928
7021
|
timeout: 3e4
|
|
@@ -5931,13 +7024,13 @@ function runTargetChecks(target) {
|
|
|
5931
7024
|
});
|
|
5932
7025
|
}
|
|
5933
7026
|
function inspectGit(path) {
|
|
5934
|
-
const inside =
|
|
7027
|
+
const inside = spawnSync5("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
5935
7028
|
cwd: path,
|
|
5936
7029
|
encoding: "utf8"
|
|
5937
7030
|
});
|
|
5938
7031
|
if (inside.status !== 0) return { isRepository: false, dirty: false };
|
|
5939
|
-
const status =
|
|
5940
|
-
const branch =
|
|
7032
|
+
const status = spawnSync5("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
|
|
7033
|
+
const branch = spawnSync5("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
|
|
5941
7034
|
return {
|
|
5942
7035
|
isRepository: true,
|
|
5943
7036
|
dirty: status.stdout.trim().length > 0,
|
|
@@ -5960,18 +7053,18 @@ function getExpectedPackageVersions() {
|
|
|
5960
7053
|
};
|
|
5961
7054
|
}
|
|
5962
7055
|
function findOwnPackageJson() {
|
|
5963
|
-
let current =
|
|
7056
|
+
let current = dirname16(fileURLToPath4(import.meta.url));
|
|
5964
7057
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
5965
|
-
const candidate =
|
|
5966
|
-
if (
|
|
5967
|
-
current =
|
|
7058
|
+
const candidate = join26(current, "package.json");
|
|
7059
|
+
if (existsSync25(candidate)) return candidate;
|
|
7060
|
+
current = dirname16(current);
|
|
5968
7061
|
}
|
|
5969
7062
|
return "";
|
|
5970
7063
|
}
|
|
5971
7064
|
function readJson3(path) {
|
|
5972
|
-
if (!path || !
|
|
7065
|
+
if (!path || !existsSync25(path)) return void 0;
|
|
5973
7066
|
try {
|
|
5974
|
-
return JSON.parse(
|
|
7067
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
5975
7068
|
} catch {
|
|
5976
7069
|
return void 0;
|
|
5977
7070
|
}
|
|
@@ -5988,15 +7081,15 @@ function deduplicateIssues(issues) {
|
|
|
5988
7081
|
|
|
5989
7082
|
// src/portfolio/ai-health/index.ts
|
|
5990
7083
|
import { homedir as homedir5 } from "node:os";
|
|
5991
|
-
import { dirname as
|
|
7084
|
+
import { dirname as dirname18, join as join34, resolve as resolve10 } from "node:path";
|
|
5992
7085
|
|
|
5993
7086
|
// src/portfolio/ai-health/entries.ts
|
|
5994
|
-
import { existsSync as
|
|
5995
|
-
import { join as
|
|
7087
|
+
import { existsSync as existsSync27, lstatSync as lstatSync12, realpathSync as realpathSync7 } from "node:fs";
|
|
7088
|
+
import { join as join27 } from "node:path";
|
|
5996
7089
|
|
|
5997
7090
|
// src/portfolio/ai-health/shared.ts
|
|
5998
7091
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5999
|
-
import { existsSync as
|
|
7092
|
+
import { existsSync as existsSync26, lstatSync as lstatSync11, readFileSync as readFileSync18, readdirSync as readdirSync12, realpathSync as realpathSync6, statSync as statSync8 } from "node:fs";
|
|
6000
7093
|
function safeRealpath(path) {
|
|
6001
7094
|
try {
|
|
6002
7095
|
return realpathSync6(path);
|
|
@@ -6006,12 +7099,13 @@ function safeRealpath(path) {
|
|
|
6006
7099
|
}
|
|
6007
7100
|
function commandVersion(command2) {
|
|
6008
7101
|
try {
|
|
6009
|
-
const
|
|
7102
|
+
const result = spawnPlatformSync(command2, ["version"], {
|
|
6010
7103
|
encoding: "utf8",
|
|
6011
7104
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6012
7105
|
timeout: 3e3
|
|
6013
7106
|
});
|
|
6014
|
-
|
|
7107
|
+
if (result.error || result.status !== 0) return void 0;
|
|
7108
|
+
return result.stdout.match(/\b\d+\.\d+\.\d+\b/)?.[0];
|
|
6015
7109
|
} catch {
|
|
6016
7110
|
return void 0;
|
|
6017
7111
|
}
|
|
@@ -6026,7 +7120,7 @@ function jsonObjectKeys(path, key) {
|
|
|
6026
7120
|
return Object.keys(value[key]).sort();
|
|
6027
7121
|
}
|
|
6028
7122
|
function tomlMcpNames(path) {
|
|
6029
|
-
if (!
|
|
7123
|
+
if (!existsSync26(path)) return [];
|
|
6030
7124
|
const names = /* @__PURE__ */ new Set();
|
|
6031
7125
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
6032
7126
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
@@ -6037,35 +7131,35 @@ function tomlMcpNames(path) {
|
|
|
6037
7131
|
}
|
|
6038
7132
|
function readJson4(path) {
|
|
6039
7133
|
try {
|
|
6040
|
-
return JSON.parse(
|
|
7134
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
6041
7135
|
} catch {
|
|
6042
7136
|
return void 0;
|
|
6043
7137
|
}
|
|
6044
7138
|
}
|
|
6045
7139
|
function safeRead(path) {
|
|
6046
7140
|
try {
|
|
6047
|
-
return
|
|
7141
|
+
return readFileSync18(path, "utf8");
|
|
6048
7142
|
} catch {
|
|
6049
7143
|
return "";
|
|
6050
7144
|
}
|
|
6051
7145
|
}
|
|
6052
7146
|
function safeReadDir(path) {
|
|
6053
7147
|
try {
|
|
6054
|
-
return
|
|
7148
|
+
return readdirSync12(path).sort();
|
|
6055
7149
|
} catch {
|
|
6056
7150
|
return [];
|
|
6057
7151
|
}
|
|
6058
7152
|
}
|
|
6059
7153
|
function safeIsDirectory(path) {
|
|
6060
7154
|
try {
|
|
6061
|
-
return
|
|
7155
|
+
return statSync8(path).isDirectory();
|
|
6062
7156
|
} catch {
|
|
6063
7157
|
return false;
|
|
6064
7158
|
}
|
|
6065
7159
|
}
|
|
6066
7160
|
function pathLexists(path) {
|
|
6067
7161
|
try {
|
|
6068
|
-
|
|
7162
|
+
lstatSync11(path);
|
|
6069
7163
|
return true;
|
|
6070
7164
|
} catch {
|
|
6071
7165
|
return false;
|
|
@@ -6103,12 +7197,12 @@ function hasWorkflowReminderHooks(hooks) {
|
|
|
6103
7197
|
);
|
|
6104
7198
|
}
|
|
6105
7199
|
function inspectEntries(root) {
|
|
6106
|
-
const agentsPath =
|
|
6107
|
-
const agents = !
|
|
6108
|
-
const claudePath =
|
|
7200
|
+
const agentsPath = join27(root, "AGENTS.md");
|
|
7201
|
+
const agents = !existsSync27(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
7202
|
+
const claudePath = join27(root, "CLAUDE.md");
|
|
6109
7203
|
let claude = "missing";
|
|
6110
7204
|
if (pathLexists(claudePath)) {
|
|
6111
|
-
const info =
|
|
7205
|
+
const info = lstatSync12(claudePath);
|
|
6112
7206
|
if (info.isSymbolicLink()) {
|
|
6113
7207
|
try {
|
|
6114
7208
|
claude = realpathSync7(claudePath) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6125,19 +7219,19 @@ function inspectEntries(root) {
|
|
|
6125
7219
|
var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
|
|
6126
7220
|
function inspectAgentLinks(root) {
|
|
6127
7221
|
const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
|
|
6128
|
-
const directoryPath =
|
|
7222
|
+
const directoryPath = join27(root, directory);
|
|
6129
7223
|
if (!pathLexists(directoryPath)) return [];
|
|
6130
7224
|
try {
|
|
6131
|
-
if (!
|
|
7225
|
+
if (!lstatSync12(directoryPath).isDirectory()) return [];
|
|
6132
7226
|
} catch {
|
|
6133
7227
|
return [];
|
|
6134
7228
|
}
|
|
6135
7229
|
return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
|
|
6136
7230
|
const relativePath = `${directory}/${name}`;
|
|
6137
|
-
const path =
|
|
7231
|
+
const path = join27(root, relativePath);
|
|
6138
7232
|
let stat;
|
|
6139
7233
|
try {
|
|
6140
|
-
stat =
|
|
7234
|
+
stat = lstatSync12(path);
|
|
6141
7235
|
} catch {
|
|
6142
7236
|
return [];
|
|
6143
7237
|
}
|
|
@@ -6158,9 +7252,9 @@ function inspectAgentLinks(root) {
|
|
|
6158
7252
|
};
|
|
6159
7253
|
}
|
|
6160
7254
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
6161
|
-
const path =
|
|
7255
|
+
const path = join27(root, filename);
|
|
6162
7256
|
if (!pathLexists(path)) return "missing";
|
|
6163
|
-
const info =
|
|
7257
|
+
const info = lstatSync12(path);
|
|
6164
7258
|
if (info.isSymbolicLink()) {
|
|
6165
7259
|
try {
|
|
6166
7260
|
return realpathSync7(path) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6198,7 +7292,7 @@ function inspectHooks(root) {
|
|
|
6198
7292
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
6199
7293
|
];
|
|
6200
7294
|
return configs.map((config) => {
|
|
6201
|
-
const value = readJson4(
|
|
7295
|
+
const value = readJson4(join27(root, config.path));
|
|
6202
7296
|
const counts = /* @__PURE__ */ new Map();
|
|
6203
7297
|
collectHookEvents(value, counts);
|
|
6204
7298
|
return {
|
|
@@ -6219,18 +7313,18 @@ function collectHookEvents(value, counts) {
|
|
|
6219
7313
|
}
|
|
6220
7314
|
}
|
|
6221
7315
|
function inspectDocs(root, expected) {
|
|
6222
|
-
const packageJson = readJson4(
|
|
7316
|
+
const packageJson = readJson4(join27(root, "package.json"));
|
|
6223
7317
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
6224
7318
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
6225
7319
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
6226
|
-
const routerMatch = safeRead(
|
|
7320
|
+
const routerMatch = safeRead(join27(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
6227
7321
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
6228
7322
|
return {
|
|
6229
7323
|
routerVersion: routerMatch?.[1],
|
|
6230
7324
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
6231
7325
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
6232
|
-
manifest:
|
|
6233
|
-
currentWork:
|
|
7326
|
+
manifest: existsSync27(join27(root, "docs/governance/MANIFEST.yml")),
|
|
7327
|
+
currentWork: existsSync27(join27(root, "docs/reference/execution/current-work.md")),
|
|
6234
7328
|
packages: {
|
|
6235
7329
|
expected,
|
|
6236
7330
|
docGov,
|
|
@@ -6292,19 +7386,17 @@ function inspectGit2(root) {
|
|
|
6292
7386
|
}
|
|
6293
7387
|
|
|
6294
7388
|
// src/portfolio/ai-health/hosts.ts
|
|
6295
|
-
import {
|
|
6296
|
-
import {
|
|
6297
|
-
import { join as join28, resolve as resolve8, sep as sep3 } from "node:path";
|
|
7389
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
7390
|
+
import { join as join29, resolve as resolve9, sep as sep3 } from "node:path";
|
|
6298
7391
|
|
|
6299
7392
|
// src/portfolio/ai-health/devspace.ts
|
|
6300
|
-
import {
|
|
6301
|
-
import {
|
|
6302
|
-
import { join as join27, relative as relative10, resolve as resolve7, sep as sep2 } from "node:path";
|
|
7393
|
+
import { existsSync as existsSync28, statSync as statSync9 } from "node:fs";
|
|
7394
|
+
import { join as join28, relative as relative11, resolve as resolve8, sep as sep2 } from "node:path";
|
|
6303
7395
|
function inspectDevSpaceHealth(options) {
|
|
6304
7396
|
const run = options.run ?? runDevSpaceCommand;
|
|
6305
|
-
const configDirectory =
|
|
6306
|
-
const configPath =
|
|
6307
|
-
const authPath =
|
|
7397
|
+
const configDirectory = join28(options.homeDir, ".devspace");
|
|
7398
|
+
const configPath = join28(configDirectory, "config.json");
|
|
7399
|
+
const authPath = join28(configDirectory, "auth.json");
|
|
6308
7400
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
6309
7401
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
6310
7402
|
const latestResult = run(
|
|
@@ -6313,9 +7405,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
6313
7405
|
5e3
|
|
6314
7406
|
);
|
|
6315
7407
|
const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
6316
|
-
const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
|
|
6317
|
-
const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
|
|
6318
|
-
const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
7408
|
+
const processResult = process.platform === "win32" ? run("tasklist", ["/FI", "IMAGENAME eq devspace.exe", "/FO", "CSV", "/NH"], 3e3) : run("pgrep", ["-f", "devspace serve"], 3e3);
|
|
7409
|
+
const pid = processResult.ok ? process.platform === "win32" ? processResult.stdout.match(/"devspace\.exe","(\d+)"/i)?.[1] : processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
|
|
7410
|
+
const processEnvironment = process.platform !== "win32" && pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
|
|
6319
7411
|
const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
|
|
6320
7412
|
/(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/
|
|
6321
7413
|
)?.[1] : void 0;
|
|
@@ -6327,9 +7419,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
6327
7419
|
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
6328
7420
|
) ? "complete" : "partial";
|
|
6329
7421
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
6330
|
-
const directoryMode =
|
|
6331
|
-
const fileMode =
|
|
6332
|
-
const authMode =
|
|
7422
|
+
const directoryMode = process.platform !== "win32" && existsSync28(configDirectory) ? modeString(statSync9(configDirectory).mode) : void 0;
|
|
7423
|
+
const fileMode = process.platform !== "win32" && existsSync28(configPath) ? modeString(statSync9(configPath).mode) : void 0;
|
|
7424
|
+
const authMode = process.platform !== "win32" && existsSync28(authPath) ? modeString(statSync9(authPath).mode) : void 0;
|
|
6333
7425
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
6334
7426
|
const recommendations = [];
|
|
6335
7427
|
let status = "healthy";
|
|
@@ -6343,7 +7435,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6343
7435
|
};
|
|
6344
7436
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
6345
7437
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
6346
|
-
if (!
|
|
7438
|
+
if (!existsSync28(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
6347
7439
|
if (directoryMode && directoryMode !== "700")
|
|
6348
7440
|
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
6349
7441
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -6353,7 +7445,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6353
7445
|
if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
|
|
6354
7446
|
if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
|
|
6355
7447
|
if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
|
|
6356
|
-
if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
|
|
7448
|
+
if (options.expectedToolMode && pid && processToolMode !== void 0 && processToolMode !== options.expectedToolMode) {
|
|
6357
7449
|
attention(
|
|
6358
7450
|
`\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
|
|
6359
7451
|
);
|
|
@@ -6376,7 +7468,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6376
7468
|
exists: configExists,
|
|
6377
7469
|
...directoryMode ? { directoryMode } : {},
|
|
6378
7470
|
...fileMode ? { fileMode } : {},
|
|
6379
|
-
authExists:
|
|
7471
|
+
authExists: existsSync28(authPath),
|
|
6380
7472
|
...authMode ? { authMode } : {},
|
|
6381
7473
|
bind,
|
|
6382
7474
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -6388,24 +7480,18 @@ function inspectDevSpaceHealth(options) {
|
|
|
6388
7480
|
};
|
|
6389
7481
|
}
|
|
6390
7482
|
function runDevSpaceCommand(command2, args, timeout) {
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
timeout
|
|
6398
|
-
})
|
|
6399
|
-
};
|
|
6400
|
-
} catch {
|
|
6401
|
-
return { ok: false, stdout: "" };
|
|
6402
|
-
}
|
|
7483
|
+
const result = spawnPlatformSync(command2, args, {
|
|
7484
|
+
encoding: "utf8",
|
|
7485
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7486
|
+
timeout
|
|
7487
|
+
});
|
|
7488
|
+
return result.error || result.status !== 0 ? { ok: false, stdout: "" } : { ok: true, stdout: result.stdout };
|
|
6403
7489
|
}
|
|
6404
7490
|
function isLoopbackHost(host) {
|
|
6405
7491
|
return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
|
|
6406
7492
|
}
|
|
6407
7493
|
function isPathInside(path, root) {
|
|
6408
|
-
const fromRoot =
|
|
7494
|
+
const fromRoot = relative11(resolve8(root), resolve8(path));
|
|
6409
7495
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`);
|
|
6410
7496
|
}
|
|
6411
7497
|
|
|
@@ -6423,9 +7509,9 @@ var MCP_DISCOVERY_PATHS = {
|
|
|
6423
7509
|
}
|
|
6424
7510
|
};
|
|
6425
7511
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
6426
|
-
const codexConfig =
|
|
6427
|
-
const claudeConfig =
|
|
6428
|
-
const grokConfig =
|
|
7512
|
+
const codexConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.codex);
|
|
7513
|
+
const claudeConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
|
|
7514
|
+
const grokConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.grok);
|
|
6429
7515
|
const hostEnvironment = {
|
|
6430
7516
|
mcp: {
|
|
6431
7517
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -6433,11 +7519,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6433
7519
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
6434
7520
|
},
|
|
6435
7521
|
skills: {
|
|
6436
|
-
codexUser: inspectSkillRoot(
|
|
6437
|
-
claudeCodeUser: inspectSkillRoot(
|
|
6438
|
-
grokUser: inspectSkillRoot(
|
|
6439
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
6440
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
7522
|
+
codexUser: inspectSkillRoot(join29(homeDir, ".agents/skills")),
|
|
7523
|
+
claudeCodeUser: inspectSkillRoot(join29(homeDir, ".claude/skills")),
|
|
7524
|
+
grokUser: inspectSkillRoot(join29(homeDir, ".grok/skills")),
|
|
7525
|
+
grokAgentsCompatibility: inspectSkillRoot(join29(homeDir, ".agents/skills")),
|
|
7526
|
+
grokClaudeCompatibility: inspectSkillRoot(join29(homeDir, ".claude/skills")),
|
|
6441
7527
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
6442
7528
|
},
|
|
6443
7529
|
grok: {
|
|
@@ -6458,20 +7544,20 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6458
7544
|
function inspectSkillRoot(path) {
|
|
6459
7545
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
6460
7546
|
const names = exists ? safeReadDir(path).filter(
|
|
6461
|
-
(name) => !name.startsWith(".") &&
|
|
7547
|
+
(name) => !name.startsWith(".") && existsSync29(join29(path, name, "SKILL.md"))
|
|
6462
7548
|
) : [];
|
|
6463
7549
|
return { path, exists, names };
|
|
6464
7550
|
}
|
|
6465
7551
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
6466
7552
|
if (!homeDir) return [];
|
|
6467
|
-
const value = readJson4(
|
|
7553
|
+
const value = readJson4(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
|
|
6468
7554
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
6469
7555
|
const candidates = new Set(
|
|
6470
|
-
[
|
|
7556
|
+
[resolve9(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
6471
7557
|
);
|
|
6472
7558
|
const names = /* @__PURE__ */ new Set();
|
|
6473
7559
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
6474
|
-
const projectPaths = [
|
|
7560
|
+
const projectPaths = [resolve9(path), safeRealpath(path)].filter(
|
|
6475
7561
|
(candidate) => Boolean(candidate)
|
|
6476
7562
|
);
|
|
6477
7563
|
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers))
|
|
@@ -6490,19 +7576,19 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6490
7576
|
});
|
|
6491
7577
|
if (!grokVersion) return empty("unavailable");
|
|
6492
7578
|
try {
|
|
6493
|
-
const
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
);
|
|
7579
|
+
const result = spawnPlatformSync("grok", ["inspect", "--json"], {
|
|
7580
|
+
cwd: root,
|
|
7581
|
+
encoding: "utf8",
|
|
7582
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join29(homeDir, ".grok") },
|
|
7583
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
7584
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7585
|
+
timeout: 8e3
|
|
7586
|
+
});
|
|
7587
|
+
if (result.error || result.status !== 0) return empty("failed");
|
|
7588
|
+
const value = JSON.parse(result.stdout);
|
|
6503
7589
|
if (!isRecord3(value)) return empty("failed");
|
|
6504
7590
|
const userClaudeNames = new Set(
|
|
6505
|
-
jsonObjectKeys(
|
|
7591
|
+
jsonObjectKeys(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
|
|
6506
7592
|
);
|
|
6507
7593
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
6508
7594
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
@@ -6570,25 +7656,26 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6570
7656
|
if (userClaudeNames.has(name)) return "user";
|
|
6571
7657
|
return "unknown";
|
|
6572
7658
|
}
|
|
6573
|
-
const resolvedSource = safeRealpath(sourcePath) ??
|
|
6574
|
-
const resolvedRoot = safeRealpath(root) ??
|
|
6575
|
-
if (resolvedSource ===
|
|
7659
|
+
const resolvedSource = safeRealpath(sourcePath) ?? resolve9(sourcePath);
|
|
7660
|
+
const resolvedRoot = safeRealpath(root) ?? resolve9(root);
|
|
7661
|
+
if (resolvedSource === join29(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
|
|
6576
7662
|
return "project-shared";
|
|
6577
7663
|
if (resolvedSource.startsWith(resolvedRoot + sep3)) return "project";
|
|
6578
|
-
if (homeDir && resolvedSource ===
|
|
7664
|
+
if (homeDir && resolvedSource === join29(resolve9(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
|
|
6579
7665
|
if (localClaudeNames.has(name)) return "project-local";
|
|
6580
7666
|
if (userClaudeNames.has(name)) return "user";
|
|
6581
7667
|
}
|
|
6582
|
-
if (homeDir && resolvedSource.startsWith(
|
|
7668
|
+
if (homeDir && resolvedSource.startsWith(resolve9(homeDir) + sep3)) return "user";
|
|
6583
7669
|
if (sourceType === "project") return "project";
|
|
6584
7670
|
return "unknown";
|
|
6585
7671
|
}
|
|
6586
7672
|
|
|
6587
7673
|
// src/portfolio/ai-health/secrets.ts
|
|
6588
|
-
import { existsSync as
|
|
6589
|
-
import { join as
|
|
7674
|
+
import { existsSync as existsSync30, lstatSync as lstatSync13, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
7675
|
+
import { join as join30, relative as relative12, sep as sep4 } from "node:path";
|
|
7676
|
+
var supportsPosixModes = process.platform !== "win32";
|
|
6590
7677
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
6591
|
-
const centralPath =
|
|
7678
|
+
const centralPath = join30(secretsRoot, id);
|
|
6592
7679
|
const centralRealPath = safeRealpath(centralPath);
|
|
6593
7680
|
const localOnlyReasons = new Map(
|
|
6594
7681
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -6600,16 +7687,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
6600
7687
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
6601
7688
|
template: isEnvironmentTemplate(path),
|
|
6602
7689
|
fixture: isEnvironmentFixture(path),
|
|
6603
|
-
symlink:
|
|
6604
|
-
centralized: pointsInside(
|
|
7690
|
+
symlink: lstatSync13(join30(root, path)).isSymbolicLink(),
|
|
7691
|
+
centralized: pointsInside(join30(root, path), centralRealPath),
|
|
6605
7692
|
localOnly: localOnlyReason !== void 0,
|
|
6606
7693
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
6607
7694
|
};
|
|
6608
7695
|
});
|
|
6609
7696
|
return {
|
|
6610
|
-
centralDirectory:
|
|
6611
|
-
centralMode:
|
|
6612
|
-
centralFiles:
|
|
7697
|
+
centralDirectory: existsSync30(centralPath) ? "present" : "absent",
|
|
7698
|
+
centralMode: supportsPosixModes && existsSync30(centralPath) ? modeString(statSync10(centralPath).mode) : void 0,
|
|
7699
|
+
centralFiles: existsSync30(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
6613
7700
|
repositoryEnvFiles: envFiles
|
|
6614
7701
|
};
|
|
6615
7702
|
}
|
|
@@ -6632,12 +7719,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
6632
7719
|
if (depth > 5) return [];
|
|
6633
7720
|
const found = [];
|
|
6634
7721
|
try {
|
|
6635
|
-
for (const entry of
|
|
7722
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
6636
7723
|
if (entry.isDirectory()) {
|
|
6637
7724
|
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
6638
|
-
found.push(...collectEnvironmentFiles(root,
|
|
7725
|
+
found.push(...collectEnvironmentFiles(root, join30(current, entry.name), depth + 1));
|
|
6639
7726
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
6640
|
-
found.push(
|
|
7727
|
+
found.push(relative12(root, join30(current, entry.name)).replaceAll("\\", "/"));
|
|
6641
7728
|
}
|
|
6642
7729
|
}
|
|
6643
7730
|
} catch {
|
|
@@ -6649,10 +7736,14 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
6649
7736
|
if (depth > 3) return [];
|
|
6650
7737
|
const found = [];
|
|
6651
7738
|
try {
|
|
6652
|
-
for (const entry of
|
|
6653
|
-
const path =
|
|
7739
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
7740
|
+
const path = join30(current, entry.name);
|
|
6654
7741
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
6655
|
-
else
|
|
7742
|
+
else
|
|
7743
|
+
found.push({
|
|
7744
|
+
path: relative12(root, path).replaceAll("\\", "/"),
|
|
7745
|
+
mode: supportsPosixModes ? modeString(lstatSync13(path).mode) : "unknown"
|
|
7746
|
+
});
|
|
6656
7747
|
}
|
|
6657
7748
|
} catch {
|
|
6658
7749
|
return found;
|
|
@@ -6676,30 +7767,35 @@ function isEnvironmentFixture(path) {
|
|
|
6676
7767
|
return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
|
|
6677
7768
|
}
|
|
6678
7769
|
function pointsInside(path, expectedRoot) {
|
|
6679
|
-
if (!expectedRoot || !
|
|
7770
|
+
if (!expectedRoot || !lstatSync13(path).isSymbolicLink()) return false;
|
|
6680
7771
|
const target = safeRealpath(path);
|
|
6681
7772
|
if (!target) return false;
|
|
6682
|
-
const fromRoot =
|
|
7773
|
+
const fromRoot = relative12(expectedRoot, target);
|
|
6683
7774
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep4}`);
|
|
6684
7775
|
}
|
|
6685
7776
|
function hasUnsafeCentralSecretPermissions(secrets) {
|
|
7777
|
+
if (!supportsPosixModes) return false;
|
|
6686
7778
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
6687
7779
|
}
|
|
6688
7780
|
function inspectSecretsRoot(path) {
|
|
6689
|
-
return
|
|
7781
|
+
return existsSync30(path) ? {
|
|
7782
|
+
path,
|
|
7783
|
+
exists: true,
|
|
7784
|
+
...supportsPosixModes ? { mode: modeString(statSync10(path).mode) } : {}
|
|
7785
|
+
} : { path, exists: false };
|
|
6690
7786
|
}
|
|
6691
7787
|
|
|
6692
7788
|
// src/portfolio/ai-health/skills.ts
|
|
6693
|
-
import { existsSync as
|
|
6694
|
-
import { join as
|
|
7789
|
+
import { existsSync as existsSync31, lstatSync as lstatSync14, realpathSync as realpathSync8 } from "node:fs";
|
|
7790
|
+
import { join as join31 } from "node:path";
|
|
6695
7791
|
function countAutomaticSkillsNeedingReview(skills) {
|
|
6696
7792
|
return skills.automatic.filter(
|
|
6697
7793
|
(item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
|
|
6698
7794
|
).length;
|
|
6699
7795
|
}
|
|
6700
7796
|
function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
6701
|
-
const lock = readJson4(
|
|
6702
|
-
const assetManifest = readJson4(
|
|
7797
|
+
const lock = readJson4(join31(root, ".pro-gov/assets.lock.json"));
|
|
7798
|
+
const assetManifest = readJson4(join31(root, ".pro-gov/assets.json"));
|
|
6703
7799
|
const managed = /* @__PURE__ */ new Set();
|
|
6704
7800
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
6705
7801
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -6711,7 +7807,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6711
7807
|
}
|
|
6712
7808
|
const inspectPlacement = (placement) => {
|
|
6713
7809
|
const directory = placement === "auto" ? "skills" : "manual-skills";
|
|
6714
|
-
const skillRoot =
|
|
7810
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6715
7811
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6716
7812
|
return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
|
|
6717
7813
|
};
|
|
@@ -6766,15 +7862,15 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6766
7862
|
},
|
|
6767
7863
|
hosts: {
|
|
6768
7864
|
codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
|
|
6769
|
-
claudeCodeProject: inspectSkillRoot(
|
|
6770
|
-
grokNativeProject: inspectSkillRoot(
|
|
7865
|
+
claudeCodeProject: inspectSkillRoot(join31(root, ".claude/skills")).names.length,
|
|
7866
|
+
grokNativeProject: inspectSkillRoot(join31(root, ".grok/skills")).names.length,
|
|
6771
7867
|
grokEffective: grokInspection.skills
|
|
6772
7868
|
}
|
|
6773
7869
|
};
|
|
6774
7870
|
}
|
|
6775
7871
|
function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
|
|
6776
|
-
const path =
|
|
6777
|
-
const stat =
|
|
7872
|
+
const path = join31(skillRoot, name);
|
|
7873
|
+
const stat = lstatSync14(path);
|
|
6778
7874
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6779
7875
|
let realPath;
|
|
6780
7876
|
try {
|
|
@@ -6783,7 +7879,7 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6783
7879
|
if (stat.isSymbolicLink()) kind = "dangling-symlink";
|
|
6784
7880
|
}
|
|
6785
7881
|
const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
|
|
6786
|
-
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" &&
|
|
7882
|
+
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync31(join31(path, "SKILL.md")) ? "project-local" : void 0;
|
|
6787
7883
|
return {
|
|
6788
7884
|
name,
|
|
6789
7885
|
kind,
|
|
@@ -6795,11 +7891,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6795
7891
|
};
|
|
6796
7892
|
}
|
|
6797
7893
|
function isPluginPack(path) {
|
|
6798
|
-
const skillsRoot =
|
|
6799
|
-
return
|
|
7894
|
+
const skillsRoot = join31(path, "skills");
|
|
7895
|
+
return existsSync31(join31(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync31(join31(skillsRoot, name, "SKILL.md")));
|
|
6800
7896
|
}
|
|
6801
7897
|
function inspectInvalidSkillEntries(root, directory) {
|
|
6802
|
-
const skillRoot =
|
|
7898
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6803
7899
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6804
7900
|
return safeReadDir(skillRoot).flatMap((name) => {
|
|
6805
7901
|
if (name === ".gitkeep") return [];
|
|
@@ -6807,14 +7903,14 @@ function inspectInvalidSkillEntries(root, directory) {
|
|
|
6807
7903
|
return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
|
|
6808
7904
|
if (name.startsWith("."))
|
|
6809
7905
|
return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
|
|
6810
|
-
const path =
|
|
6811
|
-
return !
|
|
7906
|
+
const path = join31(skillRoot, name);
|
|
7907
|
+
return !lstatSync14(path).isDirectory() && !lstatSync14(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
|
|
6812
7908
|
});
|
|
6813
7909
|
}
|
|
6814
7910
|
function skillDuplicatesUser(item, root, userSkills) {
|
|
6815
7911
|
if (userSkills.names.has(item.name)) return true;
|
|
6816
|
-
const automatic =
|
|
6817
|
-
const manual =
|
|
7912
|
+
const automatic = join31(root, ".agents/skills", item.name);
|
|
7913
|
+
const manual = join31(root, ".agents/manual-skills", item.name);
|
|
6818
7914
|
const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
|
|
6819
7915
|
return realPath ? userSkills.realPaths.has(realPath) : false;
|
|
6820
7916
|
}
|
|
@@ -6834,13 +7930,13 @@ function skillPlacementDrift(item, actualPlacement) {
|
|
|
6834
7930
|
return [];
|
|
6835
7931
|
}
|
|
6836
7932
|
function inspectClaudeSkillRoot(root) {
|
|
6837
|
-
const path =
|
|
7933
|
+
const path = join31(root, ".claude/skills");
|
|
6838
7934
|
if (!pathLexists(path)) return "missing";
|
|
6839
|
-
const stat =
|
|
7935
|
+
const stat = lstatSync14(path);
|
|
6840
7936
|
if (stat.isSymbolicLink()) {
|
|
6841
7937
|
try {
|
|
6842
7938
|
const target = realpathSync8(path);
|
|
6843
|
-
return target === realpathSync8(
|
|
7939
|
+
return target === realpathSync8(join31(root, ".agents/skills")) ? "shared-root" : "other";
|
|
6844
7940
|
} catch {
|
|
6845
7941
|
return "dangling-symlink";
|
|
6846
7942
|
}
|
|
@@ -6853,27 +7949,27 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6853
7949
|
health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
|
|
6854
7950
|
skills: []
|
|
6855
7951
|
};
|
|
6856
|
-
const agentAssetsRoot =
|
|
6857
|
-
const registry = readJson4(
|
|
7952
|
+
const agentAssetsRoot = join31(executionEngineRoot, "agent-assets");
|
|
7953
|
+
const registry = readJson4(join31(agentAssetsRoot, "registry.json"));
|
|
6858
7954
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
6859
7955
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
6860
|
-
const bundleRoot =
|
|
7956
|
+
const bundleRoot = join31(agentAssetsRoot, "bundles");
|
|
6861
7957
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
6862
7958
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
6863
7959
|
for (const file of bundleFiles) {
|
|
6864
|
-
const bundle = readJson4(
|
|
7960
|
+
const bundle = readJson4(join31(bundleRoot, file));
|
|
6865
7961
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
6866
7962
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
6867
7963
|
}
|
|
6868
7964
|
const sourceRoots = [
|
|
6869
|
-
|
|
6870
|
-
|
|
7965
|
+
join31(agentAssetsRoot, "skills/pie-skills"),
|
|
7966
|
+
join31(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
6871
7967
|
];
|
|
6872
7968
|
const source = sourceRoots.reduce(
|
|
6873
|
-
(count, root) => count + safeReadDir(root).filter((name) =>
|
|
7969
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync31(join31(root, name, "SKILL.md"))).length,
|
|
6874
7970
|
0
|
|
6875
7971
|
) + registeredSkills.filter(
|
|
6876
|
-
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(
|
|
7972
|
+
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join31(agentAssetsRoot, asset.sourcePath))
|
|
6877
7973
|
).length;
|
|
6878
7974
|
return {
|
|
6879
7975
|
health: {
|
|
@@ -6890,7 +7986,7 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6890
7986
|
return [
|
|
6891
7987
|
{
|
|
6892
7988
|
id: asset.id,
|
|
6893
|
-
sourceRealPath: safeRealpath(
|
|
7989
|
+
sourceRealPath: safeRealpath(join31(agentAssetsRoot, asset.sourcePath)),
|
|
6894
7990
|
defaultPlacement: asset.defaultPlacement,
|
|
6895
7991
|
defaultScope: asset.defaultScope === "user" ? "user" : "project"
|
|
6896
7992
|
}
|
|
@@ -6905,15 +8001,15 @@ function inspectUserSkillEvidence(root) {
|
|
|
6905
8001
|
for (const name of safeReadDir(root)) {
|
|
6906
8002
|
if (name.startsWith(".")) continue;
|
|
6907
8003
|
names.add(name);
|
|
6908
|
-
const realPath = safeRealpath(
|
|
8004
|
+
const realPath = safeRealpath(join31(root, name));
|
|
6909
8005
|
if (realPath) realPaths.add(realPath);
|
|
6910
8006
|
}
|
|
6911
8007
|
return { names, realPaths };
|
|
6912
8008
|
}
|
|
6913
8009
|
|
|
6914
8010
|
// src/portfolio/ai-health/technology.ts
|
|
6915
|
-
import { existsSync as
|
|
6916
|
-
import { join as
|
|
8011
|
+
import { existsSync as existsSync32, readdirSync as readdirSync14, statSync as statSync11 } from "node:fs";
|
|
8012
|
+
import { join as join32 } from "node:path";
|
|
6917
8013
|
function buildTechnologyMatrix(governance, repositories) {
|
|
6918
8014
|
if (!governance || governance.technologies.length === 0) return [];
|
|
6919
8015
|
const policy = governance.versionPolicy;
|
|
@@ -6940,8 +8036,8 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6940
8036
|
}).filter((item) => item !== void 0);
|
|
6941
8037
|
}).flat();
|
|
6942
8038
|
const fileSignal = (technology.files ?? []).some(
|
|
6943
|
-
(path) => hasUsableTechnologyFile(
|
|
6944
|
-
(manifest) => hasUsableTechnologyFile(
|
|
8039
|
+
(path) => hasUsableTechnologyFile(join32(repository.path, path)) || packageManifests.some(
|
|
8040
|
+
(manifest) => hasUsableTechnologyFile(join32(manifest.directory, path))
|
|
6945
8041
|
)
|
|
6946
8042
|
);
|
|
6947
8043
|
const modelSignal = [
|
|
@@ -7032,14 +8128,14 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
7032
8128
|
}).filter((technology) => technology.projectCount > 0);
|
|
7033
8129
|
}
|
|
7034
8130
|
function hasUsableTechnologyFile(path) {
|
|
7035
|
-
if (!
|
|
8131
|
+
if (!existsSync32(path)) return false;
|
|
7036
8132
|
try {
|
|
7037
|
-
const info =
|
|
8133
|
+
const info = statSync11(path);
|
|
7038
8134
|
if (info.isFile()) return true;
|
|
7039
8135
|
if (!info.isDirectory()) return false;
|
|
7040
|
-
return
|
|
8136
|
+
return readdirSync14(path, { withFileTypes: true }).some((entry) => {
|
|
7041
8137
|
if (entry.name.startsWith(".")) return false;
|
|
7042
|
-
const child =
|
|
8138
|
+
const child = join32(path, entry.name);
|
|
7043
8139
|
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
7044
8140
|
return entry.name.toLowerCase() !== "readme.md";
|
|
7045
8141
|
});
|
|
@@ -7051,7 +8147,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
|
|
|
7051
8147
|
const projectType = endpoint.projectType;
|
|
7052
8148
|
return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
|
|
7053
8149
|
if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
|
|
7054
|
-
const paths = rule.paths.filter((path) =>
|
|
8150
|
+
const paths = rule.paths.filter((path) => existsSync32(join32(root, path)));
|
|
7055
8151
|
return paths.length > 0 ? [{ rule, paths }] : [];
|
|
7056
8152
|
});
|
|
7057
8153
|
}
|
|
@@ -7066,7 +8162,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
7066
8162
|
const detection = (id) => {
|
|
7067
8163
|
const technology = technologyById.get(id);
|
|
7068
8164
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
7069
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
8165
|
+
const fileMatch = technology?.files?.some((path) => existsSync32(join32(root, path))) ?? false;
|
|
7070
8166
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
7071
8167
|
};
|
|
7072
8168
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -7105,8 +8201,8 @@ function collectPackageNames(root) {
|
|
|
7105
8201
|
}
|
|
7106
8202
|
|
|
7107
8203
|
// src/portfolio/ai-health/report.ts
|
|
7108
|
-
import { cpSync as cpSync3, existsSync as
|
|
7109
|
-
import { dirname as
|
|
8204
|
+
import { cpSync as cpSync3, existsSync as existsSync33, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8205
|
+
import { dirname as dirname17, join as join33 } from "node:path";
|
|
7110
8206
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7111
8207
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
7112
8208
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
@@ -7147,36 +8243,36 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
|
7147
8243
|
};
|
|
7148
8244
|
}
|
|
7149
8245
|
function writePortfolioAiHealthReport(report, outDir) {
|
|
7150
|
-
|
|
8246
|
+
mkdirSync11(outDir, { recursive: true });
|
|
7151
8247
|
const dashboardAssets = findDashboardAssets();
|
|
7152
8248
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
7153
|
-
const source =
|
|
7154
|
-
if (!
|
|
7155
|
-
cpSync3(source,
|
|
8249
|
+
const source = join33(dashboardAssets, file);
|
|
8250
|
+
if (!existsSync33(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
8251
|
+
cpSync3(source, join33(outDir, file));
|
|
7156
8252
|
}
|
|
7157
|
-
const jsonPath =
|
|
7158
|
-
const htmlPath =
|
|
7159
|
-
|
|
8253
|
+
const jsonPath = join33(outDir, "portfolio-ai-health.json");
|
|
8254
|
+
const htmlPath = join33(outDir, "index.html");
|
|
8255
|
+
writeFileSync10(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
7160
8256
|
`);
|
|
7161
|
-
|
|
7162
|
-
|
|
8257
|
+
writeFileSync10(
|
|
8258
|
+
join33(outDir, "data.js"),
|
|
7163
8259
|
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
|
|
7164
8260
|
`
|
|
7165
8261
|
);
|
|
7166
8262
|
return { jsonPath, htmlPath };
|
|
7167
8263
|
}
|
|
7168
8264
|
function findDashboardAssets() {
|
|
7169
|
-
const packageRoot2 =
|
|
8265
|
+
const packageRoot2 = dirname17(dirname17(fileURLToPath5(import.meta.url)));
|
|
7170
8266
|
const candidates = [
|
|
7171
8267
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
8268
|
+
join33(packageRoot2, ".dashboard-build"),
|
|
8269
|
+
join33(packageRoot2, "assets/portfolio-dashboard"),
|
|
8270
|
+
join33(process.cwd(), ".dashboard-build"),
|
|
8271
|
+
join33(process.cwd(), "assets/portfolio-dashboard"),
|
|
8272
|
+
join33(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
8273
|
+
join33(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
7178
8274
|
].filter((value) => Boolean(value));
|
|
7179
|
-
const match = candidates.find((path) =>
|
|
8275
|
+
const match = candidates.find((path) => existsSync33(join33(path, "index.html")));
|
|
7180
8276
|
if (!match)
|
|
7181
8277
|
throw new Error(
|
|
7182
8278
|
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -7194,8 +8290,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7194
8290
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
7195
8291
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
7196
8292
|
}
|
|
7197
|
-
const secretsRoot = options.secretsRoot ??
|
|
7198
|
-
|
|
8293
|
+
const secretsRoot = options.secretsRoot ?? join34(
|
|
8294
|
+
dirname18(
|
|
7199
8295
|
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
7200
8296
|
),
|
|
7201
8297
|
".secrets"
|
|
@@ -7204,9 +8300,9 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7204
8300
|
const grokVersion = commandVersion("grok");
|
|
7205
8301
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
7206
8302
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
7207
|
-
const userSkills = inspectUserSkillEvidence(
|
|
8303
|
+
const userSkills = inspectUserSkillEvidence(join34(homeDir, ".agents/skills"));
|
|
7208
8304
|
const expectedPackageVersion = packageVersion(
|
|
7209
|
-
|
|
8305
|
+
join34(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
7210
8306
|
);
|
|
7211
8307
|
const repositories = endpoints.map(
|
|
7212
8308
|
({ endpoint, role }) => inspectRepository(
|
|
@@ -7263,7 +8359,7 @@ function collectEndpoints(manifest) {
|
|
|
7263
8359
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
7264
8360
|
const seen = /* @__PURE__ */ new Set();
|
|
7265
8361
|
return result.filter(({ endpoint }) => {
|
|
7266
|
-
const key =
|
|
8362
|
+
const key = resolve10(endpoint.path);
|
|
7267
8363
|
if (seen.has(key)) return false;
|
|
7268
8364
|
seen.add(key);
|
|
7269
8365
|
return true;
|
|
@@ -7280,13 +8376,13 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
7280
8376
|
const hooks = inspectHooks(root);
|
|
7281
8377
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
7282
8378
|
const mcp = {
|
|
7283
|
-
codexProject: tomlMcpNames(
|
|
8379
|
+
codexProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.codex)),
|
|
7284
8380
|
claudeCodeProjectShared: jsonObjectKeys(
|
|
7285
|
-
|
|
8381
|
+
join34(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
|
|
7286
8382
|
"mcpServers"
|
|
7287
8383
|
),
|
|
7288
8384
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
7289
|
-
grokProject: tomlMcpNames(
|
|
8385
|
+
grokProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.grok)),
|
|
7290
8386
|
grokEffective: grokInspection.effectiveMcp,
|
|
7291
8387
|
grokInspection: grokInspection.inspection
|
|
7292
8388
|
};
|
|
@@ -7920,14 +9016,15 @@ function isHost2(value) {
|
|
|
7920
9016
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
7921
9017
|
}
|
|
7922
9018
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
7923
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
7924
|
-
return agentAssetsDir &&
|
|
9019
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join35(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
9020
|
+
return agentAssetsDir && existsSync34(join35(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
7925
9021
|
}
|
|
7926
9022
|
function reportMissingPortfolioRegistry(loaded, json) {
|
|
7927
|
-
const expectedPath = loaded.manifest?.executionEngine?.path ?
|
|
9023
|
+
const expectedPath = loaded.manifest?.executionEngine?.path ? join35(loaded.manifest.executionEngine.path, "agent-assets/registry.json") : "executionEngine.path/agent-assets/registry.json";
|
|
9024
|
+
const displayExpectedPath = expectedPath.replaceAll("\\", "/");
|
|
7928
9025
|
const issue = {
|
|
7929
9026
|
type: "missing-control-plane-registry",
|
|
7930
|
-
message: `Portfolio control-plane registry is required at ${
|
|
9027
|
+
message: `Portfolio control-plane registry is required at ${displayExpectedPath}; refusing the package fallback registry.`
|
|
7931
9028
|
};
|
|
7932
9029
|
if (json) {
|
|
7933
9030
|
console.log(
|
|
@@ -7962,10 +9059,10 @@ function printUsage5() {
|
|
|
7962
9059
|
}
|
|
7963
9060
|
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
7964
9061
|
if (!outDir) return void 0;
|
|
7965
|
-
const path =
|
|
7966
|
-
if (!
|
|
9062
|
+
const path = join35(outDir, "portfolio-ai-health.json");
|
|
9063
|
+
if (!existsSync34(path)) return void 0;
|
|
7967
9064
|
try {
|
|
7968
|
-
const value = JSON.parse(
|
|
9065
|
+
const value = JSON.parse(readFileSync19(path, "utf8"));
|
|
7969
9066
|
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
|
|
7970
9067
|
return void 0;
|
|
7971
9068
|
return value;
|
|
@@ -7975,8 +9072,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
|
|
|
7975
9072
|
}
|
|
7976
9073
|
|
|
7977
9074
|
// src/commands/sync.ts
|
|
7978
|
-
import { existsSync as
|
|
7979
|
-
import { join as
|
|
9075
|
+
import { existsSync as existsSync35, lstatSync as lstatSync15, readFileSync as readFileSync20, readlinkSync as readlinkSync4 } from "node:fs";
|
|
9076
|
+
import { join as join36 } from "node:path";
|
|
7980
9077
|
function runSync(args) {
|
|
7981
9078
|
const check = args.includes("--check");
|
|
7982
9079
|
if (!check) {
|
|
@@ -8004,7 +9101,7 @@ function runSync(args) {
|
|
|
8004
9101
|
console.log("pro-gov sync check");
|
|
8005
9102
|
console.log(`profile: ${profile}`);
|
|
8006
9103
|
for (const file of planStarterFiles(profile)) {
|
|
8007
|
-
const targetPath =
|
|
9104
|
+
const targetPath = join36(process.cwd(), file.targetPath);
|
|
8008
9105
|
const stat = safeLstat3(targetPath);
|
|
8009
9106
|
if (!stat) {
|
|
8010
9107
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -8022,14 +9119,14 @@ function runSync(args) {
|
|
|
8022
9119
|
continue;
|
|
8023
9120
|
}
|
|
8024
9121
|
if (file.kind === "symlink") {
|
|
8025
|
-
if (!stat.isSymbolicLink() || readlinkSync4(targetPath) !== file.linkTarget) {
|
|
9122
|
+
if (!stat.isSymbolicLink() || normalizeSymlinkTarget(readlinkSync4(targetPath)) !== file.linkTarget) {
|
|
8026
9123
|
console.log(`different: ${file.targetPath}`);
|
|
8027
9124
|
differences += 1;
|
|
8028
9125
|
}
|
|
8029
9126
|
continue;
|
|
8030
9127
|
}
|
|
8031
|
-
const source =
|
|
8032
|
-
const target =
|
|
9128
|
+
const source = readFileSync20(file.absoluteSourcePath, "utf8");
|
|
9129
|
+
const target = readFileSync20(targetPath, "utf8");
|
|
8033
9130
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
8034
9131
|
console.log(`different: ${file.targetPath}`);
|
|
8035
9132
|
differences += 1;
|
|
@@ -8063,13 +9160,13 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
8063
9160
|
}
|
|
8064
9161
|
function inferInstalledProfile(root) {
|
|
8065
9162
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
8066
|
-
(profile) =>
|
|
9163
|
+
(profile) => existsSync35(join36(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
8067
9164
|
);
|
|
8068
9165
|
return installed.length === 1 ? installed[0] : void 0;
|
|
8069
9166
|
}
|
|
8070
9167
|
function safeLstat3(path) {
|
|
8071
9168
|
try {
|
|
8072
|
-
return
|
|
9169
|
+
return lstatSync15(path);
|
|
8073
9170
|
} catch {
|
|
8074
9171
|
return void 0;
|
|
8075
9172
|
}
|
|
@@ -8090,6 +9187,7 @@ var COMMANDS = [
|
|
|
8090
9187
|
"assets check [--target <path>] [--strict-registry] [--json]",
|
|
8091
9188
|
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
8092
9189
|
"assets npx add|update ... --plan",
|
|
9190
|
+
"assets catalog build|check [--native-links] [--json]",
|
|
8093
9191
|
"portfolio check --config <path> [--json]",
|
|
8094
9192
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
8095
9193
|
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|