@pieai/pro-gov 0.9.1 → 0.9.3

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.
@@ -25,11 +25,11 @@
25
25
  "notes": "Public rule for choosing evidence lanes and requiring fresh verification before completion.",
26
26
  "promotion": {
27
27
  "privateSourcePath": "rules/pie-rules/ai-in-the-loop.md",
28
- "privateSourceHash": "sha256:71f487152123491a2c110ad839a90780b08e04ae3a133d59bbe977bf8b2f64e4",
28
+ "privateSourceHash": "sha256:344eba8c3b1f3130ed33e4b6a4511a332e2ca477e6a40ad7d4c73006197f5445",
29
29
  "publicHash": "sha256:911702baf6ea1d724e41ea1876d23d80f4de8dfaaadbf1399a40c982c810224c",
30
30
  "sanitized": true,
31
- "lastReviewed": "2026-08-17",
32
- "reviewNotes": "Reviewed after the private source gained governed front matter and normalized formatting. The public copy intentionally keeps the portable evidence-loop method without the private Grok compatibility lane or project governance metadata."
31
+ "lastReviewed": "2026-08-20",
32
+ "reviewNotes": "Reviewed after the private source gained governed front matter and normalized formatting. The public copy intentionally keeps the portable evidence-loop method without the private Grok compatibility lane or project governance metadata. Re-reviewed 2026-08-20: the private source moved by Markdown table padding only, with no wording change, so the sanitized public copy is unaffected and only the private hash is re-pinned."
33
33
  }
34
34
  },
35
35
  {
@@ -43,7 +43,7 @@ jobs:
43
43
 
44
44
  - uses: pnpm/action-setup@v6
45
45
  with:
46
- version: 11.9.0
46
+ version: 11.22.0
47
47
 
48
48
  - uses: actions/setup-node@v6
49
49
  with:
@@ -0,0 +1,27 @@
1
+ # Seeded by `pro-gov init`. This file is project-local after seeding — add
2
+ # your own build outputs and generated paths freely.
3
+
4
+ # Dependencies and build output
5
+ node_modules
6
+ dist
7
+ build
8
+ coverage
9
+ out
10
+ .next
11
+ .turbo
12
+ .tmp
13
+
14
+ # ─────────────────────────────────────────────────────────────────────────
15
+ # PGS-managed content — DO NOT REFORMAT. Keep these entries.
16
+ #
17
+ # `pro-gov sync --check` byte-compares docs/governance against the packaged
18
+ # starter, and `pro-gov portfolio assets-check` byte-compares the shared-rule
19
+ # snapshots. A formatter that only pads Markdown table cells is enough to fail
20
+ # both, and the guardrails cannot tell cosmetic padding from a real local edit
21
+ # — re-applying the asset then refuses with asset-lock-drift.
22
+ #
23
+ # If you need one of these files changed, change it upstream in Project
24
+ # Governance System and re-sync; do not restyle it here.
25
+ # ─────────────────────────────────────────────────────────────────────────
26
+ docs/governance
27
+ docs/policy/shared-rules
package/dist/cli.js CHANGED
@@ -60,7 +60,6 @@ function loadAgentAssetBundles(agentAssetsDir) {
60
60
 
61
61
  // src/asset-npx/maintenance.ts
62
62
  import { createHash } from "node:crypto";
63
- import { spawnSync } from "node:child_process";
64
63
  import {
65
64
  cpSync,
66
65
  existsSync as existsSync3,
@@ -72,6 +71,29 @@ import {
72
71
  } from "node:fs";
73
72
  import { join as join3, relative as relative2 } from "node:path";
74
73
  import { tmpdir } from "node:os";
74
+
75
+ // src/command-runner.ts
76
+ import {
77
+ spawnSync
78
+ } from "node:child_process";
79
+ function spawnPlatformSync(command2, args, options) {
80
+ if (process.platform !== "win32") return spawnSync(command2, args, options);
81
+ const commandLine = [command2, ...args].map(quoteWindowsArgument).join(" ");
82
+ return spawnSync(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", `"${commandLine}"`], {
83
+ ...options,
84
+ // The command line above is intentionally assembled for cmd.exe. Without
85
+ // this flag Node escapes its quotes before cmd.exe can parse them.
86
+ windowsVerbatimArguments: true
87
+ });
88
+ }
89
+ function quoteWindowsArgument(value) {
90
+ if (value.length === 0 || /[\s"&|<>^]/.test(value)) {
91
+ return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
92
+ }
93
+ return value;
94
+ }
95
+
96
+ // src/asset-npx/maintenance.ts
75
97
  function createNpxSkillsMaintenancePlan(options) {
76
98
  assertNativeNpxRoot(options.npxRoot);
77
99
  if (options.operation === "add" && !options.source) {
@@ -140,7 +162,8 @@ function buildNpxCommand(options) {
140
162
  return command2;
141
163
  }
142
164
  function defaultRunner({ command: command2, cwd, timeoutMs }) {
143
- const result = spawnSync(command2[0] ?? "npx", command2.slice(1), {
165
+ const commandName = command2[0] ?? "npx";
166
+ const result = spawnPlatformSync(commandName, command2.slice(1), {
144
167
  cwd,
145
168
  encoding: "utf8",
146
169
  timeout: timeoutMs
@@ -739,6 +762,13 @@ import { dirname as dirname4, join as join8, relative as relative5, resolve as r
739
762
  // src/asset-targets/install-plan.ts
740
763
  import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync4, readlinkSync, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
741
764
  import { basename, dirname as dirname3, join as join7, resolve } from "node:path";
765
+
766
+ // src/symlinks.ts
767
+ function normalizeSymlinkTarget(target) {
768
+ return target.replaceAll("\\", "/");
769
+ }
770
+
771
+ // src/asset-targets/install-plan.ts
742
772
  function createAssetInstallPlan(options) {
743
773
  const placement = options.placement ?? "registry";
744
774
  const assetsById = new Map(options.registry.assets.map((asset) => [asset.id, asset]));
@@ -1048,7 +1078,7 @@ function createLegacyClaudeAdoptions(options) {
1048
1078
  const targetAbsolutePath = join7(options.targetDir, targetPath);
1049
1079
  const compatibilityRootPath = join7(options.targetDir, ".claude/skills");
1050
1080
  const canonicalRootPath = join7(options.targetDir, ".agents/skills");
1051
- if (!lstatSync2(canonicalRootPath).isDirectory() || !lstatSync2(compatibilityRootPath).isSymbolicLink() || readlinkSync(compatibilityRootPath) !== "../.agents/skills" || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
1081
+ if (!lstatSync2(canonicalRootPath).isDirectory() || !lstatSync2(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync(compatibilityRootPath)) !== "../.agents/skills" || realpathSync2(compatibilityRootPath) !== realpathSync2(canonicalRootPath)) {
1052
1082
  throw new Error(
1053
1083
  `Legacy Claude compatibility root is not the exact canonical alias for ${entry.id}.`
1054
1084
  );
@@ -1289,7 +1319,7 @@ function validateAdoptedSymlink(targetDir, action) {
1289
1319
  const compatibilityRootPath = join8(targetDir, action.compatibilityRootPath);
1290
1320
  const targetAbsolutePath = join8(targetDir, action.targetPath);
1291
1321
  const legacyAbsolutePath = join8(targetDir, action.legacyTargetPath);
1292
- if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || readlinkSync2(compatibilityRootPath) !== action.expectedCompatibilityRawTarget || realpathSync3(compatibilityRootPath) !== realpathSync3(canonicalRootPath)) {
1322
+ if (!lstatSync3(canonicalRootPath).isDirectory() || !lstatSync3(compatibilityRootPath).isSymbolicLink() || normalizeSymlinkTarget(readlinkSync2(compatibilityRootPath)) !== action.expectedCompatibilityRawTarget || realpathSync3(compatibilityRootPath) !== realpathSync3(canonicalRootPath)) {
1293
1323
  throw new Error(
1294
1324
  `Legacy Claude compatibility alias changed before apply: ${action.compatibilityRootPath}`
1295
1325
  );
@@ -2217,7 +2247,6 @@ function printNpxUsage() {
2217
2247
  }
2218
2248
 
2219
2249
  // src/commands/doctor.ts
2220
- import { spawnSync as spawnSync2 } from "node:child_process";
2221
2250
  import { existsSync as existsSync12 } from "node:fs";
2222
2251
  import { createRequire } from "node:module";
2223
2252
  import { dirname as dirname6, join as join12 } from "node:path";
@@ -2252,10 +2281,12 @@ function runDoctor(args) {
2252
2281
  return runPackageDoctor(args);
2253
2282
  }
2254
2283
  function checkDocGov(options = {}) {
2255
- const run = options.run ?? ((command2, args) => spawnSync2(command2, args, {
2256
- encoding: "utf8",
2257
- stdio: "ignore"
2258
- }));
2284
+ const run = options.run ?? ((command2, args) => {
2285
+ return spawnPlatformSync(command2, args, {
2286
+ encoding: "utf8",
2287
+ stdio: "ignore"
2288
+ });
2289
+ });
2259
2290
  const dependencyCli = (options.resolveDependencyCli ?? resolveDocGovDependencyCli)();
2260
2291
  if (!dependencyCli) {
2261
2292
  const fromPath = run("doc-gov", ["--help"]);
@@ -2328,7 +2359,10 @@ function classifyOwnership(targetPath) {
2328
2359
  if (targetPath === "lefthook.yml" || targetPath === ".github/workflows/docs-check.yml") {
2329
2360
  return "optional-guardrail";
2330
2361
  }
2331
- if (targetPath === "AGENTS.md" || targetPath === "docs/policy/best-practice-for-this-project.md" || targetPath === "docs/reference/documentation-map.md" || targetPath === "docs/reference/execution/current-work.md") {
2362
+ if (targetPath === "AGENTS.md" || // Seeded so a new project starts with the managed paths protected from its
2363
+ // formatter, then owned locally — every project adds its own build outputs,
2364
+ // so this must not be byte-compared by `sync --check`.
2365
+ targetPath === ".oxfmtignore" || targetPath === "docs/policy/best-practice-for-this-project.md" || targetPath === "docs/reference/documentation-map.md" || targetPath === "docs/reference/execution/current-work.md") {
2332
2366
  return "project-local-seed";
2333
2367
  }
2334
2368
  return "shared";
@@ -2501,28 +2535,28 @@ var ROOT_DEFINITIONS = [
2501
2535
  {
2502
2536
  id: "pnpm-store",
2503
2537
  label: "pnpm Store",
2504
- relativePath: "Library/pnpm/store",
2538
+ relativePath: process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store",
2505
2539
  kind: "package-cache",
2506
2540
  note: "pnpm \u5185\u5BB9\u5BFB\u5740\u4ED3\u5E93\uFF1B\u6E05\u7406\u65F6\u53EA\u5EFA\u8BAE pnpm store prune\u3002"
2507
2541
  },
2508
2542
  {
2509
2543
  id: "playwright",
2510
2544
  label: "Playwright \u6D4F\u89C8\u5668",
2511
- relativePath: "Library/Caches/ms-playwright",
2545
+ relativePath: process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright",
2512
2546
  kind: "browser-cache",
2513
2547
  note: "\u6D4B\u8BD5\u6D4F\u89C8\u5668\u4E8C\u8FDB\u5236\u7F13\u5B58\uFF1B\u7248\u672C\u5806\u79EF\u53EF\u80FD\u9020\u6210\u663E\u8457\u78C1\u76D8\u5360\u7528\u3002"
2514
2548
  },
2515
2549
  {
2516
2550
  id: "claude-desktop",
2517
2551
  label: "Claude Desktop",
2518
- relativePath: "Library/Application Support/Claude",
2552
+ relativePath: process.platform === "win32" ? "AppData/Roaming/Claude" : "Library/Application Support/Claude",
2519
2553
  kind: "ai-host",
2520
2554
  note: "\u684C\u9762\u5E94\u7528\u8FD0\u884C\u6570\u636E\uFF0C\u9ED8\u8BA4\u53D7\u4FDD\u62A4\u3002"
2521
2555
  },
2522
2556
  {
2523
2557
  id: "cursor-desktop",
2524
2558
  label: "Cursor Desktop",
2525
- relativePath: "Library/Application Support/Cursor",
2559
+ relativePath: process.platform === "win32" ? "AppData/Roaming/Cursor" : "Library/Application Support/Cursor",
2526
2560
  kind: "ai-host",
2527
2561
  note: "\u684C\u9762\u5E94\u7528\u8FD0\u884C\u6570\u636E\uFF0C\u9ED8\u8BA4\u53D7\u4FDD\u62A4\u3002"
2528
2562
  }
@@ -2590,11 +2624,17 @@ function inspectHost(options = {}) {
2590
2624
  function inspectAgentBrowser(options) {
2591
2625
  const command2 = options.command ?? "agent-browser";
2592
2626
  try {
2593
- const output = execFileSync(command2, ["--version"], {
2627
+ const result = spawnPlatformSync(command2, ["--version"], {
2594
2628
  encoding: "utf8",
2595
2629
  stdio: ["ignore", "pipe", "ignore"],
2596
- timeout: 3e3
2597
- }).trim();
2630
+ // Host scans can run alongside the full portfolio test matrix. Keep the
2631
+ // probe bounded, but leave enough headroom for a busy Node process to
2632
+ // spawn the pinned CLI instead of misclassifying a slow start as an
2633
+ // unparseable version.
2634
+ timeout: 1e4
2635
+ });
2636
+ if (result.error || result.status !== 0) throw result.error ?? new Error("command failed");
2637
+ const output = result.stdout.trim();
2598
2638
  const installedVersion = output.match(/(?:^|\s)(\d+\.\d+\.\d+)(?:\s|$)/)?.[1];
2599
2639
  if (!installedVersion) {
2600
2640
  return {
@@ -2753,7 +2793,12 @@ function inspectFindings(homePath) {
2753
2793
  id: "pnpm-store",
2754
2794
  rootId: "pnpm-store",
2755
2795
  label: "pnpm \u672A\u5F15\u7528\u5305\u5019\u9009",
2756
- paths: [join14(homePath, "Library/pnpm/store")],
2796
+ paths: [
2797
+ join14(
2798
+ homePath,
2799
+ process.platform === "win32" ? "AppData/Local/pnpm/store" : "Library/pnpm/store"
2800
+ )
2801
+ ],
2757
2802
  category: "cache",
2758
2803
  confidence: "high",
2759
2804
  disposition: "native-tool",
@@ -2765,7 +2810,13 @@ function inspectFindings(homePath) {
2765
2810
  id: "playwright-browsers",
2766
2811
  rootId: "playwright",
2767
2812
  label: "Playwright \u6D4F\u89C8\u5668\u7248\u672C",
2768
- paths: [join14(homePath, "Library/Caches/ms-playwright"), join14(homePath, ".cache/ms-playwright")],
2813
+ paths: [
2814
+ join14(
2815
+ homePath,
2816
+ process.platform === "win32" ? "AppData/Local/ms-playwright" : "Library/Caches/ms-playwright"
2817
+ ),
2818
+ join14(homePath, ".cache/ms-playwright")
2819
+ ],
2769
2820
  category: "duplicate-runtime",
2770
2821
  confidence: "medium",
2771
2822
  disposition: "native-tool",
@@ -2787,6 +2838,8 @@ function inspectFindings(homePath) {
2787
2838
  });
2788
2839
  }
2789
2840
  function buildProtections(homePath) {
2841
+ const claudeDesktopPath = process.platform === "win32" ? "~/AppData/Roaming/Claude" : "~/Library/Application Support/Claude";
2842
+ const cursorDesktopPath = process.platform === "win32" ? "~/AppData/Roaming/Cursor" : "~/Library/Application Support/Cursor";
2790
2843
  return [
2791
2844
  {
2792
2845
  label: "\u914D\u7F6E\u4E0E\u8EAB\u4EFD\u8FB9\u754C",
@@ -2805,7 +2858,7 @@ function buildProtections(homePath) {
2805
2858
  },
2806
2859
  {
2807
2860
  label: "\u684C\u9762\u5BBF\u4E3B\u8FD0\u884C\u6570\u636E",
2808
- paths: ["~/Library/Application Support/Claude", "~/Library/Application Support/Cursor"],
2861
+ paths: [claudeDesktopPath, cursorDesktopPath],
2809
2862
  reason: "\u5E94\u7528\u6570\u636E\u5E93\u4E0E\u8FD0\u884C\u72B6\u6001\u6DF7\u5728\u5176\u4E2D\uFF0C\u4E0D\u80FD\u628A\u6574\u4E2A\u76EE\u5F55\u5F53\u7F13\u5B58\u3002"
2810
2863
  }
2811
2864
  ].map((protection) => ({
@@ -2889,7 +2942,7 @@ function displayPath(path, homePath) {
2889
2942
  const absolute = resolve3(path);
2890
2943
  const withinHome = relative6(homePath, absolute);
2891
2944
  if (withinHome === "") return "~";
2892
- if (!withinHome.startsWith("..")) return `~/${withinHome}`;
2945
+ if (!withinHome.startsWith("..")) return `~/${withinHome.replaceAll("\\", "/")}`;
2893
2946
  return absolute;
2894
2947
  }
2895
2948
  function undisplayPath(path, homePath) {
@@ -4782,7 +4835,7 @@ function formatLink(link) {
4782
4835
  }
4783
4836
 
4784
4837
  // src/lens/scan.ts
4785
- import { spawnSync as spawnSync4 } from "node:child_process";
4838
+ import { spawnSync as spawnSync3 } from "node:child_process";
4786
4839
  import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync6 } from "node:fs";
4787
4840
  import { homedir as homedir3 } from "node:os";
4788
4841
  import { join as join22 } from "node:path";
@@ -4858,7 +4911,7 @@ function inspectCompatibilityLink(root, path, expectedRawTarget) {
4858
4911
  if (stat.isDirectory()) return { ...base, status: "directory" };
4859
4912
  return { ...base, status: "other" };
4860
4913
  }
4861
- const rawTarget = readlinkSync3(absolutePath);
4914
+ const rawTarget = normalizeSymlinkTarget(readlinkSync3(absolutePath));
4862
4915
  const resolvedTarget = resolve6(dirname12(absolutePath), rawTarget);
4863
4916
  const expectedPath = resolve6(dirname12(absolutePath), expectedRawTarget);
4864
4917
  const targetStat = safeLstat2(resolvedTarget);
@@ -5064,7 +5117,7 @@ function readPackageJson(path) {
5064
5117
  }
5065
5118
 
5066
5119
  // src/repository-files.ts
5067
- import { spawnSync as spawnSync3 } from "node:child_process";
5120
+ import { spawnSync as spawnSync2 } from "node:child_process";
5068
5121
  import { existsSync as existsSync20, readdirSync as readdirSync10 } from "node:fs";
5069
5122
  import { isAbsolute as isAbsolute5, join as join21, posix as posix3, relative as relative9 } from "node:path";
5070
5123
  var gitMaxBufferBytes = 64 * 1024 * 1024;
@@ -5147,7 +5200,7 @@ function isSafeRepositoryRelativePath(path) {
5147
5200
  return path !== "" && path !== "." && !isAbsolute5(path) && !/^[a-zA-Z]:\//.test(path) && path !== ".." && !path.startsWith("../");
5148
5201
  }
5149
5202
  function runGit(root, args) {
5150
- const result = spawnSync3("git", ["-C", root, ...args], {
5203
+ const result = spawnSync2("git", ["-C", root, ...args], {
5151
5204
  encoding: "utf8",
5152
5205
  maxBuffer: gitMaxBufferBytes,
5153
5206
  env: { ...process.env, LANG: "C", LC_ALL: "C" }
@@ -5230,7 +5283,7 @@ function readGitState(targetDir) {
5230
5283
  };
5231
5284
  }
5232
5285
  function runGit2(targetDir, args) {
5233
- const result = spawnSync4("git", ["-C", targetDir, ...args], {
5286
+ const result = spawnSync3("git", ["-C", targetDir, ...args], {
5234
5287
  encoding: "utf8",
5235
5288
  maxBuffer: 64 * 1024 * 1024
5236
5289
  });
@@ -5408,7 +5461,7 @@ import { existsSync as existsSync33, readFileSync as readFileSync18 } from "node
5408
5461
  import { join as join34 } from "node:path";
5409
5462
 
5410
5463
  // src/portfolio/doctor.ts
5411
- import { spawnSync as spawnSync7 } from "node:child_process";
5464
+ import { spawnSync as spawnSync5 } from "node:child_process";
5412
5465
  import { existsSync as existsSync24, readFileSync as readFileSync16 } from "node:fs";
5413
5466
  import { createRequire as createRequire2 } from "node:module";
5414
5467
  import { homedir as homedir4 } from "node:os";
@@ -5416,7 +5469,6 @@ import { dirname as dirname15, join as join25 } from "node:path";
5416
5469
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5417
5470
 
5418
5471
  // src/host-tooling/inventory.ts
5419
- import { spawnSync as spawnSync5 } from "node:child_process";
5420
5472
  function inspectHostTooling(requirements, runner = defaultRunner2) {
5421
5473
  const hosts = [];
5422
5474
  const issues = [];
@@ -5490,7 +5542,7 @@ function parseHostPlugins(host, value) {
5490
5542
  });
5491
5543
  }
5492
5544
  function defaultRunner2({ command: command2 }) {
5493
- const result = spawnSync5(command2[0] ?? "", command2.slice(1), {
5545
+ const result = spawnPlatformSync(command2[0] ?? "", command2.slice(1), {
5494
5546
  encoding: "utf8",
5495
5547
  timeout: 1e4
5496
5548
  });
@@ -5605,7 +5657,7 @@ function pathIsSymlink(path) {
5605
5657
  }
5606
5658
 
5607
5659
  // src/portfolio/version-policy.ts
5608
- import { spawnSync as spawnSync6 } from "node:child_process";
5660
+ import { spawnSync as spawnSync4 } from "node:child_process";
5609
5661
  import { existsSync as existsSync23, lstatSync as lstatSync9, readFileSync as readFileSync15 } from "node:fs";
5610
5662
  import { dirname as dirname14, join as join24 } from "node:path";
5611
5663
  function inspectVersionPolicy(root, policy, projectType) {
@@ -5697,7 +5749,7 @@ function inspectRuntime(expectedName, expectedVersion) {
5697
5749
  function readRuntimeVersion(name) {
5698
5750
  if (name === "node") return process.versions.node;
5699
5751
  if (name !== "deno") return void 0;
5700
- const result = spawnSync6("deno", ["--version"], { encoding: "utf8" });
5752
+ const result = spawnSync4("deno", ["--version"], { encoding: "utf8" });
5701
5753
  if (result.status !== 0) return void 0;
5702
5754
  return /^deno\s+(\d+\.\d+\.\d+)/m.exec(result.stdout)?.[1];
5703
5755
  }
@@ -5915,7 +5967,7 @@ function runTargetChecks(target) {
5915
5967
  ];
5916
5968
  return commands.map((command2) => {
5917
5969
  if (!existsSync24(command2.cli)) return { name: command2.name, status: null };
5918
- const result = spawnSync7(process.execPath, [command2.cli, ...command2.args], {
5970
+ const result = spawnSync5(process.execPath, [command2.cli, ...command2.args], {
5919
5971
  cwd: target.path,
5920
5972
  encoding: "utf8",
5921
5973
  timeout: 3e4
@@ -5924,13 +5976,13 @@ function runTargetChecks(target) {
5924
5976
  });
5925
5977
  }
5926
5978
  function inspectGit(path) {
5927
- const inside = spawnSync7("git", ["rev-parse", "--is-inside-work-tree"], {
5979
+ const inside = spawnSync5("git", ["rev-parse", "--is-inside-work-tree"], {
5928
5980
  cwd: path,
5929
5981
  encoding: "utf8"
5930
5982
  });
5931
5983
  if (inside.status !== 0) return { isRepository: false, dirty: false };
5932
- const status = spawnSync7("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
5933
- const branch = spawnSync7("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
5984
+ const status = spawnSync5("git", ["status", "--porcelain"], { cwd: path, encoding: "utf8" });
5985
+ const branch = spawnSync5("git", ["branch", "--show-current"], { cwd: path, encoding: "utf8" });
5934
5986
  return {
5935
5987
  isRepository: true,
5936
5988
  dirty: status.stdout.trim().length > 0,
@@ -5999,12 +6051,13 @@ function safeRealpath(path) {
5999
6051
  }
6000
6052
  function commandVersion(command2) {
6001
6053
  try {
6002
- const output = execFileSync2(command2, ["version"], {
6054
+ const result = spawnPlatformSync(command2, ["version"], {
6003
6055
  encoding: "utf8",
6004
6056
  stdio: ["ignore", "pipe", "ignore"],
6005
6057
  timeout: 3e3
6006
6058
  });
6007
- return output.match(/\b\d+\.\d+\.\d+\b/)?.[0];
6059
+ if (result.error || result.status !== 0) return void 0;
6060
+ return result.stdout.match(/\b\d+\.\d+\.\d+\b/)?.[0];
6008
6061
  } catch {
6009
6062
  return void 0;
6010
6063
  }
@@ -6285,12 +6338,10 @@ function inspectGit2(root) {
6285
6338
  }
6286
6339
 
6287
6340
  // src/portfolio/ai-health/hosts.ts
6288
- import { execFileSync as execFileSync5 } from "node:child_process";
6289
6341
  import { existsSync as existsSync28 } from "node:fs";
6290
6342
  import { join as join28, resolve as resolve8, sep as sep3 } from "node:path";
6291
6343
 
6292
6344
  // src/portfolio/ai-health/devspace.ts
6293
- import { execFileSync as execFileSync4 } from "node:child_process";
6294
6345
  import { existsSync as existsSync27, statSync as statSync8 } from "node:fs";
6295
6346
  import { join as join27, relative as relative10, resolve as resolve7, sep as sep2 } from "node:path";
6296
6347
  function inspectDevSpaceHealth(options) {
@@ -6306,9 +6357,9 @@ function inspectDevSpaceHealth(options) {
6306
6357
  5e3
6307
6358
  );
6308
6359
  const latestVersion = latestResult.ok ? latestResult.stdout.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0] : void 0;
6309
- const processResult = run("pgrep", ["-f", "devspace serve"], 3e3);
6310
- const pid = processResult.ok ? processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
6311
- const processEnvironment = pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
6360
+ const processResult = process.platform === "win32" ? run("tasklist", ["/FI", "IMAGENAME eq devspace.exe", "/FO", "CSV", "/NH"], 3e3) : run("pgrep", ["-f", "devspace serve"], 3e3);
6361
+ const pid = processResult.ok ? process.platform === "win32" ? processResult.stdout.match(/"devspace\.exe","(\d+)"/i)?.[1] : processResult.stdout.match(/\b\d+\b/)?.[0] : void 0;
6362
+ const processEnvironment = process.platform !== "win32" && pid ? run("ps", ["eww", "-p", pid, "-o", "command="], 3e3) : void 0;
6312
6363
  const processToolMode = processEnvironment?.ok ? processEnvironment.stdout.match(
6313
6364
  /(?:^|\s)DEVSPACE_TOOL_MODE=(minimal|full|codex)(?:\s|$)/
6314
6365
  )?.[1] : void 0;
@@ -6320,9 +6371,9 @@ function inspectDevSpaceHealth(options) {
6320
6371
  (repositoryPath) => allowedRoots.some((root) => isPathInside(repositoryPath, root))
6321
6372
  ) ? "complete" : "partial";
6322
6373
  const bind = configExists && typeof configValue.host === "string" ? isLoopbackHost(configValue.host) ? "loopback" : "non-loopback" : "unknown";
6323
- const directoryMode = existsSync27(configDirectory) ? modeString(statSync8(configDirectory).mode) : void 0;
6324
- const fileMode = existsSync27(configPath) ? modeString(statSync8(configPath).mode) : void 0;
6325
- const authMode = existsSync27(authPath) ? modeString(statSync8(authPath).mode) : void 0;
6374
+ const directoryMode = process.platform !== "win32" && existsSync27(configDirectory) ? modeString(statSync8(configDirectory).mode) : void 0;
6375
+ const fileMode = process.platform !== "win32" && existsSync27(configPath) ? modeString(statSync8(configPath).mode) : void 0;
6376
+ const authMode = process.platform !== "win32" && existsSync27(authPath) ? modeString(statSync8(authPath).mode) : void 0;
6326
6377
  const update = installedVersion && latestVersion ? installedVersion === latestVersion ? "current" : "available" : "unknown";
6327
6378
  const recommendations = [];
6328
6379
  let status = "healthy";
@@ -6346,7 +6397,7 @@ function inspectDevSpaceHealth(options) {
6346
6397
  if (doctor === "failed") unhealthy("devspace doctor \u672A\u901A\u8FC7\u3002");
6347
6398
  if (portfolioCoverage === "partial") attention("DevSpace allowedRoots \u6CA1\u6709\u8986\u76D6\u5168\u90E8\u5DF2\u767B\u8BB0\u4ED3\u5E93\u3002");
6348
6399
  if (installedResult.ok && !pid) attention("DevSpace \u5DF2\u5B89\u88C5\u4F46\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u3002");
6349
- if (options.expectedToolMode && pid && processToolMode !== options.expectedToolMode) {
6400
+ if (options.expectedToolMode && pid && processToolMode !== void 0 && processToolMode !== options.expectedToolMode) {
6350
6401
  attention(
6351
6402
  `\u8FD0\u884C\u4E2D\u7684 DevSpace \u5DE5\u5177\u6A21\u5F0F\u4E3A ${processToolMode ?? "unknown"}\uFF0C\u671F\u671B ${options.expectedToolMode}\u3002`
6352
6403
  );
@@ -6381,18 +6432,12 @@ function inspectDevSpaceHealth(options) {
6381
6432
  };
6382
6433
  }
6383
6434
  function runDevSpaceCommand(command2, args, timeout) {
6384
- try {
6385
- return {
6386
- ok: true,
6387
- stdout: execFileSync4(command2, args, {
6388
- encoding: "utf8",
6389
- stdio: ["ignore", "pipe", "ignore"],
6390
- timeout
6391
- })
6392
- };
6393
- } catch {
6394
- return { ok: false, stdout: "" };
6395
- }
6435
+ const result = spawnPlatformSync(command2, args, {
6436
+ encoding: "utf8",
6437
+ stdio: ["ignore", "pipe", "ignore"],
6438
+ timeout
6439
+ });
6440
+ return result.error || result.status !== 0 ? { ok: false, stdout: "" } : { ok: true, stdout: result.stdout };
6396
6441
  }
6397
6442
  function isLoopbackHost(host) {
6398
6443
  return ["127.0.0.1", "localhost", "::1"].includes(host.trim().toLowerCase());
@@ -6483,16 +6528,16 @@ function inspectGrokProject(root, homeDir, grokVersion) {
6483
6528
  });
6484
6529
  if (!grokVersion) return empty("unavailable");
6485
6530
  try {
6486
- const value = JSON.parse(
6487
- execFileSync5("grok", ["inspect", "--json"], {
6488
- cwd: root,
6489
- encoding: "utf8",
6490
- env: { ...process.env, HOME: homeDir, GROK_HOME: join28(homeDir, ".grok") },
6491
- maxBuffer: 10 * 1024 * 1024,
6492
- stdio: ["ignore", "pipe", "ignore"],
6493
- timeout: 8e3
6494
- })
6495
- );
6531
+ const result = spawnPlatformSync("grok", ["inspect", "--json"], {
6532
+ cwd: root,
6533
+ encoding: "utf8",
6534
+ env: { ...process.env, HOME: homeDir, GROK_HOME: join28(homeDir, ".grok") },
6535
+ maxBuffer: 10 * 1024 * 1024,
6536
+ stdio: ["ignore", "pipe", "ignore"],
6537
+ timeout: 8e3
6538
+ });
6539
+ if (result.error || result.status !== 0) return empty("failed");
6540
+ const value = JSON.parse(result.stdout);
6496
6541
  if (!isRecord3(value)) return empty("failed");
6497
6542
  const userClaudeNames = new Set(
6498
6543
  jsonObjectKeys(join28(homeDir, MCP_DISCOVERY_PATHS.user.claudeCode), "mcpServers")
@@ -6580,6 +6625,7 @@ function inferGrokMcpScope(name, sourceType, sourcePath, root, homeDir, userClau
6580
6625
  // src/portfolio/ai-health/secrets.ts
6581
6626
  import { existsSync as existsSync29, lstatSync as lstatSync12, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
6582
6627
  import { join as join29, relative as relative11, sep as sep4 } from "node:path";
6628
+ var supportsPosixModes = process.platform !== "win32";
6583
6629
  function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environmentPolicy) {
6584
6630
  const centralPath = join29(secretsRoot, id);
6585
6631
  const centralRealPath = safeRealpath(centralPath);
@@ -6601,7 +6647,7 @@ function inspectRepositorySecrets(root, id, secretsRoot, isRepository, environme
6601
6647
  });
6602
6648
  return {
6603
6649
  centralDirectory: existsSync29(centralPath) ? "present" : "absent",
6604
- centralMode: existsSync29(centralPath) ? modeString(statSync9(centralPath).mode) : void 0,
6650
+ centralMode: supportsPosixModes && existsSync29(centralPath) ? modeString(statSync9(centralPath).mode) : void 0,
6605
6651
  centralFiles: existsSync29(centralPath) ? collectCentralSecretFiles(centralPath) : [],
6606
6652
  repositoryEnvFiles: envFiles
6607
6653
  };
@@ -6630,7 +6676,7 @@ function collectEnvironmentFiles(root, current = root, depth = 0) {
6630
6676
  if (!SKIP_ENV_DIRECTORIES.has(entry.name))
6631
6677
  found.push(...collectEnvironmentFiles(root, join29(current, entry.name), depth + 1));
6632
6678
  } else if (isEnvironmentFilename(entry.name) && !isProviderGeneratedEnvironmentFile(entry.name)) {
6633
- found.push(relative11(root, join29(current, entry.name)));
6679
+ found.push(relative11(root, join29(current, entry.name)).replaceAll("\\", "/"));
6634
6680
  }
6635
6681
  }
6636
6682
  } catch {
@@ -6645,7 +6691,11 @@ function collectCentralSecretFiles(root, current = root, depth = 0) {
6645
6691
  for (const entry of readdirSync12(current, { withFileTypes: true })) {
6646
6692
  const path = join29(current, entry.name);
6647
6693
  if (entry.isDirectory()) found.push(...collectCentralSecretFiles(root, path, depth + 1));
6648
- else found.push({ path: relative11(root, path), mode: modeString(lstatSync12(path).mode) });
6694
+ else
6695
+ found.push({
6696
+ path: relative11(root, path).replaceAll("\\", "/"),
6697
+ mode: supportsPosixModes ? modeString(lstatSync12(path).mode) : "unknown"
6698
+ });
6649
6699
  }
6650
6700
  } catch {
6651
6701
  return found;
@@ -6676,10 +6726,15 @@ function pointsInside(path, expectedRoot) {
6676
6726
  return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep4}`);
6677
6727
  }
6678
6728
  function hasUnsafeCentralSecretPermissions(secrets) {
6729
+ if (!supportsPosixModes) return false;
6679
6730
  return secrets.centralDirectory === "present" && (secrets.centralMode !== "700" || secrets.centralFiles.some((file) => file.mode !== "600"));
6680
6731
  }
6681
6732
  function inspectSecretsRoot(path) {
6682
- return existsSync29(path) ? { path, exists: true, mode: modeString(statSync9(path).mode) } : { path, exists: false };
6733
+ return existsSync29(path) ? {
6734
+ path,
6735
+ exists: true,
6736
+ ...supportsPosixModes ? { mode: modeString(statSync9(path).mode) } : {}
6737
+ } : { path, exists: false };
6683
6738
  }
6684
6739
 
6685
6740
  // src/portfolio/ai-health/skills.ts
@@ -7918,9 +7973,10 @@ function findPortfolioAgentAssetsDir(manifest) {
7918
7973
  }
7919
7974
  function reportMissingPortfolioRegistry(loaded, json) {
7920
7975
  const expectedPath = loaded.manifest?.executionEngine?.path ? join34(loaded.manifest.executionEngine.path, "agent-assets/registry.json") : "executionEngine.path/agent-assets/registry.json";
7976
+ const displayExpectedPath = expectedPath.replaceAll("\\", "/");
7921
7977
  const issue = {
7922
7978
  type: "missing-control-plane-registry",
7923
- message: `Portfolio control-plane registry is required at ${expectedPath}; refusing the package fallback registry.`
7979
+ message: `Portfolio control-plane registry is required at ${displayExpectedPath}; refusing the package fallback registry.`
7924
7980
  };
7925
7981
  if (json) {
7926
7982
  console.log(
@@ -8015,7 +8071,7 @@ function runSync(args) {
8015
8071
  continue;
8016
8072
  }
8017
8073
  if (file.kind === "symlink") {
8018
- if (!stat.isSymbolicLink() || readlinkSync4(targetPath) !== file.linkTarget) {
8074
+ if (!stat.isSymbolicLink() || normalizeSymlinkTarget(readlinkSync4(targetPath)) !== file.linkTarget) {
8019
8075
  console.log(`different: ${file.targetPath}`);
8020
8076
  differences += 1;
8021
8077
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pieai/pro-gov",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "Project-level distribution kit for Project Governance System.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@pieai/doc-gov": "^0.9.1"
38
+ "@pieai/doc-gov": "^0.9.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@pieai/swimmer-ui-kit": "1.3.2",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "scripts": {
55
55
  "dev": "tsx src/cli.ts",
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",
56
+ "build": "node scripts/build.mjs",
57
57
  "pretest": "pnpm --filter @pieai/doc-gov build && pnpm build",
58
58
  "test": "tsx --test src/*.test.ts src/**/*.test.ts",
59
59
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.dashboard.json --noEmit"