@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.
- 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
package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* detect-dev-server.mjs
|
|
4
|
-
*
|
|
5
|
-
* Detects an active dev server on common localhost ports,
|
|
6
|
-
* optionally starting one if not found.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* node detect-dev-server.mjs [startCommand]
|
|
10
|
-
*
|
|
11
|
-
* Examples:
|
|
12
|
-
* node detect-dev-server.mjs
|
|
13
|
-
* node detect-dev-server.mjs "npm run dev"
|
|
14
|
-
* node detect-dev-server.mjs "dotnet run --project src/Api/Api.csproj"
|
|
15
|
-
*
|
|
16
|
-
* Output JSON (stdout):
|
|
17
|
-
* { found: true, url: "http://localhost:3000", started?: true } → exit 0
|
|
18
|
-
* { found: false, error?: "..." } → exit 1
|
|
19
|
-
*
|
|
20
|
-
* Behavior:
|
|
21
|
-
* 1. Scan ports [3000, 3001, 4200, 5000, 5001, 7000, 8000, 8080] with 500ms timeout
|
|
22
|
-
* 2. If found → output { found: true, url } and exit 0
|
|
23
|
-
* 3. If not found and startCommand provided:
|
|
24
|
-
* - Spawn startCommand as background process
|
|
25
|
-
* - Poll ports every 2s for up to 30s
|
|
26
|
-
* - If server starts → output { found: true, url, started: true } and exit 0
|
|
27
|
-
* - If timeout → output { found: false, error: "timeout after 30s" } and exit 1
|
|
28
|
-
* 4. If not found and no startCommand → output { found: false } and exit 1
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
import { spawn } from 'child_process';
|
|
32
|
-
|
|
33
|
-
const PORTS = [3000, 3001, 4200, 5000, 5001, 7000, 8000, 8080];
|
|
34
|
-
const FETCH_TIMEOUT_MS = 500;
|
|
35
|
-
const START_TIMEOUT_MS = 30_000;
|
|
36
|
-
const POLL_INTERVAL_MS = 2_000;
|
|
37
|
-
|
|
38
|
-
const startCommand = process.argv[2] ?? null;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Try to fetch a URL with a timeout.
|
|
42
|
-
* Returns true if response is received (any status code), false on network error/timeout.
|
|
43
|
-
*/
|
|
44
|
-
async function isPortOpen(url) {
|
|
45
|
-
const controller = new AbortController();
|
|
46
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
47
|
-
try {
|
|
48
|
-
await fetch(url, { signal: controller.signal });
|
|
49
|
-
return true;
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
} finally {
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Scan all known ports and return the first responsive URL, or null.
|
|
59
|
-
*/
|
|
60
|
-
async function scanPorts() {
|
|
61
|
-
for (const port of PORTS) {
|
|
62
|
-
const url = `http://localhost:${port}`;
|
|
63
|
-
if (await isPortOpen(url)) {
|
|
64
|
-
return url;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Step 1: Scan for existing server ────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
const existing = await scanPorts();
|
|
73
|
-
|
|
74
|
-
if (existing) {
|
|
75
|
-
console.log(JSON.stringify({ found: true, url: existing }, null, 2));
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─── Step 2: No server found, try to start one ───────────────────────────────
|
|
80
|
-
|
|
81
|
-
if (!startCommand) {
|
|
82
|
-
console.log(JSON.stringify({ found: false }, null, 2));
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Spawn the start command as a background process
|
|
87
|
-
// Support both simple commands and commands with "cd X && Y" syntax
|
|
88
|
-
let child;
|
|
89
|
-
try {
|
|
90
|
-
// Use shell=true to handle complex commands like "cd frontend && npm run dev"
|
|
91
|
-
child = spawn(startCommand, [], {
|
|
92
|
-
shell: true,
|
|
93
|
-
detached: false,
|
|
94
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Suppress child output (we only care if it starts)
|
|
98
|
-
child.stdout?.resume();
|
|
99
|
-
child.stderr?.resume();
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.log(JSON.stringify({ found: false, error: `Failed to spawn: ${err.message}` }, null, 2));
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Poll for the server to become available
|
|
106
|
-
const startedAt = Date.now();
|
|
107
|
-
|
|
108
|
-
const result = await new Promise((resolve) => {
|
|
109
|
-
const poll = async () => {
|
|
110
|
-
const elapsed = Date.now() - startedAt;
|
|
111
|
-
|
|
112
|
-
if (elapsed >= START_TIMEOUT_MS) {
|
|
113
|
-
resolve({ found: false, error: 'timeout after 30s — server did not start in time' });
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const url = await scanPorts();
|
|
118
|
-
if (url) {
|
|
119
|
-
resolve({ found: true, url, started: true });
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
setTimeout(poll, POLL_INTERVAL_MS);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Handle child process errors / early exit
|
|
127
|
-
child.on('error', (err) => {
|
|
128
|
-
resolve({ found: false, error: `Process error: ${err.message}` });
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
child.on('exit', (code) => {
|
|
132
|
-
// Only treat as error if it exited quickly (< 3s) with non-zero
|
|
133
|
-
if (Date.now() - startedAt < 3_000 && code !== 0 && code !== null) {
|
|
134
|
-
resolve({ found: false, error: `Process exited with code ${code}` });
|
|
135
|
-
}
|
|
136
|
-
// Otherwise it may have crashed after starting — let the poll timeout handle it
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// Start polling after a short initial delay
|
|
140
|
-
setTimeout(poll, POLL_INTERVAL_MS);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Clean up the child process if still running
|
|
144
|
-
if (child && !child.killed) {
|
|
145
|
-
try {
|
|
146
|
-
child.kill('SIGTERM');
|
|
147
|
-
} catch {
|
|
148
|
-
// ignore cleanup errors
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
console.log(JSON.stringify(result, null, 2));
|
|
153
|
-
process.exit(result.found ? 0 : 1);
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* detect-stack.mjs
|
|
4
|
-
*
|
|
5
|
-
* Detects the project tech stack from config, context, or file scan.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* node detect-stack.mjs
|
|
9
|
-
*
|
|
10
|
-
* Output JSON:
|
|
11
|
-
* {
|
|
12
|
-
* stack: "DOTNET" | "NEXTJS" | "FULLSTACK" | "UNKNOWN",
|
|
13
|
-
* frontendPath: string | null,
|
|
14
|
-
* backendPath: string | null,
|
|
15
|
-
* startCommand: string | null
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* Detection order (most reliable first):
|
|
19
|
-
* 1. .morph/config/config.json → config.project.stack + frontendPath/backendPath
|
|
20
|
-
* 2. .morph/context/README.md → "## Tech Stack" section
|
|
21
|
-
* 3. Fallback: glob *.csproj (DOTNET) + package.json with dep "next" (NEXTJS)
|
|
22
|
-
*
|
|
23
|
-
* Exit code: 0 always
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
27
|
-
import { join, relative, dirname } from 'path';
|
|
28
|
-
|
|
29
|
-
const cwd = process.cwd();
|
|
30
|
-
|
|
31
|
-
/** Try to read a JSON file safely. Returns null on failure. */
|
|
32
|
-
function readJson(filePath) {
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Try to read a text file safely. Returns null on failure. */
|
|
41
|
-
function readText(filePath) {
|
|
42
|
-
try {
|
|
43
|
-
return readFileSync(filePath, 'utf-8');
|
|
44
|
-
} catch {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Find files matching a pattern recursively.
|
|
51
|
-
* Returns relative paths from cwd.
|
|
52
|
-
*/
|
|
53
|
-
function findFiles(dir, predicate, maxDepth = 4, _depth = 0) {
|
|
54
|
-
if (_depth > maxDepth) return [];
|
|
55
|
-
if (!existsSync(dir)) return [];
|
|
56
|
-
const results = [];
|
|
57
|
-
const ignored = ['node_modules', '.git', '.next', 'dist', 'build', '.turbo', 'bin', 'obj', '.morph'];
|
|
58
|
-
try {
|
|
59
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
60
|
-
if (entry.isDirectory()) {
|
|
61
|
-
if (!ignored.includes(entry.name)) {
|
|
62
|
-
results.push(...findFiles(join(dir, entry.name), predicate, maxDepth, _depth + 1));
|
|
63
|
-
}
|
|
64
|
-
} else if (entry.isFile() && predicate(entry.name)) {
|
|
65
|
-
results.push(join(dir, entry.name));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// ignore permission errors
|
|
70
|
-
}
|
|
71
|
-
return results;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Normalize a stack string from config to canonical form.
|
|
76
|
-
* Handles values like "nextjs+dotnet", "dotnet", "next.js", "nextjs", etc.
|
|
77
|
-
*/
|
|
78
|
-
function normalizeStack(raw) {
|
|
79
|
-
if (!raw || typeof raw !== 'string') return null;
|
|
80
|
-
const s = raw.toLowerCase();
|
|
81
|
-
const hasDotnet = s.includes('dotnet') || s.includes('.net');
|
|
82
|
-
const hasNext = s.includes('next') || s.includes('nextjs');
|
|
83
|
-
if (hasDotnet && hasNext) return 'FULLSTACK';
|
|
84
|
-
if (hasDotnet) return 'DOTNET';
|
|
85
|
-
if (hasNext) return 'NEXTJS';
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Parse "## Tech Stack" section from a README-style markdown file.
|
|
91
|
-
* Returns normalized stack or null.
|
|
92
|
-
*/
|
|
93
|
-
function parseReadmeStack(content) {
|
|
94
|
-
if (!content) return null;
|
|
95
|
-
// Find "## Tech Stack" section and read next ~10 lines
|
|
96
|
-
const lines = content.split('\n');
|
|
97
|
-
let inSection = false;
|
|
98
|
-
const sectionLines = [];
|
|
99
|
-
for (const line of lines) {
|
|
100
|
-
if (/^##\s+tech\s+stack/i.test(line)) {
|
|
101
|
-
inSection = true;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (inSection) {
|
|
105
|
-
if (/^##/.test(line)) break; // next section
|
|
106
|
-
sectionLines.push(line.toLowerCase());
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (sectionLines.length === 0) return null;
|
|
110
|
-
const text = sectionLines.join(' ');
|
|
111
|
-
const hasDotnet = /dotnet|\.net|c#|asp\.net/.test(text);
|
|
112
|
-
const hasNext = /next\.?js|nextjs/.test(text);
|
|
113
|
-
if (hasDotnet && hasNext) return 'FULLSTACK';
|
|
114
|
-
if (hasDotnet) return 'DOTNET';
|
|
115
|
-
if (hasNext) return 'NEXTJS';
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Build a startCommand from stack + paths.
|
|
121
|
-
*/
|
|
122
|
-
function buildStartCommand(stack, frontendPath, backendPath) {
|
|
123
|
-
if (stack === 'NEXTJS') {
|
|
124
|
-
const base = frontendPath ? join(cwd, frontendPath) : cwd;
|
|
125
|
-
// Check for custom dev script
|
|
126
|
-
const pkgPath = join(base, 'package.json');
|
|
127
|
-
const pkg = readJson(pkgPath);
|
|
128
|
-
if (pkg?.scripts?.dev) {
|
|
129
|
-
return frontendPath ? `cd ${frontendPath} && npm run dev` : 'npm run dev';
|
|
130
|
-
}
|
|
131
|
-
return frontendPath ? `cd ${frontendPath} && npm run dev` : 'npm run dev';
|
|
132
|
-
}
|
|
133
|
-
if (stack === 'DOTNET') {
|
|
134
|
-
if (backendPath) {
|
|
135
|
-
// Try to find .csproj inside backendPath
|
|
136
|
-
const csprojFiles = findFiles(join(cwd, backendPath), (n) => n.endsWith('.csproj'), 2);
|
|
137
|
-
if (csprojFiles.length > 0) {
|
|
138
|
-
const rel = relative(cwd, csprojFiles[0]).replace(/\\/g, '/');
|
|
139
|
-
return `dotnet run --project ${rel}`;
|
|
140
|
-
}
|
|
141
|
-
return `dotnet run --project ${backendPath}`;
|
|
142
|
-
}
|
|
143
|
-
// Find any .csproj
|
|
144
|
-
const csprojFiles = findFiles(cwd, (n) => n.endsWith('.csproj'), 4);
|
|
145
|
-
if (csprojFiles.length > 0) {
|
|
146
|
-
const rel = relative(cwd, csprojFiles[0]).replace(/\\/g, '/');
|
|
147
|
-
return `dotnet run --project ${rel}`;
|
|
148
|
-
}
|
|
149
|
-
return 'dotnet run';
|
|
150
|
-
}
|
|
151
|
-
if (stack === 'FULLSTACK') {
|
|
152
|
-
// Prefer Next.js start for smoke testing
|
|
153
|
-
const fe = frontendPath ? join(cwd, frontendPath) : cwd;
|
|
154
|
-
return frontendPath ? `cd ${frontendPath} && npm run dev` : 'npm run dev';
|
|
155
|
-
}
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ─── Detection Strategy 1: .morph/config/config.json ─────────────────────────
|
|
160
|
-
|
|
161
|
-
const configPath = join(cwd, '.morph', 'config', 'config.json');
|
|
162
|
-
const config = readJson(configPath);
|
|
163
|
-
|
|
164
|
-
if (config?.config?.project?.stack) {
|
|
165
|
-
const stack = normalizeStack(config.config.project.stack);
|
|
166
|
-
if (stack) {
|
|
167
|
-
const frontendPath = config.config.project.frontendPath ?? null;
|
|
168
|
-
const backendPath = config.config.project.backendPath ?? null;
|
|
169
|
-
const startCommand = buildStartCommand(stack, frontendPath, backendPath);
|
|
170
|
-
console.log(JSON.stringify({ stack, frontendPath, backendPath, startCommand }, null, 2));
|
|
171
|
-
process.exit(0);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ─── Detection Strategy 2: .morph/context/README.md ──────────────────────────
|
|
176
|
-
|
|
177
|
-
const readmePath = join(cwd, '.morph', 'context', 'README.md');
|
|
178
|
-
const readmeContent = readText(readmePath);
|
|
179
|
-
const readmeStack = parseReadmeStack(readmeContent);
|
|
180
|
-
|
|
181
|
-
if (readmeStack) {
|
|
182
|
-
const startCommand = buildStartCommand(readmeStack, null, null);
|
|
183
|
-
console.log(JSON.stringify({
|
|
184
|
-
stack: readmeStack,
|
|
185
|
-
frontendPath: null,
|
|
186
|
-
backendPath: null,
|
|
187
|
-
startCommand,
|
|
188
|
-
}, null, 2));
|
|
189
|
-
process.exit(0);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ─── Detection Strategy 3: File scan ─────────────────────────────────────────
|
|
193
|
-
|
|
194
|
-
const csprojFiles = findFiles(cwd, (n) => n.endsWith('.csproj'), 4);
|
|
195
|
-
const hasDotnet = csprojFiles.length > 0;
|
|
196
|
-
|
|
197
|
-
// Find package.json files with "next" dependency (excluding node_modules already filtered)
|
|
198
|
-
const packageJsonFiles = findFiles(cwd, (n) => n === 'package.json', 4);
|
|
199
|
-
let hasNext = false;
|
|
200
|
-
let nextPackageDir = null;
|
|
201
|
-
|
|
202
|
-
for (const pkgFile of packageJsonFiles) {
|
|
203
|
-
const pkg = readJson(pkgFile);
|
|
204
|
-
if (pkg?.dependencies?.next || pkg?.devDependencies?.next) {
|
|
205
|
-
hasNext = true;
|
|
206
|
-
nextPackageDir = relative(cwd, dirname(pkgFile)).replace(/\\/g, '/') || null;
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
let stack = 'UNKNOWN';
|
|
212
|
-
let frontendPath = null;
|
|
213
|
-
let backendPath = null;
|
|
214
|
-
|
|
215
|
-
if (hasDotnet && hasNext) {
|
|
216
|
-
stack = 'FULLSTACK';
|
|
217
|
-
frontendPath = nextPackageDir;
|
|
218
|
-
backendPath = csprojFiles.length > 0
|
|
219
|
-
? relative(cwd, dirname(csprojFiles[0])).replace(/\\/g, '/') || null
|
|
220
|
-
: null;
|
|
221
|
-
} else if (hasDotnet) {
|
|
222
|
-
stack = 'DOTNET';
|
|
223
|
-
backendPath = csprojFiles.length > 0
|
|
224
|
-
? relative(cwd, dirname(csprojFiles[0])).replace(/\\/g, '/') || null
|
|
225
|
-
: null;
|
|
226
|
-
} else if (hasNext) {
|
|
227
|
-
stack = 'NEXTJS';
|
|
228
|
-
frontendPath = nextPackageDir;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const startCommand = buildStartCommand(stack, frontendPath, backendPath);
|
|
232
|
-
|
|
233
|
-
console.log(JSON.stringify({ stack, frontendPath, backendPath, startCommand }, null, 2));
|
|
234
|
-
process.exit(0);
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: morph-simulation-checklist
|
|
3
|
-
description: Checklist for implementing mock/simulation clients for external .NET services (AI APIs, payments, email). Use when setting up simulation mode without real API access, when creating fake service implementations for development/testing, or when debugging external service integrations.
|
|
4
|
-
user-invocable: true
|
|
5
|
-
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
6
|
-
stacks:
|
|
7
|
-
- dotnet
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Simulation Checklist
|
|
11
|
-
|
|
12
|
-
Checklist for simulating external services (AI, payment, email APIs) in .NET projects.
|
|
13
|
-
|
|
14
|
-
## Pre-Implementation
|
|
15
|
-
|
|
16
|
-
### 1. Map All Dependencies
|
|
17
|
-
|
|
18
|
-
- [ ] List ALL interfaces in the flow (`grep -r "I{ServiceName}" src/ --include="*.cs" -l`)
|
|
19
|
-
- [ ] Identify **transitive dependencies** (e.g., `ArtGenerationService` → `IReplicateClient` + `IImageDownloader`)
|
|
20
|
-
- [ ] Document dependency trace: services to mock, transitive deps, required lifetimes
|
|
21
|
-
|
|
22
|
-
### 2. Verify Lifetimes
|
|
23
|
-
|
|
24
|
-
| State | Lifetime | Example |
|
|
25
|
-
|-------|----------|---------|
|
|
26
|
-
| Stateful (Dictionary, List) | **Singleton** | `FakeReplicateClient` with prediction store |
|
|
27
|
-
| Stateless | **Scoped** | `FakeEmailClient` that only logs |
|
|
28
|
-
|
|
29
|
-
```csharp
|
|
30
|
-
// ❌ Stateful + Scoped = loses state between requests
|
|
31
|
-
services.AddScoped<IReplicateClient, FakeReplicateClient>();
|
|
32
|
-
// ✅ Stateful + Singleton
|
|
33
|
-
services.AddSingleton<IReplicateClient, FakeReplicateClient>();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
### 3. Read Complete Interfaces
|
|
37
|
-
|
|
38
|
-
- [ ] Check full signatures — don't miss optional parameters (`attachments`, `CancellationToken`)
|
|
39
|
-
- [ ] For AI mocks: verify current prompts, model in use, response format
|
|
40
|
-
- [ ] For email mocks: check for attachments/inline images, log simulated sends
|
|
41
|
-
- [ ] For payment mocks: implement state transitions (PENDING → CONFIRMED → REFUNDED), simulate webhooks
|
|
42
|
-
|
|
43
|
-
## Configuration Template
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
// appsettings.Development.json
|
|
47
|
-
{ "Simulation": { "Enabled": true, "ImageDelayMs": 500, "PlaceholderImageUrl": "https://picsum.photos/1024/1024" } }
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
```csharp
|
|
51
|
-
if (configuration.GetValue<bool>("Simulation:Enabled"))
|
|
52
|
-
services.AddSimulationClients(configuration); // Stateful→Singleton, Stateless→Scoped
|
|
53
|
-
else
|
|
54
|
-
services.AddProductionClients(configuration);
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## MORPH-SPEC Integration
|
|
58
|
-
|
|
59
|
-
When implementing simulation as a morph-spec task:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
# Track the simulation setup task
|
|
63
|
-
morph-spec task start {feature} {task-id}
|
|
64
|
-
|
|
65
|
-
# After implementation, validate the feature
|
|
66
|
-
morph-spec validate-feature {feature}
|
|
67
|
-
|
|
68
|
-
# Complete the task (triggers Tier-4 validators)
|
|
69
|
-
morph-spec task done {feature} {task-id}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Reference the project's service lifetime patterns:
|
|
73
|
-
```bash
|
|
74
|
-
# Check existing DI patterns in the project
|
|
75
|
-
grep -r "AddSingleton\|AddScoped\|AddTransient" src/ --include="*.cs" -l
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Post-Implementation
|
|
79
|
-
|
|
80
|
-
- [ ] Validate DI: `dotnet build && dotnet run` + health check
|
|
81
|
-
- [ ] Test full flow end-to-end (not just unit tests)
|
|
82
|
-
- [ ] Verify simulation logs — confirm mocks are being called
|
|
83
|
-
- [ ] Run `morph-spec validate-feature {feature}` to check architecture compliance
|
|
84
|
-
|
|
85
|
-
## Common Errors
|
|
86
|
-
|
|
87
|
-
| Error | Cause | Fix |
|
|
88
|
-
|-------|-------|-----|
|
|
89
|
-
| Mock loses state between requests | Scoped lifetime for stateful service | Change to Singleton |
|
|
90
|
-
| `Unable to resolve service` at runtime | Transitive dependency not registered | Map ALL dependencies |
|
|
91
|
-
| Incorrect method signature | Didn't read full interface | Check optional params |
|
|
92
|
-
| Mock returns wrong data | Prompts/model changed | Read current prompts |
|
|
93
|
-
| Works in test, fails at runtime | Test doesn't cover full DI | Test with `dotnet run` |
|
|
94
|
-
|
|
95
|
-
## Checklist
|
|
96
|
-
|
|
97
|
-
- [ ] All interfaces mapped (including transitive)
|
|
98
|
-
- [ ] Lifetimes correct (stateful=Singleton, stateless=Scoped)
|
|
99
|
-
- [ ] Complete interface signatures implemented
|
|
100
|
-
- [ ] Simulation toggle via config
|
|
101
|
-
- [ ] End-to-end flow tested in simulation mode
|
|
102
|
-
- [ ] No real API calls in simulation mode
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
*MORPH-SPEC Simulation Checklist*
|