@polymorphism-tech/morph-spec 4.21.0 → 5.0.1

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.
Files changed (213) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1183
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -28
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -210
  35. package/framework/phases.json +40 -114
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/standards/STANDARDS.json +32 -0
  47. package/framework/standards/frontend/design-system/ai-image-generation.md +244 -0
  48. package/framework/standards/integration/mcp/mcp-tools.md +48 -0
  49. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  50. package/framework/templates/CLAUDE.md.core.template +22 -0
  51. package/framework/templates/REGISTRY.json +4 -4
  52. package/framework/workflows/configs/fast-track.json +5 -10
  53. package/framework/workflows/configs/full.json +4 -8
  54. package/framework/workflows/configs/quick.json +32 -0
  55. package/framework/workflows/configs/standard.json +57 -64
  56. package/package.json +7 -6
  57. package/src/commands/mcp/mcp-setup.js +58 -31
  58. package/src/commands/project/doctor.js +69 -70
  59. package/src/commands/project/init.js +26 -5
  60. package/src/commands/project/status.js +18 -19
  61. package/src/commands/state/approve.js +1 -1
  62. package/src/commands/validation/validate-feature.js +18 -112
  63. package/src/core/paths/output-schema.js +133 -151
  64. package/src/core/state/phase-state-machine.js +10 -24
  65. package/src/core/state/state-manager.js +17 -17
  66. package/src/core/workflows/workflow-detector.js +214 -31
  67. package/src/core/workflows/workflow-router.js +204 -0
  68. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  69. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  70. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  71. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  72. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  73. package/src/dashboard/scanner/agents-scanner.js +30 -35
  74. package/src/lib/agents/metrics-aggregator.js +1 -1
  75. package/src/lib/agents/output-collector.js +7 -8
  76. package/src/lib/context/context-builder.js +289 -0
  77. package/src/lib/detectors/claude-config-detector.js +19 -3
  78. package/src/lib/generators/context-generator.js +20 -42
  79. package/src/lib/generators/index.js +1 -0
  80. package/src/lib/generators/mandate-generator.js +512 -0
  81. package/src/lib/generators/metadata-extractor.js +1 -1
  82. package/src/lib/generators/recap-generator.js +3 -4
  83. package/src/lib/installers/mcp-installer.js +300 -92
  84. package/src/lib/phase-chain/phase-validator.js +36 -40
  85. package/src/lib/stack/stack-profile.js +74 -91
  86. package/src/lib/tasks/task-parser.js +1 -1
  87. package/src/lib/tasks/wave-resolver.js +353 -0
  88. package/src/lib/validators/checkpoint-persistence.js +2 -2
  89. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  90. package/src/lib/validators/content/index.js +1 -0
  91. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  92. package/src/scripts/global-install.js +2 -27
  93. package/src/utils/agents-installer.js +224 -447
  94. package/src/utils/claude-settings-manager.js +57 -15
  95. package/src/utils/file-copier.js +1 -0
  96. package/src/utils/hooks-installer.js +15 -117
  97. package/src/utils/skills-installer.js +12 -18
  98. package/bin/detect-agents.js +0 -226
  99. package/bin/task-manager.js +0 -1036
  100. package/framework/agents/README.md +0 -13
  101. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  102. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  103. package/framework/agents/architecture/architecture-expert.md +0 -98
  104. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  105. package/framework/agents/architecture/packages-validator.md +0 -89
  106. package/framework/agents/architecture/security-expert.md +0 -79
  107. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  108. package/framework/agents/architecture/standards-architect.md +0 -153
  109. package/framework/agents/backend/dotnet-senior.md +0 -163
  110. package/framework/agents/backend/ef-modeler.md +0 -119
  111. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  112. package/framework/agents/backend/ms-agent-expert.md +0 -55
  113. package/framework/agents/backend/neon-expert.md +0 -379
  114. package/framework/agents/frontend/css-specialist.md +0 -89
  115. package/framework/agents/frontend/design-system-validator.md +0 -100
  116. package/framework/agents/frontend/gsap-animator.md +0 -131
  117. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  118. package/framework/agents/frontend/nextjs-expert.md +0 -201
  119. package/framework/agents/frontend/ui-designer.md +0 -259
  120. package/framework/agents/infrastructure/infra-architect.md +0 -215
  121. package/framework/agents/integrations/asaas-financial.md +0 -138
  122. package/framework/agents/integrations/neon-auth.md +0 -151
  123. package/framework/agents/integrations/resend-email.md +0 -127
  124. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  125. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  126. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  127. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  128. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  129. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  130. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  131. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  132. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  133. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  134. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  135. package/framework/hooks/shared/compact-restore.js +0 -123
  136. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  137. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  138. package/framework/hooks/shared/stale-task-reset.js +0 -57
  139. package/framework/hooks/shared/worktree-helpers.js +0 -75
  140. package/framework/skills/level-0-meta/README.md +0 -7
  141. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  142. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  143. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  144. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  145. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  146. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  147. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  148. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  149. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  150. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  151. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  152. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  153. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  154. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  155. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  156. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -382
  157. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  158. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  159. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  160. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  161. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  162. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  163. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  164. package/framework/skills/level-1-workflows/README.md +0 -7
  165. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  166. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  168. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  169. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  170. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  171. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -493
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  173. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -46
  174. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  175. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  176. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  177. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  181. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  182. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  183. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -788
  184. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  185. package/framework/squad-templates/backend-only.json +0 -47
  186. package/framework/squad-templates/frontend-only.json +0 -56
  187. package/framework/squad-templates/full-stack.json +0 -74
  188. package/src/commands/agents/agent-metrics.js +0 -174
  189. package/src/commands/agents/audit-dispatch.js +0 -82
  190. package/src/commands/agents/detect-agents.js +0 -275
  191. package/src/commands/agents/dispatch-agents.js +0 -461
  192. package/src/commands/agents/index.js +0 -13
  193. package/src/commands/agents/manage-agents.js +0 -352
  194. package/src/commands/agents/project-metrics.js +0 -74
  195. package/src/commands/phase/phase-reset.js +0 -74
  196. package/src/commands/scope/escalate.js +0 -217
  197. package/src/commands/state/advance-phase.js +0 -600
  198. package/src/commands/state/phase-runner.js +0 -215
  199. package/src/commands/state/state.js +0 -444
  200. package/src/commands/task/expand.js +0 -100
  201. package/src/commands/tasks/index.js +0 -4
  202. package/src/commands/tasks/task.js +0 -105
  203. package/src/commands/templates/index.js +0 -5
  204. package/src/commands/templates/template-render.js +0 -93
  205. package/src/commands/trust/trust.js +0 -118
  206. package/src/lib/agents/agents-manager.js +0 -349
  207. package/src/lib/build/build-runner.js +0 -99
  208. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  209. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  210. package/src/lib/scope/impact-analyzer.js +0 -106
  211. package/src/lib/trust/trust-manager.js +0 -157
  212. package/src/utils/tier-helpers.js +0 -24
  213. /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
- sync: { bg: '#374151', border: '#9ca3af', text: '#f3f4f6' },
14
+ review: { bg: '#6d28d9', border: '#a78bfa', text: '#ede9fe' },
17
15
  };
18
16
 
19
- const GATED_PHASES = new Set(['uiux', 'design', 'plan', 'tasks']);
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 levels
10
+ // Colors for skill categories (v5 — all skills in skills/core/)
11
11
  const LEVEL_COLORS = {
12
- 'level-0-meta': { bg: '#134e4a', border: '#14b8a6', label: 'L0 Meta' },
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 7 columns)
15
- const PHASE_ORDER = ['proposal', 'uiux', 'design', 'plan', 'tasks', 'implement', 'sync'];
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, readdirSync, existsSync } from 'fs';
2
- import { join, basename } from 'path';
3
-
4
- function findMdFiles(dir) {
5
- const results = [];
6
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
7
- const fullPath = join(dir, entry.name);
8
- if (entry.isDirectory()) results.push(...findMdFiles(fullPath));
9
- else if (entry.name.endsWith('.md') && entry.name !== 'README.md') results.push(fullPath);
10
- }
11
- return results;
12
- }
13
-
14
- export function scanAgents(frameworkDir) {
15
- const agentsJsonPath = join(frameworkDir, 'agents.json');
16
- const agentsDir = join(frameworkDir, 'agents');
17
- if (!existsSync(agentsJsonPath) || !existsSync(agentsDir)) {
18
- return { version: 'unknown', total: 0, tiers: {}, list: [] };
19
- }
20
- const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf8'));
21
- const mdFiles = findMdFiles(agentsDir);
22
- const promptMap = {};
23
- for (const filePath of mdFiles) {
24
- promptMap[basename(filePath, '.md')] = readFileSync(filePath, 'utf8');
25
- }
26
- const list = Object.entries(agentsJson.agents).map(([id, agent]) => ({
27
- id, color: agent.color, tier: agent.tier, title: agent.title, group: agent.group,
28
- activePhases: agent.active_phases, reportsTo: agent.reports_to,
29
- domains: agent.domains || [], keywords: agent.keywords || [],
30
- validators: agent.validators || [], standards: agent.standards || [],
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 === 'archived' || f.phase === 'done').length;
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,23 +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
- schemaAnalysis: 1.0,
17
+ stackScan: 0.8,
18
+ migrationPlan: 0.8,
18
19
  spec: 1.5,
19
- contracts: 1.0,
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
27
  recap: 1.0,
28
+ reviewReport: 1.0,
30
29
  };
31
30
 
32
- /** Phase order for sorting collected outputs */
33
- const PHASE_ORDER = ['proposal', 'uiux', 'design', 'plan', 'tasks', 'implement'];
31
+ /** Phase order for sorting collected outputs (v5 NLH model) */
32
+ const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
34
33
 
35
34
  /**
36
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 settings ────────────────────────────────────────────
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
- // Avoid duplicates — later file wins
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 by tier
257
- AGENT_COUNT: agents?.total_agents || 0,
258
- TIER_1_AGENTS: Object.entries(agents?.agents || {})
259
- .filter(([id, agent]) => !id.startsWith('_') && agent.tier === 1)
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
- always_active: Array.isArray(agent.active_phases) ? agent.active_phases.includes('*') : !!agent.always_active
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-design', 'decisions.md');
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, '4-tasks', 'tasks.json');
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
- // Group by tier and build squads
385
- const teamLead = activeAgents.find(a => tierToRole(a.tier) === 'orchestrator');
386
- const domainLeaders = activeAgents.filter(a => tierToRole(a.tier) === 'leader');
387
- const specialists = activeAgents.filter(a => tierToRole(a.tier) === 'specialist');
388
- const validators = activeAgents.filter(a => tierToRole(a.tier) === 'validator');
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}/4-tasks/tasks.json`,
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: '3.0.0-hierarchical',
470
+ MORPH_VERSION: '5.0.0',
493
471
  FEATURE_ID: feature.id || featureName
494
472
  };
495
473
 
@@ -10,3 +10,4 @@ export * from './recap-generator.js';
10
10
  export * from './metadata-extractor.js';
11
11
  export * from './architecture-scanner.js';
12
12
  export * from './scaffold-generator.js';
13
+ export * from './mandate-generator.js';