godpowers 0.15.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 (444) hide show
  1. package/AGENTS.md +37 -0
  2. package/CHANGELOG.md +639 -0
  3. package/INSPIRATION.md +52 -0
  4. package/LICENSE +21 -0
  5. package/README.md +232 -0
  6. package/SKILL.md +500 -0
  7. package/agents/god-archaeologist.md +139 -0
  8. package/agents/god-architect.md +92 -0
  9. package/agents/god-auditor.md +150 -0
  10. package/agents/god-browser-tester.md +144 -0
  11. package/agents/god-context-writer.md +137 -0
  12. package/agents/god-coordinator.md +138 -0
  13. package/agents/god-debt-assessor.md +132 -0
  14. package/agents/god-debugger.md +77 -0
  15. package/agents/god-deploy-engineer.md +87 -0
  16. package/agents/god-deps-auditor.md +111 -0
  17. package/agents/god-design-reviewer.md +137 -0
  18. package/agents/god-designer.md +171 -0
  19. package/agents/god-docs-writer.md +102 -0
  20. package/agents/god-executor.md +76 -0
  21. package/agents/god-explorer.md +110 -0
  22. package/agents/god-harden-auditor.md +163 -0
  23. package/agents/god-incident-investigator.md +144 -0
  24. package/agents/god-launch-strategist.md +103 -0
  25. package/agents/god-migration-strategist.md +126 -0
  26. package/agents/god-observability-engineer.md +76 -0
  27. package/agents/god-orchestrator.md +728 -0
  28. package/agents/god-org-context-loader.md +124 -0
  29. package/agents/god-planner.md +73 -0
  30. package/agents/god-pm.md +105 -0
  31. package/agents/god-quality-reviewer.md +74 -0
  32. package/agents/god-reconciler.md +230 -0
  33. package/agents/god-reconstructor.md +124 -0
  34. package/agents/god-repo-scaffolder.md +60 -0
  35. package/agents/god-retrospective.md +109 -0
  36. package/agents/god-roadmap-reconciler.md +123 -0
  37. package/agents/god-roadmap-updater.md +89 -0
  38. package/agents/god-roadmapper.md +82 -0
  39. package/agents/god-spec-reviewer.md +70 -0
  40. package/agents/god-spike-runner.md +119 -0
  41. package/agents/god-stack-selector.md +93 -0
  42. package/agents/god-standards-check.md +132 -0
  43. package/agents/god-storyteller.md +116 -0
  44. package/agents/god-updater.md +174 -0
  45. package/bin/install.js +514 -0
  46. package/extensions/data-pack/README.md +33 -0
  47. package/extensions/data-pack/agents/god-dashboard-builder.md +66 -0
  48. package/extensions/data-pack/agents/god-etl-engineer.md +64 -0
  49. package/extensions/data-pack/agents/god-ml-feature-engineer.md +66 -0
  50. package/extensions/data-pack/manifest.yaml +39 -0
  51. package/extensions/data-pack/package.json +42 -0
  52. package/extensions/data-pack/skills/god-dashboard.md +28 -0
  53. package/extensions/data-pack/skills/god-etl.md +28 -0
  54. package/extensions/data-pack/skills/god-ml-feature.md +28 -0
  55. package/extensions/data-pack/workflows/dashboard-arc.yaml +13 -0
  56. package/extensions/data-pack/workflows/etl-arc.yaml +13 -0
  57. package/extensions/data-pack/workflows/ml-feature-arc.yaml +13 -0
  58. package/extensions/launch-pack/README.md +36 -0
  59. package/extensions/launch-pack/agents/god-indie-hackers-strategist.md +128 -0
  60. package/extensions/launch-pack/agents/god-oss-release-strategist.md +125 -0
  61. package/extensions/launch-pack/agents/god-product-hunt-strategist.md +118 -0
  62. package/extensions/launch-pack/agents/god-show-hn-strategist.md +113 -0
  63. package/extensions/launch-pack/manifest.yaml +45 -0
  64. package/extensions/launch-pack/package.json +41 -0
  65. package/extensions/launch-pack/skills/god-indie-hackers.md +39 -0
  66. package/extensions/launch-pack/skills/god-oss-release.md +43 -0
  67. package/extensions/launch-pack/skills/god-product-hunt.md +41 -0
  68. package/extensions/launch-pack/skills/god-show-hn.md +40 -0
  69. package/extensions/launch-pack/workflows/indie-hackers.yaml +13 -0
  70. package/extensions/launch-pack/workflows/oss-release.yaml +13 -0
  71. package/extensions/launch-pack/workflows/product-hunt.yaml +13 -0
  72. package/extensions/launch-pack/workflows/show-hn.yaml +13 -0
  73. package/extensions/security-pack/README.md +48 -0
  74. package/extensions/security-pack/agents/god-hipaa-auditor.md +117 -0
  75. package/extensions/security-pack/agents/god-pci-auditor.md +100 -0
  76. package/extensions/security-pack/agents/god-soc2-auditor.md +107 -0
  77. package/extensions/security-pack/manifest.yaml +39 -0
  78. package/extensions/security-pack/package.json +42 -0
  79. package/extensions/security-pack/skills/god-hipaa-audit.md +41 -0
  80. package/extensions/security-pack/skills/god-pci-audit.md +40 -0
  81. package/extensions/security-pack/skills/god-soc2-audit.md +42 -0
  82. package/extensions/security-pack/workflows/hipaa-arc.yaml +15 -0
  83. package/extensions/security-pack/workflows/pci-arc.yaml +15 -0
  84. package/extensions/security-pack/workflows/soc2-arc.yaml +15 -0
  85. package/hooks/pre-tool-use.sh +40 -0
  86. package/hooks/session-start.sh +74 -0
  87. package/lib/README.md +28 -0
  88. package/lib/agent-browser-driver.js +215 -0
  89. package/lib/agent-cache.js +194 -0
  90. package/lib/agent-validator.js +275 -0
  91. package/lib/artifact-diff.js +168 -0
  92. package/lib/artifact-linter.js +142 -0
  93. package/lib/awesome-design.js +312 -0
  94. package/lib/browser-bridge.js +209 -0
  95. package/lib/budget.js +215 -0
  96. package/lib/checkpoint.js +390 -0
  97. package/lib/code-scanner.js +262 -0
  98. package/lib/context-budget.js +170 -0
  99. package/lib/context-writer.js +348 -0
  100. package/lib/cost-tracker.js +325 -0
  101. package/lib/cross-artifact-impact.js +162 -0
  102. package/lib/cross-repo-linkage.js +150 -0
  103. package/lib/design-detector.js +167 -0
  104. package/lib/design-spec.js +348 -0
  105. package/lib/drift-detector.js +212 -0
  106. package/lib/event-reader.js +174 -0
  107. package/lib/events.js +183 -0
  108. package/lib/extensions.js +257 -0
  109. package/lib/have-nots-validator.js +647 -0
  110. package/lib/impact.js +314 -0
  111. package/lib/impeccable-bridge.js +139 -0
  112. package/lib/intent.js +177 -0
  113. package/lib/linkage.js +232 -0
  114. package/lib/meta-linter.js +263 -0
  115. package/lib/multi-repo-detector.js +182 -0
  116. package/lib/otel-exporter.js +308 -0
  117. package/lib/recipes.js +186 -0
  118. package/lib/reverse-sync.js +332 -0
  119. package/lib/review-required.js +224 -0
  120. package/lib/router.js +278 -0
  121. package/lib/runtime-audit.js +455 -0
  122. package/lib/runtime-test.js +309 -0
  123. package/lib/skillui-bridge.js +216 -0
  124. package/lib/state-lock.js +201 -0
  125. package/lib/state.js +142 -0
  126. package/lib/story-validator.js +301 -0
  127. package/lib/suite-state.js +220 -0
  128. package/lib/workflow-parser.js +109 -0
  129. package/lib/workflow-runner.js +221 -0
  130. package/package.json +63 -0
  131. package/references/HAVE-NOTS.md +573 -0
  132. package/references/building/BUILD-ANTIPATTERNS.md +102 -0
  133. package/references/building/BUILD-VERTICAL-SLICES.md +75 -0
  134. package/references/building/BUILD-WAVES.md +61 -0
  135. package/references/building/README.md +17 -0
  136. package/references/design/COLOR.md +122 -0
  137. package/references/design/DESIGN-ANATOMY.md +121 -0
  138. package/references/design/DESIGN-ANTIPATTERNS.md +108 -0
  139. package/references/design/INTERACTION.md +148 -0
  140. package/references/design/MOTION.md +120 -0
  141. package/references/design/RESPONSIVE.md +157 -0
  142. package/references/design/SPATIAL.md +109 -0
  143. package/references/design/TYPOGRAPHY.md +121 -0
  144. package/references/design/UX-WRITING.md +135 -0
  145. package/references/orchestration/MODE-DETECTION.md +74 -0
  146. package/references/orchestration/README.md +18 -0
  147. package/references/orchestration/SCALE-DETECTION.md +81 -0
  148. package/references/planning/ARCH-ANATOMY.md +143 -0
  149. package/references/planning/ARCH-ANTIPATTERNS.md +52 -0
  150. package/references/planning/PRD-ANATOMY.md +117 -0
  151. package/references/planning/PRD-ANTIPATTERNS.md +138 -0
  152. package/references/planning/README.md +16 -0
  153. package/references/planning/ROADMAP-ANATOMY.md +43 -0
  154. package/references/planning/ROADMAP-ANTIPATTERNS.md +94 -0
  155. package/references/planning/STACK-ANATOMY.md +60 -0
  156. package/references/planning/STACK-ANTIPATTERNS.md +95 -0
  157. package/references/shared/GLOSSARY.md +80 -0
  158. package/references/shared/ORCHESTRATORS.md +76 -0
  159. package/references/shared/README.md +14 -0
  160. package/references/shipping/DEPLOY-ANTIPATTERNS.md +64 -0
  161. package/references/shipping/DEPLOY-PATTERNS.md +110 -0
  162. package/references/shipping/HARDEN-ANTIPATTERNS.md +66 -0
  163. package/references/shipping/HARDEN-OWASP-WORKSHEETS.md +89 -0
  164. package/references/shipping/LAUNCH-ANTIPATTERNS.md +68 -0
  165. package/references/shipping/OBSERVE-ANTIPATTERNS.md +62 -0
  166. package/references/shipping/OBSERVE-SLO-EXAMPLES.md +107 -0
  167. package/references/shipping/README.md +18 -0
  168. package/routing/god-add-backlog.yaml +24 -0
  169. package/routing/god-add-tests.yaml +27 -0
  170. package/routing/god-add-todo.yaml +24 -0
  171. package/routing/god-agent-audit.yaml +24 -0
  172. package/routing/god-arch.yaml +46 -0
  173. package/routing/god-archaeology.yaml +28 -0
  174. package/routing/god-audit.yaml +32 -0
  175. package/routing/god-budget.yaml +24 -0
  176. package/routing/god-build-agent.yaml +24 -0
  177. package/routing/god-build.yaml +46 -0
  178. package/routing/god-cache-clear.yaml +24 -0
  179. package/routing/god-check-todos.yaml +24 -0
  180. package/routing/god-context-scan.yaml +24 -0
  181. package/routing/god-context.yaml +44 -0
  182. package/routing/god-cost.yaml +24 -0
  183. package/routing/god-debug.yaml +28 -0
  184. package/routing/god-deploy.yaml +34 -0
  185. package/routing/god-design-impact.yaml +25 -0
  186. package/routing/god-design.yaml +67 -0
  187. package/routing/god-discuss.yaml +27 -0
  188. package/routing/god-docs.yaml +33 -0
  189. package/routing/god-doctor.yaml +27 -0
  190. package/routing/god-explore.yaml +27 -0
  191. package/routing/god-extension-add.yaml +24 -0
  192. package/routing/god-extension-info.yaml +24 -0
  193. package/routing/god-extension-list.yaml +24 -0
  194. package/routing/god-extension-remove.yaml +24 -0
  195. package/routing/god-extract-learnings.yaml +24 -0
  196. package/routing/god-fast.yaml +27 -0
  197. package/routing/god-feature.yaml +34 -0
  198. package/routing/god-graph.yaml +24 -0
  199. package/routing/god-harden.yaml +41 -0
  200. package/routing/god-help.yaml +27 -0
  201. package/routing/god-hotfix.yaml +34 -0
  202. package/routing/god-hygiene.yaml +28 -0
  203. package/routing/god-init.yaml +37 -0
  204. package/routing/god-intel.yaml +24 -0
  205. package/routing/god-launch.yaml +41 -0
  206. package/routing/god-lifecycle.yaml +27 -0
  207. package/routing/god-link.yaml +24 -0
  208. package/routing/god-lint.yaml +24 -0
  209. package/routing/god-list-assumptions.yaml +27 -0
  210. package/routing/god-locate.yaml +24 -0
  211. package/routing/god-logs.yaml +24 -0
  212. package/routing/god-map-codebase.yaml +24 -0
  213. package/routing/god-metrics.yaml +24 -0
  214. package/routing/god-mode.yaml +31 -0
  215. package/routing/god-next.yaml +27 -0
  216. package/routing/god-note.yaml +24 -0
  217. package/routing/god-observe.yaml +34 -0
  218. package/routing/god-org-context.yaml +28 -0
  219. package/routing/god-party.yaml +24 -0
  220. package/routing/god-pause-work.yaml +27 -0
  221. package/routing/god-plant-seed.yaml +24 -0
  222. package/routing/god-postmortem.yaml +34 -0
  223. package/routing/god-pr-branch.yaml +25 -0
  224. package/routing/god-prd.yaml +49 -0
  225. package/routing/god-quick.yaml +28 -0
  226. package/routing/god-reconcile.yaml +48 -0
  227. package/routing/god-reconstruct.yaml +36 -0
  228. package/routing/god-redo.yaml +27 -0
  229. package/routing/god-refactor.yaml +36 -0
  230. package/routing/god-repair.yaml +27 -0
  231. package/routing/god-repo.yaml +35 -0
  232. package/routing/god-restore.yaml +27 -0
  233. package/routing/god-resume-work.yaml +27 -0
  234. package/routing/god-review-changes.yaml +25 -0
  235. package/routing/god-review.yaml +28 -0
  236. package/routing/god-roadmap-check.yaml +39 -0
  237. package/routing/god-roadmap-update.yaml +37 -0
  238. package/routing/god-roadmap.yaml +42 -0
  239. package/routing/god-rollback.yaml +27 -0
  240. package/routing/god-scan.yaml +24 -0
  241. package/routing/god-set-profile.yaml +24 -0
  242. package/routing/god-settings.yaml +24 -0
  243. package/routing/god-skip.yaml +27 -0
  244. package/routing/god-smite.yaml +29 -0
  245. package/routing/god-spike.yaml +35 -0
  246. package/routing/god-sprint.yaml +25 -0
  247. package/routing/god-stack.yaml +41 -0
  248. package/routing/god-standards.yaml +24 -0
  249. package/routing/god-status.yaml +27 -0
  250. package/routing/god-stories.yaml +24 -0
  251. package/routing/god-story-build.yaml +25 -0
  252. package/routing/god-story-close.yaml +25 -0
  253. package/routing/god-story-verify.yaml +25 -0
  254. package/routing/god-story.yaml +24 -0
  255. package/routing/god-suite-init.yaml +24 -0
  256. package/routing/god-suite-patch.yaml +25 -0
  257. package/routing/god-suite-release.yaml +25 -0
  258. package/routing/god-suite-status.yaml +25 -0
  259. package/routing/god-suite-sync.yaml +25 -0
  260. package/routing/god-sync.yaml +33 -0
  261. package/routing/god-tech-debt.yaml +32 -0
  262. package/routing/god-test-extension.yaml +24 -0
  263. package/routing/god-test-runtime.yaml +25 -0
  264. package/routing/god-thread.yaml +24 -0
  265. package/routing/god-trace.yaml +24 -0
  266. package/routing/god-undo.yaml +27 -0
  267. package/routing/god-update-deps.yaml +39 -0
  268. package/routing/god-upgrade.yaml +33 -0
  269. package/routing/god-version.yaml +24 -0
  270. package/routing/god-workstream.yaml +24 -0
  271. package/routing/god.yaml +24 -0
  272. package/routing/recipes/add-feature-defer-current-milestone.yaml +21 -0
  273. package/routing/recipes/add-feature-future-conditional.yaml +21 -0
  274. package/routing/recipes/add-feature-mid-arc-pause.yaml +33 -0
  275. package/routing/recipes/add-feature-next-milestone.yaml +23 -0
  276. package/routing/recipes/add-feature-parallel.yaml +29 -0
  277. package/routing/recipes/add-feature-prd-update.yaml +21 -0
  278. package/routing/recipes/add-feature-small.yaml +24 -0
  279. package/routing/recipes/add-feature-tiny.yaml +24 -0
  280. package/routing/recipes/bluefield-org-aware.yaml +27 -0
  281. package/routing/recipes/broken-install.yaml +22 -0
  282. package/routing/recipes/brownfield-onboarding.yaml +32 -0
  283. package/routing/recipes/bug-no-urgency.yaml +21 -0
  284. package/routing/recipes/capture-idea.yaml +22 -0
  285. package/routing/recipes/capture-todo.yaml +21 -0
  286. package/routing/recipes/clean-pr.yaml +21 -0
  287. package/routing/recipes/code-cleanup.yaml +23 -0
  288. package/routing/recipes/docs-drift.yaml +21 -0
  289. package/routing/recipes/existing-codebase-onboarding.yaml +32 -0
  290. package/routing/recipes/extract-learnings.yaml +22 -0
  291. package/routing/recipes/greenfield-fast.yaml +25 -0
  292. package/routing/recipes/greenfield-manual.yaml +32 -0
  293. package/routing/recipes/greenfield-with-ideation.yaml +29 -0
  294. package/routing/recipes/incident-postmortem.yaml +24 -0
  295. package/routing/recipes/major-framework-upgrade.yaml +23 -0
  296. package/routing/recipes/monthly-deps.yaml +22 -0
  297. package/routing/recipes/multi-repo-suite.yaml +56 -0
  298. package/routing/recipes/parallel-engineers.yaml +26 -0
  299. package/routing/recipes/pause-handoff.yaml +21 -0
  300. package/routing/recipes/production-broken.yaml +26 -0
  301. package/routing/recipes/rerun-tier.yaml +21 -0
  302. package/routing/recipes/returning-after-break.yaml +31 -0
  303. package/routing/recipes/state-drift.yaml +21 -0
  304. package/routing/recipes/undo-last.yaml +21 -0
  305. package/routing/recipes/weekly-health-check.yaml +24 -0
  306. package/routing/recipes/whats-next.yaml +22 -0
  307. package/routing/recipes/where-am-i.yaml +21 -0
  308. package/schema/events.v1.json +63 -0
  309. package/schema/extension-manifest.v1.json +84 -0
  310. package/schema/intent.v1.yaml.json +116 -0
  311. package/schema/recipe.v1.json +120 -0
  312. package/schema/routing.v1.json +163 -0
  313. package/schema/state.v1.json +146 -0
  314. package/schema/workflow.v1.json +96 -0
  315. package/skills/god-add-backlog.md +40 -0
  316. package/skills/god-add-tests.md +53 -0
  317. package/skills/god-add-todo.md +32 -0
  318. package/skills/god-agent-audit.md +87 -0
  319. package/skills/god-arch.md +81 -0
  320. package/skills/god-archaeology.md +48 -0
  321. package/skills/god-audit.md +65 -0
  322. package/skills/god-budget.md +103 -0
  323. package/skills/god-build-agent.md +91 -0
  324. package/skills/god-build.md +90 -0
  325. package/skills/god-cache-clear.md +75 -0
  326. package/skills/god-check-todos.md +42 -0
  327. package/skills/god-context-scan.md +125 -0
  328. package/skills/god-context.md +147 -0
  329. package/skills/god-cost.md +118 -0
  330. package/skills/god-debug.md +30 -0
  331. package/skills/god-deploy.md +76 -0
  332. package/skills/god-design-impact.md +86 -0
  333. package/skills/god-design.md +275 -0
  334. package/skills/god-discuss.md +46 -0
  335. package/skills/god-docs.md +81 -0
  336. package/skills/god-doctor.md +94 -0
  337. package/skills/god-explore.md +50 -0
  338. package/skills/god-export-otel.md +87 -0
  339. package/skills/god-extension-add.md +79 -0
  340. package/skills/god-extension-info.md +75 -0
  341. package/skills/god-extension-list.md +55 -0
  342. package/skills/god-extension-remove.md +66 -0
  343. package/skills/god-extract-learnings.md +60 -0
  344. package/skills/god-fast.md +47 -0
  345. package/skills/god-feature.md +114 -0
  346. package/skills/god-graph.md +56 -0
  347. package/skills/god-harden.md +106 -0
  348. package/skills/god-help.md +66 -0
  349. package/skills/god-hotfix.md +139 -0
  350. package/skills/god-hygiene.md +104 -0
  351. package/skills/god-init.md +161 -0
  352. package/skills/god-intel.md +36 -0
  353. package/skills/god-launch.md +86 -0
  354. package/skills/god-lifecycle.md +119 -0
  355. package/skills/god-link.md +90 -0
  356. package/skills/god-lint.md +128 -0
  357. package/skills/god-list-assumptions.md +56 -0
  358. package/skills/god-locate.md +97 -0
  359. package/skills/god-logs.md +57 -0
  360. package/skills/god-map-codebase.md +45 -0
  361. package/skills/god-metrics.md +51 -0
  362. package/skills/god-mode.md +159 -0
  363. package/skills/god-next.md +257 -0
  364. package/skills/god-note.md +39 -0
  365. package/skills/god-observe.md +76 -0
  366. package/skills/god-org-context.md +81 -0
  367. package/skills/god-party.md +87 -0
  368. package/skills/god-pause-work.md +64 -0
  369. package/skills/god-plant-seed.md +59 -0
  370. package/skills/god-postmortem.md +103 -0
  371. package/skills/god-pr-branch.md +50 -0
  372. package/skills/god-prd.md +90 -0
  373. package/skills/god-quick.md +50 -0
  374. package/skills/god-reconcile.md +90 -0
  375. package/skills/god-reconstruct.md +72 -0
  376. package/skills/god-redo.md +73 -0
  377. package/skills/god-refactor.md +137 -0
  378. package/skills/god-repair.md +82 -0
  379. package/skills/god-repo.md +49 -0
  380. package/skills/god-restore.md +91 -0
  381. package/skills/god-resume-work.md +42 -0
  382. package/skills/god-review-changes.md +93 -0
  383. package/skills/god-review.md +52 -0
  384. package/skills/god-roadmap-check.md +66 -0
  385. package/skills/god-roadmap-update.md +64 -0
  386. package/skills/god-roadmap.md +77 -0
  387. package/skills/god-rollback.md +88 -0
  388. package/skills/god-scan.md +106 -0
  389. package/skills/god-set-profile.md +58 -0
  390. package/skills/god-settings.md +44 -0
  391. package/skills/god-skip.md +78 -0
  392. package/skills/god-smite.md +86 -0
  393. package/skills/god-spike.md +120 -0
  394. package/skills/god-sprint.md +77 -0
  395. package/skills/god-stack.md +74 -0
  396. package/skills/god-standards.md +62 -0
  397. package/skills/god-status.md +99 -0
  398. package/skills/god-stories.md +60 -0
  399. package/skills/god-story-build.md +76 -0
  400. package/skills/god-story-close.md +82 -0
  401. package/skills/god-story-verify.md +71 -0
  402. package/skills/god-story.md +55 -0
  403. package/skills/god-suite-init.md +75 -0
  404. package/skills/god-suite-patch.md +64 -0
  405. package/skills/god-suite-release.md +58 -0
  406. package/skills/god-suite-status.md +63 -0
  407. package/skills/god-suite-sync.md +49 -0
  408. package/skills/god-sync.md +102 -0
  409. package/skills/god-tech-debt.md +56 -0
  410. package/skills/god-test-extension.md +87 -0
  411. package/skills/god-test-runtime.md +144 -0
  412. package/skills/god-thread.md +39 -0
  413. package/skills/god-trace.md +50 -0
  414. package/skills/god-undo.md +68 -0
  415. package/skills/god-update-deps.md +134 -0
  416. package/skills/god-upgrade.md +139 -0
  417. package/skills/god-version.md +37 -0
  418. package/skills/god-workstream.md +61 -0
  419. package/skills/god.md +207 -0
  420. package/templates/ARCH.md +99 -0
  421. package/templates/DEPS-AUDIT.md +66 -0
  422. package/templates/DESIGN.md +71 -0
  423. package/templates/DOCS-UPDATE-LOG.md +64 -0
  424. package/templates/HARDEN-FINDINGS.md +69 -0
  425. package/templates/MIGRATION.md +86 -0
  426. package/templates/POSTMORTEM.md +88 -0
  427. package/templates/PRD.md +80 -0
  428. package/templates/PROGRESS.md +49 -0
  429. package/templates/ROADMAP.md +47 -0
  430. package/templates/SPIKE.md +72 -0
  431. package/templates/STACK-DECISION.md +61 -0
  432. package/workflows/audit-only.yaml +22 -0
  433. package/workflows/bluefield-arc.yaml +87 -0
  434. package/workflows/brownfield-arc.yaml +44 -0
  435. package/workflows/deps-audit.yaml +56 -0
  436. package/workflows/docs-arc.yaml +22 -0
  437. package/workflows/feature-arc.yaml +59 -0
  438. package/workflows/full-arc.yaml +84 -0
  439. package/workflows/hotfix-arc.yaml +59 -0
  440. package/workflows/hygiene.yaml +43 -0
  441. package/workflows/migration-arc.yaml +73 -0
  442. package/workflows/postmortem.yaml +31 -0
  443. package/workflows/refactor-arc.yaml +59 -0
  444. package/workflows/spike.yaml +23 -0
@@ -0,0 +1,647 @@
1
+ /**
2
+ * Have-Nots Validator
3
+ *
4
+ * Registry of mechanical checks against the 99 named have-nots from
5
+ * references/HAVE-NOTS.md. Each check returns structured findings:
6
+ *
7
+ * { code, severity: 'error'|'warning'|'info', line, column, message, suggestion }
8
+ *
9
+ * Universal checks apply to all artifacts. Per-artifact checks apply only
10
+ * when the linter is given a typed artifact (e.g., 'prd', 'arch').
11
+ *
12
+ * Source of truth for which have-nots are mechanical:
13
+ *
14
+ * U-08 em/en dash mechanical (regex)
15
+ * U-09 emoji mechanical (unicode regex)
16
+ * U-02 unlabeled sentence mechanical (sentence scan + label check)
17
+ * U-10 phantom reference mechanical (link scan + filesystem check)
18
+ * U-11 future-dated timestamp mechanical (date parse vs today)
19
+ * P-04 metric without timeline mechanical (metric + time-word scan)
20
+ * P-05 metric without method mechanical (metric + measurement-word)
21
+ * P-07 no-gos empty mechanical (section presence)
22
+ * P-08 open-q without owner mechanical (table column scan)
23
+ * P-09 open-q without due date mechanical (table column scan)
24
+ * A-04 NFR not mapped mechanical (PRD NFR vs ARCH map)
25
+ *
26
+ * Substitution test (U-01) is a partial mechanical check: flags sentences
27
+ * containing only generic nouns ('users', 'developers', 'scalable',
28
+ * 'robust', 'modern', 'intuitive', 'seamless') without specific
29
+ * quantifiers or proper nouns.
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+
35
+ const GENERIC_NOUNS = [
36
+ 'users', 'developers', 'teams', 'people', 'customers',
37
+ 'scalable', 'robust', 'modern', 'intuitive', 'seamless',
38
+ 'best-in-class', 'world-class', 'next-generation', 'cutting-edge',
39
+ 'simple', 'easy', 'fast', 'powerful'
40
+ ];
41
+
42
+ const LABEL_TAGS = ['DECISION', 'HYPOTHESIS', 'OPEN QUESTION', 'OPEN-QUESTION'];
43
+
44
+ /**
45
+ * Find all line/column positions for a given regex match in content.
46
+ * Returns array of { line, column } objects (1-indexed).
47
+ */
48
+ function findPositions(content, regex) {
49
+ const positions = [];
50
+ const lines = content.split('\n');
51
+ for (let i = 0; i < lines.length; i++) {
52
+ let match;
53
+ const localRegex = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
54
+ while ((match = localRegex.exec(lines[i])) !== null) {
55
+ positions.push({ line: i + 1, column: match.index + 1, matched: match[0] });
56
+ }
57
+ }
58
+ return positions;
59
+ }
60
+
61
+ // ============================================================================
62
+ // Universal checks (apply to all artifacts)
63
+ // ============================================================================
64
+
65
+ /** U-08: em or en dash present */
66
+ function checkEmEnDash(content) {
67
+ const findings = [];
68
+ const positions = findPositions(content, /[–—]/g);
69
+ for (const p of positions) {
70
+ findings.push({
71
+ code: 'U-08',
72
+ severity: 'error',
73
+ line: p.line,
74
+ column: p.column,
75
+ message: `Em or en dash detected ("${p.matched}"). Use comma, colon, semicolon, parentheses, or hyphen instead.`,
76
+ suggestion: 'Replace with appropriate ASCII punctuation.'
77
+ });
78
+ }
79
+ return findings;
80
+ }
81
+
82
+ /** U-09: emoji present */
83
+ function checkEmoji(content) {
84
+ const findings = [];
85
+ // Common emoji ranges. Excludes basic punctuation and arrows that have
86
+ // legitimate documentation use.
87
+ const emojiRanges = /[\u{1F300}-\u{1F9FF}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu;
88
+ const positions = findPositions(content, emojiRanges);
89
+ for (const p of positions) {
90
+ findings.push({
91
+ code: 'U-09',
92
+ severity: 'error',
93
+ line: p.line,
94
+ column: p.column,
95
+ message: `Decorative emoji "${p.matched}" detected.`,
96
+ suggestion: 'Use words or icon library symbols instead.'
97
+ });
98
+ }
99
+ return findings;
100
+ }
101
+
102
+ /** U-02: unlabeled paragraph (paragraph-aware) */
103
+ function checkUnlabeled(content, opts = {}) {
104
+ const findings = [];
105
+ const lines = content.split('\n');
106
+ let inCodeBlock = false;
107
+ let paragraph = [];
108
+ let paragraphStartLine = -1;
109
+
110
+ function flushParagraph() {
111
+ if (paragraph.length === 0) return;
112
+ const text = paragraph.join(' ').trim();
113
+ paragraph = [];
114
+ if (!text || text.length < 50) return;
115
+ // Skip intro lines that end with a colon (likely list/section preamble)
116
+ if (text.endsWith(':')) return;
117
+ // If the paragraph contains ANY label tag, accept the whole paragraph
118
+ const hasLabel = LABEL_TAGS.some(tag => text.includes(`[${tag}]`));
119
+ if (hasLabel) return;
120
+ findings.push({
121
+ code: 'U-02',
122
+ severity: 'warning',
123
+ line: paragraphStartLine,
124
+ column: 1,
125
+ message: `Unlabeled paragraph: "${text.slice(0, 80)}${text.length > 80 ? '...' : ''}"`,
126
+ suggestion: 'Tag with [DECISION], [HYPOTHESIS], or [OPEN QUESTION].'
127
+ });
128
+ }
129
+
130
+ let inBulletContext = false;
131
+
132
+ for (let i = 0; i < lines.length; i++) {
133
+ const line = lines[i];
134
+ const trimmed = line.trim();
135
+
136
+ if (trimmed.startsWith('```')) {
137
+ flushParagraph();
138
+ inCodeBlock = !inCodeBlock;
139
+ continue;
140
+ }
141
+ if (inCodeBlock) continue;
142
+
143
+ // Bullet starts a bullet context (which absorbs indented continuation)
144
+ if (trimmed.startsWith('- ') || trimmed.startsWith('* ') || trimmed.startsWith('- [')) {
145
+ flushParagraph();
146
+ inBulletContext = true;
147
+ continue;
148
+ }
149
+
150
+ // Indented continuation under a bullet stays in the bullet
151
+ const isIndented = /^\s+\S/.test(line);
152
+ if (inBulletContext && isIndented) {
153
+ continue;
154
+ }
155
+
156
+ // Lines that break paragraphs
157
+ const isBreak =
158
+ !trimmed ||
159
+ trimmed.startsWith('#') ||
160
+ trimmed.startsWith('>') ||
161
+ trimmed.startsWith('|') ||
162
+ trimmed.startsWith('---') ||
163
+ trimmed.startsWith('===') ||
164
+ trimmed.startsWith('<') ||
165
+ /^\d+\.\s/.test(trimmed);
166
+
167
+ if (isBreak) {
168
+ flushParagraph();
169
+ inBulletContext = false;
170
+ continue;
171
+ }
172
+
173
+ // Non-indented content line ends bullet context
174
+ inBulletContext = false;
175
+
176
+ // Continuation or start of a paragraph
177
+ if (paragraph.length === 0) paragraphStartLine = i + 1;
178
+ paragraph.push(trimmed);
179
+ }
180
+ flushParagraph();
181
+ return findings;
182
+ }
183
+
184
+ /** U-10: phantom reference (link to file that does not exist) */
185
+ function checkPhantomRef(content, opts = {}) {
186
+ const findings = [];
187
+ const projectRoot = opts.projectRoot || process.cwd();
188
+ const docDir = opts.docDir || projectRoot;
189
+ // Match markdown links: [text](path) where path doesn't start with http
190
+ const linkRegex = /\[[^\]]+\]\(([^)]+)\)/g;
191
+ const lines = content.split('\n');
192
+ for (let i = 0; i < lines.length; i++) {
193
+ let match;
194
+ const localRegex = new RegExp(linkRegex.source, 'g');
195
+ while ((match = localRegex.exec(lines[i])) !== null) {
196
+ const ref = match[1].split('#')[0]; // strip anchor
197
+ if (!ref || ref.startsWith('http') || ref.startsWith('mailto:')) continue;
198
+ const resolved = path.isAbsolute(ref) ? ref : path.resolve(docDir, ref);
199
+ if (!fs.existsSync(resolved)) {
200
+ // Try project root resolution
201
+ const altResolved = path.resolve(projectRoot, ref);
202
+ if (!fs.existsSync(altResolved)) {
203
+ findings.push({
204
+ code: 'U-10',
205
+ severity: 'warning',
206
+ line: i + 1,
207
+ column: match.index + 1,
208
+ message: `Phantom reference: link target "${ref}" does not exist.`,
209
+ suggestion: 'Fix the link or create the referenced file.'
210
+ });
211
+ }
212
+ }
213
+ }
214
+ }
215
+ return findings;
216
+ }
217
+
218
+ /** U-11: future-dated timestamp */
219
+ function checkFutureDate(content, opts = {}) {
220
+ const findings = [];
221
+ const today = opts.today ? new Date(opts.today) : new Date();
222
+ // Match ISO-like dates: 2026-05-10, 2026-12-31
223
+ const dateRegex = /\b(20\d{2})-(\d{2})-(\d{2})\b/g;
224
+ const lines = content.split('\n');
225
+ for (let i = 0; i < lines.length; i++) {
226
+ let match;
227
+ while ((match = dateRegex.exec(lines[i])) !== null) {
228
+ const [full, year, month, day] = match;
229
+ const parsed = new Date(`${year}-${month}-${day}`);
230
+ if (isNaN(parsed.getTime())) continue;
231
+ // Allow due-date columns to be future (those are intentional)
232
+ // Heuristic: if the line contains "Due:" or "Owner:" or is in a table, skip
233
+ if (lines[i].includes('Due:') || lines[i].includes('Owner:') || lines[i].trim().startsWith('|')) {
234
+ continue;
235
+ }
236
+ // Future timestamps in body content are suspicious
237
+ const oneYearOut = new Date(today);
238
+ oneYearOut.setFullYear(today.getFullYear() + 1);
239
+ if (parsed > oneYearOut) {
240
+ findings.push({
241
+ code: 'U-11',
242
+ severity: 'warning',
243
+ line: i + 1,
244
+ column: match.index + 1,
245
+ message: `Future-dated timestamp "${full}" (more than a year out) in non-deadline context.`,
246
+ suggestion: 'Verify the date is correct or move to an Open Questions due-date.'
247
+ });
248
+ }
249
+ }
250
+ }
251
+ return findings;
252
+ }
253
+
254
+ /** U-01 (partial): substitution test - flag generic nouns without quantifiers */
255
+ function checkSubstitution(content) {
256
+ const findings = [];
257
+ const lines = content.split('\n');
258
+ for (let i = 0; i < lines.length; i++) {
259
+ const line = lines[i].trim();
260
+ if (line.startsWith('#') || line.startsWith('|') || line.startsWith('- [') || !line) continue;
261
+ // Look for sentences containing only generic nouns and no numbers/proper nouns
262
+ const sentences = line.split(/(?<=[.?!])\s+/);
263
+ for (const s of sentences) {
264
+ if (s.length < 30) continue;
265
+ const lower = s.toLowerCase();
266
+ const hasGeneric = GENERIC_NOUNS.some(g => lower.includes(` ${g} `) || lower.includes(` ${g}.`) || lower.includes(`${g} `));
267
+ const hasNumber = /\d/.test(s);
268
+ const hasProperNoun = /[A-Z][a-z]+ [A-Z]/.test(s); // Two consecutive capitalized words
269
+ if (hasGeneric && !hasNumber && !hasProperNoun) {
270
+ findings.push({
271
+ code: 'U-01',
272
+ severity: 'warning',
273
+ line: i + 1,
274
+ column: 1,
275
+ message: `Possibly generic claim (substitution test risk): "${s.slice(0, 80)}${s.length > 80 ? '...' : ''}"`,
276
+ suggestion: 'Add specific numbers, named users, or proper nouns. Could a competitor say this verbatim?'
277
+ });
278
+ }
279
+ }
280
+ }
281
+ return findings;
282
+ }
283
+
284
+ // ============================================================================
285
+ // Per-artifact checks
286
+ // ============================================================================
287
+
288
+ /**
289
+ * Collect bullet items from a section body. Multi-line bullets are joined
290
+ * into a single string. Returns [{ text, startLine }].
291
+ */
292
+ function collectBullets(body, sectionStartLine) {
293
+ const lines = body.split('\n');
294
+ const bullets = [];
295
+ let current = null;
296
+ for (let i = 0; i < lines.length; i++) {
297
+ const line = lines[i];
298
+ const trimmed = line.trim();
299
+ if (trimmed.startsWith('-') || trimmed.startsWith('*')) {
300
+ if (current) bullets.push(current);
301
+ current = { text: trimmed.replace(/^[-*]\s*/, ''), startLine: sectionStartLine + i };
302
+ } else if (current && trimmed && !/^#/.test(trimmed) && !trimmed.startsWith('|')) {
303
+ // Continuation line (indented or just wrapping)
304
+ current.text += ' ' + trimmed;
305
+ } else if (!trimmed) {
306
+ // Blank line ends the current bullet
307
+ if (current) {
308
+ bullets.push(current);
309
+ current = null;
310
+ }
311
+ } else {
312
+ // Heading or non-list content ends the current bullet
313
+ if (current) {
314
+ bullets.push(current);
315
+ current = null;
316
+ }
317
+ }
318
+ }
319
+ if (current) bullets.push(current);
320
+ return bullets;
321
+ }
322
+
323
+ /** P-04: success metric without timeline */
324
+ function checkPrdMetricTimeline(content) {
325
+ const findings = [];
326
+ const successSection = extractSection(content, /^##\s*Success Metrics/im);
327
+ if (!successSection) return findings;
328
+
329
+ const timeWords = /(within|by|in|over)\s+\d+\s*(day|days|week|weeks|month|months|year|years)|by\s+(week|day|month|year|Q[1-4])\s*\d+|by\s+\d{4}-\d{2}-\d{2}|by\s+(Q[1-4])/i;
330
+ const bullets = collectBullets(successSection.body, successSection.startLine);
331
+ for (const b of bullets) {
332
+ if (!timeWords.test(b.text)) {
333
+ findings.push({
334
+ code: 'P-04',
335
+ severity: 'error',
336
+ line: b.startLine,
337
+ column: 1,
338
+ message: 'Success metric without timeline (no "within N days/weeks/months" or "by YYYY-MM-DD").',
339
+ suggestion: 'Add a time bound to make the metric measurable.'
340
+ });
341
+ }
342
+ }
343
+ return findings;
344
+ }
345
+
346
+ /** P-05: success metric without measurement method */
347
+ function checkPrdMetricMethod(content) {
348
+ const findings = [];
349
+ const successSection = extractSection(content, /^##\s*Success Metrics/im);
350
+ if (!successSection) return findings;
351
+
352
+ const methodWords = /(measured|tracked|monitored|via|using)\s+/i;
353
+ const bullets = collectBullets(successSection.body, successSection.startLine);
354
+ for (const b of bullets) {
355
+ if (!methodWords.test(b.text)) {
356
+ findings.push({
357
+ code: 'P-05',
358
+ severity: 'warning',
359
+ line: b.startLine,
360
+ column: 1,
361
+ message: 'Success metric without measurement method.',
362
+ suggestion: 'Specify how it will be measured (e.g., "measured via analytics").'
363
+ });
364
+ }
365
+ }
366
+ return findings;
367
+ }
368
+
369
+ /** P-07: No-Gos section empty */
370
+ function checkPrdNoGos(content) {
371
+ const findings = [];
372
+ const noGoSection = extractSection(content, /^##\s*Scope and No-Gos|^##\s*No.?Gos/im);
373
+ if (!noGoSection) {
374
+ findings.push({
375
+ code: 'P-07',
376
+ severity: 'error',
377
+ line: 1,
378
+ column: 1,
379
+ message: 'Missing "Scope and No-Gos" section.',
380
+ suggestion: 'Add a section listing what is explicitly NOT being built.'
381
+ });
382
+ return findings;
383
+ }
384
+ // Check for "explicitly NOT" subsection content. Locate the heading and
385
+ // scan the lines immediately following for at least one bullet item.
386
+ const body = noGoSection.body;
387
+ const lines = body.split('\n');
388
+ let headingIdx = -1;
389
+ for (let i = 0; i < lines.length; i++) {
390
+ if (/^###\s.*not\s+in\s+scope/i.test(lines[i])) {
391
+ headingIdx = i;
392
+ break;
393
+ }
394
+ }
395
+ let hasItem = false;
396
+ if (headingIdx !== -1) {
397
+ for (let i = headingIdx + 1; i < lines.length; i++) {
398
+ if (/^###\s/.test(lines[i])) break; // next subsection
399
+ if (/^-\s+\S/.test(lines[i].trim()) || /^\*\s+\S/.test(lines[i].trim())) {
400
+ hasItem = true;
401
+ break;
402
+ }
403
+ }
404
+ } else {
405
+ // No subsection found, accept any list items in body as no-gos
406
+ hasItem = /^[-*]\s+\S/m.test(body);
407
+ }
408
+ if (!hasItem) {
409
+ findings.push({
410
+ code: 'P-07',
411
+ severity: 'error',
412
+ line: noGoSection.startLine,
413
+ column: 1,
414
+ message: 'No-Gos list is empty or missing.',
415
+ suggestion: 'List at least one thing explicitly not being built.'
416
+ });
417
+ }
418
+ return findings;
419
+ }
420
+
421
+ /** P-08, P-09: open questions missing owner or due date */
422
+ function checkPrdOpenQuestions(content) {
423
+ const findings = [];
424
+ const oqSection = extractSection(content, /^##\s*Open Questions/im);
425
+ if (!oqSection) return findings;
426
+
427
+ const lines = oqSection.body.split('\n');
428
+ // Look for table rows: | Question | Owner | Due Date | Resolution |
429
+ let inTable = false;
430
+ let headerProcessed = false;
431
+ for (let i = 0; i < lines.length; i++) {
432
+ const line = lines[i].trim();
433
+ if (line.startsWith('|') && line.includes('|')) {
434
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
435
+ // Skip header and separator rows
436
+ if (cells.some(c => /^-+$/.test(c))) continue;
437
+ if (!headerProcessed) {
438
+ if (cells.some(c => /question/i.test(c))) {
439
+ headerProcessed = true;
440
+ continue;
441
+ }
442
+ }
443
+ if (headerProcessed && cells.length >= 3) {
444
+ const question = cells[0];
445
+ const owner = cells[1] || '';
446
+ const dueDate = cells[2] || '';
447
+ if (question && /^\[?[A-Z]/.test(question) && question.length > 5) {
448
+ if (!owner || /TBD|^\[/.test(owner)) {
449
+ findings.push({
450
+ code: 'P-08',
451
+ severity: 'error',
452
+ line: oqSection.startLine + i,
453
+ column: 1,
454
+ message: `Open question "${question.slice(0, 50)}" has no named owner.`,
455
+ suggestion: 'Assign a named owner.'
456
+ });
457
+ }
458
+ if (!dueDate || /TBD|^\[/.test(dueDate)) {
459
+ findings.push({
460
+ code: 'P-09',
461
+ severity: 'error',
462
+ line: oqSection.startLine + i,
463
+ column: 1,
464
+ message: `Open question "${question.slice(0, 50)}" has no due date.`,
465
+ suggestion: 'Set a due date. "TBD" is not a date.'
466
+ });
467
+ }
468
+ }
469
+ }
470
+ }
471
+ }
472
+ return findings;
473
+ }
474
+
475
+ /** A-04: NFR not mapped to architectural choice */
476
+ function checkArchNfrMap(content, opts = {}) {
477
+ const findings = [];
478
+ const prdContent = opts.prdContent;
479
+ if (!prdContent) return findings; // Cannot cross-check without PRD
480
+
481
+ // Find NFR section in PRD
482
+ const prdNfrSection = extractSection(prdContent, /^##\s*Non-Functional Requirements/im);
483
+ if (!prdNfrSection) return findings;
484
+
485
+ const archMapSection = extractSection(content, /^##\s*NFR-to-Architecture Map/im);
486
+ if (!archMapSection) {
487
+ findings.push({
488
+ code: 'A-04',
489
+ severity: 'error',
490
+ line: 1,
491
+ column: 1,
492
+ message: 'ARCH missing "NFR-to-Architecture Map" section.',
493
+ suggestion: 'Add a section mapping each PRD NFR to an architectural choice.'
494
+ });
495
+ return findings;
496
+ }
497
+
498
+ // Extract category names from PRD NFR table
499
+ const prdLines = prdNfrSection.body.split('\n');
500
+ const nfrCategories = [];
501
+ for (const line of prdLines) {
502
+ if (line.startsWith('|')) {
503
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
504
+ if (cells.length >= 2 && !cells.some(c => /^-+$/.test(c)) && !/category/i.test(cells[0])) {
505
+ nfrCategories.push(cells[0]);
506
+ }
507
+ }
508
+ }
509
+
510
+ // Check ARCH map mentions each
511
+ const mapBody = archMapSection.body.toLowerCase();
512
+ for (const cat of nfrCategories) {
513
+ if (cat && cat.length > 1 && !mapBody.includes(cat.toLowerCase())) {
514
+ findings.push({
515
+ code: 'A-04',
516
+ severity: 'warning',
517
+ line: archMapSection.startLine,
518
+ column: 1,
519
+ message: `PRD NFR "${cat}" not mapped in ARCH NFR-to-Architecture Map.`,
520
+ suggestion: `Add a row for "${cat}" with the architectural choice that delivers it.`
521
+ });
522
+ }
523
+ }
524
+ return findings;
525
+ }
526
+
527
+ // ============================================================================
528
+ // Helpers
529
+ // ============================================================================
530
+
531
+ /**
532
+ * Extract a markdown section starting at a heading regex.
533
+ * Returns { body, startLine, endLine } or null.
534
+ */
535
+ function extractSection(content, headingRegex) {
536
+ const lines = content.split('\n');
537
+ let startIdx = -1;
538
+ for (let i = 0; i < lines.length; i++) {
539
+ if (headingRegex.test(lines[i])) {
540
+ startIdx = i;
541
+ break;
542
+ }
543
+ }
544
+ if (startIdx === -1) return null;
545
+ let endIdx = lines.length;
546
+ for (let i = startIdx + 1; i < lines.length; i++) {
547
+ if (/^##\s/.test(lines[i])) {
548
+ endIdx = i;
549
+ break;
550
+ }
551
+ }
552
+ return {
553
+ body: lines.slice(startIdx + 1, endIdx).join('\n'),
554
+ startLine: startIdx + 1,
555
+ endLine: endIdx
556
+ };
557
+ }
558
+
559
+ // ============================================================================
560
+ // Public API
561
+ // ============================================================================
562
+
563
+ const UNIVERSAL_CHECKS = [
564
+ { code: 'U-08', fn: checkEmEnDash },
565
+ { code: 'U-09', fn: checkEmoji },
566
+ { code: 'U-02', fn: checkUnlabeled },
567
+ { code: 'U-10', fn: checkPhantomRef },
568
+ { code: 'U-11', fn: checkFutureDate },
569
+ { code: 'U-01', fn: checkSubstitution }
570
+ ];
571
+
572
+ const ARTIFACT_CHECKS = {
573
+ prd: [
574
+ { code: 'P-04', fn: checkPrdMetricTimeline },
575
+ { code: 'P-05', fn: checkPrdMetricMethod },
576
+ { code: 'P-07', fn: checkPrdNoGos },
577
+ { code: 'P-08', fn: checkPrdOpenQuestions },
578
+ { code: 'P-09', fn: checkPrdOpenQuestions }
579
+ ],
580
+ arch: [
581
+ { code: 'A-04', fn: checkArchNfrMap }
582
+ ]
583
+ };
584
+
585
+ /**
586
+ * Run all checks for a given artifact.
587
+ * Returns a deduplicated, line-sorted list of findings.
588
+ */
589
+ function runChecks(content, artifactType, opts = {}) {
590
+ const findings = [];
591
+ for (const c of UNIVERSAL_CHECKS) {
592
+ findings.push(...c.fn(content, opts));
593
+ }
594
+ if (artifactType && ARTIFACT_CHECKS[artifactType]) {
595
+ const seen = new Set();
596
+ for (const c of ARTIFACT_CHECKS[artifactType]) {
597
+ // Avoid running the same fn twice (P-08 and P-09 share fn)
598
+ if (seen.has(c.fn)) continue;
599
+ seen.add(c.fn);
600
+ findings.push(...c.fn(content, opts));
601
+ }
602
+ }
603
+ // Sort by line number
604
+ findings.sort((a, b) => a.line - b.line);
605
+ // Dedupe identical findings
606
+ const dedup = [];
607
+ const seenKeys = new Set();
608
+ for (const f of findings) {
609
+ const key = `${f.code}:${f.line}:${f.message}`;
610
+ if (seenKeys.has(key)) continue;
611
+ seenKeys.add(key);
612
+ dedup.push(f);
613
+ }
614
+ return dedup;
615
+ }
616
+
617
+ /**
618
+ * Summary of findings: counts by severity and code.
619
+ */
620
+ function summarize(findings) {
621
+ const summary = { errors: 0, warnings: 0, infos: 0, byCode: {} };
622
+ for (const f of findings) {
623
+ summary[f.severity + 's']++;
624
+ summary.byCode[f.code] = (summary.byCode[f.code] || 0) + 1;
625
+ }
626
+ return summary;
627
+ }
628
+
629
+ module.exports = {
630
+ runChecks,
631
+ summarize,
632
+ UNIVERSAL_CHECKS,
633
+ ARTIFACT_CHECKS,
634
+ // Exposed for testing
635
+ checkEmEnDash,
636
+ checkEmoji,
637
+ checkUnlabeled,
638
+ checkPhantomRef,
639
+ checkFutureDate,
640
+ checkSubstitution,
641
+ checkPrdMetricTimeline,
642
+ checkPrdMetricMethod,
643
+ checkPrdNoGos,
644
+ checkPrdOpenQuestions,
645
+ checkArchNfrMap,
646
+ extractSection
647
+ };