@polymorphism-tech/morph-spec 4.22.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.
- package/CLAUDE.md +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1198
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -152
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -9
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -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', '
|
|
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
|
-
|
|
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
|
|
191
|
+
const { createInterface } = require('readline');
|
|
192
|
+
|
|
193
|
+
const STITCH_URL = 'https://stitch.googleapis.com/mcp';
|
|
194
|
+
const PROJECT_ID = '${gcpProject}';
|
|
191
195
|
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
202
|
+
let tokenExpiry = 0;
|
|
203
|
+
let pendingRequests = 0;
|
|
204
|
+
let stdinClosed = false;
|
|
205
|
+
let sessionId = null;
|
|
199
206
|
|
|
200
|
-
function
|
|
201
|
-
if (cachedToken && Date.now() <
|
|
207
|
+
function getToken() {
|
|
208
|
+
if (cachedToken && Date.now() < tokenExpiry) return cachedToken;
|
|
202
209
|
try {
|
|
203
|
-
cachedToken = execSync(
|
|
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
|
-
|
|
213
|
+
tokenExpiry = Date.now() + 50 * 60 * 1000; // 50-minute TTL
|
|
209
214
|
return cachedToken;
|
|
210
|
-
} catch
|
|
211
|
-
|
|
212
|
-
throw err;
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
213
217
|
}
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
function
|
|
217
|
-
|
|
218
|
-
tokenExpiresAt = 0;
|
|
220
|
+
function maybeExit() {
|
|
221
|
+
if (stdinClosed && pendingRequests === 0) process.exit(0);
|
|
219
222
|
}
|
|
220
223
|
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
41
|
+
'plan': {
|
|
38
42
|
order: 2,
|
|
39
|
-
name: 'FASE 2:
|
|
43
|
+
name: 'FASE 2: PLAN',
|
|
40
44
|
requiredOutputs: ['proposal.md'],
|
|
41
|
-
description: 'Technical specification,
|
|
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:
|
|
57
|
-
name: 'FASE
|
|
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
|
-
'
|
|
62
|
-
order:
|
|
63
|
-
name: 'FASE
|
|
64
|
-
requiredOutputs: ['proposal.md', 'spec.md', '
|
|
65
|
-
description: '
|
|
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
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'
|
|
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
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
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, '
|
|
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', '
|
|
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
|
-
'
|
|
231
|
-
'
|
|
232
|
-
'
|
|
233
|
-
'
|
|
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 = {
|
|
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);
|