@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions for the dispatch PostToolUse hook.
|
|
3
|
+
*
|
|
4
|
+
* Extracted for testability — no I/O, no process.exit, no stdin/stdout.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extract VALIDATION DISPATCH JSON from tool output string.
|
|
9
|
+
* Strips ANSI escape codes and parses the JSON block between delimiters.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} toolOutput - Raw tool output (may contain ANSI codes)
|
|
12
|
+
* @returns {object|null} - Parsed dispatch object or null
|
|
13
|
+
*/
|
|
14
|
+
export function extractValidationDispatch(toolOutput) {
|
|
15
|
+
if (!toolOutput || typeof toolOutput !== 'string') return null;
|
|
16
|
+
|
|
17
|
+
// Strip ANSI escape codes (chalk colors)
|
|
18
|
+
const clean = toolOutput.replace(/\x1B\[[0-9;]*m/g, '');
|
|
19
|
+
|
|
20
|
+
const match = clean.match(/--- VALIDATION DISPATCH ---\s*\n([\s\S]*?)\n\s*--- END VALIDATION DISPATCH ---/);
|
|
21
|
+
if (!match) return null;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const dispatch = JSON.parse(match[1]);
|
|
25
|
+
if (!dispatch?.validators?.length) return null;
|
|
26
|
+
return dispatch;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format a dispatch object as a mandatory instruction string for Claude.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} dispatch - Dispatch object with validators array
|
|
36
|
+
* @returns {string} - Formatted injection text
|
|
37
|
+
*/
|
|
38
|
+
export function formatValidatorInstruction(dispatch) {
|
|
39
|
+
if (!dispatch?.validators?.length) return '';
|
|
40
|
+
|
|
41
|
+
const lines = [
|
|
42
|
+
'🔍 MANDATORY VALIDATOR DISPATCH',
|
|
43
|
+
'════════════════════════════════════════',
|
|
44
|
+
'You MUST dispatch each validator below as a READ-ONLY subagent using the Agent tool.',
|
|
45
|
+
'Do NOT skip this step. Do NOT proceed until all blocking validators pass.',
|
|
46
|
+
'',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const v of dispatch.validators) {
|
|
50
|
+
const blocks = v.blocksOnFail !== false;
|
|
51
|
+
const sev = v.severity || 'error';
|
|
52
|
+
lines.push(`### ${v.title} (${v.id})`);
|
|
53
|
+
lines.push(`Severity: ${sev} | Blocks: ${blocks}`);
|
|
54
|
+
if (v.checks?.length) lines.push(`Checks: ${v.checks.join(', ')}`);
|
|
55
|
+
lines.push('');
|
|
56
|
+
if (v.taskPrompt) {
|
|
57
|
+
lines.push('Subagent prompt:');
|
|
58
|
+
lines.push(v.taskPrompt);
|
|
59
|
+
}
|
|
60
|
+
lines.push('---');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('DISPATCH INSTRUCTIONS:');
|
|
65
|
+
lines.push('1. Use the Agent tool for each validator. Set subagent_type to the validator name shown above.');
|
|
66
|
+
lines.push('2. Use model="haiku" for efficiency. Each validator is read-only.');
|
|
67
|
+
lines.push('3. Each must return { "passed": boolean, "issues": [] }');
|
|
68
|
+
lines.push('4. If any blocking validator (Blocks: true) fails, fix issues before proceeding.');
|
|
69
|
+
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse `morph-spec task done <feature> <taskId>` from a bash command string.
|
|
75
|
+
* Returns { featureName, taskId } or null if the command doesn't match.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} command
|
|
78
|
+
* @returns {{ featureName: string, taskId: string } | null}
|
|
79
|
+
*/
|
|
80
|
+
export function parseTaskDoneCommand(command) {
|
|
81
|
+
if (!command || typeof command !== 'string') return null;
|
|
82
|
+
const match = command.match(/morph-spec\s+task\s+done\s+(\S+)\s+(\S+)/);
|
|
83
|
+
if (!match) return null;
|
|
84
|
+
return { featureName: match[1], taskId: match[2] };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parse `morph-spec phase advance <feature>` from a bash command string.
|
|
89
|
+
* Returns { featureName } or null if the command doesn't match.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} command
|
|
92
|
+
* @returns {{ featureName: string } | null}
|
|
93
|
+
*/
|
|
94
|
+
export function parsePhaseAdvanceCommand(command) {
|
|
95
|
+
if (!command || typeof command !== 'string') return null;
|
|
96
|
+
const match = command.match(/morph-spec\s+phase\s+advance\s+(\S+)/);
|
|
97
|
+
if (!match) return null;
|
|
98
|
+
return { featureName: match[1] };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Extract the tool result string from a PostToolUse hook payload.
|
|
103
|
+
* Handles multiple possible formats.
|
|
104
|
+
*
|
|
105
|
+
* @param {object} payload - Hook payload
|
|
106
|
+
* @returns {string} - Tool output string (empty string if not found)
|
|
107
|
+
*/
|
|
108
|
+
export function getToolOutput(payload) {
|
|
109
|
+
if (!payload) return '';
|
|
110
|
+
const result = payload.tool_result;
|
|
111
|
+
if (typeof result === 'string') return result;
|
|
112
|
+
if (result && typeof result === 'object') {
|
|
113
|
+
return result.stdout || result.content || '';
|
|
114
|
+
}
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/** Phase order */
|
|
11
|
-
export const PHASE_ORDER = ["proposal","setup","uiux","design","clarify","tasks","implement","sync"];
|
|
11
|
+
export const PHASE_ORDER = ["proposal","setup","uiux","design","clarify","plan","tasks","implement","sync"];
|
|
12
12
|
|
|
13
13
|
/** Map phase → allowed output subdirectory */
|
|
14
14
|
export const PHASE_DIRS = {
|
|
@@ -16,10 +16,11 @@ export const PHASE_DIRS = {
|
|
|
16
16
|
setup: '0-proposal',
|
|
17
17
|
uiux: '2-ui',
|
|
18
18
|
design: '1-design',
|
|
19
|
-
clarify: '
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
clarify: '1-design',
|
|
20
|
+
plan: '3-plan',
|
|
21
|
+
tasks: '4-tasks',
|
|
22
|
+
implement: '5-implement',
|
|
23
|
+
sync: '5-implement',
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
/** Map output type (camelCase) → phase that produces it */
|
|
@@ -29,7 +30,9 @@ export const OUTPUT_PHASE_MAP = {
|
|
|
29
30
|
spec: 'design',
|
|
30
31
|
clarifications: 'clarify',
|
|
31
32
|
contracts: 'design',
|
|
33
|
+
contractsTs: 'design',
|
|
32
34
|
contractsVsa: 'design',
|
|
35
|
+
plan: 'plan',
|
|
33
36
|
tasks: 'tasks',
|
|
34
37
|
uiDesignSystem: 'uiux',
|
|
35
38
|
uiMockups: 'uiux',
|
|
@@ -46,7 +49,9 @@ export const FILENAME_TO_OUTPUT = {
|
|
|
46
49
|
'spec.md': 'spec',
|
|
47
50
|
'clarifications.md': 'clarifications',
|
|
48
51
|
'contracts.cs': 'contracts',
|
|
52
|
+
'contracts.ts': 'contractsTs',
|
|
49
53
|
'contracts-vsa.cs': 'contractsVsa',
|
|
54
|
+
'plan.md': 'plan',
|
|
50
55
|
'tasks.md': 'tasks',
|
|
51
56
|
'design-system.md': 'uiDesignSystem',
|
|
52
57
|
'mockups.md': 'uiMockups',
|
|
@@ -61,7 +66,9 @@ export const PROTECTED_SPEC_FILES = {
|
|
|
61
66
|
'schema-analysis.md': 'design',
|
|
62
67
|
'spec.md': 'design',
|
|
63
68
|
'contracts.cs': 'design',
|
|
69
|
+
'contracts.ts': 'design',
|
|
64
70
|
'contracts-vsa.cs': 'design',
|
|
71
|
+
'plan.md': 'plan',
|
|
65
72
|
'tasks.md': 'tasks',
|
|
66
73
|
'design-system.md': 'uiux',
|
|
67
74
|
'mockups.md': 'uiux',
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions for the skill-reminder PostToolUse hook.
|
|
3
|
+
* Extracted to shared/ so they can be unit-tested without running the hook entry point.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse `morph-spec task start <feature> <taskId>` from a bash command string.
|
|
8
|
+
* Returns { featureName, taskId } or null if the command doesn't match.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} command
|
|
11
|
+
* @returns {{ featureName: string, taskId: string } | null}
|
|
12
|
+
*/
|
|
13
|
+
export function parseTaskStartCommand(command) {
|
|
14
|
+
if (!command || typeof command !== 'string') return null;
|
|
15
|
+
const match = command.match(/morph-spec\s+task\s+start\s+(\S+)\s+(\S+)/);
|
|
16
|
+
if (!match) return null;
|
|
17
|
+
return { featureName: match[1], taskId: match[2] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build the mandatory skill reminder message for a task start event.
|
|
22
|
+
* Returns null if no relevant skills exist for this phase at task-start time.
|
|
23
|
+
*
|
|
24
|
+
* Relevant triggers:
|
|
25
|
+
* - beforeEachTask → invoke NOW before writing any code
|
|
26
|
+
* - beforeTaskDone → invoke BEFORE marking task done
|
|
27
|
+
* - onBugOrUnexpected → invoke IF a bug is encountered
|
|
28
|
+
*
|
|
29
|
+
* @param {string} featureName
|
|
30
|
+
* @param {string} taskId
|
|
31
|
+
* @param {string} phase
|
|
32
|
+
* @param {Object|null} phasesData - Parsed phases.json content
|
|
33
|
+
* @returns {string|null}
|
|
34
|
+
*/
|
|
35
|
+
export function buildSkillReminderMessage(featureName, taskId, phase, phasesData) {
|
|
36
|
+
try {
|
|
37
|
+
const allSkills = phasesData?.phases?.[phase]?.requiredSkills;
|
|
38
|
+
if (!allSkills || allSkills.length === 0) return null;
|
|
39
|
+
|
|
40
|
+
const nowSkills = allSkills.filter(s => s.trigger === 'beforeEachTask');
|
|
41
|
+
const laterSkills = allSkills.filter(s => s.trigger === 'beforeTaskDone');
|
|
42
|
+
const bugSkills = allSkills.filter(s => s.trigger === 'onBugOrUnexpected');
|
|
43
|
+
|
|
44
|
+
if (nowSkills.length === 0 && laterSkills.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const lines = [];
|
|
47
|
+
lines.push(`⚠️ MORPH-SPEC SKILL REMINDER — Task ${taskId} started (phase: ${phase})`);
|
|
48
|
+
lines.push('');
|
|
49
|
+
|
|
50
|
+
if (nowSkills.length > 0) {
|
|
51
|
+
lines.push('INVOKE NOW before writing any code:');
|
|
52
|
+
for (const s of nowSkills) {
|
|
53
|
+
lines.push(` → Skill(${s.skill})`);
|
|
54
|
+
}
|
|
55
|
+
lines.push('');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (laterSkills.length > 0) {
|
|
59
|
+
lines.push('INVOKE BEFORE marking task done:');
|
|
60
|
+
for (const s of laterSkills) {
|
|
61
|
+
lines.push(` → Skill(${s.skill})`);
|
|
62
|
+
}
|
|
63
|
+
lines.push('');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (bugSkills.length > 0) {
|
|
67
|
+
lines.push('IF you encounter a bug or unexpected behavior:');
|
|
68
|
+
for (const s of bugSkills) {
|
|
69
|
+
lines.push(` → Skill(${s.skill})`);
|
|
70
|
+
}
|
|
71
|
+
lines.push('');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
lines.push('These are MANDATORY. Use the Skill() tool. Do NOT skip.');
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stale Task Reset — shared hook utility
|
|
3
|
+
*
|
|
4
|
+
* Detects tasks stuck in `in_progress` for longer than STALE_MS and resets
|
|
5
|
+
* them to `pending`. Works for both v2 (tasks is array) and v3 (taskList)
|
|
6
|
+
* state formats. Pure function: mutates state in-place, returns reset log.
|
|
7
|
+
*
|
|
8
|
+
* Stale threshold: 1 hour (hardcoded — not user-configurable)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const STALE_MS = 60 * 60 * 1000; // 1 hour
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Determine if a task is stale (in_progress for longer than STALE_MS).
|
|
15
|
+
* @param {Object} task
|
|
16
|
+
* @param {number} [now] - timestamp for testability (defaults to Date.now())
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function isStaleTask(task, now = Date.now()) {
|
|
20
|
+
if (task.status !== 'in_progress') return false;
|
|
21
|
+
if (!task.startedAt) return true; // missing timestamp → treat as stale
|
|
22
|
+
return now - new Date(task.startedAt).getTime() > STALE_MS;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reset all stale in_progress tasks across all features in state.
|
|
27
|
+
* Mutates state in-place. Returns log of reset task identifiers.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} state - Full state object (state.features)
|
|
30
|
+
* @param {number} [now] - Timestamp for testability
|
|
31
|
+
* @returns {string[]} resetLog — entries like "feature-name/T008"
|
|
32
|
+
*/
|
|
33
|
+
export function resetStaleTasks(state, now = Date.now()) {
|
|
34
|
+
const resetLog = [];
|
|
35
|
+
|
|
36
|
+
for (const [featureName, feature] of Object.entries(state.features || {})) {
|
|
37
|
+
// Resolve task list for both v2 (array) and v3 (taskList) formats
|
|
38
|
+
const isV2 = Array.isArray(feature.tasks);
|
|
39
|
+
const list = isV2 ? feature.tasks : (feature.taskList || []);
|
|
40
|
+
|
|
41
|
+
for (const task of list) {
|
|
42
|
+
if (isStaleTask(task, now)) {
|
|
43
|
+
task.status = 'pending';
|
|
44
|
+
delete task.startedAt;
|
|
45
|
+
resetLog.push(`${featureName}/${task.id ?? '(unknown)'}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Re-sync counters (v3 only — v2 tasks IS the list, no separate counters)
|
|
50
|
+
if (!isV2 && feature.tasks) {
|
|
51
|
+
feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
|
|
52
|
+
feature.tasks.pending = list.filter(t => t.status === 'pending').length;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return resetLog;
|
|
57
|
+
}
|
|
@@ -62,6 +62,30 @@ export function getActiveFeature(projectPath) {
|
|
|
62
62
|
return active;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Get the most recently updated feature (any status).
|
|
67
|
+
* Useful as a fallback when getActiveFeature returns null (all features done).
|
|
68
|
+
* @param {string} [projectPath]
|
|
69
|
+
* @returns {{ name: string, feature: Object }|null}
|
|
70
|
+
*/
|
|
71
|
+
export function getMostRecentFeature(projectPath) {
|
|
72
|
+
const state = loadState(projectPath);
|
|
73
|
+
if (!state?.features) return null;
|
|
74
|
+
|
|
75
|
+
let result = null;
|
|
76
|
+
let latestUpdate = '';
|
|
77
|
+
|
|
78
|
+
for (const [name, feature] of Object.entries(state.features)) {
|
|
79
|
+
const updated = feature.updatedAt || feature.createdAt || '';
|
|
80
|
+
if (updated >= latestUpdate) {
|
|
81
|
+
latestUpdate = updated;
|
|
82
|
+
result = { name, feature };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
/**
|
|
66
90
|
* Get a specific feature by name.
|
|
67
91
|
* @param {string} featureName
|
|
@@ -74,7 +98,7 @@ export function getFeature(featureName, projectPath) {
|
|
|
74
98
|
}
|
|
75
99
|
|
|
76
100
|
/**
|
|
77
|
-
* Derive phase from filesystem (
|
|
101
|
+
* Derive phase from filesystem (phase deleted from state).
|
|
78
102
|
* Checks for phase folders in descending order, returns highest present.
|
|
79
103
|
* @param {string} featureName
|
|
80
104
|
* @param {string} [projectPath]
|
|
@@ -83,9 +107,9 @@ export function getFeature(featureName, projectPath) {
|
|
|
83
107
|
export function derivePhaseForFeature(featureName, projectPath) {
|
|
84
108
|
const basePath = join(projectPath || process.cwd(), '.morph/features', featureName);
|
|
85
109
|
const phaseMap = [
|
|
86
|
-
['
|
|
87
|
-
['
|
|
88
|
-
['
|
|
110
|
+
['5-implement', 'implement'],
|
|
111
|
+
['4-tasks', 'tasks'],
|
|
112
|
+
['3-plan', 'plan'],
|
|
89
113
|
['2-ui', 'uiux'],
|
|
90
114
|
['1-design', 'design'],
|
|
91
115
|
['0-proposal', 'proposal'],
|
|
@@ -98,7 +122,7 @@ export function derivePhaseForFeature(featureName, projectPath) {
|
|
|
98
122
|
|
|
99
123
|
/**
|
|
100
124
|
* Get the current phase of a feature.
|
|
101
|
-
* Falls back to filesystem derivation when phase is not in state
|
|
125
|
+
* Falls back to filesystem derivation when phase is not in state.
|
|
102
126
|
* @param {string} featureName
|
|
103
127
|
* @param {string} [projectPath]
|
|
104
128
|
* @returns {string|null}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for worktree-related hook logic.
|
|
3
|
+
* Pure functions — no side effects, easy to test.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse JSON output from `morph-spec worktree setup`.
|
|
8
|
+
* Finds the last line starting with '{' to skip chalk-colored output.
|
|
9
|
+
* @param {string} stdout - Raw stdout from the command
|
|
10
|
+
* @returns {Object|null}
|
|
11
|
+
*/
|
|
12
|
+
export function parseWorktreeResult(stdout) {
|
|
13
|
+
if (!stdout || typeof stdout !== 'string') return null;
|
|
14
|
+
try {
|
|
15
|
+
const trimmed = stdout.trim();
|
|
16
|
+
if (!trimmed) return null;
|
|
17
|
+
const lines = trimmed.split('\n');
|
|
18
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
19
|
+
const line = lines[i].trim();
|
|
20
|
+
if (line.startsWith('{')) {
|
|
21
|
+
return JSON.parse(line);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build context line(s) to inject based on worktree setup result.
|
|
32
|
+
* @param {Object|null} result - Parsed result from parseWorktreeResult
|
|
33
|
+
* @returns {string} Context lines to inject, or '' to skip injection
|
|
34
|
+
*/
|
|
35
|
+
export function buildWorktreeContextLine(result) {
|
|
36
|
+
if (!result) return '';
|
|
37
|
+
if (result.error) return '';
|
|
38
|
+
|
|
39
|
+
if (result.created) {
|
|
40
|
+
return `🪴 Worktree created: Working in .worktrees/${result.feature || ''} on branch ${result.branch}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (result.alreadyExists) {
|
|
44
|
+
return [
|
|
45
|
+
`⚠️ Existing worktree found for feature '${result.feature}': ${result.path} (branch: ${result.branch})`,
|
|
46
|
+
` Use AskUserQuestion to ask: "resume existing worktree or start fresh (--fresh)?"`,
|
|
47
|
+
` • Resume: continue in existing worktree (no change)`,
|
|
48
|
+
` • Fresh: run \`morph-spec worktree setup ${result.feature} --fresh\``
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return '';
|
|
53
|
+
}
|
package/framework/phases.json
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
"pausePoints": [
|
|
18
18
|
{ "id": "proposal", "label": "Approve Proposal" }
|
|
19
19
|
],
|
|
20
|
-
"agentTiers": [1]
|
|
20
|
+
"agentTiers": [1],
|
|
21
|
+
"requiredSkills": [
|
|
22
|
+
{ "trigger": "beforePhaseStart", "skill": "morph:brainstorming" }
|
|
23
|
+
]
|
|
21
24
|
},
|
|
22
25
|
"setup": {
|
|
23
26
|
"id": "setup",
|
|
@@ -33,7 +36,8 @@
|
|
|
33
36
|
"optionalOutputs": [],
|
|
34
37
|
"recommendedMCPs": ["github"],
|
|
35
38
|
"pausePoints": [],
|
|
36
|
-
"agentTiers": [1, 2]
|
|
39
|
+
"agentTiers": [1, 2],
|
|
40
|
+
"requiredSkills": []
|
|
37
41
|
},
|
|
38
42
|
"uiux": {
|
|
39
43
|
"id": "uiux",
|
|
@@ -52,7 +56,12 @@
|
|
|
52
56
|
"pausePoints": [
|
|
53
57
|
{ "id": "uiux", "label": "Approve UI/UX Design" }
|
|
54
58
|
],
|
|
55
|
-
"agentTiers": [1, 2, 3]
|
|
59
|
+
"agentTiers": [1, 2, 3],
|
|
60
|
+
"requiredSkills": [
|
|
61
|
+
{ "trigger": "beforeArchitecturalDecision", "skill": "morph:brainstorming" },
|
|
62
|
+
{ "trigger": "onHTMLPrototypeAvailable", "skill": "morph:replicate" },
|
|
63
|
+
{ "trigger": "beforePhaseComplete", "skill": "morph:frontend-review" }
|
|
64
|
+
]
|
|
56
65
|
},
|
|
57
66
|
"design": {
|
|
58
67
|
"id": "design",
|
|
@@ -71,7 +80,14 @@
|
|
|
71
80
|
"pausePoints": [
|
|
72
81
|
{ "id": "design", "label": "Approve Design Spec" }
|
|
73
82
|
],
|
|
74
|
-
"agentTiers": [1, 2, 3, 4]
|
|
83
|
+
"agentTiers": [1, 2, 3, 4],
|
|
84
|
+
"requiredSkills": [
|
|
85
|
+
{ "trigger": "beforePhaseStart", "skill": "morph:phase-design" },
|
|
86
|
+
{ "trigger": "beforePhaseStart", "skill": "morph:phase-codebase-analysis" },
|
|
87
|
+
{ "trigger": "beforeArchitecturalDecision", "skill": "morph:brainstorming" },
|
|
88
|
+
{ "trigger": "onExternalServiceIntegration", "skill": "morph:simulation-checklist" },
|
|
89
|
+
{ "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
|
|
90
|
+
]
|
|
75
91
|
},
|
|
76
92
|
"clarify": {
|
|
77
93
|
"id": "clarify",
|
|
@@ -87,12 +103,38 @@
|
|
|
87
103
|
"optionalOutputs": [],
|
|
88
104
|
"recommendedMCPs": ["context7", "github"],
|
|
89
105
|
"pausePoints": [],
|
|
90
|
-
"agentTiers": [1, 2]
|
|
106
|
+
"agentTiers": [1, 2],
|
|
107
|
+
"requiredSkills": [
|
|
108
|
+
{ "trigger": "beforePhaseStart", "skill": "morph:phase-clarify" },
|
|
109
|
+
{ "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"plan": {
|
|
113
|
+
"id": "plan",
|
|
114
|
+
"index": 5,
|
|
115
|
+
"folder": "3-plan",
|
|
116
|
+
"displayName": "Implementation Plan",
|
|
117
|
+
"goals": [
|
|
118
|
+
"Create detailed implementation plan with exact file paths and code",
|
|
119
|
+
"Define bite-sized TDD tasks with test-first approach",
|
|
120
|
+
"Establish execution strategy based on project analysis"
|
|
121
|
+
],
|
|
122
|
+
"requiredOutputs": ["plan"],
|
|
123
|
+
"optionalOutputs": [],
|
|
124
|
+
"recommendedMCPs": ["context7", "github"],
|
|
125
|
+
"pausePoints": [
|
|
126
|
+
{ "id": "plan", "label": "Approve Implementation Plan" }
|
|
127
|
+
],
|
|
128
|
+
"agentTiers": [1, 2],
|
|
129
|
+
"requiredSkills": [
|
|
130
|
+
{ "trigger": "beforePhaseStart", "skill": "morph:phase-plan" },
|
|
131
|
+
{ "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
|
|
132
|
+
]
|
|
91
133
|
},
|
|
92
134
|
"tasks": {
|
|
93
135
|
"id": "tasks",
|
|
94
|
-
"index":
|
|
95
|
-
"folder": "
|
|
136
|
+
"index": 6,
|
|
137
|
+
"folder": "4-tasks",
|
|
96
138
|
"displayName": "Task Breakdown",
|
|
97
139
|
"goals": [
|
|
98
140
|
"Break approved spec into numbered, atomic tasks",
|
|
@@ -105,12 +147,16 @@
|
|
|
105
147
|
"pausePoints": [
|
|
106
148
|
{ "id": "tasks", "label": "Approve Task List" }
|
|
107
149
|
],
|
|
108
|
-
"agentTiers": [1, 2, 3]
|
|
150
|
+
"agentTiers": [1, 2, 3],
|
|
151
|
+
"requiredSkills": [
|
|
152
|
+
{ "trigger": "onParallelTasksIdentified", "skill": "superpowers:dispatching-parallel-agents" },
|
|
153
|
+
{ "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
|
|
154
|
+
]
|
|
109
155
|
},
|
|
110
156
|
"implement": {
|
|
111
157
|
"id": "implement",
|
|
112
|
-
"index":
|
|
113
|
-
"folder": "
|
|
158
|
+
"index": 7,
|
|
159
|
+
"folder": "5-implement",
|
|
114
160
|
"displayName": "Implementation",
|
|
115
161
|
"goals": [
|
|
116
162
|
"Execute approved tasks in order",
|
|
@@ -120,13 +166,21 @@
|
|
|
120
166
|
],
|
|
121
167
|
"requiredOutputs": ["recap"],
|
|
122
168
|
"optionalOutputs": [],
|
|
123
|
-
"recommendedMCPs": ["supabase", "context7", "playwright", "github", "docker", "azure"],
|
|
169
|
+
"recommendedMCPs": ["supabase", "context7", "playwright", "github", "vercel", "docker", "azure"],
|
|
124
170
|
"pausePoints": [],
|
|
125
|
-
"agentTiers": [1, 2, 3, 4]
|
|
171
|
+
"agentTiers": [1, 2, 3, 4],
|
|
172
|
+
"requiredSkills": [
|
|
173
|
+
{ "trigger": "beforeEachTask", "skill": "superpowers:test-driven-development" },
|
|
174
|
+
{ "trigger": "beforeTaskDone", "skill": "morph:verification-before-completion" },
|
|
175
|
+
{ "trigger": "onBugOrUnexpected", "skill": "superpowers:systematic-debugging" },
|
|
176
|
+
{ "trigger": "afterAllTasks", "skill": "morph:post-implementation" },
|
|
177
|
+
{ "trigger": "afterAllTasks", "skill": "superpowers:requesting-code-review" },
|
|
178
|
+
{ "trigger": "beforePR", "skill": "morph:checklist" }
|
|
179
|
+
]
|
|
126
180
|
},
|
|
127
181
|
"sync": {
|
|
128
182
|
"id": "sync",
|
|
129
|
-
"index":
|
|
183
|
+
"index": 8,
|
|
130
184
|
"folder": null,
|
|
131
185
|
"displayName": "Sync Standards",
|
|
132
186
|
"optional": true,
|
|
@@ -139,7 +193,8 @@
|
|
|
139
193
|
"optionalOutputs": [],
|
|
140
194
|
"recommendedMCPs": [],
|
|
141
195
|
"pausePoints": [],
|
|
142
|
-
"agentTiers": [1]
|
|
196
|
+
"agentTiers": [1],
|
|
197
|
+
"requiredSkills": []
|
|
143
198
|
}
|
|
144
199
|
}
|
|
145
200
|
}
|