@polymorphism-tech/morph-spec 4.22.0 → 6.0.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 +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1198
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -152
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -9
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Phase Validator
|
|
3
|
+
*
|
|
4
|
+
* Validates consistency across morph-spec phase outputs:
|
|
5
|
+
* - FR coverage: every spec FR is referenced by at least one task
|
|
6
|
+
* - Mandate integrity: mandate includes all task IDs with acceptance criteria
|
|
7
|
+
* - Contract alignment: entities in spec Data Model appear in contracts
|
|
8
|
+
* - Dependency ordering: task dependencies are topologically valid
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync, existsSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { parseFRSections, parseContracts, extractTaskIds } from '../../generators/mandate-generator.js';
|
|
14
|
+
import { getAbsoluteOutputPath } from '../../../core/paths/output-schema.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* v5: parseTaskFromMarkdown lived in sprint-contract-generator. Inlined here as
|
|
18
|
+
* a minimal parser that extracts FR references from a task block.
|
|
19
|
+
*/
|
|
20
|
+
function parseTaskFromMarkdown(tasksContent, taskId) {
|
|
21
|
+
const headingRe = new RegExp(`^### ${taskId}\\b[^\\n]*\\n([\\s\\S]*?)(?=^### |\\Z)`, 'm');
|
|
22
|
+
const match = tasksContent.match(headingRe);
|
|
23
|
+
if (!match) return null;
|
|
24
|
+
const block = match[1];
|
|
25
|
+
const frRefs = [...block.matchAll(/\bFR-?\d+\b/g)].map(m => m[0]);
|
|
26
|
+
return { id: taskId, frs: [...new Set(frRefs)], block };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compute FR coverage — how well tasks.md covers spec.md FRs.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} specContent - Full spec.md content
|
|
33
|
+
* @param {string} tasksContent - Full tasks.md content
|
|
34
|
+
* @returns {{ ratio: number, coveredFRs: string[], uncoveredFRs: string[], taskFRRefs: Map<string, string[]> }}
|
|
35
|
+
*/
|
|
36
|
+
export function computeFRCoverage(specContent, tasksContent) {
|
|
37
|
+
const frSections = parseFRSections(specContent);
|
|
38
|
+
const taskIds = extractTaskIds(tasksContent);
|
|
39
|
+
|
|
40
|
+
// Collect all FR references from tasks
|
|
41
|
+
const allReferencedFRs = new Set();
|
|
42
|
+
const taskFRRefs = new Map();
|
|
43
|
+
|
|
44
|
+
for (const taskId of taskIds) {
|
|
45
|
+
const task = parseTaskFromMarkdown(tasksContent, taskId);
|
|
46
|
+
if (task && task.frs.length > 0) {
|
|
47
|
+
taskFRRefs.set(taskId, task.frs);
|
|
48
|
+
for (const fr of task.frs) {
|
|
49
|
+
allReferencedFRs.add(fr);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const specFRIds = [...frSections.keys()];
|
|
55
|
+
const coveredFRs = specFRIds.filter(fr => allReferencedFRs.has(fr));
|
|
56
|
+
const uncoveredFRs = specFRIds.filter(fr => !allReferencedFRs.has(fr));
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
ratio: specFRIds.length > 0 ? coveredFRs.length / specFRIds.length : 1,
|
|
60
|
+
coveredFRs,
|
|
61
|
+
uncoveredFRs,
|
|
62
|
+
taskFRRefs,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check mandate integrity — does the mandate include all tasks with acceptance criteria?
|
|
68
|
+
*
|
|
69
|
+
* @param {string} mandateContent - Full mandate.md content
|
|
70
|
+
* @param {string} tasksContent - Full tasks.md content
|
|
71
|
+
* @returns {{ complete: boolean, ratio: number, missingTasks: string[], tasksWithCriteria: string[], tasksWithoutCriteria: string[] }}
|
|
72
|
+
*/
|
|
73
|
+
export function checkMandateIntegrity(mandateContent, tasksContent) {
|
|
74
|
+
const taskIds = extractTaskIds(tasksContent);
|
|
75
|
+
const missingTasks = [];
|
|
76
|
+
const tasksWithCriteria = [];
|
|
77
|
+
const tasksWithoutCriteria = [];
|
|
78
|
+
|
|
79
|
+
for (const taskId of taskIds) {
|
|
80
|
+
const taskPattern = new RegExp(`^## ${taskId}:`, 'm');
|
|
81
|
+
if (!taskPattern.test(mandateContent)) {
|
|
82
|
+
missingTasks.push(taskId);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if this task section has acceptance criteria
|
|
87
|
+
const sectionStart = mandateContent.indexOf(`## ${taskId}:`);
|
|
88
|
+
const nextSection = mandateContent.slice(sectionStart + 1).search(/\n## /);
|
|
89
|
+
const sectionEnd = nextSection === -1 ? mandateContent.length : sectionStart + 1 + nextSection;
|
|
90
|
+
const section = mandateContent.slice(sectionStart, sectionEnd);
|
|
91
|
+
|
|
92
|
+
if (/### Acceptance Criteria/i.test(section) && /- \[[ x]\]/.test(section)) {
|
|
93
|
+
tasksWithCriteria.push(taskId);
|
|
94
|
+
} else {
|
|
95
|
+
tasksWithoutCriteria.push(taskId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const includedCount = taskIds.length - missingTasks.length;
|
|
100
|
+
return {
|
|
101
|
+
complete: missingTasks.length === 0,
|
|
102
|
+
ratio: taskIds.length > 0 ? includedCount / taskIds.length : 1,
|
|
103
|
+
missingTasks,
|
|
104
|
+
tasksWithCriteria,
|
|
105
|
+
tasksWithoutCriteria,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check contract alignment — do entities in spec Data Model appear in contracts?
|
|
111
|
+
*
|
|
112
|
+
* @param {string} specContent - Full spec.md content
|
|
113
|
+
* @param {string} contractsContent - Full contracts file content (CS or TS)
|
|
114
|
+
* @returns {{ aligned: boolean, ratio: number, matchedEntities: string[], unmatchedEntities: string[] }}
|
|
115
|
+
*/
|
|
116
|
+
export function checkContractAlignment(specContent, contractsContent) {
|
|
117
|
+
// Extract entity names from Data Model section in spec
|
|
118
|
+
const dataModelMatch = specContent.match(
|
|
119
|
+
/^##\s+(?:\d+\.?\s+)?Data Model\s*\n([\s\S]*?)(?=\n##\s|$)/im
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (!dataModelMatch) {
|
|
123
|
+
return { aligned: true, ratio: 1, matchedEntities: [], unmatchedEntities: [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const dataModelSection = dataModelMatch[1];
|
|
127
|
+
// Look for entity names: typically bold, backticked, or in headings
|
|
128
|
+
const entityPatterns = [
|
|
129
|
+
/\*\*(\w+)\*\*/g, // **Entity**
|
|
130
|
+
/`(\w+)`/g, // `Entity`
|
|
131
|
+
/^###\s+(\w+)/gm, // ### Entity
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const entities = new Set();
|
|
135
|
+
for (const pattern of entityPatterns) {
|
|
136
|
+
let match;
|
|
137
|
+
while ((match = pattern.exec(dataModelSection)) !== null) {
|
|
138
|
+
const name = match[1];
|
|
139
|
+
// Filter out common non-entity words
|
|
140
|
+
if (name.length > 2 && !/^(The|This|Each|All|and|for|with|from|that|has|can|will|not|are)$/i.test(name)) {
|
|
141
|
+
entities.add(name);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (entities.size === 0) {
|
|
147
|
+
return { aligned: true, ratio: 1, matchedEntities: [], unmatchedEntities: [] };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const contracts = parseContracts(contractsContent);
|
|
151
|
+
const contractNames = [...contracts.keys()];
|
|
152
|
+
const contractNamesLower = contractNames.map(n => n.toLowerCase());
|
|
153
|
+
|
|
154
|
+
const matchedEntities = [];
|
|
155
|
+
const unmatchedEntities = [];
|
|
156
|
+
|
|
157
|
+
for (const entity of entities) {
|
|
158
|
+
const entityLower = entity.toLowerCase();
|
|
159
|
+
// Check if entity appears in any contract name (partial match)
|
|
160
|
+
const found = contractNamesLower.some(cn => cn.includes(entityLower) || entityLower.includes(cn));
|
|
161
|
+
if (found) {
|
|
162
|
+
matchedEntities.push(entity);
|
|
163
|
+
} else {
|
|
164
|
+
unmatchedEntities.push(entity);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
aligned: unmatchedEntities.length === 0,
|
|
170
|
+
ratio: entities.size > 0 ? matchedEntities.length / entities.size : 1,
|
|
171
|
+
matchedEntities,
|
|
172
|
+
unmatchedEntities,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Check dependency ordering — are task dependencies topologically valid?
|
|
178
|
+
*
|
|
179
|
+
* @param {string} tasksContent - Full tasks.md content
|
|
180
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
181
|
+
*/
|
|
182
|
+
export function checkDependencyOrdering(tasksContent) {
|
|
183
|
+
const taskIds = extractTaskIds(tasksContent);
|
|
184
|
+
const errors = [];
|
|
185
|
+
|
|
186
|
+
// Build dependency graph
|
|
187
|
+
const depPattern = /\*\*Dependenc(?:y|ies):\*\*\s*([T\d, ]+)/gi;
|
|
188
|
+
const taskIdSet = new Set(taskIds);
|
|
189
|
+
const taskOrder = new Map(taskIds.map((id, i) => [id, i]));
|
|
190
|
+
|
|
191
|
+
// Parse each task's dependencies
|
|
192
|
+
for (const taskId of taskIds) {
|
|
193
|
+
const task = parseTaskFromMarkdown(tasksContent, taskId);
|
|
194
|
+
if (!task) continue;
|
|
195
|
+
|
|
196
|
+
// Extract dependencies from task description
|
|
197
|
+
const depMatches = [...task.description.matchAll(depPattern)];
|
|
198
|
+
for (const depMatch of depMatches) {
|
|
199
|
+
const deps = depMatch[1].split(/[,\s]+/).filter(s => /^T\d{3}$/.test(s));
|
|
200
|
+
for (const dep of deps) {
|
|
201
|
+
if (!taskIdSet.has(dep)) {
|
|
202
|
+
errors.push(`${taskId} depends on ${dep} which does not exist`);
|
|
203
|
+
} else if (taskOrder.get(dep) >= taskOrder.get(taskId)) {
|
|
204
|
+
errors.push(`${taskId} depends on ${dep} which comes after it (ordering issue)`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
valid: errors.length === 0,
|
|
212
|
+
errors,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Run full cross-phase validation on a feature's outputs.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} projectPath - Project root
|
|
220
|
+
* @param {string} featureName - Feature name
|
|
221
|
+
* @returns {Object} Full validation result with scores per dimension
|
|
222
|
+
*/
|
|
223
|
+
export function validateCrossPhase(projectPath, featureName) {
|
|
224
|
+
const specPath = getAbsoluteOutputPath(projectPath, featureName, 'spec');
|
|
225
|
+
const tasksPath = getAbsoluteOutputPath(projectPath, featureName, 'tasks');
|
|
226
|
+
|
|
227
|
+
const result = {
|
|
228
|
+
valid: true,
|
|
229
|
+
errors: [],
|
|
230
|
+
warnings: [],
|
|
231
|
+
dimensions: {},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Require at minimum spec and tasks
|
|
235
|
+
if (!existsSync(specPath) || !existsSync(tasksPath)) {
|
|
236
|
+
result.valid = false;
|
|
237
|
+
if (!existsSync(specPath)) result.errors.push('spec.md not found');
|
|
238
|
+
if (!existsSync(tasksPath)) result.errors.push('tasks.md not found');
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const specContent = readFileSync(specPath, 'utf-8');
|
|
243
|
+
const tasksContent = readFileSync(tasksPath, 'utf-8');
|
|
244
|
+
|
|
245
|
+
// 1. FR Coverage
|
|
246
|
+
const frCoverage = computeFRCoverage(specContent, tasksContent);
|
|
247
|
+
result.dimensions.frCoverage = frCoverage;
|
|
248
|
+
if (frCoverage.uncoveredFRs.length > 0) {
|
|
249
|
+
result.warnings.push(`Uncovered FRs: ${frCoverage.uncoveredFRs.join(', ')}`);
|
|
250
|
+
}
|
|
251
|
+
if (frCoverage.ratio < 0.9) {
|
|
252
|
+
result.errors.push(`FR coverage too low: ${(frCoverage.ratio * 100).toFixed(0)}% (threshold: 90%)`);
|
|
253
|
+
result.valid = false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. Mandate integrity (if mandate exists)
|
|
257
|
+
const mandatePath = getAbsoluteOutputPath(projectPath, featureName, 'mandate');
|
|
258
|
+
if (existsSync(mandatePath)) {
|
|
259
|
+
const mandateContent = readFileSync(mandatePath, 'utf-8');
|
|
260
|
+
const mandateIntegrity = checkMandateIntegrity(mandateContent, tasksContent);
|
|
261
|
+
result.dimensions.mandateIntegrity = mandateIntegrity;
|
|
262
|
+
if (mandateIntegrity.missingTasks.length > 0) {
|
|
263
|
+
result.errors.push(`Mandate missing tasks: ${mandateIntegrity.missingTasks.join(', ')}`);
|
|
264
|
+
result.valid = false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 3. Contract alignment removed in v5 — contracts live in spec.md (NLH model).
|
|
269
|
+
// See morph-implement skill for source-level contract checks.
|
|
270
|
+
|
|
271
|
+
// 4. Dependency ordering
|
|
272
|
+
const depOrdering = checkDependencyOrdering(tasksContent);
|
|
273
|
+
result.dimensions.dependencyOrdering = depOrdering;
|
|
274
|
+
if (!depOrdering.valid) {
|
|
275
|
+
for (const err of depOrdering.errors) {
|
|
276
|
+
result.warnings.push(`Dependency issue: ${err}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
@@ -1,64 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* PostToolUse dispatch hook can parse the JSON via strategy 1 (from
|
|
6
|
-
* tool_result) without falling back to a subprocess.
|
|
2
|
+
* Stub: validation dispatch was driven by tier-4 agents in v4.
|
|
3
|
+
* In v5, validation is handled by morph-eval skill invoked directly
|
|
4
|
+
* by the Evaluator role. This function is kept for call-site compatibility.
|
|
7
5
|
*
|
|
8
6
|
* @module emit-validator-dispatch
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
import { readFileSync } from 'fs';
|
|
13
|
-
import { join, dirname } from 'path';
|
|
14
|
-
import { fileURLToPath } from 'url';
|
|
15
|
-
|
|
16
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
|
|
18
9
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Non-blocking — fails silently on any error.
|
|
22
|
-
*
|
|
23
|
-
* @param {string} featureName - Feature name
|
|
24
|
-
* @param {string} phase - Current phase
|
|
25
|
-
* @param {string} cwd - Project root
|
|
10
|
+
* No-op in v5. Validation dispatch is now handled by morph-eval skill.
|
|
26
11
|
*/
|
|
27
|
-
export async function emitValidatorDispatch(
|
|
28
|
-
|
|
29
|
-
const { buildDispatchConfig } = await import('../../../commands/agents/dispatch-agents.js');
|
|
30
|
-
const config = await buildDispatchConfig(cwd, featureName, phase, { mode: 'validate' });
|
|
31
|
-
const validators = config.agents?.filter(a => a.tier === 4);
|
|
32
|
-
if (!validators || validators.length === 0) return;
|
|
33
|
-
|
|
34
|
-
const agentsPath = join(__dirname, '..', '..', '..', '..', 'framework', 'agents.json');
|
|
35
|
-
const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
|
|
36
|
-
const allAgents = agentsData.agents || {};
|
|
37
|
-
|
|
38
|
-
const validatorEntries = validators.map(v => {
|
|
39
|
-
const agentData = allAgents[v.id];
|
|
40
|
-
return {
|
|
41
|
-
id: v.id,
|
|
42
|
-
title: v.title,
|
|
43
|
-
severity: agentData?.hook_behavior?.severity || 'error',
|
|
44
|
-
blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
|
|
45
|
-
checks: agentData?.hook_behavior?.validates || [],
|
|
46
|
-
taskPrompt: v.taskPrompt,
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const dispatch = {
|
|
51
|
-
validationRequired: true,
|
|
52
|
-
phase,
|
|
53
|
-
feature: featureName,
|
|
54
|
-
validators: validatorEntries,
|
|
55
|
-
instruction: 'Dispatch these validators as READ-ONLY subagents before continuing. Each must output { "passed": boolean, "issues": [] }. Blocking validators (blocksOnFail: true) must pass before marking phase complete.',
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
|
|
59
|
-
console.log(JSON.stringify(dispatch, null, 2));
|
|
60
|
-
console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
|
|
61
|
-
} catch {
|
|
62
|
-
// Non-blocking — fail silently
|
|
63
|
-
}
|
|
12
|
+
export async function emitValidatorDispatch(_featureName, _phase, _cwd) {
|
|
13
|
+
// v5: evaluation driven by morph-eval skill, not CLI dispatch
|
|
64
14
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { homedir, platform } from 'os';
|
|
5
|
-
import { mkdir,
|
|
5
|
+
import { mkdir, readFile, writeFile } from 'fs/promises';
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
8
|
import { installGlobalStatusline } from '../utils/hooks-installer.js';
|
|
@@ -10,24 +10,6 @@ import { installGlobalStatusline } from '../utils/hooks-installer.js';
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const FRAMEWORK_DIR = join(__dirname, '..', '..', 'framework');
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* Install morph-init skill to a Claude global skills directory.
|
|
15
|
-
* @param {string} claudeDir - target ~/.claude dir (defaults to real ~/.claude)
|
|
16
|
-
*/
|
|
17
|
-
export async function installGlobalSkill(claudeDir = join(homedir(), '.claude')) {
|
|
18
|
-
const src = join(FRAMEWORK_DIR, 'skills', 'level-0-meta', 'morph-init', 'SKILL.md');
|
|
19
|
-
const destDir = join(claudeDir, 'skills', 'morph-init');
|
|
20
|
-
const dest = join(destDir, 'SKILL.md');
|
|
21
|
-
|
|
22
|
-
if (!existsSync(src)) {
|
|
23
|
-
console.warn(`morph-init skill not found at ${src} — skipping global install`);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
await mkdir(destDir, { recursive: true });
|
|
28
|
-
await copyFile(src, dest);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
13
|
/**
|
|
32
14
|
* Ensure the npm global bin directory is in the user's PATH on Windows.
|
|
33
15
|
* Adds it to the PowerShell $PROFILE so new terminals find `morph-spec`.
|
|
@@ -80,19 +62,12 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
80
62
|
const { readFileSync } = await import('fs');
|
|
81
63
|
const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
82
64
|
|
|
83
|
-
const [
|
|
84
|
-
installGlobalSkill(),
|
|
65
|
+
const [statuslineResult, pathResult] = await Promise.allSettled([
|
|
85
66
|
installGlobalStatusline(HOOKS_SRC),
|
|
86
67
|
ensureNpmGlobalInPath(),
|
|
87
68
|
]);
|
|
88
69
|
|
|
89
70
|
const results = [
|
|
90
|
-
{
|
|
91
|
-
ok: skillResult.status === 'fulfilled',
|
|
92
|
-
label: 'morph-init skill',
|
|
93
|
-
path: '~/.claude/skills/',
|
|
94
|
-
error: skillResult.reason?.message,
|
|
95
|
-
},
|
|
96
71
|
{
|
|
97
72
|
ok: statuslineResult.status === 'fulfilled',
|
|
98
73
|
label: 'statusline',
|