@polymorphism-tech/morph-spec 4.8.18 → 4.9.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 (138) hide show
  1. package/CLAUDE.md +98 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +15 -56
  4. package/bin/task-manager.js +115 -14
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +21 -0
  10. package/framework/agents.json +758 -164
  11. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  12. package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
  13. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
  14. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
  15. package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
  16. package/framework/hooks/claude-code/statusline.py +76 -30
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  18. package/framework/hooks/shared/activity-logger.js +0 -24
  19. package/framework/hooks/shared/phase-utils.js +3 -0
  20. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  21. package/framework/hooks/shared/stale-task-reset.js +57 -0
  22. package/framework/hooks/shared/state-reader.js +2 -2
  23. package/framework/hooks/shared/worktree-helpers.js +53 -0
  24. package/framework/phases.json +40 -8
  25. package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
  26. package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
  27. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
  28. package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  30. package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
  31. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  32. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  33. package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
  34. package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
  42. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
  43. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
  44. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
  45. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
  46. package/framework/standards/STANDARDS.json +640 -88
  47. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  48. package/framework/templates/REGISTRY.json +1825 -1909
  49. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  50. package/framework/templates/docs/onboarding.md +1 -5
  51. package/framework/workflows/configs/nodejs-cli.json +40 -0
  52. package/package.json +2 -6
  53. package/src/commands/agents/dispatch-agents.js +55 -4
  54. package/src/commands/project/doctor.js +16 -47
  55. package/src/commands/project/init.js +1 -1
  56. package/src/commands/project/status.js +2 -2
  57. package/src/commands/project/update.js +381 -365
  58. package/src/commands/project/worktree.js +154 -0
  59. package/src/commands/state/advance-phase.js +120 -30
  60. package/src/commands/state/approve.js +2 -2
  61. package/src/commands/state/index.js +7 -8
  62. package/src/commands/state/phase-runner.js +1 -1
  63. package/src/commands/state/state.js +61 -6
  64. package/src/commands/tasks/task.js +78 -99
  65. package/src/commands/templates/template-render.js +93 -173
  66. package/src/commands/trust/trust.js +26 -21
  67. package/src/core/paths/output-schema.js +15 -0
  68. package/src/core/state/state-manager.js +28 -54
  69. package/src/core/workflows/workflow-detector.js +9 -87
  70. package/src/lib/phase-chain/phase-validator.js +330 -0
  71. package/src/lib/stack/stack-profile.js +88 -0
  72. package/src/lib/tasks/task-classifier.js +16 -0
  73. package/src/lib/tasks/test-runner.js +77 -0
  74. package/src/lib/trust/trust-manager.js +32 -144
  75. package/src/lib/validators/spec-validator.js +58 -4
  76. package/src/lib/validators/validation-runner.js +23 -11
  77. package/src/scripts/setup-infra.js +240 -224
  78. package/src/utils/agents-installer.js +2 -2
  79. package/src/utils/banner.js +1 -1
  80. package/src/utils/claude-settings-manager.js +1 -1
  81. package/src/utils/file-copier.js +1 -0
  82. package/src/utils/hooks-installer.js +258 -8
  83. package/framework/hooks/dev/check-sync-health.js +0 -117
  84. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  85. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  86. package/framework/hooks/dev/sync-template-registry.js +0 -60
  87. package/framework/hooks/dev/validate-skill-format.js +0 -70
  88. package/framework/hooks/dev/validate-standard-format.js +0 -73
  89. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  90. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  91. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  92. package/framework/workflows/configs/design-impl.json +0 -49
  93. package/framework/workflows/configs/express.json +0 -45
  94. package/framework/workflows/configs/fast-track.json +0 -42
  95. package/framework/workflows/configs/full-morph.json +0 -79
  96. package/framework/workflows/configs/fusion.json +0 -39
  97. package/framework/workflows/configs/long-running.json +0 -33
  98. package/framework/workflows/configs/spec-only.json +0 -43
  99. package/framework/workflows/configs/ui-refresh.json +0 -49
  100. package/framework/workflows/configs/zero-touch.json +0 -82
  101. package/src/commands/project/monitor.js +0 -295
  102. package/src/commands/project/tutorial.js +0 -115
  103. package/src/commands/state/validate-phase.js +0 -238
  104. package/src/commands/templates/generate-contracts.js +0 -445
  105. package/src/core/orchestrator.js +0 -171
  106. package/src/core/registry/command-registry.js +0 -28
  107. package/src/core/registry/index.js +0 -8
  108. package/src/core/registry/validator-registry.js +0 -204
  109. package/src/core/templates/template-validator.js +0 -296
  110. package/src/generator/config-generator.js +0 -206
  111. package/src/generator/templates/config.json.template +0 -40
  112. package/src/generator/templates/project.md.template +0 -67
  113. package/src/lib/agents/micro-agent-factory.js +0 -161
  114. package/src/lib/analysis/complexity-analyzer.js +0 -441
  115. package/src/lib/analysis/index.js +0 -7
  116. package/src/lib/analytics/analytics-engine.js +0 -345
  117. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  118. package/src/lib/checkpoints/index.js +0 -7
  119. package/src/lib/context/context-bundler.js +0 -241
  120. package/src/lib/context/context-optimizer.js +0 -212
  121. package/src/lib/context/context-tracker.js +0 -273
  122. package/src/lib/context/core-four-tracker.js +0 -201
  123. package/src/lib/context/mcp-optimizer.js +0 -200
  124. package/src/lib/execution/fusion-executor.js +0 -304
  125. package/src/lib/execution/parallel-executor.js +0 -270
  126. package/src/lib/hooks/stop-hook-executor.js +0 -286
  127. package/src/lib/hops/hop-composer.js +0 -221
  128. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  129. package/src/lib/threads/thread-coordinator.js +0 -238
  130. package/src/lib/threads/thread-manager.js +0 -317
  131. package/src/lib/tracking/artifact-trail.js +0 -202
  132. package/src/scanner/project-scanner.js +0 -242
  133. package/src/ui/diff-display.js +0 -91
  134. package/src/ui/interactive-wizard.js +0 -96
  135. package/src/ui/user-review.js +0 -211
  136. package/src/ui/wizard-questions.js +0 -188
  137. package/src/utils/color-utils.js +0 -70
  138. package/src/utils/process-handler.js +0 -97
package/CLAUDE.md ADDED
@@ -0,0 +1,98 @@
1
+ # MORPH-SPEC Runtime Instructions
2
+
3
+ > by Polymorphism Tech — Spec-driven development for .NET/Blazor/Next.js/Azure
4
+
5
+ ---
6
+
7
+ ## Project Context
8
+
9
+ @.morph/context/README.md
10
+
11
+ ---
12
+
13
+ ## Critical Rules
14
+
15
+ **NEVER:**
16
+ - Skip to code without a specification
17
+ - Implement without design approval
18
+ - Ignore standards in `.morph/framework/standards/`
19
+ - Create infrastructure manually
20
+ - Generate code without defined contracts
21
+ - List questions as plain text — always use the `AskUserQuestion` tool
22
+
23
+ **ALWAYS:**
24
+ - Follow the mandatory phases
25
+ - Generate outputs in `.morph/features/{feature}/`
26
+ - Document decisions in `decisions.md`
27
+ - Checkpoint every 3 implemented tasks
28
+ - Use Infrastructure as Code
29
+ - Use `AskUserQuestion` tool whenever asking the user anything (1–4 questions per call; split into sequential calls if more)
30
+
31
+ ---
32
+
33
+ ## Quick Reference
34
+
35
+ | Command | Purpose |
36
+ |---------|---------|
37
+ | `/morph-proposal {feature}` | Full spec pipeline (phases 1–4, pauses for approval) |
38
+ | `/morph-apply {feature}` | Implement feature (phase 5) |
39
+ | `/morph-status` | Feature status dashboard |
40
+ | `/morph-preflight` | Pre-implementation validation |
41
+
42
+ ---
43
+
44
+ ## State & Outputs
45
+
46
+ | Path | Notes |
47
+ |------|-------|
48
+ | `.morph/state.json` | **READ-ONLY** — use `morph-spec` CLI to update |
49
+ | `.morph/features/{feature}/{phase}/` | Feature outputs organized by phase |
50
+ | `.morph/framework/` | **READ-ONLY** — framework files managed by morph-spec |
51
+ | `.morph/config/config.json` | Project configuration (editable) |
52
+
53
+ ### mark-output types
54
+
55
+ Use `morph-spec state mark-output <feature> <type>` with one of these exact type names:
56
+
57
+ | Type | Phase | kebab alias |
58
+ |------|-------|-------------|
59
+ | `proposal` | proposal | — |
60
+ | `schemaAnalysis` | design | `schema-analysis` |
61
+ | `spec` | design | — |
62
+ | `contracts` | design | — |
63
+ | `contractsVsa` | design | `contracts-vsa` |
64
+ | `decisions` | design | — |
65
+ | `clarifications` | clarify | — |
66
+ | `tasks` | tasks | — |
67
+ | `uiDesignSystem` | uiux | `ui-design-system` |
68
+ | `uiMockups` | uiux | `ui-mockups` |
69
+ | `uiComponents` | uiux | `ui-components` |
70
+ | `uiFlows` | uiux | `ui-flows` |
71
+ | `recap` | implement | — |
72
+ ---
73
+
74
+ ## Phase Sequence
75
+
76
+ ```
77
+ proposal → setup → [uiux] → design → clarify → tasks → implement → [sync]
78
+ ```
79
+
80
+ Use `morph-spec status {feature}` to see current phase and pending approval gates.
81
+
82
+ ---
83
+
84
+ ## Agents
85
+
86
+ Tier-1 and tier-2 MORPH agents are available as native subagents in `.claude/agents/`.
87
+ They can be invoked directly by Claude Code during multi-agent workflows.
88
+
89
+ ---
90
+
91
+ ## Context Window Tip
92
+
93
+ When using 3+ MCPs, add `"experimental": { "mcpCliMode": true }` to `.claude/settings.json`.
94
+ MCP tools load on-demand instead of all at startup — keeps context clean for actual work.
95
+
96
+ ---
97
+
98
+ *MORPH-SPEC by Polymorphism Tech*
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 4.8.18
6
+ **Version:** 4.9.0
7
7
  **Requires:** Node.js 18+, Claude Code
8
8
 
9
9
  ---
@@ -376,4 +376,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
376
376
 
377
377
  ---
378
378
 
379
- *morph-spec v4.8.18 by [Polymorphism Tech](https://polymorphism.tech)*
379
+ *morph-spec v4.9.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -12,15 +12,13 @@ import { setupInfraCommand } from '../src/commands/project/setup-infra-cmd.js';
12
12
  import { installPluginCommand } from '../src/commands/project/install-plugin-cmd.js';
13
13
  import { updateCommand } from '../src/commands/project/update.js';
14
14
  import { doctorCommand } from '../src/commands/project/doctor.js';
15
- import { tutorialCommand } from '../src/commands/project/tutorial.js';
16
15
 
17
16
  import { statusCommand } from '../src/commands/project/status.js';
18
- import { monitorCommand } from '../src/commands/project/monitor.js';
19
- import { checkpointSaveCommand, checkpointRestoreCommand, checkpointListCommand } from '../src/commands/project/checkpoint.js';
17
+ import { worktreeSetupCommand } from '../src/commands/project/worktree.js';
18
+ import { checkpointSaveCommand } from '../src/commands/project/checkpoint.js';
20
19
 
21
20
  // State commands
22
21
  import { stateCommand } from '../src/commands/state/state.js';
23
- import { validatePhaseCommand } from '../src/commands/state/validate-phase.js';
24
22
  import { advancePhaseCommand } from '../src/commands/state/advance-phase.js';
25
23
  import { approveCommand, approvalStatusCommand, unapproveCommand } from '../src/commands/state/approve.js';
26
24
  import { phaseRunCommand } from '../src/commands/state/phase-runner.js';
@@ -29,7 +27,7 @@ import { phaseRunCommand } from '../src/commands/state/phase-runner.js';
29
27
  import { dispatchAgentsCommand } from '../src/commands/agents/dispatch-agents.js';
30
28
 
31
29
  // Task commands
32
- import { taskDoneCommand, taskStartCommand, taskNextCommand, taskBulkDoneCommand } from '../src/commands/tasks/task.js';
30
+ import { taskDoneCommand, taskStartCommand, taskNextCommand } from '../src/commands/tasks/task.js';
33
31
 
34
32
  // Validation commands
35
33
  import { validateCommand } from './validate.js';
@@ -37,8 +35,6 @@ import { validateFeatureCommand } from '../src/commands/validation/validate-feat
37
35
 
38
36
  // Template commands
39
37
  import { templateRenderCommand } from '../src/commands/templates/template-render.js';
40
- import { generateContractsCommand } from '../src/commands/templates/generate-contracts.js';
41
-
42
38
  // MCP commands
43
39
  import { mcpSetupCommand } from '../src/commands/mcp/mcp-setup.js';
44
40
 
@@ -100,11 +96,6 @@ program
100
96
  .option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
101
97
  .action(doctorCommand);
102
98
 
103
- program
104
- .command('tutorial')
105
- .description('Learn the MORPH-SPEC workflow — phase pipeline and getting started')
106
- .action(tutorialCommand);
107
-
108
99
  program
109
100
  .command('status <feature>')
110
101
  .description('Show comprehensive feature status dashboard')
@@ -112,18 +103,22 @@ program
112
103
  .option('-v, --verbose', 'Show detailed output')
113
104
  .action(statusCommand);
114
105
 
115
- program
116
- .command('monitor [feature]')
117
- .description('Live TUI dashboard — hooks, agents, skills, rules in real time')
118
- .option('--compact', 'Minimal single-view output')
119
- .option('--mode <mode>', 'Start view: overview|hooks|agents|skills|rules', 'overview')
120
- .action(monitorCommand);
106
+ // Worktree commands
107
+ const worktreeCommand = program
108
+ .command('worktree')
109
+ .description('Manage git worktrees for feature isolation');
110
+
111
+ worktreeCommand
112
+ .command('setup <feature>')
113
+ .description('Create a worktree at .worktrees/{feature}/ on branch morph/{feature}')
114
+ .option('--fresh', 'Create a new dated branch even if one already exists')
115
+ .action((feature, options) => worktreeSetupCommand(feature, options));
121
116
 
122
117
  // State management commands
123
118
  program
124
119
  .command('state <action> [args...]')
125
120
  .description('Manage state.json (init | get | set | list | add-agent | remove-agent | mark-output)')
126
- .option('--force', 'Force overwrite (init command)')
121
+ .option('--force', 'Force overwrite (init) or bypass phase protection gates (set phase)')
127
122
  .option('--project <name>', 'Project name (init command)')
128
123
  .option('--type <type>', 'Project type (init command)')
129
124
  .option('--json', 'Output as JSON (get command)')
@@ -141,16 +136,6 @@ taskCommand
141
136
  .option('--dry-run', 'Show validation results without marking tasks as complete')
142
137
  .action((feature, taskIds, options) => taskDoneCommand(feature, taskIds, options));
143
138
 
144
- taskCommand
145
- .command('bulk-done <feature> [range]')
146
- .description('Bulk-complete tasks (--all | --from T001 --to T053 | T001..T082)')
147
- .option('--all', 'Complete all pending tasks')
148
- .option('--from <id>', 'Start task ID for range')
149
- .option('--to <id>', 'End task ID for range')
150
- .option('--skip-validation', 'Skip code validation (not recommended)')
151
- .option('--dry-run', 'Show validation results without marking tasks as complete')
152
- .action((feature, range, options) => taskBulkDoneCommand(feature, range, options));
153
-
154
139
  taskCommand
155
140
  .command('start <feature> <task-id>')
156
141
  .description('Start a task (mark as in_progress)')
@@ -175,14 +160,6 @@ generateCommand
175
160
  await generateRecap('.', feature, options);
176
161
  });
177
162
 
178
- generateCommand
179
- .command('contracts <feature>')
180
- .description('Generate contracts.cs from schema-analysis.md using real field names and types')
181
- .option('--dry-run', 'Preview output without writing file')
182
- .option('--output <path>', 'Override output path for contracts.cs')
183
- .option('-v, --verbose', 'Show stack trace on error')
184
- .action((feature, options) => generateContractsCommand(feature, options));
185
-
186
163
  // Validation commands (Sprint 4: Continuous Validation)
187
164
  program
188
165
  .command('validate [validator]')
@@ -219,13 +196,6 @@ phaseCommand
219
196
  .option('--skip-approval', 'Skip approval gate checks')
220
197
  .action((feature, options) => phaseRunCommand(feature, options));
221
198
 
222
- // Phase validation command (also available as standalone)
223
- program
224
- .command('validate-phase <feature> <phase>')
225
- .description('Validate prerequisites before advancing to a phase')
226
- .option('-v, --verbose', 'Show detailed output')
227
- .action(validatePhaseCommand);
228
-
229
199
  // Feature validation command (content-aware)
230
200
  program
231
201
  .command('validate-feature <feature>')
@@ -243,17 +213,6 @@ program
243
213
  .option('--label <label>', 'Custom label for checkpoint')
244
214
  .action(checkpointSaveCommand);
245
215
 
246
- program
247
- .command('checkpoint-restore <feature> [checkpoint]')
248
- .description('Restore feature from a checkpoint (latest if no name given)')
249
- .action(checkpointRestoreCommand);
250
-
251
- program
252
- .command('checkpoint-list <feature>')
253
- .description('List all checkpoints for a feature')
254
- .option('--json', 'Output as JSON')
255
- .action(checkpointListCommand);
256
-
257
216
  // Approval workflow commands
258
217
  program
259
218
  .command('approve <feature> <gate>')
@@ -300,7 +259,7 @@ trustCommand
300
259
 
301
260
  trustCommand
302
261
  .command('set <feature> <level> [reason]')
303
- .description('Manually set trust level (low|medium|high|maximum)')
262
+ .description('Manually set trust level (manual|high|auto)')
304
263
  .action(trustSetCommand);
305
264
 
306
265
  // ─────────────────────────────────────────────────────────────────────────────
@@ -15,6 +15,8 @@ import chalk from 'chalk';
15
15
 
16
16
  import { loadState, saveState } from '../src/core/state/state-manager.js';
17
17
  import { parseTasksMd, ensureTaskList, syncCounters } from '../src/lib/tasks/task-parser.js';
18
+ import { isTestTask } from '../src/lib/tasks/task-classifier.js';
19
+ import { runTestSuite } from '../src/lib/tasks/test-runner.js';
18
20
 
19
21
  const __filename = fileURLToPath(import.meta.url);
20
22
  const __dirname = dirname(__filename);
@@ -96,6 +98,8 @@ class TaskManager {
96
98
  * @param {string[]} taskIds - Task IDs to complete
97
99
  * @param {Object} options - Options
98
100
  * @param {boolean} options.skipValidation - Skip code validation
101
+ * @param {boolean} options.dryRun - Dry run (no state changes)
102
+ * @param {Function} [options._runTestSuite] - Inject a mock runTestSuite for testing
99
103
  */
100
104
  async completeTasks(featureName, taskIds, options = {}) {
101
105
  const state = loadState();
@@ -142,6 +146,12 @@ class TaskManager {
142
146
  tasksToComplete.push(task);
143
147
  }
144
148
 
149
+ // ─── Test Task Guard ────────────────────────────────────────────────────────
150
+ // When any of the tasks being completed is a test/validation task (e.g.
151
+ // "Testes e Validação"), run the project's test suite BEFORE code validation.
152
+ // The guard is skipped when --skip-validation or --dry-run is active.
153
+ runTestGuard(tasksToComplete, process.cwd(), options);
154
+
145
155
  // Run validation BEFORE marking tasks as complete
146
156
  if (tasksToComplete.length > 0 && !options.skipValidation) {
147
157
  const validationResult = await this.runValidation(featureName);
@@ -183,26 +193,32 @@ class TaskManager {
183
193
  process.exit(1);
184
194
  }
185
195
 
186
- // Validation passed — mark any pending history as passed
187
- const successState = loadState();
188
- const successFeature = successState.features[featureName];
189
- if (successFeature) {
196
+ // Validation passed — mark any pending history as passed.
197
+ // Update in-place on `feature` (same object as state.features[featureName]) so
198
+ // the single saveState(state) at the end of the function persists everything
199
+ // atomically. A separate saveState here would trigger a second renameSync on
200
+ // Windows, risking EPERM and leaving state.json without the task completions
201
+ // (the count bug), and would also be clobbered by the final save anyway.
202
+ if (feature.validationHistory) {
190
203
  for (const task of tasksToComplete) {
191
- if (successFeature.validationHistory?.[task.id]) {
192
- successFeature.validationHistory[task.id].status = 'passed';
193
- successFeature.validationHistory[task.id].updatedAt = new Date().toISOString();
204
+ if (feature.validationHistory[task.id]) {
205
+ feature.validationHistory[task.id].status = 'passed';
206
+ feature.validationHistory[task.id].updatedAt = new Date().toISOString();
194
207
  }
195
208
  }
196
- saveState(successState);
197
209
  }
198
210
  } else if (options.dryRun) {
199
211
  console.log(chalk.cyan('\n ℹ️ Dry-run — tasks NOT marked as complete (validation skipped)'));
200
212
  return [];
213
+ } else if (shouldLogSkipValidationWarning(tasksToComplete, options.skipValidation, options.dryRun)) {
214
+ console.log(chalk.yellow('\n⚠ --skip-validation: Tier-4 code validators bypassed — tasks will be marked complete without validation'));
215
+ console.log(chalk.gray(' Re-run without --skip-validation before committing.\n'));
201
216
  }
202
217
 
203
218
  // Breaking change detection (non-blocking warning)
204
219
  if (tasksToComplete.length > 0) {
205
- const breakingChanges = await detectBreakingChanges();
220
+ const _detectBreaking = options._detectBreakingChanges ?? detectBreakingChanges;
221
+ const breakingChanges = await _detectBreaking();
206
222
  if (breakingChanges && breakingChanges.length > 0) {
207
223
  console.log(chalk.yellow('\n⚠️ BREAKING CHANGE DETECTION:'));
208
224
  console.log(chalk.yellow(' Removed exports with active consumers found — review before completing:\n'));
@@ -343,7 +359,7 @@ class TaskManager {
343
359
  // This is fail-open by design: a broken validator shouldn't block commits.
344
360
  // Common cause: missing optional deps.
345
361
  console.log(chalk.yellow(`\n⚠️ Validation skipped (${error.message})`));
346
- console.log(chalk.gray(' Run manually: npx morph-spec validate --verbose'));
362
+ console.log(chalk.gray(` Run manually: npx morph-spec validate-feature ${featureName}`));
347
363
  return { passed: true, validators: {}, passRate: 1.0 };
348
364
  }
349
365
  }
@@ -408,7 +424,8 @@ class TaskManager {
408
424
  id: task.checkpoint,
409
425
  timestamp: new Date().toISOString(),
410
426
  tasksCompleted: [task.id],
411
- note: `Checkpoint: ${task.title}`
427
+ note: `Checkpoint: ${task.title}`,
428
+ passed: true // reaching a named checkpoint means the task completed successfully
412
429
  };
413
430
 
414
431
  feature.checkpoints = feature.checkpoints || [];
@@ -469,6 +486,7 @@ class TaskManager {
469
486
  timestamp: new Date().toISOString(),
470
487
  tasksCompleted: tasks.map(t => t.id),
471
488
  note: `Auto-checkpoint: ${tasks.length} tasks completed${validationNote}`,
489
+ passed: checkpointResult?.passed ?? true, // null checkpointResult means no validator ran → assume passed
472
490
  validationResults: checkpointResult?.results || null
473
491
  };
474
492
 
@@ -542,12 +560,27 @@ class TaskManager {
542
560
  }
543
561
 
544
562
  /**
545
- * Get next pending task (based on dependencies)
563
+ * Get next available task (based on dependencies).
564
+ * Stale in_progress tasks (older than 1 hour, or missing startedAt) are
565
+ * treated as effectively pending and included as candidates.
546
566
  */
547
567
  getNextTask(tasks) {
548
- const pending = tasks.filter(t => t.status === 'pending');
568
+ const STALE_MS = 60 * 60 * 1000;
569
+ const now = Date.now();
570
+
571
+ // Include stale in_progress tasks as effectively pending candidates
572
+ const isEffectivelyAvailable = (t) => {
573
+ if (t.status === 'pending') return true;
574
+ if (t.status === 'in_progress') {
575
+ const age = t.startedAt ? now - new Date(t.startedAt).getTime() : Infinity;
576
+ return age > STALE_MS;
577
+ }
578
+ return false;
579
+ };
549
580
 
550
- for (const task of pending) {
581
+ const candidates = tasks.filter(isEffectivelyAvailable);
582
+
583
+ for (const task of candidates) {
551
584
  const missingDeps = this.checkDependencies(task, tasks);
552
585
  if (missingDeps.length === 0) {
553
586
  return task;
@@ -610,6 +643,21 @@ class TaskManager {
610
643
  throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
611
644
  }
612
645
 
646
+ // Reset any sibling tasks stuck in in_progress from a previous session.
647
+ // Only tasks OTHER than the one being started, and only if stale (>1h).
648
+ const STALE_MS = 60 * 60 * 1000;
649
+ const now = Date.now();
650
+ for (const t of taskList) {
651
+ if (t.id !== taskId && t.status === 'in_progress') {
652
+ const age = t.startedAt ? now - new Date(t.startedAt).getTime() : Infinity;
653
+ if (age > STALE_MS) {
654
+ t.status = 'pending';
655
+ delete t.startedAt;
656
+ console.log(chalk.yellow(`⚠️ Task ${t.id} auto-reset to pending (stale from previous session)`));
657
+ }
658
+ }
659
+ }
660
+
613
661
  task.status = 'in_progress';
614
662
  task.startedAt = new Date().toISOString();
615
663
  syncCounters(feature);
@@ -800,4 +848,57 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
800
848
  main();
801
849
  }
802
850
 
851
+ /**
852
+ * Pure predicate: should a --skip-validation bypass warning be logged?
853
+ *
854
+ * @param {Array} tasksToComplete - Tasks that will be marked complete
855
+ * @param {boolean} skipValidation - Whether --skip-validation flag is active
856
+ * @param {boolean} dryRun - Whether --dry-run is active (has its own message)
857
+ * @returns {boolean}
858
+ */
859
+ export function shouldLogSkipValidationWarning(tasksToComplete, skipValidation, dryRun) {
860
+ return tasksToComplete.length > 0 && !!skipValidation && !dryRun;
861
+ }
862
+
863
+ /**
864
+ * Test Task Guard — run project test suite before completing test-keyword tasks.
865
+ *
866
+ * Extracted as a pure, injectable function so it can be unit-tested without
867
+ * spawning a full TaskManager or writing state to disk.
868
+ *
869
+ * @param {Array} tasksToComplete - Tasks about to be marked complete
870
+ * @param {string} projectPath - Project root (process.cwd() in production)
871
+ * @param {object} [opts]
872
+ * @param {boolean} [opts.skipValidation] - Skip the guard entirely
873
+ * @param {boolean} [opts.dryRun] - Skip the guard entirely
874
+ * @param {Function} [opts._runTestSuite] - Override runTestSuite for testing
875
+ * @param {Function} [opts._exit] - Override process.exit for testing
876
+ * @returns {void} Calls _exit/process.exit(1) on failure; returns normally otherwise
877
+ */
878
+ export function runTestGuard(tasksToComplete, projectPath, opts = {}) {
879
+ if (!tasksToComplete.length || opts.skipValidation || opts.dryRun) return;
880
+
881
+ const _runTS = opts._runTestSuite ?? runTestSuite;
882
+ const hasTestTask = tasksToComplete.some(t => isTestTask(t.title));
883
+ if (!hasTestTask) return;
884
+
885
+ const suiteResult = _runTS(projectPath);
886
+
887
+ if (suiteResult.passed === false) {
888
+ console.error(chalk.red('\n❌ Test suite FAILED — tasks NOT marked as complete'));
889
+ if (suiteResult.output) {
890
+ console.error(chalk.gray(suiteResult.output.slice(-2000)));
891
+ }
892
+ console.log(chalk.gray(' Fix failing tests, then run task done again'));
893
+ console.log(chalk.gray(' Or use --skip-validation to bypass (not recommended)\n'));
894
+ (opts._exit ?? process.exit)(1);
895
+ } else if (suiteResult.skipped) {
896
+ console.log(chalk.yellow('\n⚠️ Test suite skipped — no testCommand configured'));
897
+ console.log(chalk.gray(` Reason: ${suiteResult.reason}`));
898
+ console.log(chalk.gray(' Set project.testCommand in .morph/config/config.json to enable auto-run\n'));
899
+ } else {
900
+ console.log(chalk.green('\n✅ Test suite passed'));
901
+ }
902
+ }
903
+
803
904
  export default TaskManager;
package/bin/validate.js CHANGED
@@ -3,46 +3,75 @@
3
3
  /**
4
4
  * Validation CLI
5
5
  *
6
- * Runs all validators (package, architecture, contrast) on the project.
7
- * Part of Sprint 4: Continuous Validation
6
+ * Runs validators for the current project stack (detected from .morph/config/config.json).
7
+ *
8
+ * .NET/Blazor stacks: packages, architecture, contrast, spec-tasks
9
+ * Next.js stack: next-components, spec-tasks
10
+ * Other stacks: spec-tasks
8
11
  *
9
12
  * Usage:
10
13
  * morph-spec validate [options]
11
- * morph-spec validate packages
12
- * morph-spec validate architecture
13
- * morph-spec validate contrast
14
14
  * morph-spec validate all
15
+ * morph-spec validate next-components (Next.js projects)
16
+ * morph-spec validate packages (.NET projects)
15
17
  *
16
18
  * MORPH-SPEC 3.0
17
19
  */
18
20
 
19
21
  import chalk from 'chalk';
20
- import { validatePackages } from '../src/lib/validators/packages/package-validator.js';
21
- import { validateArchitecture } from '../src/lib/validators/architecture/architecture-validator.js';
22
- import { validateContrast } from '../src/lib/validators/ui/ui-contrast-validator.js';
23
-
24
- const VALIDATORS = {
25
- packages: {
26
- name: 'Package Compatibility',
27
- description: 'Validates NuGet package versions against .NET compatibility matrix',
28
- run: validatePackages
29
- },
30
- architecture: {
31
- name: 'Architecture Patterns',
32
- description: 'Detects DI anti-patterns and lifecycle issues in Blazor',
33
- run: validateArchitecture
34
- },
35
- contrast: {
36
- name: 'UI Contrast (WCAG)',
37
- description: 'Validates color contrast ratios for accessibility',
38
- run: validateContrast
39
- },
40
- 'spec-tasks': {
22
+ import { getStackProfile } from '../src/lib/stack/stack-profile.js';
23
+
24
+ /**
25
+ * Build the VALIDATORS registry for the current stack.
26
+ * .NET stacks → packages, architecture, contrast, spec-tasks
27
+ * nextjs stack → next-components, spec-tasks
28
+ * other stacks → spec-tasks only
29
+ */
30
+ async function buildValidators(stack) {
31
+ const validators = {};
32
+
33
+ const isDotnet = !stack || ['dotnet', 'blazor', 'aspnet', 'dotnet-blazor'].includes(stack.toLowerCase());
34
+ const isNextjs = stack && ['nextjs', 'next', 'next.js'].includes(stack.toLowerCase());
35
+
36
+ if (isDotnet) {
37
+ const { validatePackages } = await import('../src/lib/validators/packages/package-validator.js');
38
+ const { validateArchitecture } = await import('../src/lib/validators/architecture/architecture-validator.js');
39
+ const { validateContrast } = await import('../src/lib/validators/ui/ui-contrast-validator.js');
40
+
41
+ validators.packages = {
42
+ name: 'Package Compatibility',
43
+ description: 'Validates NuGet package versions against .NET compatibility matrix',
44
+ run: validatePackages
45
+ };
46
+ validators.architecture = {
47
+ name: 'Architecture Patterns',
48
+ description: 'Detects DI anti-patterns and lifecycle issues in Blazor',
49
+ run: validateArchitecture
50
+ };
51
+ validators.contrast = {
52
+ name: 'UI Contrast (WCAG)',
53
+ description: 'Validates color contrast ratios for accessibility',
54
+ run: validateContrast
55
+ };
56
+ }
57
+
58
+ if (isNextjs) {
59
+ const { validateNextComponentFiles } = await import('../src/lib/validators/nextjs/next-component-validator.js');
60
+ validators['next-components'] = {
61
+ name: 'Next.js Components',
62
+ description: "Validates 'use client' directives, hook usage, and file naming conventions",
63
+ run: (projectPath, opts) => validateNextComponentFiles(projectPath, opts)
64
+ };
65
+ }
66
+
67
+ validators['spec-tasks'] = {
41
68
  name: 'Spec vs Tasks Coverage',
42
69
  description: 'Validates that tasks.json covers all requirements from spec.md',
43
70
  run: validateSpecVsTasks
44
- }
45
- };
71
+ };
72
+
73
+ return validators;
74
+ }
46
75
 
47
76
  /**
48
77
  * Validate spec.md requirements are covered by tasks
@@ -147,12 +176,18 @@ export async function validateCommand(args = []) {
147
176
  const validatorName = args[0] || 'all';
148
177
  const options = parseOptions(args);
149
178
 
179
+ const { stack } = getStackProfile();
180
+ const VALIDATORS = await buildValidators(stack);
181
+
150
182
  console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Validator\n'));
183
+ if (stack) {
184
+ console.log(chalk.gray(`Stack: ${stack}\n`));
185
+ }
151
186
 
152
187
  if (validatorName === 'all') {
153
- await runAllValidators(options);
188
+ await runAllValidators(VALIDATORS, options);
154
189
  } else if (VALIDATORS[validatorName]) {
155
- await runValidator(validatorName, options);
190
+ await runValidator(VALIDATORS, validatorName, options);
156
191
  } else {
157
192
  console.error(chalk.red(`❌ Unknown validator: ${validatorName}`));
158
193
  console.log(chalk.gray('\nAvailable validators:'));
@@ -162,13 +197,12 @@ export async function validateCommand(args = []) {
162
197
  console.log(chalk.gray(' - all: Run all validators'));
163
198
  process.exit(1);
164
199
  }
165
-
166
200
  }
167
201
 
168
202
  /**
169
203
  * Run all validators
170
204
  */
171
- async function runAllValidators(options) {
205
+ async function runAllValidators(VALIDATORS, options) {
172
206
  const results = {};
173
207
  let totalErrors = 0;
174
208
  let totalWarnings = 0;
@@ -230,7 +264,7 @@ async function runAllValidators(options) {
230
264
  /**
231
265
  * Run single validator
232
266
  */
233
- async function runValidator(name, options) {
267
+ async function runValidator(VALIDATORS, name, options) {
234
268
  const validator = VALIDATORS[name];
235
269
 
236
270
  console.log(chalk.cyan(`▸ ${validator.name}`));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "4.8.18",
3
+ "version": "4.9.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",