@specship/specship-win32-arm64 0.24.0 → 0.28.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 (341) hide show
  1. package/bin/specship.cmd +31 -1
  2. package/lib/dist/activation/cheatsheet.d.ts +17 -1
  3. package/lib/dist/activation/cheatsheet.d.ts.map +1 -1
  4. package/lib/dist/activation/cheatsheet.js +47 -3
  5. package/lib/dist/activation/cheatsheet.js.map +1 -1
  6. package/lib/dist/activation/hook-output.d.ts +80 -0
  7. package/lib/dist/activation/hook-output.d.ts.map +1 -0
  8. package/lib/dist/activation/hook-output.js +103 -0
  9. package/lib/dist/activation/hook-output.js.map +1 -0
  10. package/lib/dist/activation/search-intercept.d.ts +60 -29
  11. package/lib/dist/activation/search-intercept.d.ts.map +1 -1
  12. package/lib/dist/activation/search-intercept.js +159 -55
  13. package/lib/dist/activation/search-intercept.js.map +1 -1
  14. package/lib/dist/activation/steering.d.ts +11 -13
  15. package/lib/dist/activation/steering.d.ts.map +1 -1
  16. package/lib/dist/activation/steering.js +25 -10
  17. package/lib/dist/activation/steering.js.map +1 -1
  18. package/lib/dist/behaviour/behaviour-surface.d.ts +47 -0
  19. package/lib/dist/behaviour/behaviour-surface.d.ts.map +1 -1
  20. package/lib/dist/behaviour/behaviour-surface.js +108 -2
  21. package/lib/dist/behaviour/behaviour-surface.js.map +1 -1
  22. package/lib/dist/bin/node-version-check.d.ts +22 -7
  23. package/lib/dist/bin/node-version-check.d.ts.map +1 -1
  24. package/lib/dist/bin/node-version-check.js +39 -9
  25. package/lib/dist/bin/node-version-check.js.map +1 -1
  26. package/lib/dist/bin/specship.js +989 -116
  27. package/lib/dist/bin/specship.js.map +1 -1
  28. package/lib/dist/commands/specship/learn.md +27 -3
  29. package/lib/dist/commands/specship/spec.md +85 -15
  30. package/lib/dist/db/spec-queries.d.ts +73 -3
  31. package/lib/dist/db/spec-queries.d.ts.map +1 -1
  32. package/lib/dist/db/spec-queries.js +139 -5
  33. package/lib/dist/db/spec-queries.js.map +1 -1
  34. package/lib/dist/db/sqlite-adapter.d.ts +7 -5
  35. package/lib/dist/db/sqlite-adapter.d.ts.map +1 -1
  36. package/lib/dist/db/sqlite-adapter.js +11 -9
  37. package/lib/dist/db/sqlite-adapter.js.map +1 -1
  38. package/lib/dist/enforce/changed-files.d.ts +39 -0
  39. package/lib/dist/enforce/changed-files.d.ts.map +1 -0
  40. package/lib/dist/enforce/changed-files.js +114 -0
  41. package/lib/dist/enforce/changed-files.js.map +1 -0
  42. package/lib/dist/enforce/enforce.d.ts +53 -2
  43. package/lib/dist/enforce/enforce.d.ts.map +1 -1
  44. package/lib/dist/enforce/enforce.js +95 -8
  45. package/lib/dist/enforce/enforce.js.map +1 -1
  46. package/lib/dist/enforce/mutation.d.ts +46 -0
  47. package/lib/dist/enforce/mutation.d.ts.map +1 -0
  48. package/lib/dist/enforce/mutation.js +104 -0
  49. package/lib/dist/enforce/mutation.js.map +1 -0
  50. package/lib/dist/enforce/suppressions.d.ts +33 -0
  51. package/lib/dist/enforce/suppressions.d.ts.map +1 -0
  52. package/lib/dist/enforce/suppressions.js +159 -0
  53. package/lib/dist/enforce/suppressions.js.map +1 -0
  54. package/lib/dist/extraction/grammars.d.ts +10 -0
  55. package/lib/dist/extraction/grammars.d.ts.map +1 -1
  56. package/lib/dist/extraction/grammars.js +32 -3
  57. package/lib/dist/extraction/grammars.js.map +1 -1
  58. package/lib/dist/extraction/index.d.ts +7 -0
  59. package/lib/dist/extraction/index.d.ts.map +1 -1
  60. package/lib/dist/extraction/index.js.map +1 -1
  61. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts +7 -0
  62. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  63. package/lib/dist/extraction/specs/markdown-spec-extractor.js +97 -10
  64. package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  65. package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm.gz +0 -0
  66. package/lib/dist/extraction/wasm/tree-sitter-scala.wasm.gz +0 -0
  67. package/lib/dist/graph/spec-coverage.d.ts +142 -0
  68. package/lib/dist/graph/spec-coverage.d.ts.map +1 -0
  69. package/lib/dist/graph/spec-coverage.js +276 -0
  70. package/lib/dist/graph/spec-coverage.js.map +1 -0
  71. package/lib/dist/health/smoke-check.js +2 -2
  72. package/lib/dist/health/smoke-check.js.map +1 -1
  73. package/lib/dist/index.d.ts +75 -7
  74. package/lib/dist/index.d.ts.map +1 -1
  75. package/lib/dist/index.js +191 -23
  76. package/lib/dist/index.js.map +1 -1
  77. package/lib/dist/installer/gemini-commands.d.ts +107 -0
  78. package/lib/dist/installer/gemini-commands.d.ts.map +1 -0
  79. package/lib/dist/installer/gemini-commands.js +264 -0
  80. package/lib/dist/installer/gemini-commands.js.map +1 -0
  81. package/lib/dist/installer/index.d.ts +55 -8
  82. package/lib/dist/installer/index.d.ts.map +1 -1
  83. package/lib/dist/installer/index.js +72 -53
  84. package/lib/dist/installer/index.js.map +1 -1
  85. package/lib/dist/installer/init-offer.d.ts +14 -9
  86. package/lib/dist/installer/init-offer.d.ts.map +1 -1
  87. package/lib/dist/installer/init-offer.js +13 -9
  88. package/lib/dist/installer/init-offer.js.map +1 -1
  89. package/lib/dist/installer/install-defaults.d.ts +81 -0
  90. package/lib/dist/installer/install-defaults.d.ts.map +1 -0
  91. package/lib/dist/installer/install-defaults.js +95 -0
  92. package/lib/dist/installer/install-defaults.js.map +1 -0
  93. package/lib/dist/installer/instructions-template.d.ts +22 -0
  94. package/lib/dist/installer/instructions-template.d.ts.map +1 -1
  95. package/lib/dist/installer/instructions-template.js +37 -1
  96. package/lib/dist/installer/instructions-template.js.map +1 -1
  97. package/lib/dist/installer/targets/claude.d.ts +18 -0
  98. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  99. package/lib/dist/installer/targets/claude.js +70 -23
  100. package/lib/dist/installer/targets/claude.js.map +1 -1
  101. package/lib/dist/installer/targets/gemini.d.ts +85 -12
  102. package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
  103. package/lib/dist/installer/targets/gemini.js +527 -30
  104. package/lib/dist/installer/targets/gemini.js.map +1 -1
  105. package/lib/dist/installer/targets/shared.d.ts +13 -0
  106. package/lib/dist/installer/targets/shared.d.ts.map +1 -1
  107. package/lib/dist/installer/targets/shared.js +22 -4
  108. package/lib/dist/installer/targets/shared.js.map +1 -1
  109. package/lib/dist/installer/targets/types.d.ts +12 -0
  110. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  111. package/lib/dist/jira/auto-publish.d.ts +71 -4
  112. package/lib/dist/jira/auto-publish.d.ts.map +1 -1
  113. package/lib/dist/jira/auto-publish.js +186 -17
  114. package/lib/dist/jira/auto-publish.js.map +1 -1
  115. package/lib/dist/jira/reconcile.d.ts +24 -1
  116. package/lib/dist/jira/reconcile.d.ts.map +1 -1
  117. package/lib/dist/jira/reconcile.js +46 -2
  118. package/lib/dist/jira/reconcile.js.map +1 -1
  119. package/lib/dist/jira/repo-config.d.ts +9 -0
  120. package/lib/dist/jira/repo-config.d.ts.map +1 -1
  121. package/lib/dist/jira/repo-config.js +11 -0
  122. package/lib/dist/jira/repo-config.js.map +1 -1
  123. package/lib/dist/jira/types.d.ts +7 -0
  124. package/lib/dist/jira/types.d.ts.map +1 -1
  125. package/lib/dist/jira/types.js.map +1 -1
  126. package/lib/dist/knowledge/enforce.d.ts +75 -0
  127. package/lib/dist/knowledge/enforce.d.ts.map +1 -0
  128. package/lib/dist/knowledge/enforce.js +250 -0
  129. package/lib/dist/knowledge/enforce.js.map +1 -0
  130. package/lib/dist/knowledge/facts.d.ts +33 -0
  131. package/lib/dist/knowledge/facts.d.ts.map +1 -0
  132. package/lib/dist/knowledge/facts.js +117 -0
  133. package/lib/dist/knowledge/facts.js.map +1 -0
  134. package/lib/dist/knowledge/proposals.d.ts +88 -0
  135. package/lib/dist/knowledge/proposals.d.ts.map +1 -0
  136. package/lib/dist/knowledge/proposals.js +360 -0
  137. package/lib/dist/knowledge/proposals.js.map +1 -0
  138. package/lib/dist/knowledge/stale.d.ts +64 -0
  139. package/lib/dist/knowledge/stale.d.ts.map +1 -0
  140. package/lib/dist/knowledge/stale.js +174 -0
  141. package/lib/dist/knowledge/stale.js.map +1 -0
  142. package/lib/dist/mcp/fitness-tool.d.ts +5 -6
  143. package/lib/dist/mcp/fitness-tool.d.ts.map +1 -1
  144. package/lib/dist/mcp/fitness-tool.js +0 -16
  145. package/lib/dist/mcp/fitness-tool.js.map +1 -1
  146. package/lib/dist/mcp/gemini-session.d.ts +50 -0
  147. package/lib/dist/mcp/gemini-session.d.ts.map +1 -0
  148. package/lib/dist/mcp/gemini-session.js +159 -0
  149. package/lib/dist/mcp/gemini-session.js.map +1 -0
  150. package/lib/dist/mcp/health-tool.d.ts +26 -0
  151. package/lib/dist/mcp/health-tool.d.ts.map +1 -0
  152. package/lib/dist/mcp/health-tool.js +50 -0
  153. package/lib/dist/mcp/health-tool.js.map +1 -0
  154. package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
  155. package/lib/dist/mcp/jira-tools.js +25 -0
  156. package/lib/dist/mcp/jira-tools.js.map +1 -1
  157. package/lib/dist/mcp/maintainability-tool.d.ts +5 -7
  158. package/lib/dist/mcp/maintainability-tool.d.ts.map +1 -1
  159. package/lib/dist/mcp/maintainability-tool.js +0 -16
  160. package/lib/dist/mcp/maintainability-tool.js.map +1 -1
  161. package/lib/dist/mcp/model-context.d.ts +31 -7
  162. package/lib/dist/mcp/model-context.d.ts.map +1 -1
  163. package/lib/dist/mcp/model-context.js +70 -21
  164. package/lib/dist/mcp/model-context.js.map +1 -1
  165. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  166. package/lib/dist/mcp/proxy.js +5 -2
  167. package/lib/dist/mcp/proxy.js.map +1 -1
  168. package/lib/dist/mcp/server-instructions.d.ts +16 -15
  169. package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
  170. package/lib/dist/mcp/server-instructions.js +72 -5
  171. package/lib/dist/mcp/server-instructions.js.map +1 -1
  172. package/lib/dist/mcp/session.js +1 -1
  173. package/lib/dist/mcp/session.js.map +1 -1
  174. package/lib/dist/mcp/spec-tools.d.ts +9 -1
  175. package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
  176. package/lib/dist/mcp/spec-tools.js +282 -49
  177. package/lib/dist/mcp/spec-tools.js.map +1 -1
  178. package/lib/dist/mcp/tools.d.ts +49 -9
  179. package/lib/dist/mcp/tools.d.ts.map +1 -1
  180. package/lib/dist/mcp/tools.js +210 -58
  181. package/lib/dist/mcp/tools.js.map +1 -1
  182. package/lib/dist/pulse/pulse.d.ts +93 -0
  183. package/lib/dist/pulse/pulse.d.ts.map +1 -0
  184. package/lib/dist/pulse/pulse.js +401 -0
  185. package/lib/dist/pulse/pulse.js.map +1 -0
  186. package/lib/dist/reflect/apply.d.ts.map +1 -1
  187. package/lib/dist/reflect/apply.js +38 -3
  188. package/lib/dist/reflect/apply.js.map +1 -1
  189. package/lib/dist/reflect/auto-sweep.d.ts +26 -0
  190. package/lib/dist/reflect/auto-sweep.d.ts.map +1 -0
  191. package/lib/dist/reflect/auto-sweep.js +90 -0
  192. package/lib/dist/reflect/auto-sweep.js.map +1 -0
  193. package/lib/dist/reflect/cli.d.ts +62 -0
  194. package/lib/dist/reflect/cli.d.ts.map +1 -0
  195. package/lib/dist/reflect/cli.js +163 -0
  196. package/lib/dist/reflect/cli.js.map +1 -0
  197. package/lib/dist/reflect/miner.d.ts.map +1 -1
  198. package/lib/dist/reflect/miner.js +119 -0
  199. package/lib/dist/reflect/miner.js.map +1 -1
  200. package/lib/dist/reflect/sweep.d.ts +1 -0
  201. package/lib/dist/reflect/sweep.d.ts.map +1 -1
  202. package/lib/dist/reflect/sweep.js +62 -13
  203. package/lib/dist/reflect/sweep.js.map +1 -1
  204. package/lib/dist/reflect/targets.d.ts +2 -0
  205. package/lib/dist/reflect/targets.d.ts.map +1 -1
  206. package/lib/dist/reflect/targets.js +33 -0
  207. package/lib/dist/reflect/targets.js.map +1 -1
  208. package/lib/dist/reflect/types.d.ts +19 -2
  209. package/lib/dist/reflect/types.d.ts.map +1 -1
  210. package/lib/dist/resolution/spec-link-resolver.d.ts +44 -2
  211. package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
  212. package/lib/dist/resolution/spec-link-resolver.js +132 -0
  213. package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
  214. package/lib/dist/resolution/test-title-links.d.ts +62 -0
  215. package/lib/dist/resolution/test-title-links.d.ts.map +1 -0
  216. package/lib/dist/resolution/test-title-links.js +97 -0
  217. package/lib/dist/resolution/test-title-links.js.map +1 -0
  218. package/lib/dist/search/query-parser.d.ts.map +1 -1
  219. package/lib/dist/search/query-parser.js +6 -1
  220. package/lib/dist/search/query-parser.js.map +1 -1
  221. package/lib/dist/server/project-registry.js +1 -1
  222. package/lib/dist/server/routes/claude.js +24 -43
  223. package/lib/dist/server/routes/graph.js +53 -7
  224. package/lib/dist/server/routes/knowledge.js +118 -0
  225. package/lib/dist/server/routes/mcp.js +4 -3
  226. package/lib/dist/server/routes/projects.js +4 -4
  227. package/lib/dist/server/routes/spec.js +49 -7
  228. package/lib/dist/server/server.js +31 -17
  229. package/lib/dist/skills/spec-author/SKILL.md +164 -0
  230. package/lib/dist/skills/spec-author/references/format.md +210 -0
  231. package/lib/dist/skills/spec-author/references/gap-questions.md +152 -0
  232. package/lib/dist/skills/spec-author/references/quality-rubric.md +125 -0
  233. package/lib/dist/skills/spec-author/references/review-checklist.md +175 -0
  234. package/lib/dist/spec-lint/lint.d.ts +82 -0
  235. package/lib/dist/spec-lint/lint.d.ts.map +1 -0
  236. package/lib/dist/spec-lint/lint.js +491 -0
  237. package/lib/dist/spec-lint/lint.js.map +1 -0
  238. package/lib/dist/spec-quality/criterion-ids.d.ts +13 -0
  239. package/lib/dist/spec-quality/criterion-ids.d.ts.map +1 -0
  240. package/lib/dist/spec-quality/criterion-ids.js +38 -0
  241. package/lib/dist/spec-quality/criterion-ids.js.map +1 -0
  242. package/lib/dist/spec-quality/evidence-kind.d.ts +54 -0
  243. package/lib/dist/spec-quality/evidence-kind.d.ts.map +1 -0
  244. package/lib/dist/spec-quality/evidence-kind.js +183 -0
  245. package/lib/dist/spec-quality/evidence-kind.js.map +1 -0
  246. package/lib/dist/spec-quality/link-persist.d.ts +65 -0
  247. package/lib/dist/spec-quality/link-persist.d.ts.map +1 -0
  248. package/lib/dist/spec-quality/link-persist.js +249 -0
  249. package/lib/dist/spec-quality/link-persist.js.map +1 -0
  250. package/lib/dist/spec-quality/plan-scope.d.ts +38 -0
  251. package/lib/dist/spec-quality/plan-scope.d.ts.map +1 -0
  252. package/lib/dist/spec-quality/plan-scope.js +129 -0
  253. package/lib/dist/spec-quality/plan-scope.js.map +1 -0
  254. package/lib/dist/spec-quality/postcheck.d.ts +85 -0
  255. package/lib/dist/spec-quality/postcheck.d.ts.map +1 -0
  256. package/lib/dist/spec-quality/postcheck.js +390 -0
  257. package/lib/dist/spec-quality/postcheck.js.map +1 -0
  258. package/lib/dist/spec-quality/readiness.d.ts +72 -0
  259. package/lib/dist/spec-quality/readiness.d.ts.map +1 -0
  260. package/lib/dist/spec-quality/readiness.js +226 -0
  261. package/lib/dist/spec-quality/readiness.js.map +1 -0
  262. package/lib/dist/statusline/index.js.map +1 -1
  263. package/lib/dist/statusline/render.d.ts +1 -1
  264. package/lib/dist/statusline/render.d.ts.map +1 -1
  265. package/lib/dist/statusline/render.js +3 -1
  266. package/lib/dist/statusline/render.js.map +1 -1
  267. package/lib/dist/ui/assets/index-BzuUWUAE.js +50 -0
  268. package/lib/dist/ui/index.html +1 -1
  269. package/lib/dist/verify/report-ingest.d.ts +88 -0
  270. package/lib/dist/verify/report-ingest.d.ts.map +1 -0
  271. package/lib/dist/verify/report-ingest.js +269 -0
  272. package/lib/dist/verify/report-ingest.js.map +1 -0
  273. package/lib/dist/workflows/agents/claude.d.ts +16 -0
  274. package/lib/dist/workflows/agents/claude.d.ts.map +1 -0
  275. package/lib/dist/workflows/agents/claude.js +82 -0
  276. package/lib/dist/workflows/agents/claude.js.map +1 -0
  277. package/lib/dist/workflows/agents/gemini.d.ts +21 -0
  278. package/lib/dist/workflows/agents/gemini.d.ts.map +1 -0
  279. package/lib/dist/workflows/agents/gemini.js +124 -0
  280. package/lib/dist/workflows/agents/gemini.js.map +1 -0
  281. package/lib/dist/workflows/agents/index.d.ts +28 -0
  282. package/lib/dist/workflows/agents/index.d.ts.map +1 -0
  283. package/lib/dist/workflows/agents/index.js +104 -0
  284. package/lib/dist/workflows/agents/index.js.map +1 -0
  285. package/lib/dist/workflows/agents/types.d.ts +55 -0
  286. package/lib/dist/workflows/agents/types.d.ts.map +1 -0
  287. package/lib/dist/workflows/agents/types.js +46 -0
  288. package/lib/dist/workflows/agents/types.js.map +1 -0
  289. package/lib/dist/workflows/batch.d.ts +63 -0
  290. package/lib/dist/workflows/batch.d.ts.map +1 -0
  291. package/lib/dist/workflows/batch.js +151 -0
  292. package/lib/dist/workflows/batch.js.map +1 -0
  293. package/lib/dist/workflows/defaults/claude-design-implement.yaml +3 -4
  294. package/lib/dist/workflows/defaults/spec-author.yaml +11 -9
  295. package/lib/dist/workflows/defaults/spec-fix.yaml +103 -12
  296. package/lib/dist/workflows/defaults/spec-implement-mixed.yaml +21 -193
  297. package/lib/dist/workflows/defaults/spec-implement.yaml +701 -41
  298. package/lib/dist/workflows/defaults/spec-verify.yaml +60 -9
  299. package/lib/dist/workflows/discovery.d.ts +13 -0
  300. package/lib/dist/workflows/discovery.d.ts.map +1 -1
  301. package/lib/dist/workflows/discovery.js +79 -17
  302. package/lib/dist/workflows/discovery.js.map +1 -1
  303. package/lib/dist/workflows/executor.d.ts +18 -0
  304. package/lib/dist/workflows/executor.d.ts.map +1 -1
  305. package/lib/dist/workflows/executor.js +130 -33
  306. package/lib/dist/workflows/executor.js.map +1 -1
  307. package/lib/dist/workflows/gc.d.ts +50 -0
  308. package/lib/dist/workflows/gc.d.ts.map +1 -0
  309. package/lib/dist/workflows/gc.js +120 -0
  310. package/lib/dist/workflows/gc.js.map +1 -0
  311. package/lib/dist/workflows/merge-queue.d.ts +64 -0
  312. package/lib/dist/workflows/merge-queue.d.ts.map +1 -0
  313. package/lib/dist/workflows/merge-queue.js +162 -0
  314. package/lib/dist/workflows/merge-queue.js.map +1 -0
  315. package/lib/dist/workflows/runners/approval.d.ts +1 -1
  316. package/lib/dist/workflows/runners/approval.d.ts.map +1 -1
  317. package/lib/dist/workflows/runners/approval.js +11 -2
  318. package/lib/dist/workflows/runners/approval.js.map +1 -1
  319. package/lib/dist/workflows/runners/bash.d.ts +15 -0
  320. package/lib/dist/workflows/runners/bash.d.ts.map +1 -1
  321. package/lib/dist/workflows/runners/bash.js +50 -1
  322. package/lib/dist/workflows/runners/bash.js.map +1 -1
  323. package/lib/dist/workflows/runners/prompt.d.ts +16 -41
  324. package/lib/dist/workflows/runners/prompt.d.ts.map +1 -1
  325. package/lib/dist/workflows/runners/prompt.js +55 -120
  326. package/lib/dist/workflows/runners/prompt.js.map +1 -1
  327. package/lib/dist/workflows/runners/types.d.ts +12 -0
  328. package/lib/dist/workflows/runners/types.d.ts.map +1 -1
  329. package/lib/dist/workflows/schemas/workflow.d.ts +17 -0
  330. package/lib/dist/workflows/schemas/workflow.d.ts.map +1 -1
  331. package/lib/dist/workflows/schemas/workflow.js +27 -0
  332. package/lib/dist/workflows/schemas/workflow.js.map +1 -1
  333. package/lib/node_modules/.package-lock.json +1 -1
  334. package/lib/package.json +8 -4
  335. package/package.json +5 -3
  336. package/lib/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
  337. package/lib/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
  338. package/lib/dist/server/ingest/impact-query.js +0 -343
  339. package/lib/dist/server/routes/events.js +0 -152
  340. package/lib/dist/ui/assets/index-CjjaftDw.js +0 -50
  341. package/node.exe +0 -4
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Deterministic spec linter (AUTHG-DOC, REQ-AUTHG-001).
3
+ *
4
+ * Every structural check in the spec-author review checklist used to be an LLM
5
+ * eyeballing the draft. This module performs them mechanically: it reuses
6
+ * `MarkdownSpecExtractor`'s own errors (so the lint and the indexer agree about
7
+ * what a spec file means) and adds the checks the extractor has no reason to
8
+ * make — duplicate ids, orphaned `.A<N>` parents, requirements with no
9
+ * acceptance criteria, unresolved `[needs review]` markers, plus a set of
10
+ * advisory quality warnings.
11
+ *
12
+ * Pure over the file system at the edges only: `lintSpecSource` is a pure
13
+ * function of (path, source), which is what the tests drive. Nothing here
14
+ * writes, and nothing here needs a database — a lint must be runnable on a
15
+ * spec file that has never been indexed.
16
+ */
17
+ export type LintSeverity = 'error' | 'warning';
18
+ /**
19
+ * Stable finding codes. Extractor-produced findings keep the extractor's own
20
+ * code (`spec_missing_id`, `spec_stranded_id`, `spec_bare_path_ref`,
21
+ * `spec_acceptance_parent_mismatch`) so one vocabulary spans both layers.
22
+ */
23
+ export type LintCode = 'spec_duplicate_id' | 'spec_orphan_acceptance_parent' | 'spec_requirement_without_acceptance' | 'spec_needs_review_marker' | 'spec_unknown_evidence_kind' | 'spec_external_fact_missing_provenance' | 'spec_malformed_enforced_rule' | 'spec_no_negative_case' | 'spec_missing_non_goals' | 'spec_missing_test_plan' | 'spec_requirement_without_rfc2119' | string;
24
+ export interface LintFinding {
25
+ code: LintCode;
26
+ severity: LintSeverity;
27
+ /** Project-relative path of the spec file. */
28
+ filePath: string;
29
+ /** 1-indexed line, when the finding has one. */
30
+ line?: number;
31
+ /** The spec id the finding is about, when it has one. */
32
+ specId?: string;
33
+ message: string;
34
+ }
35
+ export interface LintFileReport {
36
+ filePath: string;
37
+ findings: LintFinding[];
38
+ errorCount: number;
39
+ warningCount: number;
40
+ }
41
+ export interface LintReport {
42
+ /** One entry per linted file, sorted by path — the stable grouping (A4). */
43
+ files: LintFileReport[];
44
+ filesChecked: number;
45
+ errorCount: number;
46
+ warningCount: number;
47
+ /** The exit-code predicate (A3): errors fail, warnings alone do not. */
48
+ hasErrors: boolean;
49
+ }
50
+ /**
51
+ * Lint one spec file's source. `filePath` is used verbatim in findings, so
52
+ * callers should pass a project-relative path.
53
+ */
54
+ export declare function lintSpecSource(filePath: string, source: string): LintFinding[];
55
+ export interface LintOptions {
56
+ /** Root that finding paths are made relative to. Defaults to `process.cwd()`. */
57
+ projectRoot?: string;
58
+ }
59
+ /**
60
+ * Lint every `.md` under each path (a path may be a file or a directory).
61
+ * A path that does not exist contributes an error finding rather than throwing —
62
+ * a typo'd argument must not look like a clean run.
63
+ */
64
+ export declare function lintSpecs(paths: string[], options?: LintOptions): LintReport;
65
+ /**
66
+ * Human-readable, file-grouped rendering (A4). Files with no findings are
67
+ * summarized in the tail count rather than listed, so a clean corpus prints
68
+ * one line.
69
+ */
70
+ export declare function formatLintReport(report: LintReport): string;
71
+ /**
72
+ * CLI entry point, kept here so registering the subcommand is three lines in
73
+ * `src/bin/specship.ts`. Returns the text to print and the process exit code
74
+ * (A3) instead of writing or exiting itself — that is what makes it testable.
75
+ *
76
+ * Usage: `specship lint [path...] [--json]`
77
+ */
78
+ export declare function runLintCli(args?: string[], options?: LintOptions): {
79
+ output: string;
80
+ exitCode: number;
81
+ };
82
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/spec-lint/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,QAAQ,GAEhB,mBAAmB,GACnB,+BAA+B,GAC/B,qCAAqC,GACrC,0BAA0B,GAC1B,4BAA4B,GAC5B,uCAAuC,GACvC,8BAA8B,GAE9B,uBAAuB,GACvB,wBAAwB,GACxB,wBAAwB,GACxB,kCAAkC,GAElC,MAAM,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,4EAA4E;IAC5E,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;CACpB;AAkHD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CA2K9E;AA+DD,MAAM,WAAW,WAAW;IAC1B,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAmEhF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAc3D;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,WAAgB,GACxB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAQtC"}
@@ -0,0 +1,491 @@
1
+ "use strict";
2
+ /**
3
+ * Deterministic spec linter (AUTHG-DOC, REQ-AUTHG-001).
4
+ *
5
+ * Every structural check in the spec-author review checklist used to be an LLM
6
+ * eyeballing the draft. This module performs them mechanically: it reuses
7
+ * `MarkdownSpecExtractor`'s own errors (so the lint and the indexer agree about
8
+ * what a spec file means) and adds the checks the extractor has no reason to
9
+ * make — duplicate ids, orphaned `.A<N>` parents, requirements with no
10
+ * acceptance criteria, unresolved `[needs review]` markers, plus a set of
11
+ * advisory quality warnings.
12
+ *
13
+ * Pure over the file system at the edges only: `lintSpecSource` is a pure
14
+ * function of (path, source), which is what the tests drive. Nothing here
15
+ * writes, and nothing here needs a database — a lint must be runnable on a
16
+ * spec file that has never been indexed.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.lintSpecSource = lintSpecSource;
53
+ exports.lintSpecs = lintSpecs;
54
+ exports.formatLintReport = formatLintReport;
55
+ exports.runLintCli = runLintCli;
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const markdown_spec_extractor_1 = require("../extraction/specs/markdown-spec-extractor");
59
+ const evidence_kind_1 = require("../spec-quality/evidence-kind");
60
+ const enforce_1 = require("../knowledge/enforce");
61
+ /**
62
+ * Mirrors the extractor's private `ID_COMMENT`. Duplicated rather than
63
+ * imported because the extractor does not export it and its module is off
64
+ * limits to this one; the two MUST stay in sync — a marker the extractor
65
+ * recognizes and this does not would hide duplicate ids.
66
+ */
67
+ const ID_COMMENT_LINE = /^\s*<!--\s*id\s*:\s*([^\s-][^\s]*)\s*-->\s*$/;
68
+ /** RFC 2119 requirement keywords (A2). Case-insensitive: authors write both. */
69
+ const RFC_2119 = /\b(MUST|MUST NOT|SHALL|SHALL NOT|SHOULD|SHOULD NOT|MAY|REQUIRED|RECOMMENDED|OPTIONAL)\b/i;
70
+ /**
71
+ * Negative-case vocabulary (A2). A requirement whose criteria never mention a
72
+ * refusal, a failure, or an absence is almost always specifying only the happy
73
+ * path. Heuristic by construction — hence a warning, never an error.
74
+ */
75
+ const NEGATIVE_CASE = new RegExp([
76
+ 'reject', 'refus', 'invalid', 'malformed', 'error', 'fail', 'failure',
77
+ 'missing', 'absent', 'empty', 'never', 'without', 'unauthorized',
78
+ 'forbidden', 'non-zero', 'not found', 'no match', 'unmatched', 'denied',
79
+ 'conflict', 'timeout', 'exceed', 'cannot', "can't", 'must not',
80
+ 'does not', "doesn't", 'is not', "isn't", 'no ',
81
+ ].join('|'), 'i');
82
+ /** `## Non-goals` (or prose "non-goal") anywhere in the document. */
83
+ const NON_GOALS = /non[- ]goals?/i;
84
+ /**
85
+ * A test-plan signal: a `verifies:` link block, an `@verifies` marker, or prose
86
+ * naming a test plan. Any one of them means the author thought about evidence.
87
+ */
88
+ const TEST_PLAN = /(^|\n)\s*verifies:|@verifies|test plan|test-plan/i;
89
+ /** An unresolved authoring marker that must not survive into a written spec. */
90
+ const NEEDS_REVIEW = /\[needs\s+review\]/i;
91
+ /**
92
+ * Drop inline code spans and fenced blocks before scanning prose. A spec that
93
+ * documents the marker syntax writes `` `[needs review]` `` in backticks and is
94
+ * not itself unresolved — without this, the lint flags the requirement that
95
+ * defines the lint.
96
+ */
97
+ function stripCode(text) {
98
+ return text.replace(/```[\s\S]*?```/g, ' ').replace(/`[^`]*`/g, ' ');
99
+ }
100
+ /**
101
+ * True for a file that lives under `specs/` without being a spec: a brainstorm
102
+ * brief (`brief.md`) or a design handoff-bundle transcript (`source.md`, the
103
+ * designer convention). Neither carries id-marked headings by design, so every
104
+ * structural check below would fire on every heading.
105
+ */
106
+ function isNonSpecArtifact(filePath) {
107
+ const base = (filePath.split(/[/\\]/).pop() ?? '').toLowerCase();
108
+ return base === 'brief.md' || base === 'source.md';
109
+ }
110
+ /** True for a domain fact — frontmatter-keyed, no requirement structure. */
111
+ function isDomainFile(filePath) {
112
+ return filePath.replace(/\\/g, '/').includes('specs/domain/');
113
+ }
114
+ /** The provenance an external fact must carry (REQ-KNOW-001). */
115
+ const EXTERNAL_PROVENANCE = ['dependency', 'version', 'source'];
116
+ /**
117
+ * Domain-fact checks (KNOWLEDGE-DOC):
118
+ * - an `external` fact missing `dependency` / `version` / `source` is an
119
+ * error naming the missing field(s) (REQ-KNOW-001.A2) — it still indexes;
120
+ * - an `## Enforced` bullet not of the form `` `<glob>` must not match
121
+ * `/<regex>/` `` is an error (REQ-KNOW-003.A3).
122
+ */
123
+ function lintDomainFact(specs, source, add) {
124
+ const fact = specs.find((s) => s.kind === 'domain');
125
+ if (fact) {
126
+ const meta = (fact.metadata ?? {});
127
+ if (meta.type === 'external') {
128
+ const missing = EXTERNAL_PROVENANCE.filter((k) => {
129
+ const v = meta[k];
130
+ return !(typeof v === 'number' || (typeof v === 'string' && v.trim().length > 0));
131
+ });
132
+ if (missing.length > 0) {
133
+ add({
134
+ code: 'spec_external_fact_missing_provenance',
135
+ severity: 'error',
136
+ line: 1,
137
+ specId: fact.id,
138
+ message: `External fact ${fact.id} is missing ${missing.join(', ')} — an external fact must record the dependency, the version it was verified against, and its source.`,
139
+ });
140
+ }
141
+ }
142
+ }
143
+ // Parsed over the whole source so the line numbers are file lines; the
144
+ // frontmatter can't contain a `## Enforced` heading.
145
+ for (const m of (0, enforce_1.parseEnforcedRules)(source).malformed) {
146
+ add({
147
+ code: 'spec_malformed_enforced_rule',
148
+ severity: 'error',
149
+ line: m.line,
150
+ specId: fact?.id,
151
+ message: `Malformed enforced rule "${m.text}": ${m.reason}.`,
152
+ });
153
+ }
154
+ }
155
+ /**
156
+ * Lint one spec file's source. `filePath` is used verbatim in findings, so
157
+ * callers should pass a project-relative path.
158
+ */
159
+ function lintSpecSource(filePath, source) {
160
+ const findings = [];
161
+ const add = (f) => findings.push({ ...f, filePath });
162
+ // A brief or a design-handoff transcript is not a spec and is not held to a
163
+ // spec's shape — including the extractor's own `spec_missing_id`, which every
164
+ // heading in a transcript would trip. Nothing to report, at all.
165
+ if (isNonSpecArtifact(filePath))
166
+ return findings;
167
+ const lines = source.split(/\r?\n/);
168
+ const result = new markdown_spec_extractor_1.MarkdownSpecExtractor(filePath, source).extract();
169
+ // 1. The extractor's own verdict, carried through unchanged (A1). Its
170
+ // severities are already right: a missing id is an error, a bare-path link
171
+ // bullet is a warning (the no-edge pointer is legal, just invisible).
172
+ for (const e of result.errors) {
173
+ add({
174
+ code: e.code ?? 'spec_extraction_error',
175
+ severity: e.severity === 'error' ? 'error' : 'warning',
176
+ line: e.line,
177
+ message: e.message,
178
+ });
179
+ }
180
+ // 2. Duplicate ids within the file (A1). The persistence layer upserts by id,
181
+ // so the LAST occurrence wins and the earlier section silently vanishes.
182
+ const seen = new Map();
183
+ for (let i = 0; i < lines.length; i++) {
184
+ const m = (lines[i] ?? '').match(ID_COMMENT_LINE);
185
+ const id = m?.[1];
186
+ if (!id)
187
+ continue;
188
+ const first = seen.get(id);
189
+ if (first !== undefined) {
190
+ add({
191
+ code: 'spec_duplicate_id',
192
+ severity: 'error',
193
+ line: i + 1,
194
+ specId: id,
195
+ message: `Duplicate id ${id} (first declared on line ${first}). The last occurrence overwrites earlier ones, so the earlier section is lost.`,
196
+ });
197
+ }
198
+ else {
199
+ seen.set(id, i + 1);
200
+ }
201
+ }
202
+ // A domain fact keeps the extractor's verdict (its frontmatter can genuinely
203
+ // be malformed) but has no requirement/acceptance structure to check. It has
204
+ // its own two checks instead (KNOWLEDGE-DOC).
205
+ if (isDomainFile(filePath)) {
206
+ lintDomainFact(result.specs, source, add);
207
+ return sortFindings(findings);
208
+ }
209
+ const declared = new Set([...seen.keys(), ...result.specs.map((s) => s.id)]);
210
+ const acceptance = result.specs.filter((s) => s.kind === 'acceptance');
211
+ const requirements = result.specs.filter((s) => s.kind === 'requirement');
212
+ // 3. An `.A<N>` id whose parent requirement is nowhere in the file (A1). The
213
+ // extractor parents it anyway (by id), so nothing complains at index time
214
+ // while the criterion hangs off a requirement that doesn't exist.
215
+ for (const spec of result.specs) {
216
+ const parent = (0, markdown_spec_extractor_1.parseAcceptanceParentId)(spec.id);
217
+ if (parent && !declared.has(parent)) {
218
+ add({
219
+ code: 'spec_orphan_acceptance_parent',
220
+ severity: 'error',
221
+ line: spec.startLine,
222
+ specId: spec.id,
223
+ message: `${spec.id} names ${parent} as its parent requirement, but ${parent} is not declared in this file.`,
224
+ });
225
+ }
226
+ }
227
+ // 4. A requirement with no acceptance criteria (A1) — unverifiable by
228
+ // construction, and invisible to the behaviour gate.
229
+ for (const req of requirements) {
230
+ const children = acceptance.filter((a) => a.parentId === req.id);
231
+ if (children.length === 0) {
232
+ add({
233
+ code: 'spec_requirement_without_acceptance',
234
+ severity: 'error',
235
+ line: req.startLine,
236
+ specId: req.id,
237
+ message: `${req.id} has no acceptance criteria — nothing can verify it.`,
238
+ });
239
+ }
240
+ }
241
+ // 5. `[needs review]` left inside an acceptance bullet (A1).
242
+ for (const a of acceptance) {
243
+ if (NEEDS_REVIEW.test(stripCode(a.body))) {
244
+ add({
245
+ code: 'spec_needs_review_marker',
246
+ severity: 'error',
247
+ line: a.startLine,
248
+ specId: a.id,
249
+ message: `${a.id} still carries a [needs review] marker.`,
250
+ });
251
+ }
252
+ }
253
+ // 5b. An `(evidence: <kind>)` tag naming a kind outside the four
254
+ // (REQ-PIPETRUST-001.A3). Resolution would silently fall back to the
255
+ // derived kind, so the author's intent would be lost — an error.
256
+ for (const a of acceptance) {
257
+ const { invalid } = (0, evidence_kind_1.parseDeclaredEvidenceKind)(a.body);
258
+ if (invalid !== undefined) {
259
+ add({
260
+ code: 'spec_unknown_evidence_kind',
261
+ severity: 'error',
262
+ line: a.startLine,
263
+ specId: a.id,
264
+ message: `${a.id} declares unknown evidence kind "${invalid}" — use one of ${evidence_kind_1.EVIDENCE_KINDS.join(', ')}.`,
265
+ });
266
+ }
267
+ }
268
+ // 6. Happy-path-only requirements (A2, warning).
269
+ for (const req of requirements) {
270
+ const children = acceptance.filter((a) => a.parentId === req.id);
271
+ if (children.length === 0)
272
+ continue; // already an error above
273
+ if (!children.some((a) => NEGATIVE_CASE.test(a.body))) {
274
+ add({
275
+ code: 'spec_no_negative_case',
276
+ severity: 'warning',
277
+ line: req.startLine,
278
+ specId: req.id,
279
+ message: `${req.id} has no negative-case acceptance criterion (no refusal, failure, or absence is specified).`,
280
+ });
281
+ }
282
+ }
283
+ // 7. A requirement stating no obligation (A2, warning). Scoped to the
284
+ // requirement's own title and body, NOT its bullets: house style writes
285
+ // acceptance criteria declaratively ("- The tile shows no delta…"), so a
286
+ // per-bullet check fires on essentially every criterion in the corpus and
287
+ // carries no signal. The obligation belongs on the requirement.
288
+ for (const req of requirements) {
289
+ const children = acceptance.filter((a) => a.parentId === req.id);
290
+ const prose = requirementProse(lines, req, children);
291
+ if (!RFC_2119.test(req.title) && !RFC_2119.test(prose)) {
292
+ add({
293
+ code: 'spec_requirement_without_rfc2119',
294
+ severity: 'warning',
295
+ line: req.startLine,
296
+ specId: req.id,
297
+ message: `${req.id} states no requirement keyword (MUST / SHOULD / MAY) in its title or body — its obligation is ambiguous.`,
298
+ });
299
+ }
300
+ }
301
+ // 8. Document-level signals the review checklist asks for (A2, warnings).
302
+ // Only meaningful for a file that actually declares requirements.
303
+ if (requirements.length > 0) {
304
+ if (!NON_GOALS.test(source)) {
305
+ add({
306
+ code: 'spec_missing_non_goals',
307
+ severity: 'warning',
308
+ message: 'No non-goals stated — scope has no stated boundary.',
309
+ });
310
+ }
311
+ if (!TEST_PLAN.test(source)) {
312
+ add({
313
+ code: 'spec_missing_test_plan',
314
+ severity: 'warning',
315
+ message: 'No test-evidence signal (`verifies:` block, `@verifies`, or a test plan) — nothing declares how this gets verified.',
316
+ });
317
+ }
318
+ }
319
+ return sortFindings(findings);
320
+ }
321
+ /**
322
+ * A requirement's own prose: its body minus the acceptance criteria that live
323
+ * inside it. `Spec.body` spans everything under the heading, criteria included,
324
+ * so scanning it for an obligation keyword would read the bullets' wording
325
+ * back — exactly what the requirement-level check exists to avoid.
326
+ */
327
+ function requirementProse(lines, req, children) {
328
+ const from = (req.startLine ?? 1) + 1; // 1-indexed, excluding the heading
329
+ const to = req.endLine ?? lines.length;
330
+ const covered = (n) => children.some((c) => n >= (c.startLine ?? 0) && n <= (c.endLine ?? c.startLine ?? 0));
331
+ const out = [];
332
+ for (let n = from; n <= to && n <= lines.length; n++) {
333
+ const line = lines[n - 1] ?? '';
334
+ if (covered(n))
335
+ continue;
336
+ if (/^\s*#{1,6}\s*acceptance\s*$/i.test(line))
337
+ continue;
338
+ out.push(line);
339
+ }
340
+ return out.join('\n');
341
+ }
342
+ /** Stable order within a file: by line, then code, then message. */
343
+ function sortFindings(findings) {
344
+ return findings.sort((a, b) => (a.line ?? Number.MAX_SAFE_INTEGER) - (b.line ?? Number.MAX_SAFE_INTEGER) ||
345
+ a.code.localeCompare(b.code) ||
346
+ a.message.localeCompare(b.message));
347
+ }
348
+ /** Collect `.md` files under a path (file → itself). Hidden dirs skipped. */
349
+ function collectSpecFiles(target, out) {
350
+ let stat;
351
+ try {
352
+ stat = fs.statSync(target);
353
+ }
354
+ catch {
355
+ return;
356
+ }
357
+ if (stat.isFile()) {
358
+ if (target.toLowerCase().endsWith('.md'))
359
+ out.push(target);
360
+ return;
361
+ }
362
+ if (!stat.isDirectory())
363
+ return;
364
+ let entries;
365
+ try {
366
+ entries = fs.readdirSync(target, { withFileTypes: true });
367
+ }
368
+ catch {
369
+ return;
370
+ }
371
+ for (const entry of entries) {
372
+ const full = path.join(target, entry.name);
373
+ if (entry.isDirectory()) {
374
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
375
+ continue;
376
+ collectSpecFiles(full, out);
377
+ }
378
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) {
379
+ out.push(full);
380
+ }
381
+ }
382
+ }
383
+ /**
384
+ * Lint every `.md` under each path (a path may be a file or a directory).
385
+ * A path that does not exist contributes an error finding rather than throwing —
386
+ * a typo'd argument must not look like a clean run.
387
+ */
388
+ function lintSpecs(paths, options = {}) {
389
+ const projectRoot = options.projectRoot ?? process.cwd();
390
+ const files = [];
391
+ const targets = paths.length > 0 ? paths : [path.join(projectRoot, 'specs')];
392
+ const absFiles = [];
393
+ for (const target of targets) {
394
+ const abs = path.isAbsolute(target) ? target : path.join(projectRoot, target);
395
+ if (!fs.existsSync(abs)) {
396
+ files.push({
397
+ filePath: path.relative(projectRoot, abs) || target,
398
+ findings: [
399
+ {
400
+ code: 'lint_path_not_found',
401
+ severity: 'error',
402
+ filePath: path.relative(projectRoot, abs) || target,
403
+ message: `No such file or directory: ${target}`,
404
+ },
405
+ ],
406
+ errorCount: 1,
407
+ warningCount: 0,
408
+ });
409
+ continue;
410
+ }
411
+ collectSpecFiles(abs, absFiles);
412
+ }
413
+ for (const abs of [...new Set(absFiles)].sort()) {
414
+ const rel = path.relative(projectRoot, abs) || abs;
415
+ let source;
416
+ try {
417
+ source = fs.readFileSync(abs, 'utf-8');
418
+ }
419
+ catch (err) {
420
+ files.push({
421
+ filePath: rel,
422
+ findings: [
423
+ {
424
+ code: 'lint_unreadable_file',
425
+ severity: 'error',
426
+ filePath: rel,
427
+ message: `Could not read: ${err instanceof Error ? err.message : String(err)}`,
428
+ },
429
+ ],
430
+ errorCount: 1,
431
+ warningCount: 0,
432
+ });
433
+ continue;
434
+ }
435
+ const findings = lintSpecSource(rel, source);
436
+ files.push({
437
+ filePath: rel,
438
+ findings,
439
+ errorCount: findings.filter((f) => f.severity === 'error').length,
440
+ warningCount: findings.filter((f) => f.severity === 'warning').length,
441
+ });
442
+ }
443
+ files.sort((a, b) => a.filePath.localeCompare(b.filePath));
444
+ const errorCount = files.reduce((n, f) => n + f.errorCount, 0);
445
+ const warningCount = files.reduce((n, f) => n + f.warningCount, 0);
446
+ return {
447
+ files,
448
+ filesChecked: files.length,
449
+ errorCount,
450
+ warningCount,
451
+ hasErrors: errorCount > 0,
452
+ };
453
+ }
454
+ /**
455
+ * Human-readable, file-grouped rendering (A4). Files with no findings are
456
+ * summarized in the tail count rather than listed, so a clean corpus prints
457
+ * one line.
458
+ */
459
+ function formatLintReport(report) {
460
+ const out = [];
461
+ for (const file of report.files) {
462
+ if (file.findings.length === 0)
463
+ continue;
464
+ out.push(file.filePath);
465
+ for (const f of file.findings) {
466
+ const where = f.line !== undefined ? `:${f.line}` : '';
467
+ out.push(` ${f.severity === 'error' ? 'error' : 'warn '} ${f.code}${where} — ${f.message}`);
468
+ }
469
+ out.push('');
470
+ }
471
+ const counts = `${report.errorCount} error(s), ${report.warningCount} warning(s) in ${report.filesChecked} file(s)`;
472
+ out.push(report.errorCount === 0 && report.warningCount === 0 ? `Clean — ${counts}` : counts);
473
+ return out.join('\n');
474
+ }
475
+ /**
476
+ * CLI entry point, kept here so registering the subcommand is three lines in
477
+ * `src/bin/specship.ts`. Returns the text to print and the process exit code
478
+ * (A3) instead of writing or exiting itself — that is what makes it testable.
479
+ *
480
+ * Usage: `specship lint [path...] [--json]`
481
+ */
482
+ function runLintCli(args = [], options = {}) {
483
+ const json = args.includes('--json');
484
+ const paths = args.filter((a) => !a.startsWith('--'));
485
+ const report = lintSpecs(paths, options);
486
+ return {
487
+ output: json ? JSON.stringify(report, null, 2) : formatLintReport(report),
488
+ exitCode: report.hasErrors ? 1 : 0,
489
+ };
490
+ }
491
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/spec-lint/lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Spec ids named in free text, with criterion shorthand expanded
3
+ * (PIPETRUST-DOC, REQ-PIPETRUST-008).
4
+ *
5
+ * Test titles cite several criteria of one requirement in shorthand —
6
+ * `REQ-X-004.A1/A3`, `REQ-X-028.A2/.A3`, `REQ-X-001.A1 / A3`. Reading only the
7
+ * first id left the rest silently unlinked, so the title scanner and postcheck
8
+ * share this one parser. A bare `A2` with no preceding full id names nothing,
9
+ * and ids match whole: `REQ-X.A10` never yields `REQ-X.A1`.
10
+ */
11
+ /** Every spec id `text` names, shorthand expanded, in order of appearance (duplicates kept). */
12
+ export declare function criterionIdsInText(text: string): string[];
13
+ //# sourceMappingURL=criterion-ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criterion-ids.d.ts","sourceRoot":"","sources":["../../src/spec-quality/criterion-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,gGAAgG;AAChG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzD"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ /**
3
+ * Spec ids named in free text, with criterion shorthand expanded
4
+ * (PIPETRUST-DOC, REQ-PIPETRUST-008).
5
+ *
6
+ * Test titles cite several criteria of one requirement in shorthand —
7
+ * `REQ-X-004.A1/A3`, `REQ-X-028.A2/.A3`, `REQ-X-001.A1 / A3`. Reading only the
8
+ * first id left the rest silently unlinked, so the title scanner and postcheck
9
+ * share this one parser. A bare `A2` with no preceding full id names nothing,
10
+ * and ids match whole: `REQ-X.A10` never yields `REQ-X.A1`.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.criterionIdsInText = criterionIdsInText;
14
+ /** A full spec id, an optional `.A<N>` suffix, then any `/A<N>`, `, .A<N>`, `& A<N>` continuations. */
15
+ const SPEC_ID_CHAIN = /\b([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+)(\.A\d+)?((?:\s*[/,&]\s*\.?A\d+\b)*)(?![\w-])/g;
16
+ const CONTINUATION = /\.?A(\d+)/g;
17
+ /** Every spec id `text` names, shorthand expanded, in order of appearance (duplicates kept). */
18
+ function criterionIdsInText(text) {
19
+ const out = [];
20
+ SPEC_ID_CHAIN.lastIndex = 0;
21
+ let m;
22
+ while ((m = SPEC_ID_CHAIN.exec(text)) !== null) {
23
+ const base = m[1];
24
+ const first = m[2];
25
+ if (!first) {
26
+ out.push(base);
27
+ continue;
28
+ }
29
+ out.push(base + first);
30
+ const chain = m[3] ?? '';
31
+ CONTINUATION.lastIndex = 0;
32
+ let c;
33
+ while ((c = CONTINUATION.exec(chain)) !== null)
34
+ out.push(`${base}.A${c[1]}`);
35
+ }
36
+ return out;
37
+ }
38
+ //# sourceMappingURL=criterion-ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criterion-ids.js","sourceRoot":"","sources":["../../src/spec-quality/criterion-ids.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAQH,gDAkBC;AAxBD,uGAAuG;AACvG,MAAM,aAAa,GACjB,iFAAiF,CAAC;AACpF,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC,gGAAgG;AAChG,SAAgB,kBAAkB,CAAC,IAAY;IAC7C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC/C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;QACnB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzB,YAAY,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}