@polymorphism-tech/morph-spec 4.8.1 → 4.8.4
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/README.md +2 -2
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/hooks/dev/guard-version-numbers.js +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +1 -1
- package/package.json +4 -4
- package/.morph/analytics/threads-log.jsonl +0 -54
- package/.morph/state.json +0 -198
- package/docs/ARCHITECTURE.md +0 -328
- package/docs/COMMAND-FLOWS.md +0 -398
- package/docs/plans/2026-02-22-claude-docs-morph-alignment-analysis.md +0 -514
- package/docs/plans/2026-02-22-claude-settings.md +0 -517
- package/docs/plans/2026-02-22-morph-cc-alignment-impl.md +0 -730
- package/docs/plans/2026-02-22-morph-spec-next.md +0 -480
- package/docs/plans/2026-02-22-native-alignment-design.md +0 -201
- package/docs/plans/2026-02-22-native-alignment-impl.md +0 -927
- package/docs/plans/2026-02-22-native-enrichment-design.md +0 -246
- package/docs/plans/2026-02-22-native-enrichment.md +0 -737
- package/docs/plans/2026-02-23-ddd-architecture-refactor.md +0 -1155
- package/docs/plans/2026-02-23-ddd-nextsteps.md +0 -684
- package/docs/plans/2026-02-23-infra-architect-refactor.md +0 -439
- package/docs/plans/2026-02-23-nextjs-code-review-design.md +0 -157
- package/docs/plans/2026-02-23-nextjs-code-review-impl.md +0 -1256
- package/docs/plans/2026-02-23-nextjs-standards-design.md +0 -150
- package/docs/plans/2026-02-23-nextjs-standards-impl.md +0 -1848
- package/docs/plans/2026-02-24-cli-radical-simplification.md +0 -592
- package/docs/plans/2026-02-24-framework-failure-points.md +0 -125
- package/docs/plans/2026-02-24-morph-init-design.md +0 -337
- package/docs/plans/2026-02-24-morph-init-impl.md +0 -1269
- package/docs/plans/2026-02-24-tutorial-command-design.md +0 -71
- package/docs/plans/2026-02-24-tutorial-command.md +0 -298
- package/scripts/bump-version.js +0 -248
- package/scripts/generate-refs.js +0 -336
- package/scripts/generate-standards-registry.js +0 -44
- package/scripts/install-dev-hooks.js +0 -138
- package/scripts/scan-nextjs.mjs +0 -169
- package/scripts/validate-real.mjs +0 -255
|
@@ -1,1269 +0,0 @@
|
|
|
1
|
-
# morph-init Implementation Plan
|
|
2
|
-
|
|
3
|
-
**Status:** COMPLETE
|
|
4
|
-
|
|
5
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
6
|
-
|
|
7
|
-
**Goal:** Replace `morph-spec init` as user-facing entry point with a `/morph-init` skill that uses LLM analysis for robust project detection across any stack or layout, and auto-installs required Claude Code plugins.
|
|
8
|
-
|
|
9
|
-
**Architecture:** Extract infrastructure setup from `init.js` into a headless `setup-infra.js` script. Add `install-plugin.js` to auto-install superpowers + context7 from GitHub. Create `global-install.js` postinstall to distribute the `morph-init` skill globally. Write the `morph-init` SKILL.md as the single user-facing entry point.
|
|
10
|
-
|
|
11
|
-
**Key constraint:** The `morph-init` skill runs inside the *user's project*, not inside the morph-spec-framework repo. It cannot call `node src/scripts/...` directly. All script logic must be exposed as CLI subcommands (`morph-spec setup-infra`, `morph-spec install-plugin <name>`) so the skill can call them via the globally-installed `morph-spec` binary.
|
|
12
|
-
|
|
13
|
-
**Tech Stack:** Node.js ESM, fs-extra, node:https, Commander.js, Claude Code skill format (YAML frontmatter + markdown)
|
|
14
|
-
|
|
15
|
-
**Design doc:** `docs/plans/2026-02-24-morph-init-design.md`
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Task 1: Create `src/scripts/setup-infra.js` + `morph-spec setup-infra` command
|
|
20
|
-
|
|
21
|
-
Extract all infrastructure logic from `init.js` into a standalone headless module, then expose it as a CLI subcommand so the `morph-init` skill can call `morph-spec setup-infra` from any user project.
|
|
22
|
-
|
|
23
|
-
**Files:**
|
|
24
|
-
|
|
25
|
-
- Create: `src/scripts/setup-infra.js` — pure logic, exported function
|
|
26
|
-
- Create: `src/commands/project/setup-infra-cmd.js` — thin CLI wrapper
|
|
27
|
-
- Modify: `bin/morph-spec.js` — register `morph-spec setup-infra`
|
|
28
|
-
- Create: `test/scripts/setup-infra.test.js`
|
|
29
|
-
|
|
30
|
-
**Step 1: Write failing tests**
|
|
31
|
-
|
|
32
|
-
```js
|
|
33
|
-
// test/scripts/setup-infra.test.js
|
|
34
|
-
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
35
|
-
import assert from 'node:assert/strict';
|
|
36
|
-
import { existsSync, readFileSync } from 'fs';
|
|
37
|
-
import { join } from 'path';
|
|
38
|
-
import { createTempDir, cleanupTempDir } from '../helpers/test-utils.js';
|
|
39
|
-
import { setupInfra } from '../../src/scripts/setup-infra.js';
|
|
40
|
-
|
|
41
|
-
describe('setup-infra', () => {
|
|
42
|
-
let tempDir;
|
|
43
|
-
beforeEach(() => { tempDir = createTempDir(); });
|
|
44
|
-
afterEach(() => { cleanupTempDir(tempDir); });
|
|
45
|
-
|
|
46
|
-
test('creates .morph/ directory structure', async () => {
|
|
47
|
-
await setupInfra(tempDir);
|
|
48
|
-
const dirs = ['config', 'framework', 'context', 'features', 'checkpoints', 'memory', 'archive'];
|
|
49
|
-
for (const d of dirs) {
|
|
50
|
-
assert.ok(existsSync(join(tempDir, '.morph', d)), `.morph/${d} must exist`);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('writes minimal config.json with name and version only', async () => {
|
|
55
|
-
await setupInfra(tempDir);
|
|
56
|
-
const config = JSON.parse(readFileSync(join(tempDir, '.morph', 'config', 'config.json'), 'utf8'));
|
|
57
|
-
assert.ok(config.project.name, 'must have project.name');
|
|
58
|
-
assert.ok(config.frameworkVersion, 'must have frameworkVersion');
|
|
59
|
-
assert.strictEqual(config.project.stack, undefined, 'must NOT have stack — skill sets this');
|
|
60
|
-
assert.strictEqual(config.project.architecture, undefined, 'must NOT have architecture — skill sets this');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('writes placeholder context/README.md', async () => {
|
|
64
|
-
await setupInfra(tempDir);
|
|
65
|
-
const readme = readFileSync(join(tempDir, '.morph', 'context', 'README.md'), 'utf8');
|
|
66
|
-
assert.ok(readme.includes('Project Context'), 'must have header');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('installs .claude/skills/ directory', async () => {
|
|
70
|
-
await setupInfra(tempDir);
|
|
71
|
-
assert.ok(existsSync(join(tempDir, '.claude', 'skills')), '.claude/skills/ must exist');
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
test('installs .claude/agents/ directory', async () => {
|
|
75
|
-
await setupInfra(tempDir);
|
|
76
|
-
assert.ok(existsSync(join(tempDir, '.claude', 'agents')), '.claude/agents/ must exist');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('installs .claude/rules/ directory', async () => {
|
|
80
|
-
await setupInfra(tempDir);
|
|
81
|
-
assert.ok(existsSync(join(tempDir, '.claude', 'rules')), '.claude/rules/ must exist');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test('copies framework files to .morph/framework/', async () => {
|
|
85
|
-
await setupInfra(tempDir);
|
|
86
|
-
assert.ok(existsSync(join(tempDir, '.morph', 'framework', 'agents.json')), 'agents.json must be copied');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('updates .gitignore with .morph/state.json entry', async () => {
|
|
90
|
-
await setupInfra(tempDir);
|
|
91
|
-
// gitignore update is best-effort, only check if it runs without error
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test('is idempotent — safe to run twice', async () => {
|
|
95
|
-
await setupInfra(tempDir);
|
|
96
|
-
await setupInfra(tempDir); // should not throw
|
|
97
|
-
assert.ok(existsSync(join(tempDir, '.morph', 'config', 'config.json')));
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**Step 2: Run tests to verify they fail**
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
cd "R:/Polymorphism Tech/repos/morph-spec-framework"
|
|
106
|
-
node --test test/scripts/setup-infra.test.js
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Expected: FAIL — `setup-infra.js` not found
|
|
110
|
-
|
|
111
|
-
**Step 3: Implement `src/scripts/setup-infra.js`**
|
|
112
|
-
|
|
113
|
-
Extract steps 1–10 from `src/commands/project/init.js` (lines 54–220 approximately). Key differences from init.js:
|
|
114
|
-
|
|
115
|
-
- Export a `setupInfra(targetPath)` async function (no Commander, no spinner output in test mode)
|
|
116
|
-
- Use `process.env.MORPH_QUIET` to suppress spinner in tests
|
|
117
|
-
- config.json: write `{ framework, frameworkVersion, project: { name } }` only — no stack/architecture
|
|
118
|
-
- context/README.md: write the same minimal placeholder as today
|
|
119
|
-
- Reuse ALL existing imports from init.js: `installSkills`, `installAgents`, `installDomainAgents`, `installClaudeHooks`, `installGlobalStatusline`, `copyDirectory`, `copyFile`, etc.
|
|
120
|
-
|
|
121
|
-
```js
|
|
122
|
-
// src/scripts/setup-infra.js
|
|
123
|
-
import { join } from 'path';
|
|
124
|
-
import { dirname } from 'path';
|
|
125
|
-
import { fileURLToPath } from 'url';
|
|
126
|
-
import fs from 'fs-extra';
|
|
127
|
-
import ora from 'ora';
|
|
128
|
-
import { logger } from '../utils/logger.js';
|
|
129
|
-
import {
|
|
130
|
-
copyDirectory, copyFile, pathExists,
|
|
131
|
-
writeJson, ensureDir, writeFile, updateGitignore
|
|
132
|
-
} from '../utils/file-copier.js';
|
|
133
|
-
import { saveProjectMorphVersion, getInstalledCLIVersion } from '../utils/version-checker.js';
|
|
134
|
-
import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-settings-manager.js';
|
|
135
|
-
import { installSkills } from '../utils/skills-installer.js';
|
|
136
|
-
import { installAgents, installDomainAgents } from '../utils/agents-installer.js';
|
|
137
|
-
|
|
138
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
139
|
-
const FRAMEWORK_DIR = join(__dirname, '..', '..', 'framework');
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Headless infrastructure setup — no prompts, no stack detection, no MCPs.
|
|
143
|
-
* Called by /morph-init skill and by morph-spec init (delegated).
|
|
144
|
-
* @param {string} targetPath - Project root
|
|
145
|
-
*/
|
|
146
|
-
export async function setupInfra(targetPath) {
|
|
147
|
-
const quiet = process.env.MORPH_QUIET === '1';
|
|
148
|
-
const spinner = quiet ? { text: '', start() {}, stop() {}, succeed() {}, warn() {} } : ora('Installing MORPH-SPEC infrastructure...').start();
|
|
149
|
-
|
|
150
|
-
const morphPath = join(targetPath, '.morph');
|
|
151
|
-
const configDir = join(morphPath, 'config');
|
|
152
|
-
const frameworkDestDir = join(morphPath, 'framework');
|
|
153
|
-
const contextDir = join(morphPath, 'context');
|
|
154
|
-
const featuresDir = join(morphPath, 'features');
|
|
155
|
-
const claudeDest = join(targetPath, '.claude');
|
|
156
|
-
const dirName = targetPath.split(/[\\/]/).pop();
|
|
157
|
-
|
|
158
|
-
// 1. Copy CLAUDE.md
|
|
159
|
-
const claudeMdSrc = join(FRAMEWORK_DIR, 'CLAUDE.md');
|
|
160
|
-
const claudeMdDest = join(targetPath, 'CLAUDE.md');
|
|
161
|
-
if (await pathExists(claudeMdSrc)) {
|
|
162
|
-
if (await pathExists(claudeMdDest)) {
|
|
163
|
-
const existing = await fs.readFile(claudeMdDest, 'utf8');
|
|
164
|
-
if (!existing.includes('MORPH-SPEC')) {
|
|
165
|
-
await copyFile(claudeMdDest, `${claudeMdDest}.backup`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
await copyFile(claudeMdSrc, claudeMdDest);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 2. Create .morph structure
|
|
172
|
-
for (const d of [configDir, frameworkDestDir, contextDir, featuresDir,
|
|
173
|
-
join(morphPath, 'checkpoints'), join(morphPath, 'memory'), join(morphPath, 'archive')]) {
|
|
174
|
-
await ensureDir(d);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 3. Write minimal config.json (NO stack/architecture — skill fills these)
|
|
178
|
-
const configPath = join(configDir, 'config.json');
|
|
179
|
-
if (!(await pathExists(configPath))) {
|
|
180
|
-
await writeJson(configPath, {
|
|
181
|
-
framework: 'global',
|
|
182
|
-
frameworkVersion: getInstalledCLIVersion(),
|
|
183
|
-
project: { name: dirName }
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 4. Write placeholder context/README.md
|
|
188
|
-
const contextReadme = join(contextDir, 'README.md');
|
|
189
|
-
if (!(await pathExists(contextReadme))) {
|
|
190
|
-
await writeFile(contextReadme, `# ${dirName} — Project Context\n\nRun /morph-init to generate project context.\n`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// 5. Copy framework templates
|
|
194
|
-
const templatesSrc = join(FRAMEWORK_DIR, 'templates');
|
|
195
|
-
if (await pathExists(templatesSrc)) {
|
|
196
|
-
await copyDirectory(templatesSrc, join(frameworkDestDir, 'templates'));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 6. Copy framework standards
|
|
200
|
-
const standardsSrc = join(FRAMEWORK_DIR, 'standards');
|
|
201
|
-
if (await pathExists(standardsSrc)) {
|
|
202
|
-
await copyDirectory(standardsSrc, join(frameworkDestDir, 'standards'));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 6c. Copy hooks
|
|
206
|
-
const hooksSrc = join(FRAMEWORK_DIR, 'hooks');
|
|
207
|
-
if (await pathExists(hooksSrc)) {
|
|
208
|
-
await copyDirectory(hooksSrc, join(frameworkDestDir, 'hooks'));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// 7. Copy agents.json
|
|
212
|
-
const agentsSrc = join(FRAMEWORK_DIR, 'agents.json');
|
|
213
|
-
if (await pathExists(agentsSrc)) {
|
|
214
|
-
await copyFile(agentsSrc, join(frameworkDestDir, 'agents.json'));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// 8. Copy commands
|
|
218
|
-
const commandsSrc = join(FRAMEWORK_DIR, 'commands');
|
|
219
|
-
if (await pathExists(commandsSrc)) {
|
|
220
|
-
await copyDirectory(commandsSrc, join(claudeDest, 'commands'));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 9a. Install rules
|
|
224
|
-
const rulesSrc = join(FRAMEWORK_DIR, 'rules');
|
|
225
|
-
if (await pathExists(rulesSrc)) {
|
|
226
|
-
await copyDirectory(rulesSrc, join(claudeDest, 'rules'));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 9b. Install skills, agents, domain agents, CLAUDE.md
|
|
230
|
-
await installSkills(targetPath);
|
|
231
|
-
await installAgents(targetPath, FRAMEWORK_DIR, { projectStack: null });
|
|
232
|
-
await installDomainAgents(targetPath, FRAMEWORK_DIR);
|
|
233
|
-
const runtimeClaudeMdSrc = join(FRAMEWORK_DIR, 'CLAUDE.md');
|
|
234
|
-
if (await pathExists(runtimeClaudeMdSrc)) {
|
|
235
|
-
await copyFile(runtimeClaudeMdSrc, join(claudeDest, 'CLAUDE.md'));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 9c. Statusline (global, non-critical)
|
|
239
|
-
try {
|
|
240
|
-
await installGlobalStatusline(join(FRAMEWORK_DIR, 'hooks', 'claude-code'));
|
|
241
|
-
} catch { /* non-critical */ }
|
|
242
|
-
|
|
243
|
-
// 9d. Hooks
|
|
244
|
-
await installClaudeHooks(targetPath);
|
|
245
|
-
|
|
246
|
-
// 10. Version info
|
|
247
|
-
await saveProjectMorphVersion(targetPath);
|
|
248
|
-
|
|
249
|
-
// 11. .gitignore
|
|
250
|
-
await updateGitignore(targetPath);
|
|
251
|
-
|
|
252
|
-
spinner.succeed('MORPH-SPEC infrastructure installed.');
|
|
253
|
-
return { targetPath, dirName };
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
**Step 4: Run tests**
|
|
258
|
-
|
|
259
|
-
```bash
|
|
260
|
-
node --test test/scripts/setup-infra.test.js
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Expected: all tests PASS
|
|
264
|
-
|
|
265
|
-
**Step 5: Create `src/commands/project/setup-infra-cmd.js` (thin CLI wrapper)**
|
|
266
|
-
|
|
267
|
-
```js
|
|
268
|
-
// src/commands/project/setup-infra-cmd.js
|
|
269
|
-
import { setupInfra } from '../../scripts/setup-infra.js';
|
|
270
|
-
import { logger } from '../../utils/logger.js';
|
|
271
|
-
|
|
272
|
-
export async function setupInfraCommand(options) {
|
|
273
|
-
const targetPath = options.path || process.cwd();
|
|
274
|
-
try {
|
|
275
|
-
await setupInfra(targetPath);
|
|
276
|
-
} catch (err) {
|
|
277
|
-
logger.error(`setup-infra failed: ${err.message}`);
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
**Step 6: Register in `bin/morph-spec.js`**
|
|
284
|
-
|
|
285
|
-
Add import at top (with other project commands):
|
|
286
|
-
```js
|
|
287
|
-
import { setupInfraCommand } from '../src/commands/project/setup-infra-cmd.js';
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Add command registration after the `init` block (line ~109):
|
|
291
|
-
```js
|
|
292
|
-
program
|
|
293
|
-
.command('setup-infra')
|
|
294
|
-
.description('Install MORPH-SPEC infrastructure (headless, no prompts). Called by /morph-init skill.')
|
|
295
|
-
.option('-p, --path <path>', 'Target path (default: current directory)')
|
|
296
|
-
.action(setupInfraCommand);
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
**Step 7: Verify CLI command works**
|
|
300
|
-
|
|
301
|
-
```bash
|
|
302
|
-
morph-spec setup-infra --help
|
|
303
|
-
```
|
|
304
|
-
Expected: shows description and `--path` option.
|
|
305
|
-
|
|
306
|
-
**Step 8: Commit**
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
git add src/scripts/setup-infra.js src/commands/project/setup-infra-cmd.js bin/morph-spec.js test/scripts/setup-infra.test.js
|
|
310
|
-
git commit -m "feat(cli): add setup-infra.js + morph-spec setup-infra command — headless infra extracted from init.js"
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
---
|
|
314
|
-
|
|
315
|
-
## Task 2: Create `src/scripts/install-plugin.js` + `morph-spec install-plugin` command
|
|
316
|
-
|
|
317
|
-
Downloads a plugin from `anthropics/claude-plugins-official` GitHub, registers it in `~/.claude/plugins/installed_plugins.json`, and exposes the logic as a CLI subcommand callable from any user project.
|
|
318
|
-
|
|
319
|
-
**Files:**
|
|
320
|
-
|
|
321
|
-
- Create: `src/scripts/install-plugin.js` — pure logic
|
|
322
|
-
- Create: `src/commands/project/install-plugin-cmd.js` — thin CLI wrapper
|
|
323
|
-
- Modify: `bin/morph-spec.js` — register `morph-spec install-plugin <name>`
|
|
324
|
-
- Create: `test/scripts/install-plugin.test.js`
|
|
325
|
-
|
|
326
|
-
**Step 1: Write failing tests**
|
|
327
|
-
|
|
328
|
-
```js
|
|
329
|
-
// test/scripts/install-plugin.test.js
|
|
330
|
-
import { test, describe } from 'node:test';
|
|
331
|
-
import assert from 'node:assert/strict';
|
|
332
|
-
import {
|
|
333
|
-
isPluginInstalled,
|
|
334
|
-
buildInstallEntry,
|
|
335
|
-
resolvePluginVersion
|
|
336
|
-
} from '../../src/scripts/install-plugin.js';
|
|
337
|
-
|
|
338
|
-
describe('install-plugin utils', () => {
|
|
339
|
-
test('isPluginInstalled returns true when plugin key exists', () => {
|
|
340
|
-
const plugins = {
|
|
341
|
-
'superpowers@claude-plugins-official': [{ scope: 'user', version: '4.3.1' }]
|
|
342
|
-
};
|
|
343
|
-
assert.strictEqual(isPluginInstalled(plugins, 'superpowers'), true);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test('isPluginInstalled returns false when plugin is absent', () => {
|
|
347
|
-
assert.strictEqual(isPluginInstalled({}, 'superpowers'), false);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test('buildInstallEntry returns valid entry shape', () => {
|
|
351
|
-
const entry = buildInstallEntry('superpowers', '/some/path', 'abc123', '4.3.1');
|
|
352
|
-
assert.strictEqual(entry.scope, 'user');
|
|
353
|
-
assert.strictEqual(entry.version, '4.3.1');
|
|
354
|
-
assert.strictEqual(entry.gitCommitSha, 'abc123');
|
|
355
|
-
assert.ok(entry.installedAt);
|
|
356
|
-
assert.ok(entry.lastUpdated);
|
|
357
|
-
assert.ok(entry.installPath.includes('superpowers'));
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test('resolvePluginVersion returns sha string for known plugin', async () => {
|
|
361
|
-
// Uses real GitHub API — skip in offline CI
|
|
362
|
-
if (process.env.CI) return;
|
|
363
|
-
const { sha } = await resolvePluginVersion('context7');
|
|
364
|
-
assert.ok(typeof sha === 'string' && sha.length > 0, 'sha must be non-empty string');
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
**Step 2: Run to verify failure**
|
|
370
|
-
|
|
371
|
-
```bash
|
|
372
|
-
node --test test/scripts/install-plugin.test.js
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
Expected: FAIL — module not found
|
|
376
|
-
|
|
377
|
-
**Step 3: Implement `src/scripts/install-plugin.js`**
|
|
378
|
-
|
|
379
|
-
```js
|
|
380
|
-
// src/scripts/install-plugin.js
|
|
381
|
-
import { join } from 'path';
|
|
382
|
-
import { homedir } from 'os';
|
|
383
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
384
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
385
|
-
import https from 'node:https';
|
|
386
|
-
|
|
387
|
-
const PLUGINS_DIR = join(homedir(), '.claude', 'plugins');
|
|
388
|
-
const CACHE_DIR = join(PLUGINS_DIR, 'cache', 'claude-plugins-official');
|
|
389
|
-
const INSTALLED_FILE = join(PLUGINS_DIR, 'installed_plugins.json');
|
|
390
|
-
const GH_REPO = 'anthropics/claude-plugins-official';
|
|
391
|
-
const GH_API = 'https://api.github.com';
|
|
392
|
-
|
|
393
|
-
/** Fetch JSON from GitHub API */
|
|
394
|
-
async function fetchGitHub(path) {
|
|
395
|
-
return new Promise((resolve, reject) => {
|
|
396
|
-
const opts = {
|
|
397
|
-
hostname: 'api.github.com',
|
|
398
|
-
path,
|
|
399
|
-
headers: { 'User-Agent': 'morph-spec', 'Accept': 'application/vnd.github.v3+json' }
|
|
400
|
-
};
|
|
401
|
-
https.get(opts, (res) => {
|
|
402
|
-
let data = '';
|
|
403
|
-
res.on('data', c => data += c);
|
|
404
|
-
res.on('end', () => {
|
|
405
|
-
try { resolve(JSON.parse(data)); }
|
|
406
|
-
catch (e) { reject(new Error(`GitHub API parse error: ${data.slice(0, 200)}`)); }
|
|
407
|
-
});
|
|
408
|
-
}).on('error', reject);
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/** Download raw file from GitHub */
|
|
413
|
-
async function downloadFile(downloadUrl) {
|
|
414
|
-
return new Promise((resolve, reject) => {
|
|
415
|
-
https.get(downloadUrl, { headers: { 'User-Agent': 'morph-spec' } }, (res) => {
|
|
416
|
-
// Follow redirects
|
|
417
|
-
if (res.statusCode === 302 || res.statusCode === 301) {
|
|
418
|
-
const url = new URL(res.headers.location);
|
|
419
|
-
https.get({ hostname: url.hostname, path: url.pathname + url.search,
|
|
420
|
-
headers: { 'User-Agent': 'morph-spec' } }, (res2) => {
|
|
421
|
-
const chunks = [];
|
|
422
|
-
res2.on('data', c => chunks.push(c));
|
|
423
|
-
res2.on('end', () => resolve(Buffer.concat(chunks)));
|
|
424
|
-
}).on('error', reject);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
const chunks = [];
|
|
428
|
-
res.on('data', c => chunks.push(c));
|
|
429
|
-
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
430
|
-
}).on('error', reject);
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/** Read installed_plugins.json, return parsed object */
|
|
435
|
-
function readInstalledPlugins() {
|
|
436
|
-
if (!existsSync(INSTALLED_FILE)) return { version: 2, plugins: {} };
|
|
437
|
-
try { return JSON.parse(readFileSync(INSTALLED_FILE, 'utf8')); }
|
|
438
|
-
catch { return { version: 2, plugins: {} }; }
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** Check if plugin is already installed */
|
|
442
|
-
export function isPluginInstalled(plugins, name) {
|
|
443
|
-
return `${name}@claude-plugins-official` in plugins;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** Resolve plugin version + commit SHA from GitHub */
|
|
447
|
-
export async function resolvePluginVersion(pluginName) {
|
|
448
|
-
const commits = await fetchGitHub(`/repos/${GH_REPO}/commits?path=${pluginName}&per_page=1`);
|
|
449
|
-
if (!Array.isArray(commits) || commits.length === 0) {
|
|
450
|
-
throw new Error(`Could not resolve version for plugin: ${pluginName}`);
|
|
451
|
-
}
|
|
452
|
-
const sha = commits[0].sha;
|
|
453
|
-
const shortSha = sha.slice(0, 12);
|
|
454
|
-
return { sha, shortSha };
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/** Build installed_plugins.json entry */
|
|
458
|
-
export function buildInstallEntry(pluginName, installPath, gitCommitSha, version) {
|
|
459
|
-
const now = new Date().toISOString();
|
|
460
|
-
return { scope: 'user', installPath, version, installedAt: now, lastUpdated: now, gitCommitSha };
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/** Download plugin directory recursively from GitHub */
|
|
464
|
-
async function downloadPluginFiles(pluginName, sha, destDir) {
|
|
465
|
-
const tree = await fetchGitHub(`/repos/${GH_REPO}/git/trees/${sha}?recursive=1`);
|
|
466
|
-
if (!tree.tree) throw new Error('GitHub tree API returned unexpected shape');
|
|
467
|
-
|
|
468
|
-
const pluginFiles = tree.tree.filter(
|
|
469
|
-
f => f.path.startsWith(`${pluginName}/`) && f.type === 'blob'
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
if (pluginFiles.length === 0) {
|
|
473
|
-
throw new Error(`Plugin "${pluginName}" not found in repository`);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
for (const file of pluginFiles) {
|
|
477
|
-
const relativePath = file.path.slice(pluginName.length + 1); // strip "pluginName/"
|
|
478
|
-
const destPath = join(destDir, relativePath);
|
|
479
|
-
const destParent = join(destPath, '..');
|
|
480
|
-
await mkdir(destParent, { recursive: true });
|
|
481
|
-
|
|
482
|
-
const content = await downloadFile(
|
|
483
|
-
`https://raw.githubusercontent.com/${GH_REPO}/${sha}/${file.path}`
|
|
484
|
-
);
|
|
485
|
-
await writeFile(destPath, content);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Install a Claude Code plugin from GitHub.
|
|
491
|
-
* @param {string} pluginName - e.g. "superpowers"
|
|
492
|
-
* @returns {{ installed: boolean, version: string, alreadyInstalled: boolean }}
|
|
493
|
-
*/
|
|
494
|
-
export async function installPlugin(pluginName) {
|
|
495
|
-
const data = readInstalledPlugins();
|
|
496
|
-
|
|
497
|
-
if (isPluginInstalled(data.plugins, pluginName)) {
|
|
498
|
-
return { installed: true, alreadyInstalled: true, version: 'existing' };
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const { sha, shortSha } = await resolvePluginVersion(pluginName);
|
|
502
|
-
const installPath = join(CACHE_DIR, pluginName, shortSha);
|
|
503
|
-
|
|
504
|
-
await mkdir(installPath, { recursive: true });
|
|
505
|
-
await downloadPluginFiles(pluginName, sha, installPath);
|
|
506
|
-
|
|
507
|
-
const entry = buildInstallEntry(pluginName, installPath, sha, shortSha);
|
|
508
|
-
data.plugins[`${pluginName}@claude-plugins-official`] = [entry];
|
|
509
|
-
|
|
510
|
-
mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
511
|
-
writeFileSync(INSTALLED_FILE, JSON.stringify(data, null, 2));
|
|
512
|
-
|
|
513
|
-
return { installed: true, alreadyInstalled: false, version: shortSha };
|
|
514
|
-
}
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
**Step 4: Run tests**
|
|
518
|
-
|
|
519
|
-
```bash
|
|
520
|
-
node --test test/scripts/install-plugin.test.js
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
Expected: unit tests PASS (resolvePluginVersion skipped in CI)
|
|
524
|
-
|
|
525
|
-
**Step 5: Create `src/commands/project/install-plugin-cmd.js` (thin CLI wrapper)**
|
|
526
|
-
|
|
527
|
-
```js
|
|
528
|
-
// src/commands/project/install-plugin-cmd.js
|
|
529
|
-
import { installPlugin } from '../../scripts/install-plugin.js';
|
|
530
|
-
import { logger } from '../../utils/logger.js';
|
|
531
|
-
|
|
532
|
-
export async function installPluginCommand(pluginName, _options) {
|
|
533
|
-
if (!pluginName) {
|
|
534
|
-
logger.error('Plugin name required. Usage: morph-spec install-plugin <name>');
|
|
535
|
-
logger.dim(' Available: superpowers, context7');
|
|
536
|
-
process.exit(1);
|
|
537
|
-
}
|
|
538
|
-
try {
|
|
539
|
-
const result = await installPlugin(pluginName);
|
|
540
|
-
if (result.alreadyInstalled) {
|
|
541
|
-
logger.dim(` ✓ ${pluginName} already installed (${result.version})`);
|
|
542
|
-
} else {
|
|
543
|
-
logger.success(` ✓ ${pluginName} installed (${result.version})`);
|
|
544
|
-
logger.dim(' Restart Claude Code to activate.');
|
|
545
|
-
}
|
|
546
|
-
} catch (err) {
|
|
547
|
-
logger.error(`Failed to install plugin "${pluginName}": ${err.message}`);
|
|
548
|
-
logger.blank();
|
|
549
|
-
logger.warn('Manual installation:');
|
|
550
|
-
logger.dim(' 1. Claude Code → Settings (Cmd/Ctrl+,) → Extensions');
|
|
551
|
-
logger.dim(` 2. Browse → search "${pluginName}" → Install`);
|
|
552
|
-
logger.dim(' 3. Restart Claude Code');
|
|
553
|
-
process.exit(1);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
**Step 6: Register in `bin/morph-spec.js`**
|
|
559
|
-
|
|
560
|
-
Add import at top (with other project commands):
|
|
561
|
-
```js
|
|
562
|
-
import { installPluginCommand } from '../src/commands/project/install-plugin-cmd.js';
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Add command registration after `setup-infra` block:
|
|
566
|
-
```js
|
|
567
|
-
program
|
|
568
|
-
.command('install-plugin <name>')
|
|
569
|
-
.description('Install a Claude Code plugin (superpowers, context7). Called by /morph-init skill.')
|
|
570
|
-
.action(installPluginCommand);
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
**Step 7: Verify CLI command works**
|
|
574
|
-
|
|
575
|
-
```bash
|
|
576
|
-
morph-spec install-plugin --help
|
|
577
|
-
morph-spec install-plugin superpowers
|
|
578
|
-
```
|
|
579
|
-
Expected: help shows `<name>` argument; install succeeds or prints manual guide on failure.
|
|
580
|
-
|
|
581
|
-
**Step 8: Commit**
|
|
582
|
-
|
|
583
|
-
```bash
|
|
584
|
-
git add src/scripts/install-plugin.js src/commands/project/install-plugin-cmd.js bin/morph-spec.js test/scripts/install-plugin.test.js
|
|
585
|
-
git commit -m "feat(cli): add install-plugin.js + morph-spec install-plugin command — auto-install CC plugins from GitHub"
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
---
|
|
589
|
-
|
|
590
|
-
## Task 3: Create `src/scripts/global-install.js` + package.json postinstall
|
|
591
|
-
|
|
592
|
-
Copies `morph-init/SKILL.md` to `~/.claude/skills/morph-init/SKILL.md` on npm install.
|
|
593
|
-
|
|
594
|
-
**Files:**
|
|
595
|
-
|
|
596
|
-
- Create: `src/scripts/global-install.js`
|
|
597
|
-
- Create: `test/scripts/global-install.test.js`
|
|
598
|
-
- Modify: `package.json`
|
|
599
|
-
|
|
600
|
-
**Step 1: Write failing tests**
|
|
601
|
-
|
|
602
|
-
```js
|
|
603
|
-
// test/scripts/global-install.test.js
|
|
604
|
-
import { test, describe, beforeEach, afterEach } from 'node:test';
|
|
605
|
-
import assert from 'node:assert/strict';
|
|
606
|
-
import { existsSync } from 'fs';
|
|
607
|
-
import { join } from 'path';
|
|
608
|
-
import { createTempDir, cleanupTempDir } from '../helpers/test-utils.js';
|
|
609
|
-
import { installGlobalSkill } from '../../src/scripts/global-install.js';
|
|
610
|
-
|
|
611
|
-
describe('global-install', () => {
|
|
612
|
-
let tempGlobalDir;
|
|
613
|
-
beforeEach(() => { tempGlobalDir = createTempDir(); });
|
|
614
|
-
afterEach(() => { cleanupTempDir(tempGlobalDir); });
|
|
615
|
-
|
|
616
|
-
test('copies morph-init/SKILL.md to target global skills dir', async () => {
|
|
617
|
-
await installGlobalSkill(tempGlobalDir);
|
|
618
|
-
const skillPath = join(tempGlobalDir, 'skills', 'morph-init', 'SKILL.md');
|
|
619
|
-
assert.ok(existsSync(skillPath), 'morph-init/SKILL.md must be copied to global skills dir');
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
test('is idempotent — safe to run twice', async () => {
|
|
623
|
-
await installGlobalSkill(tempGlobalDir);
|
|
624
|
-
await installGlobalSkill(tempGlobalDir);
|
|
625
|
-
assert.ok(existsSync(join(tempGlobalDir, 'skills', 'morph-init', 'SKILL.md')));
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
**Step 2: Run to verify failure**
|
|
631
|
-
|
|
632
|
-
```bash
|
|
633
|
-
node --test test/scripts/global-install.test.js
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
Expected: FAIL — module not found (and `morph-init/SKILL.md` doesn't exist yet — that's Task 4)
|
|
637
|
-
|
|
638
|
-
**Step 3: Implement `src/scripts/global-install.js`**
|
|
639
|
-
|
|
640
|
-
```js
|
|
641
|
-
// src/scripts/global-install.js
|
|
642
|
-
import { join, dirname } from 'path';
|
|
643
|
-
import { fileURLToPath } from 'url';
|
|
644
|
-
import { homedir } from 'os';
|
|
645
|
-
import { mkdir, copyFile, writeFile } from 'fs/promises';
|
|
646
|
-
import { existsSync } from 'fs';
|
|
647
|
-
|
|
648
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
649
|
-
const FRAMEWORK_DIR = join(__dirname, '..', '..', 'framework');
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* Install morph-init skill to a target global Claude dir.
|
|
653
|
-
* @param {string} claudeDir - defaults to ~/.claude/
|
|
654
|
-
*/
|
|
655
|
-
export async function installGlobalSkill(claudeDir = join(homedir(), '.claude')) {
|
|
656
|
-
const src = join(FRAMEWORK_DIR, 'skills', 'level-0-meta', 'morph-init', 'SKILL.md');
|
|
657
|
-
const destDir = join(claudeDir, 'skills', 'morph-init');
|
|
658
|
-
const dest = join(destDir, 'SKILL.md');
|
|
659
|
-
|
|
660
|
-
if (!existsSync(src)) {
|
|
661
|
-
console.warn(`morph-init skill not found at ${src} — skipping global install`);
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
await mkdir(destDir, { recursive: true });
|
|
666
|
-
await copyFile(src, dest);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Entrypoint when run as postinstall script
|
|
670
|
-
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
671
|
-
installGlobalSkill()
|
|
672
|
-
.then(() => console.log('✓ morph-init skill installed to ~/.claude/skills/'))
|
|
673
|
-
.catch(e => console.warn('Could not install morph-init skill globally:', e.message));
|
|
674
|
-
}
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
**Step 4: Add postinstall to `package.json`**
|
|
678
|
-
|
|
679
|
-
In `package.json`, add to `"scripts"`:
|
|
680
|
-
|
|
681
|
-
```json
|
|
682
|
-
"postinstall": "node src/scripts/global-install.js"
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
**Step 5: Run tests** (after Task 4 creates the SKILL.md)
|
|
686
|
-
|
|
687
|
-
```bash
|
|
688
|
-
node --test test/scripts/global-install.test.js
|
|
689
|
-
```
|
|
690
|
-
|
|
691
|
-
Expected: PASS after Task 4
|
|
692
|
-
|
|
693
|
-
**Step 6: Commit**
|
|
694
|
-
|
|
695
|
-
```bash
|
|
696
|
-
git add src/scripts/global-install.js test/scripts/global-install.test.js package.json
|
|
697
|
-
git commit -m "feat(scripts): add global-install.js — postinstall copies morph-init skill to ~/.claude/skills/"
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
---
|
|
701
|
-
|
|
702
|
-
## Task 4: `framework/skills/level-0-meta/morph-init/SKILL.md`
|
|
703
|
-
|
|
704
|
-
The main user-facing skill. Already created — verify it uses the correct CLI commands.
|
|
705
|
-
|
|
706
|
-
**Key constraint:** All `morph-spec` CLI calls in the skill work in any user project because `morph-spec` is globally installed. Do NOT reference `node src/scripts/...` or any path relative to the framework repo.
|
|
707
|
-
|
|
708
|
-
**Files:**
|
|
709
|
-
|
|
710
|
-
- Verify: `framework/skills/level-0-meta/morph-init/SKILL.md` — already exists
|
|
711
|
-
|
|
712
|
-
**Step 1: Verify correct CLI commands in SKILL.md**
|
|
713
|
-
|
|
714
|
-
The skill must use:
|
|
715
|
-
```bash
|
|
716
|
-
morph-spec install-plugin superpowers # ✓ CLI subcommand — works anywhere
|
|
717
|
-
morph-spec install-plugin context7 # ✓ CLI subcommand — works anywhere
|
|
718
|
-
morph-spec setup-infra # ✓ CLI subcommand — works anywhere
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
Not:
|
|
722
|
-
```bash
|
|
723
|
-
node {MORPH_ROOT}/src/scripts/... # ✗ path only exists in framework repo
|
|
724
|
-
npx morph-spec ... # ✗ may download wrong version
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
Read the file and confirm:
|
|
728
|
-
```bash
|
|
729
|
-
grep -n "morph-spec\|node.*scripts\|MORPH_ROOT" framework/skills/level-0-meta/morph-init/SKILL.md
|
|
730
|
-
```
|
|
731
|
-
Expected: only `morph-spec install-plugin` and `morph-spec setup-infra` patterns.
|
|
732
|
-
|
|
733
|
-
**Step 2 (previously Step 1): The skill file content is**
|
|
734
|
-
|
|
735
|
-
```markdown
|
|
736
|
-
---
|
|
737
|
-
name: morph-init
|
|
738
|
-
description: >
|
|
739
|
-
LLM-powered project initialization. Installs morph-spec infrastructure if
|
|
740
|
-
needed, auto-installs required plugins (superpowers, context7), analyzes any
|
|
741
|
-
project structure intelligently, generates rich context/README.md and
|
|
742
|
-
config.json, and configures MCPs. Use once per project after installing
|
|
743
|
-
@polymorphism-tech/morph-spec. Re-run with /morph-init refresh to update
|
|
744
|
-
context as the project evolves.
|
|
745
|
-
argument-hint: "[refresh]"
|
|
746
|
-
user-invocable: true
|
|
747
|
-
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
|
|
748
|
-
---
|
|
749
|
-
|
|
750
|
-
# morph-init — LLM-Powered Project Initialization
|
|
751
|
-
|
|
752
|
-
> Run once after `npm install @polymorphism-tech/morph-spec`.
|
|
753
|
-
> Re-run as `/morph-init refresh` when your project evolves.
|
|
754
|
-
|
|
755
|
-
---
|
|
756
|
-
|
|
757
|
-
## Step 0 — Required Plugins
|
|
758
|
-
|
|
759
|
-
Check for required Claude Code plugins: **superpowers** and **context7**.
|
|
760
|
-
|
|
761
|
-
```bash
|
|
762
|
-
# Detect morph-spec package root
|
|
763
|
-
node -e "const r=require.resolve('@polymorphism-tech/morph-spec/package.json'); console.log(require('path').dirname(r))"
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
Then check `~/.claude/plugins/installed_plugins.json` for:
|
|
767
|
-
|
|
768
|
-
- `superpowers@claude-plugins-official`
|
|
769
|
-
- `context7@claude-plugins-official`
|
|
770
|
-
|
|
771
|
-
For each missing plugin, run:
|
|
772
|
-
|
|
773
|
-
```bash
|
|
774
|
-
node {MORPH_ROOT}/src/scripts/install-plugin.js {plugin-name}
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
**If auto-install succeeds:** `✓ {plugin} installed.`
|
|
778
|
-
|
|
779
|
-
**If auto-install fails:** Show this and STOP:
|
|
780
|
-
|
|
781
|
-
```
|
|
782
|
-
┌─────────────────────────────────────────────────────────────┐
|
|
783
|
-
│ Plugin {plugin} requires manual installation: │
|
|
784
|
-
│ │
|
|
785
|
-
│ 1. Claude Code → Settings (Cmd/Ctrl+,) → Extensions │
|
|
786
|
-
│ 2. Browse → search "{plugin}" → Install │
|
|
787
|
-
│ 3. Restart Claude Code │
|
|
788
|
-
│ 4. Re-run /morph-init │
|
|
789
|
-
└─────────────────────────────────────────────────────────────┘
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
Do not continue if either plugin is missing or failed to install.
|
|
793
|
-
|
|
794
|
-
---
|
|
795
|
-
|
|
796
|
-
## Step 1 — Infrastructure
|
|
797
|
-
|
|
798
|
-
Check if `.morph/` exists in the current directory.
|
|
799
|
-
|
|
800
|
-
**If MISSING:**
|
|
801
|
-
|
|
802
|
-
```bash
|
|
803
|
-
npx morph-spec setup-infra
|
|
804
|
-
```
|
|
805
|
-
|
|
806
|
-
Output: `✓ Infrastructure installed`
|
|
807
|
-
|
|
808
|
-
**If EXISTS and argument is "refresh":** Continue.
|
|
809
|
-
|
|
810
|
-
**If EXISTS and no argument:**
|
|
811
|
-
Ask: *"MORPH already initialized. Refresh context and config? (y/n)"*
|
|
812
|
-
If `n` → STOP.
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
## Step 2 — Analyze Project
|
|
817
|
-
|
|
818
|
-
Gather evidence to build a stack map. Run these searches:
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
| Tool | What to look for |
|
|
822
|
-
| --------------------------------- | ---------------------------------------------------- |
|
|
823
|
-
| `Glob **/package.json` | Detect next, supabase, clerk, stripe, shadcn in deps |
|
|
824
|
-
| `Glob **/*.csproj` | .NET projects at any depth |
|
|
825
|
-
| `Glob **/next.config.{js,ts,mjs}` | Next.js in any subdirectory |
|
|
826
|
-
| `Glob **/components.json` | shadcn/ui |
|
|
827
|
-
| `Glob **/docker-compose*.yml` | Container services |
|
|
828
|
-
| `Glob database/migrations/`** | Supabase local dev |
|
|
829
|
-
| `Glob supabase/**` | Supabase project config |
|
|
830
|
-
| `Glob **/*.razor` | Blazor |
|
|
831
|
-
| `Read .env.example` | Env vars reveal integrations |
|
|
832
|
-
| `Read README.md` | Existing project description |
|
|
833
|
-
| `Grep @supabase/supabase-js` | Supabase dependency |
|
|
834
|
-
| `Grep clerk|auth0|nextauth` | Auth provider |
|
|
835
|
-
| `Grep stripe|asaas` | Payment provider |
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
Read each found `package.json` to extract dependency names.
|
|
839
|
-
|
|
840
|
-
Build an **evidence map**:
|
|
841
|
-
|
|
842
|
-
```
|
|
843
|
-
stack: [detected stacks]
|
|
844
|
-
integrations: [detected services]
|
|
845
|
-
uiLibrary: [shadcn | fluent-ui | mudblazor | tailwind | null]
|
|
846
|
-
monorepo: [true | false]
|
|
847
|
-
frontendPath: [path if monorepo]
|
|
848
|
-
backendPath: [path if monorepo]
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
---
|
|
852
|
-
|
|
853
|
-
## Step 3 — Ask Targeted Questions
|
|
854
|
-
|
|
855
|
-
**Rule: only ask what cannot be inferred with ≥90% confidence from files.**
|
|
856
|
-
|
|
857
|
-
Ask at most 3 questions, only those that apply:
|
|
858
|
-
|
|
859
|
-
1. **(Always)** "Em uma frase: qual é o objetivo principal do **[ProjectName]**?" - use askUser tool and suggest 3 + one to user input
|
|
860
|
-
2. **(If monorepo detected)** "Confirmo: frontend em `{frontendPath}`, backend em `{backendPath}`. Correto?"
|
|
861
|
-
— If no: "Como está organizado? Descreva brevemente."
|
|
862
|
-
3. **(If Supabase detected)** "Supabase Cloud (managed) ou self-hosted?"
|
|
863
|
-
|
|
864
|
-
Do **not** ask about technology confirmed by file evidence.
|
|
865
|
-
|
|
866
|
-
---
|
|
867
|
-
|
|
868
|
-
## Step 4 — Generate `context/README.md`
|
|
869
|
-
|
|
870
|
-
Write `.morph/context/README.md` using the evidence map and answers:
|
|
871
|
-
|
|
872
|
-
```markdown
|
|
873
|
-
# [ProjectName] — Project Context
|
|
874
|
-
|
|
875
|
-
## Overview
|
|
876
|
-
[Answer from question 1]
|
|
877
|
-
|
|
878
|
-
## Tech Stack
|
|
879
|
-
| Layer | Technology | Location |
|
|
880
|
-
|------------|-----------|----------|
|
|
881
|
-
[One row per detected layer]
|
|
882
|
-
|
|
883
|
-
## Architecture
|
|
884
|
-
[monorepo / single-stack / etc — describe structure and responsibilities]
|
|
885
|
-
|
|
886
|
-
## Key Integrations
|
|
887
|
-
[service → purpose — detected via: evidence]
|
|
888
|
-
|
|
889
|
-
## Agent Notes
|
|
890
|
-
[Patterns detected from eslint/tsconfig/.editorconfig, important paths,
|
|
891
|
-
conventions relevant to MORPH agents working on this project]
|
|
892
|
-
```
|
|
893
|
-
|
|
894
|
-
---
|
|
895
|
-
|
|
896
|
-
## Step 5 — Update `config.json`
|
|
897
|
-
|
|
898
|
-
Read `.morph/config/config.json` and update `project` section:
|
|
899
|
-
|
|
900
|
-
```json
|
|
901
|
-
{
|
|
902
|
-
"framework": "global",
|
|
903
|
-
"frameworkVersion": "[current version]",
|
|
904
|
-
"project": {
|
|
905
|
-
"name": "[ProjectName]",
|
|
906
|
-
"description": "[answer from question 1]",
|
|
907
|
-
"stack": "[detected stacks joined with +]",
|
|
908
|
-
"architecture": "[monorepo | single-stack | detected pattern]",
|
|
909
|
-
"uiLibrary": "[detected or null]",
|
|
910
|
-
"integrations": ["[list of detected integrations]"],
|
|
911
|
-
"frontendPath": "[if monorepo]",
|
|
912
|
-
"backendPath": "[if monorepo]"
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
---
|
|
918
|
-
|
|
919
|
-
## Step 6 — Configure MCPs
|
|
920
|
-
|
|
921
|
-
For each detected integration with an available MCP:
|
|
922
|
-
|
|
923
|
-
**Supabase:**
|
|
924
|
-
|
|
925
|
-
> "Configure Supabase MCP now? I'll need `SUPABASE_URL` and `SERVICE_ROLE_KEY`."
|
|
926
|
-
|
|
927
|
-
- YES: collect credentials → add to `.claude/settings.local.json` under `mcpServers`
|
|
928
|
-
- NO: show snippet + `morph-spec mcp setup supabase`
|
|
929
|
-
|
|
930
|
-
**GitHub:**
|
|
931
|
-
|
|
932
|
-
> "Configure GitHub MCP? I'll need a `GITHUB_PERSONAL_ACCESS_TOKEN`."
|
|
933
|
-
|
|
934
|
-
- YES/NO: same pattern
|
|
935
|
-
|
|
936
|
-
Only ask about Figma, Docker, Azure if explicitly detected in `.env.example`.
|
|
937
|
-
|
|
938
|
-
---
|
|
939
|
-
|
|
940
|
-
## Step 7 — Final Output
|
|
941
|
-
|
|
942
|
-
Print summary:
|
|
943
|
-
|
|
944
|
-
```
|
|
945
|
-
✓ Plugins: superpowers ✓ context7 ✓
|
|
946
|
-
✓ Infrastructure: .morph/ structure, hooks, agents, rules, skills
|
|
947
|
-
✓ context/README.md generated
|
|
948
|
-
✓ config.json updated
|
|
949
|
-
Stack: [stack] | Architecture: [architecture]
|
|
950
|
-
Integrations: [integrations]
|
|
951
|
-
✓ MCPs: [configured]
|
|
952
|
-
|
|
953
|
-
[If any plugins were newly installed:]
|
|
954
|
-
○ Restart Claude Code to activate newly installed plugins.
|
|
955
|
-
|
|
956
|
-
Next: /morph-proposal [feature-name]
|
|
957
|
-
```
|
|
958
|
-
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
**Step 2: Verify file is valid**
|
|
962
|
-
|
|
963
|
-
```bash
|
|
964
|
-
# Verify YAML frontmatter parses correctly
|
|
965
|
-
node -e "
|
|
966
|
-
const fs = require('fs');
|
|
967
|
-
const content = fs.readFileSync('framework/skills/level-0-meta/morph-init/SKILL.md', 'utf8');
|
|
968
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
969
|
-
console.log(match ? 'YAML frontmatter present ✓' : 'MISSING frontmatter ✗');
|
|
970
|
-
console.log('File size:', content.length, 'bytes');
|
|
971
|
-
"
|
|
972
|
-
```
|
|
973
|
-
|
|
974
|
-
**Step 3: Update `skills-installer.js` to include morph-init**
|
|
975
|
-
|
|
976
|
-
In `src/utils/skills-installer.js`, verify that `morph-init` is picked up from `level-0-meta/` — it should be included automatically if the installer scans all subdirectories. Confirm with:
|
|
977
|
-
|
|
978
|
-
```bash
|
|
979
|
-
node -e "
|
|
980
|
-
import { installSkills } from './src/utils/skills-installer.js';
|
|
981
|
-
" 2>/dev/null || node --input-type=module <<'EOF'
|
|
982
|
-
import { createRequire } from 'module';
|
|
983
|
-
import { join, dirname } from 'path';
|
|
984
|
-
import { fileURLToPath } from 'url';
|
|
985
|
-
// Just verify the SKILL.md exists at the right path
|
|
986
|
-
import { existsSync } from 'fs';
|
|
987
|
-
const p = 'framework/skills/level-0-meta/morph-init/SKILL.md';
|
|
988
|
-
console.log(existsSync(p) ? `✓ ${p} exists` : `✗ ${p} MISSING`);
|
|
989
|
-
EOF
|
|
990
|
-
```
|
|
991
|
-
|
|
992
|
-
**Step 4: Run global-install tests (from Task 3)**
|
|
993
|
-
|
|
994
|
-
```bash
|
|
995
|
-
node --test test/scripts/global-install.test.js
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
Expected: PASS now that SKILL.md exists
|
|
999
|
-
|
|
1000
|
-
**Step 5: Commit**
|
|
1001
|
-
|
|
1002
|
-
```bash
|
|
1003
|
-
git add framework/skills/level-0-meta/morph-init/SKILL.md
|
|
1004
|
-
git commit -m "feat(skills): add morph-init skill — LLM-powered project initialization entry point"
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
---
|
|
1008
|
-
|
|
1009
|
-
## Task 5: Refactor `init.js` to delegate to `setup-infra.js`
|
|
1010
|
-
|
|
1011
|
-
`init.js` keeps its interactive flow but delegates infrastructure work to `setupInfra()`. Remove duplicate code.
|
|
1012
|
-
|
|
1013
|
-
**Files:**
|
|
1014
|
-
|
|
1015
|
-
- Modify: `src/commands/project/init.js`
|
|
1016
|
-
- Review: `test/commands/init.test.js` (should still pass)
|
|
1017
|
-
|
|
1018
|
-
**Step 1: Run existing init tests to establish baseline**
|
|
1019
|
-
|
|
1020
|
-
```bash
|
|
1021
|
-
node --test test/commands/init.test.js
|
|
1022
|
-
```
|
|
1023
|
-
|
|
1024
|
-
Note how many tests pass. Target: same count after refactor.
|
|
1025
|
-
|
|
1026
|
-
**Step 2: Refactor `init.js`**
|
|
1027
|
-
|
|
1028
|
-
Replace steps 1–10 in `init.js` (lines 54–220 approximately) with a single call:
|
|
1029
|
-
|
|
1030
|
-
```js
|
|
1031
|
-
import { setupInfra } from '../../scripts/setup-infra.js';
|
|
1032
|
-
|
|
1033
|
-
// Replace steps 1-10 with:
|
|
1034
|
-
spinner.stop();
|
|
1035
|
-
await setupInfra(targetPath);
|
|
1036
|
-
spinner.start('Continuing...');
|
|
1037
|
-
|
|
1038
|
-
// Then keep: steps 11b (Claude config detection), 11c (stack detection → update config),
|
|
1039
|
-
// 11d (MCP setup), final output
|
|
1040
|
-
```
|
|
1041
|
-
|
|
1042
|
-
The stack detection block (step 11c) still runs — it now UPDATES the config.json that `setupInfra` created. The key difference: `setupInfra` writes a minimal config first, then `init.js` enriches it.
|
|
1043
|
-
|
|
1044
|
-
**Step 3: Run init tests**
|
|
1045
|
-
|
|
1046
|
-
```bash
|
|
1047
|
-
node --test test/commands/init.test.js
|
|
1048
|
-
```
|
|
1049
|
-
|
|
1050
|
-
Expected: same pass count as baseline
|
|
1051
|
-
|
|
1052
|
-
**Step 4: Fix `structure-detector.js` — Next.js subdirectory patterns**
|
|
1053
|
-
|
|
1054
|
-
In `src/lib/detectors/structure-detector.js`, change the `nextjs` patterns in `detectStack()`:
|
|
1055
|
-
|
|
1056
|
-
```js
|
|
1057
|
-
// BEFORE (line 60-65):
|
|
1058
|
-
nextjs: [
|
|
1059
|
-
'next.config.js',
|
|
1060
|
-
'next.config.mjs',
|
|
1061
|
-
'next.config.ts',
|
|
1062
|
-
'pages/**/*.tsx',
|
|
1063
|
-
'app/**/*.tsx'
|
|
1064
|
-
],
|
|
1065
|
-
|
|
1066
|
-
// AFTER:
|
|
1067
|
-
nextjs: [
|
|
1068
|
-
'**/next.config.js',
|
|
1069
|
-
'**/next.config.mjs',
|
|
1070
|
-
'**/next.config.ts',
|
|
1071
|
-
'pages/**/*.tsx',
|
|
1072
|
-
'app/**/*.tsx',
|
|
1073
|
-
'**/pages/**/*.tsx',
|
|
1074
|
-
'**/app/**/*.tsx'
|
|
1075
|
-
],
|
|
1076
|
-
```
|
|
1077
|
-
|
|
1078
|
-
**Step 5: Verify ProspectPRO detection works**
|
|
1079
|
-
|
|
1080
|
-
```bash
|
|
1081
|
-
node -e "
|
|
1082
|
-
import { detectStructure } from './src/lib/detectors/structure-detector.js';
|
|
1083
|
-
" 2>/dev/null || node --input-type=module <<'EOF'
|
|
1084
|
-
import { detectStructure } from './src/lib/detectors/structure-detector.js';
|
|
1085
|
-
const result = await detectStructure('R:/Polymorphism Tech/repos/ProspectPRO');
|
|
1086
|
-
console.log('Stack:', result.stack); // expect: nextjs
|
|
1087
|
-
console.log('Architecture:', result.architecture);
|
|
1088
|
-
EOF
|
|
1089
|
-
```
|
|
1090
|
-
|
|
1091
|
-
Expected: stack = `nextjs` (no longer `dotnet`)
|
|
1092
|
-
|
|
1093
|
-
**Step 6: Commit**
|
|
1094
|
-
|
|
1095
|
-
```bash
|
|
1096
|
-
git add src/commands/project/init.js src/lib/detectors/structure-detector.js
|
|
1097
|
-
git commit -m "refactor(init): delegate infrastructure to setup-infra.js; fix nextjs subdirectory detection"
|
|
1098
|
-
```
|
|
1099
|
-
|
|
1100
|
-
---
|
|
1101
|
-
|
|
1102
|
-
## Task 6: Add plugin checks to `morph-spec doctor`
|
|
1103
|
-
|
|
1104
|
-
**Files:**
|
|
1105
|
-
|
|
1106
|
-
- Modify: `src/commands/project/doctor.js`
|
|
1107
|
-
- Modify: `test/commands/doctor.test.js`
|
|
1108
|
-
|
|
1109
|
-
**Step 1: Write failing tests**
|
|
1110
|
-
|
|
1111
|
-
In `test/commands/doctor.test.js`, add to the existing test suite:
|
|
1112
|
-
|
|
1113
|
-
```js
|
|
1114
|
-
describe('doctor plugin checks', () => {
|
|
1115
|
-
test('checkInstalledPlugins returns present for superpowers when in installed_plugins.json', async () => {
|
|
1116
|
-
const { checkInstalledPlugins } = await import('../../src/commands/project/doctor.js');
|
|
1117
|
-
const fakePlugins = {
|
|
1118
|
-
'superpowers@claude-plugins-official': [{ scope: 'user', version: '4.3.1' }],
|
|
1119
|
-
'context7@claude-plugins-official': [{ scope: 'user', version: 'abc123' }]
|
|
1120
|
-
};
|
|
1121
|
-
const result = checkInstalledPlugins(fakePlugins);
|
|
1122
|
-
assert.strictEqual(result.superpowers, true);
|
|
1123
|
-
assert.strictEqual(result.context7, true);
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
test('checkInstalledPlugins returns false for missing plugin', async () => {
|
|
1127
|
-
const { checkInstalledPlugins } = await import('../../src/commands/project/doctor.js');
|
|
1128
|
-
const result = checkInstalledPlugins({ 'superpowers@claude-plugins-official': [] });
|
|
1129
|
-
assert.strictEqual(result.superpowers, true);
|
|
1130
|
-
assert.strictEqual(result.context7, false);
|
|
1131
|
-
});
|
|
1132
|
-
});
|
|
1133
|
-
```
|
|
1134
|
-
|
|
1135
|
-
**Step 2: Run to verify failure**
|
|
1136
|
-
|
|
1137
|
-
```bash
|
|
1138
|
-
node --test test/commands/doctor.test.js 2>&1 | tail -20
|
|
1139
|
-
```
|
|
1140
|
-
|
|
1141
|
-
Expected: new tests FAIL — `checkInstalledPlugins` not exported
|
|
1142
|
-
|
|
1143
|
-
**Step 3: Add `checkInstalledPlugins` and plugin check section to `doctor.js`**
|
|
1144
|
-
|
|
1145
|
-
Add exported function:
|
|
1146
|
-
|
|
1147
|
-
```js
|
|
1148
|
-
export function checkInstalledPlugins(pluginsData) {
|
|
1149
|
-
const REQUIRED = ['superpowers', 'context7'];
|
|
1150
|
-
return Object.fromEntries(
|
|
1151
|
-
REQUIRED.map(name => [name, `${name}@claude-plugins-official` in pluginsData])
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
In `doctorProjectCommand()` function, after the skills/agents checks block, add:
|
|
1157
|
-
|
|
1158
|
-
```js
|
|
1159
|
-
// ── Claude Code Plugins ──────────────────────────────────────────────────
|
|
1160
|
-
console.log(chalk.cyan('\n Claude Code Plugins (required)'));
|
|
1161
|
-
const pluginsFile = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
1162
|
-
let pluginData = { plugins: {} };
|
|
1163
|
-
try {
|
|
1164
|
-
pluginData = JSON.parse(await fs.readFile(pluginsFile, 'utf8'));
|
|
1165
|
-
} catch { /* file may not exist */ }
|
|
1166
|
-
const pluginStatus = checkInstalledPlugins(pluginData.plugins || {});
|
|
1167
|
-
check('superpowers plugin', pluginStatus.superpowers, false,
|
|
1168
|
-
'Install via: Claude Code → Settings → Extensions → superpowers');
|
|
1169
|
-
check('context7 plugin', pluginStatus.context7, false,
|
|
1170
|
-
'Install via: Claude Code → Settings → Extensions → context7. Or run /morph-init');
|
|
1171
|
-
```
|
|
1172
|
-
|
|
1173
|
-
**Step 4: Run tests**
|
|
1174
|
-
|
|
1175
|
-
```bash
|
|
1176
|
-
node --test test/commands/doctor.test.js
|
|
1177
|
-
```
|
|
1178
|
-
|
|
1179
|
-
Expected: all tests PASS
|
|
1180
|
-
|
|
1181
|
-
**Step 5: Commit**
|
|
1182
|
-
|
|
1183
|
-
```bash
|
|
1184
|
-
git add src/commands/project/doctor.js test/commands/doctor.test.js
|
|
1185
|
-
git commit -m "feat(doctor): add Claude Code plugin checks for superpowers and context7"
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
---
|
|
1189
|
-
|
|
1190
|
-
## Task 7: Full test suite + final validation
|
|
1191
|
-
|
|
1192
|
-
**Step 1: Run full test suite**
|
|
1193
|
-
|
|
1194
|
-
```bash
|
|
1195
|
-
cd "R:/Polymorphism Tech/repos/morph-spec-framework"
|
|
1196
|
-
node --test --test-concurrency=1 2>&1 | tail -20
|
|
1197
|
-
```
|
|
1198
|
-
|
|
1199
|
-
Expected: all existing tests pass + new tests pass. Zero failures.
|
|
1200
|
-
|
|
1201
|
-
**Step 2: Verify `morph-init` skill is picked up by skills-installer**
|
|
1202
|
-
|
|
1203
|
-
```bash
|
|
1204
|
-
node --input-type=module <<'EOF'
|
|
1205
|
-
import { installSkills } from './src/utils/skills-installer.js';
|
|
1206
|
-
import { createTempDir, cleanupTempDir } from './test/helpers/test-utils.js';
|
|
1207
|
-
import { existsSync } from 'fs';
|
|
1208
|
-
import { join } from 'path';
|
|
1209
|
-
const tmp = createTempDir();
|
|
1210
|
-
await installSkills(tmp);
|
|
1211
|
-
const p = join(tmp, '.claude', 'skills', 'morph-init', 'SKILL.md');
|
|
1212
|
-
console.log(existsSync(p) ? '✓ morph-init installed by skills-installer' : '✗ MISSING');
|
|
1213
|
-
cleanupTempDir(tmp);
|
|
1214
|
-
EOF
|
|
1215
|
-
```
|
|
1216
|
-
|
|
1217
|
-
**Step 3: Manual smoke test — simulate `/morph-init` on ProspectPRO**
|
|
1218
|
-
|
|
1219
|
-
Open Claude Code in `R:/Polymorphism Tech/repos/ProspectPRO` and run:
|
|
1220
|
-
|
|
1221
|
-
```
|
|
1222
|
-
/morph-init refresh
|
|
1223
|
-
```
|
|
1224
|
-
|
|
1225
|
-
Verify:
|
|
1226
|
-
|
|
1227
|
-
- Plugins detected as already installed (superpowers + context7)
|
|
1228
|
-
- `.morph/` infrastructure confirmed present
|
|
1229
|
-
- Project analyzed: detects nextjs + dotnet + supabase
|
|
1230
|
-
- 3 targeted questions asked (not more)
|
|
1231
|
-
- `.morph/context/README.md` contains rich content (no `(To be detected)`)
|
|
1232
|
-
- `.morph/config/config.json` has `stack: "nextjs+dotnet"`, `integrations: ["supabase", ...]`
|
|
1233
|
-
- MCP setup offered for Supabase
|
|
1234
|
-
|
|
1235
|
-
**Step 4: Final commit**
|
|
1236
|
-
|
|
1237
|
-
```bash
|
|
1238
|
-
git add .
|
|
1239
|
-
git commit -m "feat(morph-init): complete LLM-powered initialization system
|
|
1240
|
-
|
|
1241
|
-
- setup-infra.js: headless infrastructure extracted from init.js
|
|
1242
|
-
- install-plugin.js: auto-install superpowers/context7 from GitHub
|
|
1243
|
-
- global-install.js: postinstall copies morph-init skill globally
|
|
1244
|
-
- morph-init/SKILL.md: single user-facing entry point
|
|
1245
|
-
- init.js: refactored to delegate to setup-infra
|
|
1246
|
-
- doctor.js: plugin presence checks
|
|
1247
|
-
- structure-detector.js: nextjs subdirectory detection fixed"
|
|
1248
|
-
```
|
|
1249
|
-
|
|
1250
|
-
---
|
|
1251
|
-
|
|
1252
|
-
## Summary
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
| Task | New Files / Changes | Tests |
|
|
1256
|
-
|------|--------------------|----|
|
|
1257
|
-
| 1 | `src/scripts/setup-infra.js` + `src/commands/project/setup-infra-cmd.js` + `bin/morph-spec.js` (`morph-spec setup-infra`) | `test/scripts/setup-infra.test.js` (9 tests) |
|
|
1258
|
-
| 2 | `src/scripts/install-plugin.js` + `src/commands/project/install-plugin-cmd.js` + `bin/morph-spec.js` (`morph-spec install-plugin <name>`) | `test/scripts/install-plugin.test.js` (4 tests) |
|
|
1259
|
-
| 3 | `src/scripts/global-install.js` + `package.json` postinstall | `test/scripts/global-install.test.js` (2 tests) |
|
|
1260
|
-
| 4 | `framework/skills/level-0-meta/morph-init/SKILL.md` (verify CLI commands) | Manual grep validation |
|
|
1261
|
-
| 5 | Refactor `init.js` + fix `structure-detector.js` | Existing `init.test.js` |
|
|
1262
|
-
| 6 | `doctor.js` plugin checks | 2 new tests in `doctor.test.js` |
|
|
1263
|
-
| 7 | Full suite + smoke test | All tests green |
|
|
1264
|
-
|
|
1265
|
-
**CLI commands exposed by this feature:**
|
|
1266
|
-
- `morph-spec setup-infra` — headless infra setup, called by `/morph-init` skill
|
|
1267
|
-
- `morph-spec install-plugin <name>` — install superpowers/context7, called by `/morph-init` skill
|
|
1268
|
-
|
|
1269
|
-
|