@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
|
@@ -9,14 +9,12 @@ import { useDetailPanel } from '../hooks/useDetailPanel.js';
|
|
|
9
9
|
const PHASE_COLORS = {
|
|
10
10
|
proposal: { bg: '#166534', border: '#22c55e', text: '#bbf7d0' },
|
|
11
11
|
uiux: { bg: '#854d0e', border: '#f59e0b', text: '#fef3c7' },
|
|
12
|
-
design: { bg: '#1e40af', border: '#60a5fa', text: '#dbeafe' },
|
|
13
12
|
plan: { bg: '#7c2d12', border: '#fb923c', text: '#fed7aa' },
|
|
14
|
-
tasks: { bg: '#6d28d9', border: '#a78bfa', text: '#ede9fe' },
|
|
15
13
|
implement: { bg: '#0e7490', border: '#22d3ee', text: '#cffafe' },
|
|
16
|
-
|
|
14
|
+
review: { bg: '#6d28d9', border: '#a78bfa', text: '#ede9fe' },
|
|
17
15
|
};
|
|
18
16
|
|
|
19
|
-
const GATED_PHASES = new Set(['uiux', '
|
|
17
|
+
const GATED_PHASES = new Set(['uiux', 'plan', 'review']);
|
|
20
18
|
|
|
21
19
|
// Gate node: small gold square with "G" label
|
|
22
20
|
function GateNode({ data }) {
|
|
@@ -7,10 +7,9 @@ import DetailPanel from '../components/DetailPanel.jsx';
|
|
|
7
7
|
import SearchFilter from '../components/SearchFilter.jsx';
|
|
8
8
|
import { useDetailPanel } from '../hooks/useDetailPanel.js';
|
|
9
9
|
|
|
10
|
-
// Colors for skill
|
|
10
|
+
// Colors for skill categories (v5 — all skills in skills/core/)
|
|
11
11
|
const LEVEL_COLORS = {
|
|
12
|
-
'
|
|
13
|
-
'level-1-workflows': { bg: '#1e3a5f', border: '#38bdf8', label: 'L1 Workflows' },
|
|
12
|
+
'core': { bg: '#134e4a', border: '#14b8a6', label: 'Core' },
|
|
14
13
|
};
|
|
15
14
|
const FALLBACK_LEVEL = { bg: '#1a2e1a', border: '#4ade80', label: 'Skill' };
|
|
16
15
|
|
|
@@ -11,8 +11,8 @@ const HoverContext = createContext({ hoveredPhase: null, setHoveredPhase: () =>
|
|
|
11
11
|
|
|
12
12
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
-
// Canonical phase order (we render exactly these
|
|
15
|
-
const PHASE_ORDER = ['proposal', 'uiux', '
|
|
14
|
+
// Canonical phase order (we render exactly these 5 columns)
|
|
15
|
+
const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
|
|
16
16
|
|
|
17
17
|
const PHASE_X_GAP = 200;
|
|
18
18
|
const PHASE_W = 160;
|
|
@@ -1,35 +1,30 @@
|
|
|
1
|
-
import { readFileSync,
|
|
2
|
-
import { join
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
stacks: agent.stacks || [], prompt: promptMap[id] || '',
|
|
32
|
-
teammate: agent.teammate || null,
|
|
33
|
-
}));
|
|
34
|
-
return { version: agentsJson.version, total: agentsJson.total_agents, tiers: agentsJson.tiers, list };
|
|
35
|
-
}
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scan agents.json (v5) — flat domain personas, no tiers.
|
|
6
|
+
* Returns the list shaped for the dashboard inspector.
|
|
7
|
+
*/
|
|
8
|
+
export function scanAgents(frameworkDir) {
|
|
9
|
+
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
10
|
+
if (!existsSync(agentsJsonPath)) {
|
|
11
|
+
return { version: 'unknown', total: 0, list: [] };
|
|
12
|
+
}
|
|
13
|
+
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf8'));
|
|
14
|
+
const list = Object.entries(agentsJson.agents).map(([id, agent]) => ({
|
|
15
|
+
id,
|
|
16
|
+
title: agent.title,
|
|
17
|
+
icon: agent.icon || null,
|
|
18
|
+
color: agent.color || null,
|
|
19
|
+
domains: agent.domains || [],
|
|
20
|
+
keywords: agent.keywords || [],
|
|
21
|
+
standards: agent.standards || [],
|
|
22
|
+
stack: agent.stack || ['*'],
|
|
23
|
+
persona: agent.persona || '',
|
|
24
|
+
}));
|
|
25
|
+
return {
|
|
26
|
+
version: agentsJson.version,
|
|
27
|
+
total: agentsJson.total_agents || list.length,
|
|
28
|
+
list,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -32,7 +32,7 @@ export function aggregateProjectMetrics(state) {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
const totalFeatures = featureMetrics.length;
|
|
35
|
-
const completedFeatures = featureMetrics.filter(f => f.phase === '
|
|
35
|
+
const completedFeatures = featureMetrics.filter(f => f.phase === 'review').length;
|
|
36
36
|
const totalTasks = featureMetrics.reduce((sum, f) => sum + f.taskCount, 0);
|
|
37
37
|
const completedTasks = featureMetrics.reduce((sum, f) => sum + f.completedTasks, 0);
|
|
38
38
|
const totalDispatches = featureMetrics.reduce((sum, f) => sum + f.dispatchCount, 0);
|
|
@@ -14,24 +14,22 @@ import { OUTPUT_SCHEMA, getAbsoluteOutputPath } from '../../core/paths/output-sc
|
|
|
14
14
|
*/
|
|
15
15
|
export const SOURCE_WEIGHTS = {
|
|
16
16
|
proposal: 1.0,
|
|
17
|
-
|
|
17
|
+
stackScan: 0.8,
|
|
18
|
+
migrationPlan: 0.8,
|
|
18
19
|
spec: 1.5,
|
|
19
|
-
|
|
20
|
-
contractsTs: 1.0,
|
|
21
|
-
contractsVsa: 1.0,
|
|
22
|
-
decisions: 1.0,
|
|
23
|
-
plan: 1.2,
|
|
20
|
+
mandate: 1.3,
|
|
24
21
|
tasks: 1.3,
|
|
22
|
+
decisions: 1.0,
|
|
25
23
|
uiDesignSystem: 1.0,
|
|
26
24
|
uiMockups: 1.0,
|
|
27
25
|
uiComponents: 1.0,
|
|
28
26
|
uiFlows: 1.0,
|
|
29
|
-
stitchHtml: 1.0,
|
|
30
27
|
recap: 1.0,
|
|
28
|
+
reviewReport: 1.0,
|
|
31
29
|
};
|
|
32
30
|
|
|
33
|
-
/** Phase order for sorting collected outputs */
|
|
34
|
-
const PHASE_ORDER = ['proposal', 'uiux', '
|
|
31
|
+
/** Phase order for sorting collected outputs (v5 NLH model) */
|
|
32
|
+
const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
35
|
* Collect all existing phase output files for a feature.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Context-Aware Dispatch — Agent Context Builder
|
|
3
|
+
*
|
|
4
|
+
* Assembles curated context payloads per agent, based on domain persona,
|
|
5
|
+
* phase, and active task. Agents get only what they need — not everything.
|
|
6
|
+
*
|
|
7
|
+
* @module context-builder
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const AGENTS_JSON_PATH = join(__dirname, '../../../framework/agents.json');
|
|
17
|
+
const STANDARDS_JSON_PATH = join(__dirname, '../../../framework/standards/STANDARDS.json');
|
|
18
|
+
|
|
19
|
+
let _agentsCache = null;
|
|
20
|
+
let _standardsCache = null;
|
|
21
|
+
|
|
22
|
+
function loadAgents() {
|
|
23
|
+
if (_agentsCache) return _agentsCache;
|
|
24
|
+
if (!existsSync(AGENTS_JSON_PATH)) return { agents: {} };
|
|
25
|
+
try {
|
|
26
|
+
_agentsCache = JSON.parse(readFileSync(AGENTS_JSON_PATH, 'utf8'));
|
|
27
|
+
return _agentsCache;
|
|
28
|
+
} catch { return { agents: {} }; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadStandards() {
|
|
32
|
+
if (_standardsCache) return _standardsCache;
|
|
33
|
+
if (!existsSync(STANDARDS_JSON_PATH)) return { standards: [] };
|
|
34
|
+
try {
|
|
35
|
+
_standardsCache = JSON.parse(readFileSync(STANDARDS_JSON_PATH, 'utf8'));
|
|
36
|
+
return _standardsCache;
|
|
37
|
+
} catch { return { standards: [] }; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Max chars for spec content (flat — v5 has no tier hierarchy) */
|
|
41
|
+
const SPEC_BUDGET = 8000;
|
|
42
|
+
|
|
43
|
+
/** Max number of standard digests */
|
|
44
|
+
const STANDARDS_BUDGET = 10;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Context assembly rules by phase.
|
|
48
|
+
* Controls which context sections are included.
|
|
49
|
+
*/
|
|
50
|
+
const PHASE_CONTEXT_PROFILES = {
|
|
51
|
+
proposal: {
|
|
52
|
+
includeSpec: false,
|
|
53
|
+
includeStandards: false,
|
|
54
|
+
includeTask: false,
|
|
55
|
+
includeCodePaths: false,
|
|
56
|
+
includePhaseArtifacts: false,
|
|
57
|
+
},
|
|
58
|
+
uiux: {
|
|
59
|
+
includeSpec: false,
|
|
60
|
+
includeStandards: true,
|
|
61
|
+
includeTask: false,
|
|
62
|
+
includeCodePaths: false,
|
|
63
|
+
includePhaseArtifacts: false,
|
|
64
|
+
},
|
|
65
|
+
plan: {
|
|
66
|
+
includeSpec: true,
|
|
67
|
+
includeStandards: true,
|
|
68
|
+
includeTask: false,
|
|
69
|
+
includeCodePaths: false,
|
|
70
|
+
includePhaseArtifacts: true,
|
|
71
|
+
},
|
|
72
|
+
implement: {
|
|
73
|
+
includeSpec: true,
|
|
74
|
+
includeStandards: true,
|
|
75
|
+
includeTask: true,
|
|
76
|
+
includeCodePaths: true,
|
|
77
|
+
includePhaseArtifacts: false,
|
|
78
|
+
},
|
|
79
|
+
review: {
|
|
80
|
+
includeSpec: true,
|
|
81
|
+
includeStandards: true,
|
|
82
|
+
includeTask: false,
|
|
83
|
+
includeCodePaths: true,
|
|
84
|
+
includePhaseArtifacts: true,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract only the FR sections referenced by a task.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} specContent - Full spec.md content
|
|
92
|
+
* @param {string[]} frIds - FR IDs (e.g., ['FR001', 'FR003'])
|
|
93
|
+
* @returns {string} Scoped spec with only referenced FRs
|
|
94
|
+
*/
|
|
95
|
+
export function scopeSpecToFRs(specContent, frIds) {
|
|
96
|
+
if (!frIds || frIds.length === 0 || !specContent) return specContent;
|
|
97
|
+
|
|
98
|
+
const sections = [];
|
|
99
|
+
|
|
100
|
+
// Always include overview/context sections
|
|
101
|
+
const overviewMatch = specContent.match(/^(#[^#][\s\S]*?)(?=\n##\s+FR\d)/m);
|
|
102
|
+
if (overviewMatch) {
|
|
103
|
+
sections.push(overviewMatch[1].trim());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Extract each referenced FR section
|
|
107
|
+
for (const frId of frIds) {
|
|
108
|
+
const pattern = new RegExp(
|
|
109
|
+
`(##\\s+${frId}[:\\s—–\\-][\\s\\S]*?)(?=\\n##\\s+FR\\d|\\n##\\s+[^F]|$)`,
|
|
110
|
+
'm'
|
|
111
|
+
);
|
|
112
|
+
const match = pattern.exec(specContent);
|
|
113
|
+
if (match) {
|
|
114
|
+
sections.push(match[1].trim());
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return sections.length > 0 ? sections.join('\n\n---\n\n') : specContent;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build standard digests for an agent based on their standards[] array.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} agentId - Agent ID from agents.json
|
|
125
|
+
* @returns {string[]} Array of digest paragraphs
|
|
126
|
+
*/
|
|
127
|
+
export function buildAgentDigests(agentId) {
|
|
128
|
+
const agents = loadAgents();
|
|
129
|
+
const agentData = agents.agents?.[agentId];
|
|
130
|
+
if (!agentData?.standards || agentData.standards.length === 0) return [];
|
|
131
|
+
|
|
132
|
+
const standards = loadStandards();
|
|
133
|
+
const standardsList = standards.standards || [];
|
|
134
|
+
|
|
135
|
+
const digests = [];
|
|
136
|
+
for (const stdId of agentData.standards) {
|
|
137
|
+
const std = standardsList.find(s => s.id === stdId);
|
|
138
|
+
if (std?.digest) {
|
|
139
|
+
digests.push(`[${std.id}] ${std.name}: ${std.digest}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return digests;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Extract FR IDs from a task's title/body in tasks.md format.
|
|
148
|
+
* Format: ### T001 — Title [FR001, FR003]
|
|
149
|
+
*
|
|
150
|
+
* @param {string} taskTitle - Task title that may contain [FR...] references
|
|
151
|
+
* @param {string} [taskBlock] - Full task block for body references
|
|
152
|
+
* @returns {string[]}
|
|
153
|
+
*/
|
|
154
|
+
export function extractFRIds(taskTitle, taskBlock) {
|
|
155
|
+
const frIds = new Set();
|
|
156
|
+
|
|
157
|
+
// From title: [FR001, FR003]
|
|
158
|
+
const titleMatch = (taskTitle || '').match(/\[([^\]]*FR\d+[^\]]*)\]/);
|
|
159
|
+
if (titleMatch) {
|
|
160
|
+
const matches = titleMatch[1].match(/FR\d+/g);
|
|
161
|
+
if (matches) matches.forEach(id => frIds.add(id));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// From body: **FRs:** FR001, FR002
|
|
165
|
+
if (taskBlock) {
|
|
166
|
+
const bodyMatch = taskBlock.match(/\*\*FRs?:\*\*\s*(.+)/i);
|
|
167
|
+
if (bodyMatch) {
|
|
168
|
+
const matches = bodyMatch[1].match(/FR\d+/g);
|
|
169
|
+
if (matches) matches.forEach(id => frIds.add(id));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return [...frIds];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build a curated context payload for a specific agent.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} agentId - Agent ID from agents.json
|
|
180
|
+
* @param {Object} options
|
|
181
|
+
* @param {string} options.feature - Feature name
|
|
182
|
+
* @param {string} options.phase - Current phase
|
|
183
|
+
* @param {string} [options.taskId] - Active task ID (implement phase)
|
|
184
|
+
* @param {string} [options.projectPath='.'] - Project root
|
|
185
|
+
* @returns {AgentContext}
|
|
186
|
+
*
|
|
187
|
+
* @typedef {Object} AgentContext
|
|
188
|
+
* @property {string|null} spec - Full spec or task-scoped section
|
|
189
|
+
* @property {string[]} standards - Digest paragraphs for this agent's standards
|
|
190
|
+
* @property {string|null} task - Mandate task section or null
|
|
191
|
+
* @property {string[]} codeContext - Relevant file paths
|
|
192
|
+
* @property {string|null} phaseArtifacts - Previous phase outputs
|
|
193
|
+
* @property {Object} budget - Context budget hints
|
|
194
|
+
*/
|
|
195
|
+
export async function buildAgentContext(agentId, { feature, phase, taskId, projectPath = '.' }) {
|
|
196
|
+
const agents = loadAgents();
|
|
197
|
+
const profile = PHASE_CONTEXT_PROFILES[phase] || PHASE_CONTEXT_PROFILES.implement;
|
|
198
|
+
const maxSpecChars = SPEC_BUDGET;
|
|
199
|
+
const maxStandards = STANDARDS_BUDGET;
|
|
200
|
+
|
|
201
|
+
const result = {
|
|
202
|
+
spec: null,
|
|
203
|
+
standards: [],
|
|
204
|
+
task: null,
|
|
205
|
+
codeContext: [],
|
|
206
|
+
phaseArtifacts: null,
|
|
207
|
+
budget: { maxSpecChars, maxStandards },
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Standards digests
|
|
211
|
+
if (profile.includeStandards) {
|
|
212
|
+
result.standards = buildAgentDigests(agentId).slice(0, maxStandards);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Spec content (scoped by task FRs when possible)
|
|
216
|
+
if (profile.includeSpec) {
|
|
217
|
+
try {
|
|
218
|
+
const specPath = getAbsoluteOutputPath(projectPath, feature, 'spec');
|
|
219
|
+
if (existsSync(specPath)) {
|
|
220
|
+
let specContent = readFileSync(specPath, 'utf8');
|
|
221
|
+
|
|
222
|
+
// If task-scoped, extract mandate section for this task
|
|
223
|
+
if (taskId) {
|
|
224
|
+
const mandatePath = getAbsoluteOutputPath(projectPath, feature, 'mandate');
|
|
225
|
+
if (existsSync(mandatePath)) {
|
|
226
|
+
try {
|
|
227
|
+
const { extractTaskSection } = await import('../../lib/generators/mandate-generator.js');
|
|
228
|
+
const section = extractTaskSection(readFileSync(mandatePath, 'utf8'), taskId);
|
|
229
|
+
if (section) {
|
|
230
|
+
result.spec = section.slice(0, maxSpecChars);
|
|
231
|
+
result.task = section;
|
|
232
|
+
}
|
|
233
|
+
} catch { /* fall through to raw spec */ }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!result.spec) {
|
|
238
|
+
result.spec = specContent.slice(0, maxSpecChars);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch { /* spec not available */ }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Task mandate section
|
|
245
|
+
if (profile.includeTask && taskId && !result.task) {
|
|
246
|
+
try {
|
|
247
|
+
const mandatePath = getAbsoluteOutputPath(projectPath, feature, 'mandate');
|
|
248
|
+
if (existsSync(mandatePath)) {
|
|
249
|
+
const { extractTaskSection } = await import('../../lib/generators/mandate-generator.js');
|
|
250
|
+
const section = extractTaskSection(readFileSync(mandatePath, 'utf8'), taskId);
|
|
251
|
+
if (section) result.task = section;
|
|
252
|
+
}
|
|
253
|
+
} catch { /* mandate not available */ }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Code paths from task
|
|
257
|
+
if (profile.includeCodePaths && taskId) {
|
|
258
|
+
try {
|
|
259
|
+
const planPath = getAbsoluteOutputPath(projectPath, feature, 'plan');
|
|
260
|
+
if (existsSync(planPath)) {
|
|
261
|
+
const planContent = readFileSync(planPath, 'utf8');
|
|
262
|
+
const backtickPaths = planContent.match(/`([^`]+\.[a-z]{1,4})`/g);
|
|
263
|
+
if (backtickPaths) {
|
|
264
|
+
result.codeContext = [...new Set(backtickPaths.map(p => p.replace(/`/g, '')))].slice(0, 20);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch { /* plan not available */ }
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Phase artifacts (decisions, previous outputs)
|
|
271
|
+
if (profile.includePhaseArtifacts) {
|
|
272
|
+
try {
|
|
273
|
+
const decisionsPath = getAbsoluteOutputPath(projectPath, feature, 'decisions');
|
|
274
|
+
if (existsSync(decisionsPath)) {
|
|
275
|
+
result.phaseArtifacts = readFileSync(decisionsPath, 'utf8').slice(0, 3000);
|
|
276
|
+
}
|
|
277
|
+
} catch { /* decisions not available */ }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Invalidate module-level caches. Used in tests.
|
|
285
|
+
*/
|
|
286
|
+
export function clearCache() {
|
|
287
|
+
_agentsCache = null;
|
|
288
|
+
_standardsCache = null;
|
|
289
|
+
}
|
|
@@ -27,8 +27,23 @@ export function detectClaudeConfig(targetPath) {
|
|
|
27
27
|
hooks: [],
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
const mcpJsonNames = new Set();
|
|
31
|
+
|
|
30
32
|
try {
|
|
31
|
-
// ── MCP Servers from
|
|
33
|
+
// ── MCP Servers from .mcp.json (primary — Claude Code reads this) ───────
|
|
34
|
+
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
35
|
+
if (existsSync(mcpJsonPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const mcpJson = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
|
|
38
|
+
const mcps = mcpJson.mcpServers || {};
|
|
39
|
+
for (const [name, config] of Object.entries(mcps)) {
|
|
40
|
+
result.mcpServers.push({ name, config });
|
|
41
|
+
mcpJsonNames.add(name);
|
|
42
|
+
}
|
|
43
|
+
} catch { /* invalid JSON */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── MCP Servers + hooks from settings (legacy + hooks/permissions) ───────
|
|
32
47
|
const settingsPaths = [
|
|
33
48
|
join(targetPath, '.claude', 'settings.json'),
|
|
34
49
|
join(targetPath, '.claude', 'settings.local.json'),
|
|
@@ -38,12 +53,13 @@ export function detectClaudeConfig(targetPath) {
|
|
|
38
53
|
if (existsSync(sp)) {
|
|
39
54
|
try {
|
|
40
55
|
const settings = JSON.parse(readFileSync(sp, 'utf8'));
|
|
56
|
+
// Legacy MCP entries — skip if already in .mcp.json, but allow later settings files to override earlier ones
|
|
41
57
|
const mcps = settings.mcpServers || {};
|
|
42
58
|
for (const [name, config] of Object.entries(mcps)) {
|
|
43
|
-
|
|
59
|
+
if (mcpJsonNames.has(name)) continue; // .mcp.json takes priority
|
|
44
60
|
const idx = result.mcpServers.findIndex(m => m.name === name);
|
|
45
61
|
if (idx >= 0) {
|
|
46
|
-
result.mcpServers[idx] = { name, config };
|
|
62
|
+
result.mcpServers[idx] = { name, config }; // later file wins
|
|
47
63
|
} else {
|
|
48
64
|
result.mcpServers.push({ name, config });
|
|
49
65
|
}
|
|
@@ -15,7 +15,6 @@ import fs from 'fs/promises';
|
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import { existsSync } from 'fs';
|
|
17
17
|
import { getOutputPath } from '../../core/paths/output-schema.js';
|
|
18
|
-
import { tierToRole } from '../../utils/tier-helpers.js';
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Resolve a template path: check .morph/framework/templates/ first, then framework/templates/.
|
|
@@ -253,29 +252,20 @@ export async function generateProjectContext(projectPath) {
|
|
|
253
252
|
// Project standards
|
|
254
253
|
PROJECT_STANDARDS: standards,
|
|
255
254
|
|
|
256
|
-
// Agents
|
|
257
|
-
AGENT_COUNT: agents?.total_agents ||
|
|
258
|
-
|
|
259
|
-
.filter(([id
|
|
255
|
+
// Agents (v5: flat personas, no tiers)
|
|
256
|
+
AGENT_COUNT: agents?.total_agents || Object.keys(agents?.agents || {}).length,
|
|
257
|
+
PERSONAS: Object.entries(agents?.agents || {})
|
|
258
|
+
.filter(([id]) => !id.startsWith('_'))
|
|
260
259
|
.map(([id, agent]) => ({
|
|
261
260
|
id,
|
|
262
261
|
title: agent.title,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
TIER_2_AGENTS: Object.entries(agents?.agents || {})
|
|
266
|
-
.filter(([id, agent]) => !id.startsWith('_') && agent.tier === 2)
|
|
267
|
-
.map(([id, agent]) => ({
|
|
268
|
-
id,
|
|
269
|
-
title: agent.title,
|
|
270
|
-
domains: (agent.domains || []).join(', ')
|
|
271
|
-
})),
|
|
272
|
-
TIER_3_AGENTS: Object.entries(agents?.agents || {})
|
|
273
|
-
.filter(([id, agent]) => !id.startsWith('_') && agent.tier === 3)
|
|
274
|
-
.map(([id, agent]) => ({
|
|
275
|
-
id,
|
|
276
|
-
title: agent.title,
|
|
277
|
-
reports_to: agent.reports_to || agent.relationships?.reports_to || 'N/A'
|
|
262
|
+
domains: (agent.domains || []).join(', '),
|
|
263
|
+
stack: (agent.stack || ['*']).join(', ')
|
|
278
264
|
})),
|
|
265
|
+
// v4 compat (kept empty so legacy templates still render)
|
|
266
|
+
TIER_1_AGENTS: [],
|
|
267
|
+
TIER_2_AGENTS: [],
|
|
268
|
+
TIER_3_AGENTS: [],
|
|
279
269
|
|
|
280
270
|
// Agent Teams config
|
|
281
271
|
AGENT_TEAMS_ENABLED: config?.agentTeams?.enabled || false,
|
|
@@ -342,7 +332,7 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
342
332
|
let tasks = [];
|
|
343
333
|
|
|
344
334
|
try {
|
|
345
|
-
const decisionsPath = path.join(outputsDir, '2-
|
|
335
|
+
const decisionsPath = path.join(outputsDir, '2-plan', 'decisions.md');
|
|
346
336
|
const decisionsContent = await fs.readFile(decisionsPath, 'utf8');
|
|
347
337
|
// Parse decisions.md (simplified - just extract ADR headers)
|
|
348
338
|
const adrMatches = decisionsContent.matchAll(/##\s+ADR-(\d+):\s+(.+)/g);
|
|
@@ -359,7 +349,7 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
359
349
|
}
|
|
360
350
|
|
|
361
351
|
try {
|
|
362
|
-
const tasksPath = path.join(outputsDir, '
|
|
352
|
+
const tasksPath = path.join(outputsDir, '2-plan', 'tasks.md');
|
|
363
353
|
const tasksContent = await fs.readFile(tasksPath, 'utf8');
|
|
364
354
|
tasks = JSON.parse(tasksContent).tasks || [];
|
|
365
355
|
} catch (err) {
|
|
@@ -381,24 +371,12 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
381
371
|
.filter(id => agents?.agents[id])
|
|
382
372
|
.map(id => ({ id, ...agents.agents[id] }));
|
|
383
373
|
|
|
384
|
-
//
|
|
385
|
-
const teamLead =
|
|
386
|
-
const domainLeaders =
|
|
387
|
-
const specialists = activeAgents
|
|
388
|
-
const validators = activeAgents.filter(a =>
|
|
389
|
-
|
|
390
|
-
const squads = domainLeaders.map(leader => ({
|
|
391
|
-
leader: {
|
|
392
|
-
id: leader.id,
|
|
393
|
-
title: leader.title
|
|
394
|
-
},
|
|
395
|
-
members: specialists
|
|
396
|
-
.filter(s => (s.reports_to || s.relationships?.reports_to) === leader.id)
|
|
397
|
-
.map(s => ({
|
|
398
|
-
id: s.id,
|
|
399
|
-
title: s.title
|
|
400
|
-
}))
|
|
401
|
-
}));
|
|
374
|
+
// v5: flat persona model — no tiers, no squads. Just list active personas.
|
|
375
|
+
const teamLead = null;
|
|
376
|
+
const domainLeaders = [];
|
|
377
|
+
const specialists = activeAgents;
|
|
378
|
+
const validators = activeAgents.filter(a => a.id === 'evaluator');
|
|
379
|
+
const squads = [];
|
|
402
380
|
|
|
403
381
|
// Build data object
|
|
404
382
|
const data = {
|
|
@@ -484,12 +462,12 @@ export async function generateFeatureContext(projectPath, featureName) {
|
|
|
484
462
|
PROPOSAL_PATH: getOutputPath(featureName, 'proposal'),
|
|
485
463
|
SPEC_PATH: getOutputPath(featureName, 'spec'),
|
|
486
464
|
CONTRACTS_PATH: getOutputPath(featureName, 'contracts'),
|
|
487
|
-
TASKS_PATH: `.morph/features/${featureName}/
|
|
465
|
+
TASKS_PATH: `.morph/features/${featureName}/2-plan/tasks.md`,
|
|
488
466
|
DECISIONS_PATH: getOutputPath(featureName, 'decisions'),
|
|
489
467
|
RECAP_PATH: getOutputPath(featureName, 'recap'),
|
|
490
468
|
|
|
491
469
|
// Version
|
|
492
|
-
MORPH_VERSION: '
|
|
470
|
+
MORPH_VERSION: '5.0.0',
|
|
493
471
|
FEATURE_ID: feature.id || featureName
|
|
494
472
|
};
|
|
495
473
|
|