@lsctech/polaris 0.1.0

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 (316) hide show
  1. package/README.md +87 -0
  2. package/dist/__mocks__/tool-server-linear.js +34 -0
  3. package/dist/cli/adopt-approve.js +60 -0
  4. package/dist/cli/adopt-cognition.js +199 -0
  5. package/dist/cli/adopt-instructions.js +143 -0
  6. package/dist/cli/adopt-rules.js +169 -0
  7. package/dist/cli/adopt-scan.js +25 -0
  8. package/dist/cli/adopt-smartdocs.js +192 -0
  9. package/dist/cli/adoption-inventory.js +350 -0
  10. package/dist/cli/adoption-plan.js +222 -0
  11. package/dist/cli/args.js +28 -0
  12. package/dist/cli/args.test.js +30 -0
  13. package/dist/cli/branding.js +41 -0
  14. package/dist/cli/graph.js +358 -0
  15. package/dist/cli/index.js +127 -0
  16. package/dist/cli/index.test.js +162 -0
  17. package/dist/cli/init-detect.js +61 -0
  18. package/dist/cli/init.js +492 -0
  19. package/dist/cli/init.test.js +259 -0
  20. package/dist/cli/librarian.js +32 -0
  21. package/dist/cli/medic.js +96 -0
  22. package/dist/cli/startup.test.js +23 -0
  23. package/dist/cli/tracker.js +130 -0
  24. package/dist/cli/tracker.test.js +126 -0
  25. package/dist/cli/version.js +10 -0
  26. package/dist/cli/version.test.js +11 -0
  27. package/dist/cli/worker.js +287 -0
  28. package/dist/cluster-state/fixtures/test-clusters.json +19 -0
  29. package/dist/cluster-state/store.js +302 -0
  30. package/dist/cluster-state/store.test.js +145 -0
  31. package/dist/cluster-state/types.js +2 -0
  32. package/dist/cognition/archive.js +158 -0
  33. package/dist/cognition/closeout-librarian-types.js +161 -0
  34. package/dist/cognition/cognition.test.js +623 -0
  35. package/dist/cognition/index.js +41 -0
  36. package/dist/cognition/librarian-dispatch.js +391 -0
  37. package/dist/cognition/librarian-dispatch.test.js +247 -0
  38. package/dist/cognition/librarian-packet.js +313 -0
  39. package/dist/cognition/librarian-types.js +57 -0
  40. package/dist/cognition/route-cognition-delta.js +350 -0
  41. package/dist/cognition/summary-delta.js +279 -0
  42. package/dist/cognition/validate.js +162 -0
  43. package/dist/config/defaults.js +98 -0
  44. package/dist/config/doctor.js +273 -0
  45. package/dist/config/index.js +14 -0
  46. package/dist/config/loader.js +58 -0
  47. package/dist/config/provider-detect.js +60 -0
  48. package/dist/config/provider-detect.test.js +119 -0
  49. package/dist/config/schema.js +2 -0
  50. package/dist/config/show.js +44 -0
  51. package/dist/config/show.test.js +42 -0
  52. package/dist/config/validator.js +675 -0
  53. package/dist/config/validator.test.js +77 -0
  54. package/dist/docs/canon-check.js +334 -0
  55. package/dist/docs/canon-check.test.js +207 -0
  56. package/dist/docs/doctrine.js +133 -0
  57. package/dist/docs/doctrine.test.js +139 -0
  58. package/dist/docs/index.js +168 -0
  59. package/dist/docs/index.test.js +83 -0
  60. package/dist/docs/ingest.js +295 -0
  61. package/dist/docs/ingest.test.js +71 -0
  62. package/dist/docs/migrate.js +165 -0
  63. package/dist/docs/migrate.test.js +136 -0
  64. package/dist/docs/seed-instructions.js +147 -0
  65. package/dist/docs/seed-instructions.test.js +137 -0
  66. package/dist/docs/validate-instructions.js +267 -0
  67. package/dist/docs/validate-instructions.test.js +238 -0
  68. package/dist/finalize/artifact-policy.js +214 -0
  69. package/dist/finalize/artifact-policy.test.js +84 -0
  70. package/dist/finalize/delivery-integrity.js +141 -0
  71. package/dist/finalize/finalize.test.js +210 -0
  72. package/dist/finalize/github.js +41 -0
  73. package/dist/finalize/index.js +474 -0
  74. package/dist/finalize/linear.js +213 -0
  75. package/dist/finalize/run-report.js +47 -0
  76. package/dist/finalize/steps/01-map-update.js +11 -0
  77. package/dist/finalize/steps/02-map-validate.js +11 -0
  78. package/dist/finalize/steps/03-schema-validate.js +11 -0
  79. package/dist/finalize/steps/04-run-checks.js +118 -0
  80. package/dist/finalize/steps/05-generate-report.js +14 -0
  81. package/dist/finalize/steps/06-commit.js +39 -0
  82. package/dist/finalize/steps/07-push.js +10 -0
  83. package/dist/finalize/steps/08-create-pr.js +9 -0
  84. package/dist/finalize/steps/09-update-state.js +9 -0
  85. package/dist/finalize/steps/10-append-jsonl.js +23 -0
  86. package/dist/finalize/steps/11-update-linear.js +22 -0
  87. package/dist/finalize/steps/12-archive.js +21 -0
  88. package/dist/graph/adapter/c/adapter.js +45 -0
  89. package/dist/graph/adapter/c/extract.js +135 -0
  90. package/dist/graph/adapter/c/index.js +10 -0
  91. package/dist/graph/adapter/c/runtime.js +67 -0
  92. package/dist/graph/adapter/cpp/adapter.js +45 -0
  93. package/dist/graph/adapter/cpp/extract.js +159 -0
  94. package/dist/graph/adapter/cpp/index.js +10 -0
  95. package/dist/graph/adapter/cpp/runtime.js +61 -0
  96. package/dist/graph/adapter/csharp/adapter.js +45 -0
  97. package/dist/graph/adapter/csharp/extract.js +127 -0
  98. package/dist/graph/adapter/csharp/index.js +10 -0
  99. package/dist/graph/adapter/csharp/runtime.js +61 -0
  100. package/dist/graph/adapter/dart/adapter.js +56 -0
  101. package/dist/graph/adapter/dart/extract.js +130 -0
  102. package/dist/graph/adapter/dart/index.js +10 -0
  103. package/dist/graph/adapter/dart/runtime.js +61 -0
  104. package/dist/graph/adapter/go/adapter.js +45 -0
  105. package/dist/graph/adapter/go/extract.js +112 -0
  106. package/dist/graph/adapter/go/index.js +10 -0
  107. package/dist/graph/adapter/go/runtime.js +58 -0
  108. package/dist/graph/adapter/kotlin-java/extract-common.js +83 -0
  109. package/dist/graph/adapter/kotlin-java/index.js +15 -0
  110. package/dist/graph/adapter/kotlin-java/java-adapter.js +45 -0
  111. package/dist/graph/adapter/kotlin-java/java-extract.js +42 -0
  112. package/dist/graph/adapter/kotlin-java/kotlin-adapter.js +48 -0
  113. package/dist/graph/adapter/kotlin-java/kotlin-extract.js +58 -0
  114. package/dist/graph/adapter/kotlin-java/runtime.js +75 -0
  115. package/dist/graph/adapter/python/adapter.js +45 -0
  116. package/dist/graph/adapter/python/extract.js +121 -0
  117. package/dist/graph/adapter/python/index.js +10 -0
  118. package/dist/graph/adapter/python/runtime.js +70 -0
  119. package/dist/graph/adapter/registry.js +82 -0
  120. package/dist/graph/adapter/rust/adapter.js +45 -0
  121. package/dist/graph/adapter/rust/extract.js +110 -0
  122. package/dist/graph/adapter/rust/index.js +10 -0
  123. package/dist/graph/adapter/rust/runtime.js +74 -0
  124. package/dist/graph/adapter/shell/adapter.js +34 -0
  125. package/dist/graph/adapter/shell/extract.js +98 -0
  126. package/dist/graph/adapter/shell/index.js +8 -0
  127. package/dist/graph/adapter/svelte/adapter.js +91 -0
  128. package/dist/graph/adapter/svelte/extract.js +41 -0
  129. package/dist/graph/adapter/svelte/index.js +8 -0
  130. package/dist/graph/adapter/swift/adapter.js +45 -0
  131. package/dist/graph/adapter/swift/extract.js +120 -0
  132. package/dist/graph/adapter/swift/index.js +10 -0
  133. package/dist/graph/adapter/swift/runtime.js +70 -0
  134. package/dist/graph/adapter/types.js +2 -0
  135. package/dist/graph/adapter/typescript-javascript/adapter.js +72 -0
  136. package/dist/graph/adapter/typescript-javascript/extract.js +115 -0
  137. package/dist/graph/adapter/typescript-javascript/index.js +10 -0
  138. package/dist/graph/adapter/typescript-javascript/runtime.js +97 -0
  139. package/dist/graph/capability/index.js +105 -0
  140. package/dist/graph/governance.js +104 -0
  141. package/dist/graph/parser/extract.js +5 -0
  142. package/dist/graph/parser/loader.js +2 -0
  143. package/dist/graph/parser/pipeline.js +194 -0
  144. package/dist/graph/query/index.js +277 -0
  145. package/dist/graph/query/types.js +5 -0
  146. package/dist/graph/resolver/build-edges.js +211 -0
  147. package/dist/graph/resolver/index.js +23 -0
  148. package/dist/graph/resolver/resolve-imports.js +168 -0
  149. package/dist/graph/store/adapter.js +132 -0
  150. package/dist/graph/store/queries.js +124 -0
  151. package/dist/graph/store/types.js +4 -0
  152. package/dist/ignore/defaults.js +41 -0
  153. package/dist/ignore/parser.js +39 -0
  154. package/dist/lint/map-reference-validator.js +72 -0
  155. package/dist/loop/abort.js +188 -0
  156. package/dist/loop/abort.test.js +151 -0
  157. package/dist/loop/adapters/__tests__/terminal-cli.test.js +92 -0
  158. package/dist/loop/adapters/agent-subtask.js +137 -0
  159. package/dist/loop/adapters/agent-subtask.test.js +151 -0
  160. package/dist/loop/adapters/cli-subtask-bridge.js +108 -0
  161. package/dist/loop/adapters/index.js +9 -0
  162. package/dist/loop/adapters/registry.js +17 -0
  163. package/dist/loop/adapters/terminal-cli.js +403 -0
  164. package/dist/loop/adapters/types.js +2 -0
  165. package/dist/loop/adapters/worker-instructions.js +85 -0
  166. package/dist/loop/body-parser.js +226 -0
  167. package/dist/loop/bootstrap-packet.js +97 -0
  168. package/dist/loop/bootstrap-packet.test.js +171 -0
  169. package/dist/loop/branch-governance.js +54 -0
  170. package/dist/loop/budget.js +63 -0
  171. package/dist/loop/budget.test.js +163 -0
  172. package/dist/loop/checkpoint.js +325 -0
  173. package/dist/loop/compact-return.js +59 -0
  174. package/dist/loop/continue.js +645 -0
  175. package/dist/loop/continue.test.js +337 -0
  176. package/dist/loop/dispatch-boundary.js +335 -0
  177. package/dist/loop/dispatch-state.js +223 -0
  178. package/dist/loop/dispatch-state.test.js +186 -0
  179. package/dist/loop/dispatch.js +1236 -0
  180. package/dist/loop/dispatch.test.js +499 -0
  181. package/dist/loop/ephemeral-smoke.test.js +160 -0
  182. package/dist/loop/evidence-backfill.js +216 -0
  183. package/dist/loop/execution-adapter.js +141 -0
  184. package/dist/loop/execution-adapter.test.js +66 -0
  185. package/dist/loop/finalize-evidence.js +169 -0
  186. package/dist/loop/git-custody.js +250 -0
  187. package/dist/loop/impl-packet-input.js +38 -0
  188. package/dist/loop/index.js +270 -0
  189. package/dist/loop/ledger.js +49 -0
  190. package/dist/loop/ledger.test.js +127 -0
  191. package/dist/loop/lifecycle-dispatch.js +348 -0
  192. package/dist/loop/lifecycle-dispatch.test.js +318 -0
  193. package/dist/loop/lifecycle.js +93 -0
  194. package/dist/loop/lifecycle.test.js +150 -0
  195. package/dist/loop/orphan-recovery.js +276 -0
  196. package/dist/loop/orphan-recovery.test.js +236 -0
  197. package/dist/loop/parent.js +1566 -0
  198. package/dist/loop/parent.test.js +738 -0
  199. package/dist/loop/provider-sealed-packet.test.js +164 -0
  200. package/dist/loop/resume.js +295 -0
  201. package/dist/loop/resume.test.js +172 -0
  202. package/dist/loop/run-bootstrap.js +234 -0
  203. package/dist/loop/run-preflight.js +203 -0
  204. package/dist/loop/status.js +665 -0
  205. package/dist/loop/status.test.js +235 -0
  206. package/dist/loop/wait.js +86 -0
  207. package/dist/loop/worker-packet.js +406 -0
  208. package/dist/loop/worker-packet.test.js +243 -0
  209. package/dist/loop/worker-prompt.js +260 -0
  210. package/dist/loop/worker-prompt.test.js +247 -0
  211. package/dist/loop/worker.js +498 -0
  212. package/dist/loop/worker.test.js +372 -0
  213. package/dist/map/atlas.js +77 -0
  214. package/dist/map/backfill.js +171 -0
  215. package/dist/map/backfill.test.js +120 -0
  216. package/dist/map/index.js +255 -0
  217. package/dist/map/inference.js +73 -0
  218. package/dist/map/inference.test.js +35 -0
  219. package/dist/map/query.js +157 -0
  220. package/dist/map/query.test.js +174 -0
  221. package/dist/map/update.js +355 -0
  222. package/dist/map/update.test.js +67 -0
  223. package/dist/map/validate.js +153 -0
  224. package/dist/map/validate.test.js +90 -0
  225. package/dist/map/welfare.js +86 -0
  226. package/dist/mcp/lib/invoke.js +35 -0
  227. package/dist/mcp/lib/redact.js +24 -0
  228. package/dist/mcp/lib/redact.test.js +112 -0
  229. package/dist/mcp/lib/root.js +41 -0
  230. package/dist/mcp/lib/root.test.js +114 -0
  231. package/dist/mcp/server.js +15 -0
  232. package/dist/mcp/tools/claim-child.js +119 -0
  233. package/dist/mcp/tools/claim-child.test.js +69 -0
  234. package/dist/mcp/tools/current-state.js +73 -0
  235. package/dist/mcp/tools/current-state.test.js +114 -0
  236. package/dist/mcp/tools/dispatch-result.js +186 -0
  237. package/dist/mcp/tools/dispatch-result.test.js +109 -0
  238. package/dist/mcp/tools/index.js +222 -0
  239. package/dist/mcp/tools/index.test.js +172 -0
  240. package/dist/mcp/tools/linear.js +74 -0
  241. package/dist/mcp/tools/loop-continue.js +159 -0
  242. package/dist/mcp/tools/loop-continue.test.js +186 -0
  243. package/dist/mcp/tools/loop-dry-run.js +15 -0
  244. package/dist/mcp/tools/status.js +65 -0
  245. package/dist/medic/chart-id.js +75 -0
  246. package/dist/medic/chart-schema.js +139 -0
  247. package/dist/runs/index.js +119 -0
  248. package/dist/runs/index.test.js +184 -0
  249. package/dist/runs/ledger-tail.js +15 -0
  250. package/dist/runs/list.js +29 -0
  251. package/dist/runs/reconcile.js +74 -0
  252. package/dist/runs/show.js +10 -0
  253. package/dist/runtime/audit/logger.js +15 -0
  254. package/dist/runtime/audit/reader.js +42 -0
  255. package/dist/runtime/checkpoint.js +89 -0
  256. package/dist/runtime/continuation/confirmed.js +225 -0
  257. package/dist/runtime/continuation/confirmed.test.js +421 -0
  258. package/dist/runtime/continuation/dry-run.js +74 -0
  259. package/dist/runtime/execution-window.js +106 -0
  260. package/dist/runtime/scheduling/child-selector.js +16 -0
  261. package/dist/runtime/state.js +43 -0
  262. package/dist/runtime/status.js +46 -0
  263. package/dist/runtime/verification/envelope.js +80 -0
  264. package/dist/runtime/verification/fingerprint.js +26 -0
  265. package/dist/skill-packet/cli.test.js +105 -0
  266. package/dist/skill-packet/generator.js +225 -0
  267. package/dist/skill-packet/generator.test.js +159 -0
  268. package/dist/skill-packet/index.js +49 -0
  269. package/dist/skill-packet/types.js +2 -0
  270. package/dist/smartdocs-engine/audit.js +194 -0
  271. package/dist/smartdocs-engine/audit.test.js +122 -0
  272. package/dist/smartdocs-engine/canon-check.js +340 -0
  273. package/dist/smartdocs-engine/canon-check.test.js +234 -0
  274. package/dist/smartdocs-engine/doctrine.js +633 -0
  275. package/dist/smartdocs-engine/doctrine.test.js +374 -0
  276. package/dist/smartdocs-engine/index.js +456 -0
  277. package/dist/smartdocs-engine/index.test.js +82 -0
  278. package/dist/smartdocs-engine/ingest.js +495 -0
  279. package/dist/smartdocs-engine/ingest.test.js +163 -0
  280. package/dist/smartdocs-engine/migrate.js +217 -0
  281. package/dist/smartdocs-engine/migrate.test.js +200 -0
  282. package/dist/smartdocs-engine/seed-instructions.js +335 -0
  283. package/dist/smartdocs-engine/seed-instructions.test.js +275 -0
  284. package/dist/smartdocs-engine/smartdoc-ignore.js +258 -0
  285. package/dist/smartdocs-engine/smartdoc-ignore.test.js +146 -0
  286. package/dist/smartdocs-engine/validate-instructions.js +366 -0
  287. package/dist/smartdocs-engine/validate-instructions.test.js +283 -0
  288. package/dist/tracker/adapters/github/index.js +305 -0
  289. package/dist/tracker/adapters/jira/index.js +293 -0
  290. package/dist/tracker/adapters/linear/http-errors.test.js +51 -0
  291. package/dist/tracker/adapters/linear/index.js +621 -0
  292. package/dist/tracker/adapters/linear/index.test.js +238 -0
  293. package/dist/tracker/adapters/linear.js +50 -0
  294. package/dist/tracker/adapters/local/index.js +10 -0
  295. package/dist/tracker/adapters/local-file/index.js +159 -0
  296. package/dist/tracker/adapters/mcp-bridge.js +158 -0
  297. package/dist/tracker/adapters/mcp-bridge.test.js +12 -0
  298. package/dist/tracker/adapters/spec/index.js +146 -0
  299. package/dist/tracker/capabilities.js +2 -0
  300. package/dist/tracker/index.js +49 -0
  301. package/dist/tracker/lifecycle-policy.js +139 -0
  302. package/dist/tracker/lifecycle-transition.js +164 -0
  303. package/dist/tracker/local-graph.js +98 -0
  304. package/dist/tracker/migration.js +56 -0
  305. package/dist/tracker/schema.js +86 -0
  306. package/dist/tracker/sync/index.js +404 -0
  307. package/dist/tracker/sync/queue-store.js +95 -0
  308. package/dist/tracker/sync/queue-store.test.js +104 -0
  309. package/dist/tracker/types.js +11 -0
  310. package/dist/types/linear.js +3 -0
  311. package/dist/types/result-packet.js +12 -0
  312. package/dist/types/runtime-state.js +2 -0
  313. package/dist/types/work-contract.js +2 -0
  314. package/dist/utils/monotonic-timestamp.js +13 -0
  315. package/dist/utils/monotonic-timestamp.test.js +19 -0
  316. package/package.json +64 -0
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_child_process_1 = require("node:child_process");
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const node_os_1 = require("node:os");
7
+ const vitest_1 = require("vitest");
8
+ const audit_js_1 = require("./audit.js");
9
+ function makeGitRepo() {
10
+ const repoRoot = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "polaris-audit-"));
11
+ (0, node_child_process_1.execFileSync)("git", ["init"], { cwd: repoRoot });
12
+ (0, node_child_process_1.execFileSync)("git", ["config", "user.email", "test@test.com"], { cwd: repoRoot });
13
+ (0, node_child_process_1.execFileSync)("git", ["config", "user.name", "Test"], { cwd: repoRoot });
14
+ return repoRoot;
15
+ }
16
+ function addTrackedFile(repoRoot, relPath, content = "# Doc\n\nContent") {
17
+ const dir = relPath.includes("/")
18
+ ? (0, node_path_1.join)(repoRoot, relPath.split("/").slice(0, -1).join("/"))
19
+ : repoRoot;
20
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
21
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(repoRoot, relPath), content, "utf-8");
22
+ (0, node_child_process_1.execFileSync)("git", ["add", relPath], { cwd: repoRoot });
23
+ }
24
+ (0, vitest_1.describe)("auditIngestRiskSurface", () => {
25
+ (0, vitest_1.it)("flags endpoint artifacts as HIGH risk", () => {
26
+ const repoRoot = makeGitRepo();
27
+ addTrackedFile(repoRoot, ".claude/SKILL.md", "# Skill doc\n");
28
+ const result = (0, audit_js_1.auditIngestRiskSurface)(repoRoot);
29
+ const highIngest = result.findings.filter((f) => f.risk === "HIGH" && f.pipelineStage === "ingest" && f.filePath === ".claude/SKILL.md");
30
+ (0, vitest_1.expect)(highIngest.length).toBe(1);
31
+ (0, vitest_1.expect)(highIngest[0].currentProtection).toBe("isIngestIneligible");
32
+ (0, vitest_1.expect)(result.summary.high).toBeGreaterThanOrEqual(1);
33
+ (0, vitest_1.expect)(result.summary.protectedBySmartdocIgnore).toBeGreaterThanOrEqual(1);
34
+ });
35
+ (0, vitest_1.it)("flags doctrine-candidate endpoint artifacts as HIGH with pipelineStage classify", () => {
36
+ const repoRoot = makeGitRepo();
37
+ // Content that classifyDoc will return "doctrine-candidate" for
38
+ const doctrineContent = "# Doctrine Rule\n\nYou must always check eligibility. Never silently fail.\n";
39
+ addTrackedFile(repoRoot, ".claude/POLARIS-doctrine.md", doctrineContent);
40
+ const result = (0, audit_js_1.auditIngestRiskSurface)(repoRoot);
41
+ const classifyFindings = result.findings.filter((f) => f.risk === "HIGH" &&
42
+ f.pipelineStage === "classify" &&
43
+ f.filePath === ".claude/POLARIS-doctrine.md");
44
+ (0, vitest_1.expect)(classifyFindings.length).toBe(1);
45
+ (0, vitest_1.expect)(classifyFindings[0].currentProtection).toBe("isIngestIneligible");
46
+ (0, vitest_1.expect)(classifyFindings[0].proposedEnforcementPoint).toContain("classifyDoc");
47
+ });
48
+ (0, vitest_1.it)("returns valid AuditResult shape with no tracked files", () => {
49
+ const repoRoot = makeGitRepo();
50
+ const result = (0, audit_js_1.auditIngestRiskSurface)(repoRoot);
51
+ (0, vitest_1.expect)(result).toHaveProperty("scanDate");
52
+ (0, vitest_1.expect)(result).toHaveProperty("repoRoot");
53
+ (0, vitest_1.expect)(result).toHaveProperty("findings");
54
+ (0, vitest_1.expect)(result).toHaveProperty("summary");
55
+ (0, vitest_1.expect)(Array.isArray(result.findings)).toBe(true);
56
+ (0, vitest_1.expect)(typeof result.summary.high).toBe("number");
57
+ (0, vitest_1.expect)(typeof result.summary.medium).toBe("number");
58
+ (0, vitest_1.expect)(typeof result.summary.low).toBe("number");
59
+ (0, vitest_1.expect)(typeof result.summary.protectedBySmartdocIgnore).toBe("number");
60
+ (0, vitest_1.expect)(typeof result.summary.alreadyProtected).toBe("number");
61
+ (0, vitest_1.expect)(result.summary.alreadyProtected).toBe(result.summary.protectedBySmartdocIgnore);
62
+ });
63
+ (0, vitest_1.it)("flags generated summary files as MEDIUM risk", () => {
64
+ const repoRoot = makeGitRepo();
65
+ addTrackedFile(repoRoot, "docs/sprint-summary.md", "# Sprint summary\n");
66
+ const result = (0, audit_js_1.auditIngestRiskSurface)(repoRoot);
67
+ const mediumFindings = result.findings.filter((f) => f.risk === "MEDIUM" && f.filePath === "docs/sprint-summary.md");
68
+ (0, vitest_1.expect)(mediumFindings.length).toBe(1);
69
+ (0, vitest_1.expect)(mediumFindings[0].pipelineStage).toBe("ingest");
70
+ });
71
+ });
72
+ (0, vitest_1.describe)("formatAuditMarkdown", () => {
73
+ (0, vitest_1.it)("produces markdown with summary table and findings sections", () => {
74
+ const result = {
75
+ scanDate: "2026-05-26T00:00:00.000Z",
76
+ repoRoot: "/repo",
77
+ findings: [
78
+ {
79
+ filePath: ".claude/SKILL.md",
80
+ risk: "HIGH",
81
+ reason: "Endpoint artifact matched by .smartdocignore default patterns — ingest would be blocked but file is tracked",
82
+ pipelineStage: "ingest",
83
+ currentProtection: "isIngestIneligible",
84
+ proposedEnforcementPoint: "smartdoc-ignore check in ingestDocs()",
85
+ },
86
+ {
87
+ filePath: "docs/overview.md",
88
+ risk: "MEDIUM",
89
+ reason: "Generated summary or index file — ingest risk if not protected",
90
+ pipelineStage: "ingest",
91
+ currentProtection: null,
92
+ proposedEnforcementPoint: "smartdoc-ignore pattern or explicit check",
93
+ },
94
+ ],
95
+ summary: {
96
+ high: 1,
97
+ medium: 1,
98
+ low: 0,
99
+ protectedBySmartdocIgnore: 1,
100
+ alreadyProtected: 1,
101
+ },
102
+ };
103
+ const md = (0, audit_js_1.formatAuditMarkdown)(result);
104
+ (0, vitest_1.expect)(md).toContain("# Polaris Smart Docs — Ingest Risk Audit");
105
+ (0, vitest_1.expect)(md).toContain("HIGH | 1");
106
+ (0, vitest_1.expect)(md).toContain("MEDIUM | 1");
107
+ (0, vitest_1.expect)(md).toContain(".claude/SKILL.md");
108
+ (0, vitest_1.expect)(md).toContain("docs/overview.md");
109
+ (0, vitest_1.expect)(md).toContain("## HIGH Risk Findings");
110
+ (0, vitest_1.expect)(md).toContain("## MEDIUM Risk Findings");
111
+ });
112
+ (0, vitest_1.it)("outputs no-findings message when findings is empty", () => {
113
+ const result = {
114
+ scanDate: "2026-05-26T00:00:00.000Z",
115
+ repoRoot: "/repo",
116
+ findings: [],
117
+ summary: { high: 0, medium: 0, low: 0, protectedBySmartdocIgnore: 0, alreadyProtected: 0 },
118
+ };
119
+ const md = (0, audit_js_1.formatAuditMarkdown)(result);
120
+ (0, vitest_1.expect)(md).toContain("No findings");
121
+ });
122
+ });
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCanonCheck = runCanonCheck;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ // Modal verbs that indicate behavioral assertions in doctrine/spec files
7
+ const MODAL_VERBS = /\b(must|never|always|should|required|requires|shall)\b/i;
8
+ // Domains matched by filename keywords
9
+ const DOMAIN_KEYWORDS = ["loop", "map", "finalize", "config", "cli", "docs"];
10
+ /** Walk up from `startDir` to `repoRoot` looking for the nearest POLARIS.md */
11
+ function findNearestPolarisMd(startDir, repoRoot) {
12
+ let dir = (0, node_path_1.resolve)(startDir);
13
+ const root = (0, node_path_1.resolve)(repoRoot);
14
+ let searching = true;
15
+ while (searching) {
16
+ const candidate = (0, node_path_1.join)(dir, "POLARIS.md");
17
+ if ((0, node_fs_1.existsSync)(candidate))
18
+ return candidate;
19
+ if (dir === root)
20
+ break;
21
+ const parent = (0, node_path_1.dirname)(dir);
22
+ if (parent === dir) {
23
+ searching = false;
24
+ break;
25
+ } // filesystem root
26
+ dir = parent;
27
+ }
28
+ return null;
29
+ }
30
+ /** Collect all relevant canon files for the given changed files */
31
+ function locateCanonFiles(changedFiles, repoRoot) {
32
+ const canon = new Set();
33
+ // POLARIS.md files — nearest ancestor for each changed file
34
+ for (const f of changedFiles) {
35
+ if ((0, node_path_1.basename)(f).toLowerCase() === "summary.md")
36
+ continue;
37
+ const absFile = (0, node_path_1.resolve)(repoRoot, f);
38
+ const startDir = (0, node_path_1.dirname)(absFile);
39
+ const polarisMd = findNearestPolarisMd(startDir, repoRoot);
40
+ if (polarisMd)
41
+ canon.add(polarisMd);
42
+ }
43
+ // Active doctrine
44
+ const doctrineDir = (0, node_path_1.join)(repoRoot, "docs", "doctrine", "active");
45
+ if ((0, node_fs_1.existsSync)(doctrineDir)) {
46
+ for (const f of (0, node_fs_1.readdirSync)(doctrineDir)) {
47
+ if (f.endsWith(".md") && (0, node_path_1.basename)(f).toLowerCase() !== "summary.md")
48
+ canon.add((0, node_path_1.join)(doctrineDir, f));
49
+ }
50
+ }
51
+ // Active and implemented specs — filtered by domain keyword overlap
52
+ const changedDomains = new Set();
53
+ for (const f of changedFiles) {
54
+ if ((0, node_path_1.basename)(f).toLowerCase() === "summary.md")
55
+ continue;
56
+ for (const kw of DOMAIN_KEYWORDS) {
57
+ if (f.toLowerCase().includes(kw))
58
+ changedDomains.add(kw);
59
+ }
60
+ }
61
+ for (const specSubdir of ["active", "implemented"]) {
62
+ const specDir = (0, node_path_1.join)(repoRoot, "docs", "specs", specSubdir);
63
+ if (!(0, node_fs_1.existsSync)(specDir))
64
+ continue;
65
+ for (const f of (0, node_fs_1.readdirSync)(specDir)) {
66
+ if (!f.endsWith(".md") || (0, node_path_1.basename)(f).toLowerCase() === "summary.md")
67
+ continue;
68
+ const lower = f.toLowerCase();
69
+ const matches = changedDomains.size === 0 || Array.from(changedDomains).some((kw) => lower.includes(kw));
70
+ if (matches)
71
+ canon.add((0, node_path_1.join)(specDir, f));
72
+ }
73
+ }
74
+ return canon;
75
+ }
76
+ /** Extract bullet-point lines from a named markdown section */
77
+ function extractSection(content, sectionHeader) {
78
+ const lines = content.split("\n");
79
+ const result = [];
80
+ let inSection = false;
81
+ for (const line of lines) {
82
+ if (line.startsWith("##") && line.toLowerCase().includes(sectionHeader.toLowerCase())) {
83
+ inSection = true;
84
+ continue;
85
+ }
86
+ if (inSection) {
87
+ if (line.startsWith("##"))
88
+ break; // next section
89
+ const trimmed = line.trim();
90
+ if (trimmed.startsWith("-") || trimmed.startsWith("*")) {
91
+ result.push(trimmed.replace(/^[-*]\s*/, ""));
92
+ }
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+ /**
98
+ * Check if a POLARIS.md rule explicitly names a changed file as deleted/removed while it still exists.
99
+ * This is the primary `stale-implementation` trigger for MVP.
100
+ */
101
+ function checkPolarisMd(canonFile, changedFiles, repoRoot) {
102
+ const conflicts = [];
103
+ let content;
104
+ try {
105
+ content = (0, node_fs_1.readFileSync)(canonFile, "utf-8");
106
+ }
107
+ catch {
108
+ return conflicts;
109
+ }
110
+ const editingRules = extractSection(content, "editing rules");
111
+ const archAssumptions = extractSection(content, "architecture assumptions");
112
+ const allRules = [...editingRules, ...archAssumptions];
113
+ for (const rule of allRules) {
114
+ const lower = rule.toLowerCase();
115
+ // Detect rules that say something is deleted/removed but the file still exists
116
+ const isDeleted = /\b(deleted|removed|no longer exists|do not use)\b/.test(lower);
117
+ if (!isDeleted)
118
+ continue;
119
+ for (const changedFile of changedFiles) {
120
+ const fileBasename = (0, node_path_1.basename)(changedFile);
121
+ if (lower.includes(fileBasename.toLowerCase()) && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, changedFile))) {
122
+ // Determine conflict type based on rule text and changed file
123
+ let conflictType = "stale-implementation";
124
+ const ext = changedFile.split(".").pop()?.toLowerCase() ?? "";
125
+ const isDocFile = ext === "md" || changedFile.startsWith("docs/");
126
+ const hasDocKeywords = /\b(doc|documentation|readme|guide|spec)\b/i.test(lower);
127
+ const hasCandidateKeywords = /\b(candidate|divergence|proposal)\b/i.test(lower);
128
+ if (isDocFile || hasDocKeywords) {
129
+ conflictType = "stale-docs";
130
+ }
131
+ else if (hasCandidateKeywords) {
132
+ conflictType = "candidate-divergence";
133
+ }
134
+ conflicts.push({
135
+ type: conflictType,
136
+ canonFile,
137
+ statement: rule,
138
+ changedFile,
139
+ detail: `POLARIS.md states "${fileBasename}" is deleted/removed but the file still exists`,
140
+ });
141
+ }
142
+ }
143
+ }
144
+ return conflicts;
145
+ }
146
+ /**
147
+ * Check doctrine/spec files for modal-verb assertions that directly contradict changed files.
148
+ * MVP: only flag stale-implementation when a named file/command is explicitly asserted to
149
+ * exist/be-required but is missing, or asserted as deleted while still present.
150
+ */
151
+ function checkDocFile(canonFile, changedFiles, repoRoot) {
152
+ if ((0, node_path_1.basename)(canonFile).toLowerCase() === "summary.md")
153
+ return [];
154
+ const conflicts = [];
155
+ let content;
156
+ try {
157
+ content = (0, node_fs_1.readFileSync)(canonFile, "utf-8");
158
+ }
159
+ catch {
160
+ return conflicts;
161
+ }
162
+ const lines = content.split("\n");
163
+ for (const line of lines) {
164
+ if (!MODAL_VERBS.test(line))
165
+ continue;
166
+ const lower = line.toLowerCase();
167
+ // Only flag lines that name a specific file/command that should exist but doesn't
168
+ const deletedAssertion = /\b(deleted|removed|no longer exists)\b/.test(lower);
169
+ if (!deletedAssertion)
170
+ continue;
171
+ for (const changedFile of changedFiles) {
172
+ const fileBasename = (0, node_path_1.basename)(changedFile);
173
+ if (lower.includes(fileBasename.toLowerCase()) && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(repoRoot, changedFile))) {
174
+ conflicts.push({
175
+ type: "stale-implementation",
176
+ canonFile,
177
+ statement: line.trim(),
178
+ changedFile,
179
+ detail: `Canon asserts "${fileBasename}" is deleted/removed but file still exists`,
180
+ });
181
+ }
182
+ }
183
+ }
184
+ return conflicts;
185
+ }
186
+ function appendTelemetry(telemetryFile, event) {
187
+ try {
188
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(telemetryFile), { recursive: true });
189
+ (0, node_fs_1.appendFileSync)(telemetryFile, JSON.stringify(event) + "\n", "utf-8");
190
+ }
191
+ catch {
192
+ // Non-fatal: telemetry write failure should not block operation
193
+ }
194
+ }
195
+ /**
196
+ * Run the canon reconciliation check.
197
+ *
198
+ * Emits `canon-check-start` and either `canon-check-result` or `canon-conflict-halt`
199
+ * to the telemetry file. Returns the result for the caller to act on.
200
+ *
201
+ * Note: This function does NOT call process.exit. Callers are responsible for
202
+ * halting execution based on the returned outcome.
203
+ */
204
+ function runCanonCheck(options) {
205
+ const { repoRoot, changedFiles, childId, runId, telemetryFile } = options;
206
+ const canonFilesSet = locateCanonFiles(changedFiles, repoRoot);
207
+ const canonFilesInspected = canonFilesSet.size;
208
+ appendTelemetry(telemetryFile, {
209
+ event: "canon-check-start",
210
+ run_id: runId,
211
+ child_id: childId ?? null,
212
+ canon_files_inspected: canonFilesInspected,
213
+ changed_files_count: changedFiles.length,
214
+ timestamp: new Date().toISOString(),
215
+ });
216
+ if (canonFilesInspected === 0 || changedFiles.length === 0) {
217
+ const result = { outcome: "aligned", conflicts: [], canonFilesInspected };
218
+ appendTelemetry(telemetryFile, {
219
+ event: "canon-check-result",
220
+ run_id: runId,
221
+ child_id: childId ?? null,
222
+ outcome: "aligned",
223
+ conflicts: [],
224
+ timestamp: new Date().toISOString(),
225
+ });
226
+ return result;
227
+ }
228
+ const allConflicts = [];
229
+ for (const canonFile of canonFilesSet) {
230
+ const fileBasename = (0, node_path_1.basename)(canonFile);
231
+ const isPolarisMd = fileBasename === "POLARIS.md";
232
+ const fileConflicts = isPolarisMd
233
+ ? checkPolarisMd(canonFile, changedFiles, repoRoot)
234
+ : checkDocFile(canonFile, changedFiles, repoRoot);
235
+ allConflicts.push(...fileConflicts);
236
+ }
237
+ // Classify outcome: stale-implementation wins; then stale-docs; then candidate-divergence; else aligned
238
+ let outcome = "aligned";
239
+ if (allConflicts.some((c) => c.type === "stale-implementation")) {
240
+ outcome = "stale-implementation";
241
+ }
242
+ else if (allConflicts.some((c) => c.type === "stale-docs")) {
243
+ outcome = "stale-docs";
244
+ }
245
+ else if (allConflicts.some((c) => c.type === "candidate-divergence")) {
246
+ outcome = "candidate-divergence";
247
+ }
248
+ const result = { outcome, conflicts: allConflicts, canonFilesInspected };
249
+ if (outcome === "stale-implementation") {
250
+ const first = allConflicts.find((c) => c.type === "stale-implementation");
251
+ appendTelemetry(telemetryFile, {
252
+ event: "canon-conflict-halt",
253
+ run_id: runId,
254
+ child_id: childId ?? null,
255
+ reason: first.detail,
256
+ canon_file: first.canonFile,
257
+ conflicting_statement: first.statement,
258
+ missing_or_differing: first.changedFile,
259
+ suggested_resolution: "Update the canon file to reflect current state, or remove/fix the conflicting assertion",
260
+ timestamp: new Date().toISOString(),
261
+ });
262
+ }
263
+ else {
264
+ appendTelemetry(telemetryFile, {
265
+ event: "canon-check-result",
266
+ run_id: runId,
267
+ child_id: childId ?? null,
268
+ outcome,
269
+ conflicts: allConflicts,
270
+ timestamp: new Date().toISOString(),
271
+ });
272
+ }
273
+ // For non-blocking outcomes, write draft docs as specified
274
+ if (outcome === "stale-docs") {
275
+ writeStaleDraftDocs(allConflicts.filter((c) => c.type === "stale-docs"), repoRoot, childId);
276
+ }
277
+ else if (outcome === "candidate-divergence") {
278
+ writeCandidateDraftDocs(allConflicts.filter((c) => c.type === "candidate-divergence"), repoRoot, childId);
279
+ }
280
+ return result;
281
+ }
282
+ function writeStaleDraftDocs(conflicts, repoRoot, childId) {
283
+ const rawDir = (0, node_path_1.join)(repoRoot, "docs", "raw");
284
+ try {
285
+ (0, node_fs_1.mkdirSync)(rawDir, { recursive: true });
286
+ for (const conflict of conflicts) {
287
+ const slug = (0, node_path_1.basename)(conflict.canonFile).replace(/\.md$/, "");
288
+ const date = new Date().toISOString().slice(0, 10);
289
+ const filename = `stale-flag-${slug}-${date}.md`;
290
+ const path = (0, node_path_1.join)(rawDir, filename);
291
+ if (!(0, node_fs_1.existsSync)(path)) {
292
+ (0, node_fs_1.writeFileSync)(path, [
293
+ `# Stale Doc Flag`,
294
+ ``,
295
+ `**Canon file:** ${conflict.canonFile}`,
296
+ `**Triggering file:** ${conflict.changedFile}`,
297
+ `**What appears outdated:** ${conflict.statement}`,
298
+ `**Detail:** ${conflict.detail}`,
299
+ `**Flagged by:** ${childId ?? "finalize"}`,
300
+ `**Date:** ${new Date().toISOString()}`,
301
+ ].join("\n"), "utf-8");
302
+ }
303
+ }
304
+ }
305
+ catch {
306
+ // Non-fatal
307
+ }
308
+ }
309
+ function writeCandidateDraftDocs(conflicts, repoRoot, childId) {
310
+ const candidateDir = (0, node_path_1.join)(repoRoot, "docs", "doctrine", "candidate");
311
+ try {
312
+ (0, node_fs_1.mkdirSync)(candidateDir, { recursive: true });
313
+ for (const conflict of conflicts) {
314
+ const slug = (0, node_path_1.basename)(conflict.canonFile).replace(/\.md$/, "");
315
+ const date = new Date().toISOString().slice(0, 10);
316
+ const filename = `${slug}-${date}.draft.md`;
317
+ const path = (0, node_path_1.join)(candidateDir, filename);
318
+ if (!(0, node_fs_1.existsSync)(path)) {
319
+ (0, node_fs_1.writeFileSync)(path, [
320
+ `---`,
321
+ `status: candidate`,
322
+ `source: ${conflict.canonFile}`,
323
+ `proposed-by: ${childId ?? "unknown"}`,
324
+ `proposed-at: ${new Date().toISOString()}`,
325
+ `---`,
326
+ ``,
327
+ `# Candidate Divergence Draft`,
328
+ ``,
329
+ `**Source canon file:** ${conflict.canonFile}`,
330
+ `**Conflicting statement:** ${conflict.statement}`,
331
+ `**Changed file:** ${conflict.changedFile}`,
332
+ `**Detail:** ${conflict.detail}`,
333
+ ].join("\n"), "utf-8");
334
+ }
335
+ }
336
+ }
337
+ catch {
338
+ // Non-fatal
339
+ }
340
+ }