@polymorphism-tech/morph-spec 9.0.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 (304) hide show
  1. package/CLAUDE.md +8 -9
  2. package/README.md +38 -36
  3. package/bin/morph-spec.js +4 -62
  4. package/bin/validate.js +1 -39
  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 +1 -1
  9. package/framework/MORPH.md +1 -1
  10. package/framework/agents.json +12 -4
  11. package/framework/commands/morph-apply.md +1 -1
  12. package/framework/commands/morph-status.md +1 -1
  13. package/framework/docs/CLI.md +6 -17
  14. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
  15. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
  16. package/framework/hooks/claude-code/core/chain.js +4 -65
  17. package/framework/hooks/claude-code/core/comment-guard.js +2 -16
  18. package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
  19. package/framework/hooks/claude-code/core/gate-guard.js +7 -67
  20. package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
  21. package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
  22. package/framework/hooks/claude-code/core/heavy-release.js +2 -13
  23. package/framework/hooks/claude-code/core/loop-detect.js +2 -31
  24. package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
  25. package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
  26. package/framework/hooks/claude-code/core/state-sync.js +11 -67
  27. package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
  28. package/framework/hooks/claude-code/core/validate-completion.js +11 -57
  29. package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
  30. package/framework/hooks/claude-code/core/worktree-context.js +10 -110
  31. package/framework/hooks/claude-code/core/worktree-create.js +6 -70
  32. package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
  33. package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
  34. package/framework/hooks/shared/activity-logger.js +9 -89
  35. package/framework/hooks/shared/agent-reap.js +2 -17
  36. package/framework/hooks/shared/build-check.js +6 -75
  37. package/framework/hooks/shared/claim-registry.js +9 -119
  38. package/framework/hooks/shared/command-tokens.js +6 -51
  39. package/framework/hooks/shared/comment-rules.js +5 -20
  40. package/framework/hooks/shared/comment-scanner.js +6 -25
  41. package/framework/hooks/shared/comment-stripper.js +11 -85
  42. package/framework/hooks/shared/e2e-check.js +0 -0
  43. package/framework/hooks/shared/errata.js +2 -15
  44. package/framework/hooks/shared/feature-locator.js +11 -95
  45. package/framework/hooks/shared/frontend-locations.js +1 -3
  46. package/framework/hooks/shared/gate-question.js +1 -7
  47. package/framework/hooks/shared/git-exclude.js +3 -20
  48. package/framework/hooks/shared/git-paths.js +8 -104
  49. package/framework/hooks/shared/graph-freshness.js +22 -166
  50. package/framework/hooks/shared/graph-refresh.js +21 -149
  51. package/framework/hooks/shared/heavy-command.js +4 -12
  52. package/framework/hooks/shared/hook-response.js +8 -83
  53. package/framework/hooks/shared/morph-cli.js +8 -52
  54. package/framework/hooks/shared/payload-utils.js +10 -132
  55. package/framework/hooks/shared/phase-utils.js +1 -42
  56. package/framework/hooks/shared/redact-secrets.js +4 -15
  57. package/framework/hooks/shared/reparse-scan.js +6 -66
  58. package/framework/hooks/shared/slice-progress.js +4 -23
  59. package/framework/hooks/shared/source-files.js +4 -17
  60. package/framework/hooks/shared/state-reader.js +13 -183
  61. package/framework/hooks/shared/state-writer.js +8 -52
  62. package/framework/hooks/shared/stdin-reader.js +1 -11
  63. package/framework/hooks/shared/telemetry-logger.js +7 -67
  64. package/framework/hooks/shared/transcript-usage.js +7 -29
  65. package/framework/hooks/shared/worktree-root.js +7 -29
  66. package/framework/rules/csharp-standards.md +1 -1
  67. package/framework/rules/frontend-standards.md +1 -0
  68. package/framework/rules/hangfire-standards.md +1 -1
  69. package/framework/rules/neon-standards.md +1 -1
  70. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  71. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  72. package/framework/skills/core/morph-apply/SKILL.md +2 -2
  73. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  74. package/framework/skills/core/morph-machine/SKILL.md +1 -1
  75. package/framework/skills/core/morph-media/SKILL.md +1 -1
  76. package/framework/skills/core/morph-plan/SKILL.md +2 -2
  77. package/framework/skills/core/morph-review/SKILL.md +1 -1
  78. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  79. package/framework/skills/core/morph-uiux/SKILL.md +29 -12
  80. package/framework/standards/STANDARDS.json +34 -12
  81. package/framework/standards/STANDARDS.meta.json +16 -2
  82. package/framework/standards/ai-agents/agent-archetypes.md +2 -2
  83. package/framework/standards/ai-agents/agent-session.md +1 -1
  84. package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
  85. package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
  86. package/framework/standards/ai-agents/guardrails.md +1 -1
  87. package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
  88. package/framework/standards/ai-agents/observability-patterns.md +1 -1
  89. package/framework/standards/ai-agents/production.md +1 -1
  90. package/framework/standards/ai-agents/prompt-sources.md +2 -2
  91. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  92. package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
  93. package/framework/standards/ai-agents/setup.md +2 -2
  94. package/framework/standards/ai-agents/sweet-spot.md +2 -2
  95. package/framework/standards/ai-agents/tool-adapters.md +0 -2
  96. package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
  97. package/framework/standards/ai-agents/workflows.md +1 -1
  98. package/framework/standards/architecture/code-comments.md +1 -1
  99. package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
  100. package/framework/standards/backend/api/validation.md +1 -1
  101. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
  102. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
  103. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
  104. package/framework/standards/data/nosql/cache/redis.md +1 -1
  105. package/framework/standards/data/nosql/cosmos-db.md +3 -3
  106. package/framework/standards/frontend/admin-app/forms.md +1 -1
  107. package/framework/standards/frontend/design-system/art-direction.md +88 -0
  108. package/framework/standards/frontend/design-system/premium-finish.md +77 -2
  109. package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
  110. package/framework/templates/CLAUDE.md.core.template +3 -3
  111. package/framework/templates/REGISTRY.json +9 -9
  112. package/framework/templates/docs/ui-mockups.md +4 -0
  113. package/package.json +1 -1
  114. package/src/commands/context/context-refresh.js +0 -16
  115. package/src/commands/dashboard/dashboard.js +2 -8
  116. package/src/commands/e2e/e2e.js +17 -104
  117. package/src/commands/graph/affected.js +0 -14
  118. package/src/commands/graph/build.js +11 -50
  119. package/src/commands/graph/explain.js +0 -16
  120. package/src/commands/graph/status.js +1 -12
  121. package/src/commands/mcp/mcp-setup.js +4 -62
  122. package/src/commands/observability/cost.js +5 -57
  123. package/src/commands/observability/telemetry.js +1 -14
  124. package/src/commands/project/branch.js +4 -45
  125. package/src/commands/project/claim.js +4 -48
  126. package/src/commands/project/doctor.js +48 -302
  127. package/src/commands/project/env.js +1 -27
  128. package/src/commands/project/fleet.js +3 -32
  129. package/src/commands/project/init.js +4 -68
  130. package/src/commands/project/install-plugin-cmd.js +0 -1
  131. package/src/commands/project/standards.js +1 -62
  132. package/src/commands/project/status.js +0 -53
  133. package/src/commands/project/update.js +31 -239
  134. package/src/commands/project/worktree.js +108 -882
  135. package/src/commands/scaffold/scaffold.js +0 -25
  136. package/src/commands/state/advance.js +14 -85
  137. package/src/commands/state/approve.js +14 -96
  138. package/src/commands/state/archive.js +12 -64
  139. package/src/commands/state/auto-approval.js +2 -5
  140. package/src/commands/state/create.js +22 -109
  141. package/src/commands/state/delete.js +11 -63
  142. package/src/commands/state/finish.js +98 -475
  143. package/src/commands/state/gate-check.js +22 -132
  144. package/src/commands/state/gate-decision.js +3 -39
  145. package/src/commands/state/handoff.js +3 -30
  146. package/src/commands/state/list.js +14 -84
  147. package/src/commands/state/report-check.js +8 -58
  148. package/src/commands/state/retype.js +4 -26
  149. package/src/commands/validation/index.js +0 -3
  150. package/src/commands/validation/mutate.js +2 -5
  151. package/src/commands/validation/validate-feature.js +5 -94
  152. package/src/commands/validation/verify.js +15 -98
  153. package/src/core/paths/output-schema.js +2 -101
  154. package/src/core/state/feature-identity.js +1 -25
  155. package/src/core/state/gate-order.js +0 -27
  156. package/src/core/state/lifecycle-audit.js +8 -100
  157. package/src/core/state/state-manager.js +22 -304
  158. package/src/core/templates/template-registry.js +2 -97
  159. package/src/core/templates/template-renderer.js +13 -93
  160. package/src/core/templates/template-seed.js +0 -1
  161. package/src/core/work-types.js +1 -36
  162. package/src/dashboard/app/App.jsx +1 -8
  163. package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
  164. package/src/dashboard/app/components/CommandPalette.jsx +0 -7
  165. package/src/dashboard/app/components/DetailPanel.jsx +1 -5
  166. package/src/dashboard/app/components/EntityLink.jsx +0 -12
  167. package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
  168. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
  169. package/src/dashboard/app/components/Markdown.jsx +0 -9
  170. package/src/dashboard/app/components/PhaseRail.jsx +0 -11
  171. package/src/dashboard/app/components/SearchFilter.jsx +0 -4
  172. package/src/dashboard/app/components/Sidebar.jsx +0 -5
  173. package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
  174. package/src/dashboard/app/hooks/usePref.js +1 -5
  175. package/src/dashboard/app/lib/format.js +0 -2
  176. package/src/dashboard/app/lib/phases.js +1 -5
  177. package/src/dashboard/app/lib/project.js +0 -12
  178. package/src/dashboard/app/pages/AgentMap.jsx +1 -8
  179. package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
  180. package/src/dashboard/app/pages/Features.jsx +1 -8
  181. package/src/dashboard/app/pages/Guide.jsx +0 -1
  182. package/src/dashboard/app/pages/HookInspector.jsx +2 -23
  183. package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
  184. package/src/dashboard/app/pages/Portfolio.jsx +0 -1
  185. package/src/dashboard/app/pages/RulesMap.jsx +1 -16
  186. package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
  187. package/src/dashboard/app/pages/Standards.jsx +2 -17
  188. package/src/dashboard/scanner/activity-scanner.js +10 -69
  189. package/src/dashboard/scanner/agents-scanner.js +1 -23
  190. package/src/dashboard/scanner/docs-scanner.js +0 -6
  191. package/src/dashboard/scanner/feature-status.js +2 -5
  192. package/src/dashboard/scanner/features-scanner.js +10 -66
  193. package/src/dashboard/scanner/hooks-scanner.js +5 -28
  194. package/src/dashboard/scanner/index.js +0 -4
  195. package/src/dashboard/scanner/portfolio.js +5 -29
  196. package/src/dashboard/scanner/project-api.js +8 -54
  197. package/src/dashboard/scanner/rules-scanner.js +1 -2
  198. package/src/dashboard/scanner/skills-scanner.js +2 -12
  199. package/src/dashboard/scanner/standards-scanner.js +2 -6
  200. package/src/dashboard/vite-plugin-morph-data.js +5 -19
  201. package/src/lib/ai/ai-checks.js +7 -52
  202. package/src/lib/ai/ai-project-scan.js +14 -108
  203. package/src/lib/ai/code-audit.js +7 -63
  204. package/src/lib/ai/pin-drift.js +8 -51
  205. package/src/lib/ai/registry-audit.js +11 -90
  206. package/src/lib/ci/ci-verify.js +4 -19
  207. package/src/lib/ci/github-ci.js +9 -37
  208. package/src/lib/ci/workflow-scaffold.js +2 -10
  209. package/src/lib/detectors/claude-config-detector.js +13 -74
  210. package/src/lib/detectors/index.js +4 -50
  211. package/src/lib/e2e/compose-env.js +5 -66
  212. package/src/lib/e2e/stack-teardown.js +6 -40
  213. package/src/lib/generators/architecture-scanner.js +18 -156
  214. package/src/lib/generators/handoff-generator.js +9 -53
  215. package/src/lib/generators/scaffold-generator.js +5 -101
  216. package/src/lib/generators/settings-generator.js +4 -37
  217. package/src/lib/git/branch-classifier.js +11 -84
  218. package/src/lib/git/default-branch.js +14 -169
  219. package/src/lib/git/evidence-staleness.js +1 -0
  220. package/src/lib/git/gh.js +2 -17
  221. package/src/lib/git/pending-prs.js +6 -37
  222. package/src/lib/git/pr-files.js +3 -28
  223. package/src/lib/git/pr-index.js +3 -34
  224. package/src/lib/git/tree-fingerprint.js +3 -15
  225. package/src/lib/git/tree-state.js +6 -33
  226. package/src/lib/graph/god-nodes.js +2 -22
  227. package/src/lib/graph/graph-health.js +3 -24
  228. package/src/lib/graph/graph-loader.js +5 -56
  229. package/src/lib/graph/graphify-runtime.js +7 -37
  230. package/src/lib/graph/graphifyignore.js +8 -74
  231. package/src/lib/graph/sanitize.js +16 -136
  232. package/src/lib/heavy/cleanup.js +1 -1
  233. package/src/lib/installers/effects.js +8 -93
  234. package/src/lib/installers/mcp-installer.js +9 -169
  235. package/src/lib/installers/memory-installer.js +4 -27
  236. package/src/lib/installers/templates-reconcile.js +5 -60
  237. package/src/lib/mutation/dotnet-introspection.js +20 -106
  238. package/src/lib/mutation/subset.js +1 -4
  239. package/src/lib/plan/artifact-vocabulary.js +5 -25
  240. package/src/lib/plan/checks.js +5 -31
  241. package/src/lib/plan/plan-parser.js +1 -6
  242. package/src/lib/projects/registry.js +7 -44
  243. package/src/lib/review-reports.js +5 -23
  244. package/src/lib/risk-detector.js +39 -173
  245. package/src/lib/specs/adr-index.js +6 -34
  246. package/src/lib/specs/frontmatter.js +2 -15
  247. package/src/lib/specs/gfm-table.js +1 -7
  248. package/src/lib/specs/revocations.js +1 -14
  249. package/src/lib/specs/screen-checklist.js +47 -124
  250. package/src/lib/stack/dotnet-build-ignores.js +2 -6
  251. package/src/lib/stack/stack-profile.js +1 -28
  252. package/src/lib/stack/stack-scan.js +1 -8
  253. package/src/lib/stack-filter.js +4 -27
  254. package/src/lib/standards/registry.js +1 -7
  255. package/src/lib/standards/sections.js +2 -13
  256. package/src/lib/standards/verified-against.js +19 -191
  257. package/src/lib/tasks/eval-node.js +21 -169
  258. package/src/lib/tasks/linux-runner.js +18 -142
  259. package/src/lib/tasks/test-nodes.js +43 -240
  260. package/src/lib/tasks/test-runner.js +43 -341
  261. package/src/lib/telemetry/metrics.js +7 -36
  262. package/src/lib/telemetry/telemetry-reader.js +13 -125
  263. package/src/lib/telemetry/verify-event.js +2 -30
  264. package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
  265. package/src/lib/validators/code-budget/index.js +0 -7
  266. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
  267. package/src/lib/validators/contracts/index.js +0 -6
  268. package/src/lib/validators/nextjs/index.js +0 -5
  269. package/src/lib/validators/nextjs/next-component-validator.js +4 -81
  270. package/src/lib/validators/packages/index.js +0 -6
  271. package/src/lib/validators/packages/package-validator.js +6 -56
  272. package/src/lib/validators/reuse/index.js +0 -8
  273. package/src/lib/validators/reuse/reuse-validator.js +11 -101
  274. package/src/lib/validators/reuse/symbol-scanner.js +6 -62
  275. package/src/lib/validators/traceability-check.js +5 -63
  276. package/src/lib/validators/validation-runner.js +20 -125
  277. package/src/lib/validators/verify-runner.js +52 -358
  278. package/src/lib/work-type-detector.js +5 -67
  279. package/src/lib/worktree/docker-audit.js +17 -136
  280. package/src/lib/worktree/env-allocator.js +10 -98
  281. package/src/lib/worktree/feature-truth.js +12 -115
  282. package/src/lib/worktree/finish-backup.js +32 -242
  283. package/src/lib/worktree/fleet-scanner.js +13 -95
  284. package/src/lib/worktree/ignored-triage.js +15 -91
  285. package/src/lib/worktree/vscode-settings.js +2 -3
  286. package/src/lib/worktree/worktree-audit.js +21 -213
  287. package/src/lib/worktree/worktree-include.js +3 -5
  288. package/src/scripts/global-install.js +3 -9
  289. package/src/scripts/install-plugin.js +1 -13
  290. package/src/scripts/setup-infra.js +17 -147
  291. package/src/utils/agents-installer.js +28 -275
  292. package/src/utils/banner.js +4 -25
  293. package/src/utils/claude-md-injector.js +1 -37
  294. package/src/utils/claude-settings-manager.js +1 -52
  295. package/src/utils/file-copier.js +11 -95
  296. package/src/utils/hooks-installer.js +21 -298
  297. package/src/utils/skills-installer.js +4 -101
  298. package/src/utils/version-checker.js +4 -83
  299. package/framework/commands/morph-proposal.md +0 -9
  300. package/src/commands/utils/index.js +0 -3
  301. package/src/lib/detectors/design-system-detector.js +0 -188
  302. package/src/lib/generators/design-system-generator.js +0 -508
  303. package/src/lib/specs/old-format.js +0 -68
  304. package/src/utils/agent-id-mapper.js +0 -11
@@ -1,67 +1,13 @@
1
- /**
2
- * templates-reconcile.js — reconcile a pre-existing `.morph/framework/templates/`
3
- * copy against the project's prior recorded morph-spec version.
4
- *
5
- * Shared by BOTH install paths that can encounter this directory:
6
- * - `morph-spec update` (src/commands/project/update.js) — always knows the
7
- * project's prior version (`checkProjectOutdated().current`, read from
8
- * `.morph/.morphversion` BEFORE this run overwrites it).
9
- * - `morph-spec init`/`setup-infra` (src/scripts/setup-infra.js) — the SAME
10
- * source (`getProjectMorphVersion()`, the function `checkProjectOutdated`
11
- * itself calls), read at the same point in the run (before its own write
12
- * of `.morph/.morphversion`), when it's available — e.g. a repair run
13
- * over an already-initialized project. Falls back to a plain warning when
14
- * it isn't (a project with no recorded version has nothing here for this
15
- * function to reconcile against safely).
16
- */
17
-
18
1
  import { existsSync } from 'fs';
19
2
  import { compareVersions } from '../../utils/version-checker.js';
20
3
  import { realEffects } from './effects.js';
21
4
 
22
- /**
23
- * Last CLI version whose `update`/`setup-infra` still wrote
24
- * `.morph/framework/templates/` unconditionally, before Task 2 of the R3
25
- * perf plan removed that copy. Used as the decision boundary in
26
- * `reconcileLegacyTemplatesCopy` below — see its doc comment for why a
27
- * version check is used instead of diffing file content.
28
- */
5
+ // Last CLI version whose install still copied `.morph/framework/templates/` unconditionally.
29
6
  export const LAST_VERSION_THAT_COPIED_TEMPLATES = '8.17.0';
30
7
 
31
- /**
32
- * Reconcile a pre-existing `.morph/framework/templates/` copy now that
33
- * neither install path unconditionally writes it anymore (Task 2, R3 perf
34
- * plan — resolution falls back to the npm package's own framework/templates/,
35
- * see template-registry.js / architecture-scanner.js's layered resolver).
36
- *
37
- * Decided from the project's PRIOR recorded CLI version (before THIS run
38
- * writes a new one), not by diffing content against the package. A content
39
- * diff can't tell "stale copy from an older CLI version" apart from
40
- * "deliberate user override" — and staleness is the dominant case for
41
- * anyone who actually runs `update`/`setup-infra` again, since template
42
- * content does change between releases. The version boundary is unambiguous
43
- * instead:
44
- *
45
- * - No local copy → nothing to do.
46
- * - Prior version is unrecorded, OR <= LAST_VERSION_THAT_COPIED_TEMPLATES
47
- * → the copy could ONLY have been put there by the OLD installer:
48
- * `.morph/framework/**` is Edit-denied to the agent (MORPH_DENY in
49
- * hooks-installer.js — "installer-managed, immutable at runtime"),
50
- * the directory is gitignored, and every install/update before this fix
51
- * unconditionally wiped + recopied it. There is no path by which a
52
- * project on an old version could have a genuine override sitting
53
- * here. Safe to delete unconditionally — it is dead weight, not data.
54
- * - Prior version > LAST_VERSION_THAT_COPIED_TEMPLATES → this install path
55
- * has never written this directory for this project. Anything found
56
- * here was placed deliberately (by the user or other tooling) — this is
57
- * the forward-looking override use case the layered resolver exists
58
- * for. Preserve untouched, unconditionally, with no diffing.
59
- *
60
- * @param {string} templatesDest - .morph/framework/templates (may not exist)
61
- * @param {string|null} priorProjectVersion - project's recorded morph-spec
62
- * version BEFORE this run (e.g. `checkProjectOutdated().current`)
63
- * @returns {'absent'|'removed-legacy'|'preserved-override'}
64
- */
8
+ // Decided by the PRIOR recorded version, not by diffing: a diff can't tell a stale copy from an override.
9
+ // Unrecorded or <= the boundary → only the old installer could have written it, delete.
10
+ // Newer → placed deliberately, preserve.
65
11
  export function reconcileLegacyTemplatesCopy(templatesDest, priorProjectVersion, fx = realEffects()) {
66
12
  if (!existsSync(templatesDest)) return 'absent';
67
13
 
@@ -74,8 +20,7 @@ export function reconcileLegacyTemplatesCopy(templatesDest, priorProjectVersion,
74
20
  fx.remove(templatesDest);
75
21
  return 'removed-legacy';
76
22
  } catch {
77
- // Couldn't remove — don't risk a half-broken state; leave it and let
78
- // the layered resolver's override-first check pick it up as before.
23
+ // Couldn't remove: leave it; the resolver's override-first check picks it up.
79
24
  return 'preserved-override';
80
25
  }
81
26
  }
@@ -1,10 +1,3 @@
1
- /**
2
- * .NET introspection the targeted mutation (`morph-spec mutate`) needs: which
3
- * project owns a file, which test mode the SDK picks, and which types a C# file
4
- * declares — read FAIL-SAFE (see csharpClasses).
5
- *
6
- * Pure: reads files, never spawns a process.
7
- */
8
1
  import { existsSync, readFileSync, readdirSync } from 'fs';
9
2
  import { dirname, join, resolve } from 'path';
10
3
 
@@ -13,54 +6,21 @@ import { jsoncToJson } from '../../../framework/hooks/shared/comment-stripper.js
13
6
 
14
7
  const MTP_RUNNER = 'Microsoft.Testing.Platform';
15
8
  const TYPE_HEADER = /\b(?:class|struct|record|interface)\b/;
16
- /** A C# identifier's first character: a letter, an Nl letter (`Ⅱ`), `_`, `@` (verbatim) or `\` (a unicode escape). */
9
+ // `@` = verbatim identifier, `\` = unicode escape.
17
10
  const NAME_START = '[\\p{L}\\p{Nl}_@\\\\]';
18
- /**
19
- * TRIPWIRE: every type-declaration keyword wherever it sits — applied to the RAW
20
- * text (nothing blanked) and to the blanked code alike, with NO context rule:
21
- * round 5 showed that any exclusion decided by what precedes the keyword (the
22
- * `:` of a generic constraint, a `where T :` shape) can be forged by a comment in
23
- * the raw text or by string content a cut exposes in the code. `record class`/
24
- * `record struct` count once. What follows the keyword, after optional whitespace,
25
- * is a name start (NAME_START), a comment or preprocessor line standing between
26
- * the keyword and its name (`class/**\/BTests` is legal), OR a quote — a quote is
27
- * what the RAW text shows where the blanked code shows the spaces of a string the
28
- * keyword is followed by (`"; class " : " Fake"` exposed as code): every keyword
29
- * the code counts is a keyword the raw text counts, by offset.
30
- * A constraint keyword is therefore counted too whenever a name-shaped token
31
- * follows it (`where T : class where U : …`, `: class // note`): it is not named,
32
- * so the file is unscannable. `class,` / `class\n{` / `class?` are not counted at all.
33
- */
11
+ // TRIPWIRE with NO context rule: any exclusion based on what precedes the keyword can be
12
+ // forged by a comment or exposed string. The quote in the lookahead makes every keyword
13
+ // the blanked code counts also counted in the raw text, at the same offset.
34
14
  const TYPE_KEYWORD = new RegExp(`\\b(?:record\\s+(?:class|struct)|class|record|struct|interface)\\b(?=\\s*(?:${NAME_START}|["']|\\/[/*]|#))`, 'gu');
35
- /** A type declaration the scanner can NAME: after `; { } ]`, a newline or the file start; attributes; modifiers; a NAME_START name. */
36
15
  const TYPE_DECL = new RegExp(`(?:^|[;{}\\]\\n])[ \\t]*(?:\\[[^\\]\\n]*\\][ \\t]*)*(?:(?:public|internal|private|protected|sealed|static|partial|abstract|unsafe|file|new|readonly|ref)\\s+)*(record\\s+(?:class|struct)|class|record|struct|interface)\\s+(${NAME_START}[^\\s:<({;,=]*)`, 'gu');
37
16
  const IDENT = '[\\p{L}_][\\p{L}\\p{N}_]*';
38
17
  const QUALIFIER = `(?:${IDENT}\\s*(?:\\.|::)\\s*)*`;
39
- /** An attribute's optional arguments: one nested level of parentheses, no `]`. */
40
18
  const ATTRIBUTE_ARGS = '(?:\\((?:[^()\\]]|\\([^()\\]]*\\))*\\))?';
41
- /** Any other attribute of the same section: a name, optionally with arguments. */
42
19
  const OTHER_ATTRIBUTE = `${QUALIFIER}${IDENT}\\s*${ATTRIBUTE_ARGS}`;
43
20
  const TEST_ATTRIBUTE_NAME = '(?:Fact|Theory|Test|TestMethod|DataTestMethod|TestCase|TestCaseSource)(?:Attribute)?';
44
- /**
45
- * A test attribute, read on the BLANKED code of a test-project file (hasTestAttribute):
46
- * a WHOLE name — `Fact`, `Theory`, `Test`, `TestMethod`, `DataTestMethod`, `TestCase`,
47
- * `TestCaseSource`, optionally `…Attribute` and qualified (`Xunit.Fact`) — inside an
48
- * attribute section that CLOSES as one: `[` where a declaration may start (line start,
49
- * after `;` `{` `}` `]` or a `*\/`), an optional target, other attributes before it
50
- * separated by `,`, the test name with its optional arguments, any further attributes,
51
- * then the `]` — followed by a declaration start (`[`, a name, a comment, a directive)
52
- * or the end of the input.
53
- *
54
- * A false POSITIVE is the unsafe direction here: a helper taken for a test file
55
- * narrows the subset to its own name, and the tests that USE the helper never run. The final review of branch B found
56
- * exactly that with the old prefix match (`, TestContext` / `, Factory` / `, TheoryData`
57
- * after any `,`), and its re-review (N1) with a test name merely FOLLOWED by `(` or `,`:
58
- * a C# 12 collection laid out with `[` alone on its line (`[\n Env.Dev,\n Env.Test,\n];`,
59
- * a row `[Env.Test, Env.Dev],`, `[\n Test("a"),\n];`). Requiring the section to close
60
- * rejects all three; what still matches is stated in csharpClasses. A false NEGATIVE
61
- * only leaves a test file out of a selection (a custom attribute such as `[SkippableFact]`, a test
62
- * attribute after an argument holding `]`).
63
- */
21
+ // A false POSITIVE is the unsafe side (a helper taken for a test file hides the tests that
22
+ // use it), so the whole name must sit in an attribute section that CLOSES: a C# 12
23
+ // collection such as `[\n Env.Test,\n];` must not match. Residual cases: csharpClasses.
64
24
  export const CS_TEST_ATTRIBUTE = new RegExp(
65
25
  '(?:^|[;{}\\]]|\\*\\/)[ \\t]*\\['
66
26
  + `\\s*(?:${IDENT}\\s*:(?!:)\\s*)?`
@@ -71,22 +31,12 @@ export const CS_TEST_ATTRIBUTE = new RegExp(
71
31
  'mu',
72
32
  );
73
33
 
74
- /**
75
- * File text with a leading UTF-8 BOM stripped (VS and `charset=utf-8-bom` write
76
- * one) and every C# new-line character mapped 1:1 to `\n` — CR, NEL (U+0085),
77
- * LS (U+2028), PS (U+2029): JS `\s` excludes NEL, so `class<NEL>Name` was
78
- * invisible to both regexes (compiled and discovered by xunit); a CR-only file had
79
- * no line ends at all. U+001A (Ctrl-Z) is mapped 1:1 to a space: Roslyn reads it
80
- * as whitespace and JS `\s` does not, so `class<SUB>Name` hid a declaration the
81
- * same way (B3 carry-over: compiled, green while skipping the class). Length is kept (CRLF becomes `\n\n`). null when unreadable.
82
- */
34
+ // Roslyn reads CR/NEL/LS/PS as new-lines and U+001A as whitespace; JS `\s` misses NEL
35
+ // and U+001A, which hid `class<NEL>Name`. Mapped 1:1 so offsets stay aligned with the code.
83
36
  const readText = (path) => {
84
37
  try { return readFileSync(path, 'utf8').replace(/\x1A/g, ' ').replace(/^\uFEFF/, '').replace(/[\r\u0085\u2028\u2029]/g, '\n'); } catch { return null; }
85
38
  };
86
39
 
87
- /** Which test framework a csproj references, by package (or MSBuild SDK) name. */
88
-
89
- /** Nearest file matching `projectName` walking up from a file's directory, inside the root; null when none. */
90
40
  function nearestProjectFile(fileAbs, rootAbs, projectName) {
91
41
  for (let dir = dirname(fileAbs); isInside(dir, rootAbs); dir = dirname(dir)) {
92
42
  let names = [];
@@ -97,32 +47,20 @@ function nearestProjectFile(fileAbs, rootAbs, projectName) {
97
47
  return null;
98
48
  }
99
49
 
100
- /**
101
- * The project — `.csproj`, `.fsproj` or `.vbproj` — nearest to ANY file walking
102
- * up, or null inside the root. Asked about the .NET sources the C# scan cannot
103
- * read (a bUnit `.razor`, an F# `.fs`).
104
- */
105
50
  export function nearestProjectOf(fileAbs, rootAbs) {
106
51
  return nearestProjectFile(fileAbs, rootAbs, /\.(?:cs|fs|vb)proj$/i);
107
52
  }
108
53
 
109
- /** .NET sources the C# scan cannot read: Razor (`.razor`, `.cshtml`), F# (`.fs`, `.fsi`, `.fsx`), VB (`.vb`). */
110
54
  const NON_CSHARP_SOURCE = /\.(?:razor|cshtml|fs|fsi|fsx|vb)$/i;
111
55
 
112
- /** A .NET source other than C#: no type scan, hence no class selector, for it. */
56
+ /** No type scan, hence no class selector, for these. */
113
57
  export const isNonCSharpSource = (path) => NON_CSHARP_SOURCE.test(String(path));
114
58
 
115
- /** Any .NET source a project compiles — C# or one of the others. */
116
59
  export const isDotnetSource = (path) => /\.cs$/i.test(String(path)) || isNonCSharpSource(path);
117
60
 
118
61
  /**
119
- * `.NET` test mode for a cwd: nearest `global.json` walking UP (as the SDK
120
- * resolves it — from the CWD, never from the `.sln` argument), read as JSONC.
121
- *
122
- * @param {string} absCwd
123
- * @returns {'mtp'|'vstest'|null} `mtp` when `test.runner` is Microsoft.Testing.Platform;
124
- * `vstest` when readable without it (or absent: the SDK default); null when a
125
- * global.json exists but cannot be read — never a guess.
62
+ * Nearest global.json walking up from the CWD, as the SDK resolves it (never from the
63
+ * `.sln` argument). null when one exists but cannot be read — never a guess.
126
64
  */
127
65
  export function dotnetTestMode(absCwd) {
128
66
  for (let dir = resolve(absCwd); ; dir = dirname(dir)) {
@@ -141,7 +79,6 @@ export function dotnetTestMode(absCwd) {
141
79
 
142
80
  const lineEnd = (text, i) => { const e = text.indexOf('\n', i); return e < 0 ? text.length : e; };
143
81
 
144
- /** End (exclusive) of a regular string or char literal opened at `start`; -1 if the line ends first. */
145
82
  function closeOnLine(text, start, quote) {
146
83
  for (let j = start + 1; j < text.length && text[j] !== '\n'; j++) {
147
84
  if (text[j] === '\\') j++;
@@ -150,11 +87,8 @@ function closeOnLine(text, start, quote) {
150
87
  return -1;
151
88
  }
152
89
 
153
- /**
154
- * Does an interpolated literal's content end inside a hole? A `"` inside a hole
155
- * (`$"{d["k"]}"`) ends the literal as the scanner reads it, so what it took for
156
- * the literal stops with the hole still open. `{{`/`}}` outside a hole are escapes.
157
- */
90
+ // A `"` inside a hole (`$"{d["k"]}"`) ends the literal as the scanner reads it,
91
+ // leaving the hole open. `{{`/`}}` outside a hole are escapes.
158
92
  function holeLeftOpen(content) {
159
93
  let depth = 0;
160
94
  for (let k = 0; k < content.length; k++) {
@@ -167,26 +101,9 @@ function holeLeftOpen(content) {
167
101
  }
168
102
 
169
103
  /**
170
- * C# source with every comment, preprocessor line and string/char literal
171
- * blanked — newlines and length kept — or null when the file cannot be read this way:
172
- * - a literal or comment never ends;
173
- * - an interpolated `$"…"`/`$@"…"`/`@$"…"` literal is cut by a quote inside a hole
174
- * (its content ends with the hole open). Blanking the part before the cut would
175
- * leave the rest of the hole as "code", with braces that can cancel out.
176
- *
177
- * Raw strings (`"""…"""`, 3+ quotes, with or without `$`/`$$`) end at the same run
178
- * of quotes; a quote inside a raw interpolation hole is NOT checked. Verbatim `@"…"`
179
- * ends at a lone `"`. Regular and char literals cannot cross a line.
180
- *
181
- * This is a heuristic lexer and it CAN misread (known: `holeLeftOpen` counts the
182
- * braces of a char literal such as `'}'`, so `$"{(c == '}' ? "a" : "/*")}"` is cut
183
- * at the inner quote and `"/*"` becomes code). Nothing relies on the
184
- * blanking being right: csharpClasses compares the declarations it names against
185
- * the type keywords of the RAW text, and any declaration a misread hides makes the
186
- * file unscannable.
187
- *
188
- * @param {string} text
189
- * @returns {string|null}
104
+ * Comments, preprocessor lines and literals blanked, length and newlines kept; null when
105
+ * a literal never ends or an interpolation hole is cut. A heuristic lexer that CAN misread:
106
+ * csharpClasses never trusts it alone (it checks against the raw text).
190
107
  */
191
108
  export function blankCSharpLiterals(text) {
192
109
  const out = text.split('');
@@ -232,7 +149,6 @@ export function blankCSharpLiterals(text) {
232
149
  return out.join('');
233
150
  }
234
151
 
235
- /** Type-keyword tokens in a text (raw or blanked) — every one; no context rule (see TYPE_KEYWORD). */
236
152
  const typeKeywordCount = (text) => [...text.matchAll(TYPE_KEYWORD)].length;
237
153
 
238
154
  /**
@@ -285,7 +201,7 @@ const typeKeywordCount = (text) => [...text.matchAll(TYPE_KEYWORD)].length;
285
201
  * or a custom one (`[SkippableFact]`) is not seen: the file counts as a helper and
286
202
  * it is left out of a selection;
287
203
  * - `hasTestAttribute` is still a heuristic, and ITS false positive is the unsafe
288
- * side. Since the re-review (N1) the section must close; what still matches in a
204
+ * side. The section must close; what still matches in a
289
205
  * helper — probed — is a LIST PATTERN opening a line (or after `;` `{` `}` `]`)
290
206
  * whose element is named exactly `Test`/`Fact`/… (qualified or not) and whose `]`
291
207
  * is followed by a combinator: `[Env.Test] or [Env.Dev] => true`. Such a helper is
@@ -317,10 +233,8 @@ export function csharpClasses(csFileAbs) {
317
233
  // length, so the raw text at the same offsets shows the directive.
318
234
  const split = decls.find((d) => /(^|\n)[ \t]*#/.test(text.slice(d.at - d.gap, d.at)));
319
235
  if (split) return unscannable(`a preprocessor directive stands between a type keyword and the name "${split.name}"`);
320
- // THE NET (R2, tightened in round 5): the raw text is the lexer-independent
321
- // truth about how many type keywords the file carries. Compare it — and the
322
- // blanked code — against what was actually named (see the JSDoc for why
323
- // equality of counts is equality of sets).
236
+ // The raw text is the lexer-independent truth about how many type keywords the file
237
+ // carries (the JSDoc explains why equal counts mean equal sets).
324
238
  const rawKeywords = typeKeywordCount(text);
325
239
  if (rawKeywords !== decls.length) {
326
240
  return unscannable(`${rawKeywords} type keyword(s) in the file but ${decls.length} declaration(s) recognised`);
@@ -51,10 +51,7 @@ export function testFilesCiting({ root, testProjectDirs, types, exclude = [] })
51
51
  return found.sort();
52
52
  }
53
53
 
54
- /**
55
- * The tests to run FIRST, chosen by the tool: files the feature changed, and files naming a
56
- * type the mutated files declare. A mutant the subset does not kill goes to the whole suite.
57
- */
54
+ /** Chosen by the tool, never the verifier: test files the feature changed or that name a mutated type. */
58
55
  export function dotnetSubset({ root, testProjectDirs, mutatedFiles, changed = [] }) {
59
56
  const changedSet = new Set(changed.map(posix));
60
57
  const mention = mentionOf(mutatedTypesOf(root, mutatedFiles));
@@ -1,33 +1,18 @@
1
1
  /** The inline justification that lets a criterion talk about an artifact on purpose. */
2
2
  export const ARTIFACT_MARKER_RE = /por\s+que\s+artefato\s*:/i;
3
3
 
4
- /**
5
- * Case-insensitive spelling of `word` inside a regex that must stay
6
- * case-SENSITIVE elsewhere — Node 18 has no inline `(?i:…)` modifier.
7
- */
4
+ // Node 18 has no inline `(?i:…)` modifier, and the rest of the regex must stay case-sensitive.
8
5
  const anyCase = (word) => [...word].map((c) => {
9
6
  const lo = c.toLowerCase();
10
7
  const up = c.toUpperCase();
11
8
  return lo === up ? c : `[${lo}${up}]`;
12
9
  }).join('');
13
10
 
14
- /**
15
- * What follows "arquivo em" in a behavioural export criterion ("exporta o
16
- * arquivo em CSV", "salva o arquivo em formato aberto"): an uppercase file
17
- * format acronym or the word `formato`. Case-sensitive on purpose — `src`,
18
- * `docs` or `lib` after "arquivo em" are places, and places are artifacts.
19
- */
11
+ // Case-sensitive on purpose: `src`, `docs` or `lib` after "arquivo em" are places, and places are artifacts.
20
12
  const FORMAT_TOKEN = String.raw`\s+(?:[A-Z]{2,5}(?![\p{L}\d_])|${anyCase('formato')}(?![\p{L}]))`;
21
13
 
22
- /**
23
- * Vocabulary of artifact-shaped phrasing. `term` is the label surfaced in
24
- * warnings and cited by the `morph-plan` skill prose — keep it stable, a
25
- * parity test imports it verbatim.
26
- *
27
- * Unicode lookarounds, never `\b`: in JS, `\b` does not treat accented
28
- * letters (é, á, ó…) as word characters, so it would false-boundary inside
29
- * "não" or "binário".
30
- */
14
+ // `term` is cited verbatim by the `morph-plan` skill (a parity test imports it): keep it stable.
15
+ // Unicode lookarounds, never `\b`: JS `\b` false-boundaries inside "não" or "binário".
31
16
  export const ARTIFACT_VOCABULARY = [
32
17
  { term: 'grep', re: /(?<![\p{L}\d_])grep(?![\p{L}\d_])/iu },
33
18
  { term: 'contém a string', re: /cont[ée]m\s+as?\s+strings?/iu },
@@ -44,12 +29,7 @@ export const ARTIFACT_VOCABULARY = [
44
29
  { term: 'glob **/', re: /\*\*\/|\/\*\*/ },
45
30
  ];
46
31
 
47
- /**
48
- * Vocabulary terms matched in `text`, in vocabulary order, each at most once.
49
- *
50
- * @param {string} text
51
- * @returns {string[]}
52
- */
32
+ /** Each term at most once, in vocabulary order. */
53
33
  export function findArtifactTerms(text) {
54
34
  const s = String(text ?? '');
55
35
  const found = [];
@@ -11,17 +11,12 @@ export const MAX_SLICE_FILES = 8;
11
11
  export const MAX_SLICES = 12;
12
12
  export const MAX_PARALLEL = 4;
13
13
 
14
- /** sha256 of the plan without its errata: appending a correction never invalidates checks.json. */
14
+ /** sha256 of the plan without its errata: appending a correction never invalidates the extracted checks. */
15
15
  export function planHash(text) {
16
16
  return createHash('sha256').update(frozenBody(text), 'utf8').digest('hex');
17
17
  }
18
18
 
19
- /**
20
- * Persona ids the project can dispatch: the installed agents.json, else the package's.
21
- *
22
- * @param {string} cwd
23
- * @returns {string[]|null} null when no agents.json is readable — personas then go unchecked
24
- */
19
+ /** The installed agents.json, else the package's; null when unreadable (personas then go unchecked). */
25
20
  export function knownPersonas(cwd) {
26
21
  const agents = readAgentsJson(cwd)?.agents;
27
22
  return agents && typeof agents === 'object' ? Object.keys(agents) : null;
@@ -54,14 +49,8 @@ export function dependencyCycle(slices) {
54
49
 
55
50
  const fileKey = (f) => f.replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
56
51
 
57
- /**
58
- * Waves of slices that may run at the same time: a slice joins a wave only
59
- * after every slice it depends on ran in an earlier one, and never with a
60
- * slice that touches one of its files. At most MAX_PARALLEL per wave.
61
- *
62
- * @param {{ id: string, depends?: string[], files?: string[] }[]} slices
63
- * @returns {string[][]} empty when the dependencies have a cycle
64
- */
52
+ // A slice joins a wave only after its dependencies ran, never with one touching its files, and at most
53
+ // MAX_PARALLEL per wave. Empty on a dependency cycle.
65
54
  export function parallelGroups(slices) {
66
55
  if (dependencyCycle(slices)) return [];
67
56
  const ids = new Set(slices.map((s) => s.id));
@@ -94,13 +83,6 @@ export function parallelGroups(slices) {
94
83
  return groups;
95
84
  }
96
85
 
97
- /**
98
- * Judge a parsed plan.
99
- *
100
- * @param {ReturnType<import('./plan-parser.js').parsePlan>} plan
101
- * @param {{ personas?: string[]|null, surfaces?: string[]|null }} [known]
102
- * @returns {{ errors: string[], warnings: string[] }}
103
- */
104
86
  export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
105
87
  const errors = [];
106
88
  const warnings = [];
@@ -184,7 +166,6 @@ export function validatePlan(plan, { personas = null, surfaces = null } = {}) {
184
166
  return { errors, warnings };
185
167
  }
186
168
 
187
- /** The checks.json document for a parsed plan. */
188
169
  export function buildChecks(feature, text, plan) {
189
170
  return {
190
171
  version: CHECKS_VERSION,
@@ -202,14 +183,7 @@ export function buildChecks(feature, text, plan) {
202
183
  };
203
184
  }
204
185
 
205
- /**
206
- * The feature's extracted checks and whether they still describe the plan on
207
- * disk. plan-check writes them only with zero errors, so `fresh` also means
208
- * the plan passed every plan-check rule (no slice over MAX_SLICE_FILES, every
209
- * scenario covered).
210
- *
211
- * @returns {{ status: 'fresh'|'no-plan'|'no-checks'|'unreadable'|'stale', checks: object|null }}
212
- */
186
+ /** plan-check writes the checks only with zero errors, so `fresh` also means the plan passed every rule. */
213
187
  export function readPlanChecks(cwd, featureName) {
214
188
  const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
215
189
  const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
@@ -175,12 +175,7 @@ function parseSlices(lines) {
175
175
  return slices;
176
176
  }
177
177
 
178
- /**
179
- * Parse `2-plan/plan.md` into its structure. Pure: no validation, no I/O —
180
- * `checks.js` judges what this returns.
181
- *
182
- * @param {string} text
183
- */
178
+ /** Pure: no validation, no I/O; `checks.js` judges what this returns. */
184
179
  export function parsePlan(text) {
185
180
  const lines = markdownLines(text);
186
181
  const sections = splitSections(lines);
@@ -1,19 +1,5 @@
1
- /**
2
- * Where the morph-spec projects on this machine are — the list the dashboard's
3
- * portfolio reads. Nothing else records it: each project only knows its own
4
- * `.morph/`.
5
- *
6
- * Three writers register a project as a side note of what they already do —
7
- * `init`, `update` (never under `--dry-run`) and `dashboard` — and the
8
- * dashboard adds and removes on request. Registering is fail-open: a registry
9
- * that cannot be read or written never fails the command that tried.
10
- *
11
- * The file lives in the user's home (`~/.morph-spec/projects.json`, or
12
- * `MORPH_PROJECTS_REGISTRY`), read through `os.homedir()` at call time so a
13
- * suite that isolates the home also isolates this file. Paths under the
14
- * system temp dir are never registered: the test suites run `init`/`update`
15
- * there by the hundred, including through spawned CLIs that keep the real home.
16
- */
1
+ // Paths under the system temp dir are never registered: test suites run `init`/`update` there by the
2
+ // hundred, through spawned CLIs that keep the real home. The home is read at call time so isolating it works.
17
3
 
18
4
  import { existsSync, readFileSync, readdirSync } from 'fs';
19
5
  import { homedir, tmpdir } from 'os';
@@ -35,7 +21,6 @@ export function projectKey(dir) {
35
21
 
36
22
  const sameDir = (a, b) => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b);
37
23
 
38
- /** A folder is a morph-spec project when morph-spec installed its config there. */
39
24
  export function isMorphProject(dir) {
40
25
  return existsSync(join(dir, '.morph', 'config', 'config.json'));
41
26
  }
@@ -61,18 +46,13 @@ function projectName(dir) {
61
46
  }
62
47
  }
63
48
 
64
- /**
65
- * The registered projects that still exist. A folder that was deleted or lost
66
- * its `.morph/` drops out on read — the file is rewritten on the next write.
67
- *
68
- * @returns {{ version: number, projects: Array<{ dir: string, name: string, addedAt: string, lastSeenAt: string, source: string }> }}
69
- */
49
+ /** Folders that were deleted or lost `.morph/` drop out on read. */
70
50
  export function readRegistry() {
71
51
  let data = null;
72
52
  try {
73
53
  const path = registryPath();
74
54
  if (existsSync(path)) data = JSON.parse(readFileSync(path, 'utf8'));
75
- } catch { /* unreadable registry reads as empty */ }
55
+ } catch {}
76
56
  const projects = Array.isArray(data?.projects) ? data.projects : [];
77
57
  return {
78
58
  version: REGISTRY_VERSION,
@@ -84,16 +64,7 @@ function write(projects) {
84
64
  atomicWriteJson(registryPath(), { version: REGISTRY_VERSION, projects });
85
65
  }
86
66
 
87
- /**
88
- * Note that `dir` is a morph-spec project. Idempotent; refreshes `lastSeenAt`.
89
- * Never throws.
90
- *
91
- * @param {string} dir
92
- * @param {'init'|'update'|'dashboard'|'manual'} source
93
- * @param {{ tempRoot?: string }} [opts] - test seam: the folder whose
94
- * projects are never registered (the system temp dir)
95
- * @returns {boolean} true when the project is in the registry afterwards
96
- */
67
+ /** Idempotent, never throws; `tempRoot` is the test seam for the never-registered folder. */
97
68
  export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
98
69
  try {
99
70
  const key = projectKey(dir);
@@ -115,7 +86,7 @@ export function registerProject(dir, source, { tempRoot = tmpdir() } = {}) {
115
86
  }
116
87
  }
117
88
 
118
- /** Take a project off the list. Its files are not touched. Never throws. */
89
+ /** Never throws; the project's files are not touched. */
119
90
  export function unregisterProject(dir) {
120
91
  try {
121
92
  const key = projectKey(dir);
@@ -128,15 +99,7 @@ export function unregisterProject(dir) {
128
99
  }
129
100
  }
130
101
 
131
- /**
132
- * morph-spec projects directly under each root that are not registered yet —
133
- * the portfolio offers them, the person decides. One level deep: a repos
134
- * folder is the expected root, and a deeper walk would enter node_modules and
135
- * worktrees for nothing.
136
- *
137
- * @param {string[]} roots
138
- * @returns {Array<{ dir: string, name: string }>}
139
- */
102
+ /** Unregistered projects one level under each root: deeper would walk node_modules and worktrees. */
140
103
  export function discoverProjects(roots) {
141
104
  const registered = readRegistry().projects.map((p) => p.dir);
142
105
  const out = [];
@@ -60,7 +60,7 @@ function tableRows(body) {
60
60
  return rows;
61
61
  }
62
62
 
63
- /** `| id | tipo | onde | evidência |`; `untyped` = tipo fora de {@link BLOCKER_TYPES}. */
63
+ // `| id | tipo | onde | evidência |`; `untyped` = tipo fora de BLOCKER_TYPES.
64
64
  function parseBlockers(body) {
65
65
  const blockers = [];
66
66
  const untyped = [];
@@ -74,10 +74,7 @@ function parseBlockers(body) {
74
74
 
75
75
  const STATUS_BY_KEY = Object.fromEntries(CHECK_STATUSES.map((s) => [normalize(s), s]));
76
76
 
77
- /**
78
- * `| Cn | PROVADO|NÃO PROVADO|FALHA | evidência |` de TODAS as seções: uma confirmação
79
- * traz só os Cn que mudou, então vale a última linha de cada Cn. Status por igualdade exata.
80
- */
77
+ // Vale a última linha de cada Cn em todas as seções: uma confirmação traz só os Cn que mudou.
81
78
  function parseChecks(bodies) {
82
79
  const byId = new Map();
83
80
  for (const cells of bodies.flatMap(tableRows)) {
@@ -110,10 +107,7 @@ export function parseReviewReport(text) {
110
107
  };
111
108
  }
112
109
 
113
- /**
114
- * `missing`: sem arquivo. `partial`: `PARCIAL` na 1ª linha, com ou sem veredito.
115
- * `unverdicted`: sem `**Verdict:** SHIP|FIX`. `ok`: legível — quem decide é o risk-detector.
116
- */
110
+ // `partial`: `PARCIAL` na 1ª linha, com ou sem veredito. `ok` só diz legível: quem decide é o risk-detector.
117
111
  function stateOf(path, text) {
118
112
  if (text === null) return { status: 'missing', path };
119
113
  const parsed = parseReviewReport(text);
@@ -148,13 +142,7 @@ export function reviewReports(cwd, featureName) {
148
142
  return reviewReportsFromDir(join(cwd, '.morph', 'features', featureName));
149
143
  }
150
144
 
151
- /**
152
- * Todo Cn precisa de uma linha, e só `PROVADO` cobre.
153
- *
154
- * @param {string[]} checkIds
155
- * @param {{id: string, status: string}[]|null} rows - `parseReviewReport(...).checks`
156
- * @returns {{ missing: string[], notProven: {id: string, status: string}[] }}
157
- */
145
+ /** Todo Cn precisa de uma linha, e só `PROVADO` cobre. */
158
146
  export function checksCoverage(checkIds, rows) {
159
147
  const byId = new Map((rows ?? []).map((r) => [r.id, r]));
160
148
  const missing = [];
@@ -167,12 +155,7 @@ export function checksCoverage(checkIds, rows) {
167
155
  return { missing, notProven };
168
156
  }
169
157
 
170
- /**
171
- * O sinal que `reviewPause` (risk-detector) lê; `checkIds: null` = cobertura não conferível.
172
- *
173
- * @param {ReturnType<typeof reviewReportsFromDir>} reports
174
- * @param {{ required: string[], checkIds: string[]|null }} opts
175
- */
158
+ /** O sinal que `reviewPause` lê; `checkIds: null` = cobertura não conferível. */
176
159
  export function reviewSignal(reports, { required, checkIds }) {
177
160
  const out = { required, reports: {} };
178
161
  for (const role of ROLE_ORDER) {
@@ -205,7 +188,6 @@ export function combinedVerdict(reports, required) {
205
188
  return required.every((r) => reports[r].status === 'ok' && reports[r].verdict === 'SHIP') ? 'SHIP' : null;
206
189
  }
207
190
 
208
- /** Todos os bloqueadores tipados dos relatórios presentes, com o papel que os abriu. */
209
191
  export function allBlockers(reports) {
210
192
  return ROLE_ORDER.flatMap((role) => (reports[role].blockers ?? []).map((b) => ({ ...b, role })));
211
193
  }