@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
@@ -8,7 +8,8 @@
8
8
 
9
9
  import { join } from 'path';
10
10
  import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
11
- import { execSync } from 'child_process';
11
+ import { execSync, spawn } from 'child_process';
12
+ import { homedir } from 'os';
12
13
  import { installMcpServers } from '../../utils/claude-settings-manager.js';
13
14
 
14
15
  /**
@@ -17,7 +18,7 @@ import { installMcpServers } from '../../utils/claude-settings-manager.js';
17
18
  */
18
19
  export function loadMcpRegistry() {
19
20
  const registryPath = join(
20
- import.meta.dirname, '..', '..', '..', 'framework', 'skills', 'level-0-meta', 'mcp-registry.json'
21
+ import.meta.dirname, '..', '..', '..', 'framework', 'mcp', 'registry.json'
21
22
  );
22
23
  const raw = readFileSync(registryPath, 'utf-8');
23
24
  return JSON.parse(raw);
@@ -175,122 +176,120 @@ export function installCompanionSkills(mcpsInstalled, exec = execSync) {
175
176
 
176
177
  /**
177
178
  * Generate the Stitch OAuth proxy template script.
179
+ * Cross-platform (Windows gcloud.cmd + Unix gcloud), fetch()-based,
180
+ * with readline, session tracking, and graceful shutdown.
178
181
  * @param {string} gcpProject - GCP project ID for x-goog-user-project header
179
182
  * @returns {string} CJS proxy script content
180
183
  */
181
184
  export function getStitchProxyTemplate(gcpProject) {
182
- return `/**
183
- * Stitch MCP OAuth Proxy
185
+ return `#!/usr/bin/env node
186
+ /**
187
+ * Stitch MCP stdio-to-HTTP proxy with Google auth
184
188
  * Generated by morph-spec mcp setup stitch
185
- *
186
- * Bridges stdio JSON-RPC to https://stitch.googleapis.com/mcp
187
- * using gcloud application-default credentials (OAuth2 bearer token).
188
189
  */
189
190
  const { execSync } = require('child_process');
190
- const https = require('https');
191
+ const { createInterface } = require('readline');
192
+
193
+ const STITCH_URL = 'https://stitch.googleapis.com/mcp';
194
+ const PROJECT_ID = '${gcpProject}';
191
195
 
192
- const GCP_PROJECT = '${gcpProject}';
193
- const STITCH_HOST = 'stitch.googleapis.com';
194
- const STITCH_PATH = '/mcp';
195
- const TOKEN_TTL_MS = 50 * 60 * 1000; // 50 minutes
196
+ // Cross-platform gcloud path: Windows uses gcloud.cmd from Cloud SDK install
197
+ const GCLOUD = process.platform === 'win32'
198
+ ? (process.env.LOCALAPPDATA || '') + '/Google/Cloud SDK/google-cloud-sdk/bin/gcloud.cmd'
199
+ : 'gcloud';
196
200
 
197
201
  let cachedToken = null;
198
- let tokenExpiresAt = 0;
202
+ let tokenExpiry = 0;
203
+ let pendingRequests = 0;
204
+ let stdinClosed = false;
205
+ let sessionId = null;
199
206
 
200
- function getAccessToken() {
201
- if (cachedToken && Date.now() < tokenExpiresAt) return cachedToken;
207
+ function getToken() {
208
+ if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
202
209
  try {
203
- cachedToken = execSync('gcloud auth application-default print-access-token', {
204
- encoding: 'utf-8',
205
- timeout: 10000,
206
- stdio: ['pipe', 'pipe', 'pipe']
210
+ cachedToken = execSync(\`"\${GCLOUD}" auth application-default print-access-token\`, {
211
+ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000
207
212
  }).trim();
208
- tokenExpiresAt = Date.now() + TOKEN_TTL_MS;
213
+ tokenExpiry = Date.now() + 50 * 60 * 1000; // 50-minute TTL
209
214
  return cachedToken;
210
- } catch (err) {
211
- process.stderr.write('[stitch-proxy] Failed to get access token. Run: gcloud auth application-default login\\n');
212
- throw err;
215
+ } catch {
216
+ return null;
213
217
  }
214
218
  }
215
219
 
216
- function clearTokenCache() {
217
- cachedToken = null;
218
- tokenExpiresAt = 0;
220
+ function maybeExit() {
221
+ if (stdinClosed && pendingRequests === 0) process.exit(0);
219
222
  }
220
223
 
221
- function sendRequest(body) {
222
- return new Promise((resolve, reject) => {
223
- const token = getAccessToken();
224
- const data = JSON.stringify(body);
225
- const options = {
226
- hostname: STITCH_HOST,
227
- path: STITCH_PATH,
228
- method: 'POST',
229
- headers: {
230
- 'Content-Type': 'application/json',
231
- 'Content-Length': Buffer.byteLength(data),
232
- 'Authorization': \`Bearer \${token}\`,
233
- 'x-goog-user-project': GCP_PROJECT,
234
- },
235
- };
224
+ async function handleMessage(line) {
225
+ let msg;
226
+ try { msg = JSON.parse(line); } catch { return; }
236
227
 
237
- const req = https.request(options, (res) => {
238
- let responseData = '';
239
- res.on('data', (chunk) => { responseData += chunk; });
240
- res.on('end', () => {
241
- if (res.statusCode === 401) {
242
- // Token expired — retry once with fresh token
243
- clearTokenCache();
244
- const retryToken = getAccessToken();
245
- options.headers['Authorization'] = \`Bearer \${retryToken}\`;
246
- const retryData = JSON.stringify(body);
247
- options.headers['Content-Length'] = Buffer.byteLength(retryData);
248
- const retryReq = https.request(options, (retryRes) => {
249
- let retryResponse = '';
250
- retryRes.on('data', (chunk) => { retryResponse += chunk; });
251
- retryRes.on('end', () => resolve(retryResponse));
252
- });
253
- retryReq.on('error', reject);
254
- retryReq.write(retryData);
255
- retryReq.end();
256
- } else {
257
- resolve(responseData);
258
- }
259
- });
260
- });
228
+ // Notifications (no id) don't need responses
229
+ if (msg.id === undefined && msg.method === 'notifications/initialized') return;
261
230
 
262
- req.on('error', reject);
263
- req.write(data);
264
- req.end();
265
- });
266
- }
231
+ const token = getToken();
232
+ if (!token) {
233
+ if (msg.id !== undefined) {
234
+ process.stdout.write(JSON.stringify({
235
+ jsonrpc: '2.0', id: msg.id,
236
+ error: { code: -32000, message: 'Failed to get gcloud auth token. Run: gcloud auth application-default login' }
237
+ }) + '\\n');
238
+ }
239
+ return;
240
+ }
267
241
 
268
- // Buffer stdin and split on newlines (JSON-RPC messages)
269
- let buffer = '';
270
- process.stdin.setEncoding('utf-8');
271
- process.stdin.on('data', async (chunk) => {
272
- buffer += chunk;
273
- const lines = buffer.split('\\n');
274
- buffer = lines.pop(); // keep incomplete line in buffer
242
+ const headers = {
243
+ 'Content-Type': 'application/json',
244
+ 'Authorization': \`Bearer \${token}\`,
245
+ 'x-goog-user-project': PROJECT_ID,
246
+ };
247
+ if (sessionId) headers['mcp-session-id'] = sessionId;
275
248
 
276
- for (const line of lines) {
277
- if (!line.trim()) continue;
278
- try {
279
- const request = JSON.parse(line);
280
- const response = await sendRequest(request);
281
- process.stdout.write(response + '\\n');
282
- } catch (err) {
283
- const errorResponse = JSON.stringify({
284
- jsonrpc: '2.0',
285
- id: null,
286
- error: { code: -32603, message: err.message || 'Proxy error' }
287
- });
288
- process.stdout.write(errorResponse + '\\n');
249
+ pendingRequests++;
250
+ try {
251
+ const res = await fetch(STITCH_URL, { method: 'POST', headers, body: JSON.stringify(msg) });
252
+
253
+ // Track sticky session
254
+ const sid = res.headers.get('mcp-session-id');
255
+ if (sid) sessionId = sid;
256
+
257
+ // Retry once on 401 (expired token)
258
+ if (res.status === 401) {
259
+ cachedToken = null;
260
+ tokenExpiry = 0;
261
+ const retryToken = getToken();
262
+ if (retryToken) {
263
+ headers['Authorization'] = \`Bearer \${retryToken}\`;
264
+ const retryRes = await fetch(STITCH_URL, { method: 'POST', headers, body: JSON.stringify(msg) });
265
+ const retryText = await retryRes.text();
266
+ if (retryText.trim()) {
267
+ process.stdout.write(retryText.endsWith('\\n') ? retryText : retryText + '\\n');
268
+ }
269
+ return;
270
+ }
289
271
  }
272
+
273
+ const text = await res.text();
274
+ if (text.trim()) {
275
+ process.stdout.write(text.endsWith('\\n') ? text : text + '\\n');
276
+ }
277
+ } catch (err) {
278
+ if (msg.id !== undefined) {
279
+ process.stdout.write(JSON.stringify({
280
+ jsonrpc: '2.0', id: msg.id,
281
+ error: { code: -32000, message: \`Stitch API error: \${err.message}\` }
282
+ }) + '\\n');
283
+ }
284
+ } finally {
285
+ pendingRequests--;
286
+ maybeExit();
290
287
  }
291
- });
288
+ }
292
289
 
293
- process.stderr.write(\`[stitch-proxy] Ready — GCP project: \${GCP_PROJECT}\\n\`);
290
+ const rl = createInterface({ input: process.stdin });
291
+ rl.on('line', handleMessage);
292
+ rl.on('close', () => { stdinClosed = true; maybeExit(); });
294
293
  `;
295
294
  }
296
295
 
@@ -312,6 +311,215 @@ export function generateStitchProxy(targetPath, gcpProject) {
312
311
  return { proxyPath };
313
312
  }
314
313
 
314
+ /**
315
+ * Resolve the gcloud CLI binary path.
316
+ * Checks: system PATH → bundled at ~/.stitch-mcp/ → null.
317
+ * @param {Function} exec - execSync function (for testing)
318
+ * @returns {{ gcloudPath: string|null, source: string }}
319
+ */
320
+ export function findGcloud(exec = execSync) {
321
+ // Try system PATH first
322
+ try {
323
+ exec('gcloud --version', { stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 });
324
+ return { gcloudPath: 'gcloud', source: 'system' };
325
+ } catch { /* not on PATH */ }
326
+
327
+ // Windows: try Cloud SDK default install location
328
+ if (process.platform === 'win32') {
329
+ const winPath = join(process.env.LOCALAPPDATA || '', 'Google', 'Cloud SDK', 'google-cloud-sdk', 'bin', 'gcloud.cmd');
330
+ if (existsSync(winPath)) {
331
+ return { gcloudPath: winPath, source: 'windows-sdk' };
332
+ }
333
+ }
334
+
335
+ // Try bundled at ~/.stitch-mcp/
336
+ const bundledDir = join(homedir(), '.stitch-mcp');
337
+ const bundledBin = process.platform === 'win32'
338
+ ? join(bundledDir, 'google-cloud-sdk', 'bin', 'gcloud.cmd')
339
+ : join(bundledDir, 'google-cloud-sdk', 'bin', 'gcloud');
340
+ if (existsSync(bundledBin)) {
341
+ return { gcloudPath: bundledBin, source: 'bundled' };
342
+ }
343
+
344
+ return { gcloudPath: null, source: 'not-found' };
345
+ }
346
+
347
+ /**
348
+ * Fully automated Stitch MCP setup orchestrator.
349
+ *
350
+ * Steps:
351
+ * 1. Find gcloud CLI
352
+ * 2. Verify ADC credentials
353
+ * 3. Detect GCP project
354
+ * 4. Enable stitch.googleapis.com API
355
+ * 5. Generate proxy script
356
+ * 6. Install MCP config in .mcp.json
357
+ * 7. Health-check the proxy
358
+ *
359
+ * @param {string} targetPath - Project root directory
360
+ * @param {Object} options
361
+ * @param {string} [options.gcpProject] - GCP project ID (auto-detected if omitted)
362
+ * @param {Function} [options._exec] - execSync override for testing
363
+ * @returns {Promise<Object>} Step-by-step results
364
+ */
365
+ export async function setupStitch(targetPath, options = {}) {
366
+ const exec = options._exec || execSync;
367
+ const steps = [];
368
+
369
+ // Step 1: Find gcloud
370
+ const { gcloudPath, source: gcloudSource } = findGcloud(exec);
371
+ if (!gcloudPath) {
372
+ steps.push({ step: 'find-gcloud', success: false, error: 'gcloud CLI not found. Install from: https://cloud.google.com/sdk/docs/install' });
373
+ return { success: false, steps };
374
+ }
375
+ steps.push({ step: 'find-gcloud', success: true, gcloudPath, source: gcloudSource });
376
+
377
+ const gcloudCmd = gcloudPath.includes(' ') ? `"${gcloudPath}"` : gcloudPath;
378
+
379
+ // Step 2: Verify ADC credentials
380
+ try {
381
+ exec(`${gcloudCmd} auth application-default print-access-token`, {
382
+ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000
383
+ });
384
+ steps.push({ step: 'verify-adc', success: true });
385
+ } catch {
386
+ steps.push({ step: 'verify-adc', success: false, error: 'No ADC credentials. Run: gcloud auth application-default login' });
387
+ return { success: false, steps };
388
+ }
389
+
390
+ // Step 3: Detect GCP project
391
+ let gcpProject = options.gcpProject;
392
+ if (!gcpProject) {
393
+ // Try config.json
394
+ const configPath = join(targetPath, '.morph', 'config', 'config.json');
395
+ if (existsSync(configPath)) {
396
+ try {
397
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
398
+ gcpProject = config.gcpProject || config.project?.gcpProject;
399
+ } catch { /* invalid config */ }
400
+ }
401
+ }
402
+ if (!gcpProject) {
403
+ // Try gcloud config
404
+ try {
405
+ gcpProject = exec(`${gcloudCmd} config get-value project`, {
406
+ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000
407
+ }).trim();
408
+ if (!gcpProject || gcpProject === '(unset)') gcpProject = null;
409
+ } catch { /* gcloud config failed */ }
410
+ }
411
+ if (!gcpProject) {
412
+ steps.push({ step: 'detect-project', success: false, error: 'Could not detect GCP project. Pass it explicitly or set via: gcloud config set project <id>' });
413
+ return { success: false, steps };
414
+ }
415
+ steps.push({ step: 'detect-project', success: true, gcpProject });
416
+
417
+ // Step 4: Enable Stitch API
418
+ try {
419
+ exec(`${gcloudCmd} services enable stitch.googleapis.com --project=${gcpProject}`, {
420
+ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000
421
+ });
422
+ steps.push({ step: 'enable-api', success: true });
423
+ } catch (err) {
424
+ steps.push({ step: 'enable-api', success: false, error: `Failed to enable stitch API: ${err.message}` });
425
+ // Non-fatal — API might already be enabled and user may lack permissions to re-enable
426
+ }
427
+
428
+ // Step 5: Generate proxy
429
+ const { proxyPath } = generateStitchProxy(targetPath, gcpProject);
430
+ steps.push({ step: 'generate-proxy', success: true, proxyPath });
431
+
432
+ // Step 6: Install MCP config in .mcp.json
433
+ const mcpResult = await installMcpServers(targetPath, {
434
+ stitch: { type: 'stdio', command: 'node', args: ['.claude/stitch-proxy.cjs'] }
435
+ });
436
+ steps.push({ step: 'install-mcp', success: true, added: mcpResult.added, skipped: mcpResult.skipped });
437
+
438
+ // Step 7: Health-check
439
+ const healthResult = await healthCheckStitchProxy(proxyPath);
440
+ steps.push({ step: 'health-check', ...healthResult });
441
+
442
+ const allCriticalPassed = steps.every(s =>
443
+ s.success || s.step === 'enable-api' || s.step === 'health-check'
444
+ );
445
+
446
+ return { success: allCriticalPassed, steps, gcpProject };
447
+ }
448
+
449
+ /**
450
+ * Health-check the Stitch proxy by sending an MCP initialize handshake.
451
+ * @param {string} proxyPath - Path to stitch-proxy.cjs
452
+ * @returns {Promise<Object>} { success, protocolVersion?, error? }
453
+ */
454
+ export function healthCheckStitchProxy(proxyPath) {
455
+ return new Promise((resolve) => {
456
+ const initMsg = JSON.stringify({
457
+ jsonrpc: '2.0',
458
+ id: 1,
459
+ method: 'initialize',
460
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'morph-spec', version: '1.0.0' } }
461
+ });
462
+
463
+ const child = spawn('node', [proxyPath], {
464
+ stdio: ['pipe', 'pipe', 'pipe'],
465
+ timeout: 20000,
466
+ });
467
+
468
+ let output = '';
469
+ let timedOut = false;
470
+
471
+ const timer = setTimeout(() => {
472
+ timedOut = true;
473
+ child.kill();
474
+ resolve({ success: false, error: 'Health-check timed out after 20s' });
475
+ }, 20000);
476
+
477
+ child.stdout.on('data', (data) => {
478
+ output += data.toString();
479
+ // Check if we got a response
480
+ const lines = output.split('\n').filter(l => l.trim());
481
+ for (const line of lines) {
482
+ try {
483
+ const resp = JSON.parse(line);
484
+ if (resp.result?.protocolVersion) {
485
+ clearTimeout(timer);
486
+ child.kill();
487
+ resolve({ success: true, protocolVersion: resp.result.protocolVersion });
488
+ return;
489
+ }
490
+ if (resp.error) {
491
+ clearTimeout(timer);
492
+ child.kill();
493
+ resolve({ success: false, error: resp.error.message || 'MCP error' });
494
+ return;
495
+ }
496
+ } catch { /* not valid JSON yet */ }
497
+ }
498
+ });
499
+
500
+ child.on('error', (err) => {
501
+ if (!timedOut) {
502
+ clearTimeout(timer);
503
+ resolve({ success: false, error: err.message });
504
+ }
505
+ });
506
+
507
+ child.on('close', (code) => {
508
+ if (!timedOut) {
509
+ clearTimeout(timer);
510
+ resolve({ success: false, error: output.trim()
511
+ ? `Proxy exited with code ${code} — unexpected output`
512
+ : `Proxy exited with code ${code} and no output`
513
+ });
514
+ }
515
+ });
516
+
517
+ // Send the initialize message
518
+ child.stdin.write(initMsg + '\n');
519
+ child.stdin.end();
520
+ });
521
+ }
522
+
315
523
  /**
316
524
  * Install a single MCP with provided credentials
317
525
  * @param {string} targetPath - Project root directory
@@ -13,7 +13,11 @@ import fs from 'fs';
13
13
  import path, { join } from 'path';
14
14
  import { loadState, derivePhase, getFeature } from '../../core/state/state-manager.js';
15
15
  import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
16
- import { shouldAutoApprove } from '../trust/trust-manager.js';
16
+
17
+ // v5: trust-manager removed. Auto-approval is no longer a CLI feature —
18
+ // approval gates are handled by Claude presenting the gate via skill template
19
+ // and the user responding. Stub to keep legacy callers happy.
20
+ const shouldAutoApprove = () => false;
17
21
 
18
22
  // ─────────────────────────────────────────────────────────────────────────────
19
23
  // Phase definitions (from validate-phase.js)
@@ -34,53 +38,41 @@ export const PHASES = {
34
38
  description: 'UI/UX design (conditional - only if frontend)',
35
39
  optional: true
36
40
  },
37
- 'design': {
41
+ 'plan': {
38
42
  order: 2,
39
- name: 'FASE 2: DESIGN',
43
+ name: 'FASE 2: PLAN',
40
44
  requiredOutputs: ['proposal.md'],
41
- description: 'Technical specification, contracts, and clarification'
42
- },
43
- 'plan': {
44
- order: 3,
45
- name: 'FASE 3: PLAN',
46
- requiredOutputs: ['proposal.md', 'spec.md'],
47
- description: 'Planning phase before task breakdown'
48
- },
49
- 'tasks': {
50
- order: 4,
51
- name: 'FASE 4: TASKS',
52
- requiredOutputs: ['proposal.md', 'spec.md', 'plan.md'],
53
- description: 'Break down into executable tasks'
45
+ description: 'Technical specification, implementation plan, task breakdown, and mandate'
54
46
  },
55
47
  'implement': {
56
- order: 5,
57
- name: 'FASE 5: IMPLEMENT',
48
+ order: 3,
49
+ name: 'FASE 3: IMPLEMENT',
58
50
  requiredOutputs: ['proposal.md', 'spec.md', 'tasks.md'],
59
51
  description: 'Execute tasks and implement code'
60
52
  },
61
- 'sync': {
62
- order: 6,
63
- name: 'FASE 6: SYNC',
64
- requiredOutputs: ['proposal.md', 'spec.md', 'decisions.md', 'recap.md'],
65
- description: 'Sync decisions to project standards',
66
- optional: true
53
+ 'review': {
54
+ order: 4,
55
+ name: 'FASE 4: REVIEW',
56
+ requiredOutputs: ['proposal.md', 'spec.md', 'mandate.md', 'tasks.md', 'recap.md', 'review-report.md'],
57
+ description: 'Post-implementation quality review against mandate'
67
58
  }
68
59
  };
69
60
 
70
- // Map output filenames to their phase subfolders
61
+ // Map output filenames to their phase subfolders (v5)
71
62
  const OUTPUT_PHASE_MAP = {
72
63
  'proposal.md': '0-proposal',
73
- 'schema-analysis.md': '2-design',
74
- 'spec.md': '2-design',
75
- 'contracts.cs': '2-design',
76
- 'decisions.md': '2-design',
64
+ 'stack-scan.md': '0-proposal',
65
+ 'migration-plan.md': '0-proposal',
66
+ 'spec.md': '2-plan',
67
+ 'mandate.md': '2-plan',
68
+ 'decisions.md': '2-plan',
77
69
  'design-system.md': '1-ui',
78
70
  'mockups.md': '1-ui',
79
71
  'components.md': '1-ui',
80
72
  'flows.md': '1-ui',
81
- 'plan.md': '3-plan',
82
- 'tasks.md': '4-tasks',
83
- 'recap.md': '5-implement'
73
+ 'tasks.md': '2-plan',
74
+ 'recap.md': '3-implement',
75
+ 'review-report.md': '4-review'
84
76
  };
85
77
 
86
78
  function checkOutput(featurePath, outputFile) {
@@ -147,7 +139,7 @@ export function validatePhase(featureName, targetPhase) {
147
139
 
148
140
  // Special validation: implement phase requires tasks.md to have actual tasks
149
141
  if (targetPhase === 'implement') {
150
- const tasksFilePath = path.join(featurePath, '4-tasks', 'tasks.md');
142
+ const tasksFilePath = path.join(featurePath, '2-plan', 'tasks.md');
151
143
  if (fs.existsSync(tasksFilePath)) {
152
144
  const content = fs.readFileSync(tasksFilePath, 'utf-8');
153
145
  const taskCount = (content.match(/^###\s+T\d+/gm) || []).length;
@@ -186,7 +178,7 @@ export function validatePhase(featureName, targetPhase) {
186
178
  // Phase eligibility
187
179
  // ─────────────────────────────────────────────────────────────────────────────
188
180
 
189
- const PHASE_ORDER = ['proposal', 'uiux', 'design', 'plan', 'tasks', 'implement', 'sync'];
181
+ const PHASE_ORDER = ['proposal', 'uiux', 'plan', 'implement', 'review'];
190
182
 
191
183
  function getNextPhase(currentPhase, featureName) {
192
184
  const idx = PHASE_ORDER.indexOf(currentPhase);
@@ -224,13 +216,17 @@ export function computePassRate(validationHistory) {
224
216
  return entries.length > 0 ? passed / entries.length : null;
225
217
  }
226
218
 
227
- // Map output type names to file paths for eligibility checks
219
+ // Map output type names to file paths for eligibility checks (v5)
228
220
  const OUTPUT_PATH_MAP = {
229
221
  'proposal': '0-proposal/proposal.md',
230
- 'spec': '2-design/spec.md',
231
- 'contracts': '2-design/contracts.cs',
232
- 'tasks': '4-tasks/tasks.md',
233
- 'schemaAnalysis': '2-design/schema-analysis.md',
222
+ 'stackScan': '0-proposal/stack-scan.md',
223
+ 'migrationPlan': '0-proposal/migration-plan.md',
224
+ 'spec': '2-plan/spec.md',
225
+ 'mandate': '2-plan/mandate.md',
226
+ 'tasks': '2-plan/tasks.md',
227
+ 'decisions': '2-plan/decisions.md',
228
+ 'recap': '3-implement/recap.md',
229
+ 'reviewReport': '4-review/review-report.md',
234
230
  };
235
231
 
236
232
  function getMissingRequiredOutputs(featureName, phase, projectPath) {
@@ -335,7 +331,7 @@ export function checkPhaseEligibility(featureName, opts = {}) {
335
331
  }
336
332
 
337
333
  try {
338
- const requiredGateMap = { design: 'design', tasks: 'tasks', uiux: 'uiux' };
334
+ const requiredGateMap = { plan: 'plan', uiux: 'uiux', review: 'review' };
339
335
  const gate = requiredGateMap[currentPhase];
340
336
  if (gate) {
341
337
  const trustResult = shouldAutoApprove(featureName, gate);