@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,84 +1,27 @@
1
- /**
2
- * Output Schema — Single Source of Truth (v5)
3
- *
4
- * ALL output type definitions live here. Edit ONLY this file to add, rename,
5
- * or relocate output types.
6
- */
7
-
8
1
  import { join } from 'path';
9
2
 
10
- // ============================================================================
11
- // Schema Definition
12
- // ============================================================================
13
-
14
- /**
15
- * Schema for output types produced by morph-spec NLH phases.
16
- *
17
- * Fields:
18
- * filename - The file's basename (e.g. 'plan.md'). Single-file types only.
19
- * phaseDir - Subdirectory within .morph/features/{feature}/
20
- * phase - The morph-spec phase that produces this output
21
- * protected - Whether the file requires an approval gate before editing
22
- * approvalGate - (optional) Which gate protects this file
23
- *
24
- * Multi-file types (`multi: true`) hold N files in a directory instead of one
25
- * fixed basename. They carry `dirname` + `filenamePattern` + `displayFilename`
26
- * INSTEAD of `filename`, and every derivation keyed by basename
27
- * (FILENAME_TO_OUTPUT, PROTECTED_SPEC_FILES) skips them —
28
- * there is no single basename to key on. `getOutputPath` returns the DIRECTORY
29
- * for these types.
30
- */
31
3
  export const OUTPUT_SCHEMA = {
32
- // 2-plan/ — the plan gate freezes both: plan.md accepts only an append in
33
- // `## Errata`, checks.json only a rewrite by `morph-spec plan-check`.
34
4
  plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
35
5
  checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
36
6
 
37
- // 1-design/ (optional)
38
7
  uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
39
8
  uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
40
9
  uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
41
10
  uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
42
11
 
43
- // 3-implement/ — the slice reports (`reports/F{n}.md`, see getTaskReportPath) are the record.
44
- // N files, one per scenario whose proof judges what a MODEL answers
45
- // (`prova: eval` in the plan). Runtime evidence: input, model, output, verdict — pointing
46
- // at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
47
- // construction: never in phases.json requiredOutputs. Readers: morph-review
48
- // dispatch prompt.
49
12
  evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
50
13
  phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
51
14
 
52
- // 4-review/
53
- // Um relatório por papel; o canônico da fase é o do verificador.
54
15
  verifierReport: { filename: 'verifier.md', phaseDir: '4-review', phase: 'review', protected: false },
55
16
  reviewerReport: { filename: 'reviewer.md', phaseDir: '4-review', phase: 'review', protected: false },
56
17
  securityReport: { filename: 'security.md', phaseDir: '4-review', phase: 'review', protected: false },
57
18
  };
58
19
 
59
- // ============================================================================
60
- // Builder Functions (Windows-safe via path.join)
61
- // ============================================================================
62
-
63
- /**
64
- * Leaf segment of a schema entry: the basename for single-file types, the
65
- * directory name for multi-file ones. Keeps every path builder from having to
66
- * know about `multi` on its own.
67
- */
68
20
  function leafSegment(entry) {
69
21
  return entry.multi ? entry.dirname : entry.filename;
70
22
  }
71
23
 
72
- /**
73
- * Get the morph-relative path for a specific output type of a feature.
74
- *
75
- * For `multi` types this is the DIRECTORY holding the N files
76
- * (".morph/features/my-feature/3-implement/evidencias"), not a single file.
77
- *
78
- * @param {string} featureName
79
- * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
80
- * @returns {string} e.g. ".morph/features/my-feature/2-plan/plan.md"
81
- */
24
+ /** For `multi` types this is the directory holding the N files. */
82
25
  export function getOutputPath(featureName, outputType) {
83
26
  const entry = OUTPUT_SCHEMA[outputType];
84
27
  if (!entry) {
@@ -88,10 +31,6 @@ export function getOutputPath(featureName, outputType) {
88
31
  .replace(/\\/g, '/');
89
32
  }
90
33
 
91
- /**
92
- * Get the absolute filesystem path for a specific output type.
93
- * For `multi` types this is the directory — see {@link getOutputPath}.
94
- */
95
34
  export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
96
35
  const entry = OUTPUT_SCHEMA[outputType];
97
36
  if (!entry) {
@@ -100,42 +39,20 @@ export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
100
39
  return join(projectRoot, '.morph', 'features', featureName, entry.phaseDir, leafSegment(entry));
101
40
  }
102
41
 
103
- /**
104
- * Get the feature base directory (morph-relative, forward slashes).
105
- */
106
42
  export function getFeatureDir(featureName) {
107
43
  return join('.morph', 'features', featureName).replace(/\\/g, '/');
108
44
  }
109
45
 
110
- /**
111
- * Per-task sub-agent report — the durable "return value" channel for a
112
- * dispatched implementation task (MORPH.md §6). A sub-agent's final text
113
- * message may never reach the orchestrator (idle_notification with no text
114
- * is an observed failure mode); the file it writes here always does. Doesn't
115
- * fit OUTPUT_SCHEMA's one-fixed-filename-per-type model since the filename
116
- * is parameterized by taskId, so it's a sibling helper instead of a schema
117
- * entry.
118
- *
119
- * @param {string} featureName
120
- * @param {string} taskId - e.g. "T7"
121
- * @returns {string} morph-relative, forward-slash path
122
- */
123
46
  export function getTaskReportPath(featureName, taskId) {
124
47
  return join('.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`)
125
48
  .replace(/\\/g, '/');
126
49
  }
127
50
 
128
- /** Absolute filesystem variant of {@link getTaskReportPath}. */
129
51
  export function getAbsoluteTaskReportPath(projectRoot, featureName, taskId) {
130
52
  return join(projectRoot, '.morph', 'features', featureName, '3-implement', 'reports', `${taskId}.md`);
131
53
  }
132
54
 
133
- /**
134
- * Get all output paths for a feature, ready for state.json `outputs` block.
135
- *
136
- * Multi-file types contribute their DIRECTORY, so `created` for them means
137
- * "the folder exists" — the state index tracks presence, not file count.
138
- */
55
+ /** `multi` types contribute their directory, so `created` means "the folder exists". */
139
56
  export function getAllOutputPaths(featureName) {
140
57
  const outputs = {};
141
58
  for (const [key] of Object.entries(OUTPUT_SCHEMA)) {
@@ -147,14 +64,6 @@ export function getAllOutputPaths(featureName) {
147
64
  return outputs;
148
65
  }
149
66
 
150
- // ============================================================================
151
- // Derived Constants
152
- // ============================================================================
153
-
154
- /**
155
- * filename → output type. Multi-file types are absent by construction: they
156
- * have N basenames, so there is nothing to key on.
157
- */
158
67
  export const FILENAME_TO_OUTPUT = Object.fromEntries(
159
68
  Object.entries(OUTPUT_SCHEMA)
160
69
  .filter(([, entry]) => !entry.multi)
@@ -165,20 +74,12 @@ export const OUTPUT_PHASE_MAP = Object.fromEntries(
165
74
  Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [key, entry.phase])
166
75
  );
167
76
 
168
- /**
169
- * Protected spec files and the approval gate that locks them.
170
- */
171
77
  export const PROTECTED_SPEC_FILES = Object.fromEntries(
172
78
  Object.entries(OUTPUT_SCHEMA)
173
79
  .filter(([, entry]) => entry.protected && entry.approvalGate && !entry.multi)
174
80
  .map(([, entry]) => [entry.filename, entry.approvalGate])
175
81
  );
176
82
 
177
- // ============================================================================
178
- // Contracts
179
- // ============================================================================
180
-
181
- /** The output type whose code blocks declare the contracts the code must honour. */
182
83
  export function resolveContractsOutputType() {
183
84
  return 'plan';
184
85
  }
@@ -1,32 +1,8 @@
1
- /**
2
- * Feature identity — the fields that keep a feature.json from being an
3
- * anonymous shell (the {status, updatedAt}-only scaffolds nobody could later
4
- * identify). name/createdAt/workType are auto-stamped at birth (explicit
5
- * `morph-spec create` or the state-sync hook's implicit registration);
6
- * `description` is the one field a human must supply.
7
- *
8
- * `missingIdentity` powers the audit surfaces (`list`, `doctor`) that flag
9
- * pre-existing anonymous features LENIENTLY (warn, never invalidate old
10
- * feature.json). `create` enforces `description` HARD at birth — the two-tier
11
- * split the schema's `required` array can't express (adding fields there would
12
- * invalidate every feature.json that predates this).
13
- *
14
- * Pure module — no filesystem/CLI side effects.
15
- */
16
-
17
1
  export const IDENTITY_FIELDS = ['name', 'description', 'workType', 'createdAt'];
18
2
 
19
3
  const blank = (v) => v === undefined || v === null || String(v).trim() === '';
20
4
 
21
- /**
22
- * Identity fields absent from a feature.json (empty array = fully identified).
23
- * `description` leads because its absence is the real anonymity signal.
24
- * `workType` is intentionally excluded — absent workType defaults to 'feature',
25
- * so it is never a marker of an anonymous shell.
26
- *
27
- * @param {object} feature - feature.json contents
28
- * @returns {string[]} missing identity field names
29
- */
5
+ // `workType` is excluded on purpose: absent, it defaults to `feature`.
30
6
  export function missingIdentity(feature) {
31
7
  const missing = [];
32
8
  if (blank(feature?.description)) missing.push('description');
@@ -1,19 +1,3 @@
1
- /**
2
- * Gate monotonicity — single source of truth.
3
- *
4
- * A gate may only be approved once its predecessors are approved. The strict
5
- * chain is plan → review. uiux is optional: it requires plan but gates nothing
6
- * downstream, so a feature that skips UI/UX is never blocked.
7
- *
8
- * Policy-stamped gates (approvedBy: "policy:{type}", written at birth by
9
- * `morph-spec create` for a work type's skippedGates) count as approved, so
10
- * chore/hotfix (plan pre-stamped) satisfy prereqs naturally.
11
- *
12
- * Pure module — no filesystem/CLI side effects. Consumed by
13
- * `approve`/`advance` (refuse), the state-sync hook (warn), and `list`/`doctor`
14
- * (report).
15
- */
16
-
17
1
  export const GATE_PREREQ = {
18
2
  plan: [],
19
3
  uiux: ['plan'],
@@ -22,22 +6,11 @@ export const GATE_PREREQ = {
22
6
 
23
7
  const isApproved = (gates, name) => gates?.[name]?.approved === true;
24
8
 
25
- /**
26
- * Prerequisite gates NOT yet approved that block approving `gate`.
27
- * @param {string} gate - gate about to be approved
28
- * @param {object} gates - feature.approvalGates
29
- * @returns {string[]} unmet prerequisite gate names (empty = clear to approve)
30
- */
31
9
  export function unmetPrereqs(gate, gates) {
32
10
  const prereqs = GATE_PREREQ[gate] || [];
33
11
  return prereqs.filter((p) => !isApproved(gates, p));
34
12
  }
35
13
 
36
- /**
37
- * All monotonicity violations currently present in a feature's gates.
38
- * @param {object} gates - feature.approvalGates
39
- * @returns {Array<{gate: string, missing: string[]}>} empty when in order
40
- */
41
14
  export function gateOrderViolations(gates) {
42
15
  if (!gates || typeof gates !== 'object') return [];
43
16
  const out = [];
@@ -1,23 +1,3 @@
1
- /**
2
- * Feature-lifecycle audit — the consolidated inconsistency scanner behind
3
- * `morph-spec doctor`. Read-only, non-throwing: scans `.morph/features/*` and
4
- * the thin root index and reports what's already wrong in the repo, without
5
- * touching state (never calls loadState — a diagnostic must not mutate).
6
- *
7
- * Issue types:
8
- * • gate-order — a gate approved with a predecessor pending (item 4)
9
- * • identity — a feature missing description/name/createdAt (item 3)
10
- * • orphan-folder — a feature folder with no feature.json
11
- * • stale-index — a root-index entry with no feature folder (auto-heals on
12
- * the next loadState, but surfaced so it's not a mystery)
13
- * • branch-drift — the root's copy of a feature.json disagrees with the copy
14
- * committed on `morph/{f}` (B5 — the root's view lags)
15
- * • abandoned-scaffold — an `in_progress` feature that never produced
16
- * anything: no artifacts, no approved gate, and a branch
17
- * with no commit of its own (see abandonedScaffold)
18
- * • corrupt — a feature.json that won't parse
19
- */
20
-
21
1
  import { execFileSync } from 'child_process';
22
2
  import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
23
3
  import { join } from 'path';
@@ -26,40 +6,19 @@ import { missingIdentity } from './feature-identity.js';
26
6
  import { detectDefaultBranch } from '../../lib/git/default-branch.js';
27
7
  import { commitsAhead, resolveBaseRef } from '../../lib/git/branch-classifier.js';
28
8
 
29
- /** The operational truth of a feature.json: its status and which gates are approved. */
30
9
  function operationalShape(fj) {
31
10
  const gates = {};
32
11
  for (const [g, v] of Object.entries(fj?.approvalGates || {})) gates[g] = v?.approved === true;
33
12
  return JSON.stringify({ status: fj?.status ?? null, gates });
34
13
  }
35
14
 
36
- /**
37
- * Does the root's working-tree copy of a feature.json disagree with the copy
38
- * COMMITTED on that feature's own branch?
39
- *
40
- * `.morph/state.json` is gitignored and derived, and every worktree keeps its
41
- * own — so "the index diverges between worktrees" is by design, not a defect to
42
- * fix by making the index authoritative (that would undo the whole per-feature
43
- * state model). The defect is that the divergence was INVISIBLE: since v8.4 the
44
- * authoritative feature.json travels committed on `morph/{f}`, so a primary root
45
- * parked on the default holds a copy that silently lags, and everything derived
46
- * from it (status, gates, and the index rebuilt from it) lags with it.
47
- *
48
- * Comparison is on the OPERATIONAL fields only — status and approved-gate flags
49
- * — not on bytes: reformatting or a timestamp bump is not a divergence anyone
50
- * needs to hear about.
51
- *
52
- * Fail-open at every step: no repo, no branch, no blob, unparseable JSON → no
53
- * issue. A diagnostic that invents problems is worse than one that misses some.
54
- *
55
- * @returns {string|null} the issue detail, or null when the copies agree
56
- */
15
+ // Compares only status and approved gates (bytes and timestamps are not drift).
16
+ // Fail-open: no repo, branch, blob or parseable JSON means no issue.
57
17
  function branchDrift(baseDir, feature, rootFj) {
58
18
  const branch = `morph/${feature}`;
59
19
  try {
60
20
  const current = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: baseDir, stdio: 'pipe' })
61
21
  .toString().trim();
62
- // On the feature's own branch the working tree IS the branch — nothing to compare.
63
22
  if (current === branch) return null;
64
23
 
65
24
  execFileSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: baseDir, stdio: 'pipe' });
@@ -77,18 +36,9 @@ function branchDrift(baseDir, feature, rootFj) {
77
36
  }
78
37
  }
79
38
 
80
- /**
81
- * How long a scaffold has to sit untouched before it counts as abandoned.
82
- *
83
- * A dead scaffold and a feature born five minutes ago have the SAME shape:
84
- * `create` forks `morph/{f}` off the default branch without committing, so at
85
- * birth the branch has no commit of its own and the folder holds nothing but
86
- * `feature.json`. Only elapsed time separates the two — and a check that fires
87
- * on every fresh `create` is one people learn to scroll past.
88
- */
39
+ // A dead scaffold and a feature born minutes ago have the same shape; only age tells them apart.
89
40
  const ABANDONED_SCAFFOLD_MIN_AGE_MS = 24 * 60 * 60 * 1000;
90
41
 
91
- /** Newest timestamp the feature carries, in ms; falls back to the folder's mtime. */
92
42
  function lastTouched(fdir, fj) {
93
43
  let newest = 0;
94
44
  for (const ts of [fj?.updatedAt, fj?.createdAt]) {
@@ -99,12 +49,7 @@ function lastTouched(fdir, fj) {
99
49
  try { return statSync(fdir).mtimeMs; } catch { return 0; }
100
50
  }
101
51
 
102
- /**
103
- * Does the feature dir hold any FILE other than its own `feature.json`?
104
- *
105
- * Files, not entries: `create` scaffolds the phase folders empty, so an empty
106
- * `2-plan/` is the absence of work, not evidence of it.
107
- */
52
+ // Files, not entries: `create` scaffolds the phase folders empty.
108
53
  function hasArtifacts(fdir) {
109
54
  let entries;
110
55
  try { entries = readdirSync(fdir, { withFileTypes: true }); } catch { return false; }
@@ -119,22 +64,7 @@ function hasArtifacts(fdir) {
119
64
  return false;
120
65
  }
121
66
 
122
- /**
123
- * Commits on the feature's branch that the base does not already contain, or
124
- * `null` when git cannot answer (no repo, no resolvable base, no branch, no
125
- * rev-list). "Cannot prove" must never read as "proved zero" — the predicate
126
- * below hinges on this number, so an unanswerable repo has to stay silent.
127
- *
128
- * The counting itself is `branch-classifier.js`'s `commitsAhead` against its
129
- * `resolveBaseRef`, NOT a second copy: `origin/{default}` is the authority on
130
- * what actually landed, and two drifting answers to "does this branch carry
131
- * anything of its own?" is exactly what the `doctor`/`branch prune` split was
132
- * unified to avoid.
133
- *
134
- * Note the field evidence this exists for: the branch pointed at the merge
135
- * commit of somebody else's PR. That is the base's own tip — reachable from
136
- * the base, and therefore zero commits of its own.
137
- */
67
+ // `null` when git cannot answer: "cannot prove" must never read as "proved zero".
138
68
  function ownCommitCount(baseDir, branch) {
139
69
  try {
140
70
  execFileSync('git', ['rev-parse', '--git-dir'], { cwd: baseDir, stdio: 'pipe' });
@@ -146,31 +76,15 @@ function ownCommitCount(baseDir, branch) {
146
76
  try {
147
77
  execFileSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: baseDir, stdio: 'pipe' });
148
78
  } catch {
149
- return 0; // the branch never existed — nothing was ever committed for it
79
+ return 0; // the branch never existed: nothing was committed on it
150
80
  }
151
81
  return commitsAhead(baseRef, branch, baseDir);
152
82
  }
153
83
 
154
- /**
155
- * The orphan skeleton `create --worktree` can leave behind: a feature.json on
156
- * its own, `status: in_progress`, no tasks, no gates, and a branch parked on
157
- * somebody else's commit. Every one of those alone is normal at some point in
158
- * a feature's life; together, and only after the feature has gone stale, they
159
- * mean nobody ever came back to it.
160
- *
161
- * The artifact test is deliberately broader than the "0 tasks" the report
162
- * named: a plan drafted and waiting on its gate also has zero tasks and is
163
- * very much alive. What a dead scaffold has is NOTHING — checked in the root
164
- * and, if the feature registered one, in its worktree, where uncommitted work
165
- * would otherwise be invisible to both the disk scan and `rev-list`.
166
- *
167
- * @returns {string|null} the issue detail, or null when the feature is alive
168
- */
169
84
  function abandonedScaffold(baseDir, feature, fdir, fj) {
170
85
  if (fj?.status !== 'in_progress') return null;
171
86
 
172
- // "0 gates": a gate stamped `policy:{workType}` at birth (chore/hotfix) was
173
- // never a decision anyone took, so it must not read as progress.
87
+ // A gate stamped `policy:{workType}` at birth was never a decision, so it is not progress.
174
88
  const decided = Object.values(fj.approvalGates || {}).some(
175
89
  (g) => g?.approved === true && !String(g?.approvedBy ?? '').startsWith('policy:'),
176
90
  );
@@ -185,8 +99,6 @@ function abandonedScaffold(baseDir, feature, fdir, fj) {
185
99
  const age = Date.now() - lastTouched(fdir, fj);
186
100
  if (age < ABANDONED_SCAFFOLD_MIN_AGE_MS) return null;
187
101
 
188
- // The registered branch when the feature has one, `morph/{f}` otherwise —
189
- // same name `branchDrift` above assumes.
190
102
  const branch = fj.worktree?.branch || `morph/${feature}`;
191
103
  if (ownCommitCount(baseDir, branch) !== 0) return null;
192
104
 
@@ -196,10 +108,6 @@ function abandonedScaffold(baseDir, feature, fdir, fj) {
196
108
  + 'ou retome a feature';
197
109
  }
198
110
 
199
- /**
200
- * @param {string} [baseDir] - project root (defaults to cwd)
201
- * @returns {Array<{ feature: string, type: string, detail: string }>} issues
202
- */
203
111
  export function auditFeatureLifecycle(baseDir = process.cwd()) {
204
112
  const issues = [];
205
113
  const featuresDir = join(baseDir, '.morph', 'features');
@@ -256,7 +164,7 @@ export function auditFeatureLifecycle(baseDir = process.cwd()) {
256
164
  issues.push({ feature: name, type: 'stale-index', detail: 'in state.json index but no feature folder' });
257
165
  }
258
166
  }
259
- } catch { /* index is derivable — non-fatal */ }
167
+ } catch { }
260
168
  }
261
169
 
262
170
  return issues;