@polymorphism-tech/morph-spec 4.22.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) 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 -1198
  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 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  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/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /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);