@polymorphism-tech/morph-spec 8.47.0 → 9.0.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 (222) hide show
  1. package/CLAUDE.md +48 -39
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +37 -51
  4. package/bin/validate.js +3 -111
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/morph-apply.md +5 -28
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +8 -8
  14. package/framework/commands/morph-infra.md +1 -1
  15. package/framework/commands/morph-preflight.md +2 -3
  16. package/framework/commands/morph-proposal.md +2 -50
  17. package/framework/commands/morph-status.md +3 -3
  18. package/framework/docs/CLI.md +585 -286
  19. package/framework/evals/review-checklist.md +82 -41
  20. package/framework/hooks/README.md +10 -11
  21. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  22. package/framework/hooks/claude-code/core/denylist.json +33 -3
  23. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  24. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  25. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  26. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  27. package/framework/hooks/claude-code/core/state-sync.js +7 -21
  28. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  29. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  30. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  31. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  32. package/framework/hooks/claude-code/statusline.py +55 -41
  33. package/framework/hooks/package.json +3 -0
  34. package/framework/hooks/shared/activity-logger.js +1 -1
  35. package/framework/hooks/shared/build-check.js +10 -6
  36. package/framework/hooks/shared/command-tokens.js +169 -0
  37. package/framework/hooks/shared/comment-rules.js +1 -1
  38. package/framework/hooks/shared/comment-scanner.js +10 -23
  39. package/framework/hooks/shared/comment-stripper.js +1 -1
  40. package/framework/hooks/shared/e2e-check.js +64 -27
  41. package/framework/hooks/shared/errata.js +72 -0
  42. package/framework/hooks/shared/feature-locator.js +1 -1
  43. package/framework/hooks/shared/frontend-locations.js +6 -0
  44. package/framework/hooks/shared/gate-question.js +18 -0
  45. package/framework/hooks/shared/phase-utils.js +15 -32
  46. package/framework/hooks/shared/slice-progress.js +118 -0
  47. package/framework/hooks/shared/spawn-command.js +23 -0
  48. package/framework/hooks/shared/state-reader.js +61 -87
  49. package/framework/hooks/shared/state-writer.js +2 -3
  50. package/framework/mcp/registry.json +3 -4
  51. package/framework/phases.json +23 -39
  52. package/framework/schemas/config.schema.json +14 -9
  53. package/framework/schemas/feature.schema.json +15 -50
  54. package/framework/skills/README.md +3 -4
  55. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  56. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  57. package/framework/skills/core/morph-apply/SKILL.md +149 -222
  58. package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
  59. package/framework/skills/core/morph-machine/SKILL.md +2 -2
  60. package/framework/skills/core/morph-media/SKILL.md +1 -1
  61. package/framework/skills/core/morph-plan/SKILL.md +137 -299
  62. package/framework/skills/core/morph-review/SKILL.md +111 -136
  63. package/framework/skills/core/morph-standards/SKILL.md +27 -39
  64. package/framework/skills/core/morph-uiux/SKILL.md +77 -152
  65. package/framework/standards/STANDARDS.json +86 -1218
  66. package/framework/standards/STANDARDS.meta.json +1305 -0
  67. package/framework/standards/ai-agents/evals-with-cache.md +1 -2
  68. package/framework/standards/ai-agents/setup.md +1 -1
  69. package/framework/standards/architecture/code-comments.md +6 -6
  70. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  71. package/framework/templates/README.md +2 -3
  72. package/framework/templates/REGISTRY.json +6 -170
  73. package/framework/templates/docs/plan.md +72 -0
  74. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  75. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  76. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  77. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  78. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  79. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  80. package/package.json +1 -1
  81. package/src/commands/e2e/e2e.js +2 -2
  82. package/src/commands/observability/cost.js +18 -60
  83. package/src/commands/project/doctor-procs.js +1 -1
  84. package/src/commands/project/doctor.js +9 -9
  85. package/src/commands/project/fleet.js +4 -4
  86. package/src/commands/project/heavy.js +3 -3
  87. package/src/commands/project/init.js +4 -0
  88. package/src/commands/project/setup-infra-cmd.js +2 -1
  89. package/src/commands/project/stack-scan.js +58 -0
  90. package/src/commands/project/standards.js +79 -25
  91. package/src/commands/project/status.js +29 -69
  92. package/src/commands/project/update.js +17 -1
  93. package/src/commands/project/worktree.js +1 -1
  94. package/src/commands/state/advance.js +62 -46
  95. package/src/commands/state/approve.js +81 -141
  96. package/src/commands/state/archive.js +20 -84
  97. package/src/commands/state/auto-approval.js +68 -0
  98. package/src/commands/state/create.js +28 -1
  99. package/src/commands/state/delete.js +1 -1
  100. package/src/commands/state/finish.js +11 -23
  101. package/src/commands/state/gate-check.js +85 -84
  102. package/src/commands/state/gate-decision.js +6 -4
  103. package/src/commands/state/handoff.js +1 -1
  104. package/src/commands/state/list.js +9 -9
  105. package/src/commands/state/report-check.js +157 -311
  106. package/src/commands/state/retype.js +9 -3
  107. package/src/commands/validation/mutate.js +77 -14
  108. package/src/commands/validation/plan-check.js +81 -0
  109. package/src/commands/validation/validate-feature.js +60 -135
  110. package/src/commands/validation/verify.js +40 -72
  111. package/src/core/paths/output-schema.js +19 -66
  112. package/src/core/state/gate-order.js +5 -6
  113. package/src/core/state/lifecycle-audit.js +2 -2
  114. package/src/core/state/state-manager.js +17 -98
  115. package/src/core/templates/template-registry.js +2 -2
  116. package/src/core/templates/template-seed.js +22 -0
  117. package/src/core/work-types.js +10 -10
  118. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  119. package/src/dashboard/app/components/CommandPalette.jsx +5 -5
  120. package/src/dashboard/app/components/EntityLink.jsx +1 -1
  121. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  122. package/src/dashboard/app/lib/phases.js +2 -4
  123. package/src/dashboard/app/pages/Activity.jsx +1 -1
  124. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  125. package/src/dashboard/app/pages/Features.jsx +9 -9
  126. package/src/dashboard/app/pages/Guide.jsx +2 -2
  127. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  128. package/src/dashboard/app/pages/Standards.jsx +1 -1
  129. package/src/dashboard/scanner/activity-scanner.js +5 -15
  130. package/src/dashboard/scanner/agents-scanner.js +1 -1
  131. package/src/dashboard/scanner/docs-scanner.js +2 -2
  132. package/src/dashboard/scanner/features-scanner.js +95 -60
  133. package/src/dashboard/scanner/project-api.js +1 -1
  134. package/src/dashboard/scanner/standards-scanner.js +2 -1
  135. package/src/lib/ci/ci-verify.js +17 -1
  136. package/src/lib/ci/github-ci.js +1 -1
  137. package/src/lib/detectors/index.js +3 -8
  138. package/src/lib/generators/handoff-generator.js +34 -65
  139. package/src/lib/generators/settings-generator.js +11 -89
  140. package/src/lib/git/evidence-staleness.js +1 -1
  141. package/src/lib/git/tree-fingerprint.js +19 -7
  142. package/src/lib/git/tree-state.js +2 -2
  143. package/src/lib/heavy/cleanup.js +1 -5
  144. package/src/lib/installers/memory-installer.js +6 -8
  145. package/src/lib/mutation/commands.js +3 -2
  146. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  147. package/src/lib/mutation/plan.js +14 -6
  148. package/src/lib/mutation/runner.js +1 -1
  149. package/src/lib/mutation/subset.js +46 -9
  150. package/src/lib/plan/artifact-vocabulary.js +60 -0
  151. package/src/lib/plan/checks.js +234 -0
  152. package/src/lib/plan/plan-parser.js +209 -0
  153. package/src/lib/plan/plan-template.js +18 -0
  154. package/src/lib/review-reports.js +222 -0
  155. package/src/lib/risk-detector.js +131 -140
  156. package/src/lib/specs/adr-index.js +50 -69
  157. package/src/lib/specs/design-seed.js +18 -0
  158. package/src/lib/specs/frontmatter.js +44 -0
  159. package/src/lib/specs/gfm-table.js +17 -0
  160. package/src/lib/specs/old-format.js +68 -0
  161. package/src/lib/specs/revocations.js +37 -95
  162. package/src/lib/specs/screen-checklist.js +18 -17
  163. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  164. package/src/lib/stack/stack-scan.js +267 -0
  165. package/src/lib/standards/registry.js +43 -0
  166. package/src/lib/standards/sections.js +58 -0
  167. package/src/lib/tasks/eval-node.js +4 -12
  168. package/src/lib/tasks/test-nodes.js +1 -74
  169. package/src/lib/tasks/test-runner.js +75 -117
  170. package/src/lib/telemetry/metrics.js +8 -69
  171. package/src/lib/telemetry/telemetry-reader.js +5 -118
  172. package/src/lib/telemetry/verify-event.js +5 -7
  173. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  174. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  175. package/src/lib/validators/reuse/reuse-validator.js +22 -45
  176. package/src/lib/validators/traceability-check.js +85 -129
  177. package/src/lib/validators/verify-runner.js +118 -235
  178. package/src/lib/worktree/docker-audit.js +1 -2
  179. package/src/lib/worktree/fleet-scanner.js +25 -38
  180. package/src/scripts/setup-infra.js +9 -0
  181. package/src/utils/agents-installer.js +76 -9
  182. package/src/utils/claude-settings-manager.js +0 -35
  183. package/src/utils/file-copier.js +27 -1
  184. package/src/utils/hooks-installer.js +16 -9
  185. package/src/utils/skills-installer.js +1 -2
  186. package/framework/evals/trace-schema.md +0 -67
  187. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  188. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  189. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  190. package/framework/hooks/shared/tasks-validator.js +0 -111
  191. package/framework/rules/morph-task-tracking.md +0 -54
  192. package/framework/rules/morph-workflow.md +0 -244
  193. package/framework/schemas/tasks.schema.json +0 -79
  194. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  195. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  196. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  197. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  198. package/framework/templates/docs/proposal.md +0 -192
  199. package/framework/templates/docs/spec.md +0 -57
  200. package/framework/templates/docs/ui-components.md +0 -52
  201. package/framework/templates/docs/ui-design-system.md +0 -76
  202. package/framework/templates/docs/ui-flows.md +0 -167
  203. package/framework/templates/examples/spec-examples.md +0 -91
  204. package/framework/templates/feature/decisions.md +0 -124
  205. package/framework/templates/feature/recap.md +0 -145
  206. package/framework/templates/tasks.template.json +0 -17
  207. package/src/commands/state/usecase.js +0 -143
  208. package/src/commands/tasks/dag.js +0 -181
  209. package/src/lib/agents/output-collector.js +0 -76
  210. package/src/lib/evaluator-report.js +0 -104
  211. package/src/lib/specs/acervo-format.js +0 -70
  212. package/src/lib/specs/acervo-lifecycle.js +0 -264
  213. package/src/lib/specs/done-criteria-check.js +0 -200
  214. package/src/lib/specs/usecase-check.js +0 -301
  215. package/src/lib/specs/usecase-promoter.js +0 -301
  216. package/src/lib/specs/usecase-registry.js +0 -237
  217. package/src/lib/tasks/dispatch-planner.js +0 -319
  218. package/src/lib/tasks/mermaid-renderer.js +0 -132
  219. package/src/lib/tasks/task-classifier.js +0 -30
  220. package/src/lib/tasks/task-parser.js +0 -209
  221. package/src/lib/tasks/tasks-json-parser.js +0 -296
  222. package/src/lib/tasks/test-scope.js +0 -597
@@ -1,124 +0,0 @@
1
- # Architecture Decision Records - {{titleCase FEATURE_NAME}}
2
-
3
- ## ADR-001: {Decision Title}
4
-
5
- **Date:** {{DATE}}
6
- **Status:** Proposed | Accepted | Deprecated | Superseded
7
- **Deciders:** MORPH Agents, Developer
8
- **Alcance:** feature
9
-
10
- ### Context
11
-
12
- {Describe the context and problem that led to this decision}
13
-
14
- ### Decision
15
-
16
- {Describe the decision made}
17
-
18
- ### Consequences
19
-
20
- **Pros:**
21
- - {Positive consequence 1}
22
- - {Positive consequence 2}
23
-
24
- **Cons:**
25
- - {Negative consequence 1}
26
-
27
- **Risks:**
28
- - {Identified risk}
29
-
30
- ### Alternatives Considered
31
-
32
- #### Option A: {Name}
33
- - Pros: {advantages}
34
- - Cons: {disadvantages}
35
- - **Rejected because:** {reason}
36
-
37
- #### Option B: {Name}
38
- - Pros: {advantages}
39
- - Cons: {disadvantages}
40
- - **Rejected because:** {reason}
41
-
42
- ---
43
-
44
- {{#if (eq STACK "nextjs-neon")}}
45
- ## ADR-002: Database Access Strategy
46
-
47
- **Date:** {{DATE}}
48
- **Status:** Accepted
49
-
50
- ### Context
51
-
52
- Need to decide how the .NET API accesses Neon PostgreSQL.
53
-
54
- ### Decision
55
-
56
- Use Dapper with Npgsql for direct SQL queries. No EF Core.
57
-
58
- ### Consequences
59
-
60
- **Pros:**
61
- - Full control over SQL queries
62
- - Better performance (no ORM overhead)
63
- - Direct access to PostgreSQL features (jsonb, pgvector, functions)
64
- - RLS policies apply naturally through connection
65
-
66
- **Cons:**
67
- - No automatic migrations (use Neon CLI / dotnet ef instead)
68
- - Manual SQL query writing
69
- - No change tracking
70
-
71
- ---
72
-
73
- ## ADR-003: Auth Strategy
74
-
75
- **Date:** {{DATE}}
76
- **Status:** Accepted
77
-
78
- ### Context
79
-
80
- Need authentication for both Next.js frontend and .NET API.
81
-
82
- ### Decision
83
-
84
- Use Neon Auth (Better Auth) for authentication. Auth data lives in the Neon database. JWT validated by .NET API middleware.
85
-
86
- ### Consequences
87
-
88
- **Pros:**
89
- - Auth data in same database — no external provider, no sync delays
90
- - RLS policies work with Neon Auth JWT via auth.user_id() / pg_session_jwt
91
- - Built-in social login, email/password via @neondatabase/auth SDK
92
- - Branch isolation — each Neon branch gets isolated auth data
93
- - No webhooks needed — query neon_auth schema directly
94
-
95
- **Cons:**
96
- - Neon Auth is currently in Beta
97
- - JWT must be forwarded to .NET API for backend auth
98
- {{/if}}
99
-
100
- ---
101
-
102
- ## Template for New ADRs
103
-
104
- ```markdown
105
- ## ADR-XXX: {Title}
106
-
107
- **Date:**
108
- **Status:** Proposed | Accepted | Deprecated | Superseded
109
- **Alcance:** feature | projeto — `projeto` when the rule holds beyond this feature: `morph-spec finish` copies it to `.morph/specs/adrs/`, the folder brainstorming and plan read instead of archived decisions.md
110
-
111
- ### Context
112
- {Why is this decision needed?}
113
-
114
- ### Decision
115
- {What was decided?}
116
-
117
- ### Consequences
118
- **Pros:** {list}
119
- **Cons:** {list}
120
- ```
121
-
122
- ---
123
-
124
- *Generated by MORPH Framework*
@@ -1,145 +0,0 @@
1
- # Feature Recap: {{titleCase FEATURE_NAME}}
2
-
3
- ## Summary
4
-
5
- | Field | Value |
6
- |-------|-------|
7
- | **Feature ID** | {{FEATURE_NAME}} |
8
- | **Completed** | {{DATE}} |
9
- | **Total Tasks** | {X} |
10
- | **Time Spent** | {X}h |
11
- | **Agents Used** | {list} |
12
- {{#if STACK}}| **Stack** | {{STACK}} |{{/if}}
13
-
14
- ---
15
-
16
- ## Tasks Completed
17
-
18
- | ID | Title | Category | Duration |
19
- |----|-------|----------|----------|
20
- | T001 | {title} | {{#if (eq STACK "nextjs-neon")}}Database{{else}}{category}{{/if}} | {X}min |
21
- | T002 | {title} | {{#if (eq STACK "nextjs-neon")}}Backend{{else}}{category}{{/if}} | {X}min |
22
- | T003 | {title} | {{#if (eq STACK "nextjs-neon")}}Frontend{{else}}{category}{{/if}} | {X}min |
23
-
24
- ---
25
-
26
- {{#if (eq STACK "nextjs-neon")}}
27
- ## Validation Results
28
-
29
- | Validator | Status | Details |
30
- |-----------|--------|---------|
31
- | contract-compliance | Pass/Fail | {details} |
32
- | architecture | Pass/Fail | {details} |
33
- | design-system | Pass/Fail | {details} |
34
- | coding-standards | Pass/Fail | {details} |
35
-
36
- ---
37
-
38
- {{/if}}
39
- ## What Was Delivered
40
-
41
- ### Functionality
42
- - {Functionality 1 delivered}
43
- - {Functionality 2 delivered}
44
- - {Functionality 3 delivered}
45
-
46
- {{#if (eq STACK "nextjs-neon")}}
47
- ### Database (Neon PostgreSQL)
48
- - Tables: {list}
49
- - RLS Policies: {count} policies
50
- - Migrations: {count} files
51
-
52
- ### Backend API (.NET)
53
- - Endpoints: {count} endpoints
54
- - Services: {list}
55
-
56
- ### Frontend (Next.js)
57
- - Pages: {list}
58
- - Components: {list}
59
- {{/if}}
60
-
61
- ---
62
-
63
- ## Files Created
64
-
65
- ```
66
- {{#if (eq STACK "nextjs-neon")}}
67
- migrations/{files}
68
- backend/src/{files}
69
- frontend/src/{files}
70
- {{else}}
71
- {list of created files}
72
- {{/if}}
73
- ```
74
-
75
- ## Files Modified
76
-
77
- ```
78
- {list of modified files}
79
- ```
80
-
81
- ---
82
-
83
- ## Architecture Decisions
84
-
85
- | Decision | Rationale |
86
- |----------|-----------|
87
- | {Decision 1} | {Reason} |
88
- | {Decision 2} | {Reason} |
89
-
90
- See full ADRs in `decisions.md`
91
-
92
- ---
93
-
94
- ## Test Coverage
95
-
96
- | Type | Files | Coverage |
97
- |------|-------|----------|
98
- | {{#if (eq STACK "nextjs-neon")}}Backend Unit{{else}}Unit Tests{{/if}} | {X} | {X}% |
99
- | {{#if (eq STACK "nextjs-neon")}}Frontend Component{{else}}Integration Tests{{/if}} | {X} | {X}% |
100
-
101
- ---
102
-
103
- ## Lessons Learned
104
-
105
- ### What Went Well
106
- - {Positive point 1}
107
- - {Positive point 2}
108
-
109
- ### What Could Be Improved
110
- - {Improvement point 1}
111
- - {Improvement point 2}
112
-
113
- ### Recommendations for Future
114
- - {Recommendation 1}
115
- - {Recommendation 2}
116
-
117
- ---
118
-
119
- ## Metrics
120
-
121
- | Metric | Value |
122
- |--------|-------|
123
- | Tasks Completed | {X}/{X} |
124
- | Checkpoints Passed | {X} |
125
- {{#if (eq STACK "nextjs-neon")}}| Validation Passes | {X}/{X} |
126
- {{else}}| Code Reviews | {X} |
127
- {{/if}}| Bugs Found | {X} |
128
- | Bugs Fixed | {X} |
129
-
130
- ---
131
-
132
- ## Related Resources
133
-
134
- - **Proposal:** `proposal.md`
135
- - **Spec:** `spec.md`
136
- {{#if (eq STACK "nextjs-neon")}}- **Contracts (.NET):** `contracts.cs`
137
- - **Contracts (TS):** `contracts.ts`
138
- {{else}}- **Contracts:** `contracts.cs`
139
- {{/if}}- **Tasks:** `tasks.json`
140
- - **Decisions:** `decisions.md`
141
- - **PR:** {link}
142
-
143
- ---
144
-
145
- *Generated by MORPH Framework on {{DATE}}*
@@ -1,17 +0,0 @@
1
- {
2
- "version": "1.0",
3
- "feature": "{{featureName}}",
4
- "tasks": [
5
- {
6
- "id": "T1",
7
- "title": "Example task title",
8
- "description": "What this task accomplishes and why",
9
- "dependencies": [],
10
- "effort": "M",
11
- "doneCriteria": "Verifiable acceptance condition stated in one sentence",
12
- "status": "pending",
13
- "outputs": [],
14
- "group": "backend"
15
- }
16
- ]
17
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * `morph-spec usecase reserve [--count <n>] [--feature <name>] [--json]` —
3
- * atomically mint the next N `UC-{nn}` numbers, shared across every worktree
4
- * of this repo. See `usecase-registry.js` for why this exists (worktrees only
5
- * ever see their own tree, so an independent max+1 scan collides).
6
- *
7
- * `--feature` is more than an audit label: `usecase-promoter.js`'s collision
8
- * guard uses the reservation record to tell a legitimate cross-feature
9
- * correction (never reserved — reusing a published number by design) from an
10
- * accidental collision (both features reserved the same id). Passing it is
11
- * how `morph-plan` gets that protection; omitting it degrades to "reserved,
12
- * no owner recorded" — still collision-free, just unable to name the
13
- * conflicting feature if one ever surfaces.
14
- *
15
- * `morph-spec usecase check <feature>` — see `usecaseCheckCommand` below.
16
- */
17
-
18
- import chalk from 'chalk';
19
- import { existsSync, readdirSync } from 'fs';
20
- import { join } from 'path';
21
- import { resolveGitCommonDir } from '../../../framework/hooks/shared/git-paths.js';
22
- import { reserveUseCases } from '../../lib/specs/usecase-registry.js';
23
- import { checkUseCases } from '../../lib/specs/usecase-check.js';
24
- import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
25
- import { parseTasksJsonLenient } from '../../lib/tasks/tasks-json-parser.js';
26
- import { isValidFeatureName } from '../project/worktree.js';
27
- import { printLifecycleWarnings } from '../tasks/dag.js';
28
-
29
- /**
30
- * Best-effort feature-name inference when `--feature` is omitted: only when
31
- * exactly one active feature exists is the guess unambiguous. Anything else
32
- * (zero, or more than one) is left null rather than guessed wrong — a wrong
33
- * guess would poison the collision guard's provenance record.
34
- */
35
- function inferActiveFeature(cwd) {
36
- const featuresDir = join(cwd, '.morph', 'features');
37
- try {
38
- if (!existsSync(featuresDir)) return null;
39
- const entries = readdirSync(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory());
40
- return entries.length === 1 ? entries[0].name : null;
41
- } catch {
42
- return null;
43
- }
44
- }
45
-
46
- /**
47
- * @param {{ count?: string, feature?: string, json?: boolean, cwd?: string }} [options]
48
- */
49
- export function usecaseReserveCommand(options = {}) {
50
- const cwd = options.cwd || process.cwd();
51
- const count = Number.parseInt(options.count, 10);
52
- const n = Number.isInteger(count) && count > 0 ? count : 1;
53
- const feature = options.feature || inferActiveFeature(cwd) || null;
54
-
55
- let gitCommonDir = null;
56
- try { gitCommonDir = resolveGitCommonDir(cwd); } catch { gitCommonDir = null; }
57
-
58
- let result;
59
- try {
60
- result = reserveUseCases(cwd, { feature, count: n, gitCommonDir });
61
- } catch (error) {
62
- if (options.json) {
63
- console.log(JSON.stringify({ reserved: false, error: error.message }));
64
- process.exit(1);
65
- }
66
- console.error(chalk.red(`\n❌ ${error.message}\n`));
67
- process.exit(1);
68
- }
69
-
70
- const warning = gitCommonDir
71
- ? null
72
- : 'projeto fora de um repositório git — numeração protegida só localmente; paralelismo entre worktrees NÃO está garantido.';
73
-
74
- if (options.json) {
75
- console.log(JSON.stringify({ reserved: true, ...result, feature, warning }));
76
- process.exit(0);
77
- }
78
-
79
- console.log(chalk.green(`\n✅ Reservado: ${result.numbers.join(' ')}`));
80
- if (feature) console.log(chalk.gray(` feature: ${feature}`));
81
- if (warning) console.log(chalk.yellow(`⚠️ ${warning}\n`));
82
- else console.log('');
83
- process.exit(0);
84
- }
85
-
86
- /**
87
- * `morph-spec usecase check <feature> [--json]` — the use-case half of `dag`,
88
- * runnable before `tasks.json` exists: the feature's use cases and the
89
- * revocations its `spec.md` declares, against the acervo's lifecycle. With a
90
- * `tasks.json`, also the task → use case plumbing `dag` checks.
91
- *
92
- * Exit 1 only for what `dag` also refuses (a task pointing at a use case that
93
- * does not exist, a tasks.json that does not validate); lifecycle findings are
94
- * warnings.
95
- *
96
- * @param {string} feature
97
- * @param {{ json?: boolean, cwd?: string }} [options]
98
- */
99
- export async function usecaseCheckCommand(feature, options = {}) {
100
- const cwd = options.cwd || process.cwd();
101
- const fail = (error) => {
102
- if (options.json) console.log(JSON.stringify({ feature, error }));
103
- else console.error(chalk.red(`\n❌ ${error}\n`));
104
- process.exit(1);
105
- };
106
-
107
- if (!isValidFeatureName(feature)) fail(`nome de feature inválido: "${feature}"`);
108
- const featureDir = join(cwd, '.morph', 'features', feature);
109
- if (!existsSync(featureDir)) fail(`feature não encontrada: .morph/features/${feature}`);
110
-
111
- const tasksPath = join(featureDir, '2-plan', 'tasks.json');
112
- let tasksData = null;
113
- if (existsSync(tasksPath)) {
114
- try {
115
- ({ data: tasksData } = await parseTasksJsonLenient(tasksPath));
116
- } catch (error) {
117
- fail(`tasks.json inválido: ${error.message}`);
118
- }
119
- }
120
-
121
- const useCases = tasksData ? checkUseCases(tasksData, join(featureDir, '2-plan', 'usecases')) : null;
122
- const lifecycle = checkLifecycle({ cwd, featureDir, tasksData });
123
- const broken = useCases?.errors.length > 0;
124
-
125
- if (options.json) {
126
- console.log(JSON.stringify({ feature, useCases, lifecycle }, null, 2));
127
- process.exit(broken ? 1 : 0);
128
- }
129
-
130
- if (broken) {
131
- console.error(chalk.red(`\n❌ Use cases — ${useCases.errors.length} erro(s)\n`));
132
- for (const e of useCases.errors) console.error(chalk.red(` ${e}`));
133
- }
134
- if (useCases?.warnings.length) {
135
- console.error(chalk.yellow(`\n⚠ Use cases — ${useCases.warnings.length} aviso(s)`));
136
- for (const w of useCases.warnings) console.error(chalk.yellow(` ${w}`));
137
- }
138
- printLifecycleWarnings(lifecycle);
139
- if (!broken && !useCases?.warnings.length && !lifecycle.warnings.length) {
140
- console.log(chalk.green(`\n✅ ${feature}: use cases e acervo sem achados\n`));
141
- }
142
- process.exit(broken ? 1 : 0);
143
- }
@@ -1,181 +0,0 @@
1
- import chalk from 'chalk';
2
- import { existsSync, readFileSync } from 'fs';
3
- import { join } from 'path';
4
- import { parseTasksJsonLenient, oversizedTasks, MAX_TASK_OUTPUTS } from '../../lib/tasks/tasks-json-parser.js';
5
- import { planDispatch } from '../../lib/tasks/dispatch-planner.js';
6
- import { renderMermaidFlowchart } from '../../lib/tasks/mermaid-renderer.js';
7
- import { checkUseCases } from '../../lib/specs/usecase-check.js';
8
- import { checkArtifactCriteria } from '../../lib/specs/done-criteria-check.js';
9
- import { checkLifecycle } from '../../lib/specs/acervo-lifecycle.js';
10
-
11
- /** Shared by `dag` and `usecase check` — the two surfaces of the same finding. */
12
- export function printLifecycleWarnings(lifecycle) {
13
- if (!lifecycle?.warnings?.length) return;
14
- const n = lifecycle.warnings.length;
15
- console.error(chalk.yellow(`\n⚠ Ciclo de vida do acervo — ${n} ${n === 1 ? 'aviso' : 'avisos'}`));
16
- for (const w of lifecycle.warnings) console.error(chalk.yellow(` ${w}`));
17
- }
18
-
19
- function planGateApproved(featureDir) {
20
- try {
21
- const feature = JSON.parse(readFileSync(join(featureDir, 'feature.json'), 'utf8'));
22
- return feature?.approvalGates?.plan?.approved === true;
23
- } catch {
24
- return false;
25
- }
26
- }
27
-
28
- /**
29
- * `morph-spec dag <feature>` — analyze the tasks.json DAG and print the
30
- * sequential vs parallel dispatch decision, weighted by effort.
31
- *
32
- * This is the command morph-apply §2 runs instead of re-deriving the rules
33
- * (and instead of the old inline JS snippets the LLM could not execute).
34
- *
35
- * `--mermaid` emits the DAG as a Mermaid `flowchart TD` instead (for
36
- * visualizing the plan at Gate 2), bypassing the dispatch analysis entirely.
37
- */
38
- export async function dagCommand(featureName, options = {}) {
39
- try {
40
- const featureDir = join(process.cwd(), '.morph', 'features', featureName);
41
- const planDir = join(featureDir, '2-plan');
42
- const tasksPath = join(planDir, 'tasks.json');
43
- if (!existsSync(tasksPath)) {
44
- console.error(chalk.red(`\n❌ tasks.json not found for feature "${featureName}"`));
45
- console.error(chalk.gray(`Expected at: .morph/features/${featureName}/2-plan/tasks.json\n`));
46
- process.exit(1);
47
- }
48
-
49
- // parseTasksJsonLenient validates the schema and throws with the error
50
- // list, but defaults a missing `version` (legacy plans predating the
51
- // field) instead of failing — the DAG analysis doesn't depend on it.
52
- const { data, versionDefaulted } = await parseTasksJsonLenient(tasksPath);
53
- if (versionDefaulted) {
54
- console.error(chalk.yellow(
55
- `⚠ tasks.json has no 'version' field — defaulting to "1.0" for this analysis.\n` +
56
- ` Consider adding it back explicitly (safe: protect-spec-files allows backfilling it).`
57
- ));
58
- }
59
-
60
- // --mermaid short-circuits before the dispatch analysis: visualizing the
61
- // plan's DAG doesn't need a sequential-vs-parallel decision.
62
- if (options.mermaid) {
63
- console.log(renderMermaidFlowchart(data));
64
- return;
65
- }
66
-
67
- // Use-case cross-check. Runs AFTER the --mermaid short-circuit on purpose:
68
- // rendering the DAG is pure visualization and must never be blocked by the
69
- // state of the spec. A broken `usecase` reference IS fatal here — the file
70
- // either exists or it does not, so there is no false positive to weigh
71
- // against a plan that points at a use case nobody can read.
72
- const ucCheck = checkUseCases(data, join(planDir, 'usecases'));
73
-
74
- // Artifact-postcondition warning (issue #87). Lexical, not wiring — it
75
- // runs over ALL tasks even when the feature has no use cases at all
76
- // (checkUseCases short-circuits above in that case), because a task's
77
- // own doneCriteria can describe an artifact with no use case in sight.
78
- const artifactCriteria = checkArtifactCriteria(data, join(planDir, 'usecases'));
79
-
80
- // Acervo lifecycle: a task citing a superseded use case, and the spec.md
81
- // revocation rows the finish would fail to apply. Warnings only.
82
- const lifecycle = checkLifecycle({ cwd: process.cwd(), featureDir, tasksData: data });
83
-
84
- // Task acima do teto não cabe num dispatch. Antes do Gate 2 é erro (o plano
85
- // ainda fatia); depois é aviso, para não travar feature em voo.
86
- const oversized = oversizedTasks(data);
87
- const sizeBlocks = oversized.length > 0 && !planGateApproved(featureDir);
88
- const taskSize = { max: MAX_TASK_OUTPUTS, oversized, blocking: sizeBlocks };
89
-
90
- const plan = planDispatch(data);
91
-
92
- if (options.json) {
93
- console.log(JSON.stringify({ ...plan, useCases: ucCheck, artifactCriteria, lifecycle, taskSize }, null, 2));
94
- if (ucCheck.errors.length || sizeBlocks) process.exit(1);
95
- return;
96
- }
97
-
98
- const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
99
-
100
- if (ucCheck.errors.length) {
101
- console.error(chalk.red(`\n❌ Use cases — ${plural(ucCheck.errors.length, 'erro', 'erros')}\n`));
102
- for (const e of ucCheck.errors) console.error(chalk.red(` ${e}`));
103
- console.error(chalk.gray('\n Corrija a referência no tasks.json ou restaure o use case.\n'));
104
- process.exit(1);
105
- }
106
- if (oversized.length) {
107
- const tone = sizeBlocks ? chalk.red : chalk.yellow;
108
- const head = sizeBlocks ? '❌' : '⚠';
109
- console.error(tone(`\n${head} Tamanho de task — ${plural(oversized.length, 'task', 'tasks')} acima de ${MAX_TASK_OUTPUTS} outputs`));
110
- for (const t of oversized) console.error(tone(` ${t.id}: ${t.outputs} outputs`));
111
- console.error(chalk.gray(' Fatie por pós-condição: uma task cabe num dispatch (morph-plan, "Tamanho da task").\n'));
112
- if (sizeBlocks) process.exit(1);
113
- }
114
- if (ucCheck.warnings.length) {
115
- console.error(chalk.yellow(`\n⚠ Use cases — ${plural(ucCheck.warnings.length, 'aviso', 'avisos')}`));
116
- for (const w of ucCheck.warnings) console.error(chalk.yellow(` ${w}`));
117
- }
118
- if (artifactCriteria.warnings.length) {
119
- console.error(chalk.yellow(`\n⚠ Critérios de artefato — ${plural(artifactCriteria.warnings.length, 'aviso', 'avisos')}`));
120
- for (const w of artifactCriteria.warnings) console.error(chalk.yellow(` ${w}`));
121
- console.error(chalk.gray(' Teste que lê texto de arquivo não prova comportamento (morph-plan §2, "Por que artefato").'));
122
- }
123
- printLifecycleWarnings(lifecycle);
124
-
125
- console.log(chalk.bold(`\n📊 DAG — ${featureName}`));
126
- console.log('━'.repeat(60));
127
-
128
- const { stats } = plan;
129
- console.log(`\nTasks: ${stats.total} total, ${stats.pending} pendente(s)`);
130
- const groupLine = Object.keys(stats.groups)
131
- .map(g => `${g}:${stats.groups[g]} (peso ${stats.weights[g]})`)
132
- .join(' | ');
133
- if (groupLine) console.log(chalk.gray(`Grupos pendentes: ${groupLine}`));
134
-
135
- const modeLabel = {
136
- 'sequential': 'SEQUENCIAL (auto-advance)',
137
- 'parallel-flows': 'PARALELO por fluxo de domínio',
138
- 'parallel-roots': 'PARALELO por raízes independentes',
139
- }[plan.mode];
140
- console.log(chalk.cyan(`\nDecisão: ${modeLabel}`));
141
- console.log(` → ${plan.reason}`);
142
-
143
- for (const flow of plan.flows) {
144
- console.log(` ${chalk.green('▶')} Fluxo ${chalk.bold(flow.group)} (peso ${flow.weight}): ${flow.taskIds.join(', ')}`);
145
- }
146
- if (plan.batch.length > 0) {
147
- console.log(` ${chalk.green('▶')} Batch paralelo: ${plan.batch.join(', ')}`);
148
- }
149
- if (plan.inline.length > 0) {
150
- console.log(` ${chalk.yellow('○')} Inline (sem dispatch próprio): ${plan.inline.join(', ')}`);
151
- }
152
- if (plan.sequentialRemainder.length > 0) {
153
- console.log(` ${chalk.gray('→')} Sequencial: ${plan.sequentialRemainder.join(', ')}`);
154
- }
155
-
156
- // Isolamento — only meaningful when there IS a parallel batch. In a shared
157
- // working tree two agents writing the same path corrupt each other; the
158
- // dag recommends `isolation: "worktree"` so each runs in its own worktree
159
- // (morph-apply §3b-w).
160
- const iso = plan.isolation;
161
- if (iso && (iso.recommended === 'worktree' || iso.unknownFootprint.length > 0)) {
162
- console.log(chalk.bold('\nIsolamento:'));
163
- if (iso.recommended === 'worktree') {
164
- console.log(` ${chalk.yellow('⚠')} recomendado: ${chalk.bold('worktree')} — ${iso.reason}`);
165
- for (const c of iso.collisions) {
166
- console.log(chalk.gray(` colisão em "${c.path}": ${c.units.join(' ‖ ')} (${c.tasks.join(', ')})`));
167
- }
168
- } else {
169
- console.log(` ${chalk.gray('○')} ${iso.reason}`);
170
- }
171
- if (iso.unknownFootprint.length > 0) {
172
- console.log(chalk.gray(` footprint desconhecido (sem outputs): ${iso.unknownFootprint.join(', ')}`));
173
- }
174
- }
175
-
176
- console.log('\n' + '━'.repeat(60) + '\n');
177
- } catch (error) {
178
- console.error(chalk.red(`\n❌ Error analyzing DAG: ${error.message}\n`));
179
- process.exit(1);
180
- }
181
- }
@@ -1,76 +0,0 @@
1
- /**
2
- * output-collector — Collect phase output files for multi-source agent detection
3
- *
4
- * Reads all existing phase outputs for a feature and returns them as
5
- * weighted TextSource[] for use in detectAgents().
6
- */
7
-
8
- import { readFileSync, existsSync } from 'fs';
9
- import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
10
-
11
- /**
12
- * Default source weights per output type.
13
- * Keys match OUTPUT_SCHEMA keys (camelCase).
14
- */
15
- export const SOURCE_WEIGHTS = {
16
- proposal: 1.0,
17
- stackScan: 0.8,
18
- spec: 1.5,
19
- tasks: 1.3,
20
- decisions: 1.0,
21
- uiDesignSystem: 1.0,
22
- uiMockups: 1.0,
23
- uiComponents: 1.0,
24
- uiFlows: 1.0,
25
- recap: 1.0,
26
- reviewReport: 1.0,
27
- };
28
-
29
- /** Phase order for sorting collected outputs (v5 NLH model) */
30
- const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
31
-
32
- /**
33
- * Collect all existing phase output files for a feature.
34
- *
35
- * @param {string} projectRoot - Absolute path to project root
36
- * @param {string} featureName - Feature name
37
- * @returns {Array<{source: string, content: string, weight: number}>}
38
- */
39
- export function collectPhaseOutputs(projectRoot, featureName) {
40
- const results = [];
41
-
42
- for (const [key, entry] of Object.entries(OUTPUT_SCHEMA)) {
43
- // Multi-file types resolve to a DIRECTORY — there is no single document to
44
- // weigh as one source. Skipped explicitly rather than left to fail as an
45
- // EISDIR swallowed by the catch below.
46
- if (entry.multi) continue;
47
-
48
- const absPath = getAbsoluteOutputPath(projectRoot, featureName, key);
49
- if (!existsSync(absPath)) continue;
50
-
51
- try {
52
- const content = readFileSync(absPath, 'utf8');
53
- if (!content.trim()) continue; // Skip empty files
54
-
55
- results.push({
56
- source: entry.filename,
57
- content,
58
- weight: SOURCE_WEIGHTS[key] ?? 1.0,
59
- _phase: entry.phase,
60
- });
61
- } catch {
62
- // Skip unreadable files — fail-open
63
- continue;
64
- }
65
- }
66
-
67
- // Sort by phase order
68
- results.sort((a, b) => {
69
- const orderA = PHASE_ORDER.indexOf(a._phase);
70
- const orderB = PHASE_ORDER.indexOf(b._phase);
71
- return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB);
72
- });
73
-
74
- // Remove internal _phase field
75
- return results.map(({ source, content, weight }) => ({ source, content, weight }));
76
- }