@pieai/doc-gov 0.9.6 → 0.9.8

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.
Files changed (3) hide show
  1. package/cli-guide.md +3 -1
  2. package/dist/cli.js +219 -116
  3. package/package.json +1 -1
package/cli-guide.md CHANGED
@@ -62,7 +62,9 @@ pnpm doc-gov migrate --profile doc-only --check
62
62
  - It also checks that local paths written in backticks inside router-facing
63
63
  files such as `AGENTS.md`, `CLAUDE.md`, `README.md`, and the starter
64
64
  `AGENTS.template.md` can actually be opened, which catches stale startup
65
- instructions early.
65
+ instructions early. An exact PGS-managed shared-rule symlink remains valid
66
+ when its private sibling checkout is unavailable in standalone CI; ordinary
67
+ dangling or noncanonical links still fail.
66
68
 
67
69
  It does not choose a workflow for a specific task.
68
70
 
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/approve.ts
4
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
5
- import { join as join5 } from "node:path";
4
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
5
+ import { join as join6 } from "node:path";
6
6
 
7
7
  // src/core/frontmatter.ts
8
8
  import { readFileSync } from "node:fs";
@@ -238,6 +238,95 @@ function toRepoPath2(rootDir, absolutePath) {
238
238
  return relative2(rootDir, absolutePath).split(/\\/g).join("/");
239
239
  }
240
240
 
241
+ // src/core/managed-shared-rules.ts
242
+ import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync3, readlinkSync } from "node:fs";
243
+ import { join as join3, relative as relative3, resolve as resolve2 } from "node:path";
244
+
245
+ // src/core/symlinks.ts
246
+ function normalizeSymlinkTarget(target) {
247
+ return target.replaceAll("\\", "/");
248
+ }
249
+
250
+ // src/core/managed-shared-rules.ts
251
+ var SHARED_RULE_PATH_PREFIX = "docs/policy/shared-rules/";
252
+ var PGS_SHARED_RULE_TARGET_PATTERN = /^(?:\.\.\/)+ProjectGovernanceSystem\/agent-assets\/rules\/pie-rules\/([^/]+\.md)$/;
253
+ function isUnresolvedManagedSharedRuleSymlink(rootDir, candidatePath) {
254
+ const repoPath = toRepoPath3(rootDir, candidatePath);
255
+ const filename = sharedRuleFilename(repoPath);
256
+ if (!filename) return false;
257
+ let stats;
258
+ try {
259
+ stats = lstatSync2(candidatePath);
260
+ } catch {
261
+ return false;
262
+ }
263
+ if (!stats.isSymbolicLink() || existsSync2(candidatePath)) return false;
264
+ try {
265
+ const target = normalizeSymlinkTarget(readlinkSync(candidatePath));
266
+ const match = target.match(PGS_SHARED_RULE_TARGET_PATTERN);
267
+ return match?.[1] === filename;
268
+ } catch {
269
+ return false;
270
+ }
271
+ }
272
+ function findUnavailableManagedSharedRuleEntries(rootDir) {
273
+ const manifestEntries = readManifestEntries(rootDir);
274
+ return manifestEntries.filter(
275
+ (entry) => entry.type === "policy" && isUnresolvedManagedSharedRuleSymlink(rootDir, join3(rootDir, entry.path))
276
+ );
277
+ }
278
+ function sharedRuleFilename(repoPath) {
279
+ if (!repoPath.startsWith(SHARED_RULE_PATH_PREFIX)) return void 0;
280
+ const filename = repoPath.slice(SHARED_RULE_PATH_PREFIX.length);
281
+ if (!filename || filename.includes("/") || !filename.endsWith(".md")) return void 0;
282
+ return filename;
283
+ }
284
+ function readManifestEntries(rootDir) {
285
+ const manifestPath = join3(rootDir, "docs/governance/MANIFEST.yml");
286
+ if (!existsSync2(manifestPath)) return [];
287
+ try {
288
+ const lines = readFileSync3(manifestPath, "utf8").split("\n");
289
+ const entries = [];
290
+ let current;
291
+ const flush = () => {
292
+ if (current && typeof current.id === "string" && typeof current.path === "string" && typeof current.type === "string" && typeof current.status === "string" && typeof current.canonical === "boolean" && typeof current.lastReviewed === "string" && typeof current.pinned === "boolean") {
293
+ entries.push(current);
294
+ }
295
+ };
296
+ for (const line of lines) {
297
+ const idMatch = line.match(/^\s+- id:\s*(.+?)\s*$/);
298
+ if (idMatch) {
299
+ flush();
300
+ current = { id: cleanValue(idMatch[1] ?? "") };
301
+ continue;
302
+ }
303
+ if (!current) continue;
304
+ const fieldMatch = line.match(
305
+ /^\s+(path|type|status|canonical|last_reviewed|pinned):\s*(.*?)\s*$/
306
+ );
307
+ if (!fieldMatch) continue;
308
+ const field = fieldMatch[1];
309
+ const value = cleanValue(fieldMatch[2] ?? "");
310
+ if (field === "path") current.path = value;
311
+ if (field === "type") current.type = value;
312
+ if (field === "status") current.status = value;
313
+ if (field === "canonical") current.canonical = value === "true";
314
+ if (field === "last_reviewed") current.lastReviewed = value;
315
+ if (field === "pinned") current.pinned = value === "true";
316
+ }
317
+ flush();
318
+ return entries;
319
+ } catch {
320
+ return [];
321
+ }
322
+ }
323
+ function cleanValue(value) {
324
+ return value.replace(/^(['"])(.*)\1$/, "$2");
325
+ }
326
+ function toRepoPath3(rootDir, candidatePath) {
327
+ return relative3(resolve2(rootDir), resolve2(candidatePath)).split(/\\/g).join("/");
328
+ }
329
+
241
330
  // src/core/lifecycle.ts
242
331
  var normalStatuses = [
243
332
  "draft",
@@ -405,6 +494,24 @@ function checkDocs(rootDir = process.cwd()) {
405
494
  issues.push(...result.issues);
406
495
  if (result.record) records.push(result.record);
407
496
  }
497
+ for (const entry of findUnavailableManagedSharedRuleEntries(rootDir)) {
498
+ records.push({
499
+ id: entry.id,
500
+ title: entry.id,
501
+ type: entry.type,
502
+ status: entry.status,
503
+ canonical: entry.canonical,
504
+ owner: "external-shared-rule",
505
+ created: entry.lastReviewed,
506
+ lastReviewed: entry.lastReviewed,
507
+ domain: "shared-rule",
508
+ tags: ["shared-rule"],
509
+ pinned: entry.pinned,
510
+ related: [],
511
+ supersedes: [],
512
+ path: entry.path
513
+ });
514
+ }
408
515
  issues.push(...validateGlobalIntegrity(records));
409
516
  return {
410
517
  ok: issues.length === 0,
@@ -510,8 +617,8 @@ function pathToRepo(rootDir, path) {
510
617
  }
511
618
 
512
619
  // src/core/paths.ts
513
- import { existsSync as existsSync2 } from "node:fs";
514
- import { basename, join as join3 } from "node:path";
620
+ import { existsSync as existsSync3 } from "node:fs";
621
+ import { basename, join as join4 } from "node:path";
515
622
  function planPath(rootDir, type, slugInput) {
516
623
  const cleanSlug = slugInput.replace(/^\/+|\/+$/g, "");
517
624
  if (!cleanSlug) throw new Error("Slug is required.");
@@ -580,8 +687,8 @@ function planPath(rootDir, type, slugInput) {
580
687
  throw new Error(`Unknown type: ${type}`);
581
688
  }
582
689
  function nextSerial(rootDir, scanDir, regex) {
583
- const root = join3(rootDir, scanDir);
584
- if (!existsSync2(root)) return "0001";
690
+ const root = join4(rootDir, scanDir);
691
+ if (!existsSync3(root)) return "0001";
585
692
  let max = 0;
586
693
  walkSerial(rootDir, scanDir, regex, (n) => {
587
694
  if (n > max) max = n;
@@ -610,8 +717,8 @@ function todayIso() {
610
717
  }
611
718
 
612
719
  // src/core/manifest.ts
613
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
614
- import { dirname as dirname2, join as join4 } from "node:path";
720
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
721
+ import { dirname as dirname2, join as join5 } from "node:path";
615
722
  function buildManifest(rootDir = process.cwd()) {
616
723
  const result = checkDocs(rootDir);
617
724
  if (!result.ok) {
@@ -623,14 +730,14 @@ ${error}`);
623
730
  }
624
731
  function writeManifest(rootDir = process.cwd()) {
625
732
  const manifest = buildManifest(rootDir);
626
- const path = join4(rootDir, "docs/governance/MANIFEST.yml");
733
+ const path = join5(rootDir, "docs/governance/MANIFEST.yml");
627
734
  mkdirSync(dirname2(path), { recursive: true });
628
735
  writeFileSync(path, manifest);
629
736
  }
630
737
  function manifestInSync(rootDir = process.cwd()) {
631
- const path = join4(rootDir, "docs/governance/MANIFEST.yml");
632
- if (!existsSync3(path)) return false;
633
- return normalizeManifest(readFileSync3(path, "utf8")) === normalizeManifest(buildManifest(rootDir));
738
+ const path = join5(rootDir, "docs/governance/MANIFEST.yml");
739
+ if (!existsSync4(path)) return false;
740
+ return normalizeManifest(readFileSync4(path, "utf8")) === normalizeManifest(buildManifest(rootDir));
634
741
  }
635
742
  function normalizeManifest(value) {
636
743
  return value.replace(/^generated_at: .*$/m, "generated_at: <ignored>").replace(/^generator_version: .*$/m, "generator_version: <ignored>");
@@ -659,7 +766,7 @@ function renderManifest(records) {
659
766
  function readPackageVersion() {
660
767
  for (const candidate of ["../package.json", "../../package.json"]) {
661
768
  try {
662
- const packageJson = JSON.parse(readFileSync3(new URL(candidate, import.meta.url), "utf8"));
769
+ const packageJson = JSON.parse(readFileSync4(new URL(candidate, import.meta.url), "utf8"));
663
770
  if (typeof packageJson.version === "string") return packageJson.version;
664
771
  } catch {
665
772
  }
@@ -695,8 +802,8 @@ function runApprove(args2) {
695
802
  );
696
803
  return 1;
697
804
  }
698
- const filePath = join5(root, record.path);
699
- const content = readFileSync4(filePath, "utf8");
805
+ const filePath = join6(root, record.path);
806
+ const content = readFileSync5(filePath, "utf8");
700
807
  let next = content;
701
808
  next = updateFrontmatterField(next, "status", toStatus);
702
809
  next = updateFrontmatterField(next, "canonical", "true");
@@ -742,8 +849,8 @@ ${newLines.join("\n")}${tail}`;
742
849
 
743
850
  // src/commands/archive.ts
744
851
  import { execFileSync } from "node:child_process";
745
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
746
- import { basename as basename2, dirname as dirname3, join as join6 } from "node:path";
852
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
853
+ import { basename as basename2, dirname as dirname3, join as join7 } from "node:path";
747
854
  function runArchive(args2) {
748
855
  const positional = args2.filter((a) => !a.startsWith("--"));
749
856
  const id = positional[0];
@@ -782,9 +889,9 @@ function runArchive(args2) {
782
889
  const fileName = basename2(oldPath);
783
890
  const archiveDir = `docs/archive/${quarterTag()}-${record.type}`;
784
891
  const newPath = `${archiveDir}/${fileName}`;
785
- const absNew = join6(root, newPath);
786
- const absOld = join6(root, oldPath);
787
- let content = readFileSync5(absOld, "utf8");
892
+ const absNew = join7(root, newPath);
893
+ const absOld = join7(root, oldPath);
894
+ let content = readFileSync6(absOld, "utf8");
788
895
  content = updateFrontmatterField(content, "type", "archive");
789
896
  content = updateFrontmatterField(content, "status", "archived");
790
897
  content = updateFrontmatterField(content, "canonical", "false");
@@ -813,12 +920,12 @@ function runArchive(args2) {
813
920
  }
814
921
 
815
922
  // src/commands/audit.ts
816
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
817
- import { join as join7 } from "node:path";
923
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
924
+ import { join as join8 } from "node:path";
818
925
 
819
926
  // src/core/link-checker.ts
820
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
821
- import { dirname as dirname4, extname, resolve as resolve2 } from "node:path";
927
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
928
+ import { dirname as dirname4, extname, resolve as resolve3 } from "node:path";
822
929
  var CURRENT_MARKDOWN_ROOTS = ["AGENTS.md", "README.md", "docs"];
823
930
  var CURRENT_DOC_DIR_PREFIXES = [
824
931
  "docs/canon/",
@@ -898,12 +1005,12 @@ function shouldIgnoreTarget(target) {
898
1005
  function localTargetExists(rootDir, sourcePath, target) {
899
1006
  const pathPart = decodeTarget(target).split("#")[0]?.split("?")[0] ?? "";
900
1007
  if (!pathPart) return true;
901
- const resolved = pathPart.startsWith("/") ? resolve2(rootDir, `.${pathPart}`) : resolve2(dirname4(sourcePath), pathPart);
1008
+ const resolved = pathPart.startsWith("/") ? resolve3(rootDir, `.${pathPart}`) : resolve3(dirname4(sourcePath), pathPart);
902
1009
  const candidates = [resolved];
903
1010
  if (!extname(resolved)) {
904
1011
  candidates.push(`${resolved}.md`);
905
1012
  }
906
- return candidates.some((candidate) => existsSync4(candidate));
1013
+ return candidates.some((candidate) => existsSync5(candidate));
907
1014
  }
908
1015
  function decodeTarget(target) {
909
1016
  try {
@@ -920,7 +1027,7 @@ function lineNumberAt(content, index) {
920
1027
  return line;
921
1028
  }
922
1029
  function readText(filePath) {
923
- return readFileSync6(filePath, "utf8");
1030
+ return readFileSync7(filePath, "utf8");
924
1031
  }
925
1032
 
926
1033
  // src/commands/audit.ts
@@ -934,15 +1041,15 @@ function runAudit() {
934
1041
  }
935
1042
  return 1;
936
1043
  }
937
- const migrationSource = join7(root, "Docs-for trans");
938
- if (existsSync5(migrationSource)) {
1044
+ const migrationSource = join8(root, "Docs-for trans");
1045
+ if (existsSync6(migrationSource)) {
939
1046
  const count = countFiles(migrationSource);
940
1047
  warnings += 1;
941
1048
  console.log(
942
1049
  `Migration source still exists: Docs-for trans (${count} files). This was a one-time migration shell; it should not be reintroduced.`
943
1050
  );
944
1051
  }
945
- if (existsSync5(join7(root, "DocSystemStarter.md"))) {
1052
+ if (existsSync6(join8(root, "DocSystemStarter.md"))) {
946
1053
  warnings += 1;
947
1054
  console.log(
948
1055
  "Stray root-level DocSystemStarter.md exists. The original draft has been archived; remove or re-archive."
@@ -968,7 +1075,7 @@ function runAudit() {
968
1075
  function countFiles(dir) {
969
1076
  let count = 0;
970
1077
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
971
- const path = join7(dir, entry.name);
1078
+ const path = join8(dir, entry.name);
972
1079
  if (entry.isDirectory()) count += countFiles(path);
973
1080
  if (entry.isFile() || entry.isSymbolicLink()) count += 1;
974
1081
  }
@@ -990,19 +1097,12 @@ function runCheck() {
990
1097
 
991
1098
  // src/commands/doctor.ts
992
1099
  import { spawnSync } from "node:child_process";
993
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
994
- import { isAbsolute as isAbsolute2, join as join9, resolve as resolve3 } from "node:path";
995
-
996
- // src/core/router-integrity.ts
997
- import { existsSync as existsSync6, lstatSync as lstatSync2, readlinkSync, readFileSync as readFileSync7 } from "node:fs";
998
- import { join as join8, relative as relative3 } from "node:path";
999
-
1000
- // src/core/symlinks.ts
1001
- function normalizeSymlinkTarget(target) {
1002
- return target.replaceAll("\\", "/");
1003
- }
1100
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1101
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve4 } from "node:path";
1004
1102
 
1005
1103
  // src/core/router-integrity.ts
1104
+ import { existsSync as existsSync7, lstatSync as lstatSync3, readlinkSync as readlinkSync2, readFileSync as readFileSync8 } from "node:fs";
1105
+ import { join as join9, relative as relative4 } from "node:path";
1006
1106
  var CENTRAL_REQUIRED_FILES = [
1007
1107
  "AGENTS.md",
1008
1108
  "CLAUDE.md",
@@ -1320,7 +1420,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1320
1420
  const requiredFiles = isCentral ? CENTRAL_REQUIRED_FILES : PROJECT_REQUIRED_FILES;
1321
1421
  const requiredNeedles = isCentral ? CENTRAL_REQUIRED_NEEDLES : PROJECT_REQUIRED_NEEDLES;
1322
1422
  for (const file of requiredFiles) {
1323
- if (!existsSync6(join8(rootDir, file))) {
1423
+ if (!existsSync7(join9(rootDir, file))) {
1324
1424
  issues.push({
1325
1425
  file,
1326
1426
  code: "missing-router-file",
@@ -1333,7 +1433,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1333
1433
  }
1334
1434
  issues.push(...validateHostSsot(rootDir));
1335
1435
  for (const file of FORBIDDEN_LEGACY_PATHS) {
1336
- if (existsSync6(join8(rootDir, file))) {
1436
+ if (existsSync7(join9(rootDir, file))) {
1337
1437
  issues.push({
1338
1438
  file,
1339
1439
  code: "legacy-governance-path",
@@ -1343,7 +1443,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1343
1443
  }
1344
1444
  if (!isCentral) {
1345
1445
  for (const file of FORBIDDEN_PROJECT_PATHS) {
1346
- if (existsSync6(join8(rootDir, file))) {
1446
+ if (existsSync7(join9(rootDir, file))) {
1347
1447
  issues.push({
1348
1448
  file,
1349
1449
  code: "legacy-project-policy-path",
@@ -1352,7 +1452,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1352
1452
  }
1353
1453
  }
1354
1454
  for (const file of SUPERSEDED_PROJECT_CONTRACT_PATHS) {
1355
- if (existsSync6(join8(rootDir, file))) {
1455
+ if (existsSync7(join9(rootDir, file))) {
1356
1456
  issues.push({
1357
1457
  file,
1358
1458
  code: "superseded-project-contract",
@@ -1361,7 +1461,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1361
1461
  }
1362
1462
  }
1363
1463
  for (const file of FORBIDDEN_PROJECT_ROOTS) {
1364
- if (existsSync6(join8(rootDir, file))) {
1464
+ if (existsSync7(join9(rootDir, file))) {
1365
1465
  issues.push({
1366
1466
  file,
1367
1467
  code: "project-root-integration-path",
@@ -1371,7 +1471,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1371
1471
  }
1372
1472
  } else {
1373
1473
  for (const file of FORBIDDEN_CENTRAL_ROOTS) {
1374
- if (existsSync6(join8(rootDir, file))) {
1474
+ if (existsSync7(join9(rootDir, file))) {
1375
1475
  issues.push({
1376
1476
  file,
1377
1477
  code: "central-external-shared-rule-copy",
@@ -1388,9 +1488,9 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1388
1488
  });
1389
1489
  }
1390
1490
  for (const requirement of requiredNeedles) {
1391
- const path = join8(rootDir, requirement.file);
1392
- if (!existsSync6(path)) continue;
1393
- const content = readFileSync7(path, "utf8");
1491
+ const path = join9(rootDir, requirement.file);
1492
+ if (!existsSync7(path)) continue;
1493
+ const content = readFileSync8(path, "utf8");
1394
1494
  if (!content.includes(requirement.needle)) {
1395
1495
  issues.push({
1396
1496
  file: requirement.file,
@@ -1436,7 +1536,7 @@ function validateHostSsot(rootDir) {
1436
1536
  validateExactSymlink(".claude/skills", "../.agents/skills");
1437
1537
  return issues;
1438
1538
  function validateRegularFile(file) {
1439
- const stat = safeLstat2(join8(rootDir, file));
1539
+ const stat = safeLstat2(join9(rootDir, file));
1440
1540
  if (!stat || stat.isFile()) return;
1441
1541
  issues.push({
1442
1542
  file,
@@ -1445,7 +1545,7 @@ function validateHostSsot(rootDir) {
1445
1545
  });
1446
1546
  }
1447
1547
  function validateDirectory(file) {
1448
- const stat = safeLstat2(join8(rootDir, file));
1548
+ const stat = safeLstat2(join9(rootDir, file));
1449
1549
  if (!stat || stat.isDirectory()) return;
1450
1550
  issues.push({
1451
1551
  file,
@@ -1454,7 +1554,7 @@ function validateHostSsot(rootDir) {
1454
1554
  });
1455
1555
  }
1456
1556
  function validateExactSymlink(file, expectedRawTarget) {
1457
- const path = join8(rootDir, file);
1557
+ const path = join9(rootDir, file);
1458
1558
  const stat = safeLstat2(path);
1459
1559
  if (!stat) return;
1460
1560
  if (!stat.isSymbolicLink()) {
@@ -1465,7 +1565,7 @@ function validateHostSsot(rootDir) {
1465
1565
  });
1466
1566
  return;
1467
1567
  }
1468
- const rawTarget = normalizeSymlinkTarget(readlinkSync(path));
1568
+ const rawTarget = normalizeSymlinkTarget(readlinkSync2(path));
1469
1569
  if (rawTarget !== expectedRawTarget) {
1470
1570
  issues.push({
1471
1571
  file,
@@ -1477,7 +1577,7 @@ function validateHostSsot(rootDir) {
1477
1577
  }
1478
1578
  function safeLstat2(path) {
1479
1579
  try {
1480
- return lstatSync2(path);
1580
+ return lstatSync3(path);
1481
1581
  } catch (error) {
1482
1582
  const code = error.code;
1483
1583
  if (code === "ENOENT" || code === "ENOTDIR") return void 0;
@@ -1485,27 +1585,27 @@ function safeLstat2(path) {
1485
1585
  }
1486
1586
  }
1487
1587
  function isCentralRepository(rootDir) {
1488
- const packageJsonPath = join8(rootDir, "package.json");
1489
- if (!existsSync6(packageJsonPath)) return false;
1588
+ const packageJsonPath = join9(rootDir, "package.json");
1589
+ if (!existsSync7(packageJsonPath)) return false;
1490
1590
  const packageName = readPackageName(packageJsonPath);
1491
1591
  const centralPackageNames = /* @__PURE__ */ new Set(["project-governance-system", "pro-gov"]);
1492
1592
  return centralPackageNames.has(packageName) && hasCentralRepositoryShape(rootDir);
1493
1593
  }
1494
1594
  function readPackageName(packageJsonPath) {
1495
1595
  try {
1496
- const packageJson = JSON.parse(readFileSync7(packageJsonPath, "utf8"));
1596
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
1497
1597
  return typeof packageJson.name === "string" ? packageJson.name : "";
1498
1598
  } catch {
1499
1599
  return "";
1500
1600
  }
1501
1601
  }
1502
1602
  function hasCentralRepositoryShape(rootDir) {
1503
- return existsSync6(join8(rootDir, "profiles")) && existsSync6(join8(rootDir, "starter")) && existsSync6(join8(rootDir, "integrations"));
1603
+ return existsSync7(join9(rootDir, "profiles")) && existsSync7(join9(rootDir, "starter")) && existsSync7(join9(rootDir, "integrations"));
1504
1604
  }
1505
1605
  function validateProjectAgentsRouting(rootDir) {
1506
1606
  const issues = [];
1507
1607
  const existingRoutes = PROJECT_AGENTS_ROUTING_FILES.filter(
1508
- (file) => existsSync6(join8(rootDir, file))
1608
+ (file) => existsSync7(join9(rootDir, file))
1509
1609
  );
1510
1610
  if (existingRoutes.length === 0) {
1511
1611
  issues.push({
@@ -1515,9 +1615,9 @@ function validateProjectAgentsRouting(rootDir) {
1515
1615
  });
1516
1616
  return issues;
1517
1617
  }
1518
- const agentsPath = join8(rootDir, "AGENTS.md");
1519
- if (!existsSync6(agentsPath)) return issues;
1520
- const agents = readFileSync7(agentsPath, "utf8");
1618
+ const agentsPath = join9(rootDir, "AGENTS.md");
1619
+ if (!existsSync7(agentsPath)) return issues;
1620
+ const agents = readFileSync8(agentsPath, "utf8");
1521
1621
  if (!existingRoutes.some((file) => agents.includes(file))) {
1522
1622
  issues.push({
1523
1623
  file: "AGENTS.md",
@@ -1528,9 +1628,9 @@ function validateProjectAgentsRouting(rootDir) {
1528
1628
  return issues;
1529
1629
  }
1530
1630
  function validateRouterBlock(rootDir, file) {
1531
- const path = join8(rootDir, file);
1532
- if (!existsSync6(path)) return [];
1533
- const content = readFileSync7(path, "utf8");
1631
+ const path = join9(rootDir, file);
1632
+ if (!existsSync7(path)) return [];
1633
+ const content = readFileSync8(path, "utf8");
1534
1634
  const begin = content.indexOf(ROUTER_BLOCK_BEGIN);
1535
1635
  const end = content.indexOf(ROUTER_BLOCK_END);
1536
1636
  const issues = [];
@@ -1555,9 +1655,9 @@ function validateRouterBlock(rootDir, file) {
1555
1655
  return issues;
1556
1656
  }
1557
1657
  function validateBacktickedLocalPaths(rootDir, file, pathRoot = "") {
1558
- const path = join8(rootDir, file);
1559
- if (!existsSync6(path)) return [];
1560
- const content = readFileSync7(path, "utf8");
1658
+ const path = join9(rootDir, file);
1659
+ if (!existsSync7(path)) return [];
1660
+ const content = readFileSync8(path, "utf8");
1561
1661
  const issues = [];
1562
1662
  const seen = /* @__PURE__ */ new Set();
1563
1663
  const matches = content.matchAll(/`([^`]+)`/g);
@@ -1567,7 +1667,10 @@ function validateBacktickedLocalPaths(rootDir, file, pathRoot = "") {
1567
1667
  seen.add(value);
1568
1668
  if (!isLocalPathReference(value)) continue;
1569
1669
  const normalized = value.endsWith("/") ? value.slice(0, -1) : value;
1570
- if (existsSync6(join8(rootDir, pathRoot, normalized)) || existsSync6(join8(rootDir, normalized))) {
1670
+ const candidatePaths = [join9(rootDir, pathRoot, normalized), join9(rootDir, normalized)];
1671
+ if (candidatePaths.some(
1672
+ (candidatePath) => existsSync7(candidatePath) || isUnresolvedManagedSharedRuleSymlink(rootDir, candidatePath)
1673
+ )) {
1571
1674
  continue;
1572
1675
  }
1573
1676
  issues.push({
@@ -1586,9 +1689,9 @@ function isLocalPathReference(value) {
1586
1689
  return value === "README.md" || value.endsWith(".md") || value.endsWith("/") || value.startsWith("docs/") || value.startsWith("starter/") || value.startsWith("profiles/") || value.startsWith("integrations/");
1587
1690
  }
1588
1691
  function validatePortableRouterText(rootDir, file) {
1589
- const path = join8(rootDir, file);
1590
- if (!existsSync6(path)) return [];
1591
- const content = readFileSync7(path, "utf8");
1692
+ const path = join9(rootDir, file);
1693
+ if (!existsSync7(path)) return [];
1694
+ const content = readFileSync8(path, "utf8");
1592
1695
  if (!hasNonPortablePath(content)) return [];
1593
1696
  return [
1594
1697
  {
@@ -1610,7 +1713,7 @@ function hasNonPortablePath(content) {
1610
1713
  function findGovernedReadmes(rootDir) {
1611
1714
  return listMarkdownFiles(rootDir, ["docs", "starter/docs"], {
1612
1715
  includeFile: (repoPath) => repoPath.toLowerCase().endsWith("/readme.md")
1613
- }).map((path) => relative3(rootDir, path).split(/\\/g).join("/")).filter((repoPath) => repoPath !== "README.md");
1716
+ }).map((path) => relative4(rootDir, path).split(/\\/g).join("/")).filter((repoPath) => repoPath !== "README.md");
1614
1717
  }
1615
1718
 
1616
1719
  // src/commands/doctor.ts
@@ -1678,8 +1781,8 @@ function collectDoctorIssues(rootDir = process.cwd()) {
1678
1781
  return issues;
1679
1782
  }
1680
1783
  function checkLefthook(rootDir) {
1681
- const path = join9(rootDir, "lefthook.yml");
1682
- if (!existsSync7(path)) {
1784
+ const path = join10(rootDir, "lefthook.yml");
1785
+ if (!existsSync8(path)) {
1683
1786
  return [
1684
1787
  {
1685
1788
  severity: "warning",
@@ -1688,7 +1791,7 @@ function checkLefthook(rootDir) {
1688
1791
  }
1689
1792
  ];
1690
1793
  }
1691
- const content = readFileSync8(path, "utf8");
1794
+ const content = readFileSync9(path, "utf8");
1692
1795
  const issues = [];
1693
1796
  for (const command2 of [
1694
1797
  "pnpm doc-gov router-check",
@@ -1725,8 +1828,8 @@ function checkLefthook(rootDir) {
1725
1828
  return issues;
1726
1829
  }
1727
1830
  function checkDocsCheckWorkflow(rootDir) {
1728
- const path = join9(rootDir, ".github/workflows/docs-check.yml");
1729
- if (!existsSync7(path)) {
1831
+ const path = join10(rootDir, ".github/workflows/docs-check.yml");
1832
+ if (!existsSync8(path)) {
1730
1833
  return [
1731
1834
  {
1732
1835
  severity: "warning",
@@ -1735,7 +1838,7 @@ function checkDocsCheckWorkflow(rootDir) {
1735
1838
  }
1736
1839
  ];
1737
1840
  }
1738
- const content = readFileSync8(path, "utf8");
1841
+ const content = readFileSync9(path, "utf8");
1739
1842
  const issues = [];
1740
1843
  for (const command2 of [
1741
1844
  "pnpm doc-gov router-check",
@@ -1755,15 +1858,15 @@ function checkDocsCheckWorkflow(rootDir) {
1755
1858
  return issues;
1756
1859
  }
1757
1860
  function hookCallsLefthook(path) {
1758
- return existsSync7(path) && readFileSync8(path, "utf8").includes("lefthook");
1861
+ return existsSync8(path) && readFileSync9(path, "utf8").includes("lefthook");
1759
1862
  }
1760
1863
  function resolveGitHookPath(rootDir, hookName) {
1761
1864
  const result = spawnSync("git", ["-C", rootDir, "rev-parse", "--git-path", `hooks/${hookName}`], {
1762
1865
  encoding: "utf8"
1763
1866
  });
1764
1867
  const gitPath = result.status === 0 ? result.stdout.trim() : "";
1765
- if (!gitPath) return join9(rootDir, ".git/hooks", hookName);
1766
- return isAbsolute2(gitPath) ? gitPath : resolve3(rootDir, gitPath);
1868
+ if (!gitPath) return join10(rootDir, ".git/hooks", hookName);
1869
+ return isAbsolute2(gitPath) ? gitPath : resolve4(rootDir, gitPath);
1767
1870
  }
1768
1871
 
1769
1872
  // src/commands/find.ts
@@ -1801,12 +1904,12 @@ function runFind(args2) {
1801
1904
  }
1802
1905
 
1803
1906
  // src/commands/init.ts
1804
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
1805
- import { join as join11 } from "node:path";
1907
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
1908
+ import { join as join12 } from "node:path";
1806
1909
 
1807
1910
  // src/core/templates.ts
1808
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "node:fs";
1809
- import { join as join10 } from "node:path";
1911
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
1912
+ import { join as join11 } from "node:path";
1810
1913
  var TEMPLATE_FILES = {
1811
1914
  decision: "adr.md",
1812
1915
  spec: "spec.md",
@@ -2014,19 +2117,19 @@ var DEFAULT_TEMPLATES = {
2014
2117
  function loadTemplate(rootDir, type) {
2015
2118
  const file = TEMPLATE_FILES[type];
2016
2119
  if (!file) throw new Error(`No template file mapped for type: ${type}`);
2017
- const path = join10(rootDir, "docs/governance/templates", file);
2018
- if (existsSync8(path)) return readFileSync9(path, "utf8");
2120
+ const path = join11(rootDir, "docs/governance/templates", file);
2121
+ if (existsSync9(path)) return readFileSync10(path, "utf8");
2019
2122
  const fallback = DEFAULT_TEMPLATES[file];
2020
2123
  if (fallback) return fallback;
2021
2124
  throw new Error(`Template file is missing: docs/governance/templates/${file}`);
2022
2125
  }
2023
2126
  function ensureDefaultTemplates(rootDir) {
2024
- const templatesDir = join10(rootDir, "docs/governance/templates");
2127
+ const templatesDir = join11(rootDir, "docs/governance/templates");
2025
2128
  mkdirSync3(templatesDir, { recursive: true });
2026
2129
  let created = 0;
2027
2130
  for (const [file, content] of Object.entries(DEFAULT_TEMPLATES)) {
2028
- const path = join10(templatesDir, file);
2029
- if (existsSync8(path)) continue;
2131
+ const path = join11(templatesDir, file);
2132
+ if (existsSync9(path)) continue;
2030
2133
  writeFileSync4(path, content);
2031
2134
  created++;
2032
2135
  }
@@ -2081,16 +2184,16 @@ function runInit(args2) {
2081
2184
  ];
2082
2185
  let created = 0;
2083
2186
  for (const dir of dirs) {
2084
- const abs = join11(root, dir);
2085
- if (!existsSync9(abs)) {
2187
+ const abs = join12(root, dir);
2188
+ if (!existsSync10(abs)) {
2086
2189
  mkdirSync4(abs, { recursive: true });
2087
2190
  created++;
2088
2191
  } else if (!force) {
2089
2192
  }
2090
2193
  }
2091
2194
  for (const dir of ["docs/specs/completed", "docs/plans/completed", "docs/archive"]) {
2092
- const keep = join11(root, dir, ".gitkeep");
2093
- if (!existsSync9(keep)) writeFileSync5(keep, "");
2195
+ const keep = join12(root, dir, ".gitkeep");
2196
+ if (!existsSync10(keep)) writeFileSync5(keep, "");
2094
2197
  }
2095
2198
  const templatesCreated = ensureDefaultTemplates(root);
2096
2199
  console.log(
@@ -2137,8 +2240,8 @@ function runLinks() {
2137
2240
  }
2138
2241
 
2139
2242
  // src/commands/migrate.ts
2140
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
2141
- import { join as join12 } from "node:path";
2243
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2244
+ import { join as join13 } from "node:path";
2142
2245
  var PROFILE_ROUTES = {
2143
2246
  "engineering-runtime": "docs/governance/agents-routing/engineering-runtime-v1.1.md",
2144
2247
  "doc-only": "docs/governance/agents-routing/doc-only-v1.1.md"
@@ -2175,11 +2278,11 @@ function checkMigrationReadiness(rootDir, profile) {
2175
2278
  issues.push(`${issue.file}: ${issue.code}: ${issue.message}`);
2176
2279
  }
2177
2280
  const route = PROFILE_ROUTES[profile];
2178
- if (!existsSync10(join12(rootDir, route))) {
2281
+ if (!existsSync11(join13(rootDir, route))) {
2179
2282
  issues.push(`missing selected profile route: ${route}`);
2180
2283
  }
2181
- const agentsPath = join12(rootDir, "AGENTS.md");
2182
- if (existsSync10(agentsPath) && !readFileSync10(agentsPath, "utf8").includes(route)) {
2284
+ const agentsPath = join13(rootDir, "AGENTS.md");
2285
+ if (existsSync11(agentsPath) && !readFileSync11(agentsPath, "utf8").includes(route)) {
2183
2286
  issues.push(`AGENTS.md must name selected profile route: ${route}`);
2184
2287
  }
2185
2288
  return issues;
@@ -2226,8 +2329,8 @@ function readFlag(args2, name) {
2226
2329
  }
2227
2330
 
2228
2331
  // src/commands/new.ts
2229
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
2230
- import { dirname as dirname5, join as join13 } from "node:path";
2332
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
2333
+ import { dirname as dirname5, join as join14 } from "node:path";
2231
2334
  function runNew(args2) {
2232
2335
  const positional = args2.filter((a) => !a.startsWith("--"));
2233
2336
  const owner = readFlag2(args2, "--owner") ?? "human";
@@ -2253,8 +2356,8 @@ function runNew(args2) {
2253
2356
  console.error(err.message);
2254
2357
  return 1;
2255
2358
  }
2256
- const absPath = join13(root, plan.filePath);
2257
- if (existsSync11(absPath) && !force) {
2359
+ const absPath = join14(root, plan.filePath);
2360
+ if (existsSync12(absPath) && !force) {
2258
2361
  console.error(`File already exists: ${plan.filePath}. Use --force to overwrite.`);
2259
2362
  return 1;
2260
2363
  }
@@ -2337,8 +2440,8 @@ function runScan(args2) {
2337
2440
  }
2338
2441
 
2339
2442
  // src/commands/supersede.ts
2340
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "node:fs";
2341
- import { join as join14 } from "node:path";
2443
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
2444
+ import { join as join15 } from "node:path";
2342
2445
  function runSupersede(args2) {
2343
2446
  const [oldId, newId] = args2;
2344
2447
  if (!oldId || !newId) {
@@ -2374,15 +2477,15 @@ function runSupersede(args2) {
2374
2477
  return 1;
2375
2478
  }
2376
2479
  const today = todayIso();
2377
- const oldPath = join14(root, oldRec.path);
2378
- let oldContent = readFileSync11(oldPath, "utf8");
2480
+ const oldPath = join15(root, oldRec.path);
2481
+ let oldContent = readFileSync12(oldPath, "utf8");
2379
2482
  oldContent = updateFrontmatterField(oldContent, "status", "superseded");
2380
2483
  oldContent = updateFrontmatterField(oldContent, "canonical", "false");
2381
2484
  oldContent = updateFrontmatterField(oldContent, "superseded_by", newId);
2382
2485
  oldContent = updateFrontmatterField(oldContent, "last_reviewed", today);
2383
2486
  writeFileSync7(oldPath, oldContent);
2384
- const newPath = join14(root, newRec.path);
2385
- let newContent = readFileSync11(newPath, "utf8");
2487
+ const newPath = join15(root, newRec.path);
2488
+ let newContent = readFileSync12(newPath, "utf8");
2386
2489
  newContent = appendToFrontmatterList(newContent, "supersedes", newId === oldId ? "" : oldId);
2387
2490
  newContent = updateFrontmatterField(newContent, "last_reviewed", today);
2388
2491
  writeFileSync7(newPath, newContent);
@@ -2431,13 +2534,13 @@ ${lines.join("\n")}${tail}`;
2431
2534
 
2432
2535
  // src/commands/verify-commit-msg.ts
2433
2536
  import { execFileSync as execFileSync2 } from "node:child_process";
2434
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "node:fs";
2537
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
2435
2538
  function runVerifyCommitMsg(args2) {
2436
2539
  const msgFile = args2[0];
2437
- if (!msgFile || !existsSync12(msgFile)) {
2540
+ if (!msgFile || !existsSync13(msgFile)) {
2438
2541
  return 0;
2439
2542
  }
2440
- const message = readFileSync12(msgFile, "utf8");
2543
+ const message = readFileSync13(msgFile, "utf8");
2441
2544
  if (/^Merge\b/m.test(message) || /^Revert\b/m.test(message)) return 0;
2442
2545
  let stagedFiles = [];
2443
2546
  try {
@@ -2449,7 +2552,7 @@ function runVerifyCommitMsg(args2) {
2449
2552
  }
2450
2553
  const failures = [];
2451
2554
  for (const file of stagedFiles) {
2452
- if (!existsSync12(file)) continue;
2555
+ if (!existsSync13(file)) continue;
2453
2556
  const fm = readFrontmatterFile(file);
2454
2557
  if (!fm) continue;
2455
2558
  const id = stringValue(fm.data.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pieai/doc-gov",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "AI-native documentation governance CLI for project docs, agent routing, and lifecycle checks.",
5
5
  "keywords": [
6
6
  "ai-agents",