@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
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* PostToolUse Hook:
|
|
4
|
+
* PostToolUse Hook: Dispatch + Validator Injection
|
|
5
5
|
*
|
|
6
6
|
* Event: PostToolUse | Matcher: Bash
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
8
|
+
* Handles:
|
|
9
|
+
* - morph-spec task done: auto-checkpoint every 3 tasks + validator dispatch injection
|
|
10
|
+
* - morph-spec phase advance: phase chain evaluation + validator dispatch injection
|
|
11
|
+
* - morph-spec approve: pass-through
|
|
12
|
+
*
|
|
13
|
+
* Validator dispatch: extracts VALIDATION DISPATCH JSON from tool output
|
|
14
|
+
* and injects it as additionalContext so Claude reliably dispatches validators
|
|
15
|
+
* as subagents via the Agent tool.
|
|
12
16
|
*
|
|
13
17
|
* Fail-open: exits 0 on any error.
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
20
|
import { execSync } from 'child_process';
|
|
17
21
|
import { readFileSync, existsSync } from 'fs';
|
|
18
|
-
import { resolve } from 'path';
|
|
22
|
+
import { resolve, join, dirname } from 'path';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
26
|
import { readStdin } from '../../shared/stdin-reader.js';
|
|
20
|
-
import { stateExists, getFeature } from '../../shared/state-reader.js';
|
|
27
|
+
import { stateExists, getFeature, getFeaturePhase } from '../../shared/state-reader.js';
|
|
21
28
|
import { pass, injectContext } from '../../shared/hook-response.js';
|
|
22
29
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
30
|
+
import { extractValidationDispatch, formatValidatorInstruction, getToolOutput, parseTaskDoneCommand, parsePhaseAdvanceCommand } from '../../shared/dispatch-helpers.js';
|
|
23
31
|
|
|
24
32
|
try {
|
|
25
33
|
if (!stateExists()) pass();
|
|
@@ -30,24 +38,27 @@ try {
|
|
|
30
38
|
const command = payload?.tool_input?.command || '';
|
|
31
39
|
if (!command) pass();
|
|
32
40
|
|
|
33
|
-
dispatch(command);
|
|
41
|
+
await dispatch(command, payload);
|
|
34
42
|
} catch {
|
|
35
43
|
// Fail-open
|
|
36
44
|
process.exit(0);
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
function dispatch(command) {
|
|
47
|
+
async function dispatch(command, payload) {
|
|
40
48
|
// morph-spec task done <feature> <taskId> [--skip-validation]
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
const
|
|
49
|
+
const taskDoneParsed = parseTaskDoneCommand(command);
|
|
50
|
+
if (taskDoneParsed) {
|
|
51
|
+
const { featureName } = taskDoneParsed;
|
|
52
|
+
const skipValidation = command.includes('--skip-validation');
|
|
53
|
+
|
|
54
|
+
const contextParts = [];
|
|
44
55
|
|
|
45
56
|
// Check if checkpoint should run (every 3 tasks)
|
|
46
57
|
let dispatchResult = 'task_done';
|
|
47
58
|
try {
|
|
48
59
|
const feature = getFeature(featureName);
|
|
49
60
|
if (feature?.tasks) {
|
|
50
|
-
const completed = (feature.tasks.completed || 0) + 1;
|
|
61
|
+
const completed = (feature.tasks.completed || 0) + 1;
|
|
51
62
|
if (completed > 0 && completed % 3 === 0) {
|
|
52
63
|
const checkpointNum = Math.floor(completed / 3);
|
|
53
64
|
run(`morph-spec checkpoint-save ${featureName} --note "Auto-checkpoint #${checkpointNum} at task ${completed}"`);
|
|
@@ -58,16 +69,45 @@ function dispatch(command) {
|
|
|
58
69
|
// Non-blocking
|
|
59
70
|
}
|
|
60
71
|
|
|
61
|
-
|
|
72
|
+
// Inject validator dispatch for task completion.
|
|
73
|
+
// Skip when --skip-validation: no validators needed, avoids subprocess fallback.
|
|
74
|
+
if (!skipValidation) {
|
|
75
|
+
const validatorText = await buildValidatorDispatchContext(featureName, payload);
|
|
76
|
+
if (validatorText) {
|
|
77
|
+
contextParts.push(validatorText);
|
|
78
|
+
dispatchResult = 'task_done_with_validators';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
logHookActivity('phase-dispatch', 'PostToolUse', dispatchResult);
|
|
83
|
+
|
|
84
|
+
if (contextParts.length > 0) {
|
|
85
|
+
injectContext(contextParts.join('\n\n'));
|
|
86
|
+
}
|
|
62
87
|
pass();
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
// morph-spec phase advance <feature>
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
const phaseAdvanceParsed = parsePhaseAdvanceCommand(command);
|
|
92
|
+
if (phaseAdvanceParsed) {
|
|
93
|
+
const { featureName } = phaseAdvanceParsed;
|
|
94
|
+
|
|
95
|
+
const contextParts = [];
|
|
96
|
+
|
|
97
|
+
// Phase chain evaluation
|
|
98
|
+
const chainText = buildPhaseChainContext(featureName);
|
|
99
|
+
if (chainText) contextParts.push(chainText);
|
|
100
|
+
|
|
101
|
+
// Validator dispatch injection
|
|
102
|
+
const validatorText = await buildValidatorDispatchContext(featureName, payload);
|
|
103
|
+
if (validatorText) contextParts.push(validatorText);
|
|
104
|
+
|
|
105
|
+
logHookActivity('phase-dispatch', 'PostToolUse',
|
|
106
|
+
validatorText ? 'phase_advance_with_validators' : 'phase_chain');
|
|
107
|
+
|
|
108
|
+
if (contextParts.length > 0) {
|
|
109
|
+
injectContext(contextParts.join('\n\n'));
|
|
110
|
+
}
|
|
71
111
|
pass();
|
|
72
112
|
}
|
|
73
113
|
|
|
@@ -83,45 +123,128 @@ function dispatch(command) {
|
|
|
83
123
|
}
|
|
84
124
|
|
|
85
125
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* Silently passes when workflow doesn't support chaining or feature is not found.
|
|
126
|
+
* Build phase chain context text (returns null if not applicable).
|
|
127
|
+
* Replaces the old evaluatePhaseChain that called injectContext directly.
|
|
89
128
|
*
|
|
90
129
|
* @param {string} featureName
|
|
130
|
+
* @returns {string|null}
|
|
91
131
|
*/
|
|
92
|
-
function
|
|
132
|
+
function buildPhaseChainContext(featureName) {
|
|
93
133
|
try {
|
|
94
134
|
const feature = getFeature(featureName);
|
|
95
|
-
if (!feature) return;
|
|
135
|
+
if (!feature) return null;
|
|
96
136
|
|
|
97
137
|
const workflowId = feature.workflow;
|
|
98
|
-
if (!workflowId || workflowId === 'auto') return;
|
|
138
|
+
if (!workflowId || workflowId === 'auto') return null;
|
|
99
139
|
|
|
100
|
-
// Load workflow config to check phaseChain.enabled
|
|
101
140
|
const configPath = resolve(
|
|
102
141
|
process.cwd(),
|
|
103
142
|
`.morph/framework/workflows/configs/${workflowId}.json`
|
|
104
143
|
);
|
|
105
|
-
if (!existsSync(configPath)) return;
|
|
144
|
+
if (!existsSync(configPath)) return null;
|
|
106
145
|
|
|
107
146
|
let workflowConfig;
|
|
108
147
|
try {
|
|
109
148
|
workflowConfig = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
110
149
|
} catch {
|
|
111
|
-
return;
|
|
150
|
+
return null;
|
|
112
151
|
}
|
|
113
152
|
|
|
114
|
-
if (!workflowConfig?.phaseChain?.enabled) return;
|
|
153
|
+
if (!workflowConfig?.phaseChain?.enabled) return null;
|
|
115
154
|
|
|
116
|
-
|
|
117
|
-
injectContext(
|
|
155
|
+
return (
|
|
118
156
|
`Phase advance complete. Continue the phase chain:\n` +
|
|
119
157
|
` morph-spec phase run ${featureName}\n\n` +
|
|
120
158
|
`The phase runner will check eligibility and auto-advance if all gates pass. ` +
|
|
121
159
|
`It will pause automatically on blocked tasks, low pass rate, or trust gates.`
|
|
122
160
|
);
|
|
123
161
|
} catch {
|
|
124
|
-
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extract validator dispatch from tool output and build injection context.
|
|
168
|
+
*
|
|
169
|
+
* Strategy:
|
|
170
|
+
* 1. Try to parse VALIDATION DISPATCH JSON from the tool_result (stdout of phase advance)
|
|
171
|
+
* 2. If not found, fallback to running dispatch-agents CLI in validate mode
|
|
172
|
+
*
|
|
173
|
+
* @param {string} featureName
|
|
174
|
+
* @param {object} payload - PostToolUse hook payload
|
|
175
|
+
* @returns {string|null} - Formatted injection text or null
|
|
176
|
+
*/
|
|
177
|
+
async function buildValidatorDispatchContext(featureName, payload) {
|
|
178
|
+
try {
|
|
179
|
+
// Strategy 1: Extract from tool output
|
|
180
|
+
const toolOutput = getToolOutput(payload);
|
|
181
|
+
const dispatch = extractValidationDispatch(toolOutput);
|
|
182
|
+
if (dispatch) return formatValidatorInstruction(dispatch);
|
|
183
|
+
|
|
184
|
+
// Strategy 2: Direct import of buildDispatchConfig (no subprocess)
|
|
185
|
+
return await buildDispatchDirect(featureName);
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Fallback: build validator dispatch config via direct import (no subprocess).
|
|
193
|
+
*
|
|
194
|
+
* Attempts to import buildDispatchConfig from the installed package or local
|
|
195
|
+
* framework. Falls back to null on any error (fail-open).
|
|
196
|
+
*
|
|
197
|
+
* @param {string} featureName
|
|
198
|
+
* @returns {Promise<string|null>}
|
|
199
|
+
*/
|
|
200
|
+
async function buildDispatchDirect(featureName) {
|
|
201
|
+
try {
|
|
202
|
+
const phase = getFeaturePhase(featureName);
|
|
203
|
+
if (!phase) return null;
|
|
204
|
+
|
|
205
|
+
// Try local project path first, then package path
|
|
206
|
+
let buildDispatchConfig;
|
|
207
|
+
const localPath = resolve(process.cwd(), 'src/commands/agents/dispatch-agents.js');
|
|
208
|
+
const pkgPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/src/commands/agents/dispatch-agents.js');
|
|
209
|
+
|
|
210
|
+
if (existsSync(localPath)) {
|
|
211
|
+
({ buildDispatchConfig } = await import(`file://${localPath.replace(/\\/g, '/')}`));
|
|
212
|
+
} else if (existsSync(pkgPath)) {
|
|
213
|
+
({ buildDispatchConfig } = await import(`file://${pkgPath.replace(/\\/g, '/')}`));
|
|
214
|
+
} else {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const config = await buildDispatchConfig(process.cwd(), featureName, phase, { mode: 'validate' });
|
|
219
|
+
const validators = config.agents?.filter(a => a.tier === 4);
|
|
220
|
+
if (!validators || validators.length === 0) return null;
|
|
221
|
+
|
|
222
|
+
const agentsJsonPath = resolve(process.cwd(), 'framework/agents.json');
|
|
223
|
+
const pkgAgentsPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/framework/agents.json');
|
|
224
|
+
const agentsPath = existsSync(agentsJsonPath) ? agentsJsonPath : pkgAgentsPath;
|
|
225
|
+
|
|
226
|
+
let allAgents = {};
|
|
227
|
+
if (existsSync(agentsPath)) {
|
|
228
|
+
allAgents = JSON.parse(readFileSync(agentsPath, 'utf8')).agents || {};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const dispatch = {
|
|
232
|
+
validators: validators.map(v => {
|
|
233
|
+
const agentData = allAgents[v.id];
|
|
234
|
+
return {
|
|
235
|
+
id: v.id,
|
|
236
|
+
title: v.title,
|
|
237
|
+
taskPrompt: v.taskPrompt,
|
|
238
|
+
severity: agentData?.hook_behavior?.severity || 'error',
|
|
239
|
+
blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
|
|
240
|
+
checks: agentData?.hook_behavior?.validates || [],
|
|
241
|
+
};
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return formatValidatorInstruction(dispatch);
|
|
246
|
+
} catch {
|
|
247
|
+
return null;
|
|
125
248
|
}
|
|
126
249
|
}
|
|
127
250
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PostToolUse Hook: Skill Reminder on Task Start
|
|
5
|
+
*
|
|
6
|
+
* Event: PostToolUse | Matcher: Bash
|
|
7
|
+
*
|
|
8
|
+
* Fires after `morph-spec task start <feature> <task>` completes.
|
|
9
|
+
* Reads requiredSkills from phases.json for the current phase and injects
|
|
10
|
+
* mandatory skill invocation instructions as additionalContext.
|
|
11
|
+
*
|
|
12
|
+
* The additionalContext appears directly in Claude's view of the Bash tool
|
|
13
|
+
* result — more prominent than any pre-loaded prompt text.
|
|
14
|
+
*
|
|
15
|
+
* Fail-open: exits 0 on any error.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { readFileSync, existsSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { readStdin } from '../../shared/stdin-reader.js';
|
|
21
|
+
import { stateExists, loadState, derivePhaseForFeature } from '../../shared/state-reader.js';
|
|
22
|
+
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
23
|
+
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
24
|
+
import { parseTaskStartCommand, buildSkillReminderMessage } from '../../shared/skill-reminder-helpers.js';
|
|
25
|
+
|
|
26
|
+
// Re-export for backwards compatibility (tests may import from this file)
|
|
27
|
+
export { parseTaskStartCommand, buildSkillReminderMessage };
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
// Hook entry point
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (!stateExists()) pass();
|
|
35
|
+
|
|
36
|
+
const payload = await readStdin();
|
|
37
|
+
if (!payload) pass();
|
|
38
|
+
|
|
39
|
+
const command = payload?.tool_input?.command || '';
|
|
40
|
+
const parsed = parseTaskStartCommand(command);
|
|
41
|
+
if (!parsed) pass();
|
|
42
|
+
|
|
43
|
+
const { featureName, taskId } = parsed;
|
|
44
|
+
|
|
45
|
+
// Load state to get current phase
|
|
46
|
+
const state = loadState();
|
|
47
|
+
if (!state) pass();
|
|
48
|
+
|
|
49
|
+
const feature = state?.features?.[featureName];
|
|
50
|
+
if (!feature) pass();
|
|
51
|
+
|
|
52
|
+
const phase = feature.phase || derivePhaseForFeature(featureName);
|
|
53
|
+
if (!phase) pass();
|
|
54
|
+
|
|
55
|
+
// Load phases.json (project-local first, fall back to package-level)
|
|
56
|
+
const localPhasesPath = join(process.cwd(), 'framework', 'phases.json');
|
|
57
|
+
const pkgPhasesPath = join(
|
|
58
|
+
process.cwd(), 'node_modules', '@polymorphism-tech', 'morph-spec', 'framework', 'phases.json'
|
|
59
|
+
);
|
|
60
|
+
const phasesPath = existsSync(localPhasesPath) ? localPhasesPath : pkgPhasesPath;
|
|
61
|
+
if (!existsSync(phasesPath)) pass();
|
|
62
|
+
|
|
63
|
+
let phasesData;
|
|
64
|
+
try {
|
|
65
|
+
phasesData = JSON.parse(readFileSync(phasesPath, 'utf8'));
|
|
66
|
+
} catch {
|
|
67
|
+
pass();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const message = buildSkillReminderMessage(featureName, taskId, phase, phasesData);
|
|
71
|
+
if (!message) pass();
|
|
72
|
+
|
|
73
|
+
logHookActivity('skill-reminder', 'PostToolUse', `injected(${featureName}/${taskId}/${phase})`);
|
|
74
|
+
injectContext(message);
|
|
75
|
+
} catch {
|
|
76
|
+
// Fail-open
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
@@ -16,12 +16,11 @@
|
|
|
16
16
|
* Fail-open: exits 0 on any error.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { readFileSync, existsSync } from 'fs';
|
|
20
|
-
import { join } from 'path';
|
|
21
19
|
import { readStdin } from '../../shared/stdin-reader.js';
|
|
22
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
20
|
+
import { stateExists, loadState } from '../../shared/state-reader.js';
|
|
23
21
|
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
24
22
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
23
|
+
import { parseTaskDoneCommand } from '../../shared/dispatch-helpers.js';
|
|
25
24
|
|
|
26
25
|
// Standard IDs referenced in remediation messages
|
|
27
26
|
const STANDARD_REFS = {
|
|
@@ -42,22 +41,14 @@ try {
|
|
|
42
41
|
const command = payload?.tool_input?.command || '';
|
|
43
42
|
if (!command) pass();
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!taskDoneMatch) pass();
|
|
44
|
+
const parsed = parseTaskDoneCommand(command);
|
|
45
|
+
if (!parsed) pass();
|
|
48
46
|
|
|
49
|
-
const
|
|
47
|
+
const { featureName, taskId } = parsed;
|
|
50
48
|
|
|
51
49
|
// Load state and find validationHistory for this task
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
let state;
|
|
56
|
-
try {
|
|
57
|
-
state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
58
|
-
} catch {
|
|
59
|
-
pass();
|
|
60
|
-
}
|
|
50
|
+
const state = loadState();
|
|
51
|
+
if (!state) pass();
|
|
61
52
|
|
|
62
53
|
const feature = state?.features?.[featureName];
|
|
63
54
|
if (!feature) pass();
|
|
@@ -100,7 +91,7 @@ function buildRemediationContext(featureName, taskId, taskHistory) {
|
|
|
100
91
|
}
|
|
101
92
|
}
|
|
102
93
|
|
|
103
|
-
if (allIssues.length === 0 && taskHistory.status !== 'blocked')
|
|
94
|
+
if (allIssues.length === 0 && taskHistory.status !== 'blocked') return;
|
|
104
95
|
|
|
105
96
|
const lines = [];
|
|
106
97
|
|
|
@@ -23,9 +23,7 @@ import {
|
|
|
23
23
|
} from '../../shared/state-reader.js';
|
|
24
24
|
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
25
25
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
26
|
-
|
|
27
|
-
const DECISIONS_MAX_CHARS = 1500;
|
|
28
|
-
const MAX_PENDING_TASKS = 8;
|
|
26
|
+
import { buildRichContext, DECISIONS_MAX_CHARS, MAX_PENDING_TASKS } from '../../shared/compact-restore.js';
|
|
29
27
|
|
|
30
28
|
const PHASE_POSITIONS = {
|
|
31
29
|
proposal: 1, setup: 1,
|
|
@@ -73,6 +71,21 @@ try {
|
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
73
|
|
|
74
|
+
// ── Enrich snapshot with richContext (decisions + taskList) ───────────────
|
|
75
|
+
if (active) {
|
|
76
|
+
try {
|
|
77
|
+
const { name: activeName, feature: activeFeature } = active;
|
|
78
|
+
const activePhase = derivePhaseForFeature(activeName);
|
|
79
|
+
let decisionsContent = '';
|
|
80
|
+
const decisionsPath = join(cwd, `.morph/features/${activeName}/1-design/decisions.md`);
|
|
81
|
+
if (existsSync(decisionsPath)) {
|
|
82
|
+
try { decisionsContent = readFileSync(decisionsPath, 'utf-8'); } catch { /* ignore */ }
|
|
83
|
+
}
|
|
84
|
+
snapshot.richContext = buildRichContext(activeFeature, activePhase, decisionsContent);
|
|
85
|
+
} catch { /* fail-open: richContext is optional */ }
|
|
86
|
+
}
|
|
87
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
76
89
|
const memoryDir = join(cwd, '.morph', 'memory');
|
|
77
90
|
if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
|
|
78
91
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* proposal → only 0-proposal/
|
|
10
10
|
* design, clarify → only 1-design/
|
|
11
11
|
* uiux → only 2-ui/
|
|
12
|
-
* tasks → only
|
|
13
|
-
* implement →
|
|
12
|
+
* tasks → only 4-tasks/
|
|
13
|
+
* implement → 5-implement/ + any source code (unrestricted)
|
|
14
14
|
*
|
|
15
15
|
* Files outside .morph/features/ are always allowed.
|
|
16
16
|
*
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from '../../shared/phase-utils.js';
|
|
30
30
|
import { block, pass } from '../../shared/hook-response.js';
|
|
31
31
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
32
|
+
import { getFilePath } from '../../shared/payload-utils.js';
|
|
32
33
|
|
|
33
34
|
try {
|
|
34
35
|
// Amend mode: bypass phase write enforcement for legitimate corrections
|
|
@@ -42,7 +43,7 @@ try {
|
|
|
42
43
|
const payload = await readStdin();
|
|
43
44
|
if (!payload) pass();
|
|
44
45
|
|
|
45
|
-
const filePath = payload
|
|
46
|
+
const filePath = getFilePath(payload);
|
|
46
47
|
if (!filePath) pass();
|
|
47
48
|
|
|
48
49
|
// Only check files inside .morph/features/
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Blocks Write/Edit to spec artifacts after their approval gate has passed:
|
|
9
9
|
* - 1-design/spec.md, contracts.cs, etc. → blocked if 'design' gate approved
|
|
10
|
-
* -
|
|
10
|
+
* - 4-tasks/tasks.md → blocked if 'tasks' gate approved
|
|
11
11
|
* - 2-ui/design-system.md, etc. → blocked if 'uiux' gate approved
|
|
12
12
|
*
|
|
13
13
|
* Fail-open: exits 0 on any error.
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '../../shared/phase-utils.js';
|
|
24
24
|
import { block, pass } from '../../shared/hook-response.js';
|
|
25
25
|
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
26
|
+
import { getFilePath } from '../../shared/payload-utils.js';
|
|
26
27
|
|
|
27
28
|
try {
|
|
28
29
|
// Amend mode: bypass spec protection for legitimate in-implementation corrections
|
|
@@ -36,7 +37,7 @@ try {
|
|
|
36
37
|
const payload = await readStdin();
|
|
37
38
|
if (!payload) pass();
|
|
38
39
|
|
|
39
|
-
const filePath = payload
|
|
40
|
+
const filePath = getFilePath(payload);
|
|
40
41
|
if (!filePath) pass();
|
|
41
42
|
|
|
42
43
|
// Only check files inside .morph/features/
|
|
@@ -58,7 +59,7 @@ try {
|
|
|
58
59
|
`MORPH-SPEC: '${filename}' is locked — the '${requiredGate}' gate has been approved.\n` +
|
|
59
60
|
`Editing approved specs breaks the spec contract.\n\n` +
|
|
60
61
|
`If changes are truly needed:\n` +
|
|
61
|
-
` 1. Revoke approval: morph-spec
|
|
62
|
+
` 1. Revoke approval: morph-spec unapprove ${featureName} ${requiredGate}\n` +
|
|
62
63
|
` 2. Make your edits\n` +
|
|
63
64
|
` 3. Re-approve: morph-spec approve ${featureName} ${requiredGate}`
|
|
64
65
|
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PreToolUse Hook: Advisory Task Tracking Guard
|
|
5
|
+
*
|
|
6
|
+
* Event: PreToolUse | Matcher: Write|Edit
|
|
7
|
+
*
|
|
8
|
+
* During implement phase, injects a warning if no task is currently in_progress.
|
|
9
|
+
* Advisory only (type: approve with context) — never blocks.
|
|
10
|
+
*
|
|
11
|
+
* Fail-open: exits 0 on any error.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readStdin } from '../../shared/stdin-reader.js';
|
|
15
|
+
import { stateExists, loadState, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
|
|
16
|
+
import { approve, pass } from '../../shared/hook-response.js';
|
|
17
|
+
import { getFilePath } from '../../shared/payload-utils.js';
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (!stateExists()) pass();
|
|
21
|
+
|
|
22
|
+
const payload = await readStdin();
|
|
23
|
+
if (!payload) pass();
|
|
24
|
+
|
|
25
|
+
const filePath = getFilePath(payload);
|
|
26
|
+
if (!filePath) pass();
|
|
27
|
+
|
|
28
|
+
// Skip files inside .morph/ — internal framework writes don't need task tracking
|
|
29
|
+
if (filePath.includes('.morph/') || filePath.includes('.morph\\')) pass();
|
|
30
|
+
|
|
31
|
+
// Only check during implement phase
|
|
32
|
+
const active = getActiveFeature();
|
|
33
|
+
if (!active) pass();
|
|
34
|
+
|
|
35
|
+
const { name, feature } = active;
|
|
36
|
+
const phase = feature.phase || derivePhaseForFeature(name);
|
|
37
|
+
if (phase !== 'implement') pass();
|
|
38
|
+
|
|
39
|
+
// Check if any task is in_progress
|
|
40
|
+
// Method 1: tasks.inProgress counter (v3 state format)
|
|
41
|
+
if (feature.tasks?.inProgress > 0) pass();
|
|
42
|
+
|
|
43
|
+
// Method 2: taskList array (v2 state format)
|
|
44
|
+
if (feature.taskList) {
|
|
45
|
+
const hasActive = feature.taskList.some(t => t.status === 'in_progress');
|
|
46
|
+
if (hasActive) pass();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No task in_progress — inject advisory warning
|
|
50
|
+
approve(
|
|
51
|
+
`[morph-spec] WARNING: No active task tracking detected during implement phase.\n` +
|
|
52
|
+
`Before writing code, start a task:\n` +
|
|
53
|
+
` npx morph-spec task next ${name} # see what's next\n` +
|
|
54
|
+
` npx morph-spec task start ${name} <id> # mark it in progress\n` +
|
|
55
|
+
`Task tracking ensures validators run, checkpoints fire, and progress is recorded.`
|
|
56
|
+
);
|
|
57
|
+
} catch {
|
|
58
|
+
// Fail-open
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|