@pieai/pro-gov 0.9.3 → 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 +1582 -533
- package/package.json +2 -2
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,18 +58,970 @@ 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
63
|
import {
|
|
64
|
-
cpSync,
|
|
65
64
|
existsSync as existsSync3,
|
|
65
|
+
lstatSync,
|
|
66
66
|
mkdirSync,
|
|
67
67
|
mkdtempSync,
|
|
68
|
-
readdirSync as readdirSync3,
|
|
69
68
|
readFileSync as readFileSync2,
|
|
70
|
-
|
|
69
|
+
readdirSync as readdirSync3,
|
|
70
|
+
renameSync,
|
|
71
|
+
rmSync,
|
|
72
|
+
statSync,
|
|
73
|
+
symlinkSync,
|
|
74
|
+
writeFileSync
|
|
75
|
+
} from "node:fs";
|
|
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
|
|
71
1023
|
} from "node:fs";
|
|
72
|
-
import { join as
|
|
1024
|
+
import { join as join4, relative as relative3 } from "node:path";
|
|
73
1025
|
import { tmpdir } from "node:os";
|
|
74
1026
|
|
|
75
1027
|
// src/command-runner.ts
|
|
@@ -100,7 +1052,7 @@ function createNpxSkillsMaintenancePlan(options) {
|
|
|
100
1052
|
throw new Error("npx skills add requires a source.");
|
|
101
1053
|
}
|
|
102
1054
|
const before = snapshotFiles(options.npxRoot);
|
|
103
|
-
const tempRoot =
|
|
1055
|
+
const tempRoot = mkdtempSync2(join4(tmpdir(), "pro-gov-npx-skills-"));
|
|
104
1056
|
cpSync(options.npxRoot, tempRoot, { recursive: true, dereference: false });
|
|
105
1057
|
const command2 = buildNpxCommand(options);
|
|
106
1058
|
const runner = options.runner ?? defaultRunner;
|
|
@@ -144,10 +1096,10 @@ ${stderr}`.replace(ansiEscape, "");
|
|
|
144
1096
|
}
|
|
145
1097
|
}
|
|
146
1098
|
function assertNativeNpxRoot(npxRoot) {
|
|
147
|
-
if (!
|
|
1099
|
+
if (!existsSync4(join4(npxRoot, "skills-lock.json"))) {
|
|
148
1100
|
throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
|
|
149
1101
|
}
|
|
150
|
-
if (!
|
|
1102
|
+
if (!existsSync4(join4(npxRoot, ".agents/skills"))) {
|
|
151
1103
|
throw new Error(`npx skills root is missing .agents/skills: ${npxRoot}`);
|
|
152
1104
|
}
|
|
153
1105
|
}
|
|
@@ -178,21 +1130,21 @@ function defaultRunner({ command: command2, cwd, timeoutMs }) {
|
|
|
178
1130
|
}
|
|
179
1131
|
function snapshotFiles(root) {
|
|
180
1132
|
const snapshot = /* @__PURE__ */ new Map();
|
|
181
|
-
for (const filePath of
|
|
182
|
-
const relativePath = toUnixPath2(
|
|
1133
|
+
for (const filePath of listFiles3(root)) {
|
|
1134
|
+
const relativePath = toUnixPath2(relative3(root, filePath));
|
|
183
1135
|
snapshot.set(relativePath, hashFile(filePath));
|
|
184
1136
|
}
|
|
185
1137
|
return snapshot;
|
|
186
1138
|
}
|
|
187
|
-
function
|
|
1139
|
+
function listFiles3(root) {
|
|
188
1140
|
const files = [];
|
|
189
1141
|
collectFiles(root, root, files);
|
|
190
1142
|
return files.sort();
|
|
191
1143
|
}
|
|
192
1144
|
function collectFiles(root, current, files) {
|
|
193
|
-
|
|
194
|
-
for (const entry of
|
|
195
|
-
const entryPath =
|
|
1145
|
+
mkdirSync2(root, { recursive: true });
|
|
1146
|
+
for (const entry of readdirSync4(current, { withFileTypes: true })) {
|
|
1147
|
+
const entryPath = join4(current, entry.name);
|
|
196
1148
|
if (entry.isDirectory()) {
|
|
197
1149
|
collectFiles(root, entryPath, files);
|
|
198
1150
|
} else if (entry.isFile()) {
|
|
@@ -201,11 +1153,11 @@ function collectFiles(root, current, files) {
|
|
|
201
1153
|
}
|
|
202
1154
|
}
|
|
203
1155
|
function hashFile(path) {
|
|
204
|
-
const hash =
|
|
205
|
-
const stats =
|
|
1156
|
+
const hash = createHash2("sha256");
|
|
1157
|
+
const stats = statSync2(path);
|
|
206
1158
|
hash.update(String(stats.size));
|
|
207
1159
|
hash.update("\0");
|
|
208
|
-
hash.update(
|
|
1160
|
+
hash.update(readFileSync3(path));
|
|
209
1161
|
return hash.digest("hex");
|
|
210
1162
|
}
|
|
211
1163
|
function diffSnapshots(before, after) {
|
|
@@ -232,14 +1184,14 @@ function toUnixPath2(path) {
|
|
|
232
1184
|
}
|
|
233
1185
|
|
|
234
1186
|
// src/asset-registry/loader.ts
|
|
235
|
-
import { createHash as
|
|
236
|
-
import { existsSync as
|
|
237
|
-
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";
|
|
238
1190
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
239
1191
|
|
|
240
1192
|
// src/asset-registry/registry.ts
|
|
241
|
-
import { existsSync as
|
|
242
|
-
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";
|
|
243
1195
|
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
244
1196
|
"pie-skills",
|
|
245
1197
|
"npx-skills",
|
|
@@ -283,7 +1235,7 @@ function assetSkillInstallName(asset) {
|
|
|
283
1235
|
return asset.installName ?? posix.basename(asset.sourcePath);
|
|
284
1236
|
}
|
|
285
1237
|
function isValidAssetProjectTargetPath(kind, projectTargetPath) {
|
|
286
|
-
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 ||
|
|
1238
|
+
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 || isAbsolute2(projectTargetPath) || projectTargetPath.includes("\\")) {
|
|
287
1239
|
return false;
|
|
288
1240
|
}
|
|
289
1241
|
const segments = projectTargetPath.split("/");
|
|
@@ -439,11 +1391,11 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
439
1391
|
});
|
|
440
1392
|
}
|
|
441
1393
|
if (options.agentAssetsDir && isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
442
|
-
const sourceAbsolutePath =
|
|
1394
|
+
const sourceAbsolutePath = join5(
|
|
443
1395
|
options.agentAssetsDir,
|
|
444
1396
|
normalizeRegistrySourcePath(asset.sourcePath)
|
|
445
1397
|
);
|
|
446
|
-
if (!
|
|
1398
|
+
if (!existsSync5(sourceAbsolutePath)) {
|
|
447
1399
|
issues.push({
|
|
448
1400
|
type: "missing-source-path",
|
|
449
1401
|
id: asset.id,
|
|
@@ -467,7 +1419,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
467
1419
|
message: `Local skill pack must contain .codex-plugin/plugin.json and at least one skills/*/SKILL.md: ${asset.sourcePath}`
|
|
468
1420
|
});
|
|
469
1421
|
}
|
|
470
|
-
} else if (!
|
|
1422
|
+
} else if (!existsSync5(join5(sourceAbsolutePath, "SKILL.md"))) {
|
|
471
1423
|
issues.push({
|
|
472
1424
|
type: "missing-skill-file",
|
|
473
1425
|
id: asset.id,
|
|
@@ -479,7 +1431,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
479
1431
|
}
|
|
480
1432
|
}
|
|
481
1433
|
if (options.agentAssetsDir) {
|
|
482
|
-
const npxCompatibilityLayer =
|
|
1434
|
+
const npxCompatibilityLayer = join5(options.agentAssetsDir, "skills/npx-skills/skills");
|
|
483
1435
|
if (pathExistsEvenIfDanglingSymlink(npxCompatibilityLayer)) {
|
|
484
1436
|
issues.push({
|
|
485
1437
|
type: "internal-npx-compatibility-layer",
|
|
@@ -492,20 +1444,20 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
492
1444
|
return issues;
|
|
493
1445
|
}
|
|
494
1446
|
function isLocalSkillPack(sourceAbsolutePath) {
|
|
495
|
-
const skillsRoot =
|
|
496
|
-
if (!
|
|
1447
|
+
const skillsRoot = join5(sourceAbsolutePath, "skills");
|
|
1448
|
+
if (!existsSync5(join5(sourceAbsolutePath, ".codex-plugin/plugin.json")) || !existsSync5(skillsRoot)) {
|
|
497
1449
|
return false;
|
|
498
1450
|
}
|
|
499
1451
|
try {
|
|
500
|
-
return
|
|
501
|
-
(entry) => entry.isDirectory() &&
|
|
1452
|
+
return lstatSync2(skillsRoot).isDirectory() && readdirSync5(skillsRoot, { withFileTypes: true }).some(
|
|
1453
|
+
(entry) => entry.isDirectory() && existsSync5(join5(skillsRoot, entry.name, "SKILL.md"))
|
|
502
1454
|
);
|
|
503
1455
|
} catch {
|
|
504
1456
|
return false;
|
|
505
1457
|
}
|
|
506
1458
|
}
|
|
507
1459
|
function isSafeRegistrySourcePath(sourcePath) {
|
|
508
|
-
if (!sourcePath ||
|
|
1460
|
+
if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return false;
|
|
509
1461
|
const normalized = normalizeRegistrySourcePath(sourcePath);
|
|
510
1462
|
if (normalized === "." || normalized.startsWith("../") || normalized === "..") return false;
|
|
511
1463
|
return !normalized.split("/").includes("..");
|
|
@@ -517,15 +1469,15 @@ function isWithinAgentAssetsDir(agentAssetsDir, sourceAbsolutePath) {
|
|
|
517
1469
|
try {
|
|
518
1470
|
const agentAssetsRealPath = realpathSync(agentAssetsDir);
|
|
519
1471
|
const sourceRealPath = realpathSync(sourceAbsolutePath);
|
|
520
|
-
const relativePath =
|
|
521
|
-
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !
|
|
1472
|
+
const relativePath = relative4(agentAssetsRealPath, sourceRealPath);
|
|
1473
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute2(relativePath);
|
|
522
1474
|
} catch {
|
|
523
1475
|
return false;
|
|
524
1476
|
}
|
|
525
1477
|
}
|
|
526
1478
|
function pathExistsEvenIfDanglingSymlink(path) {
|
|
527
1479
|
try {
|
|
528
|
-
|
|
1480
|
+
lstatSync2(path);
|
|
529
1481
|
return true;
|
|
530
1482
|
} catch {
|
|
531
1483
|
return false;
|
|
@@ -542,7 +1494,7 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
542
1494
|
(left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
|
|
543
1495
|
)
|
|
544
1496
|
});
|
|
545
|
-
const hash =
|
|
1497
|
+
const hash = createHash3("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
|
|
546
1498
|
return {
|
|
547
1499
|
schema: "agent-assets-registry",
|
|
548
1500
|
version: registry.schemaVersion,
|
|
@@ -552,8 +1504,8 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
552
1504
|
}
|
|
553
1505
|
function loadAgentAssetRegistry(options = {}) {
|
|
554
1506
|
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
555
|
-
const registryPath =
|
|
556
|
-
if (!
|
|
1507
|
+
const registryPath = join6(agentAssetsDir, "registry.json");
|
|
1508
|
+
if (!existsSync6(registryPath)) {
|
|
557
1509
|
return {
|
|
558
1510
|
registry: { schemaVersion: 1, assets: [] },
|
|
559
1511
|
agentAssetsDir,
|
|
@@ -561,7 +1513,7 @@ function loadAgentAssetRegistry(options = {}) {
|
|
|
561
1513
|
issues: []
|
|
562
1514
|
};
|
|
563
1515
|
}
|
|
564
|
-
const registry = JSON.parse(
|
|
1516
|
+
const registry = JSON.parse(readFileSync4(registryPath, "utf8"));
|
|
565
1517
|
return {
|
|
566
1518
|
registry,
|
|
567
1519
|
agentAssetsDir,
|
|
@@ -579,15 +1531,15 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
579
1531
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
580
1532
|
}
|
|
581
1533
|
function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
582
|
-
return hashAssetPathContent(
|
|
1534
|
+
return hashAssetPathContent(join6(agentAssetsDir, asset.sourcePath));
|
|
583
1535
|
}
|
|
584
1536
|
function hashAssetPathContent(sourceAbsolutePath) {
|
|
585
|
-
const hash =
|
|
586
|
-
for (const filePath of
|
|
587
|
-
const relativePath = toUnixPath3(
|
|
1537
|
+
const hash = createHash3("sha256");
|
|
1538
|
+
for (const filePath of listFiles4(sourceAbsolutePath)) {
|
|
1539
|
+
const relativePath = toUnixPath3(relative5(sourceAbsolutePath, filePath));
|
|
588
1540
|
hash.update(relativePath);
|
|
589
1541
|
hash.update("\0");
|
|
590
|
-
hash.update(
|
|
1542
|
+
hash.update(readFileSync4(filePath));
|
|
591
1543
|
hash.update("\0");
|
|
592
1544
|
}
|
|
593
1545
|
return `sha256:${hash.digest("hex")}`;
|
|
@@ -602,40 +1554,40 @@ function canonicalizeValue(value) {
|
|
|
602
1554
|
return value;
|
|
603
1555
|
}
|
|
604
1556
|
function findDefaultAgentAssetsDir() {
|
|
605
|
-
const packageRoot2 = findPackageRoot(
|
|
606
|
-
const repoRoot =
|
|
1557
|
+
const packageRoot2 = findPackageRoot(dirname3(fileURLToPath2(import.meta.url)));
|
|
1558
|
+
const repoRoot = join6(packageRoot2, "..", "..");
|
|
607
1559
|
const candidates = [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1560
|
+
join6(packageRoot2, "assets/agent-assets"),
|
|
1561
|
+
join6(repoRoot, "agent-assets"),
|
|
1562
|
+
join6(packageRoot2, "assets/public-agent-assets"),
|
|
1563
|
+
join6(repoRoot, "public-agent-assets")
|
|
612
1564
|
];
|
|
613
|
-
return candidates.find((candidate) =>
|
|
1565
|
+
return candidates.find((candidate) => existsSync6(join6(candidate, "registry.json"))) ?? candidates[0];
|
|
614
1566
|
}
|
|
615
1567
|
function findPackageRoot(startDir) {
|
|
616
1568
|
let current = startDir;
|
|
617
|
-
while (current !==
|
|
618
|
-
const packageJsonPath =
|
|
619
|
-
if (
|
|
1569
|
+
while (current !== dirname3(current)) {
|
|
1570
|
+
const packageJsonPath = join6(current, "package.json");
|
|
1571
|
+
if (existsSync6(packageJsonPath)) {
|
|
620
1572
|
try {
|
|
621
|
-
const packageJson = JSON.parse(
|
|
1573
|
+
const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
622
1574
|
if (packageJson.name === "@pieai/pro-gov") return current;
|
|
623
1575
|
} catch {
|
|
624
1576
|
}
|
|
625
1577
|
}
|
|
626
|
-
current =
|
|
1578
|
+
current = dirname3(current);
|
|
627
1579
|
}
|
|
628
1580
|
return startDir;
|
|
629
1581
|
}
|
|
630
|
-
function
|
|
631
|
-
const stats =
|
|
1582
|
+
function listFiles4(absolutePath) {
|
|
1583
|
+
const stats = statSync3(absolutePath);
|
|
632
1584
|
if (stats.isFile()) return [absolutePath];
|
|
633
1585
|
const files = [];
|
|
634
|
-
for (const entry of
|
|
1586
|
+
for (const entry of readdirSync6(absolutePath, { withFileTypes: true })) {
|
|
635
1587
|
if (shouldIgnoreAssetHashEntry(entry.name)) continue;
|
|
636
|
-
const entryPath =
|
|
1588
|
+
const entryPath = join6(absolutePath, entry.name);
|
|
637
1589
|
if (entry.isDirectory()) {
|
|
638
|
-
files.push(...
|
|
1590
|
+
files.push(...listFiles4(entryPath));
|
|
639
1591
|
} else if (entry.isFile()) {
|
|
640
1592
|
files.push(entryPath);
|
|
641
1593
|
}
|
|
@@ -650,8 +1602,8 @@ function toUnixPath3(path) {
|
|
|
650
1602
|
}
|
|
651
1603
|
|
|
652
1604
|
// src/asset-registry/public-promotion.ts
|
|
653
|
-
import { existsSync as
|
|
654
|
-
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";
|
|
655
1607
|
function checkPublicAssetPromotions(options) {
|
|
656
1608
|
const issues = [];
|
|
657
1609
|
let checked = 0;
|
|
@@ -689,7 +1641,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
689
1641
|
});
|
|
690
1642
|
continue;
|
|
691
1643
|
}
|
|
692
|
-
if (!
|
|
1644
|
+
if (!existsSync7(privatePathResult.path)) {
|
|
693
1645
|
issues.push({
|
|
694
1646
|
type: "missing-private-source",
|
|
695
1647
|
id: asset.id,
|
|
@@ -709,7 +1661,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
709
1661
|
});
|
|
710
1662
|
}
|
|
711
1663
|
}
|
|
712
|
-
if (!
|
|
1664
|
+
if (!existsSync7(publicPathResult.path)) {
|
|
713
1665
|
issues.push({
|
|
714
1666
|
type: "missing-public-source",
|
|
715
1667
|
id: asset.id,
|
|
@@ -736,32 +1688,32 @@ function needsPromotionCheck(asset) {
|
|
|
736
1688
|
return asset.visibility === "public" && asset.publishable;
|
|
737
1689
|
}
|
|
738
1690
|
function resolveSafePath(root, sourcePath) {
|
|
739
|
-
if (!sourcePath ||
|
|
1691
|
+
if (!sourcePath || isAbsolute3(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
|
|
740
1692
|
const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
|
|
741
1693
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
742
1694
|
return { ok: false };
|
|
743
1695
|
}
|
|
744
1696
|
if (normalized.split("/").includes("..")) return { ok: false };
|
|
745
|
-
return { ok: true, path:
|
|
1697
|
+
return { ok: true, path: join7(root, normalized) };
|
|
746
1698
|
}
|
|
747
1699
|
|
|
748
1700
|
// src/asset-targets/apply.ts
|
|
749
|
-
import { createHash as
|
|
1701
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
750
1702
|
import {
|
|
751
|
-
existsSync as
|
|
752
|
-
lstatSync as
|
|
753
|
-
mkdirSync as
|
|
1703
|
+
existsSync as existsSync9,
|
|
1704
|
+
lstatSync as lstatSync4,
|
|
1705
|
+
mkdirSync as mkdirSync3,
|
|
754
1706
|
readlinkSync as readlinkSync2,
|
|
755
1707
|
realpathSync as realpathSync3,
|
|
756
|
-
symlinkSync,
|
|
1708
|
+
symlinkSync as symlinkSync2,
|
|
757
1709
|
unlinkSync,
|
|
758
|
-
writeFileSync
|
|
1710
|
+
writeFileSync as writeFileSync2
|
|
759
1711
|
} from "node:fs";
|
|
760
|
-
import { dirname as
|
|
1712
|
+
import { dirname as dirname5, join as join9, relative as relative6, resolve as resolve3 } from "node:path";
|
|
761
1713
|
|
|
762
1714
|
// src/asset-targets/install-plan.ts
|
|
763
|
-
import { existsSync as
|
|
764
|
-
import { basename, dirname as
|
|
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";
|
|
765
1717
|
|
|
766
1718
|
// src/symlinks.ts
|
|
767
1719
|
function normalizeSymlinkTarget(target) {
|
|
@@ -888,9 +1840,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
888
1840
|
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
889
1841
|
);
|
|
890
1842
|
}
|
|
891
|
-
const sourcePath =
|
|
1843
|
+
const sourcePath = join8(agentAssetsDir, asset.sourcePath);
|
|
892
1844
|
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
893
|
-
const targetAbsolutePath =
|
|
1845
|
+
const targetAbsolutePath = join8(targetDir, targetPath);
|
|
894
1846
|
const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
|
|
895
1847
|
const managedEntry = managedEntries.find(
|
|
896
1848
|
(entry) => entry.id === asset.id && entry.targetPath === targetPath
|
|
@@ -907,7 +1859,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
907
1859
|
});
|
|
908
1860
|
}
|
|
909
1861
|
if (targetExists) {
|
|
910
|
-
const stats =
|
|
1862
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
911
1863
|
if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
|
|
912
1864
|
return {
|
|
913
1865
|
type: "update-symlink",
|
|
@@ -919,7 +1871,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
919
1871
|
if (managedEntry && managedEntry.delivery === "snapshot") {
|
|
920
1872
|
throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
|
|
921
1873
|
}
|
|
922
|
-
if (stats.isSymbolicLink() &&
|
|
1874
|
+
if (stats.isSymbolicLink() && existsSync8(targetAbsolutePath) && realpathSync2(targetAbsolutePath) === realpathSync2(sourcePath)) {
|
|
923
1875
|
return {
|
|
924
1876
|
type: "adopt-existing-symlink",
|
|
925
1877
|
assetId: asset.id,
|
|
@@ -942,10 +1894,10 @@ function createSnapshotAction(options) {
|
|
|
942
1894
|
`Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
|
|
943
1895
|
);
|
|
944
1896
|
}
|
|
945
|
-
if (!
|
|
1897
|
+
if (!statSync4(options.sourcePath).isFile()) {
|
|
946
1898
|
throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
|
|
947
1899
|
}
|
|
948
|
-
const content =
|
|
1900
|
+
const content = readFileSync5(options.sourcePath);
|
|
949
1901
|
const contentBase64 = content.toString("base64");
|
|
950
1902
|
const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
|
|
951
1903
|
const managedDelivery = options.managedEntry?.delivery ?? "symlink";
|
|
@@ -958,9 +1910,9 @@ function createSnapshotAction(options) {
|
|
|
958
1910
|
contentHash
|
|
959
1911
|
};
|
|
960
1912
|
}
|
|
961
|
-
const stats =
|
|
1913
|
+
const stats = lstatSync3(options.targetAbsolutePath);
|
|
962
1914
|
if (stats.isSymbolicLink()) {
|
|
963
|
-
if (
|
|
1915
|
+
if (existsSync8(options.targetAbsolutePath) && realpathSync2(options.targetAbsolutePath) === realpathSync2(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
|
|
964
1916
|
return {
|
|
965
1917
|
type: "migrate-symlink-to-snapshot",
|
|
966
1918
|
assetId: options.asset.id,
|
|
@@ -1016,9 +1968,9 @@ function resolveHostTargetPath(asset, _host, placement) {
|
|
|
1016
1968
|
return `.agents/skills/${assetSkillInstallName(asset)}`;
|
|
1017
1969
|
}
|
|
1018
1970
|
if (asset.kind === "rule") {
|
|
1019
|
-
return `.pro-gov/agent-assets/rules/${
|
|
1971
|
+
return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
|
|
1020
1972
|
}
|
|
1021
|
-
return `.pro-gov/agent-assets/commands/${
|
|
1973
|
+
return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
|
|
1022
1974
|
}
|
|
1023
1975
|
function resolveSkillPlacement(asset, placement) {
|
|
1024
1976
|
if (placement !== "registry") return placement;
|
|
@@ -1028,16 +1980,16 @@ function createDirectoryActions(actions) {
|
|
|
1028
1980
|
const directories = /* @__PURE__ */ new Set();
|
|
1029
1981
|
for (const action of actions) {
|
|
1030
1982
|
if (action.type === "create-dir") continue;
|
|
1031
|
-
const directory =
|
|
1983
|
+
const directory = dirname4(action.targetPath);
|
|
1032
1984
|
if (directory !== ".") directories.add(directory);
|
|
1033
1985
|
}
|
|
1034
1986
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
1035
1987
|
}
|
|
1036
1988
|
function readManagedLock(targetDir) {
|
|
1037
|
-
const lockfilePath =
|
|
1038
|
-
if (!
|
|
1989
|
+
const lockfilePath = join8(targetDir, ".pro-gov/assets.lock.json");
|
|
1990
|
+
if (!existsSync8(lockfilePath)) return { entries: [] };
|
|
1039
1991
|
try {
|
|
1040
|
-
const lockfile = JSON.parse(
|
|
1992
|
+
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
1041
1993
|
return {
|
|
1042
1994
|
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
1043
1995
|
entries: (lockfile.assets ?? []).filter(
|
|
@@ -1075,10 +2027,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1075
2027
|
throw new Error(`Legacy Claude lock entry cannot be safely normalized: ${entry.targetPath}`);
|
|
1076
2028
|
}
|
|
1077
2029
|
const targetPath = `.agents/skills/${skillName}`;
|
|
1078
|
-
const targetAbsolutePath =
|
|
1079
|
-
const compatibilityRootPath =
|
|
1080
|
-
const canonicalRootPath =
|
|
1081
|
-
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)) {
|
|
1082
2034
|
throw new Error(
|
|
1083
2035
|
`Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`
|
|
1084
2036
|
);
|
|
@@ -1087,10 +2039,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1087
2039
|
consumedTargetPaths.add(entry.targetPath);
|
|
1088
2040
|
continue;
|
|
1089
2041
|
}
|
|
1090
|
-
const targetStat =
|
|
1091
|
-
const legacyAbsolutePath =
|
|
1092
|
-
const legacyStat =
|
|
1093
|
-
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);
|
|
1094
2046
|
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || realpathSync2(targetAbsolutePath) !== realpathSync2(expectedSourcePath)) {
|
|
1095
2047
|
throw new Error(`Legacy Claude skill target cannot be safely adopted: ${entry.targetPath}`);
|
|
1096
2048
|
}
|
|
@@ -1120,9 +2072,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1120
2072
|
`Refusing to remove managed asset outside supported roots: ${entry.targetPath}`
|
|
1121
2073
|
);
|
|
1122
2074
|
}
|
|
1123
|
-
const targetAbsolutePath =
|
|
2075
|
+
const targetAbsolutePath = join8(targetDir, entry.targetPath);
|
|
1124
2076
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
|
|
1125
|
-
const stats =
|
|
2077
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
1126
2078
|
if ((entry.delivery ?? "symlink") === "snapshot") {
|
|
1127
2079
|
if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1128
2080
|
throw new Error(
|
|
@@ -1151,9 +2103,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1151
2103
|
`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
|
|
1152
2104
|
);
|
|
1153
2105
|
}
|
|
1154
|
-
const expectedSourcePath =
|
|
1155
|
-
const actualSourcePath =
|
|
1156
|
-
if (actualSourcePath !==
|
|
2106
|
+
const expectedSourcePath = join8(agentAssetsDir, entry.sourcePath);
|
|
2107
|
+
const actualSourcePath = resolve2(dirname4(targetAbsolutePath), readlinkSync(targetAbsolutePath));
|
|
2108
|
+
if (actualSourcePath !== resolve2(expectedSourcePath)) {
|
|
1157
2109
|
throw new Error(
|
|
1158
2110
|
`Refusing to remove managed symlink with changed target: ${entry.targetPath}`
|
|
1159
2111
|
);
|
|
@@ -1177,7 +2129,7 @@ function isLegacyClaudeSkillTargetPath(path) {
|
|
|
1177
2129
|
}
|
|
1178
2130
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
1179
2131
|
try {
|
|
1180
|
-
|
|
2132
|
+
lstatSync3(path);
|
|
1181
2133
|
return true;
|
|
1182
2134
|
} catch {
|
|
1183
2135
|
return false;
|
|
@@ -1201,7 +2153,7 @@ function applyAssetInstallPlan(plan) {
|
|
|
1201
2153
|
return { appliedActions };
|
|
1202
2154
|
}
|
|
1203
2155
|
function applyAction(targetDir, action) {
|
|
1204
|
-
const targetAbsolutePath =
|
|
2156
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1205
2157
|
if (action.type === "remove-symlink") {
|
|
1206
2158
|
removeManagedSymlink(targetAbsolutePath, action);
|
|
1207
2159
|
return;
|
|
@@ -1212,41 +2164,41 @@ function applyAction(targetDir, action) {
|
|
|
1212
2164
|
return;
|
|
1213
2165
|
}
|
|
1214
2166
|
if (action.type === "create-dir") {
|
|
1215
|
-
|
|
2167
|
+
mkdirSync3(targetAbsolutePath, { recursive: true });
|
|
1216
2168
|
return;
|
|
1217
2169
|
}
|
|
1218
2170
|
if (action.type === "write-file") {
|
|
1219
|
-
|
|
1220
|
-
|
|
2171
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2172
|
+
writeFileSync2(targetAbsolutePath, action.content);
|
|
1221
2173
|
return;
|
|
1222
2174
|
}
|
|
1223
|
-
|
|
1224
|
-
const sourceAbsolutePath =
|
|
1225
|
-
const symlinkTarget =
|
|
2175
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2176
|
+
const sourceAbsolutePath = resolve3(action.sourcePath);
|
|
2177
|
+
const symlinkTarget = relative6(realpathSync3(dirname5(targetAbsolutePath)), realpathSync3(sourceAbsolutePath)) || ".";
|
|
1226
2178
|
if (action.type === "symlink") {
|
|
1227
2179
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1228
2180
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1229
2181
|
}
|
|
1230
|
-
|
|
2182
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1231
2183
|
return;
|
|
1232
2184
|
}
|
|
1233
2185
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1234
|
-
|
|
2186
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1235
2187
|
return;
|
|
1236
2188
|
}
|
|
1237
|
-
const stats =
|
|
2189
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1238
2190
|
if (!stats.isSymbolicLink()) {
|
|
1239
2191
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1240
2192
|
}
|
|
1241
2193
|
unlinkSync(targetAbsolutePath);
|
|
1242
|
-
|
|
2194
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1243
2195
|
}
|
|
1244
2196
|
function validateExistingSymlink(targetDir, action) {
|
|
1245
2197
|
if (!isManagedAssetTargetPath(action.targetPath)) {
|
|
1246
2198
|
throw new Error(`Refusing unsafe existing symlink adoption: ${action.targetPath}`);
|
|
1247
2199
|
}
|
|
1248
|
-
const targetAbsolutePath =
|
|
1249
|
-
if (!
|
|
2200
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
2201
|
+
if (!lstatSync4(targetAbsolutePath).isSymbolicLink() || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
1250
2202
|
throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
|
|
1251
2203
|
}
|
|
1252
2204
|
}
|
|
@@ -1257,7 +2209,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1257
2209
|
if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
|
|
1258
2210
|
throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
|
|
1259
2211
|
}
|
|
1260
|
-
const targetAbsolutePath =
|
|
2212
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1261
2213
|
if (action.type === "snapshot") {
|
|
1262
2214
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1263
2215
|
throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
|
|
@@ -1273,7 +2225,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1273
2225
|
return;
|
|
1274
2226
|
}
|
|
1275
2227
|
if (action.type === "migrate-symlink-to-snapshot") {
|
|
1276
|
-
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !
|
|
2228
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync4(targetAbsolutePath).isSymbolicLink() || !existsSync9(targetAbsolutePath) || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
|
|
1277
2229
|
throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
|
|
1278
2230
|
}
|
|
1279
2231
|
return;
|
|
@@ -1281,30 +2233,30 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1281
2233
|
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1282
2234
|
}
|
|
1283
2235
|
function applySnapshotAction(targetDir, action) {
|
|
1284
|
-
const targetAbsolutePath =
|
|
2236
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1285
2237
|
if (action.type === "adopt-snapshot") return;
|
|
1286
2238
|
if (action.type === "remove-snapshot") {
|
|
1287
2239
|
unlinkSync(targetAbsolutePath);
|
|
1288
2240
|
return;
|
|
1289
2241
|
}
|
|
1290
2242
|
if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
|
|
1291
|
-
|
|
2243
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
1292
2244
|
if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1293
2245
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1294
2246
|
}
|
|
1295
|
-
|
|
2247
|
+
writeFileSync2(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
|
|
1296
2248
|
}
|
|
1297
2249
|
function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
|
|
1298
2250
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1299
2251
|
throw new Error(`Snapshot target changed before apply: ${targetPath}`);
|
|
1300
2252
|
}
|
|
1301
|
-
const stats =
|
|
2253
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1302
2254
|
if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
|
|
1303
2255
|
throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
|
|
1304
2256
|
}
|
|
1305
2257
|
}
|
|
1306
2258
|
function hashSnapshotBytes(content) {
|
|
1307
|
-
const hash =
|
|
2259
|
+
const hash = createHash4("sha256");
|
|
1308
2260
|
hash.update("");
|
|
1309
2261
|
hash.update("\0");
|
|
1310
2262
|
hash.update(content);
|
|
@@ -1315,17 +2267,17 @@ function validateAdoptedSymlink(targetDir, action) {
|
|
|
1315
2267
|
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
1316
2268
|
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
1317
2269
|
}
|
|
1318
|
-
const canonicalRootPath =
|
|
1319
|
-
const compatibilityRootPath =
|
|
1320
|
-
const targetAbsolutePath =
|
|
1321
|
-
const legacyAbsolutePath =
|
|
1322
|
-
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)) {
|
|
1323
2275
|
throw new Error(
|
|
1324
2276
|
`Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`
|
|
1325
2277
|
);
|
|
1326
2278
|
}
|
|
1327
|
-
const targetStat =
|
|
1328
|
-
const legacyStat =
|
|
2279
|
+
const targetStat = lstatSync4(targetAbsolutePath);
|
|
2280
|
+
const legacyStat = lstatSync4(legacyAbsolutePath);
|
|
1329
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)) {
|
|
1330
2282
|
throw new Error(`Legacy Claude skill target changed before apply: ${action.targetPath}`);
|
|
1331
2283
|
}
|
|
@@ -1337,33 +2289,33 @@ function removeManagedSymlink(targetAbsolutePath, action) {
|
|
|
1337
2289
|
);
|
|
1338
2290
|
}
|
|
1339
2291
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) return;
|
|
1340
|
-
const stats =
|
|
2292
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1341
2293
|
if (!stats.isSymbolicLink()) {
|
|
1342
2294
|
throw new Error(
|
|
1343
2295
|
`Refusing to remove path that is no longer a managed symlink: ${action.targetPath}`
|
|
1344
2296
|
);
|
|
1345
2297
|
}
|
|
1346
|
-
const actualSourcePath =
|
|
1347
|
-
if (actualSourcePath !==
|
|
2298
|
+
const actualSourcePath = resolve3(dirname5(targetAbsolutePath), readlinkSync2(targetAbsolutePath));
|
|
2299
|
+
if (actualSourcePath !== resolve3(action.expectedSourcePath)) {
|
|
1348
2300
|
throw new Error(`Refusing to remove managed symlink with changed target: ${action.targetPath}`);
|
|
1349
2301
|
}
|
|
1350
2302
|
unlinkSync(targetAbsolutePath);
|
|
1351
2303
|
}
|
|
1352
2304
|
function pathExistsEvenIfDanglingSymlink3(path) {
|
|
1353
2305
|
try {
|
|
1354
|
-
|
|
2306
|
+
lstatSync4(path);
|
|
1355
2307
|
return true;
|
|
1356
2308
|
} catch {
|
|
1357
|
-
return
|
|
2309
|
+
return existsSync9(path);
|
|
1358
2310
|
}
|
|
1359
2311
|
}
|
|
1360
2312
|
|
|
1361
2313
|
// src/asset-targets/check.ts
|
|
1362
|
-
import { existsSync as
|
|
1363
|
-
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";
|
|
1364
2316
|
function checkInstalledAssets(options) {
|
|
1365
|
-
const lockfilePath =
|
|
1366
|
-
if (!
|
|
2317
|
+
const lockfilePath = join10(options.targetDir, ".pro-gov/assets.lock.json");
|
|
2318
|
+
if (!existsSync10(lockfilePath)) {
|
|
1367
2319
|
return {
|
|
1368
2320
|
targetDir: options.targetDir,
|
|
1369
2321
|
issues: [
|
|
@@ -1375,7 +2327,7 @@ function checkInstalledAssets(options) {
|
|
|
1375
2327
|
};
|
|
1376
2328
|
}
|
|
1377
2329
|
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
1378
|
-
const lockfile = JSON.parse(
|
|
2330
|
+
const lockfile = JSON.parse(readFileSync6(lockfilePath, "utf8"));
|
|
1379
2331
|
const issues = [];
|
|
1380
2332
|
const strictRegistry = options.strictRegistry ?? false;
|
|
1381
2333
|
const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
|
|
@@ -1404,7 +2356,7 @@ function checkInstalledAssets(options) {
|
|
|
1404
2356
|
continue;
|
|
1405
2357
|
}
|
|
1406
2358
|
const asset = registryById.get(entry.id);
|
|
1407
|
-
const targetAbsolutePath =
|
|
2359
|
+
const targetAbsolutePath = join10(options.targetDir, entry.targetPath);
|
|
1408
2360
|
const delivery = entry.delivery ?? "symlink";
|
|
1409
2361
|
const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
|
|
1410
2362
|
if (delivery !== "symlink" && delivery !== "snapshot") {
|
|
@@ -1466,7 +2418,7 @@ function checkInstalledAssets(options) {
|
|
|
1466
2418
|
});
|
|
1467
2419
|
continue;
|
|
1468
2420
|
}
|
|
1469
|
-
const targetStats =
|
|
2421
|
+
const targetStats = lstatSync5(targetAbsolutePath);
|
|
1470
2422
|
if (delivery === "snapshot" && !targetStats.isFile()) {
|
|
1471
2423
|
issues.push({
|
|
1472
2424
|
type: "snapshot-not-regular-file",
|
|
@@ -1485,7 +2437,7 @@ function checkInstalledAssets(options) {
|
|
|
1485
2437
|
});
|
|
1486
2438
|
continue;
|
|
1487
2439
|
}
|
|
1488
|
-
if (delivery === "symlink" && !
|
|
2440
|
+
if (delivery === "symlink" && !existsSync10(targetAbsolutePath)) {
|
|
1489
2441
|
if (portableDeferredSkill) continue;
|
|
1490
2442
|
issues.push({
|
|
1491
2443
|
type: "dangling-symlink",
|
|
@@ -1506,8 +2458,8 @@ function checkInstalledAssets(options) {
|
|
|
1506
2458
|
});
|
|
1507
2459
|
}
|
|
1508
2460
|
if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
|
|
1509
|
-
const sourceAbsolutePath =
|
|
1510
|
-
if (!
|
|
2461
|
+
const sourceAbsolutePath = join10(options.agentAssetsDir, asset.sourcePath);
|
|
2462
|
+
if (!existsSync10(sourceAbsolutePath)) {
|
|
1511
2463
|
issues.push({
|
|
1512
2464
|
type: "missing-source",
|
|
1513
2465
|
id: entry.id,
|
|
@@ -1571,7 +2523,7 @@ function checkDuplicateSkillPlacements(targetDir, registry) {
|
|
|
1571
2523
|
const skillName = assetSkillInstallName(asset);
|
|
1572
2524
|
const autoPath = `.agents/skills/${skillName}`;
|
|
1573
2525
|
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
1574
|
-
if (pathExistsEvenIfDanglingSymlink4(
|
|
2526
|
+
if (pathExistsEvenIfDanglingSymlink4(join10(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink4(join10(targetDir, manualPath))) {
|
|
1575
2527
|
issues.push({
|
|
1576
2528
|
type: "duplicate-skill-placement",
|
|
1577
2529
|
id: asset.id,
|
|
@@ -1617,7 +2569,7 @@ function expectedRegistrySkillTargetPath(host, skillName, placement) {
|
|
|
1617
2569
|
}
|
|
1618
2570
|
function pathExistsEvenIfDanglingSymlink4(path) {
|
|
1619
2571
|
try {
|
|
1620
|
-
|
|
2572
|
+
lstatSync5(path);
|
|
1621
2573
|
return true;
|
|
1622
2574
|
} catch {
|
|
1623
2575
|
return false;
|
|
@@ -1628,8 +2580,8 @@ function isProjectSkillTarget(targetPath) {
|
|
|
1628
2580
|
}
|
|
1629
2581
|
|
|
1630
2582
|
// src/asset-targets/recommend.ts
|
|
1631
|
-
import { existsSync as
|
|
1632
|
-
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";
|
|
1633
2585
|
var frontendPackages = /* @__PURE__ */ new Set([
|
|
1634
2586
|
"@vitejs/plugin-react",
|
|
1635
2587
|
"astro",
|
|
@@ -1643,21 +2595,21 @@ var frontendPackages = /* @__PURE__ */ new Set([
|
|
|
1643
2595
|
]);
|
|
1644
2596
|
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
|
|
1645
2597
|
function discoverTargetSignals(targetDir) {
|
|
1646
|
-
const packageJson = readJson(
|
|
2598
|
+
const packageJson = readJson(join11(targetDir, "package.json"));
|
|
1647
2599
|
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
1648
2600
|
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
1649
|
-
const hasAgentEntry = agentEntryCandidates.some((file) =>
|
|
2601
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync11(join11(targetDir, file)));
|
|
1650
2602
|
const researchSignals = [
|
|
1651
|
-
|
|
1652
|
-
|
|
2603
|
+
existsSync11(join11(targetDir, "docs/research")) ? "docs/research" : "",
|
|
2604
|
+
existsSync11(join11(targetDir, "research")) ? "research" : "",
|
|
1653
2605
|
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
1654
|
-
textFileIncludes(
|
|
2606
|
+
textFileIncludes(join11(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
1655
2607
|
].filter(Boolean);
|
|
1656
2608
|
const writingSignals = [
|
|
1657
|
-
|
|
1658
|
-
|
|
2609
|
+
existsSync11(join11(targetDir, "chapters")) ? "chapters" : "",
|
|
2610
|
+
existsSync11(join11(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
1659
2611
|
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
1660
|
-
textFileIncludes(
|
|
2612
|
+
textFileIncludes(join11(targetDir, "AGENTS.md"), [
|
|
1661
2613
|
"writing mode",
|
|
1662
2614
|
"novel chapter",
|
|
1663
2615
|
"book content"
|
|
@@ -1702,24 +2654,24 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
1702
2654
|
return recommendations;
|
|
1703
2655
|
}
|
|
1704
2656
|
function readJson(path) {
|
|
1705
|
-
if (!
|
|
2657
|
+
if (!existsSync11(path)) return void 0;
|
|
1706
2658
|
try {
|
|
1707
|
-
return JSON.parse(
|
|
2659
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1708
2660
|
} catch {
|
|
1709
2661
|
return void 0;
|
|
1710
2662
|
}
|
|
1711
2663
|
}
|
|
1712
2664
|
function textFileIncludes(path, needles) {
|
|
1713
|
-
if (!
|
|
1714
|
-
const contents =
|
|
2665
|
+
if (!existsSync11(path)) return false;
|
|
2666
|
+
const contents = readFileSync7(path, "utf8").toLowerCase();
|
|
1715
2667
|
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
1716
2668
|
}
|
|
1717
2669
|
function hasBookChildDirectory(targetDir, childName) {
|
|
1718
|
-
const booksDir =
|
|
1719
|
-
if (!
|
|
2670
|
+
const booksDir = join11(targetDir, "books");
|
|
2671
|
+
if (!existsSync11(booksDir)) return false;
|
|
1720
2672
|
try {
|
|
1721
|
-
return
|
|
1722
|
-
(entry) => entry.isDirectory() &&
|
|
2673
|
+
return readdirSync7(booksDir, { withFileTypes: true }).some(
|
|
2674
|
+
(entry) => entry.isDirectory() && existsSync11(join11(booksDir, entry.name, childName))
|
|
1723
2675
|
);
|
|
1724
2676
|
} catch {
|
|
1725
2677
|
return false;
|
|
@@ -1753,9 +2705,67 @@ function runAssets(args) {
|
|
|
1753
2705
|
if (subcommand2 === "npx") {
|
|
1754
2706
|
return runAssetsNpx(rest);
|
|
1755
2707
|
}
|
|
2708
|
+
if (subcommand2 === "catalog") {
|
|
2709
|
+
return runAssetsCatalog(rest);
|
|
2710
|
+
}
|
|
1756
2711
|
printUsage();
|
|
1757
2712
|
return 1;
|
|
1758
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
|
+
}
|
|
1759
2769
|
function runAssetsPublicCheck(args) {
|
|
1760
2770
|
const options = parsePublicCheckOptions(args);
|
|
1761
2771
|
if (!options.ok) {
|
|
@@ -1835,7 +2845,7 @@ function runAssetsApply(args) {
|
|
|
1835
2845
|
return 1;
|
|
1836
2846
|
}
|
|
1837
2847
|
try {
|
|
1838
|
-
const plan = JSON.parse(
|
|
2848
|
+
const plan = JSON.parse(readFileSync8(options.value.planPath, "utf8"));
|
|
1839
2849
|
const result = applyAssetInstallPlan(plan);
|
|
1840
2850
|
console.log(`applied-actions: ${result.appliedActions.length}`);
|
|
1841
2851
|
return 0;
|
|
@@ -1960,8 +2970,8 @@ function runAssetsPlan(args) {
|
|
|
1960
2970
|
console.log("dry-run: true");
|
|
1961
2971
|
}
|
|
1962
2972
|
if (options.value.outPath) {
|
|
1963
|
-
|
|
1964
|
-
|
|
2973
|
+
mkdirSync4(dirname6(options.value.outPath), { recursive: true });
|
|
2974
|
+
writeFileSync3(options.value.outPath, `${JSON.stringify(plan, null, 2)}
|
|
1965
2975
|
`);
|
|
1966
2976
|
if (!options.value.json) {
|
|
1967
2977
|
console.log(`plan: ${options.value.outPath}`);
|
|
@@ -1973,6 +2983,33 @@ function runAssetsPlan(args) {
|
|
|
1973
2983
|
return 1;
|
|
1974
2984
|
}
|
|
1975
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
|
+
}
|
|
1976
3013
|
function parseListOptions(args) {
|
|
1977
3014
|
const options = {
|
|
1978
3015
|
json: false,
|
|
@@ -2159,10 +3196,10 @@ function parsePublicCheckOptions(args) {
|
|
|
2159
3196
|
return { ok: false, error: `Unknown assets public-check option: ${arg}` };
|
|
2160
3197
|
}
|
|
2161
3198
|
}
|
|
2162
|
-
if (!
|
|
3199
|
+
if (!existsSync12(options.publicRoot)) {
|
|
2163
3200
|
return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
|
|
2164
3201
|
}
|
|
2165
|
-
if (!
|
|
3202
|
+
if (!existsSync12(options.privateRoot)) {
|
|
2166
3203
|
return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
|
|
2167
3204
|
}
|
|
2168
3205
|
return { ok: true, value: options };
|
|
@@ -2171,13 +3208,13 @@ function getDefaultPublicCheckRoots() {
|
|
|
2171
3208
|
const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
|
|
2172
3209
|
if (defaultRegistryRoot.endsWith("public-agent-assets")) {
|
|
2173
3210
|
return {
|
|
2174
|
-
privateRoot:
|
|
3211
|
+
privateRoot: join12(dirname6(defaultRegistryRoot), "agent-assets"),
|
|
2175
3212
|
publicRoot: defaultRegistryRoot
|
|
2176
3213
|
};
|
|
2177
3214
|
}
|
|
2178
3215
|
return {
|
|
2179
3216
|
privateRoot: defaultRegistryRoot,
|
|
2180
|
-
publicRoot:
|
|
3217
|
+
publicRoot: join12(dirname6(defaultRegistryRoot), "public-agent-assets")
|
|
2181
3218
|
};
|
|
2182
3219
|
}
|
|
2183
3220
|
function listRegistryAssets(options) {
|
|
@@ -2238,6 +3275,9 @@ function printUsage() {
|
|
|
2238
3275
|
);
|
|
2239
3276
|
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
2240
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
|
+
);
|
|
2241
3281
|
}
|
|
2242
3282
|
function printNpxUsage() {
|
|
2243
3283
|
console.log("Usage: pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
@@ -2245,11 +3285,19 @@ function printNpxUsage() {
|
|
|
2245
3285
|
console.log("");
|
|
2246
3286
|
console.log("Runs npx skills only in a temporary copy and prints a reviewable plan.");
|
|
2247
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
|
+
}
|
|
2248
3296
|
|
|
2249
3297
|
// src/commands/doctor.ts
|
|
2250
|
-
import { existsSync as
|
|
3298
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2251
3299
|
import { createRequire } from "node:module";
|
|
2252
|
-
import { dirname as
|
|
3300
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
2253
3301
|
var REQUIRED_ASSETS = [
|
|
2254
3302
|
"starter/.agents/skills/.gitkeep",
|
|
2255
3303
|
"starter/AGENTS.template.md",
|
|
@@ -2314,16 +3362,16 @@ function resolveDocGovDependencyCli() {
|
|
|
2314
3362
|
try {
|
|
2315
3363
|
const require2 = createRequire(import.meta.url);
|
|
2316
3364
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
2317
|
-
const cliPath =
|
|
2318
|
-
return
|
|
3365
|
+
const cliPath = join13(dirname7(packageJsonPath), "dist/cli.js");
|
|
3366
|
+
return existsSync13(cliPath) ? cliPath : null;
|
|
2319
3367
|
} catch {
|
|
2320
3368
|
return null;
|
|
2321
3369
|
}
|
|
2322
3370
|
}
|
|
2323
3371
|
|
|
2324
3372
|
// src/commands/init.ts
|
|
2325
|
-
import { lstatSync as
|
|
2326
|
-
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";
|
|
2327
3375
|
|
|
2328
3376
|
// src/commands/shared.ts
|
|
2329
3377
|
function planStarterFiles(profile) {
|
|
@@ -2410,7 +3458,7 @@ function runInit(args) {
|
|
|
2410
3458
|
function applyStarterFiles(files, profile) {
|
|
2411
3459
|
const root = process.cwd();
|
|
2412
3460
|
const conflicts = files.filter((file) => {
|
|
2413
|
-
const targetPath =
|
|
3461
|
+
const targetPath = join14(root, file.targetPath);
|
|
2414
3462
|
const stat = safeLstat(targetPath);
|
|
2415
3463
|
if (!stat) return false;
|
|
2416
3464
|
return file.kind !== "directory" || !stat.isDirectory();
|
|
@@ -2422,19 +3470,19 @@ function applyStarterFiles(files, profile) {
|
|
|
2422
3470
|
return 1;
|
|
2423
3471
|
}
|
|
2424
3472
|
for (const file of files) {
|
|
2425
|
-
const targetPath =
|
|
2426
|
-
|
|
3473
|
+
const targetPath = join14(root, file.targetPath);
|
|
3474
|
+
mkdirSync5(dirname8(targetPath), { recursive: true });
|
|
2427
3475
|
if (file.kind === "directory") {
|
|
2428
|
-
|
|
3476
|
+
mkdirSync5(targetPath, { recursive: true });
|
|
2429
3477
|
continue;
|
|
2430
3478
|
}
|
|
2431
3479
|
if (file.kind === "symlink") {
|
|
2432
|
-
|
|
3480
|
+
symlinkSync3(file.linkTarget, targetPath);
|
|
2433
3481
|
continue;
|
|
2434
3482
|
}
|
|
2435
|
-
const source =
|
|
2436
|
-
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"),
|
|
2437
|
-
|
|
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);
|
|
2438
3486
|
}
|
|
2439
3487
|
console.log("pro-gov init APPLIED");
|
|
2440
3488
|
console.log(`profile: ${profile}`);
|
|
@@ -2451,7 +3499,7 @@ function renderAgentsTemplate(template, projectName, profile) {
|
|
|
2451
3499
|
}
|
|
2452
3500
|
function safeLstat(path) {
|
|
2453
3501
|
try {
|
|
2454
|
-
return
|
|
3502
|
+
return lstatSync6(path);
|
|
2455
3503
|
} catch {
|
|
2456
3504
|
return void 0;
|
|
2457
3505
|
}
|
|
@@ -2465,22 +3513,22 @@ function readFlag(args, flag) {
|
|
|
2465
3513
|
}
|
|
2466
3514
|
|
|
2467
3515
|
// src/commands/host-lens.ts
|
|
2468
|
-
import { mkdirSync as
|
|
2469
|
-
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";
|
|
2470
3518
|
|
|
2471
3519
|
// src/host-lens.ts
|
|
2472
3520
|
import { execFileSync } from "node:child_process";
|
|
2473
3521
|
import {
|
|
2474
3522
|
cpSync as cpSync2,
|
|
2475
|
-
existsSync as
|
|
2476
|
-
lstatSync as
|
|
2477
|
-
mkdirSync as
|
|
2478
|
-
readdirSync as
|
|
2479
|
-
statSync as
|
|
2480
|
-
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
|
|
2481
3529
|
} from "node:fs";
|
|
2482
3530
|
import { homedir } from "node:os";
|
|
2483
|
-
import { dirname as
|
|
3531
|
+
import { dirname as dirname9, join as join15, relative as relative7, resolve as resolve4 } from "node:path";
|
|
2484
3532
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2485
3533
|
var ROOT_DEFINITIONS = [
|
|
2486
3534
|
{
|
|
@@ -2562,9 +3610,9 @@ var ROOT_DEFINITIONS = [
|
|
|
2562
3610
|
}
|
|
2563
3611
|
];
|
|
2564
3612
|
function inspectHost(options = {}) {
|
|
2565
|
-
const homePath =
|
|
3613
|
+
const homePath = resolve4(options.homeDir ?? homedir());
|
|
2566
3614
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
2567
|
-
const rootPaths = ROOT_DEFINITIONS.map((definition) =>
|
|
3615
|
+
const rootPaths = ROOT_DEFINITIONS.map((definition) => join15(homePath, definition.relativePath));
|
|
2568
3616
|
const rootSizes = measurePaths(rootPaths);
|
|
2569
3617
|
const findings = inspectFindings(homePath);
|
|
2570
3618
|
const rootFindingIds = /* @__PURE__ */ new Map();
|
|
@@ -2581,7 +3629,7 @@ function inspectHost(options = {}) {
|
|
|
2581
3629
|
label: definition.label,
|
|
2582
3630
|
path: displayPath(path, homePath),
|
|
2583
3631
|
kind: definition.kind,
|
|
2584
|
-
exists:
|
|
3632
|
+
exists: existsSync14(path),
|
|
2585
3633
|
bytes: rootSizes.get(path) ?? 0,
|
|
2586
3634
|
status: rootFindings.length > 0 ? "attention" : "healthy",
|
|
2587
3635
|
note: definition.note
|
|
@@ -2688,25 +3736,25 @@ function createHostLensCleanupPlan(report) {
|
|
|
2688
3736
|
};
|
|
2689
3737
|
}
|
|
2690
3738
|
function writeHostLensReport(report, outDir) {
|
|
2691
|
-
|
|
3739
|
+
mkdirSync6(outDir, { recursive: true });
|
|
2692
3740
|
const assets = findHostDashboardAssets();
|
|
2693
3741
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
2694
|
-
const source =
|
|
2695
|
-
if (!
|
|
2696
|
-
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));
|
|
2697
3745
|
}
|
|
2698
|
-
const jsonPath =
|
|
2699
|
-
const htmlPath =
|
|
2700
|
-
|
|
3746
|
+
const jsonPath = join15(outDir, "host-lens.json");
|
|
3747
|
+
const htmlPath = join15(outDir, "index.html");
|
|
3748
|
+
writeFileSync5(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
2701
3749
|
`);
|
|
2702
|
-
|
|
3750
|
+
writeFileSync5(join15(outDir, "data.js"), `window.__HOST_LENS__ = ${safeJavaScriptJson(report)};
|
|
2703
3751
|
`);
|
|
2704
3752
|
return { jsonPath, htmlPath };
|
|
2705
3753
|
}
|
|
2706
3754
|
function inspectFindings(homePath) {
|
|
2707
3755
|
const candidates = [];
|
|
2708
3756
|
const addGroup = (value) => {
|
|
2709
|
-
const paths = value.paths.filter((path) =>
|
|
3757
|
+
const paths = value.paths.filter((path) => existsSync14(path));
|
|
2710
3758
|
if (paths.length === 0) return;
|
|
2711
3759
|
candidates.push({ ...value, paths: paths.map((path) => displayPath(path, homePath)) });
|
|
2712
3760
|
};
|
|
@@ -2714,7 +3762,7 @@ function inspectFindings(homePath) {
|
|
|
2714
3762
|
id: "codex-backups",
|
|
2715
3763
|
rootId: "codex",
|
|
2716
3764
|
label: "Codex \u5386\u53F2\u5907\u4EFD",
|
|
2717
|
-
paths: matchingChildren(
|
|
3765
|
+
paths: matchingChildren(join15(homePath, ".codex"), (name) => /^backup-/i.test(name)),
|
|
2718
3766
|
category: "backup",
|
|
2719
3767
|
confidence: "medium",
|
|
2720
3768
|
disposition: "manual-review",
|
|
@@ -2725,7 +3773,7 @@ function inspectFindings(homePath) {
|
|
|
2725
3773
|
id: "codex-cache",
|
|
2726
3774
|
rootId: "codex",
|
|
2727
3775
|
label: "Codex \u53EF\u518D\u751F\u6210\u7F13\u5B58",
|
|
2728
|
-
paths: [
|
|
3776
|
+
paths: [join15(homePath, ".codex/cache")],
|
|
2729
3777
|
category: "cache",
|
|
2730
3778
|
confidence: "high",
|
|
2731
3779
|
disposition: "report-only",
|
|
@@ -2736,7 +3784,7 @@ function inspectFindings(homePath) {
|
|
|
2736
3784
|
id: "claude-temporary",
|
|
2737
3785
|
rootId: "claude-code",
|
|
2738
3786
|
label: "Claude Code \u4E34\u65F6\u76EE\u5F55",
|
|
2739
|
-
paths: matchingChildren(
|
|
3787
|
+
paths: matchingChildren(join15(homePath, ".claude"), (name) => /^(temp|tmp)[-_]/i.test(name)),
|
|
2740
3788
|
category: "temporary",
|
|
2741
3789
|
confidence: "high",
|
|
2742
3790
|
disposition: "manual-review",
|
|
@@ -2747,7 +3795,7 @@ function inspectFindings(homePath) {
|
|
|
2747
3795
|
id: "claude-cache",
|
|
2748
3796
|
rootId: "claude-code",
|
|
2749
3797
|
label: "Claude Code \u7F13\u5B58",
|
|
2750
|
-
paths: [
|
|
3798
|
+
paths: [join15(homePath, ".claude/cache")],
|
|
2751
3799
|
category: "cache",
|
|
2752
3800
|
confidence: "high",
|
|
2753
3801
|
disposition: "report-only",
|
|
@@ -2758,7 +3806,7 @@ function inspectFindings(homePath) {
|
|
|
2758
3806
|
id: "copilot-session-history",
|
|
2759
3807
|
rootId: "copilot",
|
|
2760
3808
|
label: "Copilot \u4F1A\u8BDD\u72B6\u6001",
|
|
2761
|
-
paths: [
|
|
3809
|
+
paths: [join15(homePath, ".copilot/session-state")],
|
|
2762
3810
|
category: "session-history",
|
|
2763
3811
|
confidence: "low",
|
|
2764
3812
|
disposition: "manual-review",
|
|
@@ -2769,7 +3817,7 @@ function inspectFindings(homePath) {
|
|
|
2769
3817
|
id: "npm-npx-cache",
|
|
2770
3818
|
rootId: "npm-cache",
|
|
2771
3819
|
label: "npx \u4E34\u65F6\u5B89\u88C5\u7F13\u5B58",
|
|
2772
|
-
paths: [
|
|
3820
|
+
paths: [join15(homePath, ".npm/_npx")],
|
|
2773
3821
|
category: "cache",
|
|
2774
3822
|
confidence: "high",
|
|
2775
3823
|
disposition: "native-tool",
|
|
@@ -2781,7 +3829,7 @@ function inspectFindings(homePath) {
|
|
|
2781
3829
|
id: "npm-content-cache",
|
|
2782
3830
|
rootId: "npm-cache",
|
|
2783
3831
|
label: "npm \u5185\u5BB9\u7F13\u5B58",
|
|
2784
|
-
paths: [
|
|
3832
|
+
paths: [join15(homePath, ".npm/_cacache")],
|
|
2785
3833
|
category: "cache",
|
|
2786
3834
|
confidence: "high",
|
|
2787
3835
|
disposition: "native-tool",
|
|
@@ -2794,7 +3842,7 @@ function inspectFindings(homePath) {
|
|
|
2794
3842
|
rootId: "pnpm-store",
|
|
2795
3843
|
label: "pnpm \u672A\u5F15\u7528\u5305\u5019\u9009",
|
|
2796
3844
|
paths: [
|
|
2797
|
-
|
|
3845
|
+
join15(
|
|
2798
3846
|
homePath,
|
|
2799
3847
|
process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store"
|
|
2800
3848
|
)
|
|
@@ -2811,11 +3859,11 @@ function inspectFindings(homePath) {
|
|
|
2811
3859
|
rootId: "playwright",
|
|
2812
3860
|
label: "Playwright \u6D4F\u89C8\u5668\u7248\u672C",
|
|
2813
3861
|
paths: [
|
|
2814
|
-
|
|
3862
|
+
join15(
|
|
2815
3863
|
homePath,
|
|
2816
3864
|
process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright"
|
|
2817
3865
|
),
|
|
2818
|
-
|
|
3866
|
+
join15(homePath, ".cache/ms-playwright")
|
|
2819
3867
|
],
|
|
2820
3868
|
category: "duplicate-runtime",
|
|
2821
3869
|
confidence: "medium",
|
|
@@ -2867,16 +3915,16 @@ function buildProtections(homePath) {
|
|
|
2867
3915
|
}));
|
|
2868
3916
|
}
|
|
2869
3917
|
function matchingChildren(root, predicate) {
|
|
2870
|
-
if (!
|
|
3918
|
+
if (!existsSync14(root)) return [];
|
|
2871
3919
|
try {
|
|
2872
|
-
return
|
|
3920
|
+
return readdirSync8(root, { withFileTypes: true }).filter((entry) => predicate(entry.name)).map((entry) => join15(root, entry.name));
|
|
2873
3921
|
} catch {
|
|
2874
3922
|
return [];
|
|
2875
3923
|
}
|
|
2876
3924
|
}
|
|
2877
3925
|
function measurePaths(paths) {
|
|
2878
|
-
const uniquePaths = [...new Set(paths.map((path) =>
|
|
2879
|
-
(path) =>
|
|
3926
|
+
const uniquePaths = [...new Set(paths.map((path) => resolve4(path)))].filter(
|
|
3927
|
+
(path) => existsSync14(path)
|
|
2880
3928
|
);
|
|
2881
3929
|
const result = /* @__PURE__ */ new Map();
|
|
2882
3930
|
if (uniquePaths.length === 0) return result;
|
|
@@ -2889,7 +3937,7 @@ function measurePaths(paths) {
|
|
|
2889
3937
|
for (const line of output.split(/\r?\n/)) {
|
|
2890
3938
|
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
2891
3939
|
if (!match) continue;
|
|
2892
|
-
result.set(
|
|
3940
|
+
result.set(resolve4(match[2]), Number(match[1]) * 1024);
|
|
2893
3941
|
}
|
|
2894
3942
|
return result;
|
|
2895
3943
|
} catch {
|
|
@@ -2899,7 +3947,7 @@ function measurePaths(paths) {
|
|
|
2899
3947
|
}
|
|
2900
3948
|
function fallbackMeasure(root) {
|
|
2901
3949
|
try {
|
|
2902
|
-
const rootStats =
|
|
3950
|
+
const rootStats = lstatSync7(root);
|
|
2903
3951
|
if (!rootStats.isDirectory()) return rootStats.size;
|
|
2904
3952
|
} catch {
|
|
2905
3953
|
return 0;
|
|
@@ -2912,17 +3960,17 @@ function fallbackMeasure(root) {
|
|
|
2912
3960
|
if (!current) continue;
|
|
2913
3961
|
let entries;
|
|
2914
3962
|
try {
|
|
2915
|
-
entries =
|
|
3963
|
+
entries = readdirSync8(current, { withFileTypes: true });
|
|
2916
3964
|
} catch {
|
|
2917
3965
|
continue;
|
|
2918
3966
|
}
|
|
2919
3967
|
for (const entry of entries) {
|
|
2920
3968
|
visited += 1;
|
|
2921
|
-
const path =
|
|
3969
|
+
const path = join15(current, entry.name);
|
|
2922
3970
|
if (entry.isDirectory()) pending.push(path);
|
|
2923
3971
|
else if (entry.isFile()) {
|
|
2924
3972
|
try {
|
|
2925
|
-
bytes +=
|
|
3973
|
+
bytes += statSync5(path).size;
|
|
2926
3974
|
} catch {
|
|
2927
3975
|
}
|
|
2928
3976
|
}
|
|
@@ -2933,35 +3981,35 @@ function fallbackMeasure(root) {
|
|
|
2933
3981
|
}
|
|
2934
3982
|
function countImmediateEntries(path) {
|
|
2935
3983
|
try {
|
|
2936
|
-
return
|
|
3984
|
+
return lstatSync7(path).isDirectory() ? readdirSync8(path).length : 1;
|
|
2937
3985
|
} catch {
|
|
2938
3986
|
return 0;
|
|
2939
3987
|
}
|
|
2940
3988
|
}
|
|
2941
3989
|
function displayPath(path, homePath) {
|
|
2942
|
-
const absolute =
|
|
2943
|
-
const withinHome =
|
|
3990
|
+
const absolute = resolve4(path);
|
|
3991
|
+
const withinHome = relative7(homePath, absolute);
|
|
2944
3992
|
if (withinHome === "") return "~";
|
|
2945
3993
|
if (!withinHome.startsWith("..")) return `~/${withinHome.replaceAll("\\", "/")}`;
|
|
2946
3994
|
return absolute;
|
|
2947
3995
|
}
|
|
2948
3996
|
function undisplayPath(path, homePath) {
|
|
2949
3997
|
if (path === "~") return homePath;
|
|
2950
|
-
if (path.startsWith("~/")) return
|
|
2951
|
-
return
|
|
3998
|
+
if (path.startsWith("~/")) return join15(homePath, path.slice(2));
|
|
3999
|
+
return resolve4(path);
|
|
2952
4000
|
}
|
|
2953
4001
|
function findHostDashboardAssets() {
|
|
2954
|
-
const packageRoot2 =
|
|
4002
|
+
const packageRoot2 = dirname9(dirname9(fileURLToPath3(import.meta.url)));
|
|
2955
4003
|
const candidates = [
|
|
2956
4004
|
process.env.PGS_HOST_DASHBOARD_ASSETS_DIR,
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
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")
|
|
2963
4011
|
].filter((value) => Boolean(value));
|
|
2964
|
-
const match = candidates.find((path) =>
|
|
4012
|
+
const match = candidates.find((path) => existsSync14(join15(path, "index.html")));
|
|
2965
4013
|
if (!match)
|
|
2966
4014
|
throw new Error(
|
|
2967
4015
|
"HostLens dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -2973,12 +4021,12 @@ function safeJavaScriptJson(value) {
|
|
|
2973
4021
|
}
|
|
2974
4022
|
|
|
2975
4023
|
// src/portfolio/manifest.ts
|
|
2976
|
-
import { existsSync as
|
|
2977
|
-
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";
|
|
2978
4026
|
function loadPortfolioManifest(configPath) {
|
|
2979
4027
|
let parsed;
|
|
2980
4028
|
try {
|
|
2981
|
-
parsed = JSON.parse(
|
|
4029
|
+
parsed = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2982
4030
|
} catch (error) {
|
|
2983
4031
|
return {
|
|
2984
4032
|
configPath,
|
|
@@ -2990,7 +4038,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
2990
4038
|
]
|
|
2991
4039
|
};
|
|
2992
4040
|
}
|
|
2993
|
-
const normalized = resolveManifestPaths(parsed,
|
|
4041
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve5(configPath)));
|
|
2994
4042
|
const issues = validatePortfolioManifest(normalized);
|
|
2995
4043
|
return {
|
|
2996
4044
|
configPath,
|
|
@@ -3001,16 +4049,16 @@ function loadPortfolioManifest(configPath) {
|
|
|
3001
4049
|
function resolveManifestPaths(value, configDir) {
|
|
3002
4050
|
if (!isRecord(value)) return value;
|
|
3003
4051
|
const resolveEndpoint = (endpoint) => {
|
|
3004
|
-
if (!isRecord(endpoint) || typeof endpoint.path !== "string" ||
|
|
4052
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute4(endpoint.path)) {
|
|
3005
4053
|
return endpoint;
|
|
3006
4054
|
}
|
|
3007
|
-
return { ...endpoint, path:
|
|
4055
|
+
return { ...endpoint, path: resolve5(configDir, endpoint.path) };
|
|
3008
4056
|
};
|
|
3009
4057
|
return {
|
|
3010
4058
|
...value,
|
|
3011
|
-
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !
|
|
4059
|
+
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !isAbsolute4(value.technologyGovernance.strategySource) ? {
|
|
3012
4060
|
...value.technologyGovernance,
|
|
3013
|
-
strategySource:
|
|
4061
|
+
strategySource: resolve5(configDir, value.technologyGovernance.strategySource)
|
|
3014
4062
|
} : value.technologyGovernance,
|
|
3015
4063
|
controlPlane: resolveEndpoint(value.controlPlane),
|
|
3016
4064
|
executionEngine: resolveEndpoint(value.executionEngine),
|
|
@@ -3195,7 +4243,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
|
3195
4243
|
});
|
|
3196
4244
|
return;
|
|
3197
4245
|
}
|
|
3198
|
-
if (!
|
|
4246
|
+
if (!existsSync15(value.path)) {
|
|
3199
4247
|
issues.push({
|
|
3200
4248
|
type: "missing-path",
|
|
3201
4249
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3626,13 +4674,13 @@ function isExactVersion(value) {
|
|
|
3626
4674
|
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
|
|
3627
4675
|
}
|
|
3628
4676
|
function isRepositoryRelativePath(value) {
|
|
3629
|
-
if (value.length === 0 ||
|
|
4677
|
+
if (value.length === 0 || isAbsolute4(value)) return false;
|
|
3630
4678
|
const segments = value.replaceAll("\\", "/").split("/");
|
|
3631
4679
|
return !segments.includes("..");
|
|
3632
4680
|
}
|
|
3633
4681
|
function isExactRepositoryRelativePath(value) {
|
|
3634
4682
|
if (value.length === 0) return false;
|
|
3635
|
-
if (
|
|
4683
|
+
if (isAbsolute4(value)) return false;
|
|
3636
4684
|
if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\") || value.startsWith("//"))
|
|
3637
4685
|
return false;
|
|
3638
4686
|
if (value.includes("\\")) return false;
|
|
@@ -3930,7 +4978,7 @@ function runScan(options) {
|
|
|
3930
4978
|
const inspection = resolveInspectionOptions(options);
|
|
3931
4979
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3932
4980
|
const report = inspectHost(inspection.value);
|
|
3933
|
-
const written = writeHostLensReport(report,
|
|
4981
|
+
const written = writeHostLensReport(report, resolve6(options.outPath));
|
|
3934
4982
|
if (options.json) {
|
|
3935
4983
|
console.log(JSON.stringify({ ok: true, ...written, summary: report.summary }, null, 2));
|
|
3936
4984
|
} else {
|
|
@@ -3950,9 +4998,9 @@ function runPlan(options) {
|
|
|
3950
4998
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3951
4999
|
const report = inspectHost(inspection.value);
|
|
3952
5000
|
const plan = createHostLensCleanupPlan(report);
|
|
3953
|
-
const outPath =
|
|
3954
|
-
|
|
3955
|
-
|
|
5001
|
+
const outPath = resolve6(options.outPath);
|
|
5002
|
+
mkdirSync7(dirname11(outPath), { recursive: true });
|
|
5003
|
+
writeFileSync6(outPath, `${JSON.stringify(plan, null, 2)}
|
|
3956
5004
|
`);
|
|
3957
5005
|
if (options.json)
|
|
3958
5006
|
console.log(JSON.stringify({ ok: true, outPath, actions: plan.actions.length }, null, 2));
|
|
@@ -4025,7 +5073,7 @@ function parseOptions(args) {
|
|
|
4025
5073
|
if (arg === "--home") {
|
|
4026
5074
|
const value = args[index + 1];
|
|
4027
5075
|
if (!value) return { ok: false, error: "Expected value after --home" };
|
|
4028
|
-
options.homeDir =
|
|
5076
|
+
options.homeDir = resolve6(value);
|
|
4029
5077
|
index += 1;
|
|
4030
5078
|
continue;
|
|
4031
5079
|
}
|
|
@@ -4039,7 +5087,7 @@ function parseOptions(args) {
|
|
|
4039
5087
|
if (arg === "--config") {
|
|
4040
5088
|
const value = args[index + 1];
|
|
4041
5089
|
if (!value) return { ok: false, error: "Expected value after --config" };
|
|
4042
|
-
options.configPath =
|
|
5090
|
+
options.configPath = resolve6(value);
|
|
4043
5091
|
index += 1;
|
|
4044
5092
|
continue;
|
|
4045
5093
|
}
|
|
@@ -4065,8 +5113,8 @@ function printUsage2() {
|
|
|
4065
5113
|
}
|
|
4066
5114
|
|
|
4067
5115
|
// src/learning/recall.ts
|
|
4068
|
-
import { existsSync as
|
|
4069
|
-
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";
|
|
4070
5118
|
function recallLearnings(root, options) {
|
|
4071
5119
|
const query = options.query.trim();
|
|
4072
5120
|
const terms = tokenize(query);
|
|
@@ -4089,16 +5137,16 @@ function recallLearnings(root, options) {
|
|
|
4089
5137
|
function loadLearningRecords(root) {
|
|
4090
5138
|
const recordsByTitle = /* @__PURE__ */ new Map();
|
|
4091
5139
|
for (const relativeDir of ["docs/reference/learnings", "docs/solutions"]) {
|
|
4092
|
-
const learningDir =
|
|
4093
|
-
if (!
|
|
5140
|
+
const learningDir = join16(root, relativeDir);
|
|
5141
|
+
if (!existsSync16(learningDir)) continue;
|
|
4094
5142
|
for (const path of listMarkdownFiles(learningDir)) {
|
|
4095
5143
|
const record = readLearningRecord(root, path);
|
|
4096
5144
|
const key = record.title.trim().toLowerCase();
|
|
4097
5145
|
if (!recordsByTitle.has(key)) recordsByTitle.set(key, record);
|
|
4098
5146
|
}
|
|
4099
5147
|
}
|
|
4100
|
-
const conceptsPath =
|
|
4101
|
-
if (
|
|
5148
|
+
const conceptsPath = join16(root, "CONCEPTS.md");
|
|
5149
|
+
if (existsSync16(conceptsPath)) {
|
|
4102
5150
|
const record = readLearningRecord(root, conceptsPath);
|
|
4103
5151
|
recordsByTitle.set(`concepts:${record.title.toLowerCase()}`, record);
|
|
4104
5152
|
}
|
|
@@ -4106,8 +5154,8 @@ function loadLearningRecords(root) {
|
|
|
4106
5154
|
}
|
|
4107
5155
|
function listMarkdownFiles(dir) {
|
|
4108
5156
|
const files = [];
|
|
4109
|
-
for (const entry of
|
|
4110
|
-
const absolutePath =
|
|
5157
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
5158
|
+
const absolutePath = join16(dir, entry.name);
|
|
4111
5159
|
if (entry.isDirectory()) {
|
|
4112
5160
|
files.push(...listMarkdownFiles(absolutePath));
|
|
4113
5161
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -4117,11 +5165,11 @@ function listMarkdownFiles(dir) {
|
|
|
4117
5165
|
return files.sort();
|
|
4118
5166
|
}
|
|
4119
5167
|
function readLearningRecord(root, absolutePath) {
|
|
4120
|
-
const content =
|
|
5168
|
+
const content = readFileSync11(absolutePath, "utf8");
|
|
4121
5169
|
const parsed = splitFrontmatter(content);
|
|
4122
5170
|
const body = parsed.body;
|
|
4123
5171
|
return {
|
|
4124
|
-
relativePath: normalizePath(
|
|
5172
|
+
relativePath: normalizePath(relative8(root, absolutePath)),
|
|
4125
5173
|
title: findTitle(parsed.frontmatter, body) ?? titleFromPath(absolutePath),
|
|
4126
5174
|
metadata: parsed.frontmatter,
|
|
4127
5175
|
body
|
|
@@ -4149,7 +5197,7 @@ function findTitle(frontmatter, body) {
|
|
|
4149
5197
|
return heading ? heading.slice(2).trim() : void 0;
|
|
4150
5198
|
}
|
|
4151
5199
|
function titleFromPath(path) {
|
|
4152
|
-
return
|
|
5200
|
+
return basename4(path, ".md").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
4153
5201
|
}
|
|
4154
5202
|
function scoreRecord(record, terms) {
|
|
4155
5203
|
const title = record.title.toLowerCase();
|
|
@@ -4202,8 +5250,8 @@ function cleanMarkdownLine(input) {
|
|
|
4202
5250
|
}
|
|
4203
5251
|
|
|
4204
5252
|
// src/learning/capture.ts
|
|
4205
|
-
import { existsSync as
|
|
4206
|
-
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";
|
|
4207
5255
|
function captureLearning(root, options) {
|
|
4208
5256
|
const title = options.title.trim();
|
|
4209
5257
|
const summary = options.summary.trim();
|
|
@@ -4211,13 +5259,13 @@ function captureLearning(root, options) {
|
|
|
4211
5259
|
if (!summary) throw new Error("summary is required");
|
|
4212
5260
|
const category = slugify(options.category ?? "workflow-issues") || "workflow-issues";
|
|
4213
5261
|
const moduleName = options.module?.trim() || "PGS learning capture";
|
|
4214
|
-
const dir =
|
|
4215
|
-
|
|
5262
|
+
const dir = join17(root, "docs/reference/learnings", category);
|
|
5263
|
+
mkdirSync8(dir, { recursive: true });
|
|
4216
5264
|
const path = uniquePath(dir, slugify(title) || "learning");
|
|
4217
|
-
const idSlug =
|
|
4218
|
-
|
|
5265
|
+
const idSlug = basename5(path, ".md");
|
|
5266
|
+
writeFileSync7(path, renderLearning({ title, summary, category, moduleName, idSlug }));
|
|
4219
5267
|
return {
|
|
4220
|
-
relativePath: normalizePath2(
|
|
5268
|
+
relativePath: normalizePath2(relative9(root, path)),
|
|
4221
5269
|
title,
|
|
4222
5270
|
captureMode: "pgs-native"
|
|
4223
5271
|
};
|
|
@@ -4259,10 +5307,10 @@ function renderLearning(options) {
|
|
|
4259
5307
|
}
|
|
4260
5308
|
function uniquePath(dir, slug) {
|
|
4261
5309
|
let index = 1;
|
|
4262
|
-
let candidate =
|
|
4263
|
-
while (
|
|
5310
|
+
let candidate = join17(dir, `${slug}.md`);
|
|
5311
|
+
while (existsSync17(candidate)) {
|
|
4264
5312
|
index += 1;
|
|
4265
|
-
candidate =
|
|
5313
|
+
candidate = join17(dir, `${slug}-${index}.md`);
|
|
4266
5314
|
}
|
|
4267
5315
|
return candidate;
|
|
4268
5316
|
}
|
|
@@ -4447,12 +5495,12 @@ function printUsage3() {
|
|
|
4447
5495
|
}
|
|
4448
5496
|
|
|
4449
5497
|
// src/commands/lens.ts
|
|
4450
|
-
import { mkdirSync as
|
|
4451
|
-
import { dirname as
|
|
5498
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5499
|
+
import { dirname as dirname14 } from "node:path";
|
|
4452
5500
|
|
|
4453
5501
|
// src/lens/audit.ts
|
|
4454
|
-
import { existsSync as
|
|
4455
|
-
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";
|
|
4456
5504
|
var REQUIRED_ARTIFACTS = [
|
|
4457
5505
|
"manifest.md",
|
|
4458
5506
|
"raw/project-lens/architecture-lens.md",
|
|
@@ -4470,20 +5518,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
4470
5518
|
version: 1,
|
|
4471
5519
|
target: {
|
|
4472
5520
|
path: targetDir,
|
|
4473
|
-
name:
|
|
5521
|
+
name: basename6(targetDir) || "target"
|
|
4474
5522
|
},
|
|
4475
5523
|
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
4476
5524
|
};
|
|
4477
|
-
|
|
4478
|
-
writeJson(
|
|
5525
|
+
mkdirSync9(auditDir, { recursive: true });
|
|
5526
|
+
writeJson(join18(auditDir, "audit.contract.json"), contract);
|
|
4479
5527
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4480
|
-
writeTemplate(
|
|
5528
|
+
writeTemplate(join18(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
4481
5529
|
}
|
|
4482
5530
|
return contract;
|
|
4483
5531
|
}
|
|
4484
5532
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
4485
|
-
const contractPath =
|
|
4486
|
-
if (!
|
|
5533
|
+
const contractPath = join18(auditDir, "audit.contract.json");
|
|
5534
|
+
if (!existsSync18(contractPath)) {
|
|
4487
5535
|
return {
|
|
4488
5536
|
ok: false,
|
|
4489
5537
|
auditDir,
|
|
@@ -4497,7 +5545,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4497
5545
|
}
|
|
4498
5546
|
let contract;
|
|
4499
5547
|
try {
|
|
4500
|
-
contract = JSON.parse(
|
|
5548
|
+
contract = JSON.parse(readFileSync12(contractPath, "utf8"));
|
|
4501
5549
|
} catch (error) {
|
|
4502
5550
|
return {
|
|
4503
5551
|
ok: false,
|
|
@@ -4530,12 +5578,12 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4530
5578
|
}
|
|
4531
5579
|
}
|
|
4532
5580
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4533
|
-
const absolutePath =
|
|
4534
|
-
if (!
|
|
5581
|
+
const absolutePath = join18(auditDir, artifactPath);
|
|
5582
|
+
if (!existsSync18(absolutePath)) {
|
|
4535
5583
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
4536
5584
|
continue;
|
|
4537
5585
|
}
|
|
4538
|
-
const content =
|
|
5586
|
+
const content = readFileSync12(absolutePath, "utf8");
|
|
4539
5587
|
if (isPendingArtifact(content)) {
|
|
4540
5588
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
4541
5589
|
} else if (hasTemplateBody(content)) {
|
|
@@ -4551,13 +5599,13 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4551
5599
|
};
|
|
4552
5600
|
}
|
|
4553
5601
|
function writeJson(path, value) {
|
|
4554
|
-
|
|
4555
|
-
|
|
5602
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5603
|
+
writeFileSync8(path, `${JSON.stringify(value, null, 2)}
|
|
4556
5604
|
`);
|
|
4557
5605
|
}
|
|
4558
5606
|
function writeTemplate(path, content) {
|
|
4559
|
-
|
|
4560
|
-
|
|
5607
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5608
|
+
writeFileSync8(path, content);
|
|
4561
5609
|
}
|
|
4562
5610
|
function renderArtifactTemplate(artifactPath, contract) {
|
|
4563
5611
|
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
@@ -4836,13 +5884,13 @@ function formatLink(link) {
|
|
|
4836
5884
|
|
|
4837
5885
|
// src/lens/scan.ts
|
|
4838
5886
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4839
|
-
import { existsSync as
|
|
5887
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14, statSync as statSync7 } from "node:fs";
|
|
4840
5888
|
import { homedir as homedir3 } from "node:os";
|
|
4841
|
-
import { join as
|
|
5889
|
+
import { join as join23 } from "node:path";
|
|
4842
5890
|
|
|
4843
5891
|
// src/host-ssot.ts
|
|
4844
|
-
import { lstatSync as
|
|
4845
|
-
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";
|
|
4846
5894
|
function inspectProjectHostSsot(root) {
|
|
4847
5895
|
const agentsEntry = inspectCanonicalPath(root, "AGENTS.md");
|
|
4848
5896
|
const canonicalSkills = inspectCanonicalPath(root, ".agents/skills");
|
|
@@ -4886,7 +5934,7 @@ function inspectUserSkillsSsot(homeDir) {
|
|
|
4886
5934
|
};
|
|
4887
5935
|
}
|
|
4888
5936
|
function inspectCanonicalPath(root, path) {
|
|
4889
|
-
const absolutePath =
|
|
5937
|
+
const absolutePath = join19(root, path);
|
|
4890
5938
|
const stat = safeLstat2(absolutePath);
|
|
4891
5939
|
if (!stat) return { path, status: "missing" };
|
|
4892
5940
|
if (stat.isSymbolicLink()) {
|
|
@@ -4902,7 +5950,7 @@ function inspectCanonicalPath(root, path) {
|
|
|
4902
5950
|
return { path, status: "other" };
|
|
4903
5951
|
}
|
|
4904
5952
|
function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
4905
|
-
const absolutePath =
|
|
5953
|
+
const absolutePath = join19(root, path);
|
|
4906
5954
|
const stat = safeLstat2(absolutePath);
|
|
4907
5955
|
const base = { path, expectedRawTarget, compliant: false };
|
|
4908
5956
|
if (!stat) return { ...base, status: "missing" };
|
|
@@ -4912,8 +5960,8 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4912
5960
|
return { ...base, status: "other" };
|
|
4913
5961
|
}
|
|
4914
5962
|
const rawTarget = normalizeSymlinkTarget(readlinkSync3(absolutePath));
|
|
4915
|
-
const resolvedTarget =
|
|
4916
|
-
const expectedPath =
|
|
5963
|
+
const resolvedTarget = resolve7(dirname13(absolutePath), rawTarget);
|
|
5964
|
+
const expectedPath = resolve7(dirname13(absolutePath), expectedRawTarget);
|
|
4917
5965
|
const targetStat = safeLstat2(resolvedTarget);
|
|
4918
5966
|
if (!targetStat) {
|
|
4919
5967
|
return {
|
|
@@ -4932,7 +5980,7 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4932
5980
|
if (!targetMatches) {
|
|
4933
5981
|
return { ...base, rawTarget, resolvedTarget, status: "wrong-target" };
|
|
4934
5982
|
}
|
|
4935
|
-
if (
|
|
5983
|
+
if (isAbsolute5(rawTarget)) {
|
|
4936
5984
|
return { ...base, rawTarget, resolvedTarget, status: "absolute-symlink" };
|
|
4937
5985
|
}
|
|
4938
5986
|
if (rawTarget !== expectedRawTarget) {
|
|
@@ -4953,16 +6001,16 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4953
6001
|
}
|
|
4954
6002
|
function safeLstat2(path) {
|
|
4955
6003
|
try {
|
|
4956
|
-
return
|
|
6004
|
+
return lstatSync8(path);
|
|
4957
6005
|
} catch {
|
|
4958
6006
|
return void 0;
|
|
4959
6007
|
}
|
|
4960
6008
|
}
|
|
4961
6009
|
|
|
4962
6010
|
// src/portfolio/redundancy.ts
|
|
4963
|
-
import { existsSync as
|
|
6011
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync6 } from "node:fs";
|
|
4964
6012
|
import { homedir as homedir2 } from "node:os";
|
|
4965
|
-
import { join as
|
|
6013
|
+
import { join as join20 } from "node:path";
|
|
4966
6014
|
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
4967
6015
|
var MAX_CACHE_ENTRIES = 2e4;
|
|
4968
6016
|
function inspectHostRedundancy(options = {}) {
|
|
@@ -4993,8 +6041,8 @@ function inspectProjectRedundancy(root, options = {}) {
|
|
|
4993
6041
|
}
|
|
4994
6042
|
function inspectLegacyDirectories(root) {
|
|
4995
6043
|
const relativePath = ".agent";
|
|
4996
|
-
const path =
|
|
4997
|
-
if (!
|
|
6044
|
+
const path = join20(root, relativePath);
|
|
6045
|
+
if (!existsSync19(path)) return [];
|
|
4998
6046
|
const stats = collectDirectoryStats(path);
|
|
4999
6047
|
return [
|
|
5000
6048
|
{
|
|
@@ -5009,16 +6057,16 @@ function inspectLegacyDirectories(root) {
|
|
|
5009
6057
|
function getPlaywrightCachePaths(homeDir, configuredPath) {
|
|
5010
6058
|
const candidates = [
|
|
5011
6059
|
configuredPath && configuredPath !== "0" ? configuredPath : void 0,
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
6060
|
+
join20(homeDir, "Library/Caches/ms-playwright"),
|
|
6061
|
+
join20(homeDir, ".cache/ms-playwright"),
|
|
6062
|
+
join20(homeDir, "AppData/Local/ms-playwright")
|
|
5015
6063
|
].filter((path) => Boolean(path));
|
|
5016
6064
|
return [...new Set(candidates)];
|
|
5017
6065
|
}
|
|
5018
6066
|
function inspectPlaywrightCache(path, cache) {
|
|
5019
6067
|
const cached = cache?.get(path);
|
|
5020
6068
|
if (cached) return cached;
|
|
5021
|
-
if (!
|
|
6069
|
+
if (!existsSync19(path)) {
|
|
5022
6070
|
const missing = {
|
|
5023
6071
|
path,
|
|
5024
6072
|
exists: false,
|
|
@@ -5033,7 +6081,7 @@ function inspectPlaywrightCache(path, cache) {
|
|
|
5033
6081
|
const stats = collectDirectoryStats(path);
|
|
5034
6082
|
let revisionCount = 0;
|
|
5035
6083
|
try {
|
|
5036
|
-
revisionCount =
|
|
6084
|
+
revisionCount = readdirSync10(path, { withFileTypes: true }).filter(
|
|
5037
6085
|
(entry) => entry.isDirectory()
|
|
5038
6086
|
).length;
|
|
5039
6087
|
} catch {
|
|
@@ -5060,7 +6108,7 @@ function collectDirectoryStats(root) {
|
|
|
5060
6108
|
if (!current) continue;
|
|
5061
6109
|
let entries;
|
|
5062
6110
|
try {
|
|
5063
|
-
entries =
|
|
6111
|
+
entries = readdirSync10(current, { withFileTypes: true });
|
|
5064
6112
|
} catch {
|
|
5065
6113
|
continue;
|
|
5066
6114
|
}
|
|
@@ -5069,13 +6117,13 @@ function collectDirectoryStats(root) {
|
|
|
5069
6117
|
truncated = true;
|
|
5070
6118
|
break;
|
|
5071
6119
|
}
|
|
5072
|
-
const path =
|
|
6120
|
+
const path = join20(current, entry.name);
|
|
5073
6121
|
if (entry.isDirectory()) {
|
|
5074
6122
|
pending.push(path);
|
|
5075
6123
|
} else if (entry.isFile()) {
|
|
5076
6124
|
fileCount += 1;
|
|
5077
6125
|
try {
|
|
5078
|
-
bytes +=
|
|
6126
|
+
bytes += statSync6(path).size;
|
|
5079
6127
|
} catch {
|
|
5080
6128
|
}
|
|
5081
6129
|
}
|
|
@@ -5086,11 +6134,11 @@ function collectDirectoryStats(root) {
|
|
|
5086
6134
|
}
|
|
5087
6135
|
|
|
5088
6136
|
// src/portfolio/verification.ts
|
|
5089
|
-
import { existsSync as
|
|
5090
|
-
import { join as
|
|
6137
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "node:fs";
|
|
6138
|
+
import { join as join21 } from "node:path";
|
|
5091
6139
|
var REQUIRED_PROJECT_SCRIPTS = ["typecheck", "lint", "format:check", "verify"];
|
|
5092
6140
|
function inspectProjectVerification(root) {
|
|
5093
|
-
const packageJson = readPackageJson(
|
|
6141
|
+
const packageJson = readPackageJson(join21(root, "package.json"));
|
|
5094
6142
|
const scripts = Object.fromEntries(
|
|
5095
6143
|
REQUIRED_PROJECT_SCRIPTS.map((name) => [
|
|
5096
6144
|
name,
|
|
@@ -5108,9 +6156,9 @@ function inspectProjectVerification(root) {
|
|
|
5108
6156
|
};
|
|
5109
6157
|
}
|
|
5110
6158
|
function readPackageJson(path) {
|
|
5111
|
-
if (!
|
|
6159
|
+
if (!existsSync20(path)) return void 0;
|
|
5112
6160
|
try {
|
|
5113
|
-
return JSON.parse(
|
|
6161
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
5114
6162
|
} catch {
|
|
5115
6163
|
return void 0;
|
|
5116
6164
|
}
|
|
@@ -5118,8 +6166,8 @@ function readPackageJson(path) {
|
|
|
5118
6166
|
|
|
5119
6167
|
// src/repository-files.ts
|
|
5120
6168
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
5121
|
-
import { existsSync as
|
|
5122
|
-
import { isAbsolute as
|
|
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";
|
|
5123
6171
|
var gitMaxBufferBytes = 64 * 1024 * 1024;
|
|
5124
6172
|
var RepositoryFileDiscoveryError = class extends Error {
|
|
5125
6173
|
constructor(message) {
|
|
@@ -5130,7 +6178,7 @@ var RepositoryFileDiscoveryError = class extends Error {
|
|
|
5130
6178
|
function discoverRepositoryFiles(root, options = {}) {
|
|
5131
6179
|
const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
5132
6180
|
if (!probe.ok) {
|
|
5133
|
-
if (!probe.notRepository ||
|
|
6181
|
+
if (!probe.notRepository || existsSync21(join22(root, ".git"))) {
|
|
5134
6182
|
throw new RepositoryFileDiscoveryError(probe.message);
|
|
5135
6183
|
}
|
|
5136
6184
|
return {
|
|
@@ -5161,7 +6209,7 @@ function discoverRepositoryFiles(root, options = {}) {
|
|
|
5161
6209
|
`Git returned a path outside the repository boundary: ${path}`
|
|
5162
6210
|
);
|
|
5163
6211
|
}
|
|
5164
|
-
if (
|
|
6212
|
+
if (existsSync21(join22(root, normalized))) files.add(normalized);
|
|
5165
6213
|
}
|
|
5166
6214
|
return { source: "git", files: [...files].sort() };
|
|
5167
6215
|
}
|
|
@@ -5173,21 +6221,21 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5173
6221
|
const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
|
|
5174
6222
|
const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
|
|
5175
6223
|
const visit = (directory, depth) => {
|
|
5176
|
-
if (depth > maxDepth || !
|
|
6224
|
+
if (depth > maxDepth || !existsSync21(directory)) return;
|
|
5177
6225
|
let entries;
|
|
5178
6226
|
try {
|
|
5179
|
-
entries =
|
|
6227
|
+
entries = readdirSync11(directory, { withFileTypes: true });
|
|
5180
6228
|
} catch {
|
|
5181
6229
|
return;
|
|
5182
6230
|
}
|
|
5183
6231
|
for (const entry of entries) {
|
|
5184
|
-
const absolutePath =
|
|
6232
|
+
const absolutePath = join22(directory, entry.name);
|
|
5185
6233
|
if (entry.isDirectory()) {
|
|
5186
6234
|
if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
|
|
5187
6235
|
continue;
|
|
5188
6236
|
}
|
|
5189
6237
|
if (!entry.isFile()) continue;
|
|
5190
|
-
const relativePath = normalizeRepositoryRelativePath(
|
|
6238
|
+
const relativePath = normalizeRepositoryRelativePath(relative10(root, absolutePath));
|
|
5191
6239
|
if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
|
|
5192
6240
|
files.add(relativePath);
|
|
5193
6241
|
}
|
|
@@ -5197,7 +6245,7 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5197
6245
|
return [...files].sort();
|
|
5198
6246
|
}
|
|
5199
6247
|
function isSafeRepositoryRelativePath(path) {
|
|
5200
|
-
return path !== "" && path !== "." && !
|
|
6248
|
+
return path !== "" && path !== "." && !isAbsolute6(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
|
|
5201
6249
|
}
|
|
5202
6250
|
function runGit(root, args) {
|
|
5203
6251
|
const result = spawnSync2("git", ["-C", root, ...args], {
|
|
@@ -5237,7 +6285,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5237
6285
|
includedFileCount: files.length,
|
|
5238
6286
|
excludedFileCount: candidateFiles.length - files.length
|
|
5239
6287
|
},
|
|
5240
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) =>
|
|
6288
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync22(join23(targetDir, file))),
|
|
5241
6289
|
aiConfigFiles: [],
|
|
5242
6290
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
5243
6291
|
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
|
|
@@ -5248,19 +6296,19 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5248
6296
|
}),
|
|
5249
6297
|
packageJson,
|
|
5250
6298
|
docs: {
|
|
5251
|
-
hasDocsDirectory:
|
|
6299
|
+
hasDocsDirectory: existsSync22(join23(targetDir, "docs")),
|
|
5252
6300
|
markdownFileCount: markdownFiles.length,
|
|
5253
6301
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
5254
6302
|
},
|
|
5255
6303
|
git: readGitState(targetDir),
|
|
5256
|
-
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)
|
|
5257
6305
|
};
|
|
5258
6306
|
}
|
|
5259
6307
|
function readPackageJson2(targetDir) {
|
|
5260
|
-
const packageJsonPath =
|
|
5261
|
-
if (!
|
|
6308
|
+
const packageJsonPath = join23(targetDir, "package.json");
|
|
6309
|
+
if (!existsSync22(packageJsonPath)) return void 0;
|
|
5262
6310
|
try {
|
|
5263
|
-
const packageJson = JSON.parse(
|
|
6311
|
+
const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf8"));
|
|
5264
6312
|
return {
|
|
5265
6313
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
5266
6314
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -5351,8 +6399,8 @@ function runLensReport(args) {
|
|
|
5351
6399
|
}
|
|
5352
6400
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
5353
6401
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
5354
|
-
|
|
5355
|
-
|
|
6402
|
+
mkdirSync10(dirname14(options.value.outPath), { recursive: true });
|
|
6403
|
+
writeFileSync9(options.value.outPath, markdown);
|
|
5356
6404
|
console.log(`report: ${options.value.outPath}`);
|
|
5357
6405
|
return 0;
|
|
5358
6406
|
}
|
|
@@ -5457,15 +6505,15 @@ function printUsage4() {
|
|
|
5457
6505
|
}
|
|
5458
6506
|
|
|
5459
6507
|
// src/commands/portfolio.ts
|
|
5460
|
-
import { existsSync as
|
|
5461
|
-
import { join as
|
|
6508
|
+
import { existsSync as existsSync34, readFileSync as readFileSync19 } from "node:fs";
|
|
6509
|
+
import { join as join35 } from "node:path";
|
|
5462
6510
|
|
|
5463
6511
|
// src/portfolio/doctor.ts
|
|
5464
6512
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5465
|
-
import { existsSync as
|
|
6513
|
+
import { existsSync as existsSync25, readFileSync as readFileSync17 } from "node:fs";
|
|
5466
6514
|
import { createRequire as createRequire2 } from "node:module";
|
|
5467
6515
|
import { homedir as homedir4 } from "node:os";
|
|
5468
|
-
import { dirname as
|
|
6516
|
+
import { dirname as dirname16, join as join26 } from "node:path";
|
|
5469
6517
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5470
6518
|
|
|
5471
6519
|
// src/host-tooling/inventory.ts
|
|
@@ -5557,8 +6605,8 @@ function isRecord2(value) {
|
|
|
5557
6605
|
}
|
|
5558
6606
|
|
|
5559
6607
|
// src/portfolio/asset-state.ts
|
|
5560
|
-
import { existsSync as
|
|
5561
|
-
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";
|
|
5562
6610
|
function comparePortfolioAssetState(options) {
|
|
5563
6611
|
const expectedManifest = readPlanDocument(
|
|
5564
6612
|
options.expectedPlan,
|
|
@@ -5569,10 +6617,10 @@ function comparePortfolioAssetState(options) {
|
|
|
5569
6617
|
".pro-gov/assets.lock.json"
|
|
5570
6618
|
);
|
|
5571
6619
|
const currentManifest = readJsonFile(
|
|
5572
|
-
|
|
6620
|
+
join24(options.targetDir, ".pro-gov/assets.json")
|
|
5573
6621
|
);
|
|
5574
6622
|
const currentLock = readJsonFile(
|
|
5575
|
-
|
|
6623
|
+
join24(options.targetDir, ".pro-gov/assets.lock.json")
|
|
5576
6624
|
);
|
|
5577
6625
|
const issues = [];
|
|
5578
6626
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
@@ -5600,7 +6648,7 @@ function comparePortfolioAssetState(options) {
|
|
|
5600
6648
|
(action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
|
|
5601
6649
|
))
|
|
5602
6650
|
continue;
|
|
5603
|
-
const targetAbsolutePath =
|
|
6651
|
+
const targetAbsolutePath = join24(options.targetDir, entry.targetPath);
|
|
5604
6652
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
5605
6653
|
issues.push({
|
|
5606
6654
|
type: "orphaned-managed-symlink",
|
|
@@ -5622,9 +6670,9 @@ function readPlanDocument(plan, targetPath) {
|
|
|
5622
6670
|
}
|
|
5623
6671
|
}
|
|
5624
6672
|
function readJsonFile(path) {
|
|
5625
|
-
if (!
|
|
6673
|
+
if (!existsSync23(path)) return void 0;
|
|
5626
6674
|
try {
|
|
5627
|
-
return JSON.parse(
|
|
6675
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
5628
6676
|
} catch {
|
|
5629
6677
|
return void 0;
|
|
5630
6678
|
}
|
|
@@ -5650,7 +6698,7 @@ function normalizeLock(lock) {
|
|
|
5650
6698
|
}
|
|
5651
6699
|
function pathIsSymlink(path) {
|
|
5652
6700
|
try {
|
|
5653
|
-
return
|
|
6701
|
+
return lstatSync9(path).isSymbolicLink();
|
|
5654
6702
|
} catch {
|
|
5655
6703
|
return false;
|
|
5656
6704
|
}
|
|
@@ -5658,8 +6706,8 @@ function pathIsSymlink(path) {
|
|
|
5658
6706
|
|
|
5659
6707
|
// src/portfolio/version-policy.ts
|
|
5660
6708
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5661
|
-
import { existsSync as
|
|
5662
|
-
import { dirname as
|
|
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";
|
|
5663
6711
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
5664
6712
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
5665
6713
|
const packageManifests = collectPackageManifests(root);
|
|
@@ -5768,10 +6816,10 @@ function findDeclaredVersion(packageJson, name) {
|
|
|
5768
6816
|
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
5769
6817
|
let current = fromDirectory;
|
|
5770
6818
|
while (true) {
|
|
5771
|
-
const packageJson = readJson2(
|
|
6819
|
+
const packageJson = readJson2(join25(current, "node_modules", name, "package.json"));
|
|
5772
6820
|
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
5773
6821
|
if (current === root) return void 0;
|
|
5774
|
-
const parent =
|
|
6822
|
+
const parent = dirname15(current);
|
|
5775
6823
|
if (parent === current) return void 0;
|
|
5776
6824
|
current = parent;
|
|
5777
6825
|
}
|
|
@@ -5806,15 +6854,15 @@ function collectPackageManifests(root) {
|
|
|
5806
6854
|
if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
|
|
5807
6855
|
return;
|
|
5808
6856
|
}
|
|
5809
|
-
const path =
|
|
6857
|
+
const path = join25(root, relativePath);
|
|
5810
6858
|
try {
|
|
5811
|
-
if (!
|
|
6859
|
+
if (!lstatSync10(path).isFile()) return;
|
|
5812
6860
|
} catch {
|
|
5813
6861
|
return;
|
|
5814
6862
|
}
|
|
5815
6863
|
const packageJson = readJson2(path);
|
|
5816
6864
|
if (!packageJson) return;
|
|
5817
|
-
manifests.set(relativePath, { path: relativePath, directory:
|
|
6865
|
+
manifests.set(relativePath, { path: relativePath, directory: dirname15(path), packageJson });
|
|
5818
6866
|
};
|
|
5819
6867
|
const discovery = discoverRepositoryFiles(root, {
|
|
5820
6868
|
gitPathspecs: ["package.json", ":(glob)**/package.json"],
|
|
@@ -5829,9 +6877,9 @@ function unique(values) {
|
|
|
5829
6877
|
return [...new Set(values)];
|
|
5830
6878
|
}
|
|
5831
6879
|
function readJson2(path) {
|
|
5832
|
-
if (!
|
|
6880
|
+
if (!existsSync24(path)) return void 0;
|
|
5833
6881
|
try {
|
|
5834
|
-
return JSON.parse(
|
|
6882
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
5835
6883
|
} catch {
|
|
5836
6884
|
return void 0;
|
|
5837
6885
|
}
|
|
@@ -5864,12 +6912,12 @@ function inspectTarget(options) {
|
|
|
5864
6912
|
const { target } = options;
|
|
5865
6913
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
5866
6914
|
const issues = [];
|
|
5867
|
-
const packageJson = readJson3(
|
|
6915
|
+
const packageJson = readJson3(join26(target.path, "package.json"));
|
|
5868
6916
|
const packages = {};
|
|
5869
6917
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
5870
6918
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
5871
6919
|
const installedPackage = readJson3(
|
|
5872
|
-
|
|
6920
|
+
join26(target.path, "node_modules", packageName, "package.json")
|
|
5873
6921
|
);
|
|
5874
6922
|
const installed = installedPackage?.version;
|
|
5875
6923
|
const expected = options.expectedPackageVersions[packageName];
|
|
@@ -5929,7 +6977,7 @@ function inspectTarget(options) {
|
|
|
5929
6977
|
type: "asset-lock-drift",
|
|
5930
6978
|
message: error instanceof Error ? error.message : String(error)
|
|
5931
6979
|
});
|
|
5932
|
-
if (!
|
|
6980
|
+
if (!existsSync25(join26(target.path, ".pro-gov/assets.json"))) {
|
|
5933
6981
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
5934
6982
|
}
|
|
5935
6983
|
}
|
|
@@ -5947,15 +6995,15 @@ function inspectTarget(options) {
|
|
|
5947
6995
|
};
|
|
5948
6996
|
}
|
|
5949
6997
|
function readTargetAssetHost(targetDir) {
|
|
5950
|
-
const lockfile = readJson3(
|
|
6998
|
+
const lockfile = readJson3(join26(targetDir, ".pro-gov/assets.lock.json"));
|
|
5951
6999
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
5952
7000
|
}
|
|
5953
7001
|
function isAssetRegistryHost(value) {
|
|
5954
7002
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5955
7003
|
}
|
|
5956
7004
|
function runTargetChecks(target) {
|
|
5957
|
-
const proGovCli =
|
|
5958
|
-
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");
|
|
5959
7007
|
const commands = [
|
|
5960
7008
|
{
|
|
5961
7009
|
name: "pro-gov doctor",
|
|
@@ -5966,7 +7014,7 @@ function runTargetChecks(target) {
|
|
|
5966
7014
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
5967
7015
|
];
|
|
5968
7016
|
return commands.map((command2) => {
|
|
5969
|
-
if (!
|
|
7017
|
+
if (!existsSync25(command2.cli)) return { name: command2.name, status: null };
|
|
5970
7018
|
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
5971
7019
|
cwd: target.path,
|
|
5972
7020
|
encoding: "utf8",
|
|
@@ -6005,18 +7053,18 @@ function getExpectedPackageVersions() {
|
|
|
6005
7053
|
};
|
|
6006
7054
|
}
|
|
6007
7055
|
function findOwnPackageJson() {
|
|
6008
|
-
let current =
|
|
7056
|
+
let current = dirname16(fileURLToPath4(import.meta.url));
|
|
6009
7057
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
6010
|
-
const candidate =
|
|
6011
|
-
if (
|
|
6012
|
-
current =
|
|
7058
|
+
const candidate = join26(current, "package.json");
|
|
7059
|
+
if (existsSync25(candidate)) return candidate;
|
|
7060
|
+
current = dirname16(current);
|
|
6013
7061
|
}
|
|
6014
7062
|
return "";
|
|
6015
7063
|
}
|
|
6016
7064
|
function readJson3(path) {
|
|
6017
|
-
if (!path || !
|
|
7065
|
+
if (!path || !existsSync25(path)) return void 0;
|
|
6018
7066
|
try {
|
|
6019
|
-
return JSON.parse(
|
|
7067
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
6020
7068
|
} catch {
|
|
6021
7069
|
return void 0;
|
|
6022
7070
|
}
|
|
@@ -6033,15 +7081,15 @@ function deduplicateIssues(issues) {
|
|
|
6033
7081
|
|
|
6034
7082
|
// src/portfolio/ai-health/index.ts
|
|
6035
7083
|
import { homedir as homedir5 } from "node:os";
|
|
6036
|
-
import { dirname as
|
|
7084
|
+
import { dirname as dirname18, join as join34, resolve as resolve10 } from "node:path";
|
|
6037
7085
|
|
|
6038
7086
|
// src/portfolio/ai-health/entries.ts
|
|
6039
|
-
import { existsSync as
|
|
6040
|
-
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";
|
|
6041
7089
|
|
|
6042
7090
|
// src/portfolio/ai-health/shared.ts
|
|
6043
7091
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6044
|
-
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";
|
|
6045
7093
|
function safeRealpath(path) {
|
|
6046
7094
|
try {
|
|
6047
7095
|
return realpathSync6(path);
|
|
@@ -6072,7 +7120,7 @@ function jsonObjectKeys(path, key) {
|
|
|
6072
7120
|
return Object.keys(value[key]).sort();
|
|
6073
7121
|
}
|
|
6074
7122
|
function tomlMcpNames(path) {
|
|
6075
|
-
if (!
|
|
7123
|
+
if (!existsSync26(path)) return [];
|
|
6076
7124
|
const names = /* @__PURE__ */ new Set();
|
|
6077
7125
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
6078
7126
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
@@ -6083,35 +7131,35 @@ function tomlMcpNames(path) {
|
|
|
6083
7131
|
}
|
|
6084
7132
|
function readJson4(path) {
|
|
6085
7133
|
try {
|
|
6086
|
-
return JSON.parse(
|
|
7134
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
6087
7135
|
} catch {
|
|
6088
7136
|
return void 0;
|
|
6089
7137
|
}
|
|
6090
7138
|
}
|
|
6091
7139
|
function safeRead(path) {
|
|
6092
7140
|
try {
|
|
6093
|
-
return
|
|
7141
|
+
return readFileSync18(path, "utf8");
|
|
6094
7142
|
} catch {
|
|
6095
7143
|
return "";
|
|
6096
7144
|
}
|
|
6097
7145
|
}
|
|
6098
7146
|
function safeReadDir(path) {
|
|
6099
7147
|
try {
|
|
6100
|
-
return
|
|
7148
|
+
return readdirSync12(path).sort();
|
|
6101
7149
|
} catch {
|
|
6102
7150
|
return [];
|
|
6103
7151
|
}
|
|
6104
7152
|
}
|
|
6105
7153
|
function safeIsDirectory(path) {
|
|
6106
7154
|
try {
|
|
6107
|
-
return
|
|
7155
|
+
return statSync8(path).isDirectory();
|
|
6108
7156
|
} catch {
|
|
6109
7157
|
return false;
|
|
6110
7158
|
}
|
|
6111
7159
|
}
|
|
6112
7160
|
function pathLexists(path) {
|
|
6113
7161
|
try {
|
|
6114
|
-
|
|
7162
|
+
lstatSync11(path);
|
|
6115
7163
|
return true;
|
|
6116
7164
|
} catch {
|
|
6117
7165
|
return false;
|
|
@@ -6149,12 +7197,12 @@ function hasWorkflowReminderHooks(hooks) {
|
|
|
6149
7197
|
);
|
|
6150
7198
|
}
|
|
6151
7199
|
function inspectEntries(root) {
|
|
6152
|
-
const agentsPath =
|
|
6153
|
-
const agents = !
|
|
6154
|
-
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");
|
|
6155
7203
|
let claude = "missing";
|
|
6156
7204
|
if (pathLexists(claudePath)) {
|
|
6157
|
-
const info =
|
|
7205
|
+
const info = lstatSync12(claudePath);
|
|
6158
7206
|
if (info.isSymbolicLink()) {
|
|
6159
7207
|
try {
|
|
6160
7208
|
claude = realpathSync7(claudePath) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6171,19 +7219,19 @@ function inspectEntries(root) {
|
|
|
6171
7219
|
var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
|
|
6172
7220
|
function inspectAgentLinks(root) {
|
|
6173
7221
|
const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
|
|
6174
|
-
const directoryPath =
|
|
7222
|
+
const directoryPath = join27(root, directory);
|
|
6175
7223
|
if (!pathLexists(directoryPath)) return [];
|
|
6176
7224
|
try {
|
|
6177
|
-
if (!
|
|
7225
|
+
if (!lstatSync12(directoryPath).isDirectory()) return [];
|
|
6178
7226
|
} catch {
|
|
6179
7227
|
return [];
|
|
6180
7228
|
}
|
|
6181
7229
|
return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
|
|
6182
7230
|
const relativePath = `${directory}/${name}`;
|
|
6183
|
-
const path =
|
|
7231
|
+
const path = join27(root, relativePath);
|
|
6184
7232
|
let stat;
|
|
6185
7233
|
try {
|
|
6186
|
-
stat =
|
|
7234
|
+
stat = lstatSync12(path);
|
|
6187
7235
|
} catch {
|
|
6188
7236
|
return [];
|
|
6189
7237
|
}
|
|
@@ -6204,9 +7252,9 @@ function inspectAgentLinks(root) {
|
|
|
6204
7252
|
};
|
|
6205
7253
|
}
|
|
6206
7254
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
6207
|
-
const path =
|
|
7255
|
+
const path = join27(root, filename);
|
|
6208
7256
|
if (!pathLexists(path)) return "missing";
|
|
6209
|
-
const info =
|
|
7257
|
+
const info = lstatSync12(path);
|
|
6210
7258
|
if (info.isSymbolicLink()) {
|
|
6211
7259
|
try {
|
|
6212
7260
|
return realpathSync7(path) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6244,7 +7292,7 @@ function inspectHooks(root) {
|
|
|
6244
7292
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
6245
7293
|
];
|
|
6246
7294
|
return configs.map((config) => {
|
|
6247
|
-
const value = readJson4(
|
|
7295
|
+
const value = readJson4(join27(root, config.path));
|
|
6248
7296
|
const counts = /* @__PURE__ */ new Map();
|
|
6249
7297
|
collectHookEvents(value, counts);
|
|
6250
7298
|
return {
|
|
@@ -6265,18 +7313,18 @@ function collectHookEvents(value, counts) {
|
|
|
6265
7313
|
}
|
|
6266
7314
|
}
|
|
6267
7315
|
function inspectDocs(root, expected) {
|
|
6268
|
-
const packageJson = readJson4(
|
|
7316
|
+
const packageJson = readJson4(join27(root, "package.json"));
|
|
6269
7317
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
6270
7318
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
6271
7319
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
6272
|
-
const routerMatch = safeRead(
|
|
7320
|
+
const routerMatch = safeRead(join27(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
6273
7321
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
6274
7322
|
return {
|
|
6275
7323
|
routerVersion: routerMatch?.[1],
|
|
6276
7324
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
6277
7325
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
6278
|
-
manifest:
|
|
6279
|
-
currentWork:
|
|
7326
|
+
manifest: existsSync27(join27(root, "docs/governance/MANIFEST.yml")),
|
|
7327
|
+
currentWork: existsSync27(join27(root, "docs/reference/execution/current-work.md")),
|
|
6280
7328
|
packages: {
|
|
6281
7329
|
expected,
|
|
6282
7330
|
docGov,
|
|
@@ -6338,17 +7386,17 @@ function inspectGit2(root) {
|
|
|
6338
7386
|
}
|
|
6339
7387
|
|
|
6340
7388
|
// src/portfolio/ai-health/hosts.ts
|
|
6341
|
-
import { existsSync as
|
|
6342
|
-
import { join as
|
|
7389
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
7390
|
+
import { join as join29, resolve as resolve9, sep as sep3 } from "node:path";
|
|
6343
7391
|
|
|
6344
7392
|
// src/portfolio/ai-health/devspace.ts
|
|
6345
|
-
import { existsSync as
|
|
6346
|
-
import { join as
|
|
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";
|
|
6347
7395
|
function inspectDevSpaceHealth(options) {
|
|
6348
7396
|
const run = options.run ?? runDevSpaceCommand;
|
|
6349
|
-
const configDirectory =
|
|
6350
|
-
const configPath =
|
|
6351
|
-
const authPath =
|
|
7397
|
+
const configDirectory = join28(options.homeDir, ".devspace");
|
|
7398
|
+
const configPath = join28(configDirectory, "config.json");
|
|
7399
|
+
const authPath = join28(configDirectory, "auth.json");
|
|
6352
7400
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
6353
7401
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
6354
7402
|
const latestResult = run(
|
|
@@ -6371,9 +7419,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
6371
7419
|
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
6372
7420
|
) ? "complete" : "partial";
|
|
6373
7421
|
const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
|
|
6374
|
-
const directoryMode = process.platform !== "win32" &&
|
|
6375
|
-
const fileMode = process.platform !== "win32" &&
|
|
6376
|
-
const authMode = process.platform !== "win32" &&
|
|
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;
|
|
6377
7425
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
6378
7426
|
const recommendations = [];
|
|
6379
7427
|
let status = "healthy";
|
|
@@ -6387,7 +7435,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6387
7435
|
};
|
|
6388
7436
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
6389
7437
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
6390
|
-
if (!
|
|
7438
|
+
if (!existsSync28(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
6391
7439
|
if (directoryMode && directoryMode !== "700")
|
|
6392
7440
|
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
6393
7441
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -6420,7 +7468,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6420
7468
|
exists: configExists,
|
|
6421
7469
|
...directoryMode ? { directoryMode } : {},
|
|
6422
7470
|
...fileMode ? { fileMode } : {},
|
|
6423
|
-
authExists:
|
|
7471
|
+
authExists: existsSync28(authPath),
|
|
6424
7472
|
...authMode ? { authMode } : {},
|
|
6425
7473
|
bind,
|
|
6426
7474
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -6443,7 +7491,7 @@ function isLoopbackHost(host) {
|
|
|
6443
7491
|
return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
|
|
6444
7492
|
}
|
|
6445
7493
|
function isPathInside(path, root) {
|
|
6446
|
-
const fromRoot =
|
|
7494
|
+
const fromRoot = relative11(resolve8(root), resolve8(path));
|
|
6447
7495
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`);
|
|
6448
7496
|
}
|
|
6449
7497
|
|
|
@@ -6461,9 +7509,9 @@ var MCP_DISCOVERY_PATHS = {
|
|
|
6461
7509
|
}
|
|
6462
7510
|
};
|
|
6463
7511
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
6464
|
-
const codexConfig =
|
|
6465
|
-
const claudeConfig =
|
|
6466
|
-
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);
|
|
6467
7515
|
const hostEnvironment = {
|
|
6468
7516
|
mcp: {
|
|
6469
7517
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -6471,11 +7519,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6471
7519
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
6472
7520
|
},
|
|
6473
7521
|
skills: {
|
|
6474
|
-
codexUser: inspectSkillRoot(
|
|
6475
|
-
claudeCodeUser: inspectSkillRoot(
|
|
6476
|
-
grokUser: inspectSkillRoot(
|
|
6477
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
6478
|
-
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")),
|
|
6479
7527
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
6480
7528
|
},
|
|
6481
7529
|
grok: {
|
|
@@ -6496,20 +7544,20 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6496
7544
|
function inspectSkillRoot(path) {
|
|
6497
7545
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
6498
7546
|
const names = exists ? safeReadDir(path).filter(
|
|
6499
|
-
(name) => !name.startsWith(".") &&
|
|
7547
|
+
(name) => !name.startsWith(".") && existsSync29(join29(path, name, "SKILL.md"))
|
|
6500
7548
|
) : [];
|
|
6501
7549
|
return { path, exists, names };
|
|
6502
7550
|
}
|
|
6503
7551
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
6504
7552
|
if (!homeDir) return [];
|
|
6505
|
-
const value = readJson4(
|
|
7553
|
+
const value = readJson4(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
|
|
6506
7554
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
6507
7555
|
const candidates = new Set(
|
|
6508
|
-
[
|
|
7556
|
+
[resolve9(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
6509
7557
|
);
|
|
6510
7558
|
const names = /* @__PURE__ */ new Set();
|
|
6511
7559
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
6512
|
-
const projectPaths = [
|
|
7560
|
+
const projectPaths = [resolve9(path), safeRealpath(path)].filter(
|
|
6513
7561
|
(candidate) => Boolean(candidate)
|
|
6514
7562
|
);
|
|
6515
7563
|
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers))
|
|
@@ -6531,7 +7579,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6531
7579
|
const result = spawnPlatformSync("grok", ["inspect", "--json"], {
|
|
6532
7580
|
cwd: root,
|
|
6533
7581
|
encoding: "utf8",
|
|
6534
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
7582
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join29(homeDir, ".grok") },
|
|
6535
7583
|
maxBuffer: 10 * 1024 * 1024,
|
|
6536
7584
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6537
7585
|
timeout: 8e3
|
|
@@ -6540,7 +7588,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6540
7588
|
const value = JSON.parse(result.stdout);
|
|
6541
7589
|
if (!isRecord3(value)) return empty("failed");
|
|
6542
7590
|
const userClaudeNames = new Set(
|
|
6543
|
-
jsonObjectKeys(
|
|
7591
|
+
jsonObjectKeys(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
|
|
6544
7592
|
);
|
|
6545
7593
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
6546
7594
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
@@ -6608,26 +7656,26 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6608
7656
|
if (userClaudeNames.has(name)) return "user";
|
|
6609
7657
|
return "unknown";
|
|
6610
7658
|
}
|
|
6611
|
-
const resolvedSource = safeRealpath(sourcePath) ??
|
|
6612
|
-
const resolvedRoot = safeRealpath(root) ??
|
|
6613
|
-
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))
|
|
6614
7662
|
return "project-shared";
|
|
6615
7663
|
if (resolvedSource.startsWith(resolvedRoot + sep3)) return "project";
|
|
6616
|
-
if (homeDir && resolvedSource ===
|
|
7664
|
+
if (homeDir && resolvedSource === join29(resolve9(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
|
|
6617
7665
|
if (localClaudeNames.has(name)) return "project-local";
|
|
6618
7666
|
if (userClaudeNames.has(name)) return "user";
|
|
6619
7667
|
}
|
|
6620
|
-
if (homeDir && resolvedSource.startsWith(
|
|
7668
|
+
if (homeDir && resolvedSource.startsWith(resolve9(homeDir) + sep3)) return "user";
|
|
6621
7669
|
if (sourceType === "project") return "project";
|
|
6622
7670
|
return "unknown";
|
|
6623
7671
|
}
|
|
6624
7672
|
|
|
6625
7673
|
// src/portfolio/ai-health/secrets.ts
|
|
6626
|
-
import { existsSync as
|
|
6627
|
-
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";
|
|
6628
7676
|
var supportsPosixModes = process.platform !== "win32";
|
|
6629
7677
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
6630
|
-
const centralPath =
|
|
7678
|
+
const centralPath = join30(secretsRoot, id);
|
|
6631
7679
|
const centralRealPath = safeRealpath(centralPath);
|
|
6632
7680
|
const localOnlyReasons = new Map(
|
|
6633
7681
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -6639,16 +7687,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
6639
7687
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
6640
7688
|
template: isEnvironmentTemplate(path),
|
|
6641
7689
|
fixture: isEnvironmentFixture(path),
|
|
6642
|
-
symlink:
|
|
6643
|
-
centralized: pointsInside(
|
|
7690
|
+
symlink: lstatSync13(join30(root, path)).isSymbolicLink(),
|
|
7691
|
+
centralized: pointsInside(join30(root, path), centralRealPath),
|
|
6644
7692
|
localOnly: localOnlyReason !== void 0,
|
|
6645
7693
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
6646
7694
|
};
|
|
6647
7695
|
});
|
|
6648
7696
|
return {
|
|
6649
|
-
centralDirectory:
|
|
6650
|
-
centralMode: supportsPosixModes &&
|
|
6651
|
-
centralFiles:
|
|
7697
|
+
centralDirectory: existsSync30(centralPath) ? "present" : "absent",
|
|
7698
|
+
centralMode: supportsPosixModes && existsSync30(centralPath) ? modeString(statSync10(centralPath).mode) : void 0,
|
|
7699
|
+
centralFiles: existsSync30(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
6652
7700
|
repositoryEnvFiles: envFiles
|
|
6653
7701
|
};
|
|
6654
7702
|
}
|
|
@@ -6671,12 +7719,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
6671
7719
|
if (depth > 5) return [];
|
|
6672
7720
|
const found = [];
|
|
6673
7721
|
try {
|
|
6674
|
-
for (const entry of
|
|
7722
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
6675
7723
|
if (entry.isDirectory()) {
|
|
6676
7724
|
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
6677
|
-
found.push(...collectEnvironmentFiles(root,
|
|
7725
|
+
found.push(...collectEnvironmentFiles(root, join30(current, entry.name), depth + 1));
|
|
6678
7726
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
6679
|
-
found.push(
|
|
7727
|
+
found.push(relative12(root, join30(current, entry.name)).replaceAll("\\", "/"));
|
|
6680
7728
|
}
|
|
6681
7729
|
}
|
|
6682
7730
|
} catch {
|
|
@@ -6688,13 +7736,13 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
6688
7736
|
if (depth > 3) return [];
|
|
6689
7737
|
const found = [];
|
|
6690
7738
|
try {
|
|
6691
|
-
for (const entry of
|
|
6692
|
-
const path =
|
|
7739
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
7740
|
+
const path = join30(current, entry.name);
|
|
6693
7741
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
6694
7742
|
else
|
|
6695
7743
|
found.push({
|
|
6696
|
-
path:
|
|
6697
|
-
mode: supportsPosixModes ? modeString(
|
|
7744
|
+
path: relative12(root, path).replaceAll("\\", "/"),
|
|
7745
|
+
mode: supportsPosixModes ? modeString(lstatSync13(path).mode) : "unknown"
|
|
6698
7746
|
});
|
|
6699
7747
|
}
|
|
6700
7748
|
} catch {
|
|
@@ -6719,10 +7767,10 @@ function isEnvironmentFixture(path) {
|
|
|
6719
7767
|
return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
|
|
6720
7768
|
}
|
|
6721
7769
|
function pointsInside(path, expectedRoot) {
|
|
6722
|
-
if (!expectedRoot || !
|
|
7770
|
+
if (!expectedRoot || !lstatSync13(path).isSymbolicLink()) return false;
|
|
6723
7771
|
const target = safeRealpath(path);
|
|
6724
7772
|
if (!target) return false;
|
|
6725
|
-
const fromRoot =
|
|
7773
|
+
const fromRoot = relative12(expectedRoot, target);
|
|
6726
7774
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep4}`);
|
|
6727
7775
|
}
|
|
6728
7776
|
function hasUnsafeCentralSecretPermissions(secrets) {
|
|
@@ -6730,24 +7778,24 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
6730
7778
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
6731
7779
|
}
|
|
6732
7780
|
function inspectSecretsRoot(path) {
|
|
6733
|
-
return
|
|
7781
|
+
return existsSync30(path) ? {
|
|
6734
7782
|
path,
|
|
6735
7783
|
exists: true,
|
|
6736
|
-
...supportsPosixModes ? { mode: modeString(
|
|
7784
|
+
...supportsPosixModes ? { mode: modeString(statSync10(path).mode) } : {}
|
|
6737
7785
|
} : { path, exists: false };
|
|
6738
7786
|
}
|
|
6739
7787
|
|
|
6740
7788
|
// src/portfolio/ai-health/skills.ts
|
|
6741
|
-
import { existsSync as
|
|
6742
|
-
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";
|
|
6743
7791
|
function countAutomaticSkillsNeedingReview(skills) {
|
|
6744
7792
|
return skills.automatic.filter(
|
|
6745
7793
|
(item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
|
|
6746
7794
|
).length;
|
|
6747
7795
|
}
|
|
6748
7796
|
function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
6749
|
-
const lock = readJson4(
|
|
6750
|
-
const assetManifest = readJson4(
|
|
7797
|
+
const lock = readJson4(join31(root, ".pro-gov/assets.lock.json"));
|
|
7798
|
+
const assetManifest = readJson4(join31(root, ".pro-gov/assets.json"));
|
|
6751
7799
|
const managed = /* @__PURE__ */ new Set();
|
|
6752
7800
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
6753
7801
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -6759,7 +7807,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6759
7807
|
}
|
|
6760
7808
|
const inspectPlacement = (placement) => {
|
|
6761
7809
|
const directory = placement === "auto" ? "skills" : "manual-skills";
|
|
6762
|
-
const skillRoot =
|
|
7810
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6763
7811
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6764
7812
|
return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
|
|
6765
7813
|
};
|
|
@@ -6814,15 +7862,15 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6814
7862
|
},
|
|
6815
7863
|
hosts: {
|
|
6816
7864
|
codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
|
|
6817
|
-
claudeCodeProject: inspectSkillRoot(
|
|
6818
|
-
grokNativeProject: inspectSkillRoot(
|
|
7865
|
+
claudeCodeProject: inspectSkillRoot(join31(root, ".claude/skills")).names.length,
|
|
7866
|
+
grokNativeProject: inspectSkillRoot(join31(root, ".grok/skills")).names.length,
|
|
6819
7867
|
grokEffective: grokInspection.skills
|
|
6820
7868
|
}
|
|
6821
7869
|
};
|
|
6822
7870
|
}
|
|
6823
7871
|
function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
|
|
6824
|
-
const path =
|
|
6825
|
-
const stat =
|
|
7872
|
+
const path = join31(skillRoot, name);
|
|
7873
|
+
const stat = lstatSync14(path);
|
|
6826
7874
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6827
7875
|
let realPath;
|
|
6828
7876
|
try {
|
|
@@ -6831,7 +7879,7 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6831
7879
|
if (stat.isSymbolicLink()) kind = "dangling-symlink";
|
|
6832
7880
|
}
|
|
6833
7881
|
const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
|
|
6834
|
-
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;
|
|
6835
7883
|
return {
|
|
6836
7884
|
name,
|
|
6837
7885
|
kind,
|
|
@@ -6843,11 +7891,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6843
7891
|
};
|
|
6844
7892
|
}
|
|
6845
7893
|
function isPluginPack(path) {
|
|
6846
|
-
const skillsRoot =
|
|
6847
|
-
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")));
|
|
6848
7896
|
}
|
|
6849
7897
|
function inspectInvalidSkillEntries(root, directory) {
|
|
6850
|
-
const skillRoot =
|
|
7898
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6851
7899
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6852
7900
|
return safeReadDir(skillRoot).flatMap((name) => {
|
|
6853
7901
|
if (name === ".gitkeep") return [];
|
|
@@ -6855,14 +7903,14 @@ function inspectInvalidSkillEntries(root, directory) {
|
|
|
6855
7903
|
return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
|
|
6856
7904
|
if (name.startsWith("."))
|
|
6857
7905
|
return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
|
|
6858
|
-
const path =
|
|
6859
|
-
return !
|
|
7906
|
+
const path = join31(skillRoot, name);
|
|
7907
|
+
return !lstatSync14(path).isDirectory() && !lstatSync14(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
|
|
6860
7908
|
});
|
|
6861
7909
|
}
|
|
6862
7910
|
function skillDuplicatesUser(item, root, userSkills) {
|
|
6863
7911
|
if (userSkills.names.has(item.name)) return true;
|
|
6864
|
-
const automatic =
|
|
6865
|
-
const manual =
|
|
7912
|
+
const automatic = join31(root, ".agents/skills", item.name);
|
|
7913
|
+
const manual = join31(root, ".agents/manual-skills", item.name);
|
|
6866
7914
|
const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
|
|
6867
7915
|
return realPath ? userSkills.realPaths.has(realPath) : false;
|
|
6868
7916
|
}
|
|
@@ -6882,13 +7930,13 @@ function skillPlacementDrift(item, actualPlacement) {
|
|
|
6882
7930
|
return [];
|
|
6883
7931
|
}
|
|
6884
7932
|
function inspectClaudeSkillRoot(root) {
|
|
6885
|
-
const path =
|
|
7933
|
+
const path = join31(root, ".claude/skills");
|
|
6886
7934
|
if (!pathLexists(path)) return "missing";
|
|
6887
|
-
const stat =
|
|
7935
|
+
const stat = lstatSync14(path);
|
|
6888
7936
|
if (stat.isSymbolicLink()) {
|
|
6889
7937
|
try {
|
|
6890
7938
|
const target = realpathSync8(path);
|
|
6891
|
-
return target === realpathSync8(
|
|
7939
|
+
return target === realpathSync8(join31(root, ".agents/skills")) ? "shared-root" : "other";
|
|
6892
7940
|
} catch {
|
|
6893
7941
|
return "dangling-symlink";
|
|
6894
7942
|
}
|
|
@@ -6901,27 +7949,27 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6901
7949
|
health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
|
|
6902
7950
|
skills: []
|
|
6903
7951
|
};
|
|
6904
|
-
const agentAssetsRoot =
|
|
6905
|
-
const registry = readJson4(
|
|
7952
|
+
const agentAssetsRoot = join31(executionEngineRoot, "agent-assets");
|
|
7953
|
+
const registry = readJson4(join31(agentAssetsRoot, "registry.json"));
|
|
6906
7954
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
6907
7955
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
6908
|
-
const bundleRoot =
|
|
7956
|
+
const bundleRoot = join31(agentAssetsRoot, "bundles");
|
|
6909
7957
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
6910
7958
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
6911
7959
|
for (const file of bundleFiles) {
|
|
6912
|
-
const bundle = readJson4(
|
|
7960
|
+
const bundle = readJson4(join31(bundleRoot, file));
|
|
6913
7961
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
6914
7962
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
6915
7963
|
}
|
|
6916
7964
|
const sourceRoots = [
|
|
6917
|
-
|
|
6918
|
-
|
|
7965
|
+
join31(agentAssetsRoot, "skills/pie-skills"),
|
|
7966
|
+
join31(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
6919
7967
|
];
|
|
6920
7968
|
const source = sourceRoots.reduce(
|
|
6921
|
-
(count, root) => count + safeReadDir(root).filter((name) =>
|
|
7969
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync31(join31(root, name, "SKILL.md"))).length,
|
|
6922
7970
|
0
|
|
6923
7971
|
) + registeredSkills.filter(
|
|
6924
|
-
(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))
|
|
6925
7973
|
).length;
|
|
6926
7974
|
return {
|
|
6927
7975
|
health: {
|
|
@@ -6938,7 +7986,7 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6938
7986
|
return [
|
|
6939
7987
|
{
|
|
6940
7988
|
id: asset.id,
|
|
6941
|
-
sourceRealPath: safeRealpath(
|
|
7989
|
+
sourceRealPath: safeRealpath(join31(agentAssetsRoot, asset.sourcePath)),
|
|
6942
7990
|
defaultPlacement: asset.defaultPlacement,
|
|
6943
7991
|
defaultScope: asset.defaultScope === "user" ? "user" : "project"
|
|
6944
7992
|
}
|
|
@@ -6953,15 +8001,15 @@ function inspectUserSkillEvidence(root) {
|
|
|
6953
8001
|
for (const name of safeReadDir(root)) {
|
|
6954
8002
|
if (name.startsWith(".")) continue;
|
|
6955
8003
|
names.add(name);
|
|
6956
|
-
const realPath = safeRealpath(
|
|
8004
|
+
const realPath = safeRealpath(join31(root, name));
|
|
6957
8005
|
if (realPath) realPaths.add(realPath);
|
|
6958
8006
|
}
|
|
6959
8007
|
return { names, realPaths };
|
|
6960
8008
|
}
|
|
6961
8009
|
|
|
6962
8010
|
// src/portfolio/ai-health/technology.ts
|
|
6963
|
-
import { existsSync as
|
|
6964
|
-
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";
|
|
6965
8013
|
function buildTechnologyMatrix(governance, repositories) {
|
|
6966
8014
|
if (!governance || governance.technologies.length === 0) return [];
|
|
6967
8015
|
const policy = governance.versionPolicy;
|
|
@@ -6988,8 +8036,8 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6988
8036
|
}).filter((item) => item !== void 0);
|
|
6989
8037
|
}).flat();
|
|
6990
8038
|
const fileSignal = (technology.files ?? []).some(
|
|
6991
|
-
(path) => hasUsableTechnologyFile(
|
|
6992
|
-
(manifest) => hasUsableTechnologyFile(
|
|
8039
|
+
(path) => hasUsableTechnologyFile(join32(repository.path, path)) || packageManifests.some(
|
|
8040
|
+
(manifest) => hasUsableTechnologyFile(join32(manifest.directory, path))
|
|
6993
8041
|
)
|
|
6994
8042
|
);
|
|
6995
8043
|
const modelSignal = [
|
|
@@ -7080,14 +8128,14 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
7080
8128
|
}).filter((technology) => technology.projectCount > 0);
|
|
7081
8129
|
}
|
|
7082
8130
|
function hasUsableTechnologyFile(path) {
|
|
7083
|
-
if (!
|
|
8131
|
+
if (!existsSync32(path)) return false;
|
|
7084
8132
|
try {
|
|
7085
|
-
const info =
|
|
8133
|
+
const info = statSync11(path);
|
|
7086
8134
|
if (info.isFile()) return true;
|
|
7087
8135
|
if (!info.isDirectory()) return false;
|
|
7088
|
-
return
|
|
8136
|
+
return readdirSync14(path, { withFileTypes: true }).some((entry) => {
|
|
7089
8137
|
if (entry.name.startsWith(".")) return false;
|
|
7090
|
-
const child =
|
|
8138
|
+
const child = join32(path, entry.name);
|
|
7091
8139
|
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
7092
8140
|
return entry.name.toLowerCase() !== "readme.md";
|
|
7093
8141
|
});
|
|
@@ -7099,7 +8147,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
|
|
|
7099
8147
|
const projectType = endpoint.projectType;
|
|
7100
8148
|
return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
|
|
7101
8149
|
if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
|
|
7102
|
-
const paths = rule.paths.filter((path) =>
|
|
8150
|
+
const paths = rule.paths.filter((path) => existsSync32(join32(root, path)));
|
|
7103
8151
|
return paths.length > 0 ? [{ rule, paths }] : [];
|
|
7104
8152
|
});
|
|
7105
8153
|
}
|
|
@@ -7114,7 +8162,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
7114
8162
|
const detection = (id) => {
|
|
7115
8163
|
const technology = technologyById.get(id);
|
|
7116
8164
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
7117
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
8165
|
+
const fileMatch = technology?.files?.some((path) => existsSync32(join32(root, path))) ?? false;
|
|
7118
8166
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
7119
8167
|
};
|
|
7120
8168
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -7153,8 +8201,8 @@ function collectPackageNames(root) {
|
|
|
7153
8201
|
}
|
|
7154
8202
|
|
|
7155
8203
|
// src/portfolio/ai-health/report.ts
|
|
7156
|
-
import { cpSync as cpSync3, existsSync as
|
|
7157
|
-
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";
|
|
7158
8206
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7159
8207
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
7160
8208
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
@@ -7195,36 +8243,36 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
|
7195
8243
|
};
|
|
7196
8244
|
}
|
|
7197
8245
|
function writePortfolioAiHealthReport(report, outDir) {
|
|
7198
|
-
|
|
8246
|
+
mkdirSync11(outDir, { recursive: true });
|
|
7199
8247
|
const dashboardAssets = findDashboardAssets();
|
|
7200
8248
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
7201
|
-
const source =
|
|
7202
|
-
if (!
|
|
7203
|
-
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));
|
|
7204
8252
|
}
|
|
7205
|
-
const jsonPath =
|
|
7206
|
-
const htmlPath =
|
|
7207
|
-
|
|
8253
|
+
const jsonPath = join33(outDir, "portfolio-ai-health.json");
|
|
8254
|
+
const htmlPath = join33(outDir, "index.html");
|
|
8255
|
+
writeFileSync10(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
7208
8256
|
`);
|
|
7209
|
-
|
|
7210
|
-
|
|
8257
|
+
writeFileSync10(
|
|
8258
|
+
join33(outDir, "data.js"),
|
|
7211
8259
|
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
|
|
7212
8260
|
`
|
|
7213
8261
|
);
|
|
7214
8262
|
return { jsonPath, htmlPath };
|
|
7215
8263
|
}
|
|
7216
8264
|
function findDashboardAssets() {
|
|
7217
|
-
const packageRoot2 =
|
|
8265
|
+
const packageRoot2 = dirname17(dirname17(fileURLToPath5(import.meta.url)));
|
|
7218
8266
|
const candidates = [
|
|
7219
8267
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
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")
|
|
7226
8274
|
].filter((value) => Boolean(value));
|
|
7227
|
-
const match = candidates.find((path) =>
|
|
8275
|
+
const match = candidates.find((path) => existsSync33(join33(path, "index.html")));
|
|
7228
8276
|
if (!match)
|
|
7229
8277
|
throw new Error(
|
|
7230
8278
|
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -7242,8 +8290,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7242
8290
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
7243
8291
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
7244
8292
|
}
|
|
7245
|
-
const secretsRoot = options.secretsRoot ??
|
|
7246
|
-
|
|
8293
|
+
const secretsRoot = options.secretsRoot ?? join34(
|
|
8294
|
+
dirname18(
|
|
7247
8295
|
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
7248
8296
|
),
|
|
7249
8297
|
".secrets"
|
|
@@ -7252,9 +8300,9 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7252
8300
|
const grokVersion = commandVersion("grok");
|
|
7253
8301
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
7254
8302
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
7255
|
-
const userSkills = inspectUserSkillEvidence(
|
|
8303
|
+
const userSkills = inspectUserSkillEvidence(join34(homeDir, ".agents/skills"));
|
|
7256
8304
|
const expectedPackageVersion = packageVersion(
|
|
7257
|
-
|
|
8305
|
+
join34(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
7258
8306
|
);
|
|
7259
8307
|
const repositories = endpoints.map(
|
|
7260
8308
|
({ endpoint, role }) => inspectRepository(
|
|
@@ -7311,7 +8359,7 @@ function collectEndpoints(manifest) {
|
|
|
7311
8359
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
7312
8360
|
const seen = /* @__PURE__ */ new Set();
|
|
7313
8361
|
return result.filter(({ endpoint }) => {
|
|
7314
|
-
const key =
|
|
8362
|
+
const key = resolve10(endpoint.path);
|
|
7315
8363
|
if (seen.has(key)) return false;
|
|
7316
8364
|
seen.add(key);
|
|
7317
8365
|
return true;
|
|
@@ -7328,13 +8376,13 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
7328
8376
|
const hooks = inspectHooks(root);
|
|
7329
8377
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
7330
8378
|
const mcp = {
|
|
7331
|
-
codexProject: tomlMcpNames(
|
|
8379
|
+
codexProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.codex)),
|
|
7332
8380
|
claudeCodeProjectShared: jsonObjectKeys(
|
|
7333
|
-
|
|
8381
|
+
join34(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
|
|
7334
8382
|
"mcpServers"
|
|
7335
8383
|
),
|
|
7336
8384
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
7337
|
-
grokProject: tomlMcpNames(
|
|
8385
|
+
grokProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.grok)),
|
|
7338
8386
|
grokEffective: grokInspection.effectiveMcp,
|
|
7339
8387
|
grokInspection: grokInspection.inspection
|
|
7340
8388
|
};
|
|
@@ -7968,11 +9016,11 @@ function isHost2(value) {
|
|
|
7968
9016
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
7969
9017
|
}
|
|
7970
9018
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
7971
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
7972
|
-
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;
|
|
7973
9021
|
}
|
|
7974
9022
|
function reportMissingPortfolioRegistry(loaded, json) {
|
|
7975
|
-
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";
|
|
7976
9024
|
const displayExpectedPath = expectedPath.replaceAll("\\", "/");
|
|
7977
9025
|
const issue = {
|
|
7978
9026
|
type: "missing-control-plane-registry",
|
|
@@ -8011,10 +9059,10 @@ function printUsage5() {
|
|
|
8011
9059
|
}
|
|
8012
9060
|
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
8013
9061
|
if (!outDir) return void 0;
|
|
8014
|
-
const path =
|
|
8015
|
-
if (!
|
|
9062
|
+
const path = join35(outDir, "portfolio-ai-health.json");
|
|
9063
|
+
if (!existsSync34(path)) return void 0;
|
|
8016
9064
|
try {
|
|
8017
|
-
const value = JSON.parse(
|
|
9065
|
+
const value = JSON.parse(readFileSync19(path, "utf8"));
|
|
8018
9066
|
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
|
|
8019
9067
|
return void 0;
|
|
8020
9068
|
return value;
|
|
@@ -8024,8 +9072,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
|
|
|
8024
9072
|
}
|
|
8025
9073
|
|
|
8026
9074
|
// src/commands/sync.ts
|
|
8027
|
-
import { existsSync as
|
|
8028
|
-
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";
|
|
8029
9077
|
function runSync(args) {
|
|
8030
9078
|
const check = args.includes("--check");
|
|
8031
9079
|
if (!check) {
|
|
@@ -8053,7 +9101,7 @@ function runSync(args) {
|
|
|
8053
9101
|
console.log("pro-gov sync check");
|
|
8054
9102
|
console.log(`profile: ${profile}`);
|
|
8055
9103
|
for (const file of planStarterFiles(profile)) {
|
|
8056
|
-
const targetPath =
|
|
9104
|
+
const targetPath = join36(process.cwd(), file.targetPath);
|
|
8057
9105
|
const stat = safeLstat3(targetPath);
|
|
8058
9106
|
if (!stat) {
|
|
8059
9107
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -8077,8 +9125,8 @@ function runSync(args) {
|
|
|
8077
9125
|
}
|
|
8078
9126
|
continue;
|
|
8079
9127
|
}
|
|
8080
|
-
const source =
|
|
8081
|
-
const target =
|
|
9128
|
+
const source = readFileSync20(file.absoluteSourcePath, "utf8");
|
|
9129
|
+
const target = readFileSync20(targetPath, "utf8");
|
|
8082
9130
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
8083
9131
|
console.log(`different: ${file.targetPath}`);
|
|
8084
9132
|
differences += 1;
|
|
@@ -8112,13 +9160,13 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
8112
9160
|
}
|
|
8113
9161
|
function inferInstalledProfile(root) {
|
|
8114
9162
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
8115
|
-
(profile) =>
|
|
9163
|
+
(profile) => existsSync35(join36(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
8116
9164
|
);
|
|
8117
9165
|
return installed.length === 1 ? installed[0] : void 0;
|
|
8118
9166
|
}
|
|
8119
9167
|
function safeLstat3(path) {
|
|
8120
9168
|
try {
|
|
8121
|
-
return
|
|
9169
|
+
return lstatSync15(path);
|
|
8122
9170
|
} catch {
|
|
8123
9171
|
return void 0;
|
|
8124
9172
|
}
|
|
@@ -8139,6 +9187,7 @@ var COMMANDS = [
|
|
|
8139
9187
|
"assets check [--target <path>] [--strict-registry] [--json]",
|
|
8140
9188
|
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
8141
9189
|
"assets npx add|update ... --plan",
|
|
9190
|
+
"assets catalog build|check [--native-links] [--json]",
|
|
8142
9191
|
"portfolio check --config <path> [--json]",
|
|
8143
9192
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
8144
9193
|
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|