@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
@@ -1,349 +0,0 @@
1
- /**
2
- * agents-manager.js — Core logic for agent CRUD operations
3
- *
4
- * Pure functions that accept agentsData object. File I/O only in
5
- * loadAgentsFile / saveAgentsFile. All other functions are testable
6
- * with fixture data.
7
- *
8
- * @module agents-manager
9
- */
10
-
11
- import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
12
- import { mkdirSync } from 'node:fs';
13
- import { dirname } from 'node:path';
14
-
15
- // ── Constants ──────────────────────────────────────────────────────────────────
16
-
17
- /** Known valid phases for active_phases validation */
18
- export const KNOWN_PHASES = [
19
- 'proposal', 'uiux', 'design',
20
- 'plan', 'tasks', 'implement', 'sync',
21
- ];
22
-
23
- /** Tier labels for display and validation */
24
- export const TIER_LABELS = {
25
- 1: 'orchestrator',
26
- 2: 'leader',
27
- 3: 'specialist',
28
- 4: 'validator',
29
- };
30
-
31
- /** Tier metadata key names in agents.json */
32
- export const TIER_KEYS = {
33
- 1: 'tier_1_orchestrators',
34
- 2: 'tier_2_leaders',
35
- 3: 'tier_3_specialists',
36
- 4: 'tier_4_validators',
37
- };
38
-
39
- /** Agent ID pattern: kebab-case starting with a letter */
40
- export const AGENT_ID_PATTERN = /^[a-z][a-z0-9-]*$/;
41
-
42
- // ── Validation ─────────────────────────────────────────────────────────────────
43
-
44
- /**
45
- * Validate agent fields.
46
- * @param {string} id
47
- * @param {object} fields
48
- * @param {string[]} existingIds - IDs already in agents.json (for uniqueness check)
49
- * @param {string[]} [knownGroups] - Known group names (for warnings)
50
- * @param {object} [allAgents] - Full agents map (for circular reports_to check)
51
- * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
52
- */
53
- export function validateAgent(id, fields, existingIds = [], knownGroups = [], allAgents = {}) {
54
- const errors = [];
55
- const warnings = [];
56
-
57
- // ID validation
58
- if (!AGENT_ID_PATTERN.test(id)) {
59
- errors.push('Agent ID must be kebab-case (lowercase letters, numbers, hyphens) and start with a letter');
60
- }
61
- if (existingIds.includes(id)) {
62
- errors.push(`Agent ID '${id}' is not unique — already exists in agents.json`);
63
- }
64
-
65
- // Tier
66
- const tier = fields.tier;
67
- if (tier == null || !Number.isInteger(tier) || tier < 1 || tier > 4) {
68
- errors.push('Tier must be 1 (orchestrator), 2 (leader), 3 (specialist), or 4 (validator)');
69
- }
70
-
71
- // Title
72
- if (!fields.title || typeof fields.title !== 'string' || fields.title.trim() === '') {
73
- errors.push('Title is required');
74
- }
75
-
76
- // Group
77
- if (!fields.group || typeof fields.group !== 'string' || fields.group.trim() === '') {
78
- errors.push('Group is required');
79
- } else if (knownGroups.length > 0 && !knownGroups.includes(fields.group)) {
80
- warnings.push(`Unknown group '${fields.group}' — known groups: ${knownGroups.join(', ')}`);
81
- }
82
-
83
- // Active phases
84
- if (fields.active_phases) {
85
- const phases = fields.active_phases;
86
- if (!Array.isArray(phases)) {
87
- errors.push('active_phases must be an array');
88
- } else if (phases.length === 1 && phases[0] === '*') {
89
- // wildcard — valid
90
- } else {
91
- for (const p of phases) {
92
- if (!KNOWN_PHASES.includes(p)) {
93
- errors.push(`Invalid phase '${p}' — valid: ${KNOWN_PHASES.join(', ')}`);
94
- }
95
- }
96
- }
97
- }
98
-
99
- // reports_to — circular check
100
- if (fields.reports_to) {
101
- if (fields.reports_to === id) {
102
- errors.push('Agent cannot report to itself');
103
- } else if (fields.reports_to !== 'user' && allAgents && Object.keys(allAgents).length > 0) {
104
- // Walk the chain to detect cycles
105
- const visited = new Set([id]);
106
- let current = fields.reports_to;
107
- while (current && current !== 'user') {
108
- if (visited.has(current)) {
109
- errors.push(`Circular reports_to chain detected: ${[...visited, current].join(' \u2192 ')}`);
110
- break;
111
- }
112
- visited.add(current);
113
- current = allAgents[current]?.reports_to;
114
- }
115
- }
116
- }
117
-
118
- // spawn_prompt required for tier >= 3
119
- if (Number.isInteger(tier) && tier >= 3) {
120
- const hasPrompt = fields.teammate?.spawn_prompt ||
121
- (typeof fields.spawn_prompt === 'string' && fields.spawn_prompt.trim());
122
- if (!hasPrompt) {
123
- errors.push('spawn_prompt is required for tier 3+ agents');
124
- }
125
- }
126
-
127
- return { valid: errors.length === 0, errors, warnings };
128
- }
129
-
130
- // ── Metadata Sync ──────────────────────────────────────────────────────────────
131
-
132
- /**
133
- * Recalculate total_agents and tier counts.
134
- * @param {object} agentsData - Full agents.json data
135
- * @returns {object} Updated agentsData (mutated in place + returned)
136
- */
137
- export function syncMetadata(agentsData) {
138
- const agents = agentsData.agents || {};
139
- const entries = Object.values(agents);
140
-
141
- agentsData.total_agents = entries.length;
142
- agentsData.tiers = {
143
- tier_1_orchestrators: entries.filter(a => a.tier === 1).length,
144
- tier_2_leaders: entries.filter(a => a.tier === 2).length,
145
- tier_3_specialists: entries.filter(a => a.tier === 3).length,
146
- tier_4_validators: entries.filter(a => a.tier === 4).length,
147
- };
148
-
149
- return agentsData;
150
- }
151
-
152
- // ── List + Show ────────────────────────────────────────────────────────────────
153
-
154
- /**
155
- * List agents with optional filters.
156
- * @param {object} agentsData
157
- * @param {{ group?: string, tier?: number }} [filters]
158
- * @returns {{ agents: Array, groupCounts: Record<string, number>, total: number }}
159
- */
160
- export function listAgents(agentsData, filters = {}) {
161
- const agents = agentsData.agents || {};
162
- let entries = Object.entries(agents).map(([id, data]) => ({ id, ...data }));
163
-
164
- if (filters.group) {
165
- entries = entries.filter(a => a.group === filters.group);
166
- }
167
- if (filters.tier != null) {
168
- entries = entries.filter(a => a.tier === filters.tier);
169
- }
170
-
171
- entries.sort((a, b) => a.tier - b.tier || a.id.localeCompare(b.id));
172
-
173
- // groupCounts over ALL agents (not filtered)
174
- const groupCounts = {};
175
- for (const a of Object.values(agents)) {
176
- groupCounts[a.group] = (groupCounts[a.group] || 0) + 1;
177
- }
178
-
179
- return {
180
- agents: entries.map(a => ({
181
- id: a.id,
182
- tier: a.tier,
183
- title: a.title,
184
- group: a.group,
185
- phases: Array.isArray(a.active_phases) ? a.active_phases.join(',') : '*',
186
- keywords: (a.keywords || []).join(', ') || '\u2014',
187
- })),
188
- groupCounts,
189
- total: entries.length,
190
- };
191
- }
192
-
193
- /**
194
- * Show detailed info for a single agent.
195
- * @param {object} agentsData
196
- * @param {string} id
197
- * @returns {object|null}
198
- */
199
- export function showAgent(agentsData, id) {
200
- return agentsData.agents?.[id] ?? null;
201
- }
202
-
203
- // ── Mutations ──────────────────────────────────────────────────────────────────
204
-
205
- /**
206
- * Add a new agent.
207
- * @param {object} agentsData
208
- * @param {string} id
209
- * @param {object} fields
210
- * @returns {{ success: boolean, data?: object, changes?: string[], errors?: string[], warnings?: string[] }}
211
- */
212
- export function addAgent(agentsData, id, fields) {
213
- const existingIds = Object.keys(agentsData.agents || {});
214
- const knownGroups = [...new Set(Object.values(agentsData.agents || {}).map(a => a.group))];
215
- const validation = validateAgent(id, fields, existingIds, knownGroups, agentsData.agents);
216
-
217
- if (!validation.valid) {
218
- return { success: false, errors: validation.errors, warnings: validation.warnings };
219
- }
220
-
221
- const entry = {
222
- tier: fields.tier,
223
- title: fields.title,
224
- group: fields.group,
225
- active_phases: fields.active_phases || ['implement'],
226
- reports_to: fields.reports_to || 'user',
227
- domains: fields.domains || [],
228
- keywords: fields.keywords || [],
229
- validators: fields.validators || [],
230
- standards: fields.standards || [],
231
- };
232
-
233
- if (fields.stacks) entry.stacks = fields.stacks;
234
- if (fields.teammate) entry.teammate = fields.teammate;
235
-
236
- agentsData.agents[id] = entry;
237
- syncMetadata(agentsData);
238
-
239
- return {
240
- success: true,
241
- data: agentsData,
242
- changes: [`Added agent '${id}' (tier ${fields.tier}, group '${fields.group}')`],
243
- warnings: validation.warnings,
244
- };
245
- }
246
-
247
- /**
248
- * Edit existing agent fields.
249
- * @param {object} agentsData
250
- * @param {string} id
251
- * @param {object} changes
252
- * @returns {{ success: boolean, data?: object, changes?: string[], errors?: string[] }}
253
- */
254
- export function editAgent(agentsData, id, changes) {
255
- const agent = agentsData.agents?.[id];
256
- if (!agent) {
257
- return { success: false, errors: [`Agent '${id}' not found`] };
258
- }
259
-
260
- const changedFields = [];
261
-
262
- // Handle spawn_prompt shortcut -> nested teammate
263
- if (changes.spawn_prompt) {
264
- if (!agent.teammate) {
265
- agent.teammate = { role: 'specialist', icon: '\u{1F527}', spawn_prompt: changes.spawn_prompt };
266
- } else {
267
- agent.teammate.spawn_prompt = changes.spawn_prompt;
268
- }
269
- changedFields.push('teammate.spawn_prompt');
270
- delete changes.spawn_prompt;
271
- }
272
-
273
- // Apply direct field changes
274
- for (const [key, value] of Object.entries(changes)) {
275
- if (value !== undefined && agent[key] !== value) {
276
- changedFields.push(key);
277
- agent[key] = value;
278
- }
279
- }
280
-
281
- if (changedFields.length === 0) {
282
- return { success: true, data: agentsData, changes: ['No changes detected'] };
283
- }
284
-
285
- syncMetadata(agentsData);
286
- return { success: true, data: agentsData, changes: changedFields.map(f => `${f} updated`) };
287
- }
288
-
289
- /**
290
- * Remove an agent.
291
- * @param {object} agentsData
292
- * @param {string} id
293
- * @returns {{ success: boolean, data?: object, dependents?: string[], errors?: string[] }}
294
- */
295
- export function removeAgent(agentsData, id) {
296
- if (!agentsData.agents?.[id]) {
297
- return { success: false, errors: [`Agent '${id}' not found`] };
298
- }
299
-
300
- // Find dependents
301
- const dependents = Object.entries(agentsData.agents)
302
- .filter(([aid, a]) => aid !== id && a.reports_to === id)
303
- .map(([aid]) => aid);
304
-
305
- delete agentsData.agents[id];
306
- syncMetadata(agentsData);
307
-
308
- return { success: true, data: agentsData, dependents };
309
- }
310
-
311
- // ── File I/O ───────────────────────────────────────────────────────────────────
312
-
313
- /**
314
- * Load and parse agents.json.
315
- * @param {string} filePath
316
- * @returns {object}
317
- * @throws {Error} If file not found or invalid JSON
318
- */
319
- export function loadAgentsFile(filePath) {
320
- if (!existsSync(filePath)) {
321
- throw new Error(`agents.json not found at ${filePath}. Run 'morph-spec doctor' to diagnose.`);
322
- }
323
-
324
- try {
325
- const content = readFileSync(filePath, 'utf8');
326
- return JSON.parse(content);
327
- } catch (err) {
328
- if (err instanceof SyntaxError) {
329
- throw new Error(`Failed to parse agents.json: ${err.message}. Run 'morph-spec doctor' to diagnose.`);
330
- }
331
- throw err;
332
- }
333
- }
334
-
335
- /**
336
- * Write agents.json atomically (write to .tmp, then rename).
337
- * @param {string} filePath
338
- * @param {object} agentsData
339
- */
340
- export function saveAgentsFile(filePath, agentsData) {
341
- const dir = dirname(filePath);
342
- if (!existsSync(dir)) {
343
- mkdirSync(dir, { recursive: true });
344
- }
345
-
346
- const tmpPath = `${filePath}.tmp.${process.pid}`;
347
- writeFileSync(tmpPath, JSON.stringify(agentsData, null, 2) + '\n', 'utf8');
348
- renameSync(tmpPath, filePath);
349
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Build Runner — Resolves and executes the project's build command.
3
- *
4
- * Resolution order for build command:
5
- * 1. .morph/config/config.json → project.buildCommand
6
- * 2. Stack profile → profile.buildCommand
7
- * 3. package.json → scripts.build → "npm run build"
8
- * 4. tsconfig.json at root → "npx tsc --noEmit"
9
- * 5. null (no build command detected)
10
- */
11
-
12
- import { execSync } from 'child_process';
13
- import { existsSync, readFileSync } from 'fs';
14
- import { join } from 'path';
15
- import { getStackProfile } from '../stack/stack-profile.js';
16
-
17
- /**
18
- * Resolve the build command for a project.
19
- *
20
- * @param {string} [projectPath] - Project root directory (defaults to '.')
21
- * @returns {string | null} The build command to run, or null if none found
22
- */
23
- export function resolveBuildCommand(projectPath = '.') {
24
- // 1. Explicit override from .morph/config/config.json
25
- const configPath = join(projectPath, '.morph', 'config', 'config.json');
26
- if (existsSync(configPath)) {
27
- try {
28
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
29
- const cmd = config?.project?.buildCommand;
30
- if (cmd && typeof cmd === 'string') {
31
- return cmd;
32
- }
33
- } catch {
34
- // Malformed config — fall through to detection
35
- }
36
- }
37
-
38
- // 2. Stack profile default (only when an actual stack is configured)
39
- const profile = getStackProfile(projectPath);
40
- if (profile.stack !== 'unknown' && profile.buildCommand) {
41
- return profile.buildCommand;
42
- }
43
-
44
- // 3. package.json with a build script
45
- const packageJsonPath = join(projectPath, 'package.json');
46
- if (existsSync(packageJsonPath)) {
47
- try {
48
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
49
- if (pkg?.scripts?.build) {
50
- return 'npm run build';
51
- }
52
- } catch {
53
- // Malformed package.json — fall through
54
- }
55
- }
56
-
57
- // 4. tsconfig.json at root → generic type-check
58
- if (existsSync(join(projectPath, 'tsconfig.json'))) {
59
- return 'npx tsc --noEmit';
60
- }
61
-
62
- // 5. Nothing found
63
- return null;
64
- }
65
-
66
- /**
67
- * Run the project's build check.
68
- *
69
- * @param {string} [projectPath] - Project root directory (defaults to '.')
70
- * @param {object} [opts] - Optional overrides for testing
71
- * @param {Function} [opts._execSync] - Override execSync for unit testing
72
- * @returns {{ passed?: boolean, output?: string, exitCode?: number, command?: string, skipped?: boolean, reason?: string }}
73
- */
74
- export function runBuildCheck(projectPath = '.', opts = {}) {
75
- const cmd = resolveBuildCommand(projectPath);
76
-
77
- if (!cmd) {
78
- return { skipped: true, reason: 'No buildCommand configured and no known project file detected' };
79
- }
80
-
81
- const exec = opts._execSync ?? execSync;
82
-
83
- try {
84
- const output = exec(cmd, {
85
- cwd: projectPath,
86
- timeout: 120_000,
87
- encoding: 'utf8',
88
- stdio: 'pipe'
89
- });
90
- return { passed: true, output: output ?? '', exitCode: 0, command: cmd };
91
- } catch (err) {
92
- return {
93
- passed: false,
94
- output: (err.stdout || '') + (err.stderr || ''),
95
- exitCode: err.status ?? 1,
96
- command: cmd
97
- };
98
- }
99
- }
@@ -1,111 +0,0 @@
1
- /**
2
- * Checkpoint Hooks — Orchestrates validation, build, and test phases at checkpoints.
3
- *
4
- * This is the module that task-manager.js imports at:
5
- * import('../src/lib/checkpoints/checkpoint-hooks.js')
6
- *
7
- * All three phases run independently (failures don't short-circuit) so the user
8
- * gets a complete picture of all issues in a single checkpoint report.
9
- */
10
-
11
- import chalk from 'chalk';
12
- import { runValidation } from '../validators/validation-runner.js';
13
- import { runBuildCheck } from '../build/build-runner.js';
14
- import { runTestSuite } from '../tasks/test-runner.js';
15
- import { persistCheckpointValidation } from '../validators/checkpoint-persistence.js';
16
-
17
- /**
18
- * Determine whether a checkpoint should run based on task count and frequency.
19
- *
20
- * @param {number} tasksCompleted - Number of tasks completed so far
21
- * @param {number} frequency - Run every N tasks (default: 3)
22
- * @returns {boolean}
23
- */
24
- export function shouldRunCheckpoint(tasksCompleted, frequency = 3) {
25
- return tasksCompleted > 0 && tasksCompleted % frequency === 0;
26
- }
27
-
28
- /**
29
- * Run all checkpoint hooks: static validation, build check, and test suite.
30
- *
31
- * @param {string} featureName - The feature being worked on
32
- * @param {number} checkpointNum - Checkpoint sequence number
33
- * @param {object} [opts] - Options
34
- * @param {string} [opts.projectPath] - Project root (defaults to cwd)
35
- * @returns {Promise<{ passed: boolean, summary: { errors: number, warnings: number }, results: object }>}
36
- */
37
- export async function runCheckpointHooks(featureName, checkpointNum, opts = {}) {
38
- const projectPath = opts.projectPath ?? process.cwd();
39
- const results = {};
40
- let totalErrors = 0;
41
- let totalWarnings = 0;
42
-
43
- // Phase 1: Static validators
44
- console.log(chalk.gray('\n Phase 1/3: Running static validators...'));
45
- try {
46
- const valResult = await runValidation(projectPath, featureName, { verbose: false, mode: 'full' });
47
- results.validators = {
48
- passed: valResult.passed,
49
- errors: valResult.errors,
50
- warnings: valResult.warnings,
51
- details: valResult.results,
52
- scores: valResult.scores || null
53
- };
54
- totalErrors += valResult.errors;
55
- totalWarnings += valResult.warnings;
56
- } catch (err) {
57
- results.validators = { passed: false, errors: 1, warnings: 0, details: {}, error: err.message };
58
- totalErrors += 1;
59
- }
60
-
61
- // Phase 2: Build check
62
- console.log(chalk.gray(' Phase 2/3: Running build check...'));
63
- const buildResult = runBuildCheck(projectPath);
64
- results.build = buildResult;
65
- if (buildResult.skipped) {
66
- console.log(chalk.gray(` Skipped: ${buildResult.reason}`));
67
- } else if (buildResult.passed) {
68
- console.log(chalk.green(` ✓ Build passed (${buildResult.command})`));
69
- } else {
70
- console.log(chalk.red(` ✗ Build failed (${buildResult.command})`));
71
- totalErrors += 1;
72
- }
73
-
74
- // Phase 3: Test suite
75
- console.log(chalk.gray(' Phase 3/3: Running test suite...'));
76
- const testResult = runTestSuite(projectPath);
77
- results.tests = testResult;
78
- if (testResult.skipped) {
79
- console.log(chalk.gray(` Skipped: ${testResult.reason}`));
80
- } else if (testResult.passed) {
81
- console.log(chalk.green(' ✓ Tests passed'));
82
- } else {
83
- console.log(chalk.red(' ✗ Tests failed'));
84
- totalErrors += 1;
85
- }
86
-
87
- // Persist results
88
- const summaryText = [
89
- `Validators: ${results.validators?.errors || 0} errors, ${results.validators?.warnings || 0} warnings`,
90
- buildResult.skipped ? 'Build: skipped' : `Build: ${buildResult.passed ? 'passed' : 'FAILED'}`,
91
- testResult.skipped ? 'Tests: skipped' : `Tests: ${testResult.passed ? 'passed' : 'FAILED'}`
92
- ].join(' | ');
93
-
94
- try {
95
- persistCheckpointValidation(projectPath, featureName, {
96
- checkpointNum,
97
- timestamp: new Date().toISOString(),
98
- passed: totalErrors === 0,
99
- validators: results,
100
- summary: summaryText
101
- });
102
- } catch {
103
- // Non-blocking — persistence failure shouldn't break the checkpoint
104
- }
105
-
106
- return {
107
- passed: totalErrors === 0,
108
- summary: { errors: totalErrors, warnings: totalWarnings },
109
- results
110
- };
111
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * Sprint Contract Generator
3
- *
4
- * Generates sprint contracts from tasks.md + spec.md for the evaluator.
5
- * Contracts define objective "definition of done" criteria.
6
- */
7
-
8
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
9
- import { join } from 'path';
10
- import { getBuildCommand } from '../../../framework/hooks/shared/build-check.js';
11
-
12
- /**
13
- * Parse a task entry from tasks.md markdown.
14
- *
15
- * Expected format: ## T001: Task Title [FR001, FR003]
16
- *
17
- * @param {string} content - tasks.md content
18
- * @param {string} taskId - Task ID (e.g., 'T001')
19
- * @returns {{ id: string, title: string, frs: string[], description: string }|null}
20
- */
21
- export function parseTaskFromMarkdown(content, taskId) {
22
- const pattern = new RegExp(
23
- `##\\s+${taskId}:\\s+(.+?)(?:\\s+\\[([^\\]]+)\\])?\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`,
24
- 'i'
25
- );
26
- const match = content.match(pattern);
27
- if (!match) return null;
28
-
29
- const title = match[1].trim();
30
- const frString = match[2] || '';
31
- const description = match[3].trim();
32
-
33
- const frs = frString
34
- ? frString.split(',').map(fr => fr.trim()).filter(Boolean)
35
- : [];
36
-
37
- return { id: taskId, title, frs, description };
38
- }
39
-
40
- /**
41
- * Generate a sprint contract for a task.
42
- *
43
- * @param {string} projectPath - Project root
44
- * @param {string} featureName - Feature name
45
- * @param {string} taskId - Task ID
46
- * @returns {Object|null} Contract object or null if unable to generate
47
- */
48
- export function generateSprintContract(projectPath, featureName, taskId) {
49
- const featureDir = join(projectPath, '.morph/features', featureName);
50
- const tasksPath = join(featureDir, '4-tasks/tasks.md');
51
-
52
- if (!existsSync(tasksPath)) return null;
53
-
54
- const tasksContent = readFileSync(tasksPath, 'utf-8');
55
- const task = parseTaskFromMarkdown(tasksContent, taskId);
56
- if (!task) return null;
57
-
58
- // Read spec for FR-based criteria
59
- const specPath = join(featureDir, '2-design/spec.md');
60
- let definitionOfDone = [];
61
- if (task.frs.length > 0 && existsSync(specPath)) {
62
- const specContent = readFileSync(specPath, 'utf-8');
63
- for (const fr of task.frs) {
64
- const frPattern = new RegExp(`##\\s+${fr}[:\\s]([\\s\\S]*?)(?=\\n##\\s|$)`, 'i');
65
- const frMatch = specContent.match(frPattern);
66
- if (frMatch) {
67
- const bullets = frMatch[1].match(/^[\s]*[-*]\s+(.+)/gm);
68
- if (bullets) {
69
- definitionOfDone.push(...bullets.map(b => b.replace(/^[\s]*[-*]\s+/, '').trim()));
70
- }
71
- }
72
- }
73
- }
74
-
75
- if (definitionOfDone.length === 0) {
76
- definitionOfDone = [task.description || `Complete ${task.title}`];
77
- }
78
-
79
- // Read stack for build commands
80
- let stack = '';
81
- try {
82
- const configPath = join(projectPath, '.morph/config/config.json');
83
- if (existsSync(configPath)) {
84
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
85
- stack = config?.project?.stack || '';
86
- }
87
- } catch { /* ignore */ }
88
-
89
- const hasTsConfig = existsSync(join(projectPath, 'tsconfig.json'));
90
- const buildCmd = getBuildCommand(stack, hasTsConfig);
91
-
92
- const contract = {
93
- task: taskId,
94
- title: task.title,
95
- functionalRequirements: task.frs,
96
- definitionOfDone,
97
- verification: {
98
- build: buildCmd || 'echo "No build command configured"',
99
- test: stack.includes('dotnet') ? `dotnet test --filter ${task.title.replace(/\s+/g, '')}` : 'npm test',
100
- e2e: null
101
- },
102
- maxAttempts: 3,
103
- generatedAt: new Date().toISOString()
104
- };
105
-
106
- // Write contract to disk
107
- const contractsDir = join(featureDir, '5-implement/contracts');
108
- mkdirSync(contractsDir, { recursive: true });
109
- const yamlContent = contractToYaml(contract);
110
- writeFileSync(join(contractsDir, `${taskId}-contract.yaml`), yamlContent, 'utf-8');
111
-
112
- return contract;
113
- }
114
-
115
- /**
116
- * Simple YAML serializer for contracts (no dependency needed).
117
- *
118
- * @param {Object} contract - Contract object
119
- * @returns {string} YAML string
120
- */
121
- function contractToYaml(contract) {
122
- const lines = [
123
- `task: ${contract.task}`,
124
- `title: "${contract.title}"`,
125
- `functionalRequirements:`,
126
- ...contract.functionalRequirements.map(fr => ` - ${fr}`),
127
- `definitionOfDone:`,
128
- ...contract.definitionOfDone.map(d => ` - "${d}"`),
129
- `verification:`,
130
- ` build: "${contract.verification.build}"`,
131
- ` test: "${contract.verification.test}"`,
132
- ` e2e: ${contract.verification.e2e ? `"${contract.verification.e2e}"` : 'null'}`,
133
- `maxAttempts: ${contract.maxAttempts}`,
134
- `generatedAt: "${contract.generatedAt}"`,
135
- ];
136
- return lines.join('\n') + '\n';
137
- }