@pieai/pro-gov 0.4.5 → 0.4.7
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 +2 -0
- package/assets/docs/reference/adoption/recommended-agent-tooling.md +38 -0
- package/assets/portfolio-dashboard/app.css +1 -0
- package/assets/portfolio-dashboard/app.js +66 -0
- package/assets/portfolio-dashboard/index.html +16 -0
- package/assets/starter/AGENTS.template.md +23 -39
- package/assets/starter/docs/governance/agents-routing/doc-only-v0.9.md +1 -2
- package/cli-guide.md +8 -0
- package/dist/cli.js +538 -18
- package/package.json +9 -4
package/dist/cli.js
CHANGED
|
@@ -1801,13 +1801,7 @@ function applyStarterFiles(files, profile) {
|
|
|
1801
1801
|
}
|
|
1802
1802
|
function renderAgentsTemplate(template, projectName, profile) {
|
|
1803
1803
|
const selectedRoute = `docs/governance/agents-routing/${profile}-v0.9.md`;
|
|
1804
|
-
return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace(
|
|
1805
|
-
/6\. The selected agents routing file:\n - `docs\/governance\/agents-routing\/engineering-runtime-v0\.9\.md`, or\n - `docs\/governance\/agents-routing\/doc-only-v0\.9\.md`/,
|
|
1806
|
-
`6. The selected agents routing file: \`${selectedRoute}\``
|
|
1807
|
-
).replace(
|
|
1808
|
-
"- Name this project's adopted profile: `engineering-runtime` or `doc-only`.",
|
|
1809
|
-
`- This project adopts the \`${profile}\` profile.`
|
|
1810
|
-
);
|
|
1804
|
+
return template.replace("# PROJECT_NAME AI Router", `# ${projectName} AI Router`).replace("`PROFILE_NAME`", `\`${profile}\``).replace("`PROFILE_ROUTE`", `\`${selectedRoute}\``).replace(/\n{3,}/g, "\n\n");
|
|
1811
1805
|
}
|
|
1812
1806
|
function readFlag(args, flag) {
|
|
1813
1807
|
const index = args.indexOf(flag);
|
|
@@ -2791,8 +2785,8 @@ function printUsage3() {
|
|
|
2791
2785
|
}
|
|
2792
2786
|
|
|
2793
2787
|
// src/commands/portfolio.ts
|
|
2794
|
-
import { existsSync as
|
|
2795
|
-
import { join as
|
|
2788
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
2789
|
+
import { join as join21 } from "node:path";
|
|
2796
2790
|
|
|
2797
2791
|
// src/portfolio/manifest.ts
|
|
2798
2792
|
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "node:fs";
|
|
@@ -3383,6 +3377,488 @@ function deduplicateIssues(issues) {
|
|
|
3383
3377
|
});
|
|
3384
3378
|
}
|
|
3385
3379
|
|
|
3380
|
+
// src/portfolio/ai-health.ts
|
|
3381
|
+
import { execFileSync } from "node:child_process";
|
|
3382
|
+
import {
|
|
3383
|
+
cpSync as cpSync2,
|
|
3384
|
+
existsSync as existsSync21,
|
|
3385
|
+
lstatSync as lstatSync6,
|
|
3386
|
+
mkdirSync as mkdirSync8,
|
|
3387
|
+
readFileSync as readFileSync15,
|
|
3388
|
+
readdirSync as readdirSync8,
|
|
3389
|
+
realpathSync as realpathSync2,
|
|
3390
|
+
statSync as statSync4,
|
|
3391
|
+
writeFileSync as writeFileSync7
|
|
3392
|
+
} from "node:fs";
|
|
3393
|
+
import { dirname as dirname12, join as join20, relative as relative8, resolve as resolve4 } from "node:path";
|
|
3394
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3395
|
+
function inspectPortfolioAiHealth(options) {
|
|
3396
|
+
const endpoints = collectEndpoints(options.manifest);
|
|
3397
|
+
const secretsRoot = options.secretsRoot ?? join20(dirname12(options.manifest.controlPlane?.path ?? endpoints[0]?.endpoint.path ?? process.cwd()), ".secrets");
|
|
3398
|
+
const homeDir = options.homeDir ?? process.env.HOME ?? "";
|
|
3399
|
+
const executionEngineRoot = options.manifest.executionEngine?.path;
|
|
3400
|
+
const skillRegistry = inspectSkillRegistry(executionEngineRoot);
|
|
3401
|
+
const expectedPackageVersion = packageVersion(join20(executionEngineRoot ?? "", "packages/pro-gov/package.json"));
|
|
3402
|
+
const repositories = endpoints.map(({ endpoint, role }) => inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion));
|
|
3403
|
+
const summary = { healthy: 0, attention: 0, unhealthy: 0 };
|
|
3404
|
+
for (const repository of repositories) summary[repository.status] += 1;
|
|
3405
|
+
return {
|
|
3406
|
+
schemaVersion: 1,
|
|
3407
|
+
portfolioId: options.manifest.portfolioId,
|
|
3408
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3409
|
+
privacy: "Names, paths, counts, and configuration structure only. Secret values, environment values, MCP commands, arguments, and MCP environment maps are never collected.",
|
|
3410
|
+
secretsRoot: inspectSecretsRoot(secretsRoot),
|
|
3411
|
+
userMcp: inspectUserMcp(homeDir),
|
|
3412
|
+
skillRegistry,
|
|
3413
|
+
summary,
|
|
3414
|
+
repositories
|
|
3415
|
+
};
|
|
3416
|
+
}
|
|
3417
|
+
function writePortfolioAiHealthReport(report, outDir) {
|
|
3418
|
+
mkdirSync8(outDir, { recursive: true });
|
|
3419
|
+
const dashboardAssets = findDashboardAssets();
|
|
3420
|
+
for (const file of ["index.html", "app.js", "app.css"]) {
|
|
3421
|
+
const source = join20(dashboardAssets, file);
|
|
3422
|
+
if (!existsSync21(source)) throw new Error(`Portfolio dashboard asset is missing: ${source}`);
|
|
3423
|
+
cpSync2(source, join20(outDir, file));
|
|
3424
|
+
}
|
|
3425
|
+
const jsonPath = join20(outDir, "portfolio-ai-health.json");
|
|
3426
|
+
const htmlPath = join20(outDir, "index.html");
|
|
3427
|
+
writeFileSync7(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
3428
|
+
`);
|
|
3429
|
+
writeFileSync7(join20(outDir, "data.js"), `window.__PORTFOLIO_AI_HEALTH__ = ${safeJavaScriptJson(report)};
|
|
3430
|
+
`);
|
|
3431
|
+
return { jsonPath, htmlPath };
|
|
3432
|
+
}
|
|
3433
|
+
function collectEndpoints(manifest) {
|
|
3434
|
+
const result = [];
|
|
3435
|
+
if (manifest.controlPlane) result.push({ endpoint: manifest.controlPlane, role: "control-plane" });
|
|
3436
|
+
if (manifest.executionEngine) result.push({ endpoint: manifest.executionEngine, role: "execution-engine" });
|
|
3437
|
+
for (const target of manifest.targets) result.push({ endpoint: target, role: "target" });
|
|
3438
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3439
|
+
return result.filter(({ endpoint }) => {
|
|
3440
|
+
const key = resolve4(endpoint.path);
|
|
3441
|
+
if (seen.has(key)) return false;
|
|
3442
|
+
seen.add(key);
|
|
3443
|
+
return true;
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
function inspectRepository(endpoint, role, secretsRoot, expectedPackageVersion) {
|
|
3447
|
+
const root = endpoint.path;
|
|
3448
|
+
const git = inspectGit2(root);
|
|
3449
|
+
const entries = inspectEntries(root);
|
|
3450
|
+
const skills = inspectSkills(root);
|
|
3451
|
+
const hooks = inspectHooks(root);
|
|
3452
|
+
const docs = inspectDocs(root, expectedPackageVersion);
|
|
3453
|
+
const mcp = {
|
|
3454
|
+
root: jsonObjectKeys(join20(root, ".mcp.json"), "mcpServers"),
|
|
3455
|
+
claudeCode: jsonObjectKeys(join20(root, ".claude/settings.json"), "mcpServers"),
|
|
3456
|
+
codex: tomlMcpNames(join20(root, ".codex/config.toml"))
|
|
3457
|
+
};
|
|
3458
|
+
const secrets = inspectRepositorySecrets(root, endpoint.id, secretsRoot, git.isRepository);
|
|
3459
|
+
const recommendations = [];
|
|
3460
|
+
if (!git.isRepository) recommendations.push("\u8BE5\u8DEF\u5F84\u4E0D\u662F Git \u4ED3\u5E93\uFF1B\u786E\u8BA4\u6E05\u5355\u8DEF\u5F84\u662F\u5426\u6B63\u786E\u3002");
|
|
3461
|
+
if (git.unmergedBranches.length > 0) recommendations.push(`\u6709 ${git.unmergedBranches.length} \u6761\u5206\u652F\u5C1A\u672A\u5408\u5165\u5F53\u524D HEAD\uFF1A${git.unmergedBranches.join(", ")}\u3002`);
|
|
3462
|
+
else if (git.branches.length > 1) recommendations.push(`\u6709 ${git.branches.length} \u6761\u672C\u5730\u5206\u652F\uFF0C\u5747\u5DF2\u5408\u5165\u5F53\u524D HEAD\uFF1B\u786E\u8BA4\u65E0\u5176\u4ED6\u5BBF\u4E3B\u5360\u7528\u540E\u53EF\u6E05\u7406\u989D\u5916\u5206\u652F\u3002`);
|
|
3463
|
+
if (git.worktrees.length > 1) recommendations.push(`\u6709 ${git.worktrees.length} \u4E2A worktree\uFF1B\u5B8C\u6210\u5E76\u5408\u5E76\u540E\u518D\u62C6\u9664\u4E34\u65F6 worktree\u3002`);
|
|
3464
|
+
if (git.dirtyPaths.length > 0) recommendations.push(`\u5DE5\u4F5C\u533A\u6709 ${git.dirtyPaths.length} \u4E2A\u53D8\u66F4\u8DEF\u5F84\uFF1B\u5148\u786E\u8BA4\u5F52\u5C5E\uFF0C\u4E0D\u8981\u7531\u5065\u5EB7\u626B\u63CF\u5668\u81EA\u52A8\u6E05\u7406\u3002`);
|
|
3465
|
+
if ((git.ahead ?? 0) > 0) recommendations.push(`\u5F53\u524D\u5206\u652F\u9886\u5148\u4E0A\u6E38 ${git.ahead} \u4E2A\u63D0\u4EA4\uFF1B\u786E\u8BA4\u5DE5\u4F5C\u5B8C\u6210\u540E\u518D\u63A8\u9001\u3002`);
|
|
3466
|
+
if (entries.agents === "missing") recommendations.push("\u7F3A\u5C11 AGENTS.md\uFF1B\u65E0\u6CD5\u53D1\u73B0\u9879\u76EE\u5165\u53E3\u89C4\u5219\u3002");
|
|
3467
|
+
if (entries.agents === "custom") recommendations.push("AGENTS.md \u672A\u8BC6\u522B\u5230 PGS Router \u6807\u8BB0\uFF1B\u68C0\u67E5\u662F\u5426\u5C1A\u672A\u540C\u6B65\u6216\u5DF2\u88AB\u9879\u76EE\u5185\u5BB9\u8986\u76D6\u3002");
|
|
3468
|
+
if (entries.claude === "missing") recommendations.push("\u7F3A\u5C11 CLAUDE.md\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\uFF0C\u907F\u514D\u4E24\u4EFD\u5165\u53E3\u6F02\u79FB\u3002");
|
|
3469
|
+
if (entries.claude === "thin-adapter") recommendations.push("CLAUDE.md \u662F\u517C\u5BB9\u9002\u914D\u5668\uFF1B\u53EF\u6539\u4E3A\u76F4\u63A5\u94FE\u63A5 AGENTS.md \u4EE5\u8FDB\u4E00\u6B65\u51CF\u5C11\u7EF4\u62A4\u9762\u3002");
|
|
3470
|
+
if (entries.claude === "custom") recommendations.push("CLAUDE.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u5BB9\u6613\u4E0E AGENTS.md \u6F02\u79FB\uFF0C\u5EFA\u8BAE\u53EA\u4FDD\u7559\u9879\u76EE\u786E\u9700\u7684\u5BBF\u4E3B\u5DEE\u5F02\u3002");
|
|
3471
|
+
if (entries.claude === "dangling-symlink") recommendations.push("CLAUDE.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
3472
|
+
if (entries.gemini === "custom") recommendations.push("GEMINI.md \u662F\u72EC\u7ACB\u5165\u53E3\uFF1B\u82E5\u6CA1\u6709 Gemini \u4E13\u5C5E\u5DEE\u5F02\uFF0C\u5EFA\u8BAE\u94FE\u63A5\u5230 AGENTS.md\u3002");
|
|
3473
|
+
if (entries.gemini === "dangling-symlink") recommendations.push("GEMINI.md \u662F\u65AD\u5F00\u7684\u94FE\u63A5\uFF1B\u9700\u8981\u91CD\u65B0\u6307\u5411 AGENTS.md\u3002");
|
|
3474
|
+
if (skills.canonical.some((skill) => skill.kind === "dangling-symlink")) recommendations.push("`.agents/skills` \u4E2D\u5B58\u5728\u65AD\u5F00\u7684\u6280\u80FD\u94FE\u63A5\u3002");
|
|
3475
|
+
if (skills.claudeCompatibility === "duplicate-directory") recommendations.push("`.claude/skills` \u662F\u72EC\u7ACB\u526F\u672C\uFF1B\u5EFA\u8BAE\u94FE\u63A5\u5230 `.agents/skills`\uFF0C\u907F\u514D\u53CC\u4EFD\u6280\u80FD\u6F02\u79FB\u3002");
|
|
3476
|
+
if (skills.claudeCompatibility === "dangling-symlink") recommendations.push("`.claude/skills` \u662F\u65AD\u5F00\u7684\u94FE\u63A5\u3002");
|
|
3477
|
+
const liveEnv = secrets.repositoryEnvFiles.filter((file) => !file.template);
|
|
3478
|
+
if (liveEnv.some((file) => file.tracked)) recommendations.push("\u53D1\u73B0\u88AB Git \u8DDF\u8E2A\u7684\u771F\u5B9E\u73AF\u5883\u6587\u4EF6\uFF1B\u5E94\u7ACB\u5373\u786E\u8BA4\u5176\u4E2D\u662F\u5426\u5305\u542B\u51ED\u636E\u5E76\u8FC1\u51FA\u4ED3\u5E93\u3002");
|
|
3479
|
+
else if (liveEnv.length > 0 && secrets.centralDirectory === "absent") recommendations.push("\u4ED3\u5E93\u6709\u672C\u5730\u73AF\u5883\u6587\u4EF6\uFF0C\u4F46\u4E2D\u592E `.secrets` \u4E2D\u6CA1\u6709\u5BF9\u5E94\u76EE\u5F55\uFF1B\u786E\u8BA4\u662F\u5426\u9700\u8981\u7EB3\u5165\u5206\u5C42\u7BA1\u7406\u3002");
|
|
3480
|
+
if (!docs.packages.aligned) recommendations.push(`PGS \u5305\u7248\u672C\u672A\u4E0E\u6267\u884C\u5F15\u64CE ${docs.packages.expected ?? "\u672A\u77E5\u7248\u672C"} \u5BF9\u9F50\uFF1B\u53D1\u5E03\u4E0A\u6E38\u540E\u518D\u540C\u6B65\u76EE\u6807\u4ED3\u5E93\u3002`);
|
|
3481
|
+
if (role === "target" && !docs.manifest) recommendations.push("\u7F3A\u5C11 docs/governance/MANIFEST.yml\uFF1B\u6587\u6863\u6E05\u5355\u65E0\u6CD5\u8BC1\u660E\u5DF2\u540C\u6B65\u3002");
|
|
3482
|
+
return {
|
|
3483
|
+
id: endpoint.id,
|
|
3484
|
+
role,
|
|
3485
|
+
path: root,
|
|
3486
|
+
profile: "profile" in endpoint ? endpoint.profile : void 0,
|
|
3487
|
+
status: deriveStatus(entries, git, skills, secrets),
|
|
3488
|
+
recommendations,
|
|
3489
|
+
git,
|
|
3490
|
+
entries,
|
|
3491
|
+
hooks,
|
|
3492
|
+
mcp,
|
|
3493
|
+
skills,
|
|
3494
|
+
secrets,
|
|
3495
|
+
docs
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
function deriveStatus(entries, git, skills, secrets) {
|
|
3499
|
+
if (!git.isRepository || entries.agents === "missing" || entries.claude === "dangling-symlink" || entries.gemini === "dangling-symlink" || skills.canonical.some((item) => item.kind === "dangling-symlink") || secrets.repositoryEnvFiles.some((file) => file.tracked && !file.template)) return "unhealthy";
|
|
3500
|
+
if (entries.agents !== "pgs-router" || entries.claude !== "agents-symlink" || skills.claudeCompatibility === "duplicate-directory" || skills.claudeCompatibility === "dangling-symlink" || git.branches.length > 1 || git.worktrees.length > 1 || git.dirtyPaths.length > 0 || (git.ahead ?? 0) > 0) return "attention";
|
|
3501
|
+
return "healthy";
|
|
3502
|
+
}
|
|
3503
|
+
function inspectGit2(root) {
|
|
3504
|
+
const git = (...args) => {
|
|
3505
|
+
try {
|
|
3506
|
+
return execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trimEnd();
|
|
3507
|
+
} catch {
|
|
3508
|
+
return void 0;
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
if (git("rev-parse", "--is-inside-work-tree") !== "true") return { isRepository: false, branches: [], mergedBranches: [], unmergedBranches: [], worktrees: [], dirtyPaths: [] };
|
|
3512
|
+
const status = git("status", "--porcelain=v1", "-uall") ?? "";
|
|
3513
|
+
const worktreeText = git("worktree", "list", "--porcelain") ?? "";
|
|
3514
|
+
const upstreamCounts = git("rev-list", "--left-right", "--count", "@{upstream}...HEAD")?.split(/\s+/).map(Number);
|
|
3515
|
+
return {
|
|
3516
|
+
isRepository: true,
|
|
3517
|
+
branch: git("branch", "--show-current") || "(detached)",
|
|
3518
|
+
branches: splitLines(git("for-each-ref", "--format=%(refname:short)", "refs/heads")),
|
|
3519
|
+
mergedBranches: splitLines(git("for-each-ref", "--merged=HEAD", "--format=%(refname:short)", "refs/heads")),
|
|
3520
|
+
unmergedBranches: splitLines(git("for-each-ref", "--no-merged=HEAD", "--format=%(refname:short)", "refs/heads")),
|
|
3521
|
+
worktrees: splitLines(worktreeText).filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9)),
|
|
3522
|
+
dirtyPaths: splitLines(status).map((line) => line.slice(3)),
|
|
3523
|
+
behind: upstreamCounts?.[0],
|
|
3524
|
+
ahead: upstreamCounts?.[1]
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
function inspectEntries(root) {
|
|
3528
|
+
const agentsPath = join20(root, "AGENTS.md");
|
|
3529
|
+
const agents = !existsSync21(agentsPath) ? "missing" : safeRead(agentsPath).includes("PGS-ROUTER:BEGIN") ? "pgs-router" : "custom";
|
|
3530
|
+
const claudePath = join20(root, "CLAUDE.md");
|
|
3531
|
+
let claude = "missing";
|
|
3532
|
+
if (pathLexists(claudePath)) {
|
|
3533
|
+
const info = lstatSync6(claudePath);
|
|
3534
|
+
if (info.isSymbolicLink()) {
|
|
3535
|
+
try {
|
|
3536
|
+
claude = realpathSync2(claudePath) === realpathSync2(agentsPath) ? "agents-symlink" : "custom";
|
|
3537
|
+
} catch {
|
|
3538
|
+
claude = "dangling-symlink";
|
|
3539
|
+
}
|
|
3540
|
+
} else {
|
|
3541
|
+
const content = safeRead(claudePath);
|
|
3542
|
+
claude = /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
return { agents, claude, gemini: inspectOptionalEntry(root, "GEMINI.md", agentsPath) };
|
|
3546
|
+
}
|
|
3547
|
+
function inspectOptionalEntry(root, filename, agentsPath) {
|
|
3548
|
+
const path = join20(root, filename);
|
|
3549
|
+
if (!pathLexists(path)) return "missing";
|
|
3550
|
+
const info = lstatSync6(path);
|
|
3551
|
+
if (info.isSymbolicLink()) {
|
|
3552
|
+
try {
|
|
3553
|
+
return realpathSync2(path) === realpathSync2(agentsPath) ? "agents-symlink" : "custom";
|
|
3554
|
+
} catch {
|
|
3555
|
+
return "dangling-symlink";
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
const content = safeRead(path);
|
|
3559
|
+
return /AGENTS\.md/.test(content) && content.length < 2e3 ? "thin-adapter" : "custom";
|
|
3560
|
+
}
|
|
3561
|
+
function inspectSkills(root) {
|
|
3562
|
+
const lock = readJson3(join20(root, ".pro-gov/assets.lock.json"));
|
|
3563
|
+
const managed = /* @__PURE__ */ new Set();
|
|
3564
|
+
const bundleIds = stringArray(isRecord3(lock) ? lock.bundleIds : void 0);
|
|
3565
|
+
if (isRecord3(lock) && Array.isArray(lock.assets)) {
|
|
3566
|
+
for (const asset of lock.assets) {
|
|
3567
|
+
if (!isRecord3(asset) || typeof asset.targetPath !== "string") continue;
|
|
3568
|
+
const match = asset.targetPath.match(/^\.agents\/skills\/([^/]+)$/);
|
|
3569
|
+
if (match) managed.add(match[1]);
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
const skillRoot = join20(root, ".agents/skills");
|
|
3573
|
+
const canonical = pathLexists(skillRoot) && safeIsDirectory(skillRoot) ? safeReadDir(skillRoot).filter((name) => !name.startsWith(".")).map((name) => {
|
|
3574
|
+
const path = join20(skillRoot, name);
|
|
3575
|
+
const stat = lstatSync6(path);
|
|
3576
|
+
let kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "directory" : "file";
|
|
3577
|
+
if (stat.isSymbolicLink()) {
|
|
3578
|
+
try {
|
|
3579
|
+
realpathSync2(path);
|
|
3580
|
+
} catch {
|
|
3581
|
+
kind = "dangling-symlink";
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
return { name, kind, managed: managed.has(name) };
|
|
3585
|
+
}) : [];
|
|
3586
|
+
const locked = isRecord3(lock) && Array.isArray(lock.assets) ? lock.assets.length : 0;
|
|
3587
|
+
return {
|
|
3588
|
+
canonical,
|
|
3589
|
+
claudeCompatibility: inspectClaudeSkillRoot(root),
|
|
3590
|
+
bundleIds,
|
|
3591
|
+
lifecycle: {
|
|
3592
|
+
desired: bundleIds.length,
|
|
3593
|
+
locked,
|
|
3594
|
+
installed: canonical.filter((item) => item.managed && item.kind !== "dangling-symlink").length,
|
|
3595
|
+
discoverable: canonical.filter((item) => item.kind !== "dangling-symlink").length,
|
|
3596
|
+
runtime: "unobservable"
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
function inspectClaudeSkillRoot(root) {
|
|
3601
|
+
const path = join20(root, ".claude/skills");
|
|
3602
|
+
if (!pathLexists(path)) return "missing";
|
|
3603
|
+
const stat = lstatSync6(path);
|
|
3604
|
+
if (stat.isSymbolicLink()) {
|
|
3605
|
+
try {
|
|
3606
|
+
const target = realpathSync2(path);
|
|
3607
|
+
return target === realpathSync2(join20(root, ".agents/skills")) ? "shared-root" : "other";
|
|
3608
|
+
} catch {
|
|
3609
|
+
return "dangling-symlink";
|
|
3610
|
+
}
|
|
3611
|
+
}
|
|
3612
|
+
return stat.isDirectory() ? "duplicate-directory" : "other";
|
|
3613
|
+
}
|
|
3614
|
+
function inspectRepositorySecrets(root, id, secretsRoot, isRepository) {
|
|
3615
|
+
const centralPath = join20(secretsRoot, id);
|
|
3616
|
+
const envFiles = collectEnvironmentFiles(root).map((path) => ({
|
|
3617
|
+
path,
|
|
3618
|
+
tracked: isRepository ? gitTracks(root, path) : false,
|
|
3619
|
+
template: /(?:example|sample|template|defaults?)$/i.test(basenameOnly(path)),
|
|
3620
|
+
symlink: lstatSync6(join20(root, path)).isSymbolicLink()
|
|
3621
|
+
}));
|
|
3622
|
+
return {
|
|
3623
|
+
centralDirectory: existsSync21(centralPath) ? "present" : "absent",
|
|
3624
|
+
centralMode: existsSync21(centralPath) ? modeString(statSync4(centralPath).mode) : void 0,
|
|
3625
|
+
centralFiles: existsSync21(centralPath) ? collectCentralSecretFiles(centralPath) : [],
|
|
3626
|
+
repositoryEnvFiles: envFiles
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
var SKIP_ENV_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".next", ".nuxt", ".output", ".turbo", ".worktrees", "build", "coverage", "dist", "node_modules", "out", "target"]);
|
|
3630
|
+
function collectEnvironmentFiles(root, current = root, depth = 0) {
|
|
3631
|
+
if (depth > 5) return [];
|
|
3632
|
+
const found = [];
|
|
3633
|
+
try {
|
|
3634
|
+
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3635
|
+
if (entry.isDirectory()) {
|
|
3636
|
+
if (!SKIP_ENV_DIRECTORIES.has(entry.name)) found.push(...collectEnvironmentFiles(root, join20(current, entry.name), depth + 1));
|
|
3637
|
+
} else if (entry.name === ".env" || entry.name.startsWith(".env.")) {
|
|
3638
|
+
found.push(relative8(root, join20(current, entry.name)));
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
} catch {
|
|
3642
|
+
return found;
|
|
3643
|
+
}
|
|
3644
|
+
return found.sort();
|
|
3645
|
+
}
|
|
3646
|
+
function collectCentralSecretFiles(root, current = root, depth = 0) {
|
|
3647
|
+
if (depth > 3) return [];
|
|
3648
|
+
const found = [];
|
|
3649
|
+
try {
|
|
3650
|
+
for (const entry of readdirSync8(current, { withFileTypes: true })) {
|
|
3651
|
+
const path = join20(current, entry.name);
|
|
3652
|
+
if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
|
|
3653
|
+
else found.push({ path: relative8(root, path), mode: modeString(lstatSync6(path).mode) });
|
|
3654
|
+
}
|
|
3655
|
+
} catch {
|
|
3656
|
+
return found;
|
|
3657
|
+
}
|
|
3658
|
+
return found.sort((left, right) => left.path.localeCompare(right.path));
|
|
3659
|
+
}
|
|
3660
|
+
function basenameOnly(path) {
|
|
3661
|
+
const parts = path.split(/[\\/]/);
|
|
3662
|
+
return parts[parts.length - 1] ?? path;
|
|
3663
|
+
}
|
|
3664
|
+
function inspectSecretsRoot(path) {
|
|
3665
|
+
return existsSync21(path) ? { path, exists: true, mode: modeString(statSync4(path).mode) } : { path, exists: false };
|
|
3666
|
+
}
|
|
3667
|
+
function inspectUserMcp(homeDir) {
|
|
3668
|
+
if (!homeDir) return { codex: [], claudeCode: [] };
|
|
3669
|
+
return {
|
|
3670
|
+
codex: tomlMcpNames(join20(homeDir, ".codex/config.toml")),
|
|
3671
|
+
claudeCode: jsonObjectKeys(join20(homeDir, ".claude/settings.json"), "mcpServers")
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
var HOOK_EVENT_NAMES = /* @__PURE__ */ new Set([
|
|
3675
|
+
"PreToolUse",
|
|
3676
|
+
"PostToolUse",
|
|
3677
|
+
"PostToolUseFailure",
|
|
3678
|
+
"PermissionRequest",
|
|
3679
|
+
"UserPromptSubmit",
|
|
3680
|
+
"Notification",
|
|
3681
|
+
"SubagentStart",
|
|
3682
|
+
"SubagentStop",
|
|
3683
|
+
"Stop",
|
|
3684
|
+
"SessionStart",
|
|
3685
|
+
"SessionEnd",
|
|
3686
|
+
"PreCompact",
|
|
3687
|
+
"Setup",
|
|
3688
|
+
"TeammateIdle",
|
|
3689
|
+
"TaskCompleted",
|
|
3690
|
+
"ConfigChange",
|
|
3691
|
+
"WorktreeCreate",
|
|
3692
|
+
"WorktreeRemove"
|
|
3693
|
+
]);
|
|
3694
|
+
function inspectHooks(root) {
|
|
3695
|
+
const configs = [
|
|
3696
|
+
{ host: "agents", path: ".agents/hooks.json" },
|
|
3697
|
+
{ host: "claude-code", path: ".claude/settings.json" },
|
|
3698
|
+
{ host: "codex", path: ".codex/hooks.json" }
|
|
3699
|
+
];
|
|
3700
|
+
return configs.map((config) => {
|
|
3701
|
+
const value = readJson3(join20(root, config.path));
|
|
3702
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3703
|
+
collectHookEvents(value, counts);
|
|
3704
|
+
return {
|
|
3705
|
+
...config,
|
|
3706
|
+
events: [...counts].sort(([left], [right]) => left.localeCompare(right)).map(([name, count]) => ({ name, count }))
|
|
3707
|
+
};
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
function collectHookEvents(value, counts) {
|
|
3711
|
+
if (Array.isArray(value)) {
|
|
3712
|
+
for (const item of value) collectHookEvents(item, counts);
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (!isRecord3(value)) return;
|
|
3716
|
+
for (const [key, child] of Object.entries(value)) {
|
|
3717
|
+
if (HOOK_EVENT_NAMES.has(key)) counts.set(key, Array.isArray(child) ? child.length : 1);
|
|
3718
|
+
collectHookEvents(child, counts);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
function inspectDocs(root, expected) {
|
|
3722
|
+
const packageJson = readJson3(join20(root, "package.json"));
|
|
3723
|
+
const dependencies = isRecord3(packageJson) ? { ...recordOrEmpty(packageJson.dependencies), ...recordOrEmpty(packageJson.devDependencies) } : {};
|
|
3724
|
+
const docGov = dependencyVersion(dependencies["@pieai/doc-gov"]);
|
|
3725
|
+
const proGov = dependencyVersion(dependencies["@pieai/pro-gov"]);
|
|
3726
|
+
const routerMatch = safeRead(join20(root, "AGENTS.md")).match(/PGS-ROUTER:BEGIN\s+v([0-9.]+)/);
|
|
3727
|
+
const declared = [docGov, proGov].filter((value) => Boolean(value));
|
|
3728
|
+
return {
|
|
3729
|
+
routerVersion: routerMatch?.[1],
|
|
3730
|
+
manifest: existsSync21(join20(root, "docs/governance/MANIFEST.yml")),
|
|
3731
|
+
currentWork: existsSync21(join20(root, "docs/reference/execution/current-work.md")),
|
|
3732
|
+
packages: {
|
|
3733
|
+
expected,
|
|
3734
|
+
docGov,
|
|
3735
|
+
proGov,
|
|
3736
|
+
aligned: expected ? declared.length > 0 && declared.every((version) => version === expected) : true
|
|
3737
|
+
}
|
|
3738
|
+
};
|
|
3739
|
+
}
|
|
3740
|
+
function dependencyVersion(value) {
|
|
3741
|
+
if (typeof value !== "string") return void 0;
|
|
3742
|
+
const match = value.match(/(\d+\.\d+\.\d+)/);
|
|
3743
|
+
return match?.[1];
|
|
3744
|
+
}
|
|
3745
|
+
function packageVersion(path) {
|
|
3746
|
+
const value = readJson3(path);
|
|
3747
|
+
return isRecord3(value) && typeof value.version === "string" ? value.version : void 0;
|
|
3748
|
+
}
|
|
3749
|
+
function recordOrEmpty(value) {
|
|
3750
|
+
return isRecord3(value) ? value : {};
|
|
3751
|
+
}
|
|
3752
|
+
function inspectSkillRegistry(executionEngineRoot) {
|
|
3753
|
+
if (!executionEngineRoot) return { source: 0, registered: 0, bundled: 0, bundles: 0 };
|
|
3754
|
+
const agentAssetsRoot = join20(executionEngineRoot, "agent-assets");
|
|
3755
|
+
const registry = readJson3(join20(agentAssetsRoot, "registry.json"));
|
|
3756
|
+
const assets = isRecord3(registry) && Array.isArray(registry.assets) ? registry.assets : [];
|
|
3757
|
+
const registeredSkills = assets.filter((asset) => isRecord3(asset) && asset.kind === "skill");
|
|
3758
|
+
const bundleRoot = join20(agentAssetsRoot, "bundles");
|
|
3759
|
+
const bundleFiles = safeReadDir(bundleRoot).filter((file) => file.endsWith(".json"));
|
|
3760
|
+
const bundledIds = /* @__PURE__ */ new Set();
|
|
3761
|
+
for (const file of bundleFiles) {
|
|
3762
|
+
const bundle = readJson3(join20(bundleRoot, file));
|
|
3763
|
+
if (!isRecord3(bundle) || !Array.isArray(bundle.assets)) continue;
|
|
3764
|
+
for (const id of bundle.assets) if (typeof id === "string") bundledIds.add(id);
|
|
3765
|
+
}
|
|
3766
|
+
const sourceRoots = [join20(agentAssetsRoot, "skills/pie-skills"), join20(agentAssetsRoot, "skills/npx-skills/.agents/skills")];
|
|
3767
|
+
const source = sourceRoots.reduce((count, root) => count + safeReadDir(root).filter((name) => existsSync21(join20(root, name, "SKILL.md"))).length, 0);
|
|
3768
|
+
return {
|
|
3769
|
+
source,
|
|
3770
|
+
registered: registeredSkills.length,
|
|
3771
|
+
bundled: registeredSkills.filter((asset) => isRecord3(asset) && typeof asset.id === "string" && bundledIds.has(asset.id)).length,
|
|
3772
|
+
bundles: bundleFiles.length
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
function jsonObjectKeys(path, key) {
|
|
3776
|
+
const value = readJson3(path);
|
|
3777
|
+
if (!isRecord3(value) || !isRecord3(value[key])) return [];
|
|
3778
|
+
return Object.keys(value[key]).sort();
|
|
3779
|
+
}
|
|
3780
|
+
function tomlMcpNames(path) {
|
|
3781
|
+
if (!existsSync21(path)) return [];
|
|
3782
|
+
const names = /* @__PURE__ */ new Set();
|
|
3783
|
+
for (const line of safeRead(path).split(/\r?\n/)) {
|
|
3784
|
+
const match = line.match(/^\s*\[mcp_servers\.(?:"([^"]+)"|([^\.\]]+))\]\s*$/);
|
|
3785
|
+
const name = match?.[1] ?? match?.[2];
|
|
3786
|
+
if (name) names.add(name);
|
|
3787
|
+
}
|
|
3788
|
+
return [...names].sort();
|
|
3789
|
+
}
|
|
3790
|
+
function readJson3(path) {
|
|
3791
|
+
try {
|
|
3792
|
+
return JSON.parse(readFileSync15(path, "utf8"));
|
|
3793
|
+
} catch {
|
|
3794
|
+
return void 0;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
function safeRead(path) {
|
|
3798
|
+
try {
|
|
3799
|
+
return readFileSync15(path, "utf8");
|
|
3800
|
+
} catch {
|
|
3801
|
+
return "";
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
function safeReadDir(path) {
|
|
3805
|
+
try {
|
|
3806
|
+
return readdirSync8(path).sort();
|
|
3807
|
+
} catch {
|
|
3808
|
+
return [];
|
|
3809
|
+
}
|
|
3810
|
+
}
|
|
3811
|
+
function safeIsDirectory(path) {
|
|
3812
|
+
try {
|
|
3813
|
+
return statSync4(path).isDirectory();
|
|
3814
|
+
} catch {
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
function pathLexists(path) {
|
|
3819
|
+
try {
|
|
3820
|
+
lstatSync6(path);
|
|
3821
|
+
return true;
|
|
3822
|
+
} catch {
|
|
3823
|
+
return false;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
function gitTracks(root, path) {
|
|
3827
|
+
try {
|
|
3828
|
+
execFileSync("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "ignore" });
|
|
3829
|
+
return true;
|
|
3830
|
+
} catch {
|
|
3831
|
+
return false;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
function modeString(mode) {
|
|
3835
|
+
return (mode & 511).toString(8).padStart(3, "0");
|
|
3836
|
+
}
|
|
3837
|
+
function splitLines(value) {
|
|
3838
|
+
return value ? value.split(/\r?\n/).filter(Boolean) : [];
|
|
3839
|
+
}
|
|
3840
|
+
function stringArray(value) {
|
|
3841
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
3842
|
+
}
|
|
3843
|
+
function isRecord3(value) {
|
|
3844
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3845
|
+
}
|
|
3846
|
+
function findDashboardAssets() {
|
|
3847
|
+
const packageRoot2 = dirname12(dirname12(fileURLToPath4(import.meta.url)));
|
|
3848
|
+
const candidates = [
|
|
3849
|
+
process.env.PGS_DASHBOARD_ASSETS_DIR,
|
|
3850
|
+
join20(packageRoot2, "assets/portfolio-dashboard"),
|
|
3851
|
+
join20(process.cwd(), "assets/portfolio-dashboard"),
|
|
3852
|
+
join20(process.cwd(), "packages/pro-gov/assets/portfolio-dashboard")
|
|
3853
|
+
].filter((value) => Boolean(value));
|
|
3854
|
+
const match = candidates.find((path) => existsSync21(join20(path, "index.html")));
|
|
3855
|
+
if (!match) throw new Error("Portfolio dashboard assets were not built. Run pnpm --filter @pieai/pro-gov build.");
|
|
3856
|
+
return match;
|
|
3857
|
+
}
|
|
3858
|
+
function safeJavaScriptJson(value) {
|
|
3859
|
+
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3386
3862
|
// src/commands/portfolio.ts
|
|
3387
3863
|
function runPortfolio(args) {
|
|
3388
3864
|
const [subcommand2, ...rest] = args;
|
|
@@ -3390,9 +3866,41 @@ function runPortfolio(args) {
|
|
|
3390
3866
|
if (subcommand2 === "plan") return runPortfolioPlan(rest);
|
|
3391
3867
|
if (subcommand2 === "assets-check") return runPortfolioAssetsCheck(rest);
|
|
3392
3868
|
if (subcommand2 === "doctor") return runPortfolioDoctor(rest);
|
|
3869
|
+
if (subcommand2 === "ai-health") return runPortfolioAiHealth(rest);
|
|
3393
3870
|
printUsage4();
|
|
3394
3871
|
return 1;
|
|
3395
3872
|
}
|
|
3873
|
+
function runPortfolioAiHealth(args) {
|
|
3874
|
+
const options = parsePortfolioOptions(args);
|
|
3875
|
+
if (!options.ok) {
|
|
3876
|
+
console.error(options.error);
|
|
3877
|
+
printUsage4();
|
|
3878
|
+
return 1;
|
|
3879
|
+
}
|
|
3880
|
+
if (!options.value.outDir) {
|
|
3881
|
+
console.error("Expected --out <directory>");
|
|
3882
|
+
printUsage4();
|
|
3883
|
+
return 1;
|
|
3884
|
+
}
|
|
3885
|
+
const loaded = loadPortfolioManifest(options.value.configPath);
|
|
3886
|
+
if (loaded.issues.length > 0 || !loaded.manifest) {
|
|
3887
|
+
for (const issue of loaded.issues) console.error(`${issue.type}: ${issue.message}`);
|
|
3888
|
+
return 1;
|
|
3889
|
+
}
|
|
3890
|
+
const report = inspectPortfolioAiHealth({
|
|
3891
|
+
manifest: loaded.manifest,
|
|
3892
|
+
secretsRoot: options.value.secretsRoot
|
|
3893
|
+
});
|
|
3894
|
+
const written = writePortfolioAiHealthReport(report, options.value.outDir);
|
|
3895
|
+
if (options.value.json) {
|
|
3896
|
+
console.log(JSON.stringify({ ok: true, ...written, summary: report.summary }, null, 2));
|
|
3897
|
+
} else {
|
|
3898
|
+
console.log(`portfolio AI health report written (${report.repositories.length} repositories)`);
|
|
3899
|
+
console.log(`HTML: ${written.htmlPath}`);
|
|
3900
|
+
console.log(`JSON: ${written.jsonPath}`);
|
|
3901
|
+
}
|
|
3902
|
+
return 0;
|
|
3903
|
+
}
|
|
3396
3904
|
function runPortfolioDoctor(args) {
|
|
3397
3905
|
const options = parsePortfolioOptions(args);
|
|
3398
3906
|
if (!options.ok) {
|
|
@@ -3674,6 +4182,16 @@ function parsePortfolioOptions(args) {
|
|
|
3674
4182
|
index += 1;
|
|
3675
4183
|
} else if (arg === "--json") {
|
|
3676
4184
|
options.json = true;
|
|
4185
|
+
} else if (arg === "--out") {
|
|
4186
|
+
const outDir = args[index + 1];
|
|
4187
|
+
if (!outDir) return { ok: false, error: "Expected --out <directory>" };
|
|
4188
|
+
options.outDir = outDir;
|
|
4189
|
+
index += 1;
|
|
4190
|
+
} else if (arg === "--secrets-root") {
|
|
4191
|
+
const secretsRoot = args[index + 1];
|
|
4192
|
+
if (!secretsRoot) return { ok: false, error: "Expected --secrets-root <directory>" };
|
|
4193
|
+
options.secretsRoot = secretsRoot;
|
|
4194
|
+
index += 1;
|
|
3677
4195
|
} else {
|
|
3678
4196
|
return { ok: false, error: `Unknown portfolio option: ${arg}` };
|
|
3679
4197
|
}
|
|
@@ -3685,8 +4203,8 @@ function isHost2(value) {
|
|
|
3685
4203
|
return value === "codex" || value === "claude-code" || value === "gemini-cli" || value === "antigravity";
|
|
3686
4204
|
}
|
|
3687
4205
|
function findPortfolioAgentAssetsDir(manifest) {
|
|
3688
|
-
const agentAssetsDir = manifest?.executionEngine?.path ?
|
|
3689
|
-
return agentAssetsDir &&
|
|
4206
|
+
const agentAssetsDir = manifest?.executionEngine?.path ? join21(manifest.executionEngine.path, "agent-assets") : void 0;
|
|
4207
|
+
return agentAssetsDir && existsSync22(join21(agentAssetsDir, "registry.json")) ? agentAssetsDir : void 0;
|
|
3690
4208
|
}
|
|
3691
4209
|
function printUsage4() {
|
|
3692
4210
|
console.error("Usage:");
|
|
@@ -3694,11 +4212,12 @@ function printUsage4() {
|
|
|
3694
4212
|
console.error(" pro-gov portfolio plan --config <path> [--target <id|all>] [--host codex|claude-code|gemini-cli|antigravity] [--json]");
|
|
3695
4213
|
console.error(" pro-gov portfolio assets-check --config <path> [--target <id|all>] [--json]");
|
|
3696
4214
|
console.error(" pro-gov portfolio doctor --config <path> [--target <id|all>] [--json]");
|
|
4215
|
+
console.error(" pro-gov portfolio ai-health --config <path> --out <directory> [--secrets-root <directory>] [--json]");
|
|
3697
4216
|
}
|
|
3698
4217
|
|
|
3699
4218
|
// src/commands/sync.ts
|
|
3700
|
-
import { existsSync as
|
|
3701
|
-
import { join as
|
|
4219
|
+
import { existsSync as existsSync23, readFileSync as readFileSync16 } from "node:fs";
|
|
4220
|
+
import { join as join22 } from "node:path";
|
|
3702
4221
|
function runSync(args) {
|
|
3703
4222
|
const check = args.includes("--check");
|
|
3704
4223
|
if (!check) {
|
|
@@ -3726,8 +4245,8 @@ function runSync(args) {
|
|
|
3726
4245
|
console.log("pro-gov sync check");
|
|
3727
4246
|
console.log(`profile: ${profile}`);
|
|
3728
4247
|
for (const file of planStarterFiles(profile)) {
|
|
3729
|
-
const targetPath =
|
|
3730
|
-
if (!
|
|
4248
|
+
const targetPath = join22(process.cwd(), file.targetPath);
|
|
4249
|
+
if (!existsSync23(targetPath)) {
|
|
3731
4250
|
if (file.ownership === "optional-guardrail") continue;
|
|
3732
4251
|
console.log(`missing: ${file.targetPath}`);
|
|
3733
4252
|
differences += 1;
|
|
@@ -3735,8 +4254,8 @@ function runSync(args) {
|
|
|
3735
4254
|
}
|
|
3736
4255
|
if (file.ownership === "optional-guardrail") continue;
|
|
3737
4256
|
if (file.ownership === "project-local-seed") continue;
|
|
3738
|
-
const source =
|
|
3739
|
-
const target =
|
|
4257
|
+
const source = readFileSync16(file.absoluteSourcePath, "utf8");
|
|
4258
|
+
const target = readFileSync16(targetPath, "utf8");
|
|
3740
4259
|
if (!matchesExpectedContent(file.targetPath, source, target)) {
|
|
3741
4260
|
console.log(`different: ${file.targetPath}`);
|
|
3742
4261
|
differences += 1;
|
|
@@ -3770,7 +4289,7 @@ function normalizeMarkdownTableCell(cell) {
|
|
|
3770
4289
|
}
|
|
3771
4290
|
function inferInstalledProfile(root) {
|
|
3772
4291
|
const installed = ["engineering-runtime", "doc-only"].filter(
|
|
3773
|
-
(profile) =>
|
|
4292
|
+
(profile) => existsSync23(join22(root, `docs/governance/agents-routing/${profile}-v0.9.md`))
|
|
3774
4293
|
);
|
|
3775
4294
|
return installed.length === 1 ? installed[0] : void 0;
|
|
3776
4295
|
}
|
|
@@ -3794,6 +4313,7 @@ var COMMANDS = [
|
|
|
3794
4313
|
"portfolio plan --config <path> [--target <id|all>] [--json]",
|
|
3795
4314
|
"portfolio assets-check --config <path> [--target <id|all>] [--json]",
|
|
3796
4315
|
"portfolio doctor --config <path> [--target <id|all>] [--json]",
|
|
4316
|
+
"portfolio ai-health --config <path> --out <directory> [--secrets-root <directory>] [--json]",
|
|
3797
4317
|
"learn recall --query <text> [--target <path>] [--limit <n>] [--json]",
|
|
3798
4318
|
"learn capture --title <text> --summary <text> [--category <slug>] [--target <path>] [--json]",
|
|
3799
4319
|
"lens scan [--target <path>] [--json]",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pieai/pro-gov",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Project-level distribution kit for Project Governance System.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -35,11 +35,16 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@pieai/doc-gov": "^0.4.
|
|
38
|
+
"@pieai/doc-gov": "^0.4.7"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@pieai/swimmer-ui-kit": "1.0.1",
|
|
41
42
|
"@types/node": "24.13.2",
|
|
43
|
+
"@types/react": "19.1.16",
|
|
44
|
+
"@types/react-dom": "19.1.9",
|
|
42
45
|
"esbuild": "0.27.7",
|
|
46
|
+
"react": "19.1.1",
|
|
47
|
+
"react-dom": "19.1.1",
|
|
43
48
|
"tsx": "4.21.0",
|
|
44
49
|
"typescript": "6.0.3"
|
|
45
50
|
},
|
|
@@ -48,9 +53,9 @@
|
|
|
48
53
|
},
|
|
49
54
|
"scripts": {
|
|
50
55
|
"dev": "tsx src/cli.ts",
|
|
51
|
-
"build": "node scripts/copy-assets.mjs && esbuild src/cli.ts --bundle --platform=node --format=esm --target=node24 --banner:js=\"#!/usr/bin/env node\" --outfile=dist/cli.js && chmod +x dist/cli.js",
|
|
56
|
+
"build": "node scripts/build-dashboard.mjs && node scripts/copy-assets.mjs && esbuild src/cli.ts --bundle --platform=node --format=esm --target=node24 --banner:js=\"#!/usr/bin/env node\" --outfile=dist/cli.js && chmod +x dist/cli.js",
|
|
52
57
|
"pretest": "pnpm --filter @pieai/doc-gov build && pnpm build",
|
|
53
58
|
"test": "tsx --test src/*.test.ts src/**/*.test.ts",
|
|
54
|
-
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
59
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.dashboard.json --noEmit"
|
|
55
60
|
}
|
|
56
61
|
}
|