@pieai/doc-gov 0.9.5 → 0.9.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.
Files changed (3) hide show
  1. package/cli-guide.md +3 -1
  2. package/dist/cli.js +387 -198
  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 readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
5
- import { join as join4 } 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";
@@ -65,8 +65,7 @@ function parseScalar(value) {
65
65
  }
66
66
 
67
67
  // src/core/files.ts
68
- import { existsSync, readdirSync, statSync } from "node:fs";
69
- import { join, relative } from "node:path";
68
+ import { join as join2, relative as relative2 } from "node:path";
70
69
 
71
70
  // src/core/external-artifacts.ts
72
71
  var EXTERNAL_ARTIFACT_PREFIXES = ["docs/brainstorms/", "docs/pulse-reports/"];
@@ -80,35 +79,251 @@ function isExternalArtifactPath(repoPath) {
80
79
  return false;
81
80
  }
82
81
 
83
- // src/core/files.ts
84
- function listGovernedMarkdownFiles(rootDir) {
85
- const roots = [join(rootDir, "docs")];
82
+ // src/core/markdown-traversal.ts
83
+ import {
84
+ existsSync,
85
+ lstatSync,
86
+ readFileSync as readFileSync2,
87
+ readdirSync,
88
+ realpathSync,
89
+ statSync
90
+ } from "node:fs";
91
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
92
+ function listMarkdownFiles(rootDir, roots, options = {}) {
93
+ const resolvedRootDir = resolve(rootDir);
94
+ const allowedRoots = canonicalizeRoots(resolvedRootDir, options.allowedRoots ?? []);
86
95
  const files = [];
96
+ const visitedDirectories = /* @__PURE__ */ new Set();
87
97
  for (const root of roots) {
88
- if (!existsSync(root)) continue;
89
- files.push(...walk(rootDir, root));
98
+ walkEntry(resolveFromRoot(resolvedRootDir, root));
99
+ }
100
+ return Array.from(new Set(files)).sort();
101
+ function walkEntry(path) {
102
+ const repoPath = toRepoPath(resolvedRootDir, path);
103
+ if (options.shouldSkipTree?.(repoPath)) return;
104
+ const stat = safeLstat(path);
105
+ if (!stat) return;
106
+ if (stat.isDirectory()) {
107
+ walkDirectory(path);
108
+ return;
109
+ }
110
+ if (!stat.isFile() && !stat.isSymbolicLink()) return;
111
+ addMarkdownFile(path, repoPath, stat);
112
+ }
113
+ function walkDirectory(dir) {
114
+ const realDirectory = safeRealpath(dir);
115
+ if (!realDirectory || visitedDirectories.has(realDirectory)) return;
116
+ visitedDirectories.add(realDirectory);
117
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
118
+ const path = join(dir, entry.name);
119
+ const repoPath = toRepoPath(resolvedRootDir, path);
120
+ if (options.shouldSkipTree?.(repoPath)) continue;
121
+ if (entry.isDirectory()) {
122
+ walkDirectory(path);
123
+ continue;
124
+ }
125
+ if (entry.isFile() || entry.isSymbolicLink()) {
126
+ addMarkdownFile(path, repoPath, entry);
127
+ }
128
+ }
90
129
  }
91
- return files.sort();
130
+ function addMarkdownFile(path, repoPath, entry) {
131
+ if (!path.endsWith(".md")) return;
132
+ if (options.includeFile && !options.includeFile(repoPath)) return;
133
+ if (!isAllowedMarkdownFile(resolvedRootDir, path, allowedRoots, repoPath, entry)) return;
134
+ files.push(path);
135
+ }
136
+ }
137
+ function canonicalizeRoots(rootDir, roots) {
138
+ return Array.from(
139
+ new Set(
140
+ [rootDir, ...roots].map((root) => {
141
+ const resolved = resolveFromRoot(rootDir, root);
142
+ return safeRealpath(resolved) ?? resolved;
143
+ }).filter(Boolean)
144
+ )
145
+ );
92
146
  }
93
- function walk(rootDir, dir) {
94
- const entries = readdirSync(dir, { withFileTypes: true });
95
- const files = [];
96
- for (const entry of entries) {
97
- const path = join(dir, entry.name);
98
- const rel = toRepoPath(rootDir, path);
99
- if (shouldSkip(rel)) continue;
100
- if (entry.isSymbolicLink()) continue;
101
- if (entry.isDirectory()) files.push(...walk(rootDir, path));
102
- if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
147
+ function isAllowedMarkdownFile(rootDir, path, allowedRoots, repoPath, entry) {
148
+ const targetPath = safeRealpath(path);
149
+ if (!targetPath) return false;
150
+ const targetStat = safeStat(targetPath);
151
+ if (!targetStat?.isFile()) return false;
152
+ if (allowedRoots.some((root) => isPathWithin(targetPath, root))) return true;
153
+ return entry.isSymbolicLink() && isTrustedPgsSharedRuleTarget(rootDir, repoPath, targetPath);
154
+ }
155
+ function isTrustedPgsSharedRuleTarget(rootDir, repoPath, targetPath) {
156
+ if (!isSharedRulePath(repoPath)) return false;
157
+ const centralRoot = findCentralRepositoryRoot(dirname(targetPath));
158
+ if (!centralRoot) return false;
159
+ const sourceRoot = safeRealpath(join(centralRoot, "agent-assets/rules/pie-rules"));
160
+ return Boolean(sourceRoot && isPathWithin(targetPath, sourceRoot));
161
+ }
162
+ function isSharedRulePath(repoPath) {
163
+ return repoPath.startsWith("docs/policy/shared-rules/") && repoPath.endsWith(".md");
164
+ }
165
+ function findCentralRepositoryRoot(startDir) {
166
+ let current = startDir;
167
+ while (true) {
168
+ if (isCentralRepositoryRoot(current)) return safeRealpath(current) ?? current;
169
+ const parent = dirname(current);
170
+ if (parent === current) return void 0;
171
+ current = parent;
172
+ }
173
+ }
174
+ function isCentralRepositoryRoot(path) {
175
+ const packageJsonPath = join(path, "package.json");
176
+ if (!existsSync(packageJsonPath)) return false;
177
+ try {
178
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
179
+ if (packageJson.name !== "project-governance-system" && packageJson.name !== "pro-gov") {
180
+ return false;
181
+ }
182
+ } catch {
183
+ return false;
103
184
  }
104
- return files;
185
+ return existsSync(join(path, "profiles")) && existsSync(join(path, "starter")) && existsSync(join(path, "integrations"));
105
186
  }
106
- function shouldSkip(repoPath) {
107
- return isExternalArtifactPath(repoPath) || repoPath.startsWith("docs/governance/templates/") || repoPath === "docs/governance/MANIFEST.yml";
187
+ function isPathWithin(candidate, root) {
188
+ const relation = relative(root, candidate);
189
+ return relation === "" || !relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation);
190
+ }
191
+ function resolveFromRoot(rootDir, path) {
192
+ if (isAbsolute(path)) return path;
193
+ const resolvedRoot = resolve(rootDir);
194
+ const resolvedPath = resolve(path);
195
+ return isPathWithin(resolvedPath, resolvedRoot) ? resolvedPath : resolve(resolvedRoot, path);
108
196
  }
109
197
  function toRepoPath(rootDir, absolutePath) {
110
198
  return relative(rootDir, absolutePath).split(/\\/g).join("/");
111
199
  }
200
+ function safeLstat(path) {
201
+ try {
202
+ return lstatSync(path);
203
+ } catch (error) {
204
+ const code = error.code;
205
+ if (code === "ENOENT" || code === "ENOTDIR") return void 0;
206
+ throw error;
207
+ }
208
+ }
209
+ function safeStat(path) {
210
+ try {
211
+ return statSync(path);
212
+ } catch (error) {
213
+ const code = error.code;
214
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP") return void 0;
215
+ throw error;
216
+ }
217
+ }
218
+ function safeRealpath(path) {
219
+ try {
220
+ return realpathSync(path);
221
+ } catch (error) {
222
+ const code = error.code;
223
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP") return void 0;
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ // src/core/files.ts
229
+ function listGovernedMarkdownFiles(rootDir) {
230
+ return listMarkdownFiles(rootDir, ["docs"], {
231
+ shouldSkipTree: shouldSkip
232
+ });
233
+ }
234
+ function shouldSkip(repoPath) {
235
+ return isExternalArtifactPath(repoPath) || repoPath.startsWith("docs/governance/templates/") || repoPath === "docs/governance/MANIFEST.yml";
236
+ }
237
+ function toRepoPath2(rootDir, absolutePath) {
238
+ return relative2(rootDir, absolutePath).split(/\\/g).join("/");
239
+ }
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_PREFIX = "../../../../ProjectGovernanceSystem/agent-assets/rules/pie-rules/";
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
+ return normalizeSymlinkTarget(readlinkSync(candidatePath)) === `${PGS_SHARED_RULE_TARGET_PREFIX}${filename}`;
266
+ } catch {
267
+ return false;
268
+ }
269
+ }
270
+ function findUnavailableManagedSharedRuleEntries(rootDir) {
271
+ const manifestEntries = readManifestEntries(rootDir);
272
+ return manifestEntries.filter(
273
+ (entry) => entry.type === "policy" && isUnresolvedManagedSharedRuleSymlink(rootDir, join3(rootDir, entry.path))
274
+ );
275
+ }
276
+ function sharedRuleFilename(repoPath) {
277
+ if (!repoPath.startsWith(SHARED_RULE_PATH_PREFIX)) return void 0;
278
+ const filename = repoPath.slice(SHARED_RULE_PATH_PREFIX.length);
279
+ if (!filename || filename.includes("/") || !filename.endsWith(".md")) return void 0;
280
+ return filename;
281
+ }
282
+ function readManifestEntries(rootDir) {
283
+ const manifestPath = join3(rootDir, "docs/governance/MANIFEST.yml");
284
+ if (!existsSync2(manifestPath)) return [];
285
+ try {
286
+ const lines = readFileSync3(manifestPath, "utf8").split("\n");
287
+ const entries = [];
288
+ let current;
289
+ const flush = () => {
290
+ 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") {
291
+ entries.push(current);
292
+ }
293
+ };
294
+ for (const line of lines) {
295
+ const idMatch = line.match(/^\s+- id:\s*(.+?)\s*$/);
296
+ if (idMatch) {
297
+ flush();
298
+ current = { id: cleanValue(idMatch[1] ?? "") };
299
+ continue;
300
+ }
301
+ if (!current) continue;
302
+ const fieldMatch = line.match(
303
+ /^\s+(path|type|status|canonical|last_reviewed|pinned):\s*(.*?)\s*$/
304
+ );
305
+ if (!fieldMatch) continue;
306
+ const field = fieldMatch[1];
307
+ const value = cleanValue(fieldMatch[2] ?? "");
308
+ if (field === "path") current.path = value;
309
+ if (field === "type") current.type = value;
310
+ if (field === "status") current.status = value;
311
+ if (field === "canonical") current.canonical = value === "true";
312
+ if (field === "last_reviewed") current.lastReviewed = value;
313
+ if (field === "pinned") current.pinned = value === "true";
314
+ }
315
+ flush();
316
+ return entries;
317
+ } catch {
318
+ return [];
319
+ }
320
+ }
321
+ function cleanValue(value) {
322
+ return value.replace(/^(['"])(.*)\1$/, "$2");
323
+ }
324
+ function toRepoPath3(rootDir, candidatePath) {
325
+ return relative3(resolve2(rootDir), resolve2(candidatePath)).split(/\\/g).join("/");
326
+ }
112
327
 
113
328
  // src/core/lifecycle.ts
114
329
  var normalStatuses = [
@@ -132,7 +347,7 @@ var docTypes = [
132
347
  "archive"
133
348
  ];
134
349
  function validateFrontmatter(rootDir, file) {
135
- const path = toRepoPath(rootDir, file.path);
350
+ const path = toRepoPath2(rootDir, file.path);
136
351
  const issues = [];
137
352
  const data = file.data;
138
353
  const id = stringValue(data.id);
@@ -277,6 +492,24 @@ function checkDocs(rootDir = process.cwd()) {
277
492
  issues.push(...result.issues);
278
493
  if (result.record) records.push(result.record);
279
494
  }
495
+ for (const entry of findUnavailableManagedSharedRuleEntries(rootDir)) {
496
+ records.push({
497
+ id: entry.id,
498
+ title: entry.id,
499
+ type: entry.type,
500
+ status: entry.status,
501
+ canonical: entry.canonical,
502
+ owner: "external-shared-rule",
503
+ created: entry.lastReviewed,
504
+ lastReviewed: entry.lastReviewed,
505
+ domain: "shared-rule",
506
+ tags: ["shared-rule"],
507
+ pinned: entry.pinned,
508
+ related: [],
509
+ supersedes: [],
510
+ path: entry.path
511
+ });
512
+ }
280
513
  issues.push(...validateGlobalIntegrity(records));
281
514
  return {
282
515
  ok: issues.length === 0,
@@ -382,8 +615,8 @@ function pathToRepo(rootDir, path) {
382
615
  }
383
616
 
384
617
  // src/core/paths.ts
385
- import { existsSync as existsSync2, readdirSync as readdirSync2 } from "node:fs";
386
- import { join as join2 } from "node:path";
618
+ import { existsSync as existsSync3 } from "node:fs";
619
+ import { basename, join as join4 } from "node:path";
387
620
  function planPath(rootDir, type, slugInput) {
388
621
  const cleanSlug = slugInput.replace(/^\/+|\/+$/g, "");
389
622
  if (!cleanSlug) throw new Error("Slug is required.");
@@ -452,22 +685,18 @@ function planPath(rootDir, type, slugInput) {
452
685
  throw new Error(`Unknown type: ${type}`);
453
686
  }
454
687
  function nextSerial(rootDir, scanDir, regex) {
455
- const root = join2(rootDir, scanDir);
456
- if (!existsSync2(root)) return "0001";
688
+ const root = join4(rootDir, scanDir);
689
+ if (!existsSync3(root)) return "0001";
457
690
  let max = 0;
458
- walkSerial(root, regex, (n) => {
691
+ walkSerial(rootDir, scanDir, regex, (n) => {
459
692
  if (n > max) max = n;
460
693
  });
461
694
  return String(max + 1).padStart(4, "0");
462
695
  }
463
- function walkSerial(dir, regex, onMatch) {
464
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
465
- const path = join2(dir, entry.name);
466
- if (entry.isDirectory()) walkSerial(path, regex, onMatch);
467
- else if (entry.isFile() && entry.name.endsWith(".md")) {
468
- const m = entry.name.match(regex);
469
- if (m && m[1]) onMatch(parseInt(m[1], 10));
470
- }
696
+ function walkSerial(rootDir, dir, regex, onMatch) {
697
+ for (const path of listMarkdownFiles(rootDir, [dir])) {
698
+ const m = basename(path).match(regex);
699
+ if (m && m[1]) onMatch(parseInt(m[1], 10));
471
700
  }
472
701
  }
473
702
  function isKebabCase(slug) {
@@ -486,8 +715,8 @@ function todayIso() {
486
715
  }
487
716
 
488
717
  // src/core/manifest.ts
489
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
490
- import { dirname, join as join3 } from "node:path";
718
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "node:fs";
719
+ import { dirname as dirname2, join as join5 } from "node:path";
491
720
  function buildManifest(rootDir = process.cwd()) {
492
721
  const result = checkDocs(rootDir);
493
722
  if (!result.ok) {
@@ -499,14 +728,14 @@ ${error}`);
499
728
  }
500
729
  function writeManifest(rootDir = process.cwd()) {
501
730
  const manifest = buildManifest(rootDir);
502
- const path = join3(rootDir, "docs/governance/MANIFEST.yml");
503
- mkdirSync(dirname(path), { recursive: true });
731
+ const path = join5(rootDir, "docs/governance/MANIFEST.yml");
732
+ mkdirSync(dirname2(path), { recursive: true });
504
733
  writeFileSync(path, manifest);
505
734
  }
506
735
  function manifestInSync(rootDir = process.cwd()) {
507
- const path = join3(rootDir, "docs/governance/MANIFEST.yml");
508
- if (!existsSync3(path)) return false;
509
- return normalizeManifest(readFileSync2(path, "utf8")) === normalizeManifest(buildManifest(rootDir));
736
+ const path = join5(rootDir, "docs/governance/MANIFEST.yml");
737
+ if (!existsSync4(path)) return false;
738
+ return normalizeManifest(readFileSync4(path, "utf8")) === normalizeManifest(buildManifest(rootDir));
510
739
  }
511
740
  function normalizeManifest(value) {
512
741
  return value.replace(/^generated_at: .*$/m, "generated_at: <ignored>").replace(/^generator_version: .*$/m, "generator_version: <ignored>");
@@ -535,7 +764,7 @@ function renderManifest(records) {
535
764
  function readPackageVersion() {
536
765
  for (const candidate of ["../package.json", "../../package.json"]) {
537
766
  try {
538
- const packageJson = JSON.parse(readFileSync2(new URL(candidate, import.meta.url), "utf8"));
767
+ const packageJson = JSON.parse(readFileSync4(new URL(candidate, import.meta.url), "utf8"));
539
768
  if (typeof packageJson.version === "string") return packageJson.version;
540
769
  } catch {
541
770
  }
@@ -571,8 +800,8 @@ function runApprove(args2) {
571
800
  );
572
801
  return 1;
573
802
  }
574
- const filePath = join4(root, record.path);
575
- const content = readFileSync3(filePath, "utf8");
803
+ const filePath = join6(root, record.path);
804
+ const content = readFileSync5(filePath, "utf8");
576
805
  let next = content;
577
806
  next = updateFrontmatterField(next, "status", toStatus);
578
807
  next = updateFrontmatterField(next, "canonical", "true");
@@ -618,8 +847,8 @@ ${newLines.join("\n")}${tail}`;
618
847
 
619
848
  // src/commands/archive.ts
620
849
  import { execFileSync } from "node:child_process";
621
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
622
- import { basename, dirname as dirname2, join as join5 } from "node:path";
850
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
851
+ import { basename as basename2, dirname as dirname3, join as join7 } from "node:path";
623
852
  function runArchive(args2) {
624
853
  const positional = args2.filter((a) => !a.startsWith("--"));
625
854
  const id = positional[0];
@@ -655,18 +884,18 @@ function runArchive(args2) {
655
884
  return 1;
656
885
  }
657
886
  const oldPath = record.path;
658
- const fileName = basename(oldPath);
887
+ const fileName = basename2(oldPath);
659
888
  const archiveDir = `docs/archive/${quarterTag()}-${record.type}`;
660
889
  const newPath = `${archiveDir}/${fileName}`;
661
- const absNew = join5(root, newPath);
662
- const absOld = join5(root, oldPath);
663
- let content = readFileSync4(absOld, "utf8");
890
+ const absNew = join7(root, newPath);
891
+ const absOld = join7(root, oldPath);
892
+ let content = readFileSync6(absOld, "utf8");
664
893
  content = updateFrontmatterField(content, "type", "archive");
665
894
  content = updateFrontmatterField(content, "status", "archived");
666
895
  content = updateFrontmatterField(content, "canonical", "false");
667
896
  content = updateFrontmatterField(content, "last_reviewed", todayIso());
668
897
  content = updateFrontmatterField(content, "archive_reason", reason);
669
- mkdirSync2(dirname2(absNew), { recursive: true });
898
+ mkdirSync2(dirname3(absNew), { recursive: true });
670
899
  let movedByGit = false;
671
900
  try {
672
901
  execFileSync("git", ["mv", oldPath, newPath], { cwd: root, stdio: "ignore" });
@@ -689,12 +918,12 @@ function runArchive(args2) {
689
918
  }
690
919
 
691
920
  // src/commands/audit.ts
692
- import { existsSync as existsSync5, readdirSync as readdirSync4 } from "node:fs";
693
- import { join as join7 } from "node:path";
921
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
922
+ import { join as join8 } from "node:path";
694
923
 
695
924
  // src/core/link-checker.ts
696
- import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "node:fs";
697
- import { dirname as dirname3, extname, join as join6, resolve } from "node:path";
925
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
926
+ import { dirname as dirname4, extname, resolve as resolve3 } from "node:path";
698
927
  var CURRENT_MARKDOWN_ROOTS = ["AGENTS.md", "README.md", "docs"];
699
928
  var CURRENT_DOC_DIR_PREFIXES = [
700
929
  "docs/canon/",
@@ -719,7 +948,7 @@ function checkCurrentMarkdownLinks(rootDir = process.cwd()) {
719
948
  if (!target || shouldIgnoreTarget(target)) continue;
720
949
  checkedLinks += 1;
721
950
  if (!localTargetExists(rootDir, filePath, target)) {
722
- const file = toRepoPath(rootDir, filePath);
951
+ const file = toRepoPath2(rootDir, filePath);
723
952
  const line = lineNumberAt(content, match.index);
724
953
  issues.push({
725
954
  file,
@@ -738,30 +967,10 @@ function checkCurrentMarkdownLinks(rootDir = process.cwd()) {
738
967
  };
739
968
  }
740
969
  function listCurrentMarkdownFiles(rootDir) {
741
- const files = [];
742
- for (const root of CURRENT_MARKDOWN_ROOTS) {
743
- const fullPath = join6(rootDir, root);
744
- if (!existsSync4(fullPath)) continue;
745
- const stat = lstatSync(fullPath);
746
- if (stat.isSymbolicLink()) continue;
747
- if (stat.isDirectory()) files.push(...walkMarkdown(rootDir, fullPath));
748
- else if (stat.isFile() && fullPath.endsWith(".md")) files.push(fullPath);
749
- }
750
- return Array.from(new Set(files)).sort();
751
- }
752
- function walkMarkdown(rootDir, dir) {
753
- const files = [];
754
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
755
- const fullPath = join6(dir, entry.name);
756
- const repoPath = toRepoPath(rootDir, fullPath);
757
- if (shouldSkipTree(repoPath)) continue;
758
- if (entry.isSymbolicLink()) continue;
759
- if (entry.isDirectory()) files.push(...walkMarkdown(rootDir, fullPath));
760
- if (entry.isFile() && entry.name.endsWith(".md") && shouldIncludeSource(repoPath)) {
761
- files.push(fullPath);
762
- }
763
- }
764
- return files;
970
+ return listMarkdownFiles(rootDir, CURRENT_MARKDOWN_ROOTS, {
971
+ shouldSkipTree,
972
+ includeFile: (repoPath) => repoPath === "AGENTS.md" || repoPath === "README.md" || shouldIncludeSource(repoPath)
973
+ });
765
974
  }
766
975
  function shouldSkipTree(repoPath) {
767
976
  if (isExternalArtifactPath(repoPath)) return true;
@@ -794,12 +1003,12 @@ function shouldIgnoreTarget(target) {
794
1003
  function localTargetExists(rootDir, sourcePath, target) {
795
1004
  const pathPart = decodeTarget(target).split("#")[0]?.split("?")[0] ?? "";
796
1005
  if (!pathPart) return true;
797
- const resolved = pathPart.startsWith("/") ? resolve(rootDir, `.${pathPart}`) : resolve(dirname3(sourcePath), pathPart);
1006
+ const resolved = pathPart.startsWith("/") ? resolve3(rootDir, `.${pathPart}`) : resolve3(dirname4(sourcePath), pathPart);
798
1007
  const candidates = [resolved];
799
1008
  if (!extname(resolved)) {
800
1009
  candidates.push(`${resolved}.md`);
801
1010
  }
802
- return candidates.some((candidate) => existsSync4(candidate));
1011
+ return candidates.some((candidate) => existsSync5(candidate));
803
1012
  }
804
1013
  function decodeTarget(target) {
805
1014
  try {
@@ -816,7 +1025,7 @@ function lineNumberAt(content, index) {
816
1025
  return line;
817
1026
  }
818
1027
  function readText(filePath) {
819
- return readFileSync5(filePath, "utf8");
1028
+ return readFileSync7(filePath, "utf8");
820
1029
  }
821
1030
 
822
1031
  // src/commands/audit.ts
@@ -830,21 +1039,21 @@ function runAudit() {
830
1039
  }
831
1040
  return 1;
832
1041
  }
833
- const migrationSource = join7(root, "Docs-for trans");
834
- if (existsSync5(migrationSource)) {
1042
+ const migrationSource = join8(root, "Docs-for trans");
1043
+ if (existsSync6(migrationSource)) {
835
1044
  const count = countFiles(migrationSource);
836
1045
  warnings += 1;
837
1046
  console.log(
838
1047
  `Migration source still exists: Docs-for trans (${count} files). This was a one-time migration shell; it should not be reintroduced.`
839
1048
  );
840
1049
  }
841
- if (existsSync5(join7(root, "DocSystemStarter.md"))) {
1050
+ if (existsSync6(join8(root, "DocSystemStarter.md"))) {
842
1051
  warnings += 1;
843
1052
  console.log(
844
1053
  "Stray root-level DocSystemStarter.md exists. The original draft has been archived; remove or re-archive."
845
1054
  );
846
1055
  }
847
- const rootEntries = new Set(readdirSync4(root));
1056
+ const rootEntries = new Set(readdirSync2(root));
848
1057
  if (rootEntries.has("Docs")) {
849
1058
  console.error(
850
1059
  "Old Docs/ directory still exists. Move remaining files into docs/ or archive them."
@@ -863,8 +1072,8 @@ function runAudit() {
863
1072
  }
864
1073
  function countFiles(dir) {
865
1074
  let count = 0;
866
- for (const entry of readdirSync4(dir, { withFileTypes: true })) {
867
- const path = join7(dir, entry.name);
1075
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
1076
+ const path = join8(dir, entry.name);
868
1077
  if (entry.isDirectory()) count += countFiles(path);
869
1078
  if (entry.isFile() || entry.isSymbolicLink()) count += 1;
870
1079
  }
@@ -886,19 +1095,12 @@ function runCheck() {
886
1095
 
887
1096
  // src/commands/doctor.ts
888
1097
  import { spawnSync } from "node:child_process";
889
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
890
- import { isAbsolute, join as join9, resolve as resolve2 } from "node:path";
891
-
892
- // src/core/router-integrity.ts
893
- import { existsSync as existsSync6, lstatSync as lstatSync2, readlinkSync, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
894
- import { join as join8, relative as relative2 } from "node:path";
895
-
896
- // src/core/symlinks.ts
897
- function normalizeSymlinkTarget(target) {
898
- return target.replaceAll("\\", "/");
899
- }
1098
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1099
+ import { isAbsolute as isAbsolute2, join as join10, resolve as resolve4 } from "node:path";
900
1100
 
901
1101
  // src/core/router-integrity.ts
1102
+ import { existsSync as existsSync7, lstatSync as lstatSync3, readlinkSync as readlinkSync2, readFileSync as readFileSync8 } from "node:fs";
1103
+ import { join as join9, relative as relative4 } from "node:path";
902
1104
  var CENTRAL_REQUIRED_FILES = [
903
1105
  "AGENTS.md",
904
1106
  "CLAUDE.md",
@@ -1216,7 +1418,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1216
1418
  const requiredFiles = isCentral ? CENTRAL_REQUIRED_FILES : PROJECT_REQUIRED_FILES;
1217
1419
  const requiredNeedles = isCentral ? CENTRAL_REQUIRED_NEEDLES : PROJECT_REQUIRED_NEEDLES;
1218
1420
  for (const file of requiredFiles) {
1219
- if (!existsSync6(join8(rootDir, file))) {
1421
+ if (!existsSync7(join9(rootDir, file))) {
1220
1422
  issues.push({
1221
1423
  file,
1222
1424
  code: "missing-router-file",
@@ -1229,7 +1431,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1229
1431
  }
1230
1432
  issues.push(...validateHostSsot(rootDir));
1231
1433
  for (const file of FORBIDDEN_LEGACY_PATHS) {
1232
- if (existsSync6(join8(rootDir, file))) {
1434
+ if (existsSync7(join9(rootDir, file))) {
1233
1435
  issues.push({
1234
1436
  file,
1235
1437
  code: "legacy-governance-path",
@@ -1239,7 +1441,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1239
1441
  }
1240
1442
  if (!isCentral) {
1241
1443
  for (const file of FORBIDDEN_PROJECT_PATHS) {
1242
- if (existsSync6(join8(rootDir, file))) {
1444
+ if (existsSync7(join9(rootDir, file))) {
1243
1445
  issues.push({
1244
1446
  file,
1245
1447
  code: "legacy-project-policy-path",
@@ -1248,7 +1450,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1248
1450
  }
1249
1451
  }
1250
1452
  for (const file of SUPERSEDED_PROJECT_CONTRACT_PATHS) {
1251
- if (existsSync6(join8(rootDir, file))) {
1453
+ if (existsSync7(join9(rootDir, file))) {
1252
1454
  issues.push({
1253
1455
  file,
1254
1456
  code: "superseded-project-contract",
@@ -1257,7 +1459,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1257
1459
  }
1258
1460
  }
1259
1461
  for (const file of FORBIDDEN_PROJECT_ROOTS) {
1260
- if (existsSync6(join8(rootDir, file))) {
1462
+ if (existsSync7(join9(rootDir, file))) {
1261
1463
  issues.push({
1262
1464
  file,
1263
1465
  code: "project-root-integration-path",
@@ -1267,7 +1469,7 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1267
1469
  }
1268
1470
  } else {
1269
1471
  for (const file of FORBIDDEN_CENTRAL_ROOTS) {
1270
- if (existsSync6(join8(rootDir, file))) {
1472
+ if (existsSync7(join9(rootDir, file))) {
1271
1473
  issues.push({
1272
1474
  file,
1273
1475
  code: "central-external-shared-rule-copy",
@@ -1284,9 +1486,9 @@ function checkRouterIntegrity(rootDir = process.cwd()) {
1284
1486
  });
1285
1487
  }
1286
1488
  for (const requirement of requiredNeedles) {
1287
- const path = join8(rootDir, requirement.file);
1288
- if (!existsSync6(path)) continue;
1289
- const content = readFileSync6(path, "utf8");
1489
+ const path = join9(rootDir, requirement.file);
1490
+ if (!existsSync7(path)) continue;
1491
+ const content = readFileSync8(path, "utf8");
1290
1492
  if (!content.includes(requirement.needle)) {
1291
1493
  issues.push({
1292
1494
  file: requirement.file,
@@ -1332,7 +1534,7 @@ function validateHostSsot(rootDir) {
1332
1534
  validateExactSymlink(".claude/skills", "../.agents/skills");
1333
1535
  return issues;
1334
1536
  function validateRegularFile(file) {
1335
- const stat = safeLstat(join8(rootDir, file));
1537
+ const stat = safeLstat2(join9(rootDir, file));
1336
1538
  if (!stat || stat.isFile()) return;
1337
1539
  issues.push({
1338
1540
  file,
@@ -1341,7 +1543,7 @@ function validateHostSsot(rootDir) {
1341
1543
  });
1342
1544
  }
1343
1545
  function validateDirectory(file) {
1344
- const stat = safeLstat(join8(rootDir, file));
1546
+ const stat = safeLstat2(join9(rootDir, file));
1345
1547
  if (!stat || stat.isDirectory()) return;
1346
1548
  issues.push({
1347
1549
  file,
@@ -1350,8 +1552,8 @@ function validateHostSsot(rootDir) {
1350
1552
  });
1351
1553
  }
1352
1554
  function validateExactSymlink(file, expectedRawTarget) {
1353
- const path = join8(rootDir, file);
1354
- const stat = safeLstat(path);
1555
+ const path = join9(rootDir, file);
1556
+ const stat = safeLstat2(path);
1355
1557
  if (!stat) return;
1356
1558
  if (!stat.isSymbolicLink()) {
1357
1559
  issues.push({
@@ -1361,7 +1563,7 @@ function validateHostSsot(rootDir) {
1361
1563
  });
1362
1564
  return;
1363
1565
  }
1364
- const rawTarget = normalizeSymlinkTarget(readlinkSync(path));
1566
+ const rawTarget = normalizeSymlinkTarget(readlinkSync2(path));
1365
1567
  if (rawTarget !== expectedRawTarget) {
1366
1568
  issues.push({
1367
1569
  file,
@@ -1371,9 +1573,9 @@ function validateHostSsot(rootDir) {
1371
1573
  }
1372
1574
  }
1373
1575
  }
1374
- function safeLstat(path) {
1576
+ function safeLstat2(path) {
1375
1577
  try {
1376
- return lstatSync2(path);
1578
+ return lstatSync3(path);
1377
1579
  } catch (error) {
1378
1580
  const code = error.code;
1379
1581
  if (code === "ENOENT" || code === "ENOTDIR") return void 0;
@@ -1381,27 +1583,27 @@ function safeLstat(path) {
1381
1583
  }
1382
1584
  }
1383
1585
  function isCentralRepository(rootDir) {
1384
- const packageJsonPath = join8(rootDir, "package.json");
1385
- if (!existsSync6(packageJsonPath)) return false;
1586
+ const packageJsonPath = join9(rootDir, "package.json");
1587
+ if (!existsSync7(packageJsonPath)) return false;
1386
1588
  const packageName = readPackageName(packageJsonPath);
1387
1589
  const centralPackageNames = /* @__PURE__ */ new Set(["project-governance-system", "pro-gov"]);
1388
1590
  return centralPackageNames.has(packageName) && hasCentralRepositoryShape(rootDir);
1389
1591
  }
1390
1592
  function readPackageName(packageJsonPath) {
1391
1593
  try {
1392
- const packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf8"));
1594
+ const packageJson = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
1393
1595
  return typeof packageJson.name === "string" ? packageJson.name : "";
1394
1596
  } catch {
1395
1597
  return "";
1396
1598
  }
1397
1599
  }
1398
1600
  function hasCentralRepositoryShape(rootDir) {
1399
- return existsSync6(join8(rootDir, "profiles")) && existsSync6(join8(rootDir, "starter")) && existsSync6(join8(rootDir, "integrations"));
1601
+ return existsSync7(join9(rootDir, "profiles")) && existsSync7(join9(rootDir, "starter")) && existsSync7(join9(rootDir, "integrations"));
1400
1602
  }
1401
1603
  function validateProjectAgentsRouting(rootDir) {
1402
1604
  const issues = [];
1403
1605
  const existingRoutes = PROJECT_AGENTS_ROUTING_FILES.filter(
1404
- (file) => existsSync6(join8(rootDir, file))
1606
+ (file) => existsSync7(join9(rootDir, file))
1405
1607
  );
1406
1608
  if (existingRoutes.length === 0) {
1407
1609
  issues.push({
@@ -1411,9 +1613,9 @@ function validateProjectAgentsRouting(rootDir) {
1411
1613
  });
1412
1614
  return issues;
1413
1615
  }
1414
- const agentsPath = join8(rootDir, "AGENTS.md");
1415
- if (!existsSync6(agentsPath)) return issues;
1416
- const agents = readFileSync6(agentsPath, "utf8");
1616
+ const agentsPath = join9(rootDir, "AGENTS.md");
1617
+ if (!existsSync7(agentsPath)) return issues;
1618
+ const agents = readFileSync8(agentsPath, "utf8");
1417
1619
  if (!existingRoutes.some((file) => agents.includes(file))) {
1418
1620
  issues.push({
1419
1621
  file: "AGENTS.md",
@@ -1424,9 +1626,9 @@ function validateProjectAgentsRouting(rootDir) {
1424
1626
  return issues;
1425
1627
  }
1426
1628
  function validateRouterBlock(rootDir, file) {
1427
- const path = join8(rootDir, file);
1428
- if (!existsSync6(path)) return [];
1429
- const content = readFileSync6(path, "utf8");
1629
+ const path = join9(rootDir, file);
1630
+ if (!existsSync7(path)) return [];
1631
+ const content = readFileSync8(path, "utf8");
1430
1632
  const begin = content.indexOf(ROUTER_BLOCK_BEGIN);
1431
1633
  const end = content.indexOf(ROUTER_BLOCK_END);
1432
1634
  const issues = [];
@@ -1451,9 +1653,9 @@ function validateRouterBlock(rootDir, file) {
1451
1653
  return issues;
1452
1654
  }
1453
1655
  function validateBacktickedLocalPaths(rootDir, file, pathRoot = "") {
1454
- const path = join8(rootDir, file);
1455
- if (!existsSync6(path)) return [];
1456
- const content = readFileSync6(path, "utf8");
1656
+ const path = join9(rootDir, file);
1657
+ if (!existsSync7(path)) return [];
1658
+ const content = readFileSync8(path, "utf8");
1457
1659
  const issues = [];
1458
1660
  const seen = /* @__PURE__ */ new Set();
1459
1661
  const matches = content.matchAll(/`([^`]+)`/g);
@@ -1463,7 +1665,10 @@ function validateBacktickedLocalPaths(rootDir, file, pathRoot = "") {
1463
1665
  seen.add(value);
1464
1666
  if (!isLocalPathReference(value)) continue;
1465
1667
  const normalized = value.endsWith("/") ? value.slice(0, -1) : value;
1466
- if (existsSync6(join8(rootDir, pathRoot, normalized)) || existsSync6(join8(rootDir, normalized))) {
1668
+ const candidatePaths = [join9(rootDir, pathRoot, normalized), join9(rootDir, normalized)];
1669
+ if (candidatePaths.some(
1670
+ (candidatePath) => existsSync7(candidatePath) || isUnresolvedManagedSharedRuleSymlink(rootDir, candidatePath)
1671
+ )) {
1467
1672
  continue;
1468
1673
  }
1469
1674
  issues.push({
@@ -1482,9 +1687,9 @@ function isLocalPathReference(value) {
1482
1687
  return value === "README.md" || value.endsWith(".md") || value.endsWith("/") || value.startsWith("docs/") || value.startsWith("starter/") || value.startsWith("profiles/") || value.startsWith("integrations/");
1483
1688
  }
1484
1689
  function validatePortableRouterText(rootDir, file) {
1485
- const path = join8(rootDir, file);
1486
- if (!existsSync6(path)) return [];
1487
- const content = readFileSync6(path, "utf8");
1690
+ const path = join9(rootDir, file);
1691
+ if (!existsSync7(path)) return [];
1692
+ const content = readFileSync8(path, "utf8");
1488
1693
  if (!hasNonPortablePath(content)) return [];
1489
1694
  return [
1490
1695
  {
@@ -1504,25 +1709,9 @@ function hasNonPortablePath(content) {
1504
1709
  ) || /\b(OneDrive|CloudStorage)\b/.test(content);
1505
1710
  }
1506
1711
  function findGovernedReadmes(rootDir) {
1507
- const matches = [];
1508
- for (const relRoot of ["docs", "starter/docs"]) {
1509
- const absRoot = join8(rootDir, relRoot);
1510
- if (existsSync6(absRoot)) walk2(absRoot);
1511
- }
1512
- return matches.sort();
1513
- function walk2(dir) {
1514
- for (const entry of readdirSync5(dir, { withFileTypes: true })) {
1515
- if (entry.isSymbolicLink()) continue;
1516
- if (entry.isDirectory()) {
1517
- walk2(join8(dir, entry.name));
1518
- continue;
1519
- }
1520
- if (!entry.isFile()) continue;
1521
- if (entry.name.toLowerCase() !== "readme.md") continue;
1522
- const repoPath = relative2(rootDir, join8(dir, entry.name)).split(/\\/g).join("/");
1523
- if (repoPath !== "README.md") matches.push(repoPath);
1524
- }
1525
- }
1712
+ return listMarkdownFiles(rootDir, ["docs", "starter/docs"], {
1713
+ includeFile: (repoPath) => repoPath.toLowerCase().endsWith("/readme.md")
1714
+ }).map((path) => relative4(rootDir, path).split(/\\/g).join("/")).filter((repoPath) => repoPath !== "README.md");
1526
1715
  }
1527
1716
 
1528
1717
  // src/commands/doctor.ts
@@ -1590,8 +1779,8 @@ function collectDoctorIssues(rootDir = process.cwd()) {
1590
1779
  return issues;
1591
1780
  }
1592
1781
  function checkLefthook(rootDir) {
1593
- const path = join9(rootDir, "lefthook.yml");
1594
- if (!existsSync7(path)) {
1782
+ const path = join10(rootDir, "lefthook.yml");
1783
+ if (!existsSync8(path)) {
1595
1784
  return [
1596
1785
  {
1597
1786
  severity: "warning",
@@ -1600,7 +1789,7 @@ function checkLefthook(rootDir) {
1600
1789
  }
1601
1790
  ];
1602
1791
  }
1603
- const content = readFileSync7(path, "utf8");
1792
+ const content = readFileSync9(path, "utf8");
1604
1793
  const issues = [];
1605
1794
  for (const command2 of [
1606
1795
  "pnpm doc-gov router-check",
@@ -1637,8 +1826,8 @@ function checkLefthook(rootDir) {
1637
1826
  return issues;
1638
1827
  }
1639
1828
  function checkDocsCheckWorkflow(rootDir) {
1640
- const path = join9(rootDir, ".github/workflows/docs-check.yml");
1641
- if (!existsSync7(path)) {
1829
+ const path = join10(rootDir, ".github/workflows/docs-check.yml");
1830
+ if (!existsSync8(path)) {
1642
1831
  return [
1643
1832
  {
1644
1833
  severity: "warning",
@@ -1647,7 +1836,7 @@ function checkDocsCheckWorkflow(rootDir) {
1647
1836
  }
1648
1837
  ];
1649
1838
  }
1650
- const content = readFileSync7(path, "utf8");
1839
+ const content = readFileSync9(path, "utf8");
1651
1840
  const issues = [];
1652
1841
  for (const command2 of [
1653
1842
  "pnpm doc-gov router-check",
@@ -1667,15 +1856,15 @@ function checkDocsCheckWorkflow(rootDir) {
1667
1856
  return issues;
1668
1857
  }
1669
1858
  function hookCallsLefthook(path) {
1670
- return existsSync7(path) && readFileSync7(path, "utf8").includes("lefthook");
1859
+ return existsSync8(path) && readFileSync9(path, "utf8").includes("lefthook");
1671
1860
  }
1672
1861
  function resolveGitHookPath(rootDir, hookName) {
1673
1862
  const result = spawnSync("git", ["-C", rootDir, "rev-parse", "--git-path", `hooks/${hookName}`], {
1674
1863
  encoding: "utf8"
1675
1864
  });
1676
1865
  const gitPath = result.status === 0 ? result.stdout.trim() : "";
1677
- if (!gitPath) return join9(rootDir, ".git/hooks", hookName);
1678
- return isAbsolute(gitPath) ? gitPath : resolve2(rootDir, gitPath);
1866
+ if (!gitPath) return join10(rootDir, ".git/hooks", hookName);
1867
+ return isAbsolute2(gitPath) ? gitPath : resolve4(rootDir, gitPath);
1679
1868
  }
1680
1869
 
1681
1870
  // src/commands/find.ts
@@ -1713,12 +1902,12 @@ function runFind(args2) {
1713
1902
  }
1714
1903
 
1715
1904
  // src/commands/init.ts
1716
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
1717
- import { join as join11 } from "node:path";
1905
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
1906
+ import { join as join12 } from "node:path";
1718
1907
 
1719
1908
  // src/core/templates.ts
1720
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
1721
- import { join as join10 } from "node:path";
1909
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "node:fs";
1910
+ import { join as join11 } from "node:path";
1722
1911
  var TEMPLATE_FILES = {
1723
1912
  decision: "adr.md",
1724
1913
  spec: "spec.md",
@@ -1926,19 +2115,19 @@ var DEFAULT_TEMPLATES = {
1926
2115
  function loadTemplate(rootDir, type) {
1927
2116
  const file = TEMPLATE_FILES[type];
1928
2117
  if (!file) throw new Error(`No template file mapped for type: ${type}`);
1929
- const path = join10(rootDir, "docs/governance/templates", file);
1930
- if (existsSync8(path)) return readFileSync8(path, "utf8");
2118
+ const path = join11(rootDir, "docs/governance/templates", file);
2119
+ if (existsSync9(path)) return readFileSync10(path, "utf8");
1931
2120
  const fallback = DEFAULT_TEMPLATES[file];
1932
2121
  if (fallback) return fallback;
1933
2122
  throw new Error(`Template file is missing: docs/governance/templates/${file}`);
1934
2123
  }
1935
2124
  function ensureDefaultTemplates(rootDir) {
1936
- const templatesDir = join10(rootDir, "docs/governance/templates");
2125
+ const templatesDir = join11(rootDir, "docs/governance/templates");
1937
2126
  mkdirSync3(templatesDir, { recursive: true });
1938
2127
  let created = 0;
1939
2128
  for (const [file, content] of Object.entries(DEFAULT_TEMPLATES)) {
1940
- const path = join10(templatesDir, file);
1941
- if (existsSync8(path)) continue;
2129
+ const path = join11(templatesDir, file);
2130
+ if (existsSync9(path)) continue;
1942
2131
  writeFileSync4(path, content);
1943
2132
  created++;
1944
2133
  }
@@ -1993,16 +2182,16 @@ function runInit(args2) {
1993
2182
  ];
1994
2183
  let created = 0;
1995
2184
  for (const dir of dirs) {
1996
- const abs = join11(root, dir);
1997
- if (!existsSync9(abs)) {
2185
+ const abs = join12(root, dir);
2186
+ if (!existsSync10(abs)) {
1998
2187
  mkdirSync4(abs, { recursive: true });
1999
2188
  created++;
2000
2189
  } else if (!force) {
2001
2190
  }
2002
2191
  }
2003
2192
  for (const dir of ["docs/specs/completed", "docs/plans/completed", "docs/archive"]) {
2004
- const keep = join11(root, dir, ".gitkeep");
2005
- if (!existsSync9(keep)) writeFileSync5(keep, "");
2193
+ const keep = join12(root, dir, ".gitkeep");
2194
+ if (!existsSync10(keep)) writeFileSync5(keep, "");
2006
2195
  }
2007
2196
  const templatesCreated = ensureDefaultTemplates(root);
2008
2197
  console.log(
@@ -2049,8 +2238,8 @@ function runLinks() {
2049
2238
  }
2050
2239
 
2051
2240
  // src/commands/migrate.ts
2052
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
2053
- import { join as join12 } from "node:path";
2241
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2242
+ import { join as join13 } from "node:path";
2054
2243
  var PROFILE_ROUTES = {
2055
2244
  "engineering-runtime": "docs/governance/agents-routing/engineering-runtime-v1.1.md",
2056
2245
  "doc-only": "docs/governance/agents-routing/doc-only-v1.1.md"
@@ -2087,11 +2276,11 @@ function checkMigrationReadiness(rootDir, profile) {
2087
2276
  issues.push(`${issue.file}: ${issue.code}: ${issue.message}`);
2088
2277
  }
2089
2278
  const route = PROFILE_ROUTES[profile];
2090
- if (!existsSync10(join12(rootDir, route))) {
2279
+ if (!existsSync11(join13(rootDir, route))) {
2091
2280
  issues.push(`missing selected profile route: ${route}`);
2092
2281
  }
2093
- const agentsPath = join12(rootDir, "AGENTS.md");
2094
- if (existsSync10(agentsPath) && !readFileSync9(agentsPath, "utf8").includes(route)) {
2282
+ const agentsPath = join13(rootDir, "AGENTS.md");
2283
+ if (existsSync11(agentsPath) && !readFileSync11(agentsPath, "utf8").includes(route)) {
2095
2284
  issues.push(`AGENTS.md must name selected profile route: ${route}`);
2096
2285
  }
2097
2286
  return issues;
@@ -2138,8 +2327,8 @@ function readFlag(args2, name) {
2138
2327
  }
2139
2328
 
2140
2329
  // src/commands/new.ts
2141
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
2142
- import { dirname as dirname4, join as join13 } from "node:path";
2330
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
2331
+ import { dirname as dirname5, join as join14 } from "node:path";
2143
2332
  function runNew(args2) {
2144
2333
  const positional = args2.filter((a) => !a.startsWith("--"));
2145
2334
  const owner = readFlag2(args2, "--owner") ?? "human";
@@ -2165,8 +2354,8 @@ function runNew(args2) {
2165
2354
  console.error(err.message);
2166
2355
  return 1;
2167
2356
  }
2168
- const absPath = join13(root, plan.filePath);
2169
- if (existsSync11(absPath) && !force) {
2357
+ const absPath = join14(root, plan.filePath);
2358
+ if (existsSync12(absPath) && !force) {
2170
2359
  console.error(`File already exists: ${plan.filePath}. Use --force to overwrite.`);
2171
2360
  return 1;
2172
2361
  }
@@ -2188,7 +2377,7 @@ function runNew(args2) {
2188
2377
  tags: [plan.slug.split("-")[0] ?? "replace-me"],
2189
2378
  pinned: false
2190
2379
  });
2191
- mkdirSync5(dirname4(absPath), { recursive: true });
2380
+ mkdirSync5(dirname5(absPath), { recursive: true });
2192
2381
  writeFileSync6(absPath, rendered);
2193
2382
  console.log(`Created ${plan.filePath} with id ${plan.id}.`);
2194
2383
  try {
@@ -2249,8 +2438,8 @@ function runScan(args2) {
2249
2438
  }
2250
2439
 
2251
2440
  // src/commands/supersede.ts
2252
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
2253
- import { join as join14 } from "node:path";
2441
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
2442
+ import { join as join15 } from "node:path";
2254
2443
  function runSupersede(args2) {
2255
2444
  const [oldId, newId] = args2;
2256
2445
  if (!oldId || !newId) {
@@ -2286,15 +2475,15 @@ function runSupersede(args2) {
2286
2475
  return 1;
2287
2476
  }
2288
2477
  const today = todayIso();
2289
- const oldPath = join14(root, oldRec.path);
2290
- let oldContent = readFileSync10(oldPath, "utf8");
2478
+ const oldPath = join15(root, oldRec.path);
2479
+ let oldContent = readFileSync12(oldPath, "utf8");
2291
2480
  oldContent = updateFrontmatterField(oldContent, "status", "superseded");
2292
2481
  oldContent = updateFrontmatterField(oldContent, "canonical", "false");
2293
2482
  oldContent = updateFrontmatterField(oldContent, "superseded_by", newId);
2294
2483
  oldContent = updateFrontmatterField(oldContent, "last_reviewed", today);
2295
2484
  writeFileSync7(oldPath, oldContent);
2296
- const newPath = join14(root, newRec.path);
2297
- let newContent = readFileSync10(newPath, "utf8");
2485
+ const newPath = join15(root, newRec.path);
2486
+ let newContent = readFileSync12(newPath, "utf8");
2298
2487
  newContent = appendToFrontmatterList(newContent, "supersedes", newId === oldId ? "" : oldId);
2299
2488
  newContent = updateFrontmatterField(newContent, "last_reviewed", today);
2300
2489
  writeFileSync7(newPath, newContent);
@@ -2343,13 +2532,13 @@ ${lines.join("\n")}${tail}`;
2343
2532
 
2344
2533
  // src/commands/verify-commit-msg.ts
2345
2534
  import { execFileSync as execFileSync2 } from "node:child_process";
2346
- import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
2535
+ import { existsSync as existsSync13, readFileSync as readFileSync13 } from "node:fs";
2347
2536
  function runVerifyCommitMsg(args2) {
2348
2537
  const msgFile = args2[0];
2349
- if (!msgFile || !existsSync12(msgFile)) {
2538
+ if (!msgFile || !existsSync13(msgFile)) {
2350
2539
  return 0;
2351
2540
  }
2352
- const message = readFileSync11(msgFile, "utf8");
2541
+ const message = readFileSync13(msgFile, "utf8");
2353
2542
  if (/^Merge\b/m.test(message) || /^Revert\b/m.test(message)) return 0;
2354
2543
  let stagedFiles = [];
2355
2544
  try {
@@ -2361,7 +2550,7 @@ function runVerifyCommitMsg(args2) {
2361
2550
  }
2362
2551
  const failures = [];
2363
2552
  for (const file of stagedFiles) {
2364
- if (!existsSync12(file)) continue;
2553
+ if (!existsSync13(file)) continue;
2365
2554
  const fm = readFrontmatterFile(file);
2366
2555
  if (!fm) continue;
2367
2556
  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.5",
3
+ "version": "0.9.7",
4
4
  "description": "AI-native documentation governance CLI for project docs, agent routing, and lifecycle checks.",
5
5
  "keywords": [
6
6
  "ai-agents",