@polymorphism-tech/morph-spec 4.8.19 → 4.10.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 +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- 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 +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- 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/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- 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 +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- 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/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- 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/task/expand.js +100 -0
- 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 +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -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/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- 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/index.js +0 -8
- 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/index.js +0 -10
- 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/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- 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/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- 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/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- 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/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- 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/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
package/CLAUDE.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- Ignore standards in `.morph/framework/standards/`
|
|
19
19
|
- Create infrastructure manually
|
|
20
20
|
- Generate code without defined contracts
|
|
21
|
+
- List questions as plain text — always use the `AskUserQuestion` tool
|
|
21
22
|
|
|
22
23
|
**ALWAYS:**
|
|
23
24
|
- Follow the mandatory phases
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
- Document decisions in `decisions.md`
|
|
26
27
|
- Checkpoint every 3 implemented tasks
|
|
27
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)
|
|
28
30
|
|
|
29
31
|
---
|
|
30
32
|
|
|
@@ -48,6 +50,25 @@
|
|
|
48
50
|
| `.morph/framework/` | **READ-ONLY** — framework files managed by morph-spec |
|
|
49
51
|
| `.morph/config/config.json` | Project configuration (editable) |
|
|
50
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 | — |
|
|
51
72
|
---
|
|
52
73
|
|
|
53
74
|
## Phase Sequence
|
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.10.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.10.0 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -12,24 +12,27 @@ 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';
|
|
25
|
+
import { phaseResetCommand } from '../src/commands/phase/phase-reset.js';
|
|
27
26
|
|
|
28
27
|
// Agent commands
|
|
29
28
|
import { dispatchAgentsCommand } from '../src/commands/agents/dispatch-agents.js';
|
|
30
29
|
|
|
31
30
|
// Task commands
|
|
32
|
-
import { taskDoneCommand, taskStartCommand, taskNextCommand
|
|
31
|
+
import { taskDoneCommand, taskStartCommand, taskNextCommand } from '../src/commands/tasks/task.js';
|
|
32
|
+
|
|
33
|
+
// Scope escalation commands
|
|
34
|
+
import { scopeEscalateCommand } from '../src/commands/scope/escalate.js';
|
|
35
|
+
import { taskExpandCommand } from '../src/commands/task/expand.js';
|
|
33
36
|
|
|
34
37
|
// Validation commands
|
|
35
38
|
import { validateCommand } from './validate.js';
|
|
@@ -37,8 +40,6 @@ import { validateFeatureCommand } from '../src/commands/validation/validate-feat
|
|
|
37
40
|
|
|
38
41
|
// Template commands
|
|
39
42
|
import { templateRenderCommand } from '../src/commands/templates/template-render.js';
|
|
40
|
-
import { generateContractsCommand } from '../src/commands/templates/generate-contracts.js';
|
|
41
|
-
|
|
42
43
|
// MCP commands
|
|
43
44
|
import { mcpSetupCommand } from '../src/commands/mcp/mcp-setup.js';
|
|
44
45
|
|
|
@@ -100,11 +101,6 @@ program
|
|
|
100
101
|
.option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
|
|
101
102
|
.action(doctorCommand);
|
|
102
103
|
|
|
103
|
-
program
|
|
104
|
-
.command('tutorial')
|
|
105
|
-
.description('Learn the MORPH-SPEC workflow — phase pipeline and getting started')
|
|
106
|
-
.action(tutorialCommand);
|
|
107
|
-
|
|
108
104
|
program
|
|
109
105
|
.command('status <feature>')
|
|
110
106
|
.description('Show comprehensive feature status dashboard')
|
|
@@ -112,18 +108,22 @@ program
|
|
|
112
108
|
.option('-v, --verbose', 'Show detailed output')
|
|
113
109
|
.action(statusCommand);
|
|
114
110
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
// Worktree commands
|
|
112
|
+
const worktreeCommand = program
|
|
113
|
+
.command('worktree')
|
|
114
|
+
.description('Manage git worktrees for feature isolation');
|
|
115
|
+
|
|
116
|
+
worktreeCommand
|
|
117
|
+
.command('setup <feature>')
|
|
118
|
+
.description('Create a worktree at .worktrees/{feature}/ on branch morph/{feature}')
|
|
119
|
+
.option('--fresh', 'Create a new dated branch even if one already exists')
|
|
120
|
+
.action((feature, options) => worktreeSetupCommand(feature, options));
|
|
121
121
|
|
|
122
122
|
// State management commands
|
|
123
123
|
program
|
|
124
124
|
.command('state <action> [args...]')
|
|
125
125
|
.description('Manage state.json (init | get | set | list | add-agent | remove-agent | mark-output)')
|
|
126
|
-
.option('--force', 'Force overwrite (init
|
|
126
|
+
.option('--force', 'Force overwrite (init) or bypass phase protection gates (set phase)')
|
|
127
127
|
.option('--project <name>', 'Project name (init command)')
|
|
128
128
|
.option('--type <type>', 'Project type (init command)')
|
|
129
129
|
.option('--json', 'Output as JSON (get command)')
|
|
@@ -141,16 +141,6 @@ taskCommand
|
|
|
141
141
|
.option('--dry-run', 'Show validation results without marking tasks as complete')
|
|
142
142
|
.action((feature, taskIds, options) => taskDoneCommand(feature, taskIds, options));
|
|
143
143
|
|
|
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
144
|
taskCommand
|
|
155
145
|
.command('start <feature> <task-id>')
|
|
156
146
|
.description('Start a task (mark as in_progress)')
|
|
@@ -161,6 +151,12 @@ taskCommand
|
|
|
161
151
|
.description('Show next suggested task')
|
|
162
152
|
.action((feature, options) => taskNextCommand(feature, options));
|
|
163
153
|
|
|
154
|
+
taskCommand
|
|
155
|
+
.command('expand <feature> <task-id>')
|
|
156
|
+
.description('Expand a task into sub-tasks (low-impact scope escalation)')
|
|
157
|
+
.option('--into <subtasks...>', 'Sub-task definitions ("T017a: Title")')
|
|
158
|
+
.action((feature, taskId, options) => taskExpandCommand(feature, taskId, options));
|
|
159
|
+
|
|
164
160
|
// Generation commands
|
|
165
161
|
const generateCommand = program
|
|
166
162
|
.command('generate')
|
|
@@ -175,14 +171,6 @@ generateCommand
|
|
|
175
171
|
await generateRecap('.', feature, options);
|
|
176
172
|
});
|
|
177
173
|
|
|
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
174
|
// Validation commands (Sprint 4: Continuous Validation)
|
|
187
175
|
program
|
|
188
176
|
.command('validate [validator]')
|
|
@@ -219,12 +207,10 @@ phaseCommand
|
|
|
219
207
|
.option('--skip-approval', 'Skip approval gate checks')
|
|
220
208
|
.action((feature, options) => phaseRunCommand(feature, options));
|
|
221
209
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
.
|
|
225
|
-
.
|
|
226
|
-
.option('-v, --verbose', 'Show detailed output')
|
|
227
|
-
.action(validatePhaseCommand);
|
|
210
|
+
phaseCommand
|
|
211
|
+
.command('reset <feature> <phase>')
|
|
212
|
+
.description('Reset feature phase (recovery tool — bypasses approval gates)')
|
|
213
|
+
.action((feature, phase) => phaseResetCommand(feature, phase));
|
|
228
214
|
|
|
229
215
|
// Feature validation command (content-aware)
|
|
230
216
|
program
|
|
@@ -243,17 +229,6 @@ program
|
|
|
243
229
|
.option('--label <label>', 'Custom label for checkpoint')
|
|
244
230
|
.action(checkpointSaveCommand);
|
|
245
231
|
|
|
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
232
|
// Approval workflow commands
|
|
258
233
|
program
|
|
259
234
|
.command('approve <feature> <gate>')
|
|
@@ -300,7 +275,7 @@ trustCommand
|
|
|
300
275
|
|
|
301
276
|
trustCommand
|
|
302
277
|
.command('set <feature> <level> [reason]')
|
|
303
|
-
.description('Manually set trust level (
|
|
278
|
+
.description('Manually set trust level (manual|high|auto)')
|
|
304
279
|
.action(trustSetCommand);
|
|
305
280
|
|
|
306
281
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -326,4 +301,18 @@ program
|
|
|
326
301
|
.option('-v, --verbose', 'Show stack trace on error')
|
|
327
302
|
.action((feature, phase, options) => dispatchAgentsCommand(feature, phase, options));
|
|
328
303
|
|
|
304
|
+
// Scope escalation commands
|
|
305
|
+
const scopeCommand = program
|
|
306
|
+
.command('scope')
|
|
307
|
+
.description('Scope management (escalate)');
|
|
308
|
+
|
|
309
|
+
scopeCommand
|
|
310
|
+
.command('escalate <feature>')
|
|
311
|
+
.description('Escalate scope when implementation reveals unexpected complexity')
|
|
312
|
+
.option('--task <id>', 'Trigger task ID (required)')
|
|
313
|
+
.option('--reason <text>', 'Reason for escalation (required)')
|
|
314
|
+
.option('--target <phase>', 'Override target phase (tasks | design)')
|
|
315
|
+
.option('--dry-run', 'Show recommendation without executing')
|
|
316
|
+
.action((feature, options) => scopeEscalateCommand(feature, options));
|
|
317
|
+
|
|
329
318
|
program.parse();
|
package/bin/task-manager.js
CHANGED
|
@@ -15,6 +15,9 @@ 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';
|
|
20
|
+
import { STALE_MS, isStaleTask } from '../framework/hooks/shared/stale-task-reset.js';
|
|
18
21
|
|
|
19
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
23
|
const __dirname = dirname(__filename);
|
|
@@ -96,6 +99,8 @@ class TaskManager {
|
|
|
96
99
|
* @param {string[]} taskIds - Task IDs to complete
|
|
97
100
|
* @param {Object} options - Options
|
|
98
101
|
* @param {boolean} options.skipValidation - Skip code validation
|
|
102
|
+
* @param {boolean} options.dryRun - Dry run (no state changes)
|
|
103
|
+
* @param {Function} [options._runTestSuite] - Inject a mock runTestSuite for testing
|
|
99
104
|
*/
|
|
100
105
|
async completeTasks(featureName, taskIds, options = {}) {
|
|
101
106
|
const state = loadState();
|
|
@@ -108,7 +113,7 @@ class TaskManager {
|
|
|
108
113
|
const taskList = await ensureTaskList(feature, featureName);
|
|
109
114
|
|
|
110
115
|
if (taskList.length === 0) {
|
|
111
|
-
const tasksPath = join(process.cwd(), `.morph/features/${featureName}/
|
|
116
|
+
const tasksPath = join(process.cwd(), `.morph/features/${featureName}/4-tasks/tasks.md`);
|
|
112
117
|
const tasksExist = await access(tasksPath).then(() => true).catch(() => false);
|
|
113
118
|
if (!tasksExist) {
|
|
114
119
|
throw new Error(`No tasks found for '${featureName}' — tasks.md not generated yet.\n Complete the tasks phase first: run /phase-tasks`);
|
|
@@ -142,6 +147,12 @@ class TaskManager {
|
|
|
142
147
|
tasksToComplete.push(task);
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
// ─── Test Task Guard ────────────────────────────────────────────────────────
|
|
151
|
+
// When any of the tasks being completed is a test/validation task (e.g.
|
|
152
|
+
// "Testes e Validação"), run the project's test suite BEFORE code validation.
|
|
153
|
+
// The guard is skipped when --skip-validation or --dry-run is active.
|
|
154
|
+
runTestGuard(tasksToComplete, process.cwd(), options);
|
|
155
|
+
|
|
145
156
|
// Run validation BEFORE marking tasks as complete
|
|
146
157
|
if (tasksToComplete.length > 0 && !options.skipValidation) {
|
|
147
158
|
const validationResult = await this.runValidation(featureName);
|
|
@@ -183,26 +194,32 @@ class TaskManager {
|
|
|
183
194
|
process.exit(1);
|
|
184
195
|
}
|
|
185
196
|
|
|
186
|
-
// Validation passed — mark any pending history as passed
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
// Validation passed — mark any pending history as passed.
|
|
198
|
+
// Update in-place on `feature` (same object as state.features[featureName]) so
|
|
199
|
+
// the single saveState(state) at the end of the function persists everything
|
|
200
|
+
// atomically. A separate saveState here would trigger a second renameSync on
|
|
201
|
+
// Windows, risking EPERM and leaving state.json without the task completions
|
|
202
|
+
// (the count bug), and would also be clobbered by the final save anyway.
|
|
203
|
+
if (feature.validationHistory) {
|
|
190
204
|
for (const task of tasksToComplete) {
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
205
|
+
if (feature.validationHistory[task.id]) {
|
|
206
|
+
feature.validationHistory[task.id].status = 'passed';
|
|
207
|
+
feature.validationHistory[task.id].updatedAt = new Date().toISOString();
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
|
-
saveState(successState);
|
|
197
210
|
}
|
|
198
211
|
} else if (options.dryRun) {
|
|
199
212
|
console.log(chalk.cyan('\n ℹ️ Dry-run — tasks NOT marked as complete (validation skipped)'));
|
|
200
213
|
return [];
|
|
214
|
+
} else if (shouldLogSkipValidationWarning(tasksToComplete, options.skipValidation, options.dryRun)) {
|
|
215
|
+
console.log(chalk.yellow('\n⚠ --skip-validation: Tier-4 code validators bypassed — tasks will be marked complete without validation'));
|
|
216
|
+
console.log(chalk.gray(' Re-run without --skip-validation before committing.\n'));
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
// Breaking change detection (non-blocking warning)
|
|
204
220
|
if (tasksToComplete.length > 0) {
|
|
205
|
-
const
|
|
221
|
+
const _detectBreaking = options._detectBreakingChanges ?? detectBreakingChanges;
|
|
222
|
+
const breakingChanges = await _detectBreaking();
|
|
206
223
|
if (breakingChanges && breakingChanges.length > 0) {
|
|
207
224
|
console.log(chalk.yellow('\n⚠️ BREAKING CHANGE DETECTION:'));
|
|
208
225
|
console.log(chalk.yellow(' Removed exports with active consumers found — review before completing:\n'));
|
|
@@ -274,6 +291,18 @@ class TaskManager {
|
|
|
274
291
|
// Display progress
|
|
275
292
|
this.displayProgress(feature);
|
|
276
293
|
|
|
294
|
+
// Emit VALIDATION DISPATCH for PostToolUse hook (strategy 1 parsing).
|
|
295
|
+
// Skipped when --skip-validation is active (no validators needed).
|
|
296
|
+
if (!options.skipValidation && results.length > 0) {
|
|
297
|
+
try {
|
|
298
|
+
const { emitValidatorDispatch } = await import('../src/lib/validators/shared/emit-validator-dispatch.js');
|
|
299
|
+
const phase = feature.phase || 'implement';
|
|
300
|
+
await emitValidatorDispatch(featureName, phase, process.cwd());
|
|
301
|
+
} catch {
|
|
302
|
+
// Non-blocking
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
277
306
|
// Suggest next task
|
|
278
307
|
const nextTask = this.getNextTask(taskList);
|
|
279
308
|
if (nextTask) {
|
|
@@ -343,7 +372,7 @@ class TaskManager {
|
|
|
343
372
|
// This is fail-open by design: a broken validator shouldn't block commits.
|
|
344
373
|
// Common cause: missing optional deps.
|
|
345
374
|
console.log(chalk.yellow(`\n⚠️ Validation skipped (${error.message})`));
|
|
346
|
-
console.log(chalk.gray(
|
|
375
|
+
console.log(chalk.gray(` Run manually: npx morph-spec validate-feature ${featureName}`));
|
|
347
376
|
return { passed: true, validators: {}, passRate: 1.0 };
|
|
348
377
|
}
|
|
349
378
|
}
|
|
@@ -391,10 +420,12 @@ class TaskManager {
|
|
|
391
420
|
* Calculate progress
|
|
392
421
|
*/
|
|
393
422
|
calculateProgress(tasks) {
|
|
394
|
-
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
const
|
|
423
|
+
// Exclude expanded tasks — they are replaced by their sub-tasks
|
|
424
|
+
const activeTasks = tasks.filter(t => t.status !== 'expanded');
|
|
425
|
+
const total = activeTasks.length;
|
|
426
|
+
const completed = activeTasks.filter(t => t.status === 'completed').length;
|
|
427
|
+
const inProgress = activeTasks.filter(t => t.status === 'in_progress').length;
|
|
428
|
+
const pending = activeTasks.filter(t => t.status === 'pending').length;
|
|
398
429
|
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
399
430
|
|
|
400
431
|
return { total, completed, inProgress, pending, percentage };
|
|
@@ -408,7 +439,8 @@ class TaskManager {
|
|
|
408
439
|
id: task.checkpoint,
|
|
409
440
|
timestamp: new Date().toISOString(),
|
|
410
441
|
tasksCompleted: [task.id],
|
|
411
|
-
note: `Checkpoint: ${task.title}
|
|
442
|
+
note: `Checkpoint: ${task.title}`,
|
|
443
|
+
passed: true // reaching a named checkpoint means the task completed successfully
|
|
412
444
|
};
|
|
413
445
|
|
|
414
446
|
feature.checkpoints = feature.checkpoints || [];
|
|
@@ -469,6 +501,7 @@ class TaskManager {
|
|
|
469
501
|
timestamp: new Date().toISOString(),
|
|
470
502
|
tasksCompleted: tasks.map(t => t.id),
|
|
471
503
|
note: `Auto-checkpoint: ${tasks.length} tasks completed${validationNote}`,
|
|
504
|
+
passed: checkpointResult?.passed ?? true, // null checkpointResult means no validator ran → assume passed
|
|
472
505
|
validationResults: checkpointResult?.results || null
|
|
473
506
|
};
|
|
474
507
|
|
|
@@ -542,12 +575,22 @@ class TaskManager {
|
|
|
542
575
|
}
|
|
543
576
|
|
|
544
577
|
/**
|
|
545
|
-
* Get next
|
|
578
|
+
* Get next available task (based on dependencies).
|
|
579
|
+
* Stale in_progress tasks (older than 1 hour, or missing startedAt) are
|
|
580
|
+
* treated as effectively pending and included as candidates.
|
|
546
581
|
*/
|
|
547
582
|
getNextTask(tasks) {
|
|
548
|
-
|
|
583
|
+
// Include stale in_progress tasks as effectively pending candidates
|
|
584
|
+
const isEffectivelyAvailable = (t) => {
|
|
585
|
+
if (t.status === 'expanded') return false;
|
|
586
|
+
if (t.status === 'pending') return true;
|
|
587
|
+
if (t.status === 'in_progress') return isStaleTask(t);
|
|
588
|
+
return false;
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
const candidates = tasks.filter(isEffectivelyAvailable);
|
|
549
592
|
|
|
550
|
-
for (const task of
|
|
593
|
+
for (const task of candidates) {
|
|
551
594
|
const missingDeps = this.checkDependencies(task, tasks);
|
|
552
595
|
if (missingDeps.length === 0) {
|
|
553
596
|
return task;
|
|
@@ -570,6 +613,13 @@ class TaskManager {
|
|
|
570
613
|
const filledLength = Math.round((percentage / 100) * barLength);
|
|
571
614
|
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
572
615
|
console.log(chalk.cyan(` [${bar}] ${percentage}%`));
|
|
616
|
+
|
|
617
|
+
// Warn about tasks flagged for review (from scope escalation)
|
|
618
|
+
const taskList = feature.taskList || [];
|
|
619
|
+
const needsReviewTasks = taskList.filter(t => t.needsReview && (t.status === 'done' || t.status === 'completed'));
|
|
620
|
+
if (needsReviewTasks.length > 0) {
|
|
621
|
+
console.log(chalk.yellow(`\n ⚠ ${needsReviewTasks.length} completed task(s) flagged for review (scope escalation)`));
|
|
622
|
+
}
|
|
573
623
|
}
|
|
574
624
|
|
|
575
625
|
/**
|
|
@@ -586,7 +636,7 @@ class TaskManager {
|
|
|
586
636
|
const taskList = await ensureTaskList(feature, featureName);
|
|
587
637
|
|
|
588
638
|
if (taskList.length === 0) {
|
|
589
|
-
const tasksPath = join(process.cwd(), `.morph/features/${featureName}/
|
|
639
|
+
const tasksPath = join(process.cwd(), `.morph/features/${featureName}/4-tasks/tasks.md`);
|
|
590
640
|
const tasksExist = await access(tasksPath).then(() => true).catch(() => false);
|
|
591
641
|
if (!tasksExist) {
|
|
592
642
|
throw new Error(`No tasks found for '${featureName}' — tasks.md not generated yet.\n Complete the tasks phase first: run /phase-tasks`);
|
|
@@ -610,6 +660,16 @@ class TaskManager {
|
|
|
610
660
|
throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
|
|
611
661
|
}
|
|
612
662
|
|
|
663
|
+
// Reset any sibling tasks stuck in in_progress from a previous session.
|
|
664
|
+
// Only tasks OTHER than the one being started, and only if stale (>1h).
|
|
665
|
+
for (const t of taskList) {
|
|
666
|
+
if (t.id !== taskId && isStaleTask(t)) {
|
|
667
|
+
t.status = 'pending';
|
|
668
|
+
delete t.startedAt;
|
|
669
|
+
console.log(chalk.yellow(`⚠️ Task ${t.id} auto-reset to pending (stale from previous session)`));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
613
673
|
task.status = 'in_progress';
|
|
614
674
|
task.startedAt = new Date().toISOString();
|
|
615
675
|
syncCounters(feature);
|
|
@@ -800,4 +860,57 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
800
860
|
main();
|
|
801
861
|
}
|
|
802
862
|
|
|
863
|
+
/**
|
|
864
|
+
* Pure predicate: should a --skip-validation bypass warning be logged?
|
|
865
|
+
*
|
|
866
|
+
* @param {Array} tasksToComplete - Tasks that will be marked complete
|
|
867
|
+
* @param {boolean} skipValidation - Whether --skip-validation flag is active
|
|
868
|
+
* @param {boolean} dryRun - Whether --dry-run is active (has its own message)
|
|
869
|
+
* @returns {boolean}
|
|
870
|
+
*/
|
|
871
|
+
export function shouldLogSkipValidationWarning(tasksToComplete, skipValidation, dryRun) {
|
|
872
|
+
return tasksToComplete.length > 0 && !!skipValidation && !dryRun;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Test Task Guard — run project test suite before completing test-keyword tasks.
|
|
877
|
+
*
|
|
878
|
+
* Extracted as a pure, injectable function so it can be unit-tested without
|
|
879
|
+
* spawning a full TaskManager or writing state to disk.
|
|
880
|
+
*
|
|
881
|
+
* @param {Array} tasksToComplete - Tasks about to be marked complete
|
|
882
|
+
* @param {string} projectPath - Project root (process.cwd() in production)
|
|
883
|
+
* @param {object} [opts]
|
|
884
|
+
* @param {boolean} [opts.skipValidation] - Skip the guard entirely
|
|
885
|
+
* @param {boolean} [opts.dryRun] - Skip the guard entirely
|
|
886
|
+
* @param {Function} [opts._runTestSuite] - Override runTestSuite for testing
|
|
887
|
+
* @param {Function} [opts._exit] - Override process.exit for testing
|
|
888
|
+
* @returns {void} Calls _exit/process.exit(1) on failure; returns normally otherwise
|
|
889
|
+
*/
|
|
890
|
+
export function runTestGuard(tasksToComplete, projectPath, opts = {}) {
|
|
891
|
+
if (!tasksToComplete.length || opts.skipValidation || opts.dryRun) return;
|
|
892
|
+
|
|
893
|
+
const _runTS = opts._runTestSuite ?? runTestSuite;
|
|
894
|
+
const hasTestTask = tasksToComplete.some(t => isTestTask(t.title));
|
|
895
|
+
if (!hasTestTask) return;
|
|
896
|
+
|
|
897
|
+
const suiteResult = _runTS(projectPath);
|
|
898
|
+
|
|
899
|
+
if (suiteResult.passed === false) {
|
|
900
|
+
console.error(chalk.red('\n❌ Test suite FAILED — tasks NOT marked as complete'));
|
|
901
|
+
if (suiteResult.output) {
|
|
902
|
+
console.error(chalk.gray(suiteResult.output.slice(-2000)));
|
|
903
|
+
}
|
|
904
|
+
console.log(chalk.gray(' Fix failing tests, then run task done again'));
|
|
905
|
+
console.log(chalk.gray(' Or use --skip-validation to bypass (not recommended)\n'));
|
|
906
|
+
(opts._exit ?? process.exit)(1);
|
|
907
|
+
} else if (suiteResult.skipped) {
|
|
908
|
+
console.log(chalk.yellow('\n⚠️ Test suite skipped — no testCommand configured'));
|
|
909
|
+
console.log(chalk.gray(` Reason: ${suiteResult.reason}`));
|
|
910
|
+
console.log(chalk.gray(' Set project.testCommand in .morph/config/config.json to enable auto-run\n'));
|
|
911
|
+
} else {
|
|
912
|
+
console.log(chalk.green('\n✅ Test suite passed'));
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
803
916
|
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