@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,350 @@
1
+ "use strict";
2
+ /**
3
+ * Route-local cognition delta logic.
4
+ *
5
+ * Workers call applyRouteCognitionDelta() after implementation to determine
6
+ * whether POLARIS.md needs updating for the folders they touched. Updates are
7
+ * delta-only: only apply when operationally-relevant changes occurred.
8
+ *
9
+ * Root cognition surfaces (POLARIS.md at repo root) are skipped unless
10
+ * explicitly opted in. Bounded by locality — no repo-wide scanning.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.POLARIS_OWNED_COGNITION_FOLDERS = void 0;
14
+ exports.assessRouteHealth = assessRouteHealth;
15
+ exports.isPolarisOwnedFolder = isPolarisOwnedFolder;
16
+ exports.isUserCreatedCognitionSurface = isUserCreatedCognitionSurface;
17
+ exports.isCognitionSkippedFolder = isCognitionSkippedFolder;
18
+ exports.findNearestRoutePolarismd = findNearestRoutePolarismd;
19
+ exports.detectOperationalReasons = detectOperationalReasons;
20
+ exports.applyRouteCognitionDelta = applyRouteCognitionDelta;
21
+ exports.readRoutePolarismd = readRoutePolarismd;
22
+ const node_fs_1 = require("node:fs");
23
+ const node_path_1 = require("node:path");
24
+ const smartdoc_ignore_js_1 = require("../smartdocs-engine/smartdoc-ignore.js");
25
+ // ── Route Health Assessment ───────────────────────────────────────────────────
26
+ /**
27
+ * Assess the health state of a route based on observable signals.
28
+ *
29
+ * Signals used:
30
+ * - Staleness: entry older than threshold (default 90 days)
31
+ * - Identity completeness: missing instructionFile or role_owner
32
+ *
33
+ * Returns:
34
+ * - "stale": entry is stale (older than threshold)
35
+ * - "known-issues": identity incomplete (missing instructionFile or role_owner)
36
+ * - "healthy": route is fresh, identity complete
37
+ */
38
+ function assessRouteHealth(route, repoRoot, staleThresholdDays = 90) {
39
+ const daysSinceUpdate = (Date.now() - new Date(route.last_updated).getTime()) / (1000 * 60 * 60 * 24);
40
+ // Staleness check (highest priority)
41
+ if (daysSinceUpdate > staleThresholdDays) {
42
+ return "stale";
43
+ }
44
+ // Identity completeness check
45
+ const hasInstructionFile = route.instructionFile !== undefined && route.instructionFile !== null && route.instructionFile.trim() !== "";
46
+ const hasRoleOwner = route.role_owner !== undefined && route.role_owner !== null && route.role_owner.trim() !== "";
47
+ if (!hasInstructionFile || !hasRoleOwner) {
48
+ return "known-issues";
49
+ }
50
+ // Cognition surface check: instructionFile configured but file missing on disk
51
+ const instructionFileAbs = (0, node_path_1.join)(repoRoot, route.instructionFile);
52
+ if (!(0, node_fs_1.existsSync)(instructionFileAbs)) {
53
+ return "monitoring";
54
+ }
55
+ // Recovery window: recently updated (7–threshold days ago)
56
+ if (daysSinceUpdate > 7) {
57
+ return "recovering";
58
+ }
59
+ return "healthy";
60
+ }
61
+ // ── Skipped folder patterns ───────────────────────────────────────────────────
62
+ const SKIP_FOLDER_PREFIXES = [
63
+ ".git/",
64
+ "node_modules/",
65
+ "dist/",
66
+ ".taskchain_artifacts/",
67
+ ];
68
+ const AGENT_OPT_IN_FOLDERS = new Set([".claude", ".codex"]);
69
+ const POLARIS_RUNTIME_COGNITION_FOLDERS = new Set([
70
+ ".polaris",
71
+ ".polaris/bootstrap",
72
+ ".polaris/clusters",
73
+ ".polaris/map",
74
+ ".polaris/runs",
75
+ ]);
76
+ const POLARIS_RUNTIME_GENERATED_PREFIXES = [
77
+ ".polaris/bootstrap/",
78
+ ".polaris/clusters/",
79
+ ".polaris/graph/",
80
+ ".polaris/map/",
81
+ ".polaris/runs/",
82
+ ];
83
+ exports.POLARIS_OWNED_COGNITION_FOLDERS = [
84
+ ".polaris",
85
+ "src",
86
+ "smartdocs/specs/active",
87
+ "smartdocs/doctrine/active",
88
+ ];
89
+ function normalizeRelPath(input) {
90
+ return input.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
91
+ }
92
+ function readManagedSurfaceManifest(repoRoot) {
93
+ const manifestPath = (0, node_path_1.join)(repoRoot, ".polaris", "cognition", "managed-surfaces.json");
94
+ if (!(0, node_fs_1.existsSync)(manifestPath))
95
+ return new Set();
96
+ const raw = (0, node_fs_1.readFileSync)(manifestPath, "utf-8");
97
+ const parsed = JSON.parse(raw);
98
+ const paths = [];
99
+ if (Array.isArray(parsed)) {
100
+ for (const value of parsed) {
101
+ if (typeof value === "string")
102
+ paths.push(value);
103
+ }
104
+ }
105
+ else if (parsed && typeof parsed === "object") {
106
+ const candidateArrays = [
107
+ parsed["surfaces"],
108
+ parsed["managed_surfaces"],
109
+ parsed["managedSurfaces"],
110
+ parsed["paths"],
111
+ ];
112
+ for (const candidate of candidateArrays) {
113
+ if (!Array.isArray(candidate))
114
+ continue;
115
+ for (const value of candidate) {
116
+ if (typeof value === "string")
117
+ paths.push(value);
118
+ }
119
+ }
120
+ }
121
+ return new Set(paths.map((p) => normalizeRelPath(p)));
122
+ }
123
+ /**
124
+ * Tier 1 folders that are always considered Polaris-owned cognition surfaces.
125
+ *
126
+ * Includes fixed roots and dynamic immediate children under src/<subdirectory>.
127
+ */
128
+ function isPolarisOwnedFolder(folderRel) {
129
+ const normalized = normalizeRelPath(folderRel);
130
+ if (!normalized)
131
+ return false;
132
+ if (exports.POLARIS_OWNED_COGNITION_FOLDERS.includes(normalized)) {
133
+ return true;
134
+ }
135
+ return /^src\/[^/]+$/.test(normalized);
136
+ }
137
+ /**
138
+ * Returns true when a cognition surface should be treated as user-created and
139
+ * therefore protected from worker overwrites.
140
+ *
141
+ * A surface is protected when either:
142
+ * 1) It predates Polaris initialization (.polaris birth/creation time), or
143
+ * 2) It is listed in .polaris/cognition/managed-surfaces.json.
144
+ */
145
+ function isUserCreatedCognitionSurface(filePath, repoRoot) {
146
+ const absFile = (0, node_path_1.resolve)(repoRoot, filePath);
147
+ const relFile = normalizeRelPath((0, node_path_1.relative)(repoRoot, absFile));
148
+ if (!relFile || relFile.startsWith(".."))
149
+ return false;
150
+ const managed = readManagedSurfaceManifest(repoRoot);
151
+ if (managed.has(relFile))
152
+ return true;
153
+ if (!(0, node_fs_1.existsSync)(absFile))
154
+ return false;
155
+ const polarisDir = (0, node_path_1.join)(repoRoot, ".polaris");
156
+ if (!(0, node_fs_1.existsSync)(polarisDir))
157
+ return false;
158
+ const fileStat = (0, node_fs_1.statSync)(absFile);
159
+ const polarisStat = (0, node_fs_1.statSync)(polarisDir);
160
+ const polarisInitializedAt = polarisStat.birthtimeMs > 0 ? polarisStat.birthtimeMs : polarisStat.ctimeMs;
161
+ return fileStat.mtimeMs < polarisInitializedAt;
162
+ }
163
+ /**
164
+ * Returns true when a folder is skipped for route-local cognition:
165
+ * generated, runtime, ignored, or agent-only-with-no-opt-in.
166
+ *
167
+ * When repoRoot is provided, also consults .smartdocignore and the shared
168
+ * smartdoc-ignore eligibility rules (RUNTIME_EXCLUDED_DIR_PATTERNS,
169
+ * AGENT_COGNITION_FOLDERS, HIDDEN_SYSTEM_FOLDERS). This ensures cognition
170
+ * scanning honours the same boundaries as Smart Docs ingest.
171
+ */
172
+ function isCognitionSkippedFolder(folderRel, repoRoot) {
173
+ // Fast-path: hardcoded runtime/bootstrap prefixes (no fs needed)
174
+ for (const prefix of SKIP_FOLDER_PREFIXES) {
175
+ if (folderRel === prefix.slice(0, -1) || folderRel.startsWith(prefix)) {
176
+ return true;
177
+ }
178
+ }
179
+ if (POLARIS_RUNTIME_COGNITION_FOLDERS.has(folderRel))
180
+ return false;
181
+ for (const prefix of POLARIS_RUNTIME_GENERATED_PREFIXES) {
182
+ const rootPath = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
183
+ if (folderRel === rootPath || folderRel.startsWith(prefix))
184
+ return true;
185
+ }
186
+ // Agent folders are opt-in only
187
+ const topLevel = folderRel.split("/")[0] ?? folderRel;
188
+ if (AGENT_OPT_IN_FOLDERS.has(topLevel))
189
+ return true;
190
+ // When repoRoot is available, defer to the smartdoc-ignore authority.
191
+ // This ensures .smartdocignore patterns and shared exclusion lists are
192
+ // honoured — POLARIS.md and SUMMARY.md are bounded agent/context files,
193
+ // not ingest candidates.
194
+ if (repoRoot) {
195
+ const eligibility = (0, smartdoc_ignore_js_1.isDirectoryEligible)(folderRel, repoRoot);
196
+ if (!eligibility.eligible)
197
+ return true;
198
+ // Also check .smartdocignore patterns directly for directory paths
199
+ if (!folderRel.startsWith(".polaris")) {
200
+ const ig = (0, smartdoc_ignore_js_1.parseSmartDocIgnore)(repoRoot);
201
+ if (ig.ignores(folderRel) || ig.ignores(`${folderRel}/`))
202
+ return true;
203
+ }
204
+ }
205
+ return false;
206
+ }
207
+ // ── Nearest route-local POLARIS.md ────────────────────────────────────────────
208
+ /**
209
+ * Walk upward from `filePath` (relative to repoRoot) and return the nearest
210
+ * non-root POLARIS.md. Returns null when only root or no POLARIS.md exists.
211
+ */
212
+ function findNearestRoutePolarismd(filePath, repoRoot, skipRoot) {
213
+ const parts = filePath.split("/");
214
+ // Walk from deepest dir upward, excluding the root level when skipRoot=true
215
+ for (let i = parts.length - 1; i >= 1; i--) {
216
+ const dir = parts.slice(0, i).join("/");
217
+ if (isCognitionSkippedFolder(dir, repoRoot))
218
+ continue;
219
+ const candidate = (0, node_path_1.join)(repoRoot, dir, "POLARIS.md");
220
+ if ((0, node_fs_1.existsSync)(candidate)) {
221
+ return (0, node_path_1.relative)(repoRoot, candidate).replace(/\\/g, "/");
222
+ }
223
+ }
224
+ // Fall back to root only when not skipping root
225
+ if (!skipRoot) {
226
+ const rootCandidate = (0, node_path_1.join)(repoRoot, "POLARIS.md");
227
+ if ((0, node_fs_1.existsSync)(rootCandidate))
228
+ return "POLARIS.md";
229
+ }
230
+ return null;
231
+ }
232
+ // ── Change signal detection ───────────────────────────────────────────────────
233
+ /** Signals in a file path that suggest operational relevance. */
234
+ const OPERATIONAL_SIGNALS = [
235
+ { pattern: /\/index\.ts$/, reason: "folder-responsibilities-changed" },
236
+ { pattern: /\/cli\//, reason: "commands-workflows-changed" },
237
+ { pattern: /\/commands?\//, reason: "commands-workflows-changed" },
238
+ { pattern: /scripts\//, reason: "commands-workflows-changed" },
239
+ { pattern: /\/config\//, reason: "execution-constraints-changed" },
240
+ { pattern: /\/schema\.ts$/, reason: "execution-constraints-changed" },
241
+ { pattern: /\/loader\.ts$/, reason: "execution-constraints-changed" },
242
+ { pattern: /\/map\//, reason: "ownership-routing-changed" },
243
+ { pattern: /\/atlas\.ts$/, reason: "ownership-routing-changed" },
244
+ { pattern: /\/inference\.ts$/, reason: "ownership-routing-changed" },
245
+ { pattern: /\/dispatch/, reason: "operational-behavior-changed" },
246
+ { pattern: /\/worker\.ts$/, reason: "operational-behavior-changed" },
247
+ { pattern: /\/parent\.ts$/, reason: "operational-behavior-changed" },
248
+ { pattern: /\/finalize\//, reason: "operational-behavior-changed" },
249
+ { pattern: /\/cognition\//, reason: "operational-behavior-changed" },
250
+ ];
251
+ /**
252
+ * Determine whether a set of touched files warrants a POLARIS.md update.
253
+ * Returns the reasons found; empty array means no update warranted.
254
+ */
255
+ function detectOperationalReasons(touchedFiles) {
256
+ const reasonSet = new Set();
257
+ for (const file of touchedFiles) {
258
+ // Skip test files, comments-only changes, etc.
259
+ if (file.endsWith(".test.ts") || file.endsWith(".test.js"))
260
+ continue;
261
+ if (file.endsWith(".md") || file.endsWith(".json")) {
262
+ // Only config json or doc-linking md are operationally relevant
263
+ if (!file.includes("config") && !file.endsWith("POLARIS.md") && !file.endsWith("SUMMARY.md")) {
264
+ continue;
265
+ }
266
+ }
267
+ for (const signal of OPERATIONAL_SIGNALS) {
268
+ if (signal.pattern.test(file)) {
269
+ reasonSet.add(signal.reason);
270
+ }
271
+ }
272
+ }
273
+ return Array.from(reasonSet);
274
+ }
275
+ // ── Missing surface detection ─────────────────────────────────────────────────
276
+ /**
277
+ * For each touched file, check whether its nearest eligible folder has a
278
+ * POLARIS.md. Return folders that are newly eligible but missing one.
279
+ */
280
+ function detectMissingCognitionSurfaces(touchedFiles, repoRoot, skipRoot) {
281
+ const missing = new Set();
282
+ for (const file of touchedFiles) {
283
+ const parts = file.split("/");
284
+ for (let i = parts.length - 1; i >= 1; i--) {
285
+ const dir = parts.slice(0, i).join("/");
286
+ if (isCognitionSkippedFolder(dir, repoRoot))
287
+ continue;
288
+ const candidate = (0, node_path_1.join)(repoRoot, dir, "POLARIS.md");
289
+ if (!(0, node_fs_1.existsSync)(candidate)) {
290
+ // Only report if there are source files in this folder (not empty dirs)
291
+ missing.add(dir);
292
+ }
293
+ break; // Only report the nearest ancestor folder per file
294
+ }
295
+ }
296
+ return Array.from(missing);
297
+ }
298
+ // ── Main delta entry point ────────────────────────────────────────────────────
299
+ /**
300
+ * Apply route-local cognition delta logic after child implementation.
301
+ *
302
+ * Does NOT write POLARIS.md. Returns a result describing what was found so
303
+ * that the worker prompt can instruct the agent accordingly, or the caller can
304
+ * conditionally trigger an update.
305
+ */
306
+ function applyRouteCognitionDelta(options) {
307
+ const { repoRoot, touchedFiles, skipRoot = true } = options;
308
+ const inspectedFolders = new Set();
309
+ const routeLocalTargets = new Set();
310
+ const missingCognitionSurfaces = [];
311
+ for (const file of touchedFiles) {
312
+ const dir = (0, node_path_1.dirname)(file);
313
+ if (dir && dir !== ".") {
314
+ if (!isCognitionSkippedFolder(dir)) {
315
+ inspectedFolders.add(dir);
316
+ }
317
+ }
318
+ const target = findNearestRoutePolarismd(file, repoRoot, skipRoot);
319
+ if (target)
320
+ routeLocalTargets.add(target);
321
+ }
322
+ const reasons = detectOperationalReasons(touchedFiles);
323
+ const updateWarranted = reasons.length > 0;
324
+ const missing = detectMissingCognitionSurfaces(touchedFiles, repoRoot, skipRoot);
325
+ missingCognitionSurfaces.push(...missing);
326
+ return {
327
+ inspectedFolders: Array.from(inspectedFolders),
328
+ routeLocalTargets: Array.from(routeLocalTargets),
329
+ updateWarranted,
330
+ reasons,
331
+ missingCognitionSurfaces,
332
+ healthState: undefined,
333
+ };
334
+ }
335
+ // ── POLARIS.md content read helper ────────────────────────────────────────────
336
+ /**
337
+ * Read the content of a route-local POLARIS.md (relative to repoRoot).
338
+ * Returns null when missing.
339
+ */
340
+ function readRoutePolarismd(polarisRel, repoRoot) {
341
+ const abs = (0, node_path_1.resolve)(repoRoot, polarisRel);
342
+ if (!(0, node_fs_1.existsSync)(abs))
343
+ return null;
344
+ try {
345
+ return (0, node_fs_1.readFileSync)(abs, "utf-8");
346
+ }
347
+ catch {
348
+ return null;
349
+ }
350
+ }
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ /**
3
+ * SUMMARY.md delta logic.
4
+ *
5
+ * SUMMARY.md is the informational/contextual compression surface for a route.
6
+ * It must stay short, remain informational only, and never become operational
7
+ * doctrine or a giant architecture dump.
8
+ *
9
+ * Workers and ingest pipelines call applySummaryDelta() to determine whether
10
+ * a SUMMARY.md update is warranted. Updates are delta-only: only spec/canon/
11
+ * architecture/doctrine-linkage changes trigger a SUMMARY.md refresh.
12
+ *
13
+ * Root SUMMARY.md is skipped — route-local cognition begins below root.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.SUMMARY_MAX_BYTES = void 0;
17
+ exports.detectPrecedenceLevel = detectPrecedenceLevel;
18
+ exports.detectSummaryReasons = detectSummaryReasons;
19
+ exports.detectSourcePathSignals = detectSourcePathSignals;
20
+ exports.findNearestSummarymd = findNearestSummarymd;
21
+ exports.detectMissingSummaries = detectMissingSummaries;
22
+ exports.applySummaryDelta = applySummaryDelta;
23
+ exports.readSummarymd = readSummarymd;
24
+ exports.isSummaryOversized = isSummaryOversized;
25
+ exports.hasDoctrineBled = hasDoctrineBled;
26
+ const node_fs_1 = require("node:fs");
27
+ const node_path_1 = require("node:path");
28
+ const route_cognition_delta_js_1 = require("./route-cognition-delta.js");
29
+ const doctrine_js_1 = require("../smartdocs-engine/doctrine.js");
30
+ // ── Signals that trigger SUMMARY.md delta ─────────────────────────────────────
31
+ const SUMMARY_SIGNALS = [
32
+ { pattern: /smartdocs\/docs\/specs\/raw\//, reason: "linked-docs-changed" },
33
+ { pattern: /smartdocs\/docs\/specs\/active\//, reason: "linked-docs-changed" },
34
+ { pattern: /smartdocs\/docs\/doctrine\/active\//, reason: "doctrine-spec-linkage-changed" },
35
+ { pattern: /smartdocs\/docs\/architecture\//, reason: "architecture-meaning-changed" },
36
+ { pattern: /smartdocs\/docs\/decisions\//, reason: "doctrine-spec-linkage-changed" },
37
+ { pattern: /\/canon\//, reason: "canon-relationships-changed" },
38
+ { pattern: /POLARIS\.md$/, reason: "canon-relationships-changed" },
39
+ { pattern: /AGENTS\.md$/, reason: "canon-relationships-changed" },
40
+ { pattern: /CLAUDE\.md$/, reason: "canon-relationships-changed" },
41
+ { pattern: /polaris\.config\.json$/, reason: "canon-relationships-changed" },
42
+ ];
43
+ /**
44
+ * Determine the highest-precedence cognition source for the given touched files.
45
+ *
46
+ * Priority (highest first):
47
+ * 1. promoted-doctrine — active doctrine files in smartdocs/doctrine/active/
48
+ * 2. spec-or-arch — linked specs, architecture, or decision docs
49
+ * 3. route-polaris-md — a route-local POLARIS.md was touched
50
+ * 4. source-inference — no above signals; fallback
51
+ */
52
+ function detectPrecedenceLevel(touchedFiles) {
53
+ for (const file of touchedFiles) {
54
+ if (/smartdocs\/docs\/doctrine\/active\//.test(file))
55
+ return "promoted-doctrine";
56
+ }
57
+ for (const file of touchedFiles) {
58
+ if (/smartdocs\/docs\/specs\/active\//.test(file) ||
59
+ /smartdocs\/docs\/architecture\//.test(file) ||
60
+ /smartdocs\/docs\/decisions\//.test(file) ||
61
+ /smartdocs\/docs\/specs\/raw\//.test(file))
62
+ return "spec-or-arch";
63
+ }
64
+ for (const file of touchedFiles) {
65
+ if (/POLARIS\.md$/.test(file))
66
+ return "route-polaris-md";
67
+ }
68
+ return "source-inference";
69
+ }
70
+ /**
71
+ * Determine which summary-related delta reasons are triggered by the provided file paths.
72
+ *
73
+ * @param touchedFiles - List of touched file paths (relative-like strings) to evaluate against summary signal patterns
74
+ * @returns An array of unique `SummaryDeltaReason` values that matched any signal pattern for the given files
75
+ */
76
+ function detectSummaryReasons(touchedFiles) {
77
+ const reasonSet = new Set();
78
+ for (const file of touchedFiles) {
79
+ for (const signal of SUMMARY_SIGNALS) {
80
+ if (signal.pattern.test(file)) {
81
+ reasonSet.add(signal.reason);
82
+ }
83
+ }
84
+ }
85
+ return Array.from(reasonSet);
86
+ }
87
+ // ── source_paths enrichment ───────────────────────────────────────────────────
88
+ const SMARTDOC_SCAN_DIRS = [
89
+ "smartdocs/doctrine/active",
90
+ "smartdocs/specs/active",
91
+ ];
92
+ /**
93
+ * Detects whether any active SmartDoc declares a `source_paths` entry that exactly matches a touched file.
94
+ *
95
+ * @param touchedFiles - Array of touched file paths (normalized-ish strings, typically repository-relative)
96
+ * @param repoRoot - Filesystem path to the repository root used to locate active SmartDocs
97
+ * @returns `true` if at least one `source_paths` entry in an active SmartDoc matches a touched file, `false` otherwise
98
+ */
99
+ function detectSourcePathSignals(touchedFiles, repoRoot) {
100
+ if (touchedFiles.length === 0)
101
+ return false;
102
+ const touchedSet = new Set(touchedFiles.map((f) => f.replace(/\\/g, "/")));
103
+ for (const scanDir of SMARTDOC_SCAN_DIRS) {
104
+ const absDir = (0, node_path_1.join)(repoRoot, scanDir);
105
+ if (!(0, node_fs_1.existsSync)(absDir))
106
+ continue;
107
+ let entries;
108
+ try {
109
+ entries = (0, node_fs_1.readdirSync)(absDir);
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ for (const entry of entries) {
115
+ if (!entry.endsWith(".md"))
116
+ continue;
117
+ const absFile = (0, node_path_1.join)(absDir, entry);
118
+ let content;
119
+ try {
120
+ content = (0, node_fs_1.readFileSync)(absFile, "utf-8");
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ const fm = (0, doctrine_js_1.parseFrontMatter)(content);
126
+ const sourcePaths = fm["source_paths"];
127
+ if (!sourcePaths)
128
+ continue;
129
+ const paths = sourcePaths
130
+ .split(",")
131
+ .map((p) => p.trim())
132
+ .filter(Boolean);
133
+ for (const sp of paths) {
134
+ if (touchedSet.has(sp))
135
+ return true;
136
+ }
137
+ }
138
+ }
139
+ return false;
140
+ }
141
+ // ── Nearest route SUMMARY.md ──────────────────────────────────────────────────
142
+ /**
143
+ * Find the nearest SUMMARY.md relative to a given file (below root).
144
+ * Returns null when none found or only root exists.
145
+ */
146
+ function findNearestSummarymd(filePath, repoRoot, skipRoot) {
147
+ const parts = filePath.split("/");
148
+ for (let i = parts.length - 1; i >= 1; i--) {
149
+ const dir = parts.slice(0, i).join("/");
150
+ if ((0, route_cognition_delta_js_1.isCognitionSkippedFolder)(dir, repoRoot))
151
+ continue;
152
+ const candidate = (0, node_path_1.join)(repoRoot, dir, "SUMMARY.md");
153
+ if ((0, node_fs_1.existsSync)(candidate)) {
154
+ return (0, node_path_1.relative)(repoRoot, candidate).replace(/\\/g, "/");
155
+ }
156
+ }
157
+ if (!skipRoot) {
158
+ const rootCandidate = (0, node_path_1.join)(repoRoot, "SUMMARY.md");
159
+ if ((0, node_fs_1.existsSync)(rootCandidate))
160
+ return "SUMMARY.md";
161
+ }
162
+ return null;
163
+ }
164
+ // ── Missing SUMMARY.md detection ──────────────────────────────────────────────
165
+ /**
166
+ * For each touched file, find folders that have a POLARIS.md but are missing a
167
+ * SUMMARY.md. These are eligible for a SUMMARY.md draft.
168
+ *
169
+ * Informational only — callers should surface this as a hint, not pressure.
170
+ * Workers must NOT create SUMMARY.md automatically from this result.
171
+ */
172
+ function detectMissingSummaries(touchedFiles, repoRoot, skipRoot) {
173
+ const missing = new Set();
174
+ for (const file of touchedFiles) {
175
+ const parts = file.split("/");
176
+ for (let i = parts.length - 1; i >= 1; i--) {
177
+ const dir = parts.slice(0, i).join("/");
178
+ if ((0, route_cognition_delta_js_1.isCognitionSkippedFolder)(dir, repoRoot))
179
+ continue;
180
+ const hasPolarismd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, dir, "POLARIS.md"));
181
+ const hasSummarymd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, dir, "SUMMARY.md"));
182
+ if (hasPolarismd && !hasSummarymd) {
183
+ missing.add(dir);
184
+ }
185
+ break;
186
+ }
187
+ if (!skipRoot) {
188
+ const hasPolarismd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "POLARIS.md"));
189
+ const hasSummarymd = (0, node_fs_1.existsSync)((0, node_path_1.join)(repoRoot, "SUMMARY.md"));
190
+ if (hasPolarismd && !hasSummarymd) {
191
+ missing.add(".");
192
+ }
193
+ }
194
+ }
195
+ return Array.from(missing);
196
+ }
197
+ // ── Main delta entry point ────────────────────────────────────────────────────
198
+ /**
199
+ * Determine whether a route-local SUMMARY.md should be updated based on the given touched files.
200
+ *
201
+ * Does not modify repository files; analyzes touched paths and repository state to decide
202
+ * whether a SUMMARY.md update is warranted and where updates or drafts might be targeted.
203
+ *
204
+ * @param options - Analysis options: `repoRoot` (repository root path), `touchedFiles` (list of touched file paths), and `skipRoot` (optional; when true, exclude root `SUMMARY.md` from consideration; defaults to `true`)
205
+ * @returns A `SummaryDeltaResult` containing:
206
+ * - `updateWarranted`: `true` if an update is recommended, `false` otherwise;
207
+ * - `reasons`: triggered `SummaryDeltaReason` values;
208
+ * - `summaryTargets`: relative paths of nearest `SUMMARY.md` candidates;
209
+ * - `missingSummaries`: directories that contain `POLARIS.md` but lack `SUMMARY.md`;
210
+ * - `precedenceSource`: computed `SummaryPrecedenceLevel`.
211
+ */
212
+ function applySummaryDelta(options) {
213
+ const { repoRoot, touchedFiles, skipRoot = true } = options;
214
+ const reasons = detectSummaryReasons(touchedFiles);
215
+ // Enrich signals: if any touched file appears in source_paths of an active SmartDoc,
216
+ // treat this as a linked-docs-changed signal even if path patterns didn't match.
217
+ if (detectSourcePathSignals(touchedFiles, repoRoot)) {
218
+ if (!reasons.includes("linked-docs-changed")) {
219
+ reasons.push("linked-docs-changed");
220
+ }
221
+ }
222
+ const updateWarranted = reasons.length > 0;
223
+ const precedenceSource = detectPrecedenceLevel(touchedFiles);
224
+ const summaryTargets = new Set();
225
+ for (const file of touchedFiles) {
226
+ const target = findNearestSummarymd(file, repoRoot, skipRoot);
227
+ if (target)
228
+ summaryTargets.add(target);
229
+ }
230
+ const missingSummaries = detectMissingSummaries(touchedFiles, repoRoot, skipRoot);
231
+ return {
232
+ updateWarranted,
233
+ reasons,
234
+ summaryTargets: Array.from(summaryTargets),
235
+ missingSummaries,
236
+ precedenceSource,
237
+ };
238
+ }
239
+ // ── SUMMARY.md content guard ──────────────────────────────────────────────────
240
+ exports.SUMMARY_MAX_BYTES = 4096;
241
+ /**
242
+ * Read SUMMARY.md content (relative to repoRoot). Returns null when missing.
243
+ */
244
+ function readSummarymd(summaryRel, repoRoot) {
245
+ const abs = (0, node_path_1.resolve)(repoRoot, summaryRel);
246
+ if (!(0, node_fs_1.existsSync)(abs))
247
+ return null;
248
+ try {
249
+ return (0, node_fs_1.readFileSync)(abs, "utf-8");
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
255
+ /**
256
+ * Returns true when a SUMMARY.md content string exceeds the size guard.
257
+ */
258
+ function isSummaryOversized(content) {
259
+ return Buffer.byteLength(content, "utf-8") > exports.SUMMARY_MAX_BYTES;
260
+ }
261
+ /**
262
+ * Returns true when a SUMMARY.md contains strong doctrine bleed patterns
263
+ * (operational imperatives that belong in POLARIS.md).
264
+ *
265
+ * Deliberately narrow to avoid false-positives from quoted text or normal
266
+ * prose that happens to contain "do not" or "never" in context.
267
+ * This check is WARN-only — it must never be a hard blocker.
268
+ */
269
+ function hasDoctrineBled(content) {
270
+ const lower = content.toLowerCase();
271
+ // Only flag unambiguous operational section headings and strong imperatives
272
+ // that clearly belong in POLARIS.md rather than informational summaries.
273
+ return (lower.includes("## editing rules") ||
274
+ lower.includes("## constraints") ||
275
+ lower.includes("## stop rule") ||
276
+ lower.includes("## forbidden") ||
277
+ // Strong imperative pattern: "must always" or "must never" at line start
278
+ /^\s*[-*]?\s*\w[^.]*\bmust\s+(always|never)\b/m.test(lower));
279
+ }