@pieai/pro-gov 0.9.3 → 0.9.5
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 +1589 -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,977 @@ 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 < b ? -1 : a > b ? 1 : 0)) {
|
|
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(
|
|
629
|
+
(left, right) => compareStableStrings(left.relativePath, right.relativePath)
|
|
630
|
+
);
|
|
631
|
+
for (let index = 1; index < sorted.length; index += 1) {
|
|
632
|
+
if (sorted[index - 1].relativePath === sorted[index].relativePath) {
|
|
633
|
+
throw new Error(`Duplicate native catalog link path: ${sorted[index].relativePath}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return sorted;
|
|
637
|
+
}
|
|
638
|
+
function writePortableCatalog(catalogRoot, files) {
|
|
639
|
+
mkdirSync(catalogRoot, { recursive: true });
|
|
640
|
+
const warnings = [];
|
|
641
|
+
const previous = readPortableManifest(catalogRoot);
|
|
642
|
+
const previousFiles = new Set(previous?.portableFiles ?? []);
|
|
643
|
+
const expectedFiles = [...files.keys()].sort();
|
|
644
|
+
for (const relativePath of previousFiles) {
|
|
645
|
+
assertManagedPortablePath(relativePath);
|
|
646
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
647
|
+
if (pathEntryExists(targetPath)) {
|
|
648
|
+
const stats = lstatSync(targetPath);
|
|
649
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
650
|
+
throw new Error(
|
|
651
|
+
`Refusing to replace a managed file that is no longer regular: ${relativePath}`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const relativePath of expectedFiles) {
|
|
657
|
+
assertManagedPortablePath(relativePath);
|
|
658
|
+
assertCatalogPathParents(catalogRoot, relativePath);
|
|
659
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
660
|
+
if (pathEntryExists(targetPath) && !previousFiles.has(relativePath)) {
|
|
661
|
+
throw new Error(`Refusing to replace unmanaged catalog path: ${relativePath}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const legacyDirectories = findLegacyManagedDirectories(catalogRoot);
|
|
665
|
+
const stageRoot = mkdtempSync(join3(dirname2(catalogRoot), ".asset-catalog-stage-"));
|
|
666
|
+
const backupRoot = mkdtempSync(join3(dirname2(catalogRoot), ".asset-catalog-backup-"));
|
|
667
|
+
const installed = [];
|
|
668
|
+
const backedUp = [];
|
|
669
|
+
const legacyBackedUp = [];
|
|
670
|
+
try {
|
|
671
|
+
for (const [relativePath, content] of files) {
|
|
672
|
+
const stagedPath = resolveCatalogPath(stageRoot, relativePath);
|
|
673
|
+
mkdirSync(dirname2(stagedPath), { recursive: true });
|
|
674
|
+
writeFileSync(stagedPath, content);
|
|
675
|
+
}
|
|
676
|
+
writeFileSync(join3(stageRoot, portableManifestName), renderPortableManifest(files));
|
|
677
|
+
for (const relativePath of [...previousFiles, portableManifestName]) {
|
|
678
|
+
const sourcePath = resolveCatalogPath(catalogRoot, relativePath);
|
|
679
|
+
if (!pathEntryExists(sourcePath)) continue;
|
|
680
|
+
const backupPath = resolveCatalogPath(backupRoot, `portable/${relativePath}`);
|
|
681
|
+
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
682
|
+
renameSync(sourcePath, backupPath);
|
|
683
|
+
backedUp.push(relativePath);
|
|
684
|
+
}
|
|
685
|
+
for (const name of legacyDirectories) {
|
|
686
|
+
const sourcePath = join3(catalogRoot, name);
|
|
687
|
+
const backupPath = resolveCatalogPath(backupRoot, `legacy/${name}`);
|
|
688
|
+
mkdirSync(dirname2(backupPath), { recursive: true });
|
|
689
|
+
renameSync(sourcePath, backupPath);
|
|
690
|
+
legacyBackedUp.push(name);
|
|
691
|
+
}
|
|
692
|
+
for (const relativePath of [...expectedFiles, portableManifestName]) {
|
|
693
|
+
const sourcePath = resolveCatalogPath(stageRoot, relativePath);
|
|
694
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
695
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
696
|
+
renameSync(sourcePath, targetPath);
|
|
697
|
+
installed.push(relativePath);
|
|
698
|
+
}
|
|
699
|
+
for (const relativePath of previousFiles) {
|
|
700
|
+
pruneEmptyCatalogParents(catalogRoot, dirname2(relativePath));
|
|
701
|
+
}
|
|
702
|
+
} catch (error) {
|
|
703
|
+
for (const relativePath of installed.reverse()) {
|
|
704
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
705
|
+
if (pathEntryExists(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
706
|
+
}
|
|
707
|
+
for (const relativePath of backedUp.reverse()) {
|
|
708
|
+
const backupPath = resolveCatalogPath(backupRoot, `portable/${relativePath}`);
|
|
709
|
+
const targetPath = resolveCatalogPath(catalogRoot, relativePath);
|
|
710
|
+
if (!pathEntryExists(backupPath)) continue;
|
|
711
|
+
mkdirSync(dirname2(targetPath), { recursive: true });
|
|
712
|
+
renameSync(backupPath, targetPath);
|
|
713
|
+
}
|
|
714
|
+
for (const name of legacyBackedUp.reverse()) {
|
|
715
|
+
const backupPath = resolveCatalogPath(backupRoot, `legacy/${name}`);
|
|
716
|
+
const targetPath = join3(catalogRoot, name);
|
|
717
|
+
if (pathEntryExists(backupPath)) renameSync(backupPath, targetPath);
|
|
718
|
+
}
|
|
719
|
+
throw error;
|
|
720
|
+
} finally {
|
|
721
|
+
removeTemporaryTree(stageRoot, warnings);
|
|
722
|
+
removeTemporaryTree(backupRoot, warnings);
|
|
723
|
+
}
|
|
724
|
+
return warnings;
|
|
725
|
+
}
|
|
726
|
+
function renderPortableManifest(files) {
|
|
727
|
+
const manifest = {
|
|
728
|
+
schemaVersion: 1,
|
|
729
|
+
generator: manifestGenerator,
|
|
730
|
+
portableFiles: [...files.keys()].sort()
|
|
731
|
+
};
|
|
732
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
733
|
+
`;
|
|
734
|
+
}
|
|
735
|
+
function renderLocalLinksManifest(links) {
|
|
736
|
+
const manifest = {
|
|
737
|
+
schemaVersion: 1,
|
|
738
|
+
generator: manifestGenerator,
|
|
739
|
+
links: [...links].sort()
|
|
740
|
+
};
|
|
741
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
742
|
+
`;
|
|
743
|
+
}
|
|
744
|
+
function readPortableManifest(catalogRoot) {
|
|
745
|
+
const path = join3(catalogRoot, portableManifestName);
|
|
746
|
+
if (!existsSync3(path)) return void 0;
|
|
747
|
+
const value = readManifest(path, "portableFiles");
|
|
748
|
+
for (const relativePath of value.items) assertManagedPortablePath(relativePath);
|
|
749
|
+
return {
|
|
750
|
+
schemaVersion: 1,
|
|
751
|
+
generator: manifestGenerator,
|
|
752
|
+
portableFiles: value.items
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function readLocalLinksManifest(catalogRoot) {
|
|
756
|
+
const path = join3(catalogRoot, localLinksManifestName);
|
|
757
|
+
if (!existsSync3(path)) return void 0;
|
|
758
|
+
const value = readManifest(path, "links");
|
|
759
|
+
for (const relativePath of value.items) assertManagedLocalLinkPath(relativePath);
|
|
760
|
+
return { schemaVersion: 1, generator: manifestGenerator, links: value.items };
|
|
761
|
+
}
|
|
762
|
+
function readManifest(path, listKey) {
|
|
763
|
+
const stats = lstatSync(path);
|
|
764
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
765
|
+
throw new Error(`Invalid asset catalog manifest file: ${path}`);
|
|
766
|
+
}
|
|
767
|
+
let parsed;
|
|
768
|
+
try {
|
|
769
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
770
|
+
} catch {
|
|
771
|
+
throw new Error(`Invalid asset catalog manifest JSON: ${path}`);
|
|
772
|
+
}
|
|
773
|
+
const items = parsed[listKey];
|
|
774
|
+
if (parsed.schemaVersion !== 1 || parsed.generator !== manifestGenerator || !Array.isArray(items) || items.some((item) => typeof item !== "string") || new Set(items).size !== items.length) {
|
|
775
|
+
throw new Error(`Invalid asset catalog manifest shape: ${path}`);
|
|
776
|
+
}
|
|
777
|
+
return { items: [...items].sort() };
|
|
778
|
+
}
|
|
779
|
+
function findLegacyManagedDirectories(catalogRoot) {
|
|
780
|
+
const result = [];
|
|
781
|
+
for (const name of legacyManagedDirectoryNames) {
|
|
782
|
+
const root = join3(catalogRoot, name);
|
|
783
|
+
if (!existsSync3(root)) continue;
|
|
784
|
+
const stats = lstatSync(root);
|
|
785
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
786
|
+
throw new Error(`Refusing to migrate non-directory legacy catalog output: ${root}`);
|
|
787
|
+
}
|
|
788
|
+
const sentinel = join3(root, legacyManagedSentinel);
|
|
789
|
+
if (!existsSync3(sentinel) || !lstatSync(sentinel).isFile()) {
|
|
790
|
+
throw new Error(`Refusing to migrate unmanaged legacy catalog directory: ${root}`);
|
|
791
|
+
}
|
|
792
|
+
result.push(name);
|
|
793
|
+
}
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
796
|
+
function assertManagedPortablePath(relativePath) {
|
|
797
|
+
if (relativePath !== "INDEX.md" && relativePath !== "index.html" && relativePath !== "catalog.json" && !/^by-topic\/[a-z0-9]+(?:-[a-z0-9]+)*\/README\.md$/u.test(relativePath)) {
|
|
798
|
+
throw new Error(`Unsafe managed portable catalog path: ${relativePath}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function assertManagedLocalLinkPath(relativePath) {
|
|
802
|
+
if (!/^by-topic\/[a-z0-9]+(?:-[a-z0-9]+)*\/[a-z0-9][a-z0-9._-]*\.asset-link$/u.test(relativePath)) {
|
|
803
|
+
throw new Error(`Unsafe managed local catalog link path: ${relativePath}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function resolveCatalogPath(root, relativePath) {
|
|
807
|
+
const absoluteRoot = resolve(root);
|
|
808
|
+
const absolutePath = resolve(absoluteRoot, ...normalizeRelativePath(relativePath).split("/"));
|
|
809
|
+
const back = normalizeRelativePath(relative2(absoluteRoot, absolutePath));
|
|
810
|
+
if (back === ".." || back.startsWith("../") || isAbsolute(back)) {
|
|
811
|
+
throw new Error(`Catalog path escapes its managed root: ${relativePath}`);
|
|
812
|
+
}
|
|
813
|
+
return absolutePath;
|
|
814
|
+
}
|
|
815
|
+
function assertCatalogPathParents(catalogRoot, relativePath) {
|
|
816
|
+
const absoluteRoot = resolve(catalogRoot);
|
|
817
|
+
let current = dirname2(resolveCatalogPath(catalogRoot, relativePath));
|
|
818
|
+
while (current !== absoluteRoot) {
|
|
819
|
+
if (existsSync3(current)) {
|
|
820
|
+
const stats = lstatSync(current);
|
|
821
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Refusing to write through a non-directory catalog parent: ${normalizeRelativePath(relative2(absoluteRoot, current))}`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const parent = dirname2(current);
|
|
828
|
+
if (parent === current) throw new Error(`Catalog parent escapes its root: ${relativePath}`);
|
|
829
|
+
current = parent;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function pruneEmptyCatalogParents(catalogRoot, relativeDirectory) {
|
|
833
|
+
const absoluteRoot = resolve(catalogRoot);
|
|
834
|
+
let current = resolveCatalogPath(catalogRoot, relativeDirectory);
|
|
835
|
+
while (current !== absoluteRoot && existsSync3(current)) {
|
|
836
|
+
const stats = lstatSync(current);
|
|
837
|
+
if (!stats.isDirectory() || stats.isSymbolicLink() || readdirSync3(current).length > 0) return;
|
|
838
|
+
rmSync(current, { recursive: true, force: true });
|
|
839
|
+
current = dirname2(current);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function pathEntryExists(path) {
|
|
843
|
+
try {
|
|
844
|
+
lstatSync(path);
|
|
845
|
+
return true;
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (error.code === "ENOENT") return false;
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function removeTemporaryTree(path, warnings) {
|
|
852
|
+
try {
|
|
853
|
+
rmSync(path, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
854
|
+
} catch (error) {
|
|
855
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
856
|
+
warnings.push(`temporary catalog cleanup pending (${path}): ${reason}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function findUncoveredSourcePaths(agentAssetsDir, entries, ignoredSourcePaths) {
|
|
860
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
861
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, "skills"))) {
|
|
862
|
+
if (basename(filePath) === "SKILL.md") {
|
|
863
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, dirname2(filePath))));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
for (const root of ["rules", "commands", "prompts"]) {
|
|
867
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, root))) {
|
|
868
|
+
if (extname(filePath).toLowerCase() === ".md") {
|
|
869
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, filePath)));
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
for (const filePath of listFiles2(join3(agentAssetsDir, "tooling"))) {
|
|
874
|
+
candidates.add(normalizeRelativePath(relative2(agentAssetsDir, filePath)));
|
|
875
|
+
}
|
|
876
|
+
return [...candidates].filter((candidate) => !isCoveredByAnySource(candidate, entries, ignoredSourcePaths)).sort();
|
|
877
|
+
}
|
|
878
|
+
function isCoveredByAnySource(candidate, entries, ignoredSourcePaths) {
|
|
879
|
+
const covers = (sourcePath) => candidate === sourcePath || candidate.startsWith(`${sourcePath}/`);
|
|
880
|
+
return entries.some((entry) => covers(entry.sourcePath)) || ignoredSourcePaths.some(covers);
|
|
881
|
+
}
|
|
882
|
+
function listFiles2(root) {
|
|
883
|
+
if (!existsSync3(root)) return [];
|
|
884
|
+
const stats = lstatSync(root);
|
|
885
|
+
if (stats.isSymbolicLink()) return [];
|
|
886
|
+
if (stats.isFile()) return [root];
|
|
887
|
+
const files = [];
|
|
888
|
+
for (const entry of readdirSync3(root, { withFileTypes: true })) {
|
|
889
|
+
if (ignoredTraversalNames.has(entry.name) || entry.name.startsWith("._")) continue;
|
|
890
|
+
const path = join3(root, entry.name);
|
|
891
|
+
if (entry.isDirectory()) files.push(...listFiles2(path));
|
|
892
|
+
else if (entry.isFile()) files.push(path);
|
|
893
|
+
}
|
|
894
|
+
return files.sort();
|
|
895
|
+
}
|
|
896
|
+
function readAssetDescription(sourceAbsolutePath, sourceType, fallback) {
|
|
897
|
+
const documentPath = sourceType === "directory" ? join3(sourceAbsolutePath, "SKILL.md") : sourceAbsolutePath;
|
|
898
|
+
if (!existsSync3(documentPath)) return fallback;
|
|
899
|
+
const text = readFileSync2(documentPath, "utf8");
|
|
900
|
+
const frontmatter = text.match(/^---\r?\n([\s\S]*?)\r?\n---/u)?.[1] ?? "";
|
|
901
|
+
const description = extractFrontmatterDescription(frontmatter);
|
|
902
|
+
if (description) return compactDescription(description, fallback);
|
|
903
|
+
const withoutFrontmatter = text.replace(/^---\r?\n[\s\S]*?\r?\n---\s*/u, "");
|
|
904
|
+
const paragraph = withoutFrontmatter.split(/\r?\n\r?\n/u).map((part) => part.replace(/\r?\n/g, " ").trim()).find((part) => part && !part.startsWith("#") && !part.startsWith("```"));
|
|
905
|
+
return compactDescription(paragraph ?? fallback, fallback);
|
|
906
|
+
}
|
|
907
|
+
function extractFrontmatterDescription(frontmatter) {
|
|
908
|
+
const lines = frontmatter.split(/\r?\n/u);
|
|
909
|
+
const index = lines.findIndex((line) => /^description\s*:/u.test(line));
|
|
910
|
+
if (index < 0) return void 0;
|
|
911
|
+
const inlineValue = lines[index].replace(/^description\s*:\s*/u, "").trim();
|
|
912
|
+
if (!/^[|>][+-]?$/u.test(inlineValue)) {
|
|
913
|
+
return inlineValue ? stripWrappingQuotes(inlineValue) : void 0;
|
|
914
|
+
}
|
|
915
|
+
const block = [];
|
|
916
|
+
for (const line of lines.slice(index + 1)) {
|
|
917
|
+
if (line.trim() && !/^\s/u.test(line)) break;
|
|
918
|
+
block.push(line.replace(/^\s+/u, "").trim());
|
|
919
|
+
}
|
|
920
|
+
const separator = inlineValue.startsWith("|") ? "\n" : " ";
|
|
921
|
+
const value = block.join(separator).trim();
|
|
922
|
+
return value || void 0;
|
|
923
|
+
}
|
|
924
|
+
function compactDescription(value, fallback) {
|
|
925
|
+
const compact = value.replace(/\[([^\]]+)]\([^)]*\)/g, "$1").replace(/[`*_>#]/g, "").replace(/\s+/g, " ").trim();
|
|
926
|
+
if (!compact) return fallback;
|
|
927
|
+
return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
|
|
928
|
+
}
|
|
929
|
+
function stripWrappingQuotes(value) {
|
|
930
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
931
|
+
return value.slice(1, -1);
|
|
932
|
+
}
|
|
933
|
+
return value;
|
|
934
|
+
}
|
|
935
|
+
function resolveSafeSourcePath(agentAssetsDir, sourcePath, id) {
|
|
936
|
+
if (!isSafeSourcePath(sourcePath)) throw new Error(`Unsafe sourcePath for ${id}: ${sourcePath}`);
|
|
937
|
+
const absolutePath = resolve(agentAssetsDir, ...normalizeRelativePath(sourcePath).split("/"));
|
|
938
|
+
const relativePath = normalizeRelativePath(relative2(resolve(agentAssetsDir), absolutePath));
|
|
939
|
+
if (relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath)) {
|
|
940
|
+
throw new Error(`Source path escapes agent-assets for ${id}: ${sourcePath}`);
|
|
941
|
+
}
|
|
942
|
+
if (!existsSync3(absolutePath)) throw new Error(`Missing catalog source for ${id}: ${sourcePath}`);
|
|
943
|
+
return absolutePath;
|
|
944
|
+
}
|
|
945
|
+
function isSafeSourcePath(sourcePath) {
|
|
946
|
+
if (!sourcePath || isAbsolute(sourcePath) || sourcePath.includes("\\")) return false;
|
|
947
|
+
const normalized = normalizeRelativePath(sourcePath);
|
|
948
|
+
return normalized !== "." && normalized !== ".." && !normalized.startsWith("../");
|
|
949
|
+
}
|
|
950
|
+
function createBundleMembership(bundles) {
|
|
951
|
+
const membership = /* @__PURE__ */ new Map();
|
|
952
|
+
for (const bundle of bundles) {
|
|
953
|
+
for (const assetId of bundle.assets) {
|
|
954
|
+
membership.set(assetId, [...membership.get(assetId) ?? [], bundle.id]);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
for (const [id, bundleIds] of membership) membership.set(id, uniqueSorted(bundleIds));
|
|
958
|
+
return membership;
|
|
959
|
+
}
|
|
960
|
+
function assertUniqueEntries(entries) {
|
|
961
|
+
const ids = /* @__PURE__ */ new Set();
|
|
962
|
+
for (const entry of entries) {
|
|
963
|
+
if (ids.has(entry.id)) throw new Error(`Duplicate catalog entry id: ${entry.id}`);
|
|
964
|
+
ids.add(entry.id);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function assertKnownTopics(id, topics, topicIds) {
|
|
968
|
+
for (const topic of topics) {
|
|
969
|
+
if (!topicIds.has(topic)) throw new Error(`Unknown topic ${topic} on catalog entry ${id}`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function nativeLinkName(entry) {
|
|
973
|
+
const base = safePathSegment(entry.id.replace("/", "--"));
|
|
974
|
+
return `${base}.asset-link`;
|
|
975
|
+
}
|
|
976
|
+
function safePathSegment(value) {
|
|
977
|
+
const safe = value.normalize("NFKD").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
978
|
+
if (!safe || safe === "." || safe === "..")
|
|
979
|
+
throw new Error(`Unsafe catalog path segment: ${value}`);
|
|
980
|
+
return safe;
|
|
981
|
+
}
|
|
982
|
+
function normalizeRelativePath(path) {
|
|
983
|
+
return path.replaceAll("\\", "/");
|
|
984
|
+
}
|
|
985
|
+
function encodeRelativeHref(path) {
|
|
986
|
+
return normalizeRelativePath(path).split("/").map((part) => part === "." || part === ".." ? part : encodeURIComponent(part)).join("/");
|
|
987
|
+
}
|
|
988
|
+
function uniqueSorted(values) {
|
|
989
|
+
return [...new Set(values)].sort(compareStableStrings);
|
|
990
|
+
}
|
|
991
|
+
function compareCatalogEntries(left, right) {
|
|
992
|
+
return compareStableStrings(
|
|
993
|
+
`${left.family}:${left.title}:${left.id}`,
|
|
994
|
+
`${right.family}:${right.title}:${right.id}`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
function compareStableStrings(left, right) {
|
|
998
|
+
if (left === right) return 0;
|
|
999
|
+
return left < right ? -1 : 1;
|
|
1000
|
+
}
|
|
1001
|
+
function escapeTable(value) {
|
|
1002
|
+
return value.replaceAll("|", "\\|").replace(/\r?\n/g, " ");
|
|
1003
|
+
}
|
|
1004
|
+
function escapeHtml(value) {
|
|
1005
|
+
return value.replace(/[&<>"']/g, (character) => {
|
|
1006
|
+
const entities = {
|
|
1007
|
+
"&": "&",
|
|
1008
|
+
"<": "<",
|
|
1009
|
+
">": ">",
|
|
1010
|
+
'"': """,
|
|
1011
|
+
"'": "'"
|
|
1012
|
+
};
|
|
1013
|
+
return entities[character] ?? character;
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
function sha256(value) {
|
|
1017
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/asset-npx/maintenance.ts
|
|
1021
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1022
|
+
import {
|
|
1023
|
+
cpSync,
|
|
1024
|
+
existsSync as existsSync4,
|
|
1025
|
+
mkdirSync as mkdirSync2,
|
|
1026
|
+
mkdtempSync as mkdtempSync2,
|
|
1027
|
+
readdirSync as readdirSync4,
|
|
1028
|
+
readFileSync as readFileSync3,
|
|
1029
|
+
statSync as statSync2
|
|
71
1030
|
} from "node:fs";
|
|
72
|
-
import { join as
|
|
1031
|
+
import { join as join4, relative as relative3 } from "node:path";
|
|
73
1032
|
import { tmpdir } from "node:os";
|
|
74
1033
|
|
|
75
1034
|
// src/command-runner.ts
|
|
@@ -100,7 +1059,7 @@ function createNpxSkillsMaintenancePlan(options) {
|
|
|
100
1059
|
throw new Error("npx skills add requires a source.");
|
|
101
1060
|
}
|
|
102
1061
|
const before = snapshotFiles(options.npxRoot);
|
|
103
|
-
const tempRoot =
|
|
1062
|
+
const tempRoot = mkdtempSync2(join4(tmpdir(), "pro-gov-npx-skills-"));
|
|
104
1063
|
cpSync(options.npxRoot, tempRoot, { recursive: true, dereference: false });
|
|
105
1064
|
const command2 = buildNpxCommand(options);
|
|
106
1065
|
const runner = options.runner ?? defaultRunner;
|
|
@@ -144,10 +1103,10 @@ ${stderr}`.replace(ansiEscape, "");
|
|
|
144
1103
|
}
|
|
145
1104
|
}
|
|
146
1105
|
function assertNativeNpxRoot(npxRoot) {
|
|
147
|
-
if (!
|
|
1106
|
+
if (!existsSync4(join4(npxRoot, "skills-lock.json"))) {
|
|
148
1107
|
throw new Error(`npx skills root is missing skills-lock.json: ${npxRoot}`);
|
|
149
1108
|
}
|
|
150
|
-
if (!
|
|
1109
|
+
if (!existsSync4(join4(npxRoot, ".agents/skills"))) {
|
|
151
1110
|
throw new Error(`npx skills root is missing .agents/skills: ${npxRoot}`);
|
|
152
1111
|
}
|
|
153
1112
|
}
|
|
@@ -178,21 +1137,21 @@ function defaultRunner({ command: command2, cwd, timeoutMs }) {
|
|
|
178
1137
|
}
|
|
179
1138
|
function snapshotFiles(root) {
|
|
180
1139
|
const snapshot = /* @__PURE__ */ new Map();
|
|
181
|
-
for (const filePath of
|
|
182
|
-
const relativePath = toUnixPath2(
|
|
1140
|
+
for (const filePath of listFiles3(root)) {
|
|
1141
|
+
const relativePath = toUnixPath2(relative3(root, filePath));
|
|
183
1142
|
snapshot.set(relativePath, hashFile(filePath));
|
|
184
1143
|
}
|
|
185
1144
|
return snapshot;
|
|
186
1145
|
}
|
|
187
|
-
function
|
|
1146
|
+
function listFiles3(root) {
|
|
188
1147
|
const files = [];
|
|
189
1148
|
collectFiles(root, root, files);
|
|
190
1149
|
return files.sort();
|
|
191
1150
|
}
|
|
192
1151
|
function collectFiles(root, current, files) {
|
|
193
|
-
|
|
194
|
-
for (const entry of
|
|
195
|
-
const entryPath =
|
|
1152
|
+
mkdirSync2(root, { recursive: true });
|
|
1153
|
+
for (const entry of readdirSync4(current, { withFileTypes: true })) {
|
|
1154
|
+
const entryPath = join4(current, entry.name);
|
|
196
1155
|
if (entry.isDirectory()) {
|
|
197
1156
|
collectFiles(root, entryPath, files);
|
|
198
1157
|
} else if (entry.isFile()) {
|
|
@@ -201,11 +1160,11 @@ function collectFiles(root, current, files) {
|
|
|
201
1160
|
}
|
|
202
1161
|
}
|
|
203
1162
|
function hashFile(path) {
|
|
204
|
-
const hash =
|
|
205
|
-
const stats =
|
|
1163
|
+
const hash = createHash2("sha256");
|
|
1164
|
+
const stats = statSync2(path);
|
|
206
1165
|
hash.update(String(stats.size));
|
|
207
1166
|
hash.update("\0");
|
|
208
|
-
hash.update(
|
|
1167
|
+
hash.update(readFileSync3(path));
|
|
209
1168
|
return hash.digest("hex");
|
|
210
1169
|
}
|
|
211
1170
|
function diffSnapshots(before, after) {
|
|
@@ -232,14 +1191,14 @@ function toUnixPath2(path) {
|
|
|
232
1191
|
}
|
|
233
1192
|
|
|
234
1193
|
// src/asset-registry/loader.ts
|
|
235
|
-
import { createHash as
|
|
236
|
-
import { existsSync as
|
|
237
|
-
import { dirname as
|
|
1194
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1195
|
+
import { existsSync as existsSync6, readdirSync as readdirSync6, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
1196
|
+
import { dirname as dirname3, join as join6, relative as relative5 } from "node:path";
|
|
238
1197
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
239
1198
|
|
|
240
1199
|
// src/asset-registry/registry.ts
|
|
241
|
-
import { existsSync as
|
|
242
|
-
import { isAbsolute, join as
|
|
1200
|
+
import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync5, realpathSync } from "node:fs";
|
|
1201
|
+
import { isAbsolute as isAbsolute2, join as join5, posix, relative as relative4, sep } from "node:path";
|
|
243
1202
|
var supportedFamilies = /* @__PURE__ */ new Set([
|
|
244
1203
|
"pie-skills",
|
|
245
1204
|
"npx-skills",
|
|
@@ -283,7 +1242,7 @@ function assetSkillInstallName(asset) {
|
|
|
283
1242
|
return asset.installName ?? posix.basename(asset.sourcePath);
|
|
284
1243
|
}
|
|
285
1244
|
function isValidAssetProjectTargetPath(kind, projectTargetPath) {
|
|
286
|
-
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 ||
|
|
1245
|
+
if (typeof projectTargetPath !== "string" || projectTargetPath.length === 0 || isAbsolute2(projectTargetPath) || projectTargetPath.includes("\\")) {
|
|
287
1246
|
return false;
|
|
288
1247
|
}
|
|
289
1248
|
const segments = projectTargetPath.split("/");
|
|
@@ -439,11 +1398,11 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
439
1398
|
});
|
|
440
1399
|
}
|
|
441
1400
|
if (options.agentAssetsDir && isSafeRegistrySourcePath(asset.sourcePath)) {
|
|
442
|
-
const sourceAbsolutePath =
|
|
1401
|
+
const sourceAbsolutePath = join5(
|
|
443
1402
|
options.agentAssetsDir,
|
|
444
1403
|
normalizeRegistrySourcePath(asset.sourcePath)
|
|
445
1404
|
);
|
|
446
|
-
if (!
|
|
1405
|
+
if (!existsSync5(sourceAbsolutePath)) {
|
|
447
1406
|
issues.push({
|
|
448
1407
|
type: "missing-source-path",
|
|
449
1408
|
id: asset.id,
|
|
@@ -467,7 +1426,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
467
1426
|
message: `Local skill pack must contain .codex-plugin/plugin.json and at least one skills/*/SKILL.md: ${asset.sourcePath}`
|
|
468
1427
|
});
|
|
469
1428
|
}
|
|
470
|
-
} else if (!
|
|
1429
|
+
} else if (!existsSync5(join5(sourceAbsolutePath, "SKILL.md"))) {
|
|
471
1430
|
issues.push({
|
|
472
1431
|
type: "missing-skill-file",
|
|
473
1432
|
id: asset.id,
|
|
@@ -479,7 +1438,7 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
479
1438
|
}
|
|
480
1439
|
}
|
|
481
1440
|
if (options.agentAssetsDir) {
|
|
482
|
-
const npxCompatibilityLayer =
|
|
1441
|
+
const npxCompatibilityLayer = join5(options.agentAssetsDir, "skills/npx-skills/skills");
|
|
483
1442
|
if (pathExistsEvenIfDanglingSymlink(npxCompatibilityLayer)) {
|
|
484
1443
|
issues.push({
|
|
485
1444
|
type: "internal-npx-compatibility-layer",
|
|
@@ -492,20 +1451,20 @@ function validateAssetRegistry(registry, options = {}) {
|
|
|
492
1451
|
return issues;
|
|
493
1452
|
}
|
|
494
1453
|
function isLocalSkillPack(sourceAbsolutePath) {
|
|
495
|
-
const skillsRoot =
|
|
496
|
-
if (!
|
|
1454
|
+
const skillsRoot = join5(sourceAbsolutePath, "skills");
|
|
1455
|
+
if (!existsSync5(join5(sourceAbsolutePath, ".codex-plugin/plugin.json")) || !existsSync5(skillsRoot)) {
|
|
497
1456
|
return false;
|
|
498
1457
|
}
|
|
499
1458
|
try {
|
|
500
|
-
return
|
|
501
|
-
(entry) => entry.isDirectory() &&
|
|
1459
|
+
return lstatSync2(skillsRoot).isDirectory() && readdirSync5(skillsRoot, { withFileTypes: true }).some(
|
|
1460
|
+
(entry) => entry.isDirectory() && existsSync5(join5(skillsRoot, entry.name, "SKILL.md"))
|
|
502
1461
|
);
|
|
503
1462
|
} catch {
|
|
504
1463
|
return false;
|
|
505
1464
|
}
|
|
506
1465
|
}
|
|
507
1466
|
function isSafeRegistrySourcePath(sourcePath) {
|
|
508
|
-
if (!sourcePath ||
|
|
1467
|
+
if (!sourcePath || isAbsolute2(sourcePath) || sourcePath.startsWith("/")) return false;
|
|
509
1468
|
const normalized = normalizeRegistrySourcePath(sourcePath);
|
|
510
1469
|
if (normalized === "." || normalized.startsWith("../") || normalized === "..") return false;
|
|
511
1470
|
return !normalized.split("/").includes("..");
|
|
@@ -517,15 +1476,15 @@ function isWithinAgentAssetsDir(agentAssetsDir, sourceAbsolutePath) {
|
|
|
517
1476
|
try {
|
|
518
1477
|
const agentAssetsRealPath = realpathSync(agentAssetsDir);
|
|
519
1478
|
const sourceRealPath = realpathSync(sourceAbsolutePath);
|
|
520
|
-
const relativePath =
|
|
521
|
-
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !
|
|
1479
|
+
const relativePath = relative4(agentAssetsRealPath, sourceRealPath);
|
|
1480
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute2(relativePath);
|
|
522
1481
|
} catch {
|
|
523
1482
|
return false;
|
|
524
1483
|
}
|
|
525
1484
|
}
|
|
526
1485
|
function pathExistsEvenIfDanglingSymlink(path) {
|
|
527
1486
|
try {
|
|
528
|
-
|
|
1487
|
+
lstatSync2(path);
|
|
529
1488
|
return true;
|
|
530
1489
|
} catch {
|
|
531
1490
|
return false;
|
|
@@ -542,7 +1501,7 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
542
1501
|
(left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0
|
|
543
1502
|
)
|
|
544
1503
|
});
|
|
545
|
-
const hash =
|
|
1504
|
+
const hash = createHash3("sha256").update(JSON.stringify(canonicalRegistry)).digest("hex");
|
|
546
1505
|
return {
|
|
547
1506
|
schema: "agent-assets-registry",
|
|
548
1507
|
version: registry.schemaVersion,
|
|
@@ -552,8 +1511,8 @@ function createAgentAssetRegistryProvenance(registry, selectedAssetIds) {
|
|
|
552
1511
|
}
|
|
553
1512
|
function loadAgentAssetRegistry(options = {}) {
|
|
554
1513
|
const agentAssetsDir = options.agentAssetsDir ?? findDefaultAgentAssetsDir();
|
|
555
|
-
const registryPath =
|
|
556
|
-
if (!
|
|
1514
|
+
const registryPath = join6(agentAssetsDir, "registry.json");
|
|
1515
|
+
if (!existsSync6(registryPath)) {
|
|
557
1516
|
return {
|
|
558
1517
|
registry: { schemaVersion: 1, assets: [] },
|
|
559
1518
|
agentAssetsDir,
|
|
@@ -561,7 +1520,7 @@ function loadAgentAssetRegistry(options = {}) {
|
|
|
561
1520
|
issues: []
|
|
562
1521
|
};
|
|
563
1522
|
}
|
|
564
|
-
const registry = JSON.parse(
|
|
1523
|
+
const registry = JSON.parse(readFileSync4(registryPath, "utf8"));
|
|
565
1524
|
return {
|
|
566
1525
|
registry,
|
|
567
1526
|
agentAssetsDir,
|
|
@@ -579,15 +1538,15 @@ function createAgentAssetLockEntries(registry, agentAssetsDir, assetIds) {
|
|
|
579
1538
|
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
580
1539
|
}
|
|
581
1540
|
function hashAgentAssetContent(asset, agentAssetsDir) {
|
|
582
|
-
return hashAssetPathContent(
|
|
1541
|
+
return hashAssetPathContent(join6(agentAssetsDir, asset.sourcePath));
|
|
583
1542
|
}
|
|
584
1543
|
function hashAssetPathContent(sourceAbsolutePath) {
|
|
585
|
-
const hash =
|
|
586
|
-
for (const filePath of
|
|
587
|
-
const relativePath = toUnixPath3(
|
|
1544
|
+
const hash = createHash3("sha256");
|
|
1545
|
+
for (const filePath of listFiles4(sourceAbsolutePath)) {
|
|
1546
|
+
const relativePath = toUnixPath3(relative5(sourceAbsolutePath, filePath));
|
|
588
1547
|
hash.update(relativePath);
|
|
589
1548
|
hash.update("\0");
|
|
590
|
-
hash.update(
|
|
1549
|
+
hash.update(readFileSync4(filePath));
|
|
591
1550
|
hash.update("\0");
|
|
592
1551
|
}
|
|
593
1552
|
return `sha256:${hash.digest("hex")}`;
|
|
@@ -602,40 +1561,40 @@ function canonicalizeValue(value) {
|
|
|
602
1561
|
return value;
|
|
603
1562
|
}
|
|
604
1563
|
function findDefaultAgentAssetsDir() {
|
|
605
|
-
const packageRoot2 = findPackageRoot(
|
|
606
|
-
const repoRoot =
|
|
1564
|
+
const packageRoot2 = findPackageRoot(dirname3(fileURLToPath2(import.meta.url)));
|
|
1565
|
+
const repoRoot = join6(packageRoot2, "..", "..");
|
|
607
1566
|
const candidates = [
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1567
|
+
join6(packageRoot2, "assets/agent-assets"),
|
|
1568
|
+
join6(repoRoot, "agent-assets"),
|
|
1569
|
+
join6(packageRoot2, "assets/public-agent-assets"),
|
|
1570
|
+
join6(repoRoot, "public-agent-assets")
|
|
612
1571
|
];
|
|
613
|
-
return candidates.find((candidate) =>
|
|
1572
|
+
return candidates.find((candidate) => existsSync6(join6(candidate, "registry.json"))) ?? candidates[0];
|
|
614
1573
|
}
|
|
615
1574
|
function findPackageRoot(startDir) {
|
|
616
1575
|
let current = startDir;
|
|
617
|
-
while (current !==
|
|
618
|
-
const packageJsonPath =
|
|
619
|
-
if (
|
|
1576
|
+
while (current !== dirname3(current)) {
|
|
1577
|
+
const packageJsonPath = join6(current, "package.json");
|
|
1578
|
+
if (existsSync6(packageJsonPath)) {
|
|
620
1579
|
try {
|
|
621
|
-
const packageJson = JSON.parse(
|
|
1580
|
+
const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf8"));
|
|
622
1581
|
if (packageJson.name === "@pieai/pro-gov") return current;
|
|
623
1582
|
} catch {
|
|
624
1583
|
}
|
|
625
1584
|
}
|
|
626
|
-
current =
|
|
1585
|
+
current = dirname3(current);
|
|
627
1586
|
}
|
|
628
1587
|
return startDir;
|
|
629
1588
|
}
|
|
630
|
-
function
|
|
631
|
-
const stats =
|
|
1589
|
+
function listFiles4(absolutePath) {
|
|
1590
|
+
const stats = statSync3(absolutePath);
|
|
632
1591
|
if (stats.isFile()) return [absolutePath];
|
|
633
1592
|
const files = [];
|
|
634
|
-
for (const entry of
|
|
1593
|
+
for (const entry of readdirSync6(absolutePath, { withFileTypes: true })) {
|
|
635
1594
|
if (shouldIgnoreAssetHashEntry(entry.name)) continue;
|
|
636
|
-
const entryPath =
|
|
1595
|
+
const entryPath = join6(absolutePath, entry.name);
|
|
637
1596
|
if (entry.isDirectory()) {
|
|
638
|
-
files.push(...
|
|
1597
|
+
files.push(...listFiles4(entryPath));
|
|
639
1598
|
} else if (entry.isFile()) {
|
|
640
1599
|
files.push(entryPath);
|
|
641
1600
|
}
|
|
@@ -650,8 +1609,8 @@ function toUnixPath3(path) {
|
|
|
650
1609
|
}
|
|
651
1610
|
|
|
652
1611
|
// src/asset-registry/public-promotion.ts
|
|
653
|
-
import { existsSync as
|
|
654
|
-
import { isAbsolute as
|
|
1612
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1613
|
+
import { isAbsolute as isAbsolute3, join as join7, posix as posix2 } from "node:path";
|
|
655
1614
|
function checkPublicAssetPromotions(options) {
|
|
656
1615
|
const issues = [];
|
|
657
1616
|
let checked = 0;
|
|
@@ -689,7 +1648,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
689
1648
|
});
|
|
690
1649
|
continue;
|
|
691
1650
|
}
|
|
692
|
-
if (!
|
|
1651
|
+
if (!existsSync7(privatePathResult.path)) {
|
|
693
1652
|
issues.push({
|
|
694
1653
|
type: "missing-private-source",
|
|
695
1654
|
id: asset.id,
|
|
@@ -709,7 +1668,7 @@ function checkPublicAssetPromotions(options) {
|
|
|
709
1668
|
});
|
|
710
1669
|
}
|
|
711
1670
|
}
|
|
712
|
-
if (!
|
|
1671
|
+
if (!existsSync7(publicPathResult.path)) {
|
|
713
1672
|
issues.push({
|
|
714
1673
|
type: "missing-public-source",
|
|
715
1674
|
id: asset.id,
|
|
@@ -736,32 +1695,32 @@ function needsPromotionCheck(asset) {
|
|
|
736
1695
|
return asset.visibility === "public" && asset.publishable;
|
|
737
1696
|
}
|
|
738
1697
|
function resolveSafePath(root, sourcePath) {
|
|
739
|
-
if (!sourcePath ||
|
|
1698
|
+
if (!sourcePath || isAbsolute3(sourcePath) || sourcePath.startsWith("/")) return { ok: false };
|
|
740
1699
|
const normalized = posix2.normalize(sourcePath.replaceAll("\\", "/"));
|
|
741
1700
|
if (normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
742
1701
|
return { ok: false };
|
|
743
1702
|
}
|
|
744
1703
|
if (normalized.split("/").includes("..")) return { ok: false };
|
|
745
|
-
return { ok: true, path:
|
|
1704
|
+
return { ok: true, path: join7(root, normalized) };
|
|
746
1705
|
}
|
|
747
1706
|
|
|
748
1707
|
// src/asset-targets/apply.ts
|
|
749
|
-
import { createHash as
|
|
1708
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
750
1709
|
import {
|
|
751
|
-
existsSync as
|
|
752
|
-
lstatSync as
|
|
753
|
-
mkdirSync as
|
|
1710
|
+
existsSync as existsSync9,
|
|
1711
|
+
lstatSync as lstatSync4,
|
|
1712
|
+
mkdirSync as mkdirSync3,
|
|
754
1713
|
readlinkSync as readlinkSync2,
|
|
755
1714
|
realpathSync as realpathSync3,
|
|
756
|
-
symlinkSync,
|
|
1715
|
+
symlinkSync as symlinkSync2,
|
|
757
1716
|
unlinkSync,
|
|
758
|
-
writeFileSync
|
|
1717
|
+
writeFileSync as writeFileSync2
|
|
759
1718
|
} from "node:fs";
|
|
760
|
-
import { dirname as
|
|
1719
|
+
import { dirname as dirname5, join as join9, relative as relative6, resolve as resolve3 } from "node:path";
|
|
761
1720
|
|
|
762
1721
|
// src/asset-targets/install-plan.ts
|
|
763
|
-
import { existsSync as
|
|
764
|
-
import { basename, dirname as
|
|
1722
|
+
import { existsSync as existsSync8, lstatSync as lstatSync3, readFileSync as readFileSync5, readlinkSync, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
1723
|
+
import { basename as basename2, dirname as dirname4, join as join8, resolve as resolve2 } from "node:path";
|
|
765
1724
|
|
|
766
1725
|
// src/symlinks.ts
|
|
767
1726
|
function normalizeSymlinkTarget(target) {
|
|
@@ -888,9 +1847,9 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
888
1847
|
`User-scoped asset ${asset.id} must be linked at the user level, not installed into a project target.`
|
|
889
1848
|
);
|
|
890
1849
|
}
|
|
891
|
-
const sourcePath =
|
|
1850
|
+
const sourcePath = join8(agentAssetsDir, asset.sourcePath);
|
|
892
1851
|
const targetPath = resolveHostTargetPath(asset, host, placement);
|
|
893
|
-
const targetAbsolutePath =
|
|
1852
|
+
const targetAbsolutePath = join8(targetDir, targetPath);
|
|
894
1853
|
const targetExists = pathExistsEvenIfDanglingSymlink2(targetAbsolutePath);
|
|
895
1854
|
const managedEntry = managedEntries.find(
|
|
896
1855
|
(entry) => entry.id === asset.id && entry.targetPath === targetPath
|
|
@@ -907,7 +1866,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
907
1866
|
});
|
|
908
1867
|
}
|
|
909
1868
|
if (targetExists) {
|
|
910
|
-
const stats =
|
|
1869
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
911
1870
|
if (stats.isSymbolicLink() && managedEntry && (managedEntry.delivery ?? "symlink") === "symlink") {
|
|
912
1871
|
return {
|
|
913
1872
|
type: "update-symlink",
|
|
@@ -919,7 +1878,7 @@ function createAssetAction(asset, agentAssetsDir, targetDir, host, placement, ma
|
|
|
919
1878
|
if (managedEntry && managedEntry.delivery === "snapshot") {
|
|
920
1879
|
throw new Error(`Refusing to replace managed snapshot with a symlink: ${targetPath}`);
|
|
921
1880
|
}
|
|
922
|
-
if (stats.isSymbolicLink() &&
|
|
1881
|
+
if (stats.isSymbolicLink() && existsSync8(targetAbsolutePath) && realpathSync2(targetAbsolutePath) === realpathSync2(sourcePath)) {
|
|
923
1882
|
return {
|
|
924
1883
|
type: "adopt-existing-symlink",
|
|
925
1884
|
assetId: asset.id,
|
|
@@ -942,10 +1901,10 @@ function createSnapshotAction(options) {
|
|
|
942
1901
|
`Snapshot delivery requires a rule target under docs/policy/shared-rules/: ${options.asset.id}`
|
|
943
1902
|
);
|
|
944
1903
|
}
|
|
945
|
-
if (!
|
|
1904
|
+
if (!statSync4(options.sourcePath).isFile()) {
|
|
946
1905
|
throw new Error(`Snapshot source must be a regular file: ${options.asset.sourcePath}`);
|
|
947
1906
|
}
|
|
948
|
-
const content =
|
|
1907
|
+
const content = readFileSync5(options.sourcePath);
|
|
949
1908
|
const contentBase64 = content.toString("base64");
|
|
950
1909
|
const contentHash = hashAgentAssetContent(options.asset, options.agentAssetsDir);
|
|
951
1910
|
const managedDelivery = options.managedEntry?.delivery ?? "symlink";
|
|
@@ -958,9 +1917,9 @@ function createSnapshotAction(options) {
|
|
|
958
1917
|
contentHash
|
|
959
1918
|
};
|
|
960
1919
|
}
|
|
961
|
-
const stats =
|
|
1920
|
+
const stats = lstatSync3(options.targetAbsolutePath);
|
|
962
1921
|
if (stats.isSymbolicLink()) {
|
|
963
|
-
if (
|
|
1922
|
+
if (existsSync8(options.targetAbsolutePath) && realpathSync2(options.targetAbsolutePath) === realpathSync2(options.sourcePath) && hashAssetPathContent(options.targetAbsolutePath) === contentHash) {
|
|
964
1923
|
return {
|
|
965
1924
|
type: "migrate-symlink-to-snapshot",
|
|
966
1925
|
assetId: options.asset.id,
|
|
@@ -1016,9 +1975,9 @@ function resolveHostTargetPath(asset, _host, placement) {
|
|
|
1016
1975
|
return `.agents/skills/${assetSkillInstallName(asset)}`;
|
|
1017
1976
|
}
|
|
1018
1977
|
if (asset.kind === "rule") {
|
|
1019
|
-
return `.pro-gov/agent-assets/rules/${
|
|
1978
|
+
return `.pro-gov/agent-assets/rules/${basename2(asset.sourcePath)}`;
|
|
1020
1979
|
}
|
|
1021
|
-
return `.pro-gov/agent-assets/commands/${
|
|
1980
|
+
return `.pro-gov/agent-assets/commands/${basename2(asset.sourcePath)}`;
|
|
1022
1981
|
}
|
|
1023
1982
|
function resolveSkillPlacement(asset, placement) {
|
|
1024
1983
|
if (placement !== "registry") return placement;
|
|
@@ -1028,16 +1987,16 @@ function createDirectoryActions(actions) {
|
|
|
1028
1987
|
const directories = /* @__PURE__ */ new Set();
|
|
1029
1988
|
for (const action of actions) {
|
|
1030
1989
|
if (action.type === "create-dir") continue;
|
|
1031
|
-
const directory =
|
|
1990
|
+
const directory = dirname4(action.targetPath);
|
|
1032
1991
|
if (directory !== ".") directories.add(directory);
|
|
1033
1992
|
}
|
|
1034
1993
|
return [...directories].sort().map((targetPath) => ({ type: "create-dir", targetPath }));
|
|
1035
1994
|
}
|
|
1036
1995
|
function readManagedLock(targetDir) {
|
|
1037
|
-
const lockfilePath =
|
|
1038
|
-
if (!
|
|
1996
|
+
const lockfilePath = join8(targetDir, ".pro-gov/assets.lock.json");
|
|
1997
|
+
if (!existsSync8(lockfilePath)) return { entries: [] };
|
|
1039
1998
|
try {
|
|
1040
|
-
const lockfile = JSON.parse(
|
|
1999
|
+
const lockfile = JSON.parse(readFileSync5(lockfilePath, "utf8"));
|
|
1041
2000
|
return {
|
|
1042
2001
|
host: typeof lockfile.host === "string" ? lockfile.host : void 0,
|
|
1043
2002
|
entries: (lockfile.assets ?? []).filter(
|
|
@@ -1075,10 +2034,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1075
2034
|
throw new Error(`Legacy Claude lock entry cannot be safely normalized: ${entry.targetPath}`);
|
|
1076
2035
|
}
|
|
1077
2036
|
const targetPath = `.agents/skills/${skillName}`;
|
|
1078
|
-
const targetAbsolutePath =
|
|
1079
|
-
const compatibilityRootPath =
|
|
1080
|
-
const canonicalRootPath =
|
|
1081
|
-
if (!
|
|
2037
|
+
const targetAbsolutePath = join8(options.targetDir, targetPath);
|
|
2038
|
+
const compatibilityRootPath = join8(options.targetDir, ".claude/skills");
|
|
2039
|
+
const canonicalRootPath = join8(options.targetDir, ".agents/skills");
|
|
2040
|
+
if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync(compatibilityRootPath)) !== "../.agents/skills" || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
|
|
1082
2041
|
throw new Error(
|
|
1083
2042
|
`Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`
|
|
1084
2043
|
);
|
|
@@ -1087,10 +2046,10 @@ function createLegacyClaudeAdoptions(options) {
|
|
|
1087
2046
|
consumedTargetPaths.add(entry.targetPath);
|
|
1088
2047
|
continue;
|
|
1089
2048
|
}
|
|
1090
|
-
const targetStat =
|
|
1091
|
-
const legacyAbsolutePath =
|
|
1092
|
-
const legacyStat =
|
|
1093
|
-
const expectedSourcePath =
|
|
2049
|
+
const targetStat = lstatSync3(targetAbsolutePath);
|
|
2050
|
+
const legacyAbsolutePath = join8(options.targetDir, entry.targetPath);
|
|
2051
|
+
const legacyStat = lstatSync3(legacyAbsolutePath);
|
|
2052
|
+
const expectedSourcePath = join8(options.agentAssetsDir, entry.sourcePath);
|
|
1094
2053
|
if (!targetStat.isSymbolicLink() || !legacyStat.isSymbolicLink() || targetStat.dev !== legacyStat.dev || targetStat.ino !== legacyStat.ino || realpathSync2(targetAbsolutePath) !== realpathSync2(expectedSourcePath)) {
|
|
1095
2054
|
throw new Error(`Legacy Claude skill target cannot be safely adopted: ${entry.targetPath}`);
|
|
1096
2055
|
}
|
|
@@ -1120,9 +2079,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1120
2079
|
`Refusing to remove managed asset outside supported roots: ${entry.targetPath}`
|
|
1121
2080
|
);
|
|
1122
2081
|
}
|
|
1123
|
-
const targetAbsolutePath =
|
|
2082
|
+
const targetAbsolutePath = join8(targetDir, entry.targetPath);
|
|
1124
2083
|
if (!pathExistsEvenIfDanglingSymlink2(targetAbsolutePath)) continue;
|
|
1125
|
-
const stats =
|
|
2084
|
+
const stats = lstatSync3(targetAbsolutePath);
|
|
1126
2085
|
if ((entry.delivery ?? "symlink") === "snapshot") {
|
|
1127
2086
|
if (!isValidSnapshotProjectTargetPath(entry.targetPath)) {
|
|
1128
2087
|
throw new Error(
|
|
@@ -1151,9 +2110,9 @@ function createRemovalActions(targetDir, agentAssetsDir, managedEntries, expecte
|
|
|
1151
2110
|
`Refusing to remove path that is no longer a managed symlink: ${entry.targetPath}`
|
|
1152
2111
|
);
|
|
1153
2112
|
}
|
|
1154
|
-
const expectedSourcePath =
|
|
1155
|
-
const actualSourcePath =
|
|
1156
|
-
if (actualSourcePath !==
|
|
2113
|
+
const expectedSourcePath = join8(agentAssetsDir, entry.sourcePath);
|
|
2114
|
+
const actualSourcePath = resolve2(dirname4(targetAbsolutePath), readlinkSync(targetAbsolutePath));
|
|
2115
|
+
if (actualSourcePath !== resolve2(expectedSourcePath)) {
|
|
1157
2116
|
throw new Error(
|
|
1158
2117
|
`Refusing to remove managed symlink with changed target: ${entry.targetPath}`
|
|
1159
2118
|
);
|
|
@@ -1177,7 +2136,7 @@ function isLegacyClaudeSkillTargetPath(path) {
|
|
|
1177
2136
|
}
|
|
1178
2137
|
function pathExistsEvenIfDanglingSymlink2(path) {
|
|
1179
2138
|
try {
|
|
1180
|
-
|
|
2139
|
+
lstatSync3(path);
|
|
1181
2140
|
return true;
|
|
1182
2141
|
} catch {
|
|
1183
2142
|
return false;
|
|
@@ -1201,7 +2160,7 @@ function applyAssetInstallPlan(plan) {
|
|
|
1201
2160
|
return { appliedActions };
|
|
1202
2161
|
}
|
|
1203
2162
|
function applyAction(targetDir, action) {
|
|
1204
|
-
const targetAbsolutePath =
|
|
2163
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1205
2164
|
if (action.type === "remove-symlink") {
|
|
1206
2165
|
removeManagedSymlink(targetAbsolutePath, action);
|
|
1207
2166
|
return;
|
|
@@ -1212,41 +2171,41 @@ function applyAction(targetDir, action) {
|
|
|
1212
2171
|
return;
|
|
1213
2172
|
}
|
|
1214
2173
|
if (action.type === "create-dir") {
|
|
1215
|
-
|
|
2174
|
+
mkdirSync3(targetAbsolutePath, { recursive: true });
|
|
1216
2175
|
return;
|
|
1217
2176
|
}
|
|
1218
2177
|
if (action.type === "write-file") {
|
|
1219
|
-
|
|
1220
|
-
|
|
2178
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2179
|
+
writeFileSync2(targetAbsolutePath, action.content);
|
|
1221
2180
|
return;
|
|
1222
2181
|
}
|
|
1223
|
-
|
|
1224
|
-
const sourceAbsolutePath =
|
|
1225
|
-
const symlinkTarget =
|
|
2182
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
2183
|
+
const sourceAbsolutePath = resolve3(action.sourcePath);
|
|
2184
|
+
const symlinkTarget = relative6(realpathSync3(dirname5(targetAbsolutePath)), realpathSync3(sourceAbsolutePath)) || ".";
|
|
1226
2185
|
if (action.type === "symlink") {
|
|
1227
2186
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1228
2187
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1229
2188
|
}
|
|
1230
|
-
|
|
2189
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1231
2190
|
return;
|
|
1232
2191
|
}
|
|
1233
2192
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1234
|
-
|
|
2193
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1235
2194
|
return;
|
|
1236
2195
|
}
|
|
1237
|
-
const stats =
|
|
2196
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1238
2197
|
if (!stats.isSymbolicLink()) {
|
|
1239
2198
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1240
2199
|
}
|
|
1241
2200
|
unlinkSync(targetAbsolutePath);
|
|
1242
|
-
|
|
2201
|
+
symlinkSync2(symlinkTarget, targetAbsolutePath);
|
|
1243
2202
|
}
|
|
1244
2203
|
function validateExistingSymlink(targetDir, action) {
|
|
1245
2204
|
if (!isManagedAssetTargetPath(action.targetPath)) {
|
|
1246
2205
|
throw new Error(`Refusing unsafe existing symlink adoption: ${action.targetPath}`);
|
|
1247
2206
|
}
|
|
1248
|
-
const targetAbsolutePath =
|
|
1249
|
-
if (!
|
|
2207
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
2208
|
+
if (!lstatSync4(targetAbsolutePath).isSymbolicLink() || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath)) {
|
|
1250
2209
|
throw new Error(`Existing skill symlink changed before apply: ${action.targetPath}`);
|
|
1251
2210
|
}
|
|
1252
2211
|
}
|
|
@@ -1257,7 +2216,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1257
2216
|
if ("contentBase64" in action && hashSnapshotBytes(Buffer.from(action.contentBase64, "base64")) !== action.contentHash) {
|
|
1258
2217
|
throw new Error(`Snapshot content hash is invalid: ${action.targetPath}`);
|
|
1259
2218
|
}
|
|
1260
|
-
const targetAbsolutePath =
|
|
2219
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1261
2220
|
if (action.type === "snapshot") {
|
|
1262
2221
|
if (pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1263
2222
|
throw new Error(`Snapshot target changed before apply: ${action.targetPath}`);
|
|
@@ -1273,7 +2232,7 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1273
2232
|
return;
|
|
1274
2233
|
}
|
|
1275
2234
|
if (action.type === "migrate-symlink-to-snapshot") {
|
|
1276
|
-
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !
|
|
2235
|
+
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath) || !lstatSync4(targetAbsolutePath).isSymbolicLink() || !existsSync9(targetAbsolutePath) || realpathSync3(targetAbsolutePath) !== realpathSync3(action.sourcePath) || hashAssetPathContent(targetAbsolutePath) !== action.contentHash) {
|
|
1277
2236
|
throw new Error(`Snapshot symlink changed before apply: ${action.targetPath}`);
|
|
1278
2237
|
}
|
|
1279
2238
|
return;
|
|
@@ -1281,30 +2240,30 @@ function validateSnapshotAction(targetDir, action) {
|
|
|
1281
2240
|
assertRegularSnapshotHash(targetAbsolutePath, action.expectedContentHash, action.targetPath);
|
|
1282
2241
|
}
|
|
1283
2242
|
function applySnapshotAction(targetDir, action) {
|
|
1284
|
-
const targetAbsolutePath =
|
|
2243
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
1285
2244
|
if (action.type === "adopt-snapshot") return;
|
|
1286
2245
|
if (action.type === "remove-snapshot") {
|
|
1287
2246
|
unlinkSync(targetAbsolutePath);
|
|
1288
2247
|
return;
|
|
1289
2248
|
}
|
|
1290
2249
|
if (action.type === "migrate-symlink-to-snapshot") unlinkSync(targetAbsolutePath);
|
|
1291
|
-
|
|
2250
|
+
mkdirSync3(dirname5(targetAbsolutePath), { recursive: true });
|
|
1292
2251
|
if (action.type === "snapshot" && pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1293
2252
|
throw new Error(`Refusing to overwrite unmanaged target: ${action.targetPath}`);
|
|
1294
2253
|
}
|
|
1295
|
-
|
|
2254
|
+
writeFileSync2(targetAbsolutePath, Buffer.from(action.contentBase64, "base64"));
|
|
1296
2255
|
}
|
|
1297
2256
|
function assertRegularSnapshotHash(targetAbsolutePath, expectedHash, targetPath) {
|
|
1298
2257
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) {
|
|
1299
2258
|
throw new Error(`Snapshot target changed before apply: ${targetPath}`);
|
|
1300
2259
|
}
|
|
1301
|
-
const stats =
|
|
2260
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1302
2261
|
if (!stats.isFile() || hashAssetPathContent(targetAbsolutePath) !== expectedHash) {
|
|
1303
2262
|
throw new Error(`Snapshot target hash changed before apply: ${targetPath}`);
|
|
1304
2263
|
}
|
|
1305
2264
|
}
|
|
1306
2265
|
function hashSnapshotBytes(content) {
|
|
1307
|
-
const hash =
|
|
2266
|
+
const hash = createHash4("sha256");
|
|
1308
2267
|
hash.update("");
|
|
1309
2268
|
hash.update("\0");
|
|
1310
2269
|
hash.update(content);
|
|
@@ -1315,17 +2274,17 @@ function validateAdoptedSymlink(targetDir, action) {
|
|
|
1315
2274
|
if (!isManagedAssetTargetPath(action.targetPath) || !isLegacyClaudeSkillTargetPath(action.legacyTargetPath) || action.compatibilityRootPath !== ".claude/skills" || action.expectedCompatibilityRawTarget !== "../.agents/skills") {
|
|
1316
2275
|
throw new Error(`Refusing unsafe legacy Claude adoption: ${action.legacyTargetPath}`);
|
|
1317
2276
|
}
|
|
1318
|
-
const canonicalRootPath =
|
|
1319
|
-
const compatibilityRootPath =
|
|
1320
|
-
const targetAbsolutePath =
|
|
1321
|
-
const legacyAbsolutePath =
|
|
1322
|
-
if (!
|
|
2277
|
+
const canonicalRootPath = join9(targetDir, ".agents/skills");
|
|
2278
|
+
const compatibilityRootPath = join9(targetDir, action.compatibilityRootPath);
|
|
2279
|
+
const targetAbsolutePath = join9(targetDir, action.targetPath);
|
|
2280
|
+
const legacyAbsolutePath = join9(targetDir, action.legacyTargetPath);
|
|
2281
|
+
if (!lstatSync4(canonicalRootPath).isDirectory() || !lstatSync4(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync2(compatibilityRootPath)) !== action.expectedCompatibilityRawTarget || realpathSync3(compatibilityRootPath) !== realpathSync3(canonicalRootPath)) {
|
|
1323
2282
|
throw new Error(
|
|
1324
2283
|
`Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`
|
|
1325
2284
|
);
|
|
1326
2285
|
}
|
|
1327
|
-
const targetStat =
|
|
1328
|
-
const legacyStat =
|
|
2286
|
+
const targetStat = lstatSync4(targetAbsolutePath);
|
|
2287
|
+
const legacyStat = lstatSync4(legacyAbsolutePath);
|
|
1329
2288
|
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
2289
|
throw new Error(`Legacy Claude skill target changed before apply: ${action.targetPath}`);
|
|
1331
2290
|
}
|
|
@@ -1337,33 +2296,33 @@ function removeManagedSymlink(targetAbsolutePath, action) {
|
|
|
1337
2296
|
);
|
|
1338
2297
|
}
|
|
1339
2298
|
if (!pathExistsEvenIfDanglingSymlink3(targetAbsolutePath)) return;
|
|
1340
|
-
const stats =
|
|
2299
|
+
const stats = lstatSync4(targetAbsolutePath);
|
|
1341
2300
|
if (!stats.isSymbolicLink()) {
|
|
1342
2301
|
throw new Error(
|
|
1343
2302
|
`Refusing to remove path that is no longer a managed symlink: ${action.targetPath}`
|
|
1344
2303
|
);
|
|
1345
2304
|
}
|
|
1346
|
-
const actualSourcePath =
|
|
1347
|
-
if (actualSourcePath !==
|
|
2305
|
+
const actualSourcePath = resolve3(dirname5(targetAbsolutePath), readlinkSync2(targetAbsolutePath));
|
|
2306
|
+
if (actualSourcePath !== resolve3(action.expectedSourcePath)) {
|
|
1348
2307
|
throw new Error(`Refusing to remove managed symlink with changed target: ${action.targetPath}`);
|
|
1349
2308
|
}
|
|
1350
2309
|
unlinkSync(targetAbsolutePath);
|
|
1351
2310
|
}
|
|
1352
2311
|
function pathExistsEvenIfDanglingSymlink3(path) {
|
|
1353
2312
|
try {
|
|
1354
|
-
|
|
2313
|
+
lstatSync4(path);
|
|
1355
2314
|
return true;
|
|
1356
2315
|
} catch {
|
|
1357
|
-
return
|
|
2316
|
+
return existsSync9(path);
|
|
1358
2317
|
}
|
|
1359
2318
|
}
|
|
1360
2319
|
|
|
1361
2320
|
// src/asset-targets/check.ts
|
|
1362
|
-
import { existsSync as
|
|
1363
|
-
import { join as
|
|
2321
|
+
import { existsSync as existsSync10, lstatSync as lstatSync5, readFileSync as readFileSync6, realpathSync as realpathSync4 } from "node:fs";
|
|
2322
|
+
import { join as join10 } from "node:path";
|
|
1364
2323
|
function checkInstalledAssets(options) {
|
|
1365
|
-
const lockfilePath =
|
|
1366
|
-
if (!
|
|
2324
|
+
const lockfilePath = join10(options.targetDir, ".pro-gov/assets.lock.json");
|
|
2325
|
+
if (!existsSync10(lockfilePath)) {
|
|
1367
2326
|
return {
|
|
1368
2327
|
targetDir: options.targetDir,
|
|
1369
2328
|
issues: [
|
|
@@ -1375,7 +2334,7 @@ function checkInstalledAssets(options) {
|
|
|
1375
2334
|
};
|
|
1376
2335
|
}
|
|
1377
2336
|
const registryById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
|
|
1378
|
-
const lockfile = JSON.parse(
|
|
2337
|
+
const lockfile = JSON.parse(readFileSync6(lockfilePath, "utf8"));
|
|
1379
2338
|
const issues = [];
|
|
1380
2339
|
const strictRegistry = options.strictRegistry ?? false;
|
|
1381
2340
|
const selectedAssetIds = (lockfile.assets ?? []).map((entry) => entry.id);
|
|
@@ -1404,7 +2363,7 @@ function checkInstalledAssets(options) {
|
|
|
1404
2363
|
continue;
|
|
1405
2364
|
}
|
|
1406
2365
|
const asset = registryById.get(entry.id);
|
|
1407
|
-
const targetAbsolutePath =
|
|
2366
|
+
const targetAbsolutePath = join10(options.targetDir, entry.targetPath);
|
|
1408
2367
|
const delivery = entry.delivery ?? "symlink";
|
|
1409
2368
|
const portableDeferredSkill = delivery === "symlink" && !strictRegistry && isProjectSkillTarget(entry.targetPath);
|
|
1410
2369
|
if (delivery !== "symlink" && delivery !== "snapshot") {
|
|
@@ -1466,7 +2425,7 @@ function checkInstalledAssets(options) {
|
|
|
1466
2425
|
});
|
|
1467
2426
|
continue;
|
|
1468
2427
|
}
|
|
1469
|
-
const targetStats =
|
|
2428
|
+
const targetStats = lstatSync5(targetAbsolutePath);
|
|
1470
2429
|
if (delivery === "snapshot" && !targetStats.isFile()) {
|
|
1471
2430
|
issues.push({
|
|
1472
2431
|
type: "snapshot-not-regular-file",
|
|
@@ -1485,7 +2444,7 @@ function checkInstalledAssets(options) {
|
|
|
1485
2444
|
});
|
|
1486
2445
|
continue;
|
|
1487
2446
|
}
|
|
1488
|
-
if (delivery === "symlink" && !
|
|
2447
|
+
if (delivery === "symlink" && !existsSync10(targetAbsolutePath)) {
|
|
1489
2448
|
if (portableDeferredSkill) continue;
|
|
1490
2449
|
issues.push({
|
|
1491
2450
|
type: "dangling-symlink",
|
|
@@ -1506,8 +2465,8 @@ function checkInstalledAssets(options) {
|
|
|
1506
2465
|
});
|
|
1507
2466
|
}
|
|
1508
2467
|
if (!asset || !strictRegistry || registryProvenanceMismatch) continue;
|
|
1509
|
-
const sourceAbsolutePath =
|
|
1510
|
-
if (!
|
|
2468
|
+
const sourceAbsolutePath = join10(options.agentAssetsDir, asset.sourcePath);
|
|
2469
|
+
if (!existsSync10(sourceAbsolutePath)) {
|
|
1511
2470
|
issues.push({
|
|
1512
2471
|
type: "missing-source",
|
|
1513
2472
|
id: entry.id,
|
|
@@ -1571,7 +2530,7 @@ function checkDuplicateSkillPlacements(targetDir, registry) {
|
|
|
1571
2530
|
const skillName = assetSkillInstallName(asset);
|
|
1572
2531
|
const autoPath = `.agents/skills/${skillName}`;
|
|
1573
2532
|
const manualPath = `.agents/manual-skills/${skillName}`;
|
|
1574
|
-
if (pathExistsEvenIfDanglingSymlink4(
|
|
2533
|
+
if (pathExistsEvenIfDanglingSymlink4(join10(targetDir, autoPath)) && pathExistsEvenIfDanglingSymlink4(join10(targetDir, manualPath))) {
|
|
1575
2534
|
issues.push({
|
|
1576
2535
|
type: "duplicate-skill-placement",
|
|
1577
2536
|
id: asset.id,
|
|
@@ -1617,7 +2576,7 @@ function expectedRegistrySkillTargetPath(host, skillName, placement) {
|
|
|
1617
2576
|
}
|
|
1618
2577
|
function pathExistsEvenIfDanglingSymlink4(path) {
|
|
1619
2578
|
try {
|
|
1620
|
-
|
|
2579
|
+
lstatSync5(path);
|
|
1621
2580
|
return true;
|
|
1622
2581
|
} catch {
|
|
1623
2582
|
return false;
|
|
@@ -1628,8 +2587,8 @@ function isProjectSkillTarget(targetPath) {
|
|
|
1628
2587
|
}
|
|
1629
2588
|
|
|
1630
2589
|
// src/asset-targets/recommend.ts
|
|
1631
|
-
import { existsSync as
|
|
1632
|
-
import { join as
|
|
2590
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
2591
|
+
import { join as join11 } from "node:path";
|
|
1633
2592
|
var frontendPackages = /* @__PURE__ */ new Set([
|
|
1634
2593
|
"@vitejs/plugin-react",
|
|
1635
2594
|
"astro",
|
|
@@ -1643,21 +2602,21 @@ var frontendPackages = /* @__PURE__ */ new Set([
|
|
|
1643
2602
|
]);
|
|
1644
2603
|
var agentEntryCandidates = ["AGENTS.md", "CLAUDE.md"];
|
|
1645
2604
|
function discoverTargetSignals(targetDir) {
|
|
1646
|
-
const packageJson = readJson(
|
|
2605
|
+
const packageJson = readJson(join11(targetDir, "package.json"));
|
|
1647
2606
|
const dependencyNames = packageJson ? Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) : [];
|
|
1648
2607
|
const frontendSignals = dependencyNames.filter((name) => frontendPackages.has(name)).sort();
|
|
1649
|
-
const hasAgentEntry = agentEntryCandidates.some((file) =>
|
|
2608
|
+
const hasAgentEntry = agentEntryCandidates.some((file) => existsSync11(join11(targetDir, file)));
|
|
1650
2609
|
const researchSignals = [
|
|
1651
|
-
|
|
1652
|
-
|
|
2610
|
+
existsSync11(join11(targetDir, "docs/research")) ? "docs/research" : "",
|
|
2611
|
+
existsSync11(join11(targetDir, "research")) ? "research" : "",
|
|
1653
2612
|
hasBookChildDirectory(targetDir, "research") ? "books/*/research" : "",
|
|
1654
|
-
textFileIncludes(
|
|
2613
|
+
textFileIncludes(join11(targetDir, "README.md"), ["research", "\u8C03\u7814"]) ? "README research" : ""
|
|
1655
2614
|
].filter(Boolean);
|
|
1656
2615
|
const writingSignals = [
|
|
1657
|
-
|
|
1658
|
-
|
|
2616
|
+
existsSync11(join11(targetDir, "chapters")) ? "chapters" : "",
|
|
2617
|
+
existsSync11(join11(targetDir, "src/chapters")) ? "src/chapters" : "",
|
|
1659
2618
|
hasBookChildDirectory(targetDir, "chapters") ? "books/*/chapters" : "",
|
|
1660
|
-
textFileIncludes(
|
|
2619
|
+
textFileIncludes(join11(targetDir, "AGENTS.md"), [
|
|
1661
2620
|
"writing mode",
|
|
1662
2621
|
"novel chapter",
|
|
1663
2622
|
"book content"
|
|
@@ -1702,24 +2661,24 @@ function recommendBundlesForTarget(targetDir) {
|
|
|
1702
2661
|
return recommendations;
|
|
1703
2662
|
}
|
|
1704
2663
|
function readJson(path) {
|
|
1705
|
-
if (!
|
|
2664
|
+
if (!existsSync11(path)) return void 0;
|
|
1706
2665
|
try {
|
|
1707
|
-
return JSON.parse(
|
|
2666
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1708
2667
|
} catch {
|
|
1709
2668
|
return void 0;
|
|
1710
2669
|
}
|
|
1711
2670
|
}
|
|
1712
2671
|
function textFileIncludes(path, needles) {
|
|
1713
|
-
if (!
|
|
1714
|
-
const contents =
|
|
2672
|
+
if (!existsSync11(path)) return false;
|
|
2673
|
+
const contents = readFileSync7(path, "utf8").toLowerCase();
|
|
1715
2674
|
return needles.some((needle) => contents.includes(needle.toLowerCase()));
|
|
1716
2675
|
}
|
|
1717
2676
|
function hasBookChildDirectory(targetDir, childName) {
|
|
1718
|
-
const booksDir =
|
|
1719
|
-
if (!
|
|
2677
|
+
const booksDir = join11(targetDir, "books");
|
|
2678
|
+
if (!existsSync11(booksDir)) return false;
|
|
1720
2679
|
try {
|
|
1721
|
-
return
|
|
1722
|
-
(entry) => entry.isDirectory() &&
|
|
2680
|
+
return readdirSync7(booksDir, { withFileTypes: true }).some(
|
|
2681
|
+
(entry) => entry.isDirectory() && existsSync11(join11(booksDir, entry.name, childName))
|
|
1723
2682
|
);
|
|
1724
2683
|
} catch {
|
|
1725
2684
|
return false;
|
|
@@ -1753,9 +2712,67 @@ function runAssets(args) {
|
|
|
1753
2712
|
if (subcommand2 === "npx") {
|
|
1754
2713
|
return runAssetsNpx(rest);
|
|
1755
2714
|
}
|
|
2715
|
+
if (subcommand2 === "catalog") {
|
|
2716
|
+
return runAssetsCatalog(rest);
|
|
2717
|
+
}
|
|
1756
2718
|
printUsage();
|
|
1757
2719
|
return 1;
|
|
1758
2720
|
}
|
|
2721
|
+
function runAssetsCatalog(args) {
|
|
2722
|
+
const [operation, ...rest] = args;
|
|
2723
|
+
if (!operation || operation === "--help" || operation === "-h") {
|
|
2724
|
+
printCatalogUsage();
|
|
2725
|
+
return operation ? 0 : 1;
|
|
2726
|
+
}
|
|
2727
|
+
if (operation !== "build" && operation !== "check") {
|
|
2728
|
+
printCatalogUsage();
|
|
2729
|
+
return 1;
|
|
2730
|
+
}
|
|
2731
|
+
const options = parseCatalogOptions(operation, rest);
|
|
2732
|
+
if (!options.ok) {
|
|
2733
|
+
console.error(options.error);
|
|
2734
|
+
printCatalogUsage();
|
|
2735
|
+
return 1;
|
|
2736
|
+
}
|
|
2737
|
+
try {
|
|
2738
|
+
const loaded = loadAgentAssetRegistry(
|
|
2739
|
+
options.value.agentAssetsDir ? { agentAssetsDir: options.value.agentAssetsDir } : void 0
|
|
2740
|
+
);
|
|
2741
|
+
if (loaded.issues.length > 0) {
|
|
2742
|
+
for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
2743
|
+
return 1;
|
|
2744
|
+
}
|
|
2745
|
+
const catalogRoot = options.value.catalogRoot ?? join12(loaded.agentAssetsDir, "#catalogs");
|
|
2746
|
+
const common = {
|
|
2747
|
+
agentAssetsDir: loaded.agentAssetsDir,
|
|
2748
|
+
catalogRoot,
|
|
2749
|
+
registry: loaded.registry,
|
|
2750
|
+
bundles: loadAgentAssetBundles(loaded.agentAssetsDir)
|
|
2751
|
+
};
|
|
2752
|
+
if (operation === "check") {
|
|
2753
|
+
const result2 = checkAssetCatalog(common);
|
|
2754
|
+
if (options.value.json) console.log(JSON.stringify(result2, null, 2));
|
|
2755
|
+
else if (result2.issues.length === 0)
|
|
2756
|
+
console.log(`asset catalog check passed (${result2.assetCount} assets)`);
|
|
2757
|
+
else for (const issue of result2.issues) console.log(issue);
|
|
2758
|
+
return result2.issues.length === 0 ? 0 : 1;
|
|
2759
|
+
}
|
|
2760
|
+
const result = buildAssetCatalog({ ...common, nativeLinks: options.value.nativeLinks });
|
|
2761
|
+
if (options.value.json) console.log(JSON.stringify(result, null, 2));
|
|
2762
|
+
else {
|
|
2763
|
+
console.log(`catalog: ${result.catalogRoot}`);
|
|
2764
|
+
console.log(`assets: ${result.assetCount}`);
|
|
2765
|
+
console.log(`topics: ${result.topicCount}`);
|
|
2766
|
+
console.log(`portable-files: ${result.portableFileCount}`);
|
|
2767
|
+
console.log(`native-links: ${result.nativeLinkCount}`);
|
|
2768
|
+
for (const warning of result.warnings) console.warn(`warning: ${warning}`);
|
|
2769
|
+
}
|
|
2770
|
+
return 0;
|
|
2771
|
+
} catch (error) {
|
|
2772
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2773
|
+
return 1;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
1759
2776
|
function runAssetsPublicCheck(args) {
|
|
1760
2777
|
const options = parsePublicCheckOptions(args);
|
|
1761
2778
|
if (!options.ok) {
|
|
@@ -1835,7 +2852,7 @@ function runAssetsApply(args) {
|
|
|
1835
2852
|
return 1;
|
|
1836
2853
|
}
|
|
1837
2854
|
try {
|
|
1838
|
-
const plan = JSON.parse(
|
|
2855
|
+
const plan = JSON.parse(readFileSync8(options.value.planPath, "utf8"));
|
|
1839
2856
|
const result = applyAssetInstallPlan(plan);
|
|
1840
2857
|
console.log(`applied-actions: ${result.appliedActions.length}`);
|
|
1841
2858
|
return 0;
|
|
@@ -1960,8 +2977,8 @@ function runAssetsPlan(args) {
|
|
|
1960
2977
|
console.log("dry-run: true");
|
|
1961
2978
|
}
|
|
1962
2979
|
if (options.value.outPath) {
|
|
1963
|
-
|
|
1964
|
-
|
|
2980
|
+
mkdirSync4(dirname6(options.value.outPath), { recursive: true });
|
|
2981
|
+
writeFileSync3(options.value.outPath, `${JSON.stringify(plan, null, 2)}
|
|
1965
2982
|
`);
|
|
1966
2983
|
if (!options.value.json) {
|
|
1967
2984
|
console.log(`plan: ${options.value.outPath}`);
|
|
@@ -1973,6 +2990,33 @@ function runAssetsPlan(args) {
|
|
|
1973
2990
|
return 1;
|
|
1974
2991
|
}
|
|
1975
2992
|
}
|
|
2993
|
+
function parseCatalogOptions(operation, args) {
|
|
2994
|
+
const options = { nativeLinks: false, json: false };
|
|
2995
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2996
|
+
const arg = args[index];
|
|
2997
|
+
if (arg === "--agent-assets") {
|
|
2998
|
+
const value = args[index + 1];
|
|
2999
|
+
if (!value) return { ok: false, error: "Expected --agent-assets <path>" };
|
|
3000
|
+
options.agentAssetsDir = value;
|
|
3001
|
+
index += 1;
|
|
3002
|
+
} else if (arg === "--catalog-root") {
|
|
3003
|
+
const value = args[index + 1];
|
|
3004
|
+
if (!value) return { ok: false, error: "Expected --catalog-root <path>" };
|
|
3005
|
+
options.catalogRoot = value;
|
|
3006
|
+
index += 1;
|
|
3007
|
+
} else if (arg === "--native-links") {
|
|
3008
|
+
if (operation !== "build") {
|
|
3009
|
+
return { ok: false, error: "--native-links is only valid with assets catalog build" };
|
|
3010
|
+
}
|
|
3011
|
+
options.nativeLinks = true;
|
|
3012
|
+
} else if (arg === "--json") {
|
|
3013
|
+
options.json = true;
|
|
3014
|
+
} else {
|
|
3015
|
+
return { ok: false, error: `Unknown assets catalog option: ${arg}` };
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
return { ok: true, value: options };
|
|
3019
|
+
}
|
|
1976
3020
|
function parseListOptions(args) {
|
|
1977
3021
|
const options = {
|
|
1978
3022
|
json: false,
|
|
@@ -2159,10 +3203,10 @@ function parsePublicCheckOptions(args) {
|
|
|
2159
3203
|
return { ok: false, error: `Unknown assets public-check option: ${arg}` };
|
|
2160
3204
|
}
|
|
2161
3205
|
}
|
|
2162
|
-
if (!
|
|
3206
|
+
if (!existsSync12(options.publicRoot)) {
|
|
2163
3207
|
return { ok: false, error: `Public agent assets root does not exist: ${options.publicRoot}` };
|
|
2164
3208
|
}
|
|
2165
|
-
if (!
|
|
3209
|
+
if (!existsSync12(options.privateRoot)) {
|
|
2166
3210
|
return { ok: false, error: `Private agent assets root does not exist: ${options.privateRoot}` };
|
|
2167
3211
|
}
|
|
2168
3212
|
return { ok: true, value: options };
|
|
@@ -2171,13 +3215,13 @@ function getDefaultPublicCheckRoots() {
|
|
|
2171
3215
|
const defaultRegistryRoot = loadAgentAssetRegistry().agentAssetsDir;
|
|
2172
3216
|
if (defaultRegistryRoot.endsWith("public-agent-assets")) {
|
|
2173
3217
|
return {
|
|
2174
|
-
privateRoot:
|
|
3218
|
+
privateRoot: join12(dirname6(defaultRegistryRoot), "agent-assets"),
|
|
2175
3219
|
publicRoot: defaultRegistryRoot
|
|
2176
3220
|
};
|
|
2177
3221
|
}
|
|
2178
3222
|
return {
|
|
2179
3223
|
privateRoot: defaultRegistryRoot,
|
|
2180
|
-
publicRoot:
|
|
3224
|
+
publicRoot: join12(dirname6(defaultRegistryRoot), "public-agent-assets")
|
|
2181
3225
|
};
|
|
2182
3226
|
}
|
|
2183
3227
|
function listRegistryAssets(options) {
|
|
@@ -2238,6 +3282,9 @@ function printUsage() {
|
|
|
2238
3282
|
);
|
|
2239
3283
|
console.error(" pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
2240
3284
|
console.error(" pro-gov assets npx update [--skill <name>] --plan [--root <path>]");
|
|
3285
|
+
console.error(
|
|
3286
|
+
" pro-gov assets catalog build|check [--agent-assets <path>] [--catalog-root <path>] [--native-links] [--json]"
|
|
3287
|
+
);
|
|
2241
3288
|
}
|
|
2242
3289
|
function printNpxUsage() {
|
|
2243
3290
|
console.log("Usage: pro-gov assets npx add <source> [--skill <name>] --plan [--root <path>]");
|
|
@@ -2245,11 +3292,19 @@ function printNpxUsage() {
|
|
|
2245
3292
|
console.log("");
|
|
2246
3293
|
console.log("Runs npx skills only in a temporary copy and prints a reviewable plan.");
|
|
2247
3294
|
}
|
|
3295
|
+
function printCatalogUsage() {
|
|
3296
|
+
console.log(
|
|
3297
|
+
"Usage: pro-gov assets catalog build [--agent-assets <path>] [--catalog-root <path>] [--native-links] [--json]"
|
|
3298
|
+
);
|
|
3299
|
+
console.log(
|
|
3300
|
+
"Usage: pro-gov assets catalog check [--agent-assets <path>] [--catalog-root <path>] [--json]"
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
2248
3303
|
|
|
2249
3304
|
// src/commands/doctor.ts
|
|
2250
|
-
import { existsSync as
|
|
3305
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2251
3306
|
import { createRequire } from "node:module";
|
|
2252
|
-
import { dirname as
|
|
3307
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
2253
3308
|
var REQUIRED_ASSETS = [
|
|
2254
3309
|
"starter/.agents/skills/.gitkeep",
|
|
2255
3310
|
"starter/AGENTS.template.md",
|
|
@@ -2314,16 +3369,16 @@ function resolveDocGovDependencyCli() {
|
|
|
2314
3369
|
try {
|
|
2315
3370
|
const require2 = createRequire(import.meta.url);
|
|
2316
3371
|
const packageJsonPath = require2.resolve("@pieai/doc-gov/package.json");
|
|
2317
|
-
const cliPath =
|
|
2318
|
-
return
|
|
3372
|
+
const cliPath = join13(dirname7(packageJsonPath), "dist/cli.js");
|
|
3373
|
+
return existsSync13(cliPath) ? cliPath : null;
|
|
2319
3374
|
} catch {
|
|
2320
3375
|
return null;
|
|
2321
3376
|
}
|
|
2322
3377
|
}
|
|
2323
3378
|
|
|
2324
3379
|
// src/commands/init.ts
|
|
2325
|
-
import { lstatSync as
|
|
2326
|
-
import { basename as
|
|
3380
|
+
import { lstatSync as lstatSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, symlinkSync as symlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3381
|
+
import { basename as basename3, dirname as dirname8, join as join14 } from "node:path";
|
|
2327
3382
|
|
|
2328
3383
|
// src/commands/shared.ts
|
|
2329
3384
|
function planStarterFiles(profile) {
|
|
@@ -2410,7 +3465,7 @@ function runInit(args) {
|
|
|
2410
3465
|
function applyStarterFiles(files, profile) {
|
|
2411
3466
|
const root = process.cwd();
|
|
2412
3467
|
const conflicts = files.filter((file) => {
|
|
2413
|
-
const targetPath =
|
|
3468
|
+
const targetPath = join14(root, file.targetPath);
|
|
2414
3469
|
const stat = safeLstat(targetPath);
|
|
2415
3470
|
if (!stat) return false;
|
|
2416
3471
|
return file.kind !== "directory" || !stat.isDirectory();
|
|
@@ -2422,19 +3477,19 @@ function applyStarterFiles(files, profile) {
|
|
|
2422
3477
|
return 1;
|
|
2423
3478
|
}
|
|
2424
3479
|
for (const file of files) {
|
|
2425
|
-
const targetPath =
|
|
2426
|
-
|
|
3480
|
+
const targetPath = join14(root, file.targetPath);
|
|
3481
|
+
mkdirSync5(dirname8(targetPath), { recursive: true });
|
|
2427
3482
|
if (file.kind === "directory") {
|
|
2428
|
-
|
|
3483
|
+
mkdirSync5(targetPath, { recursive: true });
|
|
2429
3484
|
continue;
|
|
2430
3485
|
}
|
|
2431
3486
|
if (file.kind === "symlink") {
|
|
2432
|
-
|
|
3487
|
+
symlinkSync3(file.linkTarget, targetPath);
|
|
2433
3488
|
continue;
|
|
2434
3489
|
}
|
|
2435
|
-
const source =
|
|
2436
|
-
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"),
|
|
2437
|
-
|
|
3490
|
+
const source = readFileSync9(file.absoluteSourcePath);
|
|
3491
|
+
const content = file.targetPath === "AGENTS.md" ? renderAgentsTemplate(source.toString("utf8"), basename3(root), profile) : source;
|
|
3492
|
+
writeFileSync4(targetPath, content);
|
|
2438
3493
|
}
|
|
2439
3494
|
console.log("pro-gov init APPLIED");
|
|
2440
3495
|
console.log(`profile: ${profile}`);
|
|
@@ -2451,7 +3506,7 @@ function renderAgentsTemplate(template, projectName, profile) {
|
|
|
2451
3506
|
}
|
|
2452
3507
|
function safeLstat(path) {
|
|
2453
3508
|
try {
|
|
2454
|
-
return
|
|
3509
|
+
return lstatSync6(path);
|
|
2455
3510
|
} catch {
|
|
2456
3511
|
return void 0;
|
|
2457
3512
|
}
|
|
@@ -2465,22 +3520,22 @@ function readFlag(args, flag) {
|
|
|
2465
3520
|
}
|
|
2466
3521
|
|
|
2467
3522
|
// src/commands/host-lens.ts
|
|
2468
|
-
import { mkdirSync as
|
|
2469
|
-
import { dirname as
|
|
3523
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3524
|
+
import { dirname as dirname11, resolve as resolve6 } from "node:path";
|
|
2470
3525
|
|
|
2471
3526
|
// src/host-lens.ts
|
|
2472
3527
|
import { execFileSync } from "node:child_process";
|
|
2473
3528
|
import {
|
|
2474
3529
|
cpSync as cpSync2,
|
|
2475
|
-
existsSync as
|
|
2476
|
-
lstatSync as
|
|
2477
|
-
mkdirSync as
|
|
2478
|
-
readdirSync as
|
|
2479
|
-
statSync as
|
|
2480
|
-
writeFileSync as
|
|
3530
|
+
existsSync as existsSync14,
|
|
3531
|
+
lstatSync as lstatSync7,
|
|
3532
|
+
mkdirSync as mkdirSync6,
|
|
3533
|
+
readdirSync as readdirSync8,
|
|
3534
|
+
statSync as statSync5,
|
|
3535
|
+
writeFileSync as writeFileSync5
|
|
2481
3536
|
} from "node:fs";
|
|
2482
3537
|
import { homedir } from "node:os";
|
|
2483
|
-
import { dirname as
|
|
3538
|
+
import { dirname as dirname9, join as join15, relative as relative7, resolve as resolve4 } from "node:path";
|
|
2484
3539
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2485
3540
|
var ROOT_DEFINITIONS = [
|
|
2486
3541
|
{
|
|
@@ -2562,9 +3617,9 @@ var ROOT_DEFINITIONS = [
|
|
|
2562
3617
|
}
|
|
2563
3618
|
];
|
|
2564
3619
|
function inspectHost(options = {}) {
|
|
2565
|
-
const homePath =
|
|
3620
|
+
const homePath = resolve4(options.homeDir ?? homedir());
|
|
2566
3621
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
2567
|
-
const rootPaths = ROOT_DEFINITIONS.map((definition) =>
|
|
3622
|
+
const rootPaths = ROOT_DEFINITIONS.map((definition) => join15(homePath, definition.relativePath));
|
|
2568
3623
|
const rootSizes = measurePaths(rootPaths);
|
|
2569
3624
|
const findings = inspectFindings(homePath);
|
|
2570
3625
|
const rootFindingIds = /* @__PURE__ */ new Map();
|
|
@@ -2581,7 +3636,7 @@ function inspectHost(options = {}) {
|
|
|
2581
3636
|
label: definition.label,
|
|
2582
3637
|
path: displayPath(path, homePath),
|
|
2583
3638
|
kind: definition.kind,
|
|
2584
|
-
exists:
|
|
3639
|
+
exists: existsSync14(path),
|
|
2585
3640
|
bytes: rootSizes.get(path) ?? 0,
|
|
2586
3641
|
status: rootFindings.length > 0 ? "attention" : "healthy",
|
|
2587
3642
|
note: definition.note
|
|
@@ -2688,25 +3743,25 @@ function createHostLensCleanupPlan(report) {
|
|
|
2688
3743
|
};
|
|
2689
3744
|
}
|
|
2690
3745
|
function writeHostLensReport(report, outDir) {
|
|
2691
|
-
|
|
3746
|
+
mkdirSync6(outDir, { recursive: true });
|
|
2692
3747
|
const assets = findHostDashboardAssets();
|
|
2693
3748
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
2694
|
-
const source =
|
|
2695
|
-
if (!
|
|
2696
|
-
cpSync2(source,
|
|
3749
|
+
const source = join15(assets, file);
|
|
3750
|
+
if (!existsSync14(source)) throw new Error(`HostLens dashboard asset is missing: ${source}`);
|
|
3751
|
+
cpSync2(source, join15(outDir, file));
|
|
2697
3752
|
}
|
|
2698
|
-
const jsonPath =
|
|
2699
|
-
const htmlPath =
|
|
2700
|
-
|
|
3753
|
+
const jsonPath = join15(outDir, "host-lens.json");
|
|
3754
|
+
const htmlPath = join15(outDir, "index.html");
|
|
3755
|
+
writeFileSync5(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
2701
3756
|
`);
|
|
2702
|
-
|
|
3757
|
+
writeFileSync5(join15(outDir, "data.js"), `window.__HOST_LENS__ = ${safeJavaScriptJson(report)};
|
|
2703
3758
|
`);
|
|
2704
3759
|
return { jsonPath, htmlPath };
|
|
2705
3760
|
}
|
|
2706
3761
|
function inspectFindings(homePath) {
|
|
2707
3762
|
const candidates = [];
|
|
2708
3763
|
const addGroup = (value) => {
|
|
2709
|
-
const paths = value.paths.filter((path) =>
|
|
3764
|
+
const paths = value.paths.filter((path) => existsSync14(path));
|
|
2710
3765
|
if (paths.length === 0) return;
|
|
2711
3766
|
candidates.push({ ...value, paths: paths.map((path) => displayPath(path, homePath)) });
|
|
2712
3767
|
};
|
|
@@ -2714,7 +3769,7 @@ function inspectFindings(homePath) {
|
|
|
2714
3769
|
id: "codex-backups",
|
|
2715
3770
|
rootId: "codex",
|
|
2716
3771
|
label: "Codex \u5386\u53F2\u5907\u4EFD",
|
|
2717
|
-
paths: matchingChildren(
|
|
3772
|
+
paths: matchingChildren(join15(homePath, ".codex"), (name) => /^backup-/i.test(name)),
|
|
2718
3773
|
category: "backup",
|
|
2719
3774
|
confidence: "medium",
|
|
2720
3775
|
disposition: "manual-review",
|
|
@@ -2725,7 +3780,7 @@ function inspectFindings(homePath) {
|
|
|
2725
3780
|
id: "codex-cache",
|
|
2726
3781
|
rootId: "codex",
|
|
2727
3782
|
label: "Codex \u53EF\u518D\u751F\u6210\u7F13\u5B58",
|
|
2728
|
-
paths: [
|
|
3783
|
+
paths: [join15(homePath, ".codex/cache")],
|
|
2729
3784
|
category: "cache",
|
|
2730
3785
|
confidence: "high",
|
|
2731
3786
|
disposition: "report-only",
|
|
@@ -2736,7 +3791,7 @@ function inspectFindings(homePath) {
|
|
|
2736
3791
|
id: "claude-temporary",
|
|
2737
3792
|
rootId: "claude-code",
|
|
2738
3793
|
label: "Claude Code \u4E34\u65F6\u76EE\u5F55",
|
|
2739
|
-
paths: matchingChildren(
|
|
3794
|
+
paths: matchingChildren(join15(homePath, ".claude"), (name) => /^(temp|tmp)[-_]/i.test(name)),
|
|
2740
3795
|
category: "temporary",
|
|
2741
3796
|
confidence: "high",
|
|
2742
3797
|
disposition: "manual-review",
|
|
@@ -2747,7 +3802,7 @@ function inspectFindings(homePath) {
|
|
|
2747
3802
|
id: "claude-cache",
|
|
2748
3803
|
rootId: "claude-code",
|
|
2749
3804
|
label: "Claude Code \u7F13\u5B58",
|
|
2750
|
-
paths: [
|
|
3805
|
+
paths: [join15(homePath, ".claude/cache")],
|
|
2751
3806
|
category: "cache",
|
|
2752
3807
|
confidence: "high",
|
|
2753
3808
|
disposition: "report-only",
|
|
@@ -2758,7 +3813,7 @@ function inspectFindings(homePath) {
|
|
|
2758
3813
|
id: "copilot-session-history",
|
|
2759
3814
|
rootId: "copilot",
|
|
2760
3815
|
label: "Copilot \u4F1A\u8BDD\u72B6\u6001",
|
|
2761
|
-
paths: [
|
|
3816
|
+
paths: [join15(homePath, ".copilot/session-state")],
|
|
2762
3817
|
category: "session-history",
|
|
2763
3818
|
confidence: "low",
|
|
2764
3819
|
disposition: "manual-review",
|
|
@@ -2769,7 +3824,7 @@ function inspectFindings(homePath) {
|
|
|
2769
3824
|
id: "npm-npx-cache",
|
|
2770
3825
|
rootId: "npm-cache",
|
|
2771
3826
|
label: "npx \u4E34\u65F6\u5B89\u88C5\u7F13\u5B58",
|
|
2772
|
-
paths: [
|
|
3827
|
+
paths: [join15(homePath, ".npm/_npx")],
|
|
2773
3828
|
category: "cache",
|
|
2774
3829
|
confidence: "high",
|
|
2775
3830
|
disposition: "native-tool",
|
|
@@ -2781,7 +3836,7 @@ function inspectFindings(homePath) {
|
|
|
2781
3836
|
id: "npm-content-cache",
|
|
2782
3837
|
rootId: "npm-cache",
|
|
2783
3838
|
label: "npm \u5185\u5BB9\u7F13\u5B58",
|
|
2784
|
-
paths: [
|
|
3839
|
+
paths: [join15(homePath, ".npm/_cacache")],
|
|
2785
3840
|
category: "cache",
|
|
2786
3841
|
confidence: "high",
|
|
2787
3842
|
disposition: "native-tool",
|
|
@@ -2794,7 +3849,7 @@ function inspectFindings(homePath) {
|
|
|
2794
3849
|
rootId: "pnpm-store",
|
|
2795
3850
|
label: "pnpm \u672A\u5F15\u7528\u5305\u5019\u9009",
|
|
2796
3851
|
paths: [
|
|
2797
|
-
|
|
3852
|
+
join15(
|
|
2798
3853
|
homePath,
|
|
2799
3854
|
process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store"
|
|
2800
3855
|
)
|
|
@@ -2811,11 +3866,11 @@ function inspectFindings(homePath) {
|
|
|
2811
3866
|
rootId: "playwright",
|
|
2812
3867
|
label: "Playwright \u6D4F\u89C8\u5668\u7248\u672C",
|
|
2813
3868
|
paths: [
|
|
2814
|
-
|
|
3869
|
+
join15(
|
|
2815
3870
|
homePath,
|
|
2816
3871
|
process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright"
|
|
2817
3872
|
),
|
|
2818
|
-
|
|
3873
|
+
join15(homePath, ".cache/ms-playwright")
|
|
2819
3874
|
],
|
|
2820
3875
|
category: "duplicate-runtime",
|
|
2821
3876
|
confidence: "medium",
|
|
@@ -2867,16 +3922,16 @@ function buildProtections(homePath) {
|
|
|
2867
3922
|
}));
|
|
2868
3923
|
}
|
|
2869
3924
|
function matchingChildren(root, predicate) {
|
|
2870
|
-
if (!
|
|
3925
|
+
if (!existsSync14(root)) return [];
|
|
2871
3926
|
try {
|
|
2872
|
-
return
|
|
3927
|
+
return readdirSync8(root, { withFileTypes: true }).filter((entry) => predicate(entry.name)).map((entry) => join15(root, entry.name));
|
|
2873
3928
|
} catch {
|
|
2874
3929
|
return [];
|
|
2875
3930
|
}
|
|
2876
3931
|
}
|
|
2877
3932
|
function measurePaths(paths) {
|
|
2878
|
-
const uniquePaths = [...new Set(paths.map((path) =>
|
|
2879
|
-
(path) =>
|
|
3933
|
+
const uniquePaths = [...new Set(paths.map((path) => resolve4(path)))].filter(
|
|
3934
|
+
(path) => existsSync14(path)
|
|
2880
3935
|
);
|
|
2881
3936
|
const result = /* @__PURE__ */ new Map();
|
|
2882
3937
|
if (uniquePaths.length === 0) return result;
|
|
@@ -2889,7 +3944,7 @@ function measurePaths(paths) {
|
|
|
2889
3944
|
for (const line of output.split(/\r?\n/)) {
|
|
2890
3945
|
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
2891
3946
|
if (!match) continue;
|
|
2892
|
-
result.set(
|
|
3947
|
+
result.set(resolve4(match[2]), Number(match[1]) * 1024);
|
|
2893
3948
|
}
|
|
2894
3949
|
return result;
|
|
2895
3950
|
} catch {
|
|
@@ -2899,7 +3954,7 @@ function measurePaths(paths) {
|
|
|
2899
3954
|
}
|
|
2900
3955
|
function fallbackMeasure(root) {
|
|
2901
3956
|
try {
|
|
2902
|
-
const rootStats =
|
|
3957
|
+
const rootStats = lstatSync7(root);
|
|
2903
3958
|
if (!rootStats.isDirectory()) return rootStats.size;
|
|
2904
3959
|
} catch {
|
|
2905
3960
|
return 0;
|
|
@@ -2912,17 +3967,17 @@ function fallbackMeasure(root) {
|
|
|
2912
3967
|
if (!current) continue;
|
|
2913
3968
|
let entries;
|
|
2914
3969
|
try {
|
|
2915
|
-
entries =
|
|
3970
|
+
entries = readdirSync8(current, { withFileTypes: true });
|
|
2916
3971
|
} catch {
|
|
2917
3972
|
continue;
|
|
2918
3973
|
}
|
|
2919
3974
|
for (const entry of entries) {
|
|
2920
3975
|
visited += 1;
|
|
2921
|
-
const path =
|
|
3976
|
+
const path = join15(current, entry.name);
|
|
2922
3977
|
if (entry.isDirectory()) pending.push(path);
|
|
2923
3978
|
else if (entry.isFile()) {
|
|
2924
3979
|
try {
|
|
2925
|
-
bytes +=
|
|
3980
|
+
bytes += statSync5(path).size;
|
|
2926
3981
|
} catch {
|
|
2927
3982
|
}
|
|
2928
3983
|
}
|
|
@@ -2933,35 +3988,35 @@ function fallbackMeasure(root) {
|
|
|
2933
3988
|
}
|
|
2934
3989
|
function countImmediateEntries(path) {
|
|
2935
3990
|
try {
|
|
2936
|
-
return
|
|
3991
|
+
return lstatSync7(path).isDirectory() ? readdirSync8(path).length : 1;
|
|
2937
3992
|
} catch {
|
|
2938
3993
|
return 0;
|
|
2939
3994
|
}
|
|
2940
3995
|
}
|
|
2941
3996
|
function displayPath(path, homePath) {
|
|
2942
|
-
const absolute =
|
|
2943
|
-
const withinHome =
|
|
3997
|
+
const absolute = resolve4(path);
|
|
3998
|
+
const withinHome = relative7(homePath, absolute);
|
|
2944
3999
|
if (withinHome === "") return "~";
|
|
2945
4000
|
if (!withinHome.startsWith("..")) return `~/${withinHome.replaceAll("\\", "/")}`;
|
|
2946
4001
|
return absolute;
|
|
2947
4002
|
}
|
|
2948
4003
|
function undisplayPath(path, homePath) {
|
|
2949
4004
|
if (path === "~") return homePath;
|
|
2950
|
-
if (path.startsWith("~/")) return
|
|
2951
|
-
return
|
|
4005
|
+
if (path.startsWith("~/")) return join15(homePath, path.slice(2));
|
|
4006
|
+
return resolve4(path);
|
|
2952
4007
|
}
|
|
2953
4008
|
function findHostDashboardAssets() {
|
|
2954
|
-
const packageRoot2 =
|
|
4009
|
+
const packageRoot2 = dirname9(dirname9(fileURLToPath3(import.meta.url)));
|
|
2955
4010
|
const candidates = [
|
|
2956
4011
|
process.env.PGS_HOST_DASHBOARD_ASSETS_DIR,
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
4012
|
+
join15(packageRoot2, ".host-dashboard-build"),
|
|
4013
|
+
join15(packageRoot2, "assets/host-dashboard"),
|
|
4014
|
+
join15(process.cwd(), ".host-dashboard-build"),
|
|
4015
|
+
join15(process.cwd(), "assets/host-dashboard"),
|
|
4016
|
+
join15(process.cwd(), "packages/pro-gov/.host-dashboard-build"),
|
|
4017
|
+
join15(process.cwd(), "packages/pro-gov/assets/host-dashboard")
|
|
2963
4018
|
].filter((value) => Boolean(value));
|
|
2964
|
-
const match = candidates.find((path) =>
|
|
4019
|
+
const match = candidates.find((path) => existsSync14(join15(path, "index.html")));
|
|
2965
4020
|
if (!match)
|
|
2966
4021
|
throw new Error(
|
|
2967
4022
|
"HostLens dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -2973,12 +4028,12 @@ function safeJavaScriptJson(value) {
|
|
|
2973
4028
|
}
|
|
2974
4029
|
|
|
2975
4030
|
// src/portfolio/manifest.ts
|
|
2976
|
-
import { existsSync as
|
|
2977
|
-
import { dirname as
|
|
4031
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
|
|
4032
|
+
import { dirname as dirname10, isAbsolute as isAbsolute4, resolve as resolve5 } from "node:path";
|
|
2978
4033
|
function loadPortfolioManifest(configPath) {
|
|
2979
4034
|
let parsed;
|
|
2980
4035
|
try {
|
|
2981
|
-
parsed = JSON.parse(
|
|
4036
|
+
parsed = JSON.parse(readFileSync10(configPath, "utf8"));
|
|
2982
4037
|
} catch (error) {
|
|
2983
4038
|
return {
|
|
2984
4039
|
configPath,
|
|
@@ -2990,7 +4045,7 @@ function loadPortfolioManifest(configPath) {
|
|
|
2990
4045
|
]
|
|
2991
4046
|
};
|
|
2992
4047
|
}
|
|
2993
|
-
const normalized = resolveManifestPaths(parsed,
|
|
4048
|
+
const normalized = resolveManifestPaths(parsed, dirname10(resolve5(configPath)));
|
|
2994
4049
|
const issues = validatePortfolioManifest(normalized);
|
|
2995
4050
|
return {
|
|
2996
4051
|
configPath,
|
|
@@ -3001,16 +4056,16 @@ function loadPortfolioManifest(configPath) {
|
|
|
3001
4056
|
function resolveManifestPaths(value, configDir) {
|
|
3002
4057
|
if (!isRecord(value)) return value;
|
|
3003
4058
|
const resolveEndpoint = (endpoint) => {
|
|
3004
|
-
if (!isRecord(endpoint) || typeof endpoint.path !== "string" ||
|
|
4059
|
+
if (!isRecord(endpoint) || typeof endpoint.path !== "string" || isAbsolute4(endpoint.path)) {
|
|
3005
4060
|
return endpoint;
|
|
3006
4061
|
}
|
|
3007
|
-
return { ...endpoint, path:
|
|
4062
|
+
return { ...endpoint, path: resolve5(configDir, endpoint.path) };
|
|
3008
4063
|
};
|
|
3009
4064
|
return {
|
|
3010
4065
|
...value,
|
|
3011
|
-
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !
|
|
4066
|
+
technologyGovernance: isRecord(value.technologyGovernance) && typeof value.technologyGovernance.strategySource === "string" && !isAbsolute4(value.technologyGovernance.strategySource) ? {
|
|
3012
4067
|
...value.technologyGovernance,
|
|
3013
|
-
strategySource:
|
|
4068
|
+
strategySource: resolve5(configDir, value.technologyGovernance.strategySource)
|
|
3014
4069
|
} : value.technologyGovernance,
|
|
3015
4070
|
controlPlane: resolveEndpoint(value.controlPlane),
|
|
3016
4071
|
executionEngine: resolveEndpoint(value.executionEngine),
|
|
@@ -3195,7 +4250,7 @@ function validateEndpoint(value, field, issues, technologyCatalog) {
|
|
|
3195
4250
|
});
|
|
3196
4251
|
return;
|
|
3197
4252
|
}
|
|
3198
|
-
if (!
|
|
4253
|
+
if (!existsSync15(value.path)) {
|
|
3199
4254
|
issues.push({
|
|
3200
4255
|
type: "missing-path",
|
|
3201
4256
|
id: typeof value.id === "string" ? value.id : void 0,
|
|
@@ -3626,13 +4681,13 @@ function isExactVersion(value) {
|
|
|
3626
4681
|
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(value);
|
|
3627
4682
|
}
|
|
3628
4683
|
function isRepositoryRelativePath(value) {
|
|
3629
|
-
if (value.length === 0 ||
|
|
4684
|
+
if (value.length === 0 || isAbsolute4(value)) return false;
|
|
3630
4685
|
const segments = value.replaceAll("\\", "/").split("/");
|
|
3631
4686
|
return !segments.includes("..");
|
|
3632
4687
|
}
|
|
3633
4688
|
function isExactRepositoryRelativePath(value) {
|
|
3634
4689
|
if (value.length === 0) return false;
|
|
3635
|
-
if (
|
|
4690
|
+
if (isAbsolute4(value)) return false;
|
|
3636
4691
|
if (/^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\") || value.startsWith("//"))
|
|
3637
4692
|
return false;
|
|
3638
4693
|
if (value.includes("\\")) return false;
|
|
@@ -3930,7 +4985,7 @@ function runScan(options) {
|
|
|
3930
4985
|
const inspection = resolveInspectionOptions(options);
|
|
3931
4986
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3932
4987
|
const report = inspectHost(inspection.value);
|
|
3933
|
-
const written = writeHostLensReport(report,
|
|
4988
|
+
const written = writeHostLensReport(report, resolve6(options.outPath));
|
|
3934
4989
|
if (options.json) {
|
|
3935
4990
|
console.log(JSON.stringify({ ok: true, ...written, summary: report.summary }, null, 2));
|
|
3936
4991
|
} else {
|
|
@@ -3950,9 +5005,9 @@ function runPlan(options) {
|
|
|
3950
5005
|
if (!inspection.ok) return reportConfigError(inspection.error);
|
|
3951
5006
|
const report = inspectHost(inspection.value);
|
|
3952
5007
|
const plan = createHostLensCleanupPlan(report);
|
|
3953
|
-
const outPath =
|
|
3954
|
-
|
|
3955
|
-
|
|
5008
|
+
const outPath = resolve6(options.outPath);
|
|
5009
|
+
mkdirSync7(dirname11(outPath), { recursive: true });
|
|
5010
|
+
writeFileSync6(outPath, `${JSON.stringify(plan, null, 2)}
|
|
3956
5011
|
`);
|
|
3957
5012
|
if (options.json)
|
|
3958
5013
|
console.log(JSON.stringify({ ok: true, outPath, actions: plan.actions.length }, null, 2));
|
|
@@ -4025,7 +5080,7 @@ function parseOptions(args) {
|
|
|
4025
5080
|
if (arg === "--home") {
|
|
4026
5081
|
const value = args[index + 1];
|
|
4027
5082
|
if (!value) return { ok: false, error: "Expected value after --home" };
|
|
4028
|
-
options.homeDir =
|
|
5083
|
+
options.homeDir = resolve6(value);
|
|
4029
5084
|
index += 1;
|
|
4030
5085
|
continue;
|
|
4031
5086
|
}
|
|
@@ -4039,7 +5094,7 @@ function parseOptions(args) {
|
|
|
4039
5094
|
if (arg === "--config") {
|
|
4040
5095
|
const value = args[index + 1];
|
|
4041
5096
|
if (!value) return { ok: false, error: "Expected value after --config" };
|
|
4042
|
-
options.configPath =
|
|
5097
|
+
options.configPath = resolve6(value);
|
|
4043
5098
|
index += 1;
|
|
4044
5099
|
continue;
|
|
4045
5100
|
}
|
|
@@ -4065,8 +5120,8 @@ function printUsage2() {
|
|
|
4065
5120
|
}
|
|
4066
5121
|
|
|
4067
5122
|
// src/learning/recall.ts
|
|
4068
|
-
import { existsSync as
|
|
4069
|
-
import { basename as
|
|
5123
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, readFileSync as readFileSync11 } from "node:fs";
|
|
5124
|
+
import { basename as basename4, join as join16, relative as relative8 } from "node:path";
|
|
4070
5125
|
function recallLearnings(root, options) {
|
|
4071
5126
|
const query = options.query.trim();
|
|
4072
5127
|
const terms = tokenize(query);
|
|
@@ -4089,16 +5144,16 @@ function recallLearnings(root, options) {
|
|
|
4089
5144
|
function loadLearningRecords(root) {
|
|
4090
5145
|
const recordsByTitle = /* @__PURE__ */ new Map();
|
|
4091
5146
|
for (const relativeDir of ["docs/reference/learnings", "docs/solutions"]) {
|
|
4092
|
-
const learningDir =
|
|
4093
|
-
if (!
|
|
5147
|
+
const learningDir = join16(root, relativeDir);
|
|
5148
|
+
if (!existsSync16(learningDir)) continue;
|
|
4094
5149
|
for (const path of listMarkdownFiles(learningDir)) {
|
|
4095
5150
|
const record = readLearningRecord(root, path);
|
|
4096
5151
|
const key = record.title.trim().toLowerCase();
|
|
4097
5152
|
if (!recordsByTitle.has(key)) recordsByTitle.set(key, record);
|
|
4098
5153
|
}
|
|
4099
5154
|
}
|
|
4100
|
-
const conceptsPath =
|
|
4101
|
-
if (
|
|
5155
|
+
const conceptsPath = join16(root, "CONCEPTS.md");
|
|
5156
|
+
if (existsSync16(conceptsPath)) {
|
|
4102
5157
|
const record = readLearningRecord(root, conceptsPath);
|
|
4103
5158
|
recordsByTitle.set(`concepts:${record.title.toLowerCase()}`, record);
|
|
4104
5159
|
}
|
|
@@ -4106,8 +5161,8 @@ function loadLearningRecords(root) {
|
|
|
4106
5161
|
}
|
|
4107
5162
|
function listMarkdownFiles(dir) {
|
|
4108
5163
|
const files = [];
|
|
4109
|
-
for (const entry of
|
|
4110
|
-
const absolutePath =
|
|
5164
|
+
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
5165
|
+
const absolutePath = join16(dir, entry.name);
|
|
4111
5166
|
if (entry.isDirectory()) {
|
|
4112
5167
|
files.push(...listMarkdownFiles(absolutePath));
|
|
4113
5168
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
@@ -4117,11 +5172,11 @@ function listMarkdownFiles(dir) {
|
|
|
4117
5172
|
return files.sort();
|
|
4118
5173
|
}
|
|
4119
5174
|
function readLearningRecord(root, absolutePath) {
|
|
4120
|
-
const content =
|
|
5175
|
+
const content = readFileSync11(absolutePath, "utf8");
|
|
4121
5176
|
const parsed = splitFrontmatter(content);
|
|
4122
5177
|
const body = parsed.body;
|
|
4123
5178
|
return {
|
|
4124
|
-
relativePath: normalizePath(
|
|
5179
|
+
relativePath: normalizePath(relative8(root, absolutePath)),
|
|
4125
5180
|
title: findTitle(parsed.frontmatter, body) ?? titleFromPath(absolutePath),
|
|
4126
5181
|
metadata: parsed.frontmatter,
|
|
4127
5182
|
body
|
|
@@ -4149,7 +5204,7 @@ function findTitle(frontmatter, body) {
|
|
|
4149
5204
|
return heading ? heading.slice(2).trim() : void 0;
|
|
4150
5205
|
}
|
|
4151
5206
|
function titleFromPath(path) {
|
|
4152
|
-
return
|
|
5207
|
+
return basename4(path, ".md").split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
4153
5208
|
}
|
|
4154
5209
|
function scoreRecord(record, terms) {
|
|
4155
5210
|
const title = record.title.toLowerCase();
|
|
@@ -4202,8 +5257,8 @@ function cleanMarkdownLine(input) {
|
|
|
4202
5257
|
}
|
|
4203
5258
|
|
|
4204
5259
|
// src/learning/capture.ts
|
|
4205
|
-
import { existsSync as
|
|
4206
|
-
import { basename as
|
|
5260
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5261
|
+
import { basename as basename5, join as join17, relative as relative9 } from "node:path";
|
|
4207
5262
|
function captureLearning(root, options) {
|
|
4208
5263
|
const title = options.title.trim();
|
|
4209
5264
|
const summary = options.summary.trim();
|
|
@@ -4211,13 +5266,13 @@ function captureLearning(root, options) {
|
|
|
4211
5266
|
if (!summary) throw new Error("summary is required");
|
|
4212
5267
|
const category = slugify(options.category ?? "workflow-issues") || "workflow-issues";
|
|
4213
5268
|
const moduleName = options.module?.trim() || "PGS learning capture";
|
|
4214
|
-
const dir =
|
|
4215
|
-
|
|
5269
|
+
const dir = join17(root, "docs/reference/learnings", category);
|
|
5270
|
+
mkdirSync8(dir, { recursive: true });
|
|
4216
5271
|
const path = uniquePath(dir, slugify(title) || "learning");
|
|
4217
|
-
const idSlug =
|
|
4218
|
-
|
|
5272
|
+
const idSlug = basename5(path, ".md");
|
|
5273
|
+
writeFileSync7(path, renderLearning({ title, summary, category, moduleName, idSlug }));
|
|
4219
5274
|
return {
|
|
4220
|
-
relativePath: normalizePath2(
|
|
5275
|
+
relativePath: normalizePath2(relative9(root, path)),
|
|
4221
5276
|
title,
|
|
4222
5277
|
captureMode: "pgs-native"
|
|
4223
5278
|
};
|
|
@@ -4259,10 +5314,10 @@ function renderLearning(options) {
|
|
|
4259
5314
|
}
|
|
4260
5315
|
function uniquePath(dir, slug) {
|
|
4261
5316
|
let index = 1;
|
|
4262
|
-
let candidate =
|
|
4263
|
-
while (
|
|
5317
|
+
let candidate = join17(dir, `${slug}.md`);
|
|
5318
|
+
while (existsSync17(candidate)) {
|
|
4264
5319
|
index += 1;
|
|
4265
|
-
candidate =
|
|
5320
|
+
candidate = join17(dir, `${slug}-${index}.md`);
|
|
4266
5321
|
}
|
|
4267
5322
|
return candidate;
|
|
4268
5323
|
}
|
|
@@ -4447,12 +5502,12 @@ function printUsage3() {
|
|
|
4447
5502
|
}
|
|
4448
5503
|
|
|
4449
5504
|
// src/commands/lens.ts
|
|
4450
|
-
import { mkdirSync as
|
|
4451
|
-
import { dirname as
|
|
5505
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5506
|
+
import { dirname as dirname14 } from "node:path";
|
|
4452
5507
|
|
|
4453
5508
|
// src/lens/audit.ts
|
|
4454
|
-
import { existsSync as
|
|
4455
|
-
import { basename as
|
|
5509
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5510
|
+
import { basename as basename6, dirname as dirname12, join as join18 } from "node:path";
|
|
4456
5511
|
var REQUIRED_ARTIFACTS = [
|
|
4457
5512
|
"manifest.md",
|
|
4458
5513
|
"raw/project-lens/architecture-lens.md",
|
|
@@ -4470,20 +5525,20 @@ function createProjectLensAuditPackage(targetDir, auditDir) {
|
|
|
4470
5525
|
version: 1,
|
|
4471
5526
|
target: {
|
|
4472
5527
|
path: targetDir,
|
|
4473
|
-
name:
|
|
5528
|
+
name: basename6(targetDir) || "target"
|
|
4474
5529
|
},
|
|
4475
5530
|
requiredArtifacts: [...REQUIRED_ARTIFACTS]
|
|
4476
5531
|
};
|
|
4477
|
-
|
|
4478
|
-
writeJson(
|
|
5532
|
+
mkdirSync9(auditDir, { recursive: true });
|
|
5533
|
+
writeJson(join18(auditDir, "audit.contract.json"), contract);
|
|
4479
5534
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4480
|
-
writeTemplate(
|
|
5535
|
+
writeTemplate(join18(auditDir, artifactPath), renderArtifactTemplate(artifactPath, contract));
|
|
4481
5536
|
}
|
|
4482
5537
|
return contract;
|
|
4483
5538
|
}
|
|
4484
5539
|
function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
4485
|
-
const contractPath =
|
|
4486
|
-
if (!
|
|
5540
|
+
const contractPath = join18(auditDir, "audit.contract.json");
|
|
5541
|
+
if (!existsSync18(contractPath)) {
|
|
4487
5542
|
return {
|
|
4488
5543
|
ok: false,
|
|
4489
5544
|
auditDir,
|
|
@@ -4497,7 +5552,7 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4497
5552
|
}
|
|
4498
5553
|
let contract;
|
|
4499
5554
|
try {
|
|
4500
|
-
contract = JSON.parse(
|
|
5555
|
+
contract = JSON.parse(readFileSync12(contractPath, "utf8"));
|
|
4501
5556
|
} catch (error) {
|
|
4502
5557
|
return {
|
|
4503
5558
|
ok: false,
|
|
@@ -4530,12 +5585,12 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4530
5585
|
}
|
|
4531
5586
|
}
|
|
4532
5587
|
for (const artifactPath of REQUIRED_ARTIFACTS) {
|
|
4533
|
-
const absolutePath =
|
|
4534
|
-
if (!
|
|
5588
|
+
const absolutePath = join18(auditDir, artifactPath);
|
|
5589
|
+
if (!existsSync18(absolutePath)) {
|
|
4535
5590
|
issues.push({ type: "missing-required-artifact", path: artifactPath });
|
|
4536
5591
|
continue;
|
|
4537
5592
|
}
|
|
4538
|
-
const content =
|
|
5593
|
+
const content = readFileSync12(absolutePath, "utf8");
|
|
4539
5594
|
if (isPendingArtifact(content)) {
|
|
4540
5595
|
issues.push({ type: "artifact-not-complete", path: artifactPath });
|
|
4541
5596
|
} else if (hasTemplateBody(content)) {
|
|
@@ -4551,13 +5606,13 @@ function checkProjectLensAuditPackage(auditDir, options = {}) {
|
|
|
4551
5606
|
};
|
|
4552
5607
|
}
|
|
4553
5608
|
function writeJson(path, value) {
|
|
4554
|
-
|
|
4555
|
-
|
|
5609
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5610
|
+
writeFileSync8(path, `${JSON.stringify(value, null, 2)}
|
|
4556
5611
|
`);
|
|
4557
5612
|
}
|
|
4558
5613
|
function writeTemplate(path, content) {
|
|
4559
|
-
|
|
4560
|
-
|
|
5614
|
+
mkdirSync9(dirname12(path), { recursive: true });
|
|
5615
|
+
writeFileSync8(path, content);
|
|
4561
5616
|
}
|
|
4562
5617
|
function renderArtifactTemplate(artifactPath, contract) {
|
|
4563
5618
|
const title = artifactPath.replace(/\.md$/, "").split("/").map((part) => part.replaceAll("-", " ")).join(" / ");
|
|
@@ -4836,13 +5891,13 @@ function formatLink(link) {
|
|
|
4836
5891
|
|
|
4837
5892
|
// src/lens/scan.ts
|
|
4838
5893
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
4839
|
-
import { existsSync as
|
|
5894
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14, statSync as statSync7 } from "node:fs";
|
|
4840
5895
|
import { homedir as homedir3 } from "node:os";
|
|
4841
|
-
import { join as
|
|
5896
|
+
import { join as join23 } from "node:path";
|
|
4842
5897
|
|
|
4843
5898
|
// src/host-ssot.ts
|
|
4844
|
-
import { lstatSync as
|
|
4845
|
-
import { dirname as
|
|
5899
|
+
import { lstatSync as lstatSync8, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "node:fs";
|
|
5900
|
+
import { dirname as dirname13, isAbsolute as isAbsolute5, join as join19, resolve as resolve7 } from "node:path";
|
|
4846
5901
|
function inspectProjectHostSsot(root) {
|
|
4847
5902
|
const agentsEntry = inspectCanonicalPath(root, "AGENTS.md");
|
|
4848
5903
|
const canonicalSkills = inspectCanonicalPath(root, ".agents/skills");
|
|
@@ -4886,7 +5941,7 @@ function inspectUserSkillsSsot(homeDir) {
|
|
|
4886
5941
|
};
|
|
4887
5942
|
}
|
|
4888
5943
|
function inspectCanonicalPath(root, path) {
|
|
4889
|
-
const absolutePath =
|
|
5944
|
+
const absolutePath = join19(root, path);
|
|
4890
5945
|
const stat = safeLstat2(absolutePath);
|
|
4891
5946
|
if (!stat) return { path, status: "missing" };
|
|
4892
5947
|
if (stat.isSymbolicLink()) {
|
|
@@ -4902,7 +5957,7 @@ function inspectCanonicalPath(root, path) {
|
|
|
4902
5957
|
return { path, status: "other" };
|
|
4903
5958
|
}
|
|
4904
5959
|
function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
4905
|
-
const absolutePath =
|
|
5960
|
+
const absolutePath = join19(root, path);
|
|
4906
5961
|
const stat = safeLstat2(absolutePath);
|
|
4907
5962
|
const base = { path, expectedRawTarget, compliant: false };
|
|
4908
5963
|
if (!stat) return { ...base, status: "missing" };
|
|
@@ -4912,8 +5967,8 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4912
5967
|
return { ...base, status: "other" };
|
|
4913
5968
|
}
|
|
4914
5969
|
const rawTarget = normalizeSymlinkTarget(readlinkSync3(absolutePath));
|
|
4915
|
-
const resolvedTarget =
|
|
4916
|
-
const expectedPath =
|
|
5970
|
+
const resolvedTarget = resolve7(dirname13(absolutePath), rawTarget);
|
|
5971
|
+
const expectedPath = resolve7(dirname13(absolutePath), expectedRawTarget);
|
|
4917
5972
|
const targetStat = safeLstat2(resolvedTarget);
|
|
4918
5973
|
if (!targetStat) {
|
|
4919
5974
|
return {
|
|
@@ -4932,7 +5987,7 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4932
5987
|
if (!targetMatches) {
|
|
4933
5988
|
return { ...base, rawTarget, resolvedTarget, status: "wrong-target" };
|
|
4934
5989
|
}
|
|
4935
|
-
if (
|
|
5990
|
+
if (isAbsolute5(rawTarget)) {
|
|
4936
5991
|
return { ...base, rawTarget, resolvedTarget, status: "absolute-symlink" };
|
|
4937
5992
|
}
|
|
4938
5993
|
if (rawTarget !== expectedRawTarget) {
|
|
@@ -4953,16 +6008,16 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
|
|
|
4953
6008
|
}
|
|
4954
6009
|
function safeLstat2(path) {
|
|
4955
6010
|
try {
|
|
4956
|
-
return
|
|
6011
|
+
return lstatSync8(path);
|
|
4957
6012
|
} catch {
|
|
4958
6013
|
return void 0;
|
|
4959
6014
|
}
|
|
4960
6015
|
}
|
|
4961
6016
|
|
|
4962
6017
|
// src/portfolio/redundancy.ts
|
|
4963
|
-
import { existsSync as
|
|
6018
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, statSync as statSync6 } from "node:fs";
|
|
4964
6019
|
import { homedir as homedir2 } from "node:os";
|
|
4965
|
-
import { join as
|
|
6020
|
+
import { join as join20 } from "node:path";
|
|
4966
6021
|
var DEFAULT_CACHE_THRESHOLD_BYTES = 1e9;
|
|
4967
6022
|
var MAX_CACHE_ENTRIES = 2e4;
|
|
4968
6023
|
function inspectHostRedundancy(options = {}) {
|
|
@@ -4993,8 +6048,8 @@ function inspectProjectRedundancy(root, options = {}) {
|
|
|
4993
6048
|
}
|
|
4994
6049
|
function inspectLegacyDirectories(root) {
|
|
4995
6050
|
const relativePath = ".agent";
|
|
4996
|
-
const path =
|
|
4997
|
-
if (!
|
|
6051
|
+
const path = join20(root, relativePath);
|
|
6052
|
+
if (!existsSync19(path)) return [];
|
|
4998
6053
|
const stats = collectDirectoryStats(path);
|
|
4999
6054
|
return [
|
|
5000
6055
|
{
|
|
@@ -5009,16 +6064,16 @@ function inspectLegacyDirectories(root) {
|
|
|
5009
6064
|
function getPlaywrightCachePaths(homeDir, configuredPath) {
|
|
5010
6065
|
const candidates = [
|
|
5011
6066
|
configuredPath && configuredPath !== "0" ? configuredPath : void 0,
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
6067
|
+
join20(homeDir, "Library/Caches/ms-playwright"),
|
|
6068
|
+
join20(homeDir, ".cache/ms-playwright"),
|
|
6069
|
+
join20(homeDir, "AppData/Local/ms-playwright")
|
|
5015
6070
|
].filter((path) => Boolean(path));
|
|
5016
6071
|
return [...new Set(candidates)];
|
|
5017
6072
|
}
|
|
5018
6073
|
function inspectPlaywrightCache(path, cache) {
|
|
5019
6074
|
const cached = cache?.get(path);
|
|
5020
6075
|
if (cached) return cached;
|
|
5021
|
-
if (!
|
|
6076
|
+
if (!existsSync19(path)) {
|
|
5022
6077
|
const missing = {
|
|
5023
6078
|
path,
|
|
5024
6079
|
exists: false,
|
|
@@ -5033,7 +6088,7 @@ function inspectPlaywrightCache(path, cache) {
|
|
|
5033
6088
|
const stats = collectDirectoryStats(path);
|
|
5034
6089
|
let revisionCount = 0;
|
|
5035
6090
|
try {
|
|
5036
|
-
revisionCount =
|
|
6091
|
+
revisionCount = readdirSync10(path, { withFileTypes: true }).filter(
|
|
5037
6092
|
(entry) => entry.isDirectory()
|
|
5038
6093
|
).length;
|
|
5039
6094
|
} catch {
|
|
@@ -5060,7 +6115,7 @@ function collectDirectoryStats(root) {
|
|
|
5060
6115
|
if (!current) continue;
|
|
5061
6116
|
let entries;
|
|
5062
6117
|
try {
|
|
5063
|
-
entries =
|
|
6118
|
+
entries = readdirSync10(current, { withFileTypes: true });
|
|
5064
6119
|
} catch {
|
|
5065
6120
|
continue;
|
|
5066
6121
|
}
|
|
@@ -5069,13 +6124,13 @@ function collectDirectoryStats(root) {
|
|
|
5069
6124
|
truncated = true;
|
|
5070
6125
|
break;
|
|
5071
6126
|
}
|
|
5072
|
-
const path =
|
|
6127
|
+
const path = join20(current, entry.name);
|
|
5073
6128
|
if (entry.isDirectory()) {
|
|
5074
6129
|
pending.push(path);
|
|
5075
6130
|
} else if (entry.isFile()) {
|
|
5076
6131
|
fileCount += 1;
|
|
5077
6132
|
try {
|
|
5078
|
-
bytes +=
|
|
6133
|
+
bytes += statSync6(path).size;
|
|
5079
6134
|
} catch {
|
|
5080
6135
|
}
|
|
5081
6136
|
}
|
|
@@ -5086,11 +6141,11 @@ function collectDirectoryStats(root) {
|
|
|
5086
6141
|
}
|
|
5087
6142
|
|
|
5088
6143
|
// src/portfolio/verification.ts
|
|
5089
|
-
import { existsSync as
|
|
5090
|
-
import { join as
|
|
6144
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "node:fs";
|
|
6145
|
+
import { join as join21 } from "node:path";
|
|
5091
6146
|
var REQUIRED_PROJECT_SCRIPTS = ["typecheck", "lint", "format:check", "verify"];
|
|
5092
6147
|
function inspectProjectVerification(root) {
|
|
5093
|
-
const packageJson = readPackageJson(
|
|
6148
|
+
const packageJson = readPackageJson(join21(root, "package.json"));
|
|
5094
6149
|
const scripts = Object.fromEntries(
|
|
5095
6150
|
REQUIRED_PROJECT_SCRIPTS.map((name) => [
|
|
5096
6151
|
name,
|
|
@@ -5108,9 +6163,9 @@ function inspectProjectVerification(root) {
|
|
|
5108
6163
|
};
|
|
5109
6164
|
}
|
|
5110
6165
|
function readPackageJson(path) {
|
|
5111
|
-
if (!
|
|
6166
|
+
if (!existsSync20(path)) return void 0;
|
|
5112
6167
|
try {
|
|
5113
|
-
return JSON.parse(
|
|
6168
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
5114
6169
|
} catch {
|
|
5115
6170
|
return void 0;
|
|
5116
6171
|
}
|
|
@@ -5118,8 +6173,8 @@ function readPackageJson(path) {
|
|
|
5118
6173
|
|
|
5119
6174
|
// src/repository-files.ts
|
|
5120
6175
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
5121
|
-
import { existsSync as
|
|
5122
|
-
import { isAbsolute as
|
|
6176
|
+
import { existsSync as existsSync21, readdirSync as readdirSync11 } from "node:fs";
|
|
6177
|
+
import { isAbsolute as isAbsolute6, join as join22, posix as posix3, relative as relative10 } from "node:path";
|
|
5123
6178
|
var gitMaxBufferBytes = 64 * 1024 * 1024;
|
|
5124
6179
|
var RepositoryFileDiscoveryError = class extends Error {
|
|
5125
6180
|
constructor(message) {
|
|
@@ -5130,7 +6185,7 @@ var RepositoryFileDiscoveryError = class extends Error {
|
|
|
5130
6185
|
function discoverRepositoryFiles(root, options = {}) {
|
|
5131
6186
|
const probe = runGit(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
5132
6187
|
if (!probe.ok) {
|
|
5133
|
-
if (!probe.notRepository ||
|
|
6188
|
+
if (!probe.notRepository || existsSync21(join22(root, ".git"))) {
|
|
5134
6189
|
throw new RepositoryFileDiscoveryError(probe.message);
|
|
5135
6190
|
}
|
|
5136
6191
|
return {
|
|
@@ -5161,7 +6216,7 @@ function discoverRepositoryFiles(root, options = {}) {
|
|
|
5161
6216
|
`Git returned a path outside the repository boundary: ${path}`
|
|
5162
6217
|
);
|
|
5163
6218
|
}
|
|
5164
|
-
if (
|
|
6219
|
+
if (existsSync21(join22(root, normalized))) files.add(normalized);
|
|
5165
6220
|
}
|
|
5166
6221
|
return { source: "git", files: [...files].sort() };
|
|
5167
6222
|
}
|
|
@@ -5173,21 +6228,21 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5173
6228
|
const maxDepth = options.fallbackMaxDepth ?? Number.POSITIVE_INFINITY;
|
|
5174
6229
|
const ignoredDirectories2 = options.fallbackIgnoredDirectories ?? /* @__PURE__ */ new Set();
|
|
5175
6230
|
const visit = (directory, depth) => {
|
|
5176
|
-
if (depth > maxDepth || !
|
|
6231
|
+
if (depth > maxDepth || !existsSync21(directory)) return;
|
|
5177
6232
|
let entries;
|
|
5178
6233
|
try {
|
|
5179
|
-
entries =
|
|
6234
|
+
entries = readdirSync11(directory, { withFileTypes: true });
|
|
5180
6235
|
} catch {
|
|
5181
6236
|
return;
|
|
5182
6237
|
}
|
|
5183
6238
|
for (const entry of entries) {
|
|
5184
|
-
const absolutePath =
|
|
6239
|
+
const absolutePath = join22(directory, entry.name);
|
|
5185
6240
|
if (entry.isDirectory()) {
|
|
5186
6241
|
if (!ignoredDirectories2.has(entry.name)) visit(absolutePath, depth + 1);
|
|
5187
6242
|
continue;
|
|
5188
6243
|
}
|
|
5189
6244
|
if (!entry.isFile()) continue;
|
|
5190
|
-
const relativePath = normalizeRepositoryRelativePath(
|
|
6245
|
+
const relativePath = normalizeRepositoryRelativePath(relative10(root, absolutePath));
|
|
5191
6246
|
if (isSafeRepositoryRelativePath(relativePath) && (options.fallbackIncludeFile?.(relativePath) ?? true)) {
|
|
5192
6247
|
files.add(relativePath);
|
|
5193
6248
|
}
|
|
@@ -5197,7 +6252,7 @@ function discoverFilesystemFiles(root, options) {
|
|
|
5197
6252
|
return [...files].sort();
|
|
5198
6253
|
}
|
|
5199
6254
|
function isSafeRepositoryRelativePath(path) {
|
|
5200
|
-
return path !== "" && path !== "." && !
|
|
6255
|
+
return path !== "" && path !== "." && !isAbsolute6(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
|
|
5201
6256
|
}
|
|
5202
6257
|
function runGit(root, args) {
|
|
5203
6258
|
const result = spawnSync2("git", ["-C", root, ...args], {
|
|
@@ -5237,7 +6292,7 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5237
6292
|
includedFileCount: files.length,
|
|
5238
6293
|
excludedFileCount: candidateFiles.length - files.length
|
|
5239
6294
|
},
|
|
5240
|
-
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) =>
|
|
6295
|
+
aiEntryFiles: ["AGENTS.md", "CLAUDE.md"].filter((file) => existsSync22(join23(targetDir, file))),
|
|
5241
6296
|
aiConfigFiles: [],
|
|
5242
6297
|
hostSsot: inspectProjectHostSsot(targetDir),
|
|
5243
6298
|
userHostSsot: inspectUserSkillsSsot(options.homeDir ?? process.env.HOME ?? homedir3()),
|
|
@@ -5248,19 +6303,19 @@ function scanProjectLensTarget(targetDir, options = {}) {
|
|
|
5248
6303
|
}),
|
|
5249
6304
|
packageJson,
|
|
5250
6305
|
docs: {
|
|
5251
|
-
hasDocsDirectory:
|
|
6306
|
+
hasDocsDirectory: existsSync22(join23(targetDir, "docs")),
|
|
5252
6307
|
markdownFileCount: markdownFiles.length,
|
|
5253
6308
|
governanceFiles: markdownFiles.filter((file) => file.startsWith("docs/governance/") || file.startsWith("docs/policy/")).sort()
|
|
5254
6309
|
},
|
|
5255
6310
|
git: readGitState(targetDir),
|
|
5256
|
-
largeFiles: files.map((file) => ({ path: file, bytes:
|
|
6311
|
+
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
6312
|
};
|
|
5258
6313
|
}
|
|
5259
6314
|
function readPackageJson2(targetDir) {
|
|
5260
|
-
const packageJsonPath =
|
|
5261
|
-
if (!
|
|
6315
|
+
const packageJsonPath = join23(targetDir, "package.json");
|
|
6316
|
+
if (!existsSync22(packageJsonPath)) return void 0;
|
|
5262
6317
|
try {
|
|
5263
|
-
const packageJson = JSON.parse(
|
|
6318
|
+
const packageJson = JSON.parse(readFileSync14(packageJsonPath, "utf8"));
|
|
5264
6319
|
return {
|
|
5265
6320
|
scripts: Object.keys(packageJson.scripts ?? {}).sort(),
|
|
5266
6321
|
dependencies: Object.keys(packageJson.dependencies ?? {}).sort(),
|
|
@@ -5351,8 +6406,8 @@ function runLensReport(args) {
|
|
|
5351
6406
|
}
|
|
5352
6407
|
const report = scanProjectLensTarget(options.value.targetDir);
|
|
5353
6408
|
const markdown = renderProjectLensMarkdownReport(report);
|
|
5354
|
-
|
|
5355
|
-
|
|
6409
|
+
mkdirSync10(dirname14(options.value.outPath), { recursive: true });
|
|
6410
|
+
writeFileSync9(options.value.outPath, markdown);
|
|
5356
6411
|
console.log(`report: ${options.value.outPath}`);
|
|
5357
6412
|
return 0;
|
|
5358
6413
|
}
|
|
@@ -5457,15 +6512,15 @@ function printUsage4() {
|
|
|
5457
6512
|
}
|
|
5458
6513
|
|
|
5459
6514
|
// src/commands/portfolio.ts
|
|
5460
|
-
import { existsSync as
|
|
5461
|
-
import { join as
|
|
6515
|
+
import { existsSync as existsSync34, readFileSync as readFileSync19 } from "node:fs";
|
|
6516
|
+
import { join as join35 } from "node:path";
|
|
5462
6517
|
|
|
5463
6518
|
// src/portfolio/doctor.ts
|
|
5464
6519
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5465
|
-
import { existsSync as
|
|
6520
|
+
import { existsSync as existsSync25, readFileSync as readFileSync17 } from "node:fs";
|
|
5466
6521
|
import { createRequire as createRequire2 } from "node:module";
|
|
5467
6522
|
import { homedir as homedir4 } from "node:os";
|
|
5468
|
-
import { dirname as
|
|
6523
|
+
import { dirname as dirname16, join as join26 } from "node:path";
|
|
5469
6524
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5470
6525
|
|
|
5471
6526
|
// src/host-tooling/inventory.ts
|
|
@@ -5557,8 +6612,8 @@ function isRecord2(value) {
|
|
|
5557
6612
|
}
|
|
5558
6613
|
|
|
5559
6614
|
// src/portfolio/asset-state.ts
|
|
5560
|
-
import { existsSync as
|
|
5561
|
-
import { join as
|
|
6615
|
+
import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
|
|
6616
|
+
import { join as join24 } from "node:path";
|
|
5562
6617
|
function comparePortfolioAssetState(options) {
|
|
5563
6618
|
const expectedManifest = readPlanDocument(
|
|
5564
6619
|
options.expectedPlan,
|
|
@@ -5569,10 +6624,10 @@ function comparePortfolioAssetState(options) {
|
|
|
5569
6624
|
".pro-gov/assets.lock.json"
|
|
5570
6625
|
);
|
|
5571
6626
|
const currentManifest = readJsonFile(
|
|
5572
|
-
|
|
6627
|
+
join24(options.targetDir, ".pro-gov/assets.json")
|
|
5573
6628
|
);
|
|
5574
6629
|
const currentLock = readJsonFile(
|
|
5575
|
-
|
|
6630
|
+
join24(options.targetDir, ".pro-gov/assets.lock.json")
|
|
5576
6631
|
);
|
|
5577
6632
|
const issues = [];
|
|
5578
6633
|
if (!sameStrings(currentManifest?.bundleIds, expectedManifest?.bundleIds)) {
|
|
@@ -5600,7 +6655,7 @@ function comparePortfolioAssetState(options) {
|
|
|
5600
6655
|
(action) => action.type === "adopt-symlink" && action.assetId === entry.id && action.legacyTargetPath === entry.targetPath
|
|
5601
6656
|
))
|
|
5602
6657
|
continue;
|
|
5603
|
-
const targetAbsolutePath =
|
|
6658
|
+
const targetAbsolutePath = join24(options.targetDir, entry.targetPath);
|
|
5604
6659
|
if (!pathIsSymlink(targetAbsolutePath)) continue;
|
|
5605
6660
|
issues.push({
|
|
5606
6661
|
type: "orphaned-managed-symlink",
|
|
@@ -5622,9 +6677,9 @@ function readPlanDocument(plan, targetPath) {
|
|
|
5622
6677
|
}
|
|
5623
6678
|
}
|
|
5624
6679
|
function readJsonFile(path) {
|
|
5625
|
-
if (!
|
|
6680
|
+
if (!existsSync23(path)) return void 0;
|
|
5626
6681
|
try {
|
|
5627
|
-
return JSON.parse(
|
|
6682
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
5628
6683
|
} catch {
|
|
5629
6684
|
return void 0;
|
|
5630
6685
|
}
|
|
@@ -5650,7 +6705,7 @@ function normalizeLock(lock) {
|
|
|
5650
6705
|
}
|
|
5651
6706
|
function pathIsSymlink(path) {
|
|
5652
6707
|
try {
|
|
5653
|
-
return
|
|
6708
|
+
return lstatSync9(path).isSymbolicLink();
|
|
5654
6709
|
} catch {
|
|
5655
6710
|
return false;
|
|
5656
6711
|
}
|
|
@@ -5658,8 +6713,8 @@ function pathIsSymlink(path) {
|
|
|
5658
6713
|
|
|
5659
6714
|
// src/portfolio/version-policy.ts
|
|
5660
6715
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5661
|
-
import { existsSync as
|
|
5662
|
-
import { dirname as
|
|
6716
|
+
import { existsSync as existsSync24, lstatSync as lstatSync10, readFileSync as readFileSync16 } from "node:fs";
|
|
6717
|
+
import { dirname as dirname15, join as join25 } from "node:path";
|
|
5663
6718
|
function inspectVersionPolicy(root, policy, projectType) {
|
|
5664
6719
|
if (!policy) return { status: "compliant", packages: [], runtimes: [], attentionCount: 0 };
|
|
5665
6720
|
const packageManifests = collectPackageManifests(root);
|
|
@@ -5768,10 +6823,10 @@ function findDeclaredVersion(packageJson, name) {
|
|
|
5768
6823
|
function readInstalledVersion(root, name, fromDirectory = root) {
|
|
5769
6824
|
let current = fromDirectory;
|
|
5770
6825
|
while (true) {
|
|
5771
|
-
const packageJson = readJson2(
|
|
6826
|
+
const packageJson = readJson2(join25(current, "node_modules", name, "package.json"));
|
|
5772
6827
|
if (typeof packageJson?.version === "string") return packageJson.version;
|
|
5773
6828
|
if (current === root) return void 0;
|
|
5774
|
-
const parent =
|
|
6829
|
+
const parent = dirname15(current);
|
|
5775
6830
|
if (parent === current) return void 0;
|
|
5776
6831
|
current = parent;
|
|
5777
6832
|
}
|
|
@@ -5806,15 +6861,15 @@ function collectPackageManifests(root) {
|
|
|
5806
6861
|
if (relativePath.split("/").at(-1) !== "package.json" || directorySegments.length > 6 || directorySegments.some((segment) => ignored.has(segment))) {
|
|
5807
6862
|
return;
|
|
5808
6863
|
}
|
|
5809
|
-
const path =
|
|
6864
|
+
const path = join25(root, relativePath);
|
|
5810
6865
|
try {
|
|
5811
|
-
if (!
|
|
6866
|
+
if (!lstatSync10(path).isFile()) return;
|
|
5812
6867
|
} catch {
|
|
5813
6868
|
return;
|
|
5814
6869
|
}
|
|
5815
6870
|
const packageJson = readJson2(path);
|
|
5816
6871
|
if (!packageJson) return;
|
|
5817
|
-
manifests.set(relativePath, { path: relativePath, directory:
|
|
6872
|
+
manifests.set(relativePath, { path: relativePath, directory: dirname15(path), packageJson });
|
|
5818
6873
|
};
|
|
5819
6874
|
const discovery = discoverRepositoryFiles(root, {
|
|
5820
6875
|
gitPathspecs: ["package.json", ":(glob)**/package.json"],
|
|
@@ -5829,9 +6884,9 @@ function unique(values) {
|
|
|
5829
6884
|
return [...new Set(values)];
|
|
5830
6885
|
}
|
|
5831
6886
|
function readJson2(path) {
|
|
5832
|
-
if (!
|
|
6887
|
+
if (!existsSync24(path)) return void 0;
|
|
5833
6888
|
try {
|
|
5834
|
-
return JSON.parse(
|
|
6889
|
+
return JSON.parse(readFileSync16(path, "utf8"));
|
|
5835
6890
|
} catch {
|
|
5836
6891
|
return void 0;
|
|
5837
6892
|
}
|
|
@@ -5864,12 +6919,12 @@ function inspectTarget(options) {
|
|
|
5864
6919
|
const { target } = options;
|
|
5865
6920
|
const hostSsot = inspectProjectHostSsot(target.path);
|
|
5866
6921
|
const issues = [];
|
|
5867
|
-
const packageJson = readJson3(
|
|
6922
|
+
const packageJson = readJson3(join26(target.path, "package.json"));
|
|
5868
6923
|
const packages = {};
|
|
5869
6924
|
for (const packageName of ["@pieai/pro-gov", "@pieai/doc-gov"]) {
|
|
5870
6925
|
const declared = packageJson?.devDependencies?.[packageName] ?? packageJson?.dependencies?.[packageName];
|
|
5871
6926
|
const installedPackage = readJson3(
|
|
5872
|
-
|
|
6927
|
+
join26(target.path, "node_modules", packageName, "package.json")
|
|
5873
6928
|
);
|
|
5874
6929
|
const installed = installedPackage?.version;
|
|
5875
6930
|
const expected = options.expectedPackageVersions[packageName];
|
|
@@ -5929,7 +6984,7 @@ function inspectTarget(options) {
|
|
|
5929
6984
|
type: "asset-lock-drift",
|
|
5930
6985
|
message: error instanceof Error ? error.message : String(error)
|
|
5931
6986
|
});
|
|
5932
|
-
if (!
|
|
6987
|
+
if (!existsSync25(join26(target.path, ".pro-gov/assets.json"))) {
|
|
5933
6988
|
issues.push({ type: "bundle-drift", message: "Target asset manifest is missing." });
|
|
5934
6989
|
}
|
|
5935
6990
|
}
|
|
@@ -5947,15 +7002,15 @@ function inspectTarget(options) {
|
|
|
5947
7002
|
};
|
|
5948
7003
|
}
|
|
5949
7004
|
function readTargetAssetHost(targetDir) {
|
|
5950
|
-
const lockfile = readJson3(
|
|
7005
|
+
const lockfile = readJson3(join26(targetDir, ".pro-gov/assets.lock.json"));
|
|
5951
7006
|
return isAssetRegistryHost(lockfile?.host) ? lockfile.host : void 0;
|
|
5952
7007
|
}
|
|
5953
7008
|
function isAssetRegistryHost(value) {
|
|
5954
7009
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
5955
7010
|
}
|
|
5956
7011
|
function runTargetChecks(target) {
|
|
5957
|
-
const proGovCli =
|
|
5958
|
-
const docGovCli =
|
|
7012
|
+
const proGovCli = join26(target.path, "node_modules/@pieai/pro-gov/dist/cli.js");
|
|
7013
|
+
const docGovCli = join26(target.path, "node_modules/@pieai/doc-gov/dist/cli.js");
|
|
5959
7014
|
const commands = [
|
|
5960
7015
|
{
|
|
5961
7016
|
name: "pro-gov doctor",
|
|
@@ -5966,7 +7021,7 @@ function runTargetChecks(target) {
|
|
|
5966
7021
|
{ name: "doc-gov scan --check", cli: docGovCli, args: ["scan", "--check"] }
|
|
5967
7022
|
];
|
|
5968
7023
|
return commands.map((command2) => {
|
|
5969
|
-
if (!
|
|
7024
|
+
if (!existsSync25(command2.cli)) return { name: command2.name, status: null };
|
|
5970
7025
|
const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
|
|
5971
7026
|
cwd: target.path,
|
|
5972
7027
|
encoding: "utf8",
|
|
@@ -6005,18 +7060,18 @@ function getExpectedPackageVersions() {
|
|
|
6005
7060
|
};
|
|
6006
7061
|
}
|
|
6007
7062
|
function findOwnPackageJson() {
|
|
6008
|
-
let current =
|
|
7063
|
+
let current = dirname16(fileURLToPath4(import.meta.url));
|
|
6009
7064
|
for (let depth = 0; depth < 5; depth += 1) {
|
|
6010
|
-
const candidate =
|
|
6011
|
-
if (
|
|
6012
|
-
current =
|
|
7065
|
+
const candidate = join26(current, "package.json");
|
|
7066
|
+
if (existsSync25(candidate)) return candidate;
|
|
7067
|
+
current = dirname16(current);
|
|
6013
7068
|
}
|
|
6014
7069
|
return "";
|
|
6015
7070
|
}
|
|
6016
7071
|
function readJson3(path) {
|
|
6017
|
-
if (!path || !
|
|
7072
|
+
if (!path || !existsSync25(path)) return void 0;
|
|
6018
7073
|
try {
|
|
6019
|
-
return JSON.parse(
|
|
7074
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
6020
7075
|
} catch {
|
|
6021
7076
|
return void 0;
|
|
6022
7077
|
}
|
|
@@ -6033,15 +7088,15 @@ function deduplicateIssues(issues) {
|
|
|
6033
7088
|
|
|
6034
7089
|
// src/portfolio/ai-health/index.ts
|
|
6035
7090
|
import { homedir as homedir5 } from "node:os";
|
|
6036
|
-
import { dirname as
|
|
7091
|
+
import { dirname as dirname18, join as join34, resolve as resolve10 } from "node:path";
|
|
6037
7092
|
|
|
6038
7093
|
// src/portfolio/ai-health/entries.ts
|
|
6039
|
-
import { existsSync as
|
|
6040
|
-
import { join as
|
|
7094
|
+
import { existsSync as existsSync27, lstatSync as lstatSync12, realpathSync as realpathSync7 } from "node:fs";
|
|
7095
|
+
import { join as join27 } from "node:path";
|
|
6041
7096
|
|
|
6042
7097
|
// src/portfolio/ai-health/shared.ts
|
|
6043
7098
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
6044
|
-
import { existsSync as
|
|
7099
|
+
import { existsSync as existsSync26, lstatSync as lstatSync11, readFileSync as readFileSync18, readdirSync as readdirSync12, realpathSync as realpathSync6, statSync as statSync8 } from "node:fs";
|
|
6045
7100
|
function safeRealpath(path) {
|
|
6046
7101
|
try {
|
|
6047
7102
|
return realpathSync6(path);
|
|
@@ -6072,7 +7127,7 @@ function jsonObjectKeys(path, key) {
|
|
|
6072
7127
|
return Object.keys(value[key]).sort();
|
|
6073
7128
|
}
|
|
6074
7129
|
function tomlMcpNames(path) {
|
|
6075
|
-
if (!
|
|
7130
|
+
if (!existsSync26(path)) return [];
|
|
6076
7131
|
const names = /* @__PURE__ */ new Set();
|
|
6077
7132
|
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
6078
7133
|
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^.\]]+))\]\s*$/);
|
|
@@ -6083,35 +7138,35 @@ function tomlMcpNames(path) {
|
|
|
6083
7138
|
}
|
|
6084
7139
|
function readJson4(path) {
|
|
6085
7140
|
try {
|
|
6086
|
-
return JSON.parse(
|
|
7141
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
6087
7142
|
} catch {
|
|
6088
7143
|
return void 0;
|
|
6089
7144
|
}
|
|
6090
7145
|
}
|
|
6091
7146
|
function safeRead(path) {
|
|
6092
7147
|
try {
|
|
6093
|
-
return
|
|
7148
|
+
return readFileSync18(path, "utf8");
|
|
6094
7149
|
} catch {
|
|
6095
7150
|
return "";
|
|
6096
7151
|
}
|
|
6097
7152
|
}
|
|
6098
7153
|
function safeReadDir(path) {
|
|
6099
7154
|
try {
|
|
6100
|
-
return
|
|
7155
|
+
return readdirSync12(path).sort();
|
|
6101
7156
|
} catch {
|
|
6102
7157
|
return [];
|
|
6103
7158
|
}
|
|
6104
7159
|
}
|
|
6105
7160
|
function safeIsDirectory(path) {
|
|
6106
7161
|
try {
|
|
6107
|
-
return
|
|
7162
|
+
return statSync8(path).isDirectory();
|
|
6108
7163
|
} catch {
|
|
6109
7164
|
return false;
|
|
6110
7165
|
}
|
|
6111
7166
|
}
|
|
6112
7167
|
function pathLexists(path) {
|
|
6113
7168
|
try {
|
|
6114
|
-
|
|
7169
|
+
lstatSync11(path);
|
|
6115
7170
|
return true;
|
|
6116
7171
|
} catch {
|
|
6117
7172
|
return false;
|
|
@@ -6149,12 +7204,12 @@ function hasWorkflowReminderHooks(hooks) {
|
|
|
6149
7204
|
);
|
|
6150
7205
|
}
|
|
6151
7206
|
function inspectEntries(root) {
|
|
6152
|
-
const agentsPath =
|
|
6153
|
-
const agents = !
|
|
6154
|
-
const claudePath =
|
|
7207
|
+
const agentsPath = join27(root, "AGENTS.md");
|
|
7208
|
+
const agents = !existsSync27(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
7209
|
+
const claudePath = join27(root, "CLAUDE.md");
|
|
6155
7210
|
let claude = "missing";
|
|
6156
7211
|
if (pathLexists(claudePath)) {
|
|
6157
|
-
const info =
|
|
7212
|
+
const info = lstatSync12(claudePath);
|
|
6158
7213
|
if (info.isSymbolicLink()) {
|
|
6159
7214
|
try {
|
|
6160
7215
|
claude = realpathSync7(claudePath) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6171,19 +7226,19 @@ function inspectEntries(root) {
|
|
|
6171
7226
|
var AGENT_LINK_ROOTS = [".agents/workflows", ".agents/commands", ".claude/commands"];
|
|
6172
7227
|
function inspectAgentLinks(root) {
|
|
6173
7228
|
const entries = AGENT_LINK_ROOTS.flatMap((directory) => {
|
|
6174
|
-
const directoryPath =
|
|
7229
|
+
const directoryPath = join27(root, directory);
|
|
6175
7230
|
if (!pathLexists(directoryPath)) return [];
|
|
6176
7231
|
try {
|
|
6177
|
-
if (!
|
|
7232
|
+
if (!lstatSync12(directoryPath).isDirectory()) return [];
|
|
6178
7233
|
} catch {
|
|
6179
7234
|
return [];
|
|
6180
7235
|
}
|
|
6181
7236
|
return safeReadDir(directoryPath).filter((name) => !name.startsWith(".")).flatMap((name) => {
|
|
6182
7237
|
const relativePath = `${directory}/${name}`;
|
|
6183
|
-
const path =
|
|
7238
|
+
const path = join27(root, relativePath);
|
|
6184
7239
|
let stat;
|
|
6185
7240
|
try {
|
|
6186
|
-
stat =
|
|
7241
|
+
stat = lstatSync12(path);
|
|
6187
7242
|
} catch {
|
|
6188
7243
|
return [];
|
|
6189
7244
|
}
|
|
@@ -6204,9 +7259,9 @@ function inspectAgentLinks(root) {
|
|
|
6204
7259
|
};
|
|
6205
7260
|
}
|
|
6206
7261
|
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
6207
|
-
const path =
|
|
7262
|
+
const path = join27(root, filename);
|
|
6208
7263
|
if (!pathLexists(path)) return "missing";
|
|
6209
|
-
const info =
|
|
7264
|
+
const info = lstatSync12(path);
|
|
6210
7265
|
if (info.isSymbolicLink()) {
|
|
6211
7266
|
try {
|
|
6212
7267
|
return realpathSync7(path) === realpathSync7(agentsPath) ? "agents-symlink" : "custom";
|
|
@@ -6244,7 +7299,7 @@ function inspectHooks(root) {
|
|
|
6244
7299
|
{ host: "codex", path: ".codex/hooks.json" }
|
|
6245
7300
|
];
|
|
6246
7301
|
return configs.map((config) => {
|
|
6247
|
-
const value = readJson4(
|
|
7302
|
+
const value = readJson4(join27(root, config.path));
|
|
6248
7303
|
const counts = /* @__PURE__ */ new Map();
|
|
6249
7304
|
collectHookEvents(value, counts);
|
|
6250
7305
|
return {
|
|
@@ -6265,18 +7320,18 @@ function collectHookEvents(value, counts) {
|
|
|
6265
7320
|
}
|
|
6266
7321
|
}
|
|
6267
7322
|
function inspectDocs(root, expected) {
|
|
6268
|
-
const packageJson = readJson4(
|
|
7323
|
+
const packageJson = readJson4(join27(root, "package.json"));
|
|
6269
7324
|
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
6270
7325
|
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
6271
7326
|
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
6272
|
-
const routerMatch = safeRead(
|
|
7327
|
+
const routerMatch = safeRead(join27(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
6273
7328
|
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
6274
7329
|
return {
|
|
6275
7330
|
routerVersion: routerMatch?.[1],
|
|
6276
7331
|
expectedRouterVersion: CURRENT_ROUTER_VERSION,
|
|
6277
7332
|
routerAligned: routerMatch?.[1] === CURRENT_ROUTER_VERSION,
|
|
6278
|
-
manifest:
|
|
6279
|
-
currentWork:
|
|
7333
|
+
manifest: existsSync27(join27(root, "docs/governance/MANIFEST.yml")),
|
|
7334
|
+
currentWork: existsSync27(join27(root, "docs/reference/execution/current-work.md")),
|
|
6280
7335
|
packages: {
|
|
6281
7336
|
expected,
|
|
6282
7337
|
docGov,
|
|
@@ -6338,17 +7393,17 @@ function inspectGit2(root) {
|
|
|
6338
7393
|
}
|
|
6339
7394
|
|
|
6340
7395
|
// src/portfolio/ai-health/hosts.ts
|
|
6341
|
-
import { existsSync as
|
|
6342
|
-
import { join as
|
|
7396
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
7397
|
+
import { join as join29, resolve as resolve9, sep as sep3 } from "node:path";
|
|
6343
7398
|
|
|
6344
7399
|
// src/portfolio/ai-health/devspace.ts
|
|
6345
|
-
import { existsSync as
|
|
6346
|
-
import { join as
|
|
7400
|
+
import { existsSync as existsSync28, statSync as statSync9 } from "node:fs";
|
|
7401
|
+
import { join as join28, relative as relative11, resolve as resolve8, sep as sep2 } from "node:path";
|
|
6347
7402
|
function inspectDevSpaceHealth(options) {
|
|
6348
7403
|
const run = options.run ?? runDevSpaceCommand;
|
|
6349
|
-
const configDirectory =
|
|
6350
|
-
const configPath =
|
|
6351
|
-
const authPath =
|
|
7404
|
+
const configDirectory = join28(options.homeDir, ".devspace");
|
|
7405
|
+
const configPath = join28(configDirectory, "config.json");
|
|
7406
|
+
const authPath = join28(configDirectory, "auth.json");
|
|
6352
7407
|
const installedResult = run("devspace", ["--version"], 3e3);
|
|
6353
7408
|
const installedVersion = installedResult.ok ? installedResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
|
|
6354
7409
|
const latestResult = run(
|
|
@@ -6371,9 +7426,9 @@ function inspectDevSpaceHealth(options) {
|
|
|
6371
7426
|
(repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
|
|
6372
7427
|
) ? "complete" : "partial";
|
|
6373
7428
|
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" &&
|
|
7429
|
+
const directoryMode = process.platform !== "win32" && existsSync28(configDirectory) ? modeString(statSync9(configDirectory).mode) : void 0;
|
|
7430
|
+
const fileMode = process.platform !== "win32" && existsSync28(configPath) ? modeString(statSync9(configPath).mode) : void 0;
|
|
7431
|
+
const authMode = process.platform !== "win32" && existsSync28(authPath) ? modeString(statSync9(authPath).mode) : void 0;
|
|
6377
7432
|
const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
|
|
6378
7433
|
const recommendations = [];
|
|
6379
7434
|
let status = "healthy";
|
|
@@ -6387,7 +7442,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6387
7442
|
};
|
|
6388
7443
|
if (!installedResult.ok) unhealthy("\u672C\u673A\u672A\u53D1\u73B0 DevSpace\uFF1B\u65E0\u6CD5\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u533A\u670D\u52A1\u3002");
|
|
6389
7444
|
if (!configExists) unhealthy("\u7F3A\u5C11 ~/.devspace/config.json\u3002");
|
|
6390
|
-
if (!
|
|
7445
|
+
if (!existsSync28(authPath)) unhealthy("\u7F3A\u5C11 ~/.devspace/auth.json\u3002");
|
|
6391
7446
|
if (directoryMode && directoryMode !== "700")
|
|
6392
7447
|
unhealthy(`~/.devspace \u76EE\u5F55\u6743\u9650\u4E3A ${directoryMode}\uFF0C\u5E94\u6536\u7D27\u4E3A 700\u3002`);
|
|
6393
7448
|
if (fileMode && fileMode !== "600") unhealthy(`DevSpace \u914D\u7F6E\u6587\u4EF6\u6743\u9650\u4E3A ${fileMode}\uFF0C\u5E94\u4E3A 600\u3002`);
|
|
@@ -6420,7 +7475,7 @@ function inspectDevSpaceHealth(options) {
|
|
|
6420
7475
|
exists: configExists,
|
|
6421
7476
|
...directoryMode ? { directoryMode } : {},
|
|
6422
7477
|
...fileMode ? { fileMode } : {},
|
|
6423
|
-
authExists:
|
|
7478
|
+
authExists: existsSync28(authPath),
|
|
6424
7479
|
...authMode ? { authMode } : {},
|
|
6425
7480
|
bind,
|
|
6426
7481
|
portValid: configExists && typeof configValue.port === "number" && Number.isInteger(configValue.port) && configValue.port > 0 && configValue.port <= 65535,
|
|
@@ -6443,7 +7498,7 @@ function isLoopbackHost(host) {
|
|
|
6443
7498
|
return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
|
|
6444
7499
|
}
|
|
6445
7500
|
function isPathInside(path, root) {
|
|
6446
|
-
const fromRoot =
|
|
7501
|
+
const fromRoot = relative11(resolve8(root), resolve8(path));
|
|
6447
7502
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep2}`);
|
|
6448
7503
|
}
|
|
6449
7504
|
|
|
@@ -6461,9 +7516,9 @@ var MCP_DISCOVERY_PATHS = {
|
|
|
6461
7516
|
}
|
|
6462
7517
|
};
|
|
6463
7518
|
function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceSettings) {
|
|
6464
|
-
const codexConfig =
|
|
6465
|
-
const claudeConfig =
|
|
6466
|
-
const grokConfig =
|
|
7519
|
+
const codexConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.codex);
|
|
7520
|
+
const claudeConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode);
|
|
7521
|
+
const grokConfig = join29(homeDir, MCP_DISCOVERY_PATHS.user.grok);
|
|
6467
7522
|
const hostEnvironment = {
|
|
6468
7523
|
mcp: {
|
|
6469
7524
|
codexUser: { path: codexConfig, names: tomlMcpNames(codexConfig) },
|
|
@@ -6471,11 +7526,11 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6471
7526
|
grokUser: { path: grokConfig, names: tomlMcpNames(grokConfig) }
|
|
6472
7527
|
},
|
|
6473
7528
|
skills: {
|
|
6474
|
-
codexUser: inspectSkillRoot(
|
|
6475
|
-
claudeCodeUser: inspectSkillRoot(
|
|
6476
|
-
grokUser: inspectSkillRoot(
|
|
6477
|
-
grokAgentsCompatibility: inspectSkillRoot(
|
|
6478
|
-
grokClaudeCompatibility: inspectSkillRoot(
|
|
7529
|
+
codexUser: inspectSkillRoot(join29(homeDir, ".agents/skills")),
|
|
7530
|
+
claudeCodeUser: inspectSkillRoot(join29(homeDir, ".claude/skills")),
|
|
7531
|
+
grokUser: inspectSkillRoot(join29(homeDir, ".grok/skills")),
|
|
7532
|
+
grokAgentsCompatibility: inspectSkillRoot(join29(homeDir, ".agents/skills")),
|
|
7533
|
+
grokClaudeCompatibility: inspectSkillRoot(join29(homeDir, ".claude/skills")),
|
|
6479
7534
|
ssot: inspectUserSkillsSsot(homeDir)
|
|
6480
7535
|
},
|
|
6481
7536
|
grok: {
|
|
@@ -6496,20 +7551,20 @@ function inspectHostEnvironment(homeDir, grokVersion, repositoryPaths, devspaceS
|
|
|
6496
7551
|
function inspectSkillRoot(path) {
|
|
6497
7552
|
const exists = pathLexists(path) && safeIsDirectory(path);
|
|
6498
7553
|
const names = exists ? safeReadDir(path).filter(
|
|
6499
|
-
(name) => !name.startsWith(".") &&
|
|
7554
|
+
(name) => !name.startsWith(".") && existsSync29(join29(path, name, "SKILL.md"))
|
|
6500
7555
|
) : [];
|
|
6501
7556
|
return { path, exists, names };
|
|
6502
7557
|
}
|
|
6503
7558
|
function claudeProjectLocalMcpNames(homeDir, root) {
|
|
6504
7559
|
if (!homeDir) return [];
|
|
6505
|
-
const value = readJson4(
|
|
7560
|
+
const value = readJson4(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode));
|
|
6506
7561
|
if (!isRecord3(value) || !isRecord3(value.projects)) return [];
|
|
6507
7562
|
const candidates = new Set(
|
|
6508
|
-
[
|
|
7563
|
+
[resolve9(root), safeRealpath(root)].filter((path) => Boolean(path))
|
|
6509
7564
|
);
|
|
6510
7565
|
const names = /* @__PURE__ */ new Set();
|
|
6511
7566
|
for (const [path, project] of Object.entries(value.projects)) {
|
|
6512
|
-
const projectPaths = [
|
|
7567
|
+
const projectPaths = [resolve9(path), safeRealpath(path)].filter(
|
|
6513
7568
|
(candidate) => Boolean(candidate)
|
|
6514
7569
|
);
|
|
6515
7570
|
if (!projectPaths.some((candidate) => candidates.has(candidate)) || !isRecord3(project) || !isRecord3(project.mcpServers))
|
|
@@ -6531,7 +7586,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6531
7586
|
const result = spawnPlatformSync("grok", ["inspect", "--json"], {
|
|
6532
7587
|
cwd: root,
|
|
6533
7588
|
encoding: "utf8",
|
|
6534
|
-
env: { ...process.env, HOME: homeDir, GROK_HOME:
|
|
7589
|
+
env: { ...process.env, HOME: homeDir, GROK_HOME: join29(homeDir, ".grok") },
|
|
6535
7590
|
maxBuffer: 10 * 1024 * 1024,
|
|
6536
7591
|
stdio: ["ignore", "pipe", "ignore"],
|
|
6537
7592
|
timeout: 8e3
|
|
@@ -6540,7 +7595,7 @@ function inspectGrokProject(root, homeDir, grokVersion) {
|
|
|
6540
7595
|
const value = JSON.parse(result.stdout);
|
|
6541
7596
|
if (!isRecord3(value)) return empty("failed");
|
|
6542
7597
|
const userClaudeNames = new Set(
|
|
6543
|
-
jsonObjectKeys(
|
|
7598
|
+
jsonObjectKeys(join29(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
|
|
6544
7599
|
);
|
|
6545
7600
|
const localClaudeNames = new Set(claudeProjectLocalMcpNames(homeDir, root));
|
|
6546
7601
|
const effectiveMcp = Array.isArray(value.mcpServers) ? value.mcpServers.flatMap((item) => {
|
|
@@ -6608,26 +7663,26 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
|
|
|
6608
7663
|
if (userClaudeNames.has(name)) return "user";
|
|
6609
7664
|
return "unknown";
|
|
6610
7665
|
}
|
|
6611
|
-
const resolvedSource = safeRealpath(sourcePath) ??
|
|
6612
|
-
const resolvedRoot = safeRealpath(root) ??
|
|
6613
|
-
if (resolvedSource ===
|
|
7666
|
+
const resolvedSource = safeRealpath(sourcePath) ?? resolve9(sourcePath);
|
|
7667
|
+
const resolvedRoot = safeRealpath(root) ?? resolve9(root);
|
|
7668
|
+
if (resolvedSource === join29(resolvedRoot, MCP_DISCOVERY_PATHS.project.claudeCodeShared))
|
|
6614
7669
|
return "project-shared";
|
|
6615
7670
|
if (resolvedSource.startsWith(resolvedRoot + sep3)) return "project";
|
|
6616
|
-
if (homeDir && resolvedSource ===
|
|
7671
|
+
if (homeDir && resolvedSource === join29(resolve9(homeDir), MCP_DISCOVERY_PATHS.user.claudeCode)) {
|
|
6617
7672
|
if (localClaudeNames.has(name)) return "project-local";
|
|
6618
7673
|
if (userClaudeNames.has(name)) return "user";
|
|
6619
7674
|
}
|
|
6620
|
-
if (homeDir && resolvedSource.startsWith(
|
|
7675
|
+
if (homeDir && resolvedSource.startsWith(resolve9(homeDir) + sep3)) return "user";
|
|
6621
7676
|
if (sourceType === "project") return "project";
|
|
6622
7677
|
return "unknown";
|
|
6623
7678
|
}
|
|
6624
7679
|
|
|
6625
7680
|
// src/portfolio/ai-health/secrets.ts
|
|
6626
|
-
import { existsSync as
|
|
6627
|
-
import { join as
|
|
7681
|
+
import { existsSync as existsSync30, lstatSync as lstatSync13, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
7682
|
+
import { join as join30, relative as relative12, sep as sep4 } from "node:path";
|
|
6628
7683
|
var supportsPosixModes = process.platform !== "win32";
|
|
6629
7684
|
function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
|
|
6630
|
-
const centralPath =
|
|
7685
|
+
const centralPath = join30(secretsRoot, id);
|
|
6631
7686
|
const centralRealPath = safeRealpath(centralPath);
|
|
6632
7687
|
const localOnlyReasons = new Map(
|
|
6633
7688
|
(environmentPolicy?.localOnly ?? []).map((entry) => [entry.path, entry.reason])
|
|
@@ -6639,16 +7694,16 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
|
|
|
6639
7694
|
tracked: isRepository ? gitTracks(root, path) : false,
|
|
6640
7695
|
template: isEnvironmentTemplate(path),
|
|
6641
7696
|
fixture: isEnvironmentFixture(path),
|
|
6642
|
-
symlink:
|
|
6643
|
-
centralized: pointsInside(
|
|
7697
|
+
symlink: lstatSync13(join30(root, path)).isSymbolicLink(),
|
|
7698
|
+
centralized: pointsInside(join30(root, path), centralRealPath),
|
|
6644
7699
|
localOnly: localOnlyReason !== void 0,
|
|
6645
7700
|
...localOnlyReason !== void 0 ? { localOnlyReason } : {}
|
|
6646
7701
|
};
|
|
6647
7702
|
});
|
|
6648
7703
|
return {
|
|
6649
|
-
centralDirectory:
|
|
6650
|
-
centralMode: supportsPosixModes &&
|
|
6651
|
-
centralFiles:
|
|
7704
|
+
centralDirectory: existsSync30(centralPath) ? "present" : "absent",
|
|
7705
|
+
centralMode: supportsPosixModes && existsSync30(centralPath) ? modeString(statSync10(centralPath).mode) : void 0,
|
|
7706
|
+
centralFiles: existsSync30(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
6652
7707
|
repositoryEnvFiles: envFiles
|
|
6653
7708
|
};
|
|
6654
7709
|
}
|
|
@@ -6671,12 +7726,12 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
|
6671
7726
|
if (depth > 5) return [];
|
|
6672
7727
|
const found = [];
|
|
6673
7728
|
try {
|
|
6674
|
-
for (const entry of
|
|
7729
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
6675
7730
|
if (entry.isDirectory()) {
|
|
6676
7731
|
if (!SKIP_ENV_DIRECTORIES.has(entry.name))
|
|
6677
|
-
found.push(...collectEnvironmentFiles(root,
|
|
7732
|
+
found.push(...collectEnvironmentFiles(root, join30(current, entry.name), depth + 1));
|
|
6678
7733
|
} else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
|
|
6679
|
-
found.push(
|
|
7734
|
+
found.push(relative12(root, join30(current, entry.name)).replaceAll("\\", "/"));
|
|
6680
7735
|
}
|
|
6681
7736
|
}
|
|
6682
7737
|
} catch {
|
|
@@ -6688,13 +7743,13 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
|
6688
7743
|
if (depth > 3) return [];
|
|
6689
7744
|
const found = [];
|
|
6690
7745
|
try {
|
|
6691
|
-
for (const entry of
|
|
6692
|
-
const path =
|
|
7746
|
+
for (const entry of readdirSync13(current, { withFileTypes: true })) {
|
|
7747
|
+
const path = join30(current, entry.name);
|
|
6693
7748
|
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
6694
7749
|
else
|
|
6695
7750
|
found.push({
|
|
6696
|
-
path:
|
|
6697
|
-
mode: supportsPosixModes ? modeString(
|
|
7751
|
+
path: relative12(root, path).replaceAll("\\", "/"),
|
|
7752
|
+
mode: supportsPosixModes ? modeString(lstatSync13(path).mode) : "unknown"
|
|
6698
7753
|
});
|
|
6699
7754
|
}
|
|
6700
7755
|
} catch {
|
|
@@ -6719,10 +7774,10 @@ function isEnvironmentFixture(path) {
|
|
|
6719
7774
|
return /(^|[\\/])(?:tests?|__tests__)[\\/]fixtures?[\\/]/i.test(path) || /(^|[\\/])__fixtures__[\\/]/i.test(path);
|
|
6720
7775
|
}
|
|
6721
7776
|
function pointsInside(path, expectedRoot) {
|
|
6722
|
-
if (!expectedRoot || !
|
|
7777
|
+
if (!expectedRoot || !lstatSync13(path).isSymbolicLink()) return false;
|
|
6723
7778
|
const target = safeRealpath(path);
|
|
6724
7779
|
if (!target) return false;
|
|
6725
|
-
const fromRoot =
|
|
7780
|
+
const fromRoot = relative12(expectedRoot, target);
|
|
6726
7781
|
return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep4}`);
|
|
6727
7782
|
}
|
|
6728
7783
|
function hasUnsafeCentralSecretPermissions(secrets) {
|
|
@@ -6730,24 +7785,24 @@ function hasUnsafeCentralSecretPermissions(secrets) {
|
|
|
6730
7785
|
return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
|
|
6731
7786
|
}
|
|
6732
7787
|
function inspectSecretsRoot(path) {
|
|
6733
|
-
return
|
|
7788
|
+
return existsSync30(path) ? {
|
|
6734
7789
|
path,
|
|
6735
7790
|
exists: true,
|
|
6736
|
-
...supportsPosixModes ? { mode: modeString(
|
|
7791
|
+
...supportsPosixModes ? { mode: modeString(statSync10(path).mode) } : {}
|
|
6737
7792
|
} : { path, exists: false };
|
|
6738
7793
|
}
|
|
6739
7794
|
|
|
6740
7795
|
// src/portfolio/ai-health/skills.ts
|
|
6741
|
-
import { existsSync as
|
|
6742
|
-
import { join as
|
|
7796
|
+
import { existsSync as existsSync31, lstatSync as lstatSync14, realpathSync as realpathSync8 } from "node:fs";
|
|
7797
|
+
import { join as join31 } from "node:path";
|
|
6743
7798
|
function countAutomaticSkillsNeedingReview(skills) {
|
|
6744
7799
|
return skills.automatic.filter(
|
|
6745
7800
|
(item) => !item.managed || !item.registryId || item.expectedPlacement !== "auto" || item.expectedScope === "user"
|
|
6746
7801
|
).length;
|
|
6747
7802
|
}
|
|
6748
7803
|
function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
6749
|
-
const lock = readJson4(
|
|
6750
|
-
const assetManifest = readJson4(
|
|
7804
|
+
const lock = readJson4(join31(root, ".pro-gov/assets.lock.json"));
|
|
7805
|
+
const assetManifest = readJson4(join31(root, ".pro-gov/assets.json"));
|
|
6751
7806
|
const managed = /* @__PURE__ */ new Set();
|
|
6752
7807
|
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
6753
7808
|
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
@@ -6759,7 +7814,7 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6759
7814
|
}
|
|
6760
7815
|
const inspectPlacement = (placement) => {
|
|
6761
7816
|
const directory = placement === "auto" ? "skills" : "manual-skills";
|
|
6762
|
-
const skillRoot =
|
|
7817
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6763
7818
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6764
7819
|
return safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => inspectSkillItem(skillRoot, directory, name, managed, registeredSkills));
|
|
6765
7820
|
};
|
|
@@ -6814,15 +7869,15 @@ function inspectSkills(root, grokInspection, registeredSkills, userSkills) {
|
|
|
6814
7869
|
},
|
|
6815
7870
|
hosts: {
|
|
6816
7871
|
codexProject: automatic.filter((item) => item.kind !== "dangling-symlink").length,
|
|
6817
|
-
claudeCodeProject: inspectSkillRoot(
|
|
6818
|
-
grokNativeProject: inspectSkillRoot(
|
|
7872
|
+
claudeCodeProject: inspectSkillRoot(join31(root, ".claude/skills")).names.length,
|
|
7873
|
+
grokNativeProject: inspectSkillRoot(join31(root, ".grok/skills")).names.length,
|
|
6819
7874
|
grokEffective: grokInspection.skills
|
|
6820
7875
|
}
|
|
6821
7876
|
};
|
|
6822
7877
|
}
|
|
6823
7878
|
function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills) {
|
|
6824
|
-
const path =
|
|
6825
|
-
const stat =
|
|
7879
|
+
const path = join31(skillRoot, name);
|
|
7880
|
+
const stat = lstatSync14(path);
|
|
6826
7881
|
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
6827
7882
|
let realPath;
|
|
6828
7883
|
try {
|
|
@@ -6831,7 +7886,7 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6831
7886
|
if (stat.isSymbolicLink()) kind = "dangling-symlink";
|
|
6832
7887
|
}
|
|
6833
7888
|
const registered = realPath ? registeredSkills.find((skill) => skill.sourceRealPath === realPath) : void 0;
|
|
6834
|
-
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" &&
|
|
7889
|
+
const classification = registered ? void 0 : realPath && isPluginPack(realPath) ? "plugin-pack" : kind === "directory" && existsSync31(join31(path, "SKILL.md")) ? "project-local" : void 0;
|
|
6835
7890
|
return {
|
|
6836
7891
|
name,
|
|
6837
7892
|
kind,
|
|
@@ -6843,11 +7898,11 @@ function inspectSkillItem(skillRoot, directory, name, managed, registeredSkills)
|
|
|
6843
7898
|
};
|
|
6844
7899
|
}
|
|
6845
7900
|
function isPluginPack(path) {
|
|
6846
|
-
const skillsRoot =
|
|
6847
|
-
return
|
|
7901
|
+
const skillsRoot = join31(path, "skills");
|
|
7902
|
+
return existsSync31(join31(path, ".codex-plugin/plugin.json")) && safeIsDirectory(skillsRoot) && safeReadDir(skillsRoot).some((name) => existsSync31(join31(skillsRoot, name, "SKILL.md")));
|
|
6848
7903
|
}
|
|
6849
7904
|
function inspectInvalidSkillEntries(root, directory) {
|
|
6850
|
-
const skillRoot =
|
|
7905
|
+
const skillRoot = join31(root, ".agents", directory);
|
|
6851
7906
|
if (!pathLexists(skillRoot) || !safeIsDirectory(skillRoot)) return [];
|
|
6852
7907
|
return safeReadDir(skillRoot).flatMap((name) => {
|
|
6853
7908
|
if (name === ".gitkeep") return [];
|
|
@@ -6855,14 +7910,14 @@ function inspectInvalidSkillEntries(root, directory) {
|
|
|
6855
7910
|
return [{ path: `.agents/${directory}/${name}`, reason: "metadata-junk" }];
|
|
6856
7911
|
if (name.startsWith("."))
|
|
6857
7912
|
return [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }];
|
|
6858
|
-
const path =
|
|
6859
|
-
return !
|
|
7913
|
+
const path = join31(skillRoot, name);
|
|
7914
|
+
return !lstatSync14(path).isDirectory() && !lstatSync14(path).isSymbolicLink() ? [{ path: `.agents/${directory}/${name}`, reason: "unexpected-file" }] : [];
|
|
6860
7915
|
});
|
|
6861
7916
|
}
|
|
6862
7917
|
function skillDuplicatesUser(item, root, userSkills) {
|
|
6863
7918
|
if (userSkills.names.has(item.name)) return true;
|
|
6864
|
-
const automatic =
|
|
6865
|
-
const manual =
|
|
7919
|
+
const automatic = join31(root, ".agents/skills", item.name);
|
|
7920
|
+
const manual = join31(root, ".agents/manual-skills", item.name);
|
|
6866
7921
|
const realPath = safeRealpath(pathLexists(automatic) ? automatic : manual);
|
|
6867
7922
|
return realPath ? userSkills.realPaths.has(realPath) : false;
|
|
6868
7923
|
}
|
|
@@ -6882,13 +7937,13 @@ function skillPlacementDrift(item, actualPlacement) {
|
|
|
6882
7937
|
return [];
|
|
6883
7938
|
}
|
|
6884
7939
|
function inspectClaudeSkillRoot(root) {
|
|
6885
|
-
const path =
|
|
7940
|
+
const path = join31(root, ".claude/skills");
|
|
6886
7941
|
if (!pathLexists(path)) return "missing";
|
|
6887
|
-
const stat =
|
|
7942
|
+
const stat = lstatSync14(path);
|
|
6888
7943
|
if (stat.isSymbolicLink()) {
|
|
6889
7944
|
try {
|
|
6890
7945
|
const target = realpathSync8(path);
|
|
6891
|
-
return target === realpathSync8(
|
|
7946
|
+
return target === realpathSync8(join31(root, ".agents/skills")) ? "shared-root" : "other";
|
|
6892
7947
|
} catch {
|
|
6893
7948
|
return "dangling-symlink";
|
|
6894
7949
|
}
|
|
@@ -6901,27 +7956,27 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6901
7956
|
health: { source: 0, registered: 0, bundled: 0, bundles: 0 },
|
|
6902
7957
|
skills: []
|
|
6903
7958
|
};
|
|
6904
|
-
const agentAssetsRoot =
|
|
6905
|
-
const registry = readJson4(
|
|
7959
|
+
const agentAssetsRoot = join31(executionEngineRoot, "agent-assets");
|
|
7960
|
+
const registry = readJson4(join31(agentAssetsRoot, "registry.json"));
|
|
6906
7961
|
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
6907
7962
|
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
6908
|
-
const bundleRoot =
|
|
7963
|
+
const bundleRoot = join31(agentAssetsRoot, "bundles");
|
|
6909
7964
|
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
6910
7965
|
const bundledIds = /* @__PURE__ */ new Set();
|
|
6911
7966
|
for (const file of bundleFiles) {
|
|
6912
|
-
const bundle = readJson4(
|
|
7967
|
+
const bundle = readJson4(join31(bundleRoot, file));
|
|
6913
7968
|
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
6914
7969
|
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
6915
7970
|
}
|
|
6916
7971
|
const sourceRoots = [
|
|
6917
|
-
|
|
6918
|
-
|
|
7972
|
+
join31(agentAssetsRoot, "skills/pie-skills"),
|
|
7973
|
+
join31(agentAssetsRoot, "skills/npx-skills/.agents/skills")
|
|
6919
7974
|
];
|
|
6920
7975
|
const source = sourceRoots.reduce(
|
|
6921
|
-
(count, root) => count + safeReadDir(root).filter((name) =>
|
|
7976
|
+
(count, root) => count + safeReadDir(root).filter((name) => existsSync31(join31(root, name, "SKILL.md"))).length,
|
|
6922
7977
|
0
|
|
6923
7978
|
) + registeredSkills.filter(
|
|
6924
|
-
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(
|
|
7979
|
+
(asset) => isRecord3(asset) && asset.sourceKind === "local-pack" && typeof asset.sourcePath === "string" && isPluginPack(join31(agentAssetsRoot, asset.sourcePath))
|
|
6925
7980
|
).length;
|
|
6926
7981
|
return {
|
|
6927
7982
|
health: {
|
|
@@ -6938,7 +7993,7 @@ function inspectSkillRegistry(executionEngineRoot) {
|
|
|
6938
7993
|
return [
|
|
6939
7994
|
{
|
|
6940
7995
|
id: asset.id,
|
|
6941
|
-
sourceRealPath: safeRealpath(
|
|
7996
|
+
sourceRealPath: safeRealpath(join31(agentAssetsRoot, asset.sourcePath)),
|
|
6942
7997
|
defaultPlacement: asset.defaultPlacement,
|
|
6943
7998
|
defaultScope: asset.defaultScope === "user" ? "user" : "project"
|
|
6944
7999
|
}
|
|
@@ -6953,15 +8008,15 @@ function inspectUserSkillEvidence(root) {
|
|
|
6953
8008
|
for (const name of safeReadDir(root)) {
|
|
6954
8009
|
if (name.startsWith(".")) continue;
|
|
6955
8010
|
names.add(name);
|
|
6956
|
-
const realPath = safeRealpath(
|
|
8011
|
+
const realPath = safeRealpath(join31(root, name));
|
|
6957
8012
|
if (realPath) realPaths.add(realPath);
|
|
6958
8013
|
}
|
|
6959
8014
|
return { names, realPaths };
|
|
6960
8015
|
}
|
|
6961
8016
|
|
|
6962
8017
|
// src/portfolio/ai-health/technology.ts
|
|
6963
|
-
import { existsSync as
|
|
6964
|
-
import { join as
|
|
8018
|
+
import { existsSync as existsSync32, readdirSync as readdirSync14, statSync as statSync11 } from "node:fs";
|
|
8019
|
+
import { join as join32 } from "node:path";
|
|
6965
8020
|
function buildTechnologyMatrix(governance, repositories) {
|
|
6966
8021
|
if (!governance || governance.technologies.length === 0) return [];
|
|
6967
8022
|
const policy = governance.versionPolicy;
|
|
@@ -6988,8 +8043,8 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
6988
8043
|
}).filter((item) => item !== void 0);
|
|
6989
8044
|
}).flat();
|
|
6990
8045
|
const fileSignal = (technology.files ?? []).some(
|
|
6991
|
-
(path) => hasUsableTechnologyFile(
|
|
6992
|
-
(manifest) => hasUsableTechnologyFile(
|
|
8046
|
+
(path) => hasUsableTechnologyFile(join32(repository.path, path)) || packageManifests.some(
|
|
8047
|
+
(manifest) => hasUsableTechnologyFile(join32(manifest.directory, path))
|
|
6993
8048
|
)
|
|
6994
8049
|
);
|
|
6995
8050
|
const modelSignal = [
|
|
@@ -7080,14 +8135,14 @@ function buildTechnologyMatrix(governance, repositories) {
|
|
|
7080
8135
|
}).filter((technology) => technology.projectCount > 0);
|
|
7081
8136
|
}
|
|
7082
8137
|
function hasUsableTechnologyFile(path) {
|
|
7083
|
-
if (!
|
|
8138
|
+
if (!existsSync32(path)) return false;
|
|
7084
8139
|
try {
|
|
7085
|
-
const info =
|
|
8140
|
+
const info = statSync11(path);
|
|
7086
8141
|
if (info.isFile()) return true;
|
|
7087
8142
|
if (!info.isDirectory()) return false;
|
|
7088
|
-
return
|
|
8143
|
+
return readdirSync14(path, { withFileTypes: true }).some((entry) => {
|
|
7089
8144
|
if (entry.name.startsWith(".")) return false;
|
|
7090
|
-
const child =
|
|
8145
|
+
const child = join32(path, entry.name);
|
|
7091
8146
|
if (entry.isDirectory()) return hasUsableTechnologyFile(child);
|
|
7092
8147
|
return entry.name.toLowerCase() !== "readme.md";
|
|
7093
8148
|
});
|
|
@@ -7099,7 +8154,7 @@ function inspectExclusiveOwnership(root, endpoint, governance) {
|
|
|
7099
8154
|
const projectType = endpoint.projectType;
|
|
7100
8155
|
return (governance?.exclusiveOwnership ?? []).flatMap((rule) => {
|
|
7101
8156
|
if (projectType && rule.allowedProjectTypes.includes(projectType)) return [];
|
|
7102
|
-
const paths = rule.paths.filter((path) =>
|
|
8157
|
+
const paths = rule.paths.filter((path) => existsSync32(join32(root, path)));
|
|
7103
8158
|
return paths.length > 0 ? [{ rule, paths }] : [];
|
|
7104
8159
|
});
|
|
7105
8160
|
}
|
|
@@ -7114,7 +8169,7 @@ function inspectProjectModel(root, endpoint, governance) {
|
|
|
7114
8169
|
const detection = (id) => {
|
|
7115
8170
|
const technology = technologyById.get(id);
|
|
7116
8171
|
const packageMatch = technology?.packages?.some((name) => packages.has(name)) ?? false;
|
|
7117
|
-
const fileMatch = technology?.files?.some((path) =>
|
|
8172
|
+
const fileMatch = technology?.files?.some((path) => existsSync32(join32(root, path))) ?? false;
|
|
7118
8173
|
return { id, label: technology?.label ?? id, detected: packageMatch || fileMatch };
|
|
7119
8174
|
};
|
|
7120
8175
|
const selected = new Set(endpoint.capabilities ?? []);
|
|
@@ -7153,8 +8208,8 @@ function collectPackageNames(root) {
|
|
|
7153
8208
|
}
|
|
7154
8209
|
|
|
7155
8210
|
// src/portfolio/ai-health/report.ts
|
|
7156
|
-
import { cpSync as cpSync3, existsSync as
|
|
7157
|
-
import { dirname as
|
|
8211
|
+
import { cpSync as cpSync3, existsSync as existsSync33, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8212
|
+
import { dirname as dirname17, join as join33 } from "node:path";
|
|
7158
8213
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7159
8214
|
function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
7160
8215
|
const repositoriesById = /* @__PURE__ */ new Map();
|
|
@@ -7195,36 +8250,36 @@ function mergePortfolioAiHealthReport(existing, latest, allRepositoryIds) {
|
|
|
7195
8250
|
};
|
|
7196
8251
|
}
|
|
7197
8252
|
function writePortfolioAiHealthReport(report, outDir) {
|
|
7198
|
-
|
|
8253
|
+
mkdirSync11(outDir, { recursive: true });
|
|
7199
8254
|
const dashboardAssets = findDashboardAssets();
|
|
7200
8255
|
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
7201
|
-
const source =
|
|
7202
|
-
if (!
|
|
7203
|
-
cpSync3(source,
|
|
8256
|
+
const source = join33(dashboardAssets, file);
|
|
8257
|
+
if (!existsSync33(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
8258
|
+
cpSync3(source, join33(outDir, file));
|
|
7204
8259
|
}
|
|
7205
|
-
const jsonPath =
|
|
7206
|
-
const htmlPath =
|
|
7207
|
-
|
|
8260
|
+
const jsonPath = join33(outDir, "portfolio-ai-health.json");
|
|
8261
|
+
const htmlPath = join33(outDir, "index.html");
|
|
8262
|
+
writeFileSync10(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
7208
8263
|
`);
|
|
7209
|
-
|
|
7210
|
-
|
|
8264
|
+
writeFileSync10(
|
|
8265
|
+
join33(outDir, "data.js"),
|
|
7211
8266
|
`window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson2(report)};
|
|
7212
8267
|
`
|
|
7213
8268
|
);
|
|
7214
8269
|
return { jsonPath, htmlPath };
|
|
7215
8270
|
}
|
|
7216
8271
|
function findDashboardAssets() {
|
|
7217
|
-
const packageRoot2 =
|
|
8272
|
+
const packageRoot2 = dirname17(dirname17(fileURLToPath5(import.meta.url)));
|
|
7218
8273
|
const candidates = [
|
|
7219
8274
|
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
8275
|
+
join33(packageRoot2, ".dashboard-build"),
|
|
8276
|
+
join33(packageRoot2, "assets/portfolio-dashboard"),
|
|
8277
|
+
join33(process.cwd(), ".dashboard-build"),
|
|
8278
|
+
join33(process.cwd(), "assets/portfolio-dashboard"),
|
|
8279
|
+
join33(process.cwd(), "packages/pro-gov/.dashboard-build"),
|
|
8280
|
+
join33(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
7226
8281
|
].filter((value) => Boolean(value));
|
|
7227
|
-
const match = candidates.find((path) =>
|
|
8282
|
+
const match = candidates.find((path) => existsSync33(join33(path, "index.html")));
|
|
7228
8283
|
if (!match)
|
|
7229
8284
|
throw new Error(
|
|
7230
8285
|
"Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build."
|
|
@@ -7242,8 +8297,8 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7242
8297
|
if (options.targetId && options.targetId !== "all" && endpoints.length === 0) {
|
|
7243
8298
|
throw new Error(`Unknown portfolio target: ${options.targetId}`);
|
|
7244
8299
|
}
|
|
7245
|
-
const secretsRoot = options.secretsRoot ??
|
|
7246
|
-
|
|
8300
|
+
const secretsRoot = options.secretsRoot ?? join34(
|
|
8301
|
+
dirname18(
|
|
7247
8302
|
options.manifest.controlPlane?.path ?? allEndpoints[0]?.endpoint.path ?? process.cwd()
|
|
7248
8303
|
),
|
|
7249
8304
|
".secrets"
|
|
@@ -7252,9 +8307,9 @@ function inspectPortfolioAiHealth(options) {
|
|
|
7252
8307
|
const grokVersion = commandVersion("grok");
|
|
7253
8308
|
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
7254
8309
|
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
7255
|
-
const userSkills = inspectUserSkillEvidence(
|
|
8310
|
+
const userSkills = inspectUserSkillEvidence(join34(homeDir, ".agents/skills"));
|
|
7256
8311
|
const expectedPackageVersion = packageVersion(
|
|
7257
|
-
|
|
8312
|
+
join34(executionEngineRoot ?? "", "packages/pro-gov/package.json")
|
|
7258
8313
|
);
|
|
7259
8314
|
const repositories = endpoints.map(
|
|
7260
8315
|
({ endpoint, role }) => inspectRepository(
|
|
@@ -7311,7 +8366,7 @@ function collectEndpoints(manifest) {
|
|
|
7311
8366
|
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
7312
8367
|
const seen = /* @__PURE__ */ new Set();
|
|
7313
8368
|
return result.filter(({ endpoint }) => {
|
|
7314
|
-
const key =
|
|
8369
|
+
const key = resolve10(endpoint.path);
|
|
7315
8370
|
if (seen.has(key)) return false;
|
|
7316
8371
|
seen.add(key);
|
|
7317
8372
|
return true;
|
|
@@ -7328,13 +8383,13 @@ function inspectRepository(endpoint, role, secretsRoot, homeDir, expectedPackage
|
|
|
7328
8383
|
const hooks = inspectHooks(root);
|
|
7329
8384
|
const docs = inspectDocs(root, role === "execution-engine" ? void 0 : expectedPackageVersion);
|
|
7330
8385
|
const mcp = {
|
|
7331
|
-
codexProject: tomlMcpNames(
|
|
8386
|
+
codexProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.codex)),
|
|
7332
8387
|
claudeCodeProjectShared: jsonObjectKeys(
|
|
7333
|
-
|
|
8388
|
+
join34(root, MCP_DISCOVERY_PATHS.project.claudeCodeShared),
|
|
7334
8389
|
"mcpServers"
|
|
7335
8390
|
),
|
|
7336
8391
|
claudeCodeProjectLocal: claudeProjectLocalMcpNames(homeDir, root),
|
|
7337
|
-
grokProject: tomlMcpNames(
|
|
8392
|
+
grokProject: tomlMcpNames(join34(root, MCP_DISCOVERY_PATHS.project.grok)),
|
|
7338
8393
|
grokEffective: grokInspection.effectiveMcp,
|
|
7339
8394
|
grokInspection: grokInspection.inspection
|
|
7340
8395
|
};
|
|
@@ -7968,11 +9023,11 @@ function isHost2(value) {
|
|
|
7968
9023
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
7969
9024
|
}
|
|
7970
9025
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
7971
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
7972
|
-
return agentAssetsDir &&
|
|
9026
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join35(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
9027
|
+
return agentAssetsDir && existsSync34(join35(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
7973
9028
|
}
|
|
7974
9029
|
function reportMissingPortfolioRegistry(loaded, json) {
|
|
7975
|
-
const expectedPath = loaded.manifest?.executionEngine?.path ?
|
|
9030
|
+
const expectedPath = loaded.manifest?.executionEngine?.path ? join35(loaded.manifest.executionEngine.path, "agent-assets/registry.json") : "executionEngine.path/agent-assets/registry.json";
|
|
7976
9031
|
const displayExpectedPath = expectedPath.replaceAll("\\", "/");
|
|
7977
9032
|
const issue = {
|
|
7978
9033
|
type: "missing-control-plane-registry",
|
|
@@ -8011,10 +9066,10 @@ function printUsage5() {
|
|
|
8011
9066
|
}
|
|
8012
9067
|
function readExistingAiHealthReport(outDir, portfolioId) {
|
|
8013
9068
|
if (!outDir) return void 0;
|
|
8014
|
-
const path =
|
|
8015
|
-
if (!
|
|
9069
|
+
const path = join35(outDir, "portfolio-ai-health.json");
|
|
9070
|
+
if (!existsSync34(path)) return void 0;
|
|
8016
9071
|
try {
|
|
8017
|
-
const value = JSON.parse(
|
|
9072
|
+
const value = JSON.parse(readFileSync19(path, "utf8"));
|
|
8018
9073
|
if (!value || typeof value !== "object" || value.portfolioId !== portfolioId || !Array.isArray(value.repositories))
|
|
8019
9074
|
return void 0;
|
|
8020
9075
|
return value;
|
|
@@ -8024,8 +9079,8 @@ function readExistingAiHealthReport(outDir, portfolioId) {
|
|
|
8024
9079
|
}
|
|
8025
9080
|
|
|
8026
9081
|
// src/commands/sync.ts
|
|
8027
|
-
import { existsSync as
|
|
8028
|
-
import { join as
|
|
9082
|
+
import { existsSync as existsSync35, lstatSync as lstatSync15, readFileSync as readFileSync20, readlinkSync as readlinkSync4 } from "node:fs";
|
|
9083
|
+
import { join as join36 } from "node:path";
|
|
8029
9084
|
function runSync(args) {
|
|
8030
9085
|
const check = args.includes("--check");
|
|
8031
9086
|
if (!check) {
|
|
@@ -8053,7 +9108,7 @@ function runSync(args) {
|
|
|
8053
9108
|
console.log("pro-gov sync check");
|
|
8054
9109
|
console.log(`profile: ${profile}`);
|
|
8055
9110
|
for (const file of planStarterFiles(profile)) {
|
|
8056
|
-
const targetPath =
|
|
9111
|
+
const targetPath = join36(process.cwd(), file.targetPath);
|
|
8057
9112
|
const stat = safeLstat3(targetPath);
|
|
8058
9113
|
if (!stat) {
|
|
8059
9114
|
if (file.ownership === "optional-guardrail") continue;
|
|
@@ -8077,8 +9132,8 @@ function runSync(args) {
|
|
|
8077
9132
|
}
|
|
8078
9133
|
continue;
|
|
8079
9134
|
}
|
|
8080
|
-
const source =
|
|
8081
|
-
const target =
|
|
9135
|
+
const source = readFileSync20(file.absoluteSourcePath, "utf8");
|
|
9136
|
+
const target = readFileSync20(targetPath, "utf8");
|
|
8082
9137
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
8083
9138
|
console.log(`different: ${file.targetPath}`);
|
|
8084
9139
|
differences += 1;
|
|
@@ -8112,13 +9167,13 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
8112
9167
|
}
|
|
8113
9168
|
function inferInstalledProfile(root) {
|
|
8114
9169
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
8115
|
-
(profile) =>
|
|
9170
|
+
(profile) => existsSync35(join36(root, `docs/governance/agents-routing/${profile}-v1.1.md`))
|
|
8116
9171
|
);
|
|
8117
9172
|
return installed.length === 1 ? installed[0] : void 0;
|
|
8118
9173
|
}
|
|
8119
9174
|
function safeLstat3(path) {
|
|
8120
9175
|
try {
|
|
8121
|
-
return
|
|
9176
|
+
return lstatSync15(path);
|
|
8122
9177
|
} catch {
|
|
8123
9178
|
return void 0;
|
|
8124
9179
|
}
|
|
@@ -8139,6 +9194,7 @@ var COMMANDS = [
|
|
|
8139
9194
|
"assets check [--target <path>] [--strict-registry] [--json]",
|
|
8140
9195
|
"assets public-check [--public-root <path>] [--private-root <path>] [--json]",
|
|
8141
9196
|
"assets npx add|update ... --plan",
|
|
9197
|
+
"assets catalog build|check [--native-links] [--json]",
|
|
8142
9198
|
"portfolio check --config <path> [--json]",
|
|
8143
9199
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
8144
9200
|
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|