@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.
- package/CLAUDE.md +98 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +15 -56
- package/bin/task-manager.js +115 -14
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +21 -0
- package/framework/agents.json +758 -164
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +2 -2
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +40 -8
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
- package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +1 -5
- package/framework/workflows/configs/nodejs-cli.json +40 -0
- package/package.json +2 -6
- package/src/commands/agents/dispatch-agents.js +55 -4
- package/src/commands/project/doctor.js +16 -47
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/state/advance-phase.js +120 -30
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +15 -0
- package/src/core/state/state-manager.js +28 -54
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/phase-chain/phase-validator.js +330 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +240 -224
- package/src/utils/agents-installer.js +2 -2
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +258 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/templates/template-validator.js +0 -296
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- 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.
|
|
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.
|
|
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 {
|
|
19
|
-
import { checkpointSaveCommand
|
|
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
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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 (
|
|
262
|
+
.description('Manually set trust level (manual|high|auto)')
|
|
304
263
|
.action(trustSetCommand);
|
|
305
264
|
|
|
306
265
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/bin/task-manager.js
CHANGED
|
@@ -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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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 (
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
7
|
-
*
|
|
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 {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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}`));
|
package/claude-plugin.json
CHANGED