@polymorphism-tech/morph-spec 8.47.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/CLAUDE.md +51 -43
  2. package/README.md +39 -38
  3. package/bin/morph-spec.js +41 -113
  4. package/bin/validate.js +1 -147
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +86 -21
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-status.md +4 -4
  17. package/framework/docs/CLI.md +574 -286
  18. package/framework/evals/review-checklist.md +82 -41
  19. package/framework/hooks/README.md +10 -11
  20. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  21. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  22. package/framework/hooks/claude-code/core/chain.js +4 -65
  23. package/framework/hooks/claude-code/core/comment-guard.js +3 -17
  24. package/framework/hooks/claude-code/core/denylist.json +33 -3
  25. package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
  26. package/framework/hooks/claude-code/core/gate-guard.js +9 -78
  27. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  28. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  29. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  30. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  31. package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
  32. package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
  33. package/framework/hooks/claude-code/core/state-sync.js +15 -85
  34. package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
  35. package/framework/hooks/claude-code/core/validate-completion.js +37 -147
  36. package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
  37. package/framework/hooks/claude-code/core/worktree-context.js +15 -114
  38. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  39. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  40. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  41. package/framework/hooks/claude-code/statusline.py +55 -41
  42. package/framework/hooks/package.json +3 -0
  43. package/framework/hooks/shared/activity-logger.js +9 -89
  44. package/framework/hooks/shared/agent-reap.js +2 -17
  45. package/framework/hooks/shared/build-check.js +11 -76
  46. package/framework/hooks/shared/claim-registry.js +9 -119
  47. package/framework/hooks/shared/command-tokens.js +168 -44
  48. package/framework/hooks/shared/comment-rules.js +5 -20
  49. package/framework/hooks/shared/comment-scanner.js +11 -43
  50. package/framework/hooks/shared/comment-stripper.js +11 -85
  51. package/framework/hooks/shared/e2e-check.js +0 -0
  52. package/framework/hooks/shared/errata.js +59 -0
  53. package/framework/hooks/shared/feature-locator.js +11 -95
  54. package/framework/hooks/shared/frontend-locations.js +4 -0
  55. package/framework/hooks/shared/gate-question.js +12 -0
  56. package/framework/hooks/shared/git-exclude.js +3 -20
  57. package/framework/hooks/shared/git-paths.js +8 -104
  58. package/framework/hooks/shared/graph-freshness.js +22 -166
  59. package/framework/hooks/shared/graph-refresh.js +21 -149
  60. package/framework/hooks/shared/heavy-command.js +4 -12
  61. package/framework/hooks/shared/hook-response.js +8 -83
  62. package/framework/hooks/shared/morph-cli.js +8 -52
  63. package/framework/hooks/shared/payload-utils.js +10 -132
  64. package/framework/hooks/shared/phase-utils.js +14 -72
  65. package/framework/hooks/shared/redact-secrets.js +4 -15
  66. package/framework/hooks/shared/reparse-scan.js +6 -66
  67. package/framework/hooks/shared/slice-progress.js +99 -0
  68. package/framework/hooks/shared/source-files.js +4 -17
  69. package/framework/hooks/shared/spawn-command.js +23 -0
  70. package/framework/hooks/shared/state-reader.js +51 -247
  71. package/framework/hooks/shared/state-writer.js +9 -54
  72. package/framework/hooks/shared/stdin-reader.js +1 -11
  73. package/framework/hooks/shared/telemetry-logger.js +7 -67
  74. package/framework/hooks/shared/transcript-usage.js +7 -29
  75. package/framework/hooks/shared/worktree-root.js +7 -29
  76. package/framework/mcp/registry.json +3 -4
  77. package/framework/phases.json +23 -39
  78. package/framework/rules/csharp-standards.md +1 -1
  79. package/framework/rules/frontend-standards.md +1 -0
  80. package/framework/rules/hangfire-standards.md +1 -1
  81. package/framework/rules/neon-standards.md +1 -1
  82. package/framework/schemas/config.schema.json +14 -9
  83. package/framework/schemas/feature.schema.json +15 -50
  84. package/framework/skills/README.md +3 -4
  85. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  86. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  87. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  88. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  89. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  90. package/framework/skills/core/morph-media/SKILL.md +1 -1
  91. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  92. package/framework/skills/core/morph-review/SKILL.md +111 -136
  93. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  94. package/framework/skills/core/morph-uiux/SKILL.md +94 -152
  95. package/framework/standards/STANDARDS.json +109 -1219
  96. package/framework/standards/STANDARDS.meta.json +1319 -0
  97. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  98. package/framework/standards/ai-agents/agent-session.md +1 -1
  99. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  100. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  101. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  102. package/framework/standards/ai-agents/guardrails.md +1 -1
  103. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  104. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  105. package/framework/standards/ai-agents/production.md +1 -1
  106. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  107. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  108. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  109. package/framework/standards/ai-agents/setup.md +3 -3
  110. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  111. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  112. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  113. package/framework/standards/ai-agents/workflows.md +1 -1
  114. package/framework/standards/architecture/code-comments.md +7 -7
  115. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  116. package/framework/standards/backend/api/validation.md +1 -1
  117. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  118. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  119. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  120. package/framework/standards/data/nosql/cache/redis.md +1 -1
  121. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  122. package/framework/standards/frontend/admin-app/forms.md +1 -1
  123. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  124. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  125. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  126. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  127. package/framework/templates/CLAUDE.md.core.template +3 -3
  128. package/framework/templates/README.md +2 -3
  129. package/framework/templates/REGISTRY.json +15 -179
  130. package/framework/templates/docs/plan.md +72 -0
  131. package/framework/templates/docs/ui-mockups.md +4 -0
  132. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  134. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  135. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  136. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  137. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  138. package/package.json +1 -1
  139. package/src/commands/context/context-refresh.js +0 -16
  140. package/src/commands/dashboard/dashboard.js +2 -8
  141. package/src/commands/e2e/e2e.js +18 -105
  142. package/src/commands/graph/affected.js +0 -14
  143. package/src/commands/graph/build.js +11 -50
  144. package/src/commands/graph/explain.js +0 -16
  145. package/src/commands/graph/status.js +1 -12
  146. package/src/commands/mcp/mcp-setup.js +4 -62
  147. package/src/commands/observability/cost.js +17 -111
  148. package/src/commands/observability/telemetry.js +1 -14
  149. package/src/commands/project/branch.js +4 -45
  150. package/src/commands/project/claim.js +4 -48
  151. package/src/commands/project/doctor-procs.js +1 -1
  152. package/src/commands/project/doctor.js +53 -307
  153. package/src/commands/project/env.js +1 -27
  154. package/src/commands/project/fleet.js +6 -35
  155. package/src/commands/project/heavy.js +3 -3
  156. package/src/commands/project/init.js +8 -68
  157. package/src/commands/project/install-plugin-cmd.js +0 -1
  158. package/src/commands/project/setup-infra-cmd.js +2 -1
  159. package/src/commands/project/stack-scan.js +58 -0
  160. package/src/commands/project/standards.js +65 -72
  161. package/src/commands/project/status.js +28 -121
  162. package/src/commands/project/update.js +31 -223
  163. package/src/commands/project/worktree.js +109 -883
  164. package/src/commands/scaffold/scaffold.js +0 -25
  165. package/src/commands/state/advance.js +65 -120
  166. package/src/commands/state/approve.js +87 -229
  167. package/src/commands/state/archive.js +23 -139
  168. package/src/commands/state/auto-approval.js +65 -0
  169. package/src/commands/state/create.js +49 -109
  170. package/src/commands/state/delete.js +11 -63
  171. package/src/commands/state/finish.js +105 -494
  172. package/src/commands/state/gate-check.js +74 -183
  173. package/src/commands/state/gate-decision.js +8 -42
  174. package/src/commands/state/handoff.js +4 -31
  175. package/src/commands/state/list.js +21 -91
  176. package/src/commands/state/report-check.js +134 -338
  177. package/src/commands/state/retype.js +12 -28
  178. package/src/commands/validation/index.js +0 -3
  179. package/src/commands/validation/mutate.js +79 -19
  180. package/src/commands/validation/plan-check.js +81 -0
  181. package/src/commands/validation/validate-feature.js +52 -216
  182. package/src/commands/validation/verify.js +45 -160
  183. package/src/core/paths/output-schema.js +9 -155
  184. package/src/core/state/feature-identity.js +1 -25
  185. package/src/core/state/gate-order.js +2 -30
  186. package/src/core/state/lifecycle-audit.js +8 -100
  187. package/src/core/state/state-manager.js +32 -395
  188. package/src/core/templates/template-registry.js +2 -97
  189. package/src/core/templates/template-renderer.js +13 -93
  190. package/src/core/templates/template-seed.js +21 -0
  191. package/src/core/work-types.js +10 -45
  192. package/src/dashboard/app/App.jsx +1 -8
  193. package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
  194. package/src/dashboard/app/components/CommandPalette.jsx +4 -11
  195. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  196. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  197. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  198. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  199. package/src/dashboard/app/components/Markdown.jsx +0 -9
  200. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  201. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  202. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  203. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  204. package/src/dashboard/app/hooks/usePref.js +1 -5
  205. package/src/dashboard/app/lib/format.js +0 -2
  206. package/src/dashboard/app/lib/phases.js +3 -9
  207. package/src/dashboard/app/lib/project.js +0 -12
  208. package/src/dashboard/app/pages/Activity.jsx +1 -1
  209. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  210. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
  211. package/src/dashboard/app/pages/Features.jsx +9 -16
  212. package/src/dashboard/app/pages/Guide.jsx +2 -3
  213. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  214. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
  215. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  216. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  217. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  218. package/src/dashboard/app/pages/Standards.jsx +2 -17
  219. package/src/dashboard/scanner/activity-scanner.js +14 -83
  220. package/src/dashboard/scanner/agents-scanner.js +1 -23
  221. package/src/dashboard/scanner/docs-scanner.js +1 -7
  222. package/src/dashboard/scanner/feature-status.js +2 -5
  223. package/src/dashboard/scanner/features-scanner.js +92 -113
  224. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  225. package/src/dashboard/scanner/index.js +0 -4
  226. package/src/dashboard/scanner/portfolio.js +5 -29
  227. package/src/dashboard/scanner/project-api.js +8 -54
  228. package/src/dashboard/scanner/rules-scanner.js +1 -2
  229. package/src/dashboard/scanner/skills-scanner.js +2 -12
  230. package/src/dashboard/scanner/standards-scanner.js +4 -7
  231. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  232. package/src/lib/ai/ai-checks.js +7 -52
  233. package/src/lib/ai/ai-project-scan.js +14 -108
  234. package/src/lib/ai/code-audit.js +7 -63
  235. package/src/lib/ai/pin-drift.js +8 -51
  236. package/src/lib/ai/registry-audit.js +11 -90
  237. package/src/lib/ci/ci-verify.js +14 -13
  238. package/src/lib/ci/github-ci.js +9 -37
  239. package/src/lib/ci/workflow-scaffold.js +2 -10
  240. package/src/lib/detectors/claude-config-detector.js +13 -74
  241. package/src/lib/detectors/index.js +8 -59
  242. package/src/lib/e2e/compose-env.js +5 -66
  243. package/src/lib/e2e/stack-teardown.js +6 -40
  244. package/src/lib/generators/architecture-scanner.js +18 -156
  245. package/src/lib/generators/handoff-generator.js +37 -112
  246. package/src/lib/generators/scaffold-generator.js +5 -101
  247. package/src/lib/generators/settings-generator.js +11 -122
  248. package/src/lib/git/branch-classifier.js +11 -84
  249. package/src/lib/git/default-branch.js +14 -169
  250. package/src/lib/git/evidence-staleness.js +2 -1
  251. package/src/lib/git/gh.js +2 -17
  252. package/src/lib/git/pending-prs.js +6 -37
  253. package/src/lib/git/pr-files.js +3 -28
  254. package/src/lib/git/pr-index.js +3 -34
  255. package/src/lib/git/tree-fingerprint.js +17 -17
  256. package/src/lib/git/tree-state.js +6 -33
  257. package/src/lib/graph/god-nodes.js +2 -22
  258. package/src/lib/graph/graph-health.js +3 -24
  259. package/src/lib/graph/graph-loader.js +5 -56
  260. package/src/lib/graph/graphify-runtime.js +7 -37
  261. package/src/lib/graph/graphifyignore.js +8 -74
  262. package/src/lib/graph/sanitize.js +16 -136
  263. package/src/lib/heavy/cleanup.js +2 -6
  264. package/src/lib/installers/effects.js +8 -93
  265. package/src/lib/installers/mcp-installer.js +9 -169
  266. package/src/lib/installers/memory-installer.js +8 -33
  267. package/src/lib/installers/templates-reconcile.js +5 -60
  268. package/src/lib/mutation/commands.js +3 -2
  269. package/src/lib/mutation/dotnet-introspection.js +281 -0
  270. package/src/lib/mutation/plan.js +14 -6
  271. package/src/lib/mutation/runner.js +1 -1
  272. package/src/lib/mutation/subset.js +47 -13
  273. package/src/lib/plan/artifact-vocabulary.js +40 -0
  274. package/src/lib/plan/checks.js +208 -0
  275. package/src/lib/plan/plan-parser.js +204 -0
  276. package/src/lib/plan/plan-template.js +18 -0
  277. package/src/lib/projects/registry.js +7 -44
  278. package/src/lib/review-reports.js +204 -0
  279. package/src/lib/risk-detector.js +131 -274
  280. package/src/lib/specs/adr-index.js +38 -85
  281. package/src/lib/specs/design-seed.js +18 -0
  282. package/src/lib/specs/frontmatter.js +31 -0
  283. package/src/lib/specs/gfm-table.js +11 -0
  284. package/src/lib/specs/revocations.js +31 -102
  285. package/src/lib/specs/screen-checklist.js +56 -132
  286. package/src/lib/stack/dotnet-build-ignores.js +43 -0
  287. package/src/lib/stack/stack-profile.js +1 -28
  288. package/src/lib/stack/stack-scan.js +260 -0
  289. package/src/lib/stack-filter.js +4 -27
  290. package/src/lib/standards/registry.js +37 -0
  291. package/src/lib/standards/sections.js +47 -0
  292. package/src/lib/standards/verified-against.js +19 -191
  293. package/src/lib/tasks/eval-node.js +23 -179
  294. package/src/lib/tasks/linux-runner.js +18 -142
  295. package/src/lib/tasks/test-nodes.js +43 -313
  296. package/src/lib/tasks/test-runner.js +107 -447
  297. package/src/lib/telemetry/metrics.js +13 -103
  298. package/src/lib/telemetry/telemetry-reader.js +13 -238
  299. package/src/lib/telemetry/verify-event.js +4 -34
  300. package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
  301. package/src/lib/validators/code-budget/index.js +0 -7
  302. package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
  303. package/src/lib/validators/contracts/index.js +0 -6
  304. package/src/lib/validators/nextjs/index.js +0 -5
  305. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  306. package/src/lib/validators/packages/index.js +0 -6
  307. package/src/lib/validators/packages/package-validator.js +6 -56
  308. package/src/lib/validators/reuse/index.js +0 -8
  309. package/src/lib/validators/reuse/reuse-validator.js +24 -137
  310. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  311. package/src/lib/validators/traceability-check.js +54 -156
  312. package/src/lib/validators/validation-runner.js +20 -125
  313. package/src/lib/validators/verify-runner.js +152 -575
  314. package/src/lib/work-type-detector.js +5 -67
  315. package/src/lib/worktree/docker-audit.js +17 -137
  316. package/src/lib/worktree/env-allocator.js +10 -98
  317. package/src/lib/worktree/feature-truth.js +12 -115
  318. package/src/lib/worktree/finish-backup.js +32 -242
  319. package/src/lib/worktree/fleet-scanner.js +31 -126
  320. package/src/lib/worktree/ignored-triage.js +15 -91
  321. package/src/lib/worktree/vscode-settings.js +2 -3
  322. package/src/lib/worktree/worktree-audit.js +21 -213
  323. package/src/lib/worktree/worktree-include.js +3 -5
  324. package/src/scripts/global-install.js +3 -9
  325. package/src/scripts/install-plugin.js +1 -13
  326. package/src/scripts/setup-infra.js +17 -138
  327. package/src/utils/agents-installer.js +92 -272
  328. package/src/utils/banner.js +4 -25
  329. package/src/utils/claude-md-injector.js +1 -37
  330. package/src/utils/claude-settings-manager.js +1 -87
  331. package/src/utils/file-copier.js +32 -90
  332. package/src/utils/hooks-installer.js +26 -296
  333. package/src/utils/skills-installer.js +4 -102
  334. package/src/utils/version-checker.js +4 -83
  335. package/framework/commands/morph-proposal.md +0 -57
  336. package/framework/evals/trace-schema.md +0 -67
  337. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  338. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  339. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  340. package/framework/hooks/shared/tasks-validator.js +0 -111
  341. package/framework/rules/morph-task-tracking.md +0 -54
  342. package/framework/rules/morph-workflow.md +0 -244
  343. package/framework/schemas/tasks.schema.json +0 -79
  344. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  345. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  346. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  347. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  348. package/framework/templates/docs/proposal.md +0 -192
  349. package/framework/templates/docs/spec.md +0 -57
  350. package/framework/templates/docs/ui-components.md +0 -52
  351. package/framework/templates/docs/ui-design-system.md +0 -76
  352. package/framework/templates/docs/ui-flows.md +0 -167
  353. package/framework/templates/examples/spec-examples.md +0 -91
  354. package/framework/templates/feature/decisions.md +0 -124
  355. package/framework/templates/feature/recap.md +0 -145
  356. package/framework/templates/tasks.template.json +0 -17
  357. package/src/commands/state/usecase.js +0 -143
  358. package/src/commands/tasks/dag.js +0 -181
  359. package/src/commands/utils/index.js +0 -3
  360. package/src/lib/agents/output-collector.js +0 -76
  361. package/src/lib/detectors/design-system-detector.js +0 -188
  362. package/src/lib/evaluator-report.js +0 -104
  363. package/src/lib/generators/design-system-generator.js +0 -508
  364. package/src/lib/specs/acervo-format.js +0 -70
  365. package/src/lib/specs/acervo-lifecycle.js +0 -264
  366. package/src/lib/specs/done-criteria-check.js +0 -200
  367. package/src/lib/specs/usecase-check.js +0 -301
  368. package/src/lib/specs/usecase-promoter.js +0 -301
  369. package/src/lib/specs/usecase-registry.js +0 -237
  370. package/src/lib/tasks/dispatch-planner.js +0 -319
  371. package/src/lib/tasks/dotnet-test-introspection.js +0 -632
  372. package/src/lib/tasks/mermaid-renderer.js +0 -132
  373. package/src/lib/tasks/task-classifier.js +0 -30
  374. package/src/lib/tasks/task-parser.js +0 -209
  375. package/src/lib/tasks/tasks-json-parser.js +0 -296
  376. package/src/lib/tasks/test-scope.js +0 -597
  377. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,44 +1,13 @@
1
- /**
2
- * claim-registry.js — advisory feature ownership across parallel sessions.
3
- *
4
- * Two Claude Code sessions worked the SAME feature at the same time without
5
- * seeing each other: one finished it and removed the worktree out from under
6
- * the other's running sub-agent. A complete recap.md was discarded, a sub-agent
7
- * wasted, and a decision taken on state that was already obsolete. There was no
8
- * lock, no claim, no heartbeat — not even a warning.
9
- *
10
- * DELIBERATELY ADVISORY. This is not a lock: nothing here ever blocks a
11
- * command. The framework is fail-open by principle, and a strong lock left
12
- * behind by a crashed session would strand a feature for no gain. Being
13
- * VISIBLE is the whole fix.
14
- *
15
- * Location: `<git-common-dir>/morph-spec/claims.json`, beside the fleet's
16
- * env-registry, for the same three reasons — it can never dirty the `git
17
- * status` the finish gates read, it survives `git clean -fdx`, and the COMMON
18
- * dir is the one place every linked worktree of the repo shares. That last one
19
- * is decisive here: in the observed incident one session sat inside the
20
- * worktree and the other in the primary root, so a claim written to the
21
- * branch-local `feature.json` would have been invisible to exactly the session
22
- * that needed to read it.
23
- *
24
- * Writes are tmp+rename (torn file impossible); a corrupt registry reads as
25
- * empty — the cost is a lost warning, never a crash.
26
- */
1
+ // ADVISORY, never a lock: a lock left by a crashed session would strand a feature.
2
+ // Lives in the git COMMON dir, the one place every worktree of the repo shares.
27
3
 
28
4
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
29
5
  import { dirname, join } from 'path';
30
6
 
31
- /**
32
- * A claim with no heartbeat for this long is considered dead. The heartbeat
33
- * fires on tool activity, so this is really "how long a session may sit idle
34
- * before its claim stops warning others".
35
- */
36
7
  export const CLAIM_TTL_MS = 30 * 60 * 1000;
37
8
 
38
- /** Minimum gap between heartbeat WRITES. The heartbeat runs on every tool call. */
39
9
  export const HEARTBEAT_THROTTLE_MS = 60 * 1000;
40
10
 
41
- /** Claim file path for a repo's common git dir. */
42
11
  export function claimsPath(gitCommonDir) {
43
12
  return join(gitCommonDir, 'morph-spec', 'claims.json');
44
13
  }
@@ -60,23 +29,13 @@ function writeRegistry(gitCommonDir, registry) {
60
29
  renameSync(tmp, path);
61
30
  }
62
31
 
63
- /** Is this claim still within its TTL? A missing/unparseable heartbeat is dead. */
64
32
  export function isLive(claim, now = Date.now()) {
65
33
  const beat = Date.parse(claim?.heartbeatAt ?? '');
66
34
  return Number.isFinite(beat) && now - beat < CLAIM_TTL_MS;
67
35
  }
68
36
 
69
- /**
70
- * Claim a feature for this session, or refresh an existing claim of ours.
71
- *
72
- * A claim held by ANOTHER session is TAKEN OVER, not refused — advisory by
73
- * design, and the caller has already been warned via `foreignClaim`. The
74
- * takeover is recorded in `previousSessionId` so it is never silent.
75
- *
76
- * @param {string} feature
77
- * @param {{ gitCommonDir: string, sessionId: string, pid?: number, path?: string }} opts
78
- * @returns {object} the stored claim
79
- */
37
+ // Another session's claim is TAKEN OVER, not refused; `previousSessionId` keeps
38
+ // the takeover visible.
80
39
  export function acquireClaim(feature, { gitCommonDir, sessionId, pid = process.pid, path = null }) {
81
40
  const registry = readRegistry(gitCommonDir);
82
41
  const previous = registry.claims[feature];
@@ -95,17 +54,7 @@ export function acquireClaim(feature, { gitCommonDir, sessionId, pid = process.p
95
54
  return claim;
96
55
  }
97
56
 
98
- /**
99
- * Refresh our own claim's heartbeat. Throttled — this runs on every tool call,
100
- * and a write per call would be pure I/O waste.
101
- *
102
- * Never touches a claim owned by another session: a heartbeat is proof of life,
103
- * and forging someone else's would defeat the TTL.
104
- *
105
- * @param {string} feature
106
- * @param {{ gitCommonDir: string, sessionId: string }} opts
107
- * @returns {boolean} true when a write actually happened
108
- */
57
+ // Never another session's claim: forging its heartbeat would defeat the TTL.
109
58
  export function heartbeatClaim(feature, { gitCommonDir, sessionId }) {
110
59
  const registry = readRegistry(gitCommonDir);
111
60
  const claim = registry.claims[feature];
@@ -119,45 +68,13 @@ export function heartbeatClaim(feature, { gitCommonDir, sessionId }) {
119
68
  return true;
120
69
  }
121
70
 
122
- /** Local, git-independent heartbeat pre-check marker — see `shouldHeartbeat`. */
123
71
  function heartbeatMarkPath(cwd) {
124
72
  return join(cwd, '.morph', 'logs', '.heartbeat-check.json');
125
73
  }
126
74
 
127
- /**
128
- * Cheap, git-independent pre-check for whether a heartbeat is even worth
129
- * ATTEMPTING. `heartbeatClaim` already throttles the WRITE to `claims.json`,
130
- * but that registry lives under the git COMMON dir — resolving it (even via
131
- * the pure-FS `git-paths.js`, never mind the old `execSync('git rev-parse
132
- * --git-common-dir')`) is work `telemetry-log.js` pays on every tool call.
133
- * This answers "is it due" from a tiny marker colocated with the CALLER's own
134
- * `.morph/logs/` (already known, no git lookup of any kind) so that on ~99%
135
- * of calls NOTHING git-related happens at all — not the subprocess, not even
136
- * the FS walk.
137
- *
138
- * Side effect: when it returns `true` (heartbeat IS due), it immediately
139
- * stamps the marker — mirroring `heartbeatClaim`'s own check-and-write
140
- * pattern — so the NEXT call within the throttle window short-circuits here
141
- * even if the caller's subsequent git resolution then fails (not actually a
142
- * git repo, say): a failed attempt should not be retried on every single tool
143
- * call either.
144
- *
145
- * This is a SEPARATE, LOCAL throttle from `claims.json`'s own authoritative
146
- * `heartbeatAt` (keyed by git-common-dir, enforced inside `heartbeatClaim`) —
147
- * the two can drift by up to one throttle window with no consequence: a
148
- * missed local pre-check only costs a claim heartbeat that gets written on
149
- * the very next call, and claims are advisory to begin with.
150
- *
151
- * Fail-open: any read/write error is treated as "due" — a broken marker must
152
- * never permanently suppress the actual (authoritative) heartbeat. Not
153
- * tmp+rename like `claims.json` — this file is low-stakes (worst case from a
154
- * torn write is corrupt JSON, which just reads back as "due" next time) and
155
- * shared by nobody outside this one project tree.
156
- *
157
- * @param {string} feature
158
- * @param {{ cwd: string, sessionId: string, now?: number }} opts
159
- * @returns {boolean}
160
- */
75
+ // Local pre-check so most tool calls skip resolving the git common dir. Stamps
76
+ // the marker when due, so a failed attempt is not retried on every call; an
77
+ // unreadable marker counts as due.
161
78
  export function shouldHeartbeat(feature, { cwd, sessionId, now = Date.now() }) {
162
79
  const markPath = heartbeatMarkPath(cwd);
163
80
  try {
@@ -167,27 +84,16 @@ export function shouldHeartbeat(feature, { cwd, sessionId, now = Date.now() }) {
167
84
  if (Number.isFinite(at) && now - at < HEARTBEAT_THROTTLE_MS) return false;
168
85
  }
169
86
  } catch {
170
- // missing/corrupt marker — treat as due
171
87
  }
172
88
 
173
89
  try {
174
90
  mkdirSync(dirname(markPath), { recursive: true });
175
91
  writeFileSync(markPath, JSON.stringify({ feature, sessionId, at: new Date(now).toISOString() }));
176
92
  } catch {
177
- // best-effort — a failed stamp only costs a repeated attempt next call
178
93
  }
179
94
  return true;
180
95
  }
181
96
 
182
- /**
183
- * The LIVE claim on this feature held by a session that is not us — the thing
184
- * worth warning about. Returns null when the feature is unclaimed, claimed by
185
- * us, or claimed by a session whose heartbeat went stale.
186
- *
187
- * @param {string} feature
188
- * @param {{ gitCommonDir: string, sessionId?: string }} opts
189
- * @returns {object|null}
190
- */
191
97
  export function foreignClaim(feature, { gitCommonDir, sessionId }) {
192
98
  const claim = readRegistry(gitCommonDir).claims[feature];
193
99
  if (!claim) return null;
@@ -195,14 +101,6 @@ export function foreignClaim(feature, { gitCommonDir, sessionId }) {
195
101
  return isLive(claim) ? claim : null;
196
102
  }
197
103
 
198
- /**
199
- * Drop a feature's claim. Called by the manual `morph-spec claim <f> --release`
200
- * (recovering from a session that died without releasing) and by `finish`.
201
- *
202
- * @param {string} feature
203
- * @param {{ gitCommonDir: string }} opts
204
- * @returns {boolean} false when there was nothing to release
205
- */
206
104
  export function releaseClaim(feature, { gitCommonDir }) {
207
105
  const registry = readRegistry(gitCommonDir);
208
106
  if (!registry.claims[feature]) return false;
@@ -211,14 +109,7 @@ export function releaseClaim(feature, { gitCommonDir }) {
211
109
  return true;
212
110
  }
213
111
 
214
- /**
215
- * Every claim on record, live and stale, newest heartbeat first. `live` is
216
- * computed, never stored — a stored flag would go wrong the moment nobody
217
- * rewrites the file.
218
- *
219
- * @param {string} gitCommonDir
220
- * @returns {object[]}
221
- */
112
+ // `live` is computed, never stored: a stored flag goes wrong once nobody rewrites the file.
222
113
  export function listClaims(gitCommonDir) {
223
114
  const now = Date.now();
224
115
  return Object.values(readRegistry(gitCommonDir).claims)
@@ -226,7 +117,6 @@ export function listClaims(gitCommonDir) {
226
117
  .sort((a, b) => String(b.heartbeatAt).localeCompare(String(a.heartbeatAt)));
227
118
  }
228
119
 
229
- /** Human-readable age of a claim's last heartbeat ("3 min", "2 h", "—"). */
230
120
  export function claimAge(claim, now = Date.now()) {
231
121
  const beat = Date.parse(claim?.heartbeatAt ?? '');
232
122
  if (!Number.isFinite(beat)) return '—';
@@ -1,19 +1,6 @@
1
- /**
2
- * command-tokens.js — shell-ish tokenizer shared by the PreToolUse Bash guards.
3
- *
4
- * Lives in shared/ (not inside a hook) on purpose: every core hook ends in a
5
- * standalone tail that reads stdin and exits, so a hook importing a sibling
6
- * hook for a helper would fire THAT tail in its own process — consuming the
7
- * payload and exiting before the importer ever ran. block-raw-worktree-add
8
- * and block-destructive-worktree-removal both read paths off these tokens,
9
- * so they agree on what a quoted path is.
10
- */
11
-
12
- /**
13
- * Split a shell-ish command into tokens, honoring single and double quotes.
14
- * @param {string} command
15
- * @returns {string[]}
16
- */
1
+ // Lives in shared/: a hook importing a sibling hook would fire that hook's
2
+ // stdin-reading tail in its own process.
3
+
17
4
  export function tokenize(command) {
18
5
  const tokens = [];
19
6
  const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
@@ -22,7 +9,6 @@ export function tokenize(command) {
22
9
  return tokens;
23
10
  }
24
11
 
25
- /** Index of the first pipe (`|`, not `||` nor `>|`) outside quotes, or -1. */
26
12
  function firstUnquotedPipe(command) {
27
13
  const s = String(command || '');
28
14
  let quote = null;
@@ -43,24 +29,11 @@ function firstUnquotedPipe(command) {
43
29
  return -1;
44
30
  }
45
31
 
46
- /**
47
- * Does the command pipe one process into another outside quotes? Without
48
- * `pipefail` the shell reports the LAST command's status, so the exit code of
49
- * `dotnet test … | grep …` is grep's, not the test run's.
50
- * @param {string} command
51
- * @returns {boolean}
52
- */
53
32
  export function hasUnquotedPipe(command) {
54
33
  return firstUnquotedPipe(command) >= 0;
55
34
  }
56
35
 
57
- /**
58
- * The work a command does, without the filter that shapes its output:
59
- * `dotnet test 2>&1 | tail -25` and `dotnet test | tail -20` run the same
60
- * tests. Whitespace collapsed, stderr redirections dropped.
61
- * @param {string} command
62
- * @returns {string}
63
- */
36
+ /** The command without the pipe filter and stderr redirections that shape its output. */
64
37
  export function producerOf(command) {
65
38
  const s = String(command || '');
66
39
  const pipe = firstUnquotedPipe(s);
@@ -70,11 +43,6 @@ export function producerOf(command) {
70
43
  .trim();
71
44
  }
72
45
 
73
- /**
74
- * The command split at `;`, newline and background `&` outside quotes, plus
75
- * whether a background `&` was seen. `&&`, `||` and `>&`/`&>` redirections
76
- * stay inside their segment.
77
- */
78
46
  function topLevelSegments(command) {
79
47
  const s = String(command || '');
80
48
  const segments = [];
@@ -102,14 +70,7 @@ function topLevelSegments(command) {
102
70
 
103
71
  const REPORTER = /^(?:echo|printf|true|:|exit\s+0)\b/;
104
72
 
105
- /**
106
- * Is the exit code of this command the exit code of the work it ran? The
107
- * shell reports the last segment of a `;` sequence: `dotnet test > log;
108
- * echo "exit=$?"` always exits 0, and so does `dotnet test || true`. A pipe
109
- * without `pipefail` reports the filter, and a background `&` reports 0.
110
- * @param {string} command
111
- * @returns {boolean}
112
- */
73
+ /** True when the shell reports something other than the work's exit code (`; echo`, `|| true`, pipe, `&`). */
113
74
  export function exitCodeMasked(command) {
114
75
  const { segments, background } = topLevelSegments(command);
115
76
  if (background) return true;
@@ -117,3 +78,166 @@ export function exitCodeMasked(command) {
117
78
  const last = segments[segments.length - 1] || '';
118
79
  return (segments.length > 1 && REPORTER.test(last)) || /\|\|/.test(last.replace(/"[^"]*"|'[^']*'/g, ''));
119
80
  }
81
+
82
+ const WRITE_REDIRECT = /^(?:[1&])?>>?\|?/;
83
+
84
+ function quotedTokens(text) {
85
+ const tokens = [];
86
+ const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
87
+ let m;
88
+ while ((m = re.exec(text)) !== null) {
89
+ tokens.push({ text: m[1] ?? m[2] ?? m[3], quoted: m[3] === undefined, single: m[2] !== undefined });
90
+ }
91
+ return tokens;
92
+ }
93
+
94
+ const CHAIN = /^(?:&&|\|\||\||\|&)$/;
95
+ const ASSIGNMENT = /^([A-Za-z_]\w*)=(.*)$/;
96
+ const REDIRECT = /^(?:\d|&)?[<>]/;
97
+ const SCRIPT_RUNNER = /^(?:python[\d.]*|py|node|bun|deno)$/;
98
+
99
+ function simpleCommands(head) {
100
+ const commands = [];
101
+ for (const segment of topLevelSegments(head).segments) {
102
+ let current = [];
103
+ for (const token of quotedTokens(segment)) {
104
+ if (!token.quoted && CHAIN.test(token.text)) { commands.push(current); current = []; } else current.push(token);
105
+ }
106
+ commands.push(current);
107
+ }
108
+ return commands.filter((c) => c.length > 0);
109
+ }
110
+
111
+ function positionals(args, takesValue) {
112
+ const out = [];
113
+ const values = [];
114
+ for (let i = 0; i < args.length; i++) {
115
+ const { text, quoted } = args[i];
116
+ if (!quoted && REDIRECT.test(text)) { if (/[<>]$/.test(text)) i++; continue; }
117
+ if (!quoted && text.startsWith('-') && text !== '-') {
118
+ if (takesValue(text) && i + 1 < args.length) values.push(args[++i].text);
119
+ continue;
120
+ }
121
+ out.push(text);
122
+ }
123
+ return { files: out, values };
124
+ }
125
+
126
+ function sedTargets(args) {
127
+ const inPlace = args.some(({ text, quoted }) => !quoted && (/^-[A-Za-z]*i/.test(text) || text.startsWith('--in-place')));
128
+ if (!inPlace) return [];
129
+ const { files, values } = positionals(args, (o) => /^-[A-Za-z]*[ef]$/.test(o) || o === '--expression' || o === '--file');
130
+ return values.length > 0 ? files : files.slice(1);
131
+ }
132
+
133
+ function perlTargets(args) {
134
+ if (!args.some(({ text, quoted }) => !quoted && /^-[A-Za-z]*i/.test(text))) return [];
135
+ const { files, values } = positionals(args, (o) => /^-[A-Za-z.]*[eE]$/.test(o));
136
+ return values.length > 0 ? files : files.slice(1);
137
+ }
138
+
139
+ function copyTargets(args) {
140
+ if (args.some(({ text }) => text === '-t' || text.startsWith('--target-directory'))) return [];
141
+ const { files } = positionals(args, () => false);
142
+ if (files.length < 2) return [];
143
+ const dest = files[files.length - 1];
144
+ if (!dest.endsWith('/')) return [dest];
145
+ return files.slice(0, -1).map((src) => dest + src.replace(/^.*\//, ''));
146
+ }
147
+
148
+ const PY_OPEN_WRITE = /\bopen\(\s*([^,()]+?)\s*,\s*(?:mode\s*=\s*)?(['"])([^'"]*)\2/g;
149
+ const JS_WRITE = /\b(?:writeFileSync|writeFile|appendFileSync|appendFile)\(\s*([^,()]+?)\s*,/g;
150
+ const PY_PATH_WRITE = /\.write_(?:text|bytes)\(/;
151
+ const STRING_LITERAL = /(['"`])((?:(?!\1)[^\\\n]|\\.)*)\1/g;
152
+ const PATH_LITERAL = /^[\w@./-]*[\w-]\.[A-Za-z]{1,5}$/;
153
+
154
+ function literalOf(expr) {
155
+ const m = /^(['"`])([^'"`]*)\1$/.exec(expr.trim());
156
+ return m ? m[2] : null;
157
+ }
158
+
159
+ // A write call with a computed path (`open(p, "w")`) counts every path-shaped literal in the script.
160
+ function scriptWrites(script) {
161
+ const literals = [];
162
+ let computed = PY_PATH_WRITE.test(script);
163
+ for (const m of script.matchAll(PY_OPEN_WRITE)) {
164
+ if (!/[wax+]/.test(m[3])) continue;
165
+ const lit = literalOf(m[1]);
166
+ if (lit === null) computed = true; else literals.push(lit);
167
+ }
168
+ for (const m of script.matchAll(JS_WRITE)) {
169
+ const lit = literalOf(m[1]);
170
+ if (lit === null) computed = true; else literals.push(lit);
171
+ }
172
+ if (!computed) return literals;
173
+ const mentioned = [...script.matchAll(STRING_LITERAL)].map((m) => m[2]).filter((s) => PATH_LITERAL.test(s));
174
+ return [...new Set([...literals, ...mentioned])];
175
+ }
176
+
177
+ function runnerScript(args, body) {
178
+ for (let i = 0; i < args.length; i++) {
179
+ if (['-c', '-e', '-p', '--eval', '--print'].includes(args[i].text) && !args[i].quoted) return args[i + 1]?.text ?? null;
180
+ }
181
+ const { files } = positionals(args, () => false);
182
+ return (files.length === 0 || files[0] === '-') ? body : null;
183
+ }
184
+
185
+ function heredocBody(lines, at) {
186
+ if (at < 0) return null;
187
+ const delim = /<<-?\s*(['"]?)([\w.-]+)\1/.exec(lines[at])?.[2];
188
+ if (!delim) return null;
189
+ const end = lines.findIndex((line, i) => i > at && line.trim() === delim);
190
+ return lines.slice(at + 1, end < 0 ? undefined : end).join('\n');
191
+ }
192
+
193
+ function commandWriteTargets(head, body) {
194
+ const vars = new Map();
195
+ const expand = ({ text, single }) => (single ? text : text.replace(/\$\{?([A-Za-z_]\w*)\}?/g, (all, name) => vars.get(name) ?? all));
196
+ const targets = [];
197
+ for (const tokens of simpleCommands(head)) {
198
+ let i = 0;
199
+ for (; i < tokens.length && !tokens[i].quoted && ASSIGNMENT.test(tokens[i].text); i++) {
200
+ const [, name, value] = ASSIGNMENT.exec(tokens[i].text);
201
+ vars.set(name, value);
202
+ }
203
+ if (i >= tokens.length) continue;
204
+ const word = tokens[i].text.replace(/^.*[\\/]/, '').replace(/\.exe$/i, '');
205
+ const args = tokens.slice(i + 1).map((t) => ({ ...t, text: expand(t) }));
206
+ if (word === 'sed') targets.push(...sedTargets(args));
207
+ else if (word === 'perl') targets.push(...perlTargets(args));
208
+ else if (word === 'cp' || word === 'mv') targets.push(...copyTargets(args));
209
+ else if (SCRIPT_RUNNER.test(word)) {
210
+ const script = runnerScript(args, body);
211
+ if (script) targets.push(...scriptWrites(script));
212
+ }
213
+ }
214
+ return targets;
215
+ }
216
+
217
+ // The redirect scan stops at the heredoc line: the body is data, and an
218
+ // apostrophe in it would derail the quote-aware split.
219
+ export function writeTargets(command) {
220
+ const lines = String(command || '').split('\n');
221
+ const heredocAt = lines.findIndex((line) => line.includes('<<'));
222
+ const head = (heredocAt >= 0 ? lines.slice(0, heredocAt + 1) : lines).join('\n');
223
+ const tokens = quotedTokens(head);
224
+
225
+ const targets = [];
226
+ for (let i = 0; i < tokens.length; i++) {
227
+ const { text, quoted } = tokens[i];
228
+ if (quoted) continue;
229
+ const op = text.match(WRITE_REDIRECT);
230
+ if (op) {
231
+ const rest = text.slice(op[0].length);
232
+ const target = rest || tokens[i + 1]?.text;
233
+ if (target && !target.startsWith('&')) targets.push(target);
234
+ continue;
235
+ }
236
+ if (text.replace(/^.*[\/]/, '') === 'tee') {
237
+ for (let j = i + 1; j < tokens.length && !/^[;&|]/.test(tokens[j].text); j++) {
238
+ if (!tokens[j].text.startsWith('-')) targets.push(tokens[j].text);
239
+ }
240
+ }
241
+ }
242
+ return [...new Set([...targets, ...commandWriteTargets(head, heredocBody(lines, heredocAt))])];
243
+ }
@@ -1,11 +1,5 @@
1
- /**
2
- * The comment shapes the `architecture-code-comments` standard forbids in
3
- * every case, judged only on the comments an edit ADDED. Whether a short
4
- * comment restates the code is not decidable here; that stays with the
5
- * Gate 3 evaluator and the `code-budget` density.
6
- *
7
- * @module comment-rules
8
- */
1
+ // Judges only the comments an edit ADDED, and only their shape: whether a
2
+ // comment restates the code is not decidable here.
9
3
 
10
4
  import { existsSync, readdirSync, readFileSync } from 'fs';
11
5
  import { dirname, join } from 'path';
@@ -58,13 +52,7 @@ function nearest(startDir, test) {
58
52
  }
59
53
  }
60
54
 
61
- /**
62
- * Doc-comments are allowed only on a package consumed outside the repo. When
63
- * that cannot be read from the nearest project file, the rule stays quiet.
64
- *
65
- * @param {string} file - absolute path of the edited file
66
- * @returns {boolean}
67
- */
55
+ /** Doc-comments are allowed only on a package consumed outside the repo; unreadable = quiet. */
68
56
  export function docCommentsForbidden(file) {
69
57
  const dir = dirname(file);
70
58
  if (/\.cs$/i.test(file)) {
@@ -85,7 +73,7 @@ export function docCommentsForbidden(file) {
85
73
  return JSON.parse(readFileSync(pkg, 'utf8')).private === true;
86
74
  }
87
75
 
88
- /** Lines of `after` whose comment is not one `before` already had (moved comments do not count). */
76
+ // Moved comments do not count as added.
89
77
  function addedCommentLines(before, after, file) {
90
78
  const remaining = new Map();
91
79
  for (const text of commentLines(before, file).values()) {
@@ -115,10 +103,7 @@ function addedRegionLines(before, after) {
115
103
  return out;
116
104
  }
117
105
 
118
- /**
119
- * @param {{ file: string, before: string, after: string, docCommentsForbidden?: boolean }} input
120
- * @returns {Array<{ line: number, rule: 'process'|'banner'|'doc-comment'|'long-block', text: string }>}
121
- */
106
+ /** @returns {Array<{ line: number, rule: 'process'|'banner'|'doc-comment'|'long-block', text: string }>} */
122
107
  export function commentFindings({ file, before, after, docCommentsForbidden: docForbidden = false }) {
123
108
  const isCs = /\.cs$/i.test(file);
124
109
  const all = commentLines(after, file);
@@ -1,58 +1,27 @@
1
- /**
2
- * Linhas de comentário de um arquivo e se elas contam história de processo.
3
- *
4
- * O comentário é o que `stripComments` apaga: o mesmo varredor que já separa
5
- * comentário de string para o `doctor --ai`, com a sintaxe de TS/JS ligada
6
- * fora do C#. Um regex por linha confundiria `https://` com comentário.
7
- *
8
- * @module comment-scanner
9
- */
1
+ // Comentário é o que `stripComments` apaga: um regex por linha confundiria
2
+ // `https://` com comentário.
10
3
 
11
4
  import { stripComments } from './comment-stripper.js';
12
5
 
13
- /** Vocabulário de processo do morph-spec, fora o id de task (abaixo). */
6
+ // A mesma grafia de `C{n}`/`F{n}` é tecla, métrica, célula ou o modelo C-quatro:
7
+ // esses vizinhos não contam.
8
+ const PLAN_ID = new RegExp(String.raw`(?<!\b(?:key|tecla|press|aperte|cell|c[eé]lula|column|coluna)\s+)(?<![\w.#$/-])[CF]\d{1,2}(?![\w.(-]|\s+(?:key|tecla|score|model|modelo|diagram|diagrama)\b)`, 'i');
9
+
14
10
  const PROCESS_CITATION = [
15
11
  /\bUC[-\s]?\d+/,
16
12
  /\bADR[-\s]?\d+/,
17
13
  /\bFR[-\s]?\d+/,
18
14
  /\bgate\s?[1-3]\b/i,
19
- /\bspec\.md\b/i,
15
+ /\bplan\.md\b/i,
16
+ /\bchecks\.json\b/i,
20
17
  /\bmockups?\b/i,
21
18
  ];
22
19
 
23
- /**
24
- * `T{n}` também é nome de parâmetro genérico. Não casa depois de `template`/
25
- * `typename`/`generic`, nem colado a letra ou dígito (a hora ISO `24T10`).
26
- */
27
- const TASK_ID = /(?<!\b(?:[Tt]emplate|[Tt]ypename|[Gg]eneric)\s+)(?<![\w"])T\d{1,3}\b(?!")/;
28
-
29
- /** Lista de tipos `<…>` e `{…}` (`Func<T1, T2>`, `cref="Func{T1,T2}"`, XML doc), de dentro para fora. */
30
- const TYPE_LIST = /<[^<>]*>|\{[^{}]*\}/g;
31
-
32
- function withoutTypeLists(text) {
33
- let previous;
34
- let current = text;
35
- do {
36
- previous = current;
37
- current = current.replace(TYPE_LIST, ' ');
38
- } while (current !== previous);
39
- return current;
40
- }
41
-
42
- /**
43
- * @param {string} commentText
44
- * @returns {boolean} o comentário usa o vocabulário de processo acima
45
- */
46
20
  export function citesProcess(commentText) {
47
- return PROCESS_CITATION.some((re) => re.test(commentText))
48
- || TASK_ID.test(withoutTypeLists(commentText));
21
+ return PROCESS_CITATION.some((re) => re.test(commentText)) || PLAN_ID.test(commentText);
49
22
  }
50
23
 
51
- /**
52
- * @param {string} content - conteúdo do arquivo
53
- * @param {string} file - path, só para escolher a sintaxe
54
- * @returns {Map<number, string>} nº da linha (1-based) → texto do comentário nela
55
- */
24
+ /** @returns {Map<number, string>} linha 1-based → texto do comentário; `file` só escolhe a sintaxe */
56
25
  export function commentLines(content, file) {
57
26
  const text = String(content || '');
58
27
  const stripped = stripComments(text, { js: !/\.cs$/i.test(file) });
@@ -66,8 +35,7 @@ export function commentLines(content, file) {
66
35
  if (first < 0) first = k;
67
36
  last = k;
68
37
  }
69
- // O espaço dentro do comentário não muda ao ser apagado; o trecho vai do
70
- // primeiro ao último caractere apagado.
38
+ // Espaço dentro do comentário não muda ao ser apagado.
71
39
  if (first >= 0) out.set(index + 1, row.slice(first, last + 1).trim());
72
40
  start += row.length + 1;
73
41
  });