@aslomon/effectum 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -16
- package/bin/install.js +189 -113
- package/bin/lib/app-types.js +96 -0
- package/bin/lib/config.js +22 -3
- package/bin/lib/constants.js +14 -30
- package/bin/lib/foundation.js +63 -0
- package/bin/lib/languages.js +77 -0
- package/bin/lib/recommendation.js +392 -0
- package/bin/lib/specializations.js +123 -0
- package/bin/lib/template.js +2 -1
- package/bin/lib/ui.js +350 -21
- package/bin/reconfigure.js +29 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/logo-light.svg">
|
|
6
|
+
<img src="docs/logo-light.svg" alt="effectum" width="280">
|
|
7
|
+
</picture>
|
|
8
|
+
|
|
9
|
+
<br><br>
|
|
4
10
|
|
|
5
11
|
### Describe what you want. Get production-ready code.
|
|
6
12
|
|
|
@@ -9,10 +15,11 @@ _Effectum (Latin): the result, the accomplishment — that which has been brough
|
|
|
9
15
|
[](LICENSE)
|
|
10
16
|
[](https://claude.ai/claude-code)
|
|
11
17
|
[](CONTRIBUTING.md)
|
|
18
|
+
[](https://aslomon.github.io/effectum/)
|
|
12
19
|
|
|
13
20
|
<br>
|
|
14
21
|
|
|
15
|
-
[Quick Start](#-quick-start) · [How It Works](#-how-it-works) · [The Workflow](#-the-workflow) · [PRD Workshop](#-the-prd-workshop) · [How is this different?](#-how-is-this-different) · [
|
|
22
|
+
[Quick Start](#-quick-start) · [How It Works](#-how-it-works) · [The Workflow](#-the-workflow) · [PRD Workshop](#-the-prd-workshop) · [How is this different?](#-how-is-this-different) · [Website](https://aslomon.github.io/effectum/)
|
|
16
23
|
|
|
17
24
|
</div>
|
|
18
25
|
|
|
@@ -82,15 +89,15 @@ claude
|
|
|
82
89
|
|
|
83
90
|
One command. Everything you need for autonomous Claude Code development.
|
|
84
91
|
|
|
85
|
-
| What
|
|
86
|
-
|
|
92
|
+
| What | Details |
|
|
93
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
94
|
| **10 workflow commands** | `/plan`, `/tdd`, `/verify`, `/e2e`, `/code-review`, `/build-fix`, `/refactor-clean`, `/ralph-loop`, `/cancel-ralph`, `/checkpoint` |
|
|
88
|
-
| **PRD Workshop**
|
|
89
|
-
| **4 MCP servers**
|
|
90
|
-
| **Playwright setup**
|
|
91
|
-
| **Stack presets**
|
|
92
|
-
| **Quality gates**
|
|
93
|
-
| **Guardrails**
|
|
95
|
+
| **PRD Workshop** | 8 knowledge files for guided specification writing |
|
|
96
|
+
| **4 MCP servers** | Context7, Playwright, Sequential Thinking, Filesystem |
|
|
97
|
+
| **Playwright setup** | Browser install + `playwright.config.ts` |
|
|
98
|
+
| **Stack presets** | Next.js + Supabase, Python + FastAPI, Swift/SwiftUI, Generic |
|
|
99
|
+
| **Quality gates** | 8 automated checks (build, types, lint, tests, security, etc.) |
|
|
100
|
+
| **Guardrails** | Rules that prevent known mistakes |
|
|
94
101
|
|
|
95
102
|
---
|
|
96
103
|
|
|
@@ -296,12 +303,12 @@ The better the spec, the better the code.
|
|
|
296
303
|
|
|
297
304
|
## 🆚 How is this different?
|
|
298
305
|
|
|
299
|
-
| Tool
|
|
300
|
-
|
|
301
|
-
| **GSD**
|
|
302
|
-
| **BMAD**
|
|
303
|
-
| **SpecKit**
|
|
304
|
-
| **Taskmaster** | Task breakdown from PRDs
|
|
306
|
+
| Tool | What it does | What Effectum adds |
|
|
307
|
+
| -------------- | ----------------------------------------- | -------------------------------------------------------------------------- |
|
|
308
|
+
| **GSD** | Context engineering, prevents context rot | PRD Workshop (helps you write the spec), Ralph Loop (autonomous overnight) |
|
|
309
|
+
| **BMAD** | Full enterprise methodology | Same ideas, 90% less ceremony |
|
|
310
|
+
| **SpecKit** | Living specifications | + Autonomous execution + Quality gates |
|
|
311
|
+
| **Taskmaster** | Task breakdown from PRDs | + TDD workflow + Code review + E2E testing |
|
|
305
312
|
|
|
306
313
|
The short version: Effectum doesn't invent new concepts. It combines what already works, removes what doesn't, and packages it so it actually runs.
|
|
307
314
|
|
package/bin/install.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Effectum interactive installer.
|
|
4
|
-
*
|
|
3
|
+
* Effectum interactive installer — Intelligent Setup Recommender.
|
|
4
|
+
*
|
|
5
|
+
* 9-step flow:
|
|
6
|
+
* 1. Install Scope (global / local)
|
|
7
|
+
* 2. Project Basics (name, stack, package manager)
|
|
8
|
+
* 3. App Type (web-app, api, mobile, cli, ...)
|
|
9
|
+
* 4. Description (free-text intent)
|
|
10
|
+
* 5. Language (15+ languages + custom)
|
|
11
|
+
* 6. Autonomy (conservative / standard / full)
|
|
12
|
+
* 7. Recommendation (preview calculated setup)
|
|
13
|
+
* 8. Decision (use recommended / customize / manual)
|
|
14
|
+
* 9. Install (write files)
|
|
5
15
|
*
|
|
6
16
|
* Usage:
|
|
7
|
-
* npx @aslomon/effectum
|
|
8
|
-
* npx @aslomon/effectum --global
|
|
9
|
-
* npx @aslomon/effectum --local
|
|
10
|
-
* npx @aslomon/effectum --yes
|
|
11
|
-
* npx @aslomon/effectum --dry-run
|
|
17
|
+
* npx @aslomon/effectum -> interactive install
|
|
18
|
+
* npx @aslomon/effectum --global -> non-interactive global install
|
|
19
|
+
* npx @aslomon/effectum --local -> non-interactive local install
|
|
20
|
+
* npx @aslomon/effectum --yes -> non-interactive with smart defaults
|
|
21
|
+
* npx @aslomon/effectum --dry-run -> show plan without writing
|
|
12
22
|
*/
|
|
13
23
|
"use strict";
|
|
14
24
|
|
|
@@ -22,26 +32,38 @@ const {
|
|
|
22
32
|
buildSubstitutionMap,
|
|
23
33
|
renderTemplate,
|
|
24
34
|
findTemplatePath,
|
|
25
|
-
findRemainingPlaceholders,
|
|
26
35
|
} = require("./lib/template");
|
|
27
36
|
const { writeConfig } = require("./lib/config");
|
|
28
37
|
const { AUTONOMY_MAP, FORMATTER_MAP, MCP_SERVERS } = require("./lib/constants");
|
|
29
|
-
const {
|
|
38
|
+
const {
|
|
39
|
+
ensureDir,
|
|
40
|
+
deepMerge,
|
|
41
|
+
findRepoRoot: findRepoRootShared,
|
|
42
|
+
} = require("./lib/utils");
|
|
43
|
+
const { recommend } = require("./lib/recommendation");
|
|
30
44
|
const {
|
|
31
45
|
initClack,
|
|
32
46
|
getClack,
|
|
33
47
|
printBanner,
|
|
48
|
+
askScope,
|
|
34
49
|
askProjectName,
|
|
35
50
|
askStack,
|
|
51
|
+
askAppType,
|
|
52
|
+
askDescription,
|
|
36
53
|
askLanguage,
|
|
37
54
|
askAutonomy,
|
|
38
|
-
|
|
55
|
+
showRecommendation,
|
|
56
|
+
askSetupMode,
|
|
57
|
+
askCustomize,
|
|
58
|
+
askManual,
|
|
39
59
|
askPlaywright,
|
|
40
60
|
askGitBranch,
|
|
41
61
|
showSummary,
|
|
42
62
|
showOutro,
|
|
43
63
|
} = require("./lib/ui");
|
|
44
64
|
|
|
65
|
+
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
45
67
|
function copyFile(src, dest, opts = {}) {
|
|
46
68
|
if (fs.existsSync(dest) && opts.skipExisting) {
|
|
47
69
|
return { status: "skipped", dest };
|
|
@@ -71,7 +93,7 @@ function findRepoRoot() {
|
|
|
71
93
|
return findRepoRootShared();
|
|
72
94
|
}
|
|
73
95
|
|
|
74
|
-
// ─── Parse CLI args
|
|
96
|
+
// ─── Parse CLI args ─────────────────────────────────────────────────────────
|
|
75
97
|
|
|
76
98
|
function parseArgs(argv) {
|
|
77
99
|
const args = argv.slice(2);
|
|
@@ -84,11 +106,11 @@ function parseArgs(argv) {
|
|
|
84
106
|
yes: args.includes("--yes") || args.includes("-y"),
|
|
85
107
|
dryRun: args.includes("--dry-run"),
|
|
86
108
|
help: args.includes("--help") || args.includes("-h"),
|
|
87
|
-
nonInteractive: false,
|
|
109
|
+
nonInteractive: false,
|
|
88
110
|
};
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
// ─── MCP server install helpers
|
|
113
|
+
// ─── MCP server install helpers ─────────────────────────────────────────────
|
|
92
114
|
|
|
93
115
|
function checkPackageAvailable(pkg) {
|
|
94
116
|
try {
|
|
@@ -174,7 +196,7 @@ function addMcpToSettings(settingsPath, mcpResults, targetDir) {
|
|
|
174
196
|
);
|
|
175
197
|
}
|
|
176
198
|
|
|
177
|
-
// ─── Playwright install helpers
|
|
199
|
+
// ─── Playwright install helpers ─────────────────────────────────────────────
|
|
178
200
|
|
|
179
201
|
function installPlaywrightBrowsers() {
|
|
180
202
|
try {
|
|
@@ -228,7 +250,7 @@ export default defineConfig({
|
|
|
228
250
|
return { status: "created", dest: tsConfig };
|
|
229
251
|
}
|
|
230
252
|
|
|
231
|
-
// ─── Core install: copy commands, templates, stacks
|
|
253
|
+
// ─── Core install: copy commands, templates, stacks ─────────────────────────
|
|
232
254
|
|
|
233
255
|
function installBaseFiles(targetDir, repoRoot, isGlobal) {
|
|
234
256
|
const claudeDir = isGlobal ? targetDir : path.join(targetDir, ".claude");
|
|
@@ -278,7 +300,7 @@ function installBaseFiles(targetDir, repoRoot, isGlobal) {
|
|
|
278
300
|
return steps;
|
|
279
301
|
}
|
|
280
302
|
|
|
281
|
-
// ─── Generate configured files (CLAUDE.md, settings.json, guardrails.md)
|
|
303
|
+
// ─── Generate configured files (CLAUDE.md, settings.json, guardrails.md) ───
|
|
282
304
|
|
|
283
305
|
function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
|
|
284
306
|
const claudeDir = isGlobal ? targetDir : path.join(targetDir, ".claude");
|
|
@@ -292,16 +314,14 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
|
|
|
292
314
|
const claudeMdTmpl = findTemplatePath("CLAUDE.md.tmpl", targetDir, repoRoot);
|
|
293
315
|
const { content: claudeMdContent, remaining: claudeMdRemaining } =
|
|
294
316
|
renderTemplate(claudeMdTmpl, vars);
|
|
295
|
-
const claudeMdDest =
|
|
296
|
-
? path.join(targetDir, "CLAUDE.md")
|
|
297
|
-
: path.join(targetDir, "CLAUDE.md");
|
|
317
|
+
const claudeMdDest = path.join(targetDir, "CLAUDE.md");
|
|
298
318
|
ensureDir(path.dirname(claudeMdDest));
|
|
299
319
|
fs.writeFileSync(claudeMdDest, claudeMdContent, "utf8");
|
|
300
320
|
steps.push({ status: "created", dest: claudeMdDest });
|
|
301
321
|
|
|
302
322
|
if (claudeMdRemaining.length > 0) {
|
|
303
323
|
console.warn(
|
|
304
|
-
|
|
324
|
+
`Warning: CLAUDE.md has remaining placeholders: ${claudeMdRemaining.join(", ")}`,
|
|
305
325
|
);
|
|
306
326
|
}
|
|
307
327
|
|
|
@@ -327,6 +347,12 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
|
|
|
327
347
|
deny: settingsObj.permissions?.deny || [],
|
|
328
348
|
};
|
|
329
349
|
|
|
350
|
+
// Apply Agent Teams env var
|
|
351
|
+
if (settingsObj.env) {
|
|
352
|
+
settingsObj.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS =
|
|
353
|
+
config.recommended && config.recommended.agentTeams ? "1" : "0";
|
|
354
|
+
}
|
|
355
|
+
|
|
330
356
|
// Apply formatter in PostToolUse hook
|
|
331
357
|
const formatter = FORMATTER_MAP[config.stack] || FORMATTER_MAP.generic;
|
|
332
358
|
if (settingsObj.hooks?.PostToolUse) {
|
|
@@ -374,7 +400,6 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
|
|
|
374
400
|
const guardrailsRaw = fs.readFileSync(guardrailsTmpl, "utf8");
|
|
375
401
|
let guardrailsContent = guardrailsRaw;
|
|
376
402
|
|
|
377
|
-
// Replace "No stack-specific guardrails..." with actual content
|
|
378
403
|
if (stackSections.STACK_SPECIFIC_GUARDRAILS) {
|
|
379
404
|
guardrailsContent = guardrailsContent.replace(
|
|
380
405
|
/No stack-specific guardrails configured yet\. Run \/setup to configure for your stack\./,
|
|
@@ -396,26 +421,39 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
|
|
|
396
421
|
return steps;
|
|
397
422
|
}
|
|
398
423
|
|
|
399
|
-
// ─── Smart defaults for non-interactive mode
|
|
424
|
+
// ─── Smart defaults for non-interactive mode ────────────────────────────────
|
|
400
425
|
|
|
401
426
|
function buildSmartDefaults(targetDir) {
|
|
402
427
|
const detected = detectAll(targetDir);
|
|
403
|
-
const
|
|
404
|
-
|
|
428
|
+
const stack = detected.stack || "generic";
|
|
429
|
+
const formatter = FORMATTER_MAP[stack] || FORMATTER_MAP.generic;
|
|
430
|
+
|
|
431
|
+
const rec = recommend({
|
|
432
|
+
stack,
|
|
433
|
+
appType: "web-app",
|
|
434
|
+
description: "",
|
|
435
|
+
autonomyLevel: "standard",
|
|
436
|
+
language: "english",
|
|
437
|
+
});
|
|
438
|
+
|
|
405
439
|
return {
|
|
406
440
|
projectName: detected.projectName,
|
|
407
|
-
stack
|
|
441
|
+
stack,
|
|
442
|
+
appType: "web-app",
|
|
443
|
+
description: "",
|
|
408
444
|
language: "english",
|
|
409
445
|
autonomyLevel: "standard",
|
|
410
446
|
packageManager: detected.packageManager,
|
|
411
447
|
formatter: formatter.name,
|
|
412
|
-
mcpServers:
|
|
413
|
-
playwrightBrowsers:
|
|
448
|
+
mcpServers: rec.mcps,
|
|
449
|
+
playwrightBrowsers: rec.mcps.includes("playwright"),
|
|
414
450
|
installScope: "local",
|
|
451
|
+
recommended: rec,
|
|
452
|
+
mode: "recommended",
|
|
415
453
|
};
|
|
416
454
|
}
|
|
417
455
|
|
|
418
|
-
// ─── Git branch creation
|
|
456
|
+
// ─── Git branch creation ────────────────────────────────────────────────────
|
|
419
457
|
|
|
420
458
|
function createGitBranch(name) {
|
|
421
459
|
const result = spawnSync("git", ["checkout", "-b", name], {
|
|
@@ -425,7 +463,7 @@ function createGitBranch(name) {
|
|
|
425
463
|
return result.status === 0;
|
|
426
464
|
}
|
|
427
465
|
|
|
428
|
-
// ─── Main
|
|
466
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
429
467
|
|
|
430
468
|
async function main() {
|
|
431
469
|
const args = parseArgs(process.argv);
|
|
@@ -434,10 +472,10 @@ async function main() {
|
|
|
434
472
|
// Help
|
|
435
473
|
if (args.help) {
|
|
436
474
|
console.log(`
|
|
437
|
-
effectum —
|
|
475
|
+
effectum — intelligent setup for Claude Code
|
|
438
476
|
|
|
439
477
|
Usage:
|
|
440
|
-
npx effectum Interactive installer
|
|
478
|
+
npx effectum Interactive installer (9-step flow)
|
|
441
479
|
npx effectum init Per-project init (after global install)
|
|
442
480
|
npx effectum reconfigure Re-apply config from .effectum.json
|
|
443
481
|
npx effectum --global Install globally (~/.claude/, no prompts)
|
|
@@ -477,7 +515,7 @@ Options:
|
|
|
477
515
|
const homeClaudeDir = path.join(os.homedir(), ".claude");
|
|
478
516
|
const targetDir = isGlobal ? homeClaudeDir : process.cwd();
|
|
479
517
|
|
|
480
|
-
// ── Non-interactive mode
|
|
518
|
+
// ── Non-interactive mode ──────────────────────────────────────────────────
|
|
481
519
|
if (isNonInteractive) {
|
|
482
520
|
const config = buildSmartDefaults(targetDir);
|
|
483
521
|
config.installScope = isGlobal ? "global" : "local";
|
|
@@ -505,17 +543,19 @@ Options:
|
|
|
505
543
|
writeConfig(targetDir, config);
|
|
506
544
|
}
|
|
507
545
|
|
|
508
|
-
// MCP servers
|
|
509
|
-
|
|
510
|
-
|
|
546
|
+
// MCP servers — always install recommended MCPs (or explicit --with-mcp)
|
|
547
|
+
const mcpKeys = config.mcpServers || (config.recommended ? config.recommended.mcps : []) || [];
|
|
548
|
+
if (mcpKeys.length > 0 || args.withMcp) {
|
|
549
|
+
const keysToInstall = mcpKeys.length > 0 ? mcpKeys : MCP_SERVERS.map((s) => s.key);
|
|
550
|
+
const mcpResults = installMcpServers(keysToInstall);
|
|
511
551
|
const settingsPath = isGlobal
|
|
512
552
|
? path.join(homeClaudeDir, "settings.json")
|
|
513
553
|
: path.join(targetDir, ".claude", "settings.json");
|
|
514
554
|
addMcpToSettings(settingsPath, mcpResults, targetDir);
|
|
515
555
|
}
|
|
516
556
|
|
|
517
|
-
// Playwright
|
|
518
|
-
if (args.withPlaywright) {
|
|
557
|
+
// Playwright — install if recommended or explicit
|
|
558
|
+
if (args.withPlaywright || config.playwrightBrowsers) {
|
|
519
559
|
installPlaywrightBrowsers();
|
|
520
560
|
if (!isGlobal) ensurePlaywrightConfig(process.cwd());
|
|
521
561
|
}
|
|
@@ -524,7 +564,7 @@ Options:
|
|
|
524
564
|
process.exit(0);
|
|
525
565
|
}
|
|
526
566
|
|
|
527
|
-
// ── Interactive mode
|
|
567
|
+
// ── Interactive mode — 9-step flow ────────────────────────────────────────
|
|
528
568
|
const p = await initClack();
|
|
529
569
|
printBanner();
|
|
530
570
|
|
|
@@ -536,47 +576,77 @@ Options:
|
|
|
536
576
|
);
|
|
537
577
|
}
|
|
538
578
|
|
|
539
|
-
// Scope
|
|
540
|
-
const scopeValue = await
|
|
541
|
-
message: "Install scope",
|
|
542
|
-
options: [
|
|
543
|
-
{
|
|
544
|
-
value: "local",
|
|
545
|
-
label: "Local",
|
|
546
|
-
hint: "This project only (./.claude/)",
|
|
547
|
-
},
|
|
548
|
-
{
|
|
549
|
-
value: "global",
|
|
550
|
-
label: "Global",
|
|
551
|
-
hint: "All projects (~/.claude/)",
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
initialValue: "local",
|
|
555
|
-
});
|
|
556
|
-
if (p.isCancel(scopeValue)) {
|
|
557
|
-
p.cancel("Setup cancelled.");
|
|
558
|
-
process.exit(0);
|
|
559
|
-
}
|
|
579
|
+
// ── Step 1: Install Scope ─────────────────────────────────────────────────
|
|
580
|
+
const scopeValue = await askScope();
|
|
560
581
|
const installGlobal = scopeValue === "global";
|
|
561
582
|
const installTargetDir = installGlobal ? homeClaudeDir : process.cwd();
|
|
562
583
|
|
|
563
|
-
//
|
|
564
|
-
|
|
584
|
+
// Global install: skip project-specific steps, install base only
|
|
585
|
+
if (installGlobal) {
|
|
586
|
+
if (args.dryRun) {
|
|
587
|
+
p.log.info("Dry run — no files will be written.");
|
|
588
|
+
p.note(
|
|
589
|
+
[
|
|
590
|
+
"~/.claude/commands/*.md",
|
|
591
|
+
"~/.claude/settings.json",
|
|
592
|
+
"~/.claude/guardrails.md",
|
|
593
|
+
"~/.effectum/templates/",
|
|
594
|
+
"~/.effectum/stacks/",
|
|
595
|
+
].join("\n"),
|
|
596
|
+
"Files to be created",
|
|
597
|
+
);
|
|
598
|
+
p.outro("Dry run complete. No changes made.");
|
|
599
|
+
process.exit(0);
|
|
600
|
+
}
|
|
565
601
|
|
|
566
|
-
|
|
602
|
+
const s = p.spinner();
|
|
603
|
+
s.start("Installing global workflow commands and templates...");
|
|
604
|
+
installBaseFiles(installTargetDir, repoRoot, true);
|
|
605
|
+
s.stop("Global base files installed");
|
|
606
|
+
|
|
607
|
+
showOutro(true);
|
|
608
|
+
process.exit(0);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// ── Step 2: Project Basics ────────────────────────────────────────────────
|
|
612
|
+
const projectName = await askProjectName(detected.projectName);
|
|
567
613
|
const stack = await askStack(detected.stack);
|
|
568
614
|
|
|
569
|
-
//
|
|
615
|
+
// ── Step 3: App Type ──────────────────────────────────────────────────────
|
|
616
|
+
const appType = await askAppType();
|
|
617
|
+
|
|
618
|
+
// ── Step 4: Description ───────────────────────────────────────────────────
|
|
619
|
+
const description = await askDescription();
|
|
620
|
+
|
|
621
|
+
// ── Step 5: Language ──────────────────────────────────────────────────────
|
|
570
622
|
const langResult = await askLanguage();
|
|
571
623
|
|
|
572
|
-
// Autonomy
|
|
624
|
+
// ── Step 6: Autonomy ──────────────────────────────────────────────────────
|
|
573
625
|
const autonomyLevel = await askAutonomy();
|
|
574
626
|
|
|
575
|
-
//
|
|
576
|
-
const
|
|
627
|
+
// ── Step 7: Recommendation Preview ────────────────────────────────────────
|
|
628
|
+
const rec = recommend({
|
|
629
|
+
stack,
|
|
630
|
+
appType,
|
|
631
|
+
description,
|
|
632
|
+
autonomyLevel,
|
|
633
|
+
language: langResult.language,
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
showRecommendation(rec);
|
|
577
637
|
|
|
578
|
-
//
|
|
579
|
-
const
|
|
638
|
+
// ── Step 8: Decision ──────────────────────────────────────────────────────
|
|
639
|
+
const setupMode = await askSetupMode();
|
|
640
|
+
|
|
641
|
+
let finalSetup = rec;
|
|
642
|
+
if (setupMode === "customize") {
|
|
643
|
+
finalSetup = await askCustomize(rec);
|
|
644
|
+
} else if (setupMode === "manual") {
|
|
645
|
+
finalSetup = await askManual();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Check if playwright is in the final MCP list and ask about browser install
|
|
649
|
+
const wantPlaywright = finalSetup.mcps.includes("playwright")
|
|
580
650
|
? await askPlaywright()
|
|
581
651
|
: false;
|
|
582
652
|
|
|
@@ -584,23 +654,34 @@ Options:
|
|
|
584
654
|
const gitBranch = await askGitBranch();
|
|
585
655
|
|
|
586
656
|
// Build config object
|
|
587
|
-
const
|
|
657
|
+
const formatterDef = FORMATTER_MAP[stack] || FORMATTER_MAP.generic;
|
|
588
658
|
const config = {
|
|
589
659
|
projectName,
|
|
590
660
|
stack,
|
|
661
|
+
appType,
|
|
662
|
+
description,
|
|
591
663
|
language: langResult.language,
|
|
592
664
|
...(langResult.customLanguage
|
|
593
665
|
? { customLanguage: langResult.customLanguage }
|
|
594
666
|
: {}),
|
|
595
667
|
autonomyLevel,
|
|
596
668
|
packageManager: detected.packageManager,
|
|
597
|
-
formatter:
|
|
598
|
-
mcpServers:
|
|
669
|
+
formatter: formatterDef.name,
|
|
670
|
+
mcpServers: finalSetup.mcps,
|
|
599
671
|
playwrightBrowsers: wantPlaywright,
|
|
600
|
-
installScope:
|
|
672
|
+
installScope: "local",
|
|
673
|
+
recommended: {
|
|
674
|
+
commands: finalSetup.commands,
|
|
675
|
+
hooks: finalSetup.hooks,
|
|
676
|
+
skills: finalSetup.skills,
|
|
677
|
+
mcps: finalSetup.mcps,
|
|
678
|
+
subagents: finalSetup.subagents,
|
|
679
|
+
agentTeams: finalSetup.agentTeams,
|
|
680
|
+
},
|
|
681
|
+
mode: setupMode,
|
|
601
682
|
};
|
|
602
683
|
|
|
603
|
-
// ── Dry run
|
|
684
|
+
// ── Dry run ───────────────────────────────────────────────────────────────
|
|
604
685
|
if (args.dryRun) {
|
|
605
686
|
p.log.info("Dry run — no files will be written.");
|
|
606
687
|
p.note(JSON.stringify(config, null, 2), "Planned Configuration");
|
|
@@ -612,7 +693,7 @@ Options:
|
|
|
612
693
|
"AUTONOMOUS-WORKFLOW.md",
|
|
613
694
|
".effectum.json",
|
|
614
695
|
];
|
|
615
|
-
if (
|
|
696
|
+
if (finalSetup.mcps.length > 0) {
|
|
616
697
|
plannedFiles.push("MCP servers in settings.json");
|
|
617
698
|
}
|
|
618
699
|
p.note(plannedFiles.join("\n"), "Files to be created/updated");
|
|
@@ -620,62 +701,60 @@ Options:
|
|
|
620
701
|
process.exit(0);
|
|
621
702
|
}
|
|
622
703
|
|
|
623
|
-
// ──
|
|
704
|
+
// ── Step 9: Install ───────────────────────────────────────────────────────
|
|
705
|
+
|
|
706
|
+
// Create git branch if requested
|
|
624
707
|
if (gitBranch.create) {
|
|
625
|
-
const
|
|
626
|
-
|
|
708
|
+
const sGit = p.spinner();
|
|
709
|
+
sGit.start("Creating git branch...");
|
|
627
710
|
const ok = createGitBranch(gitBranch.name);
|
|
628
711
|
if (ok) {
|
|
629
|
-
|
|
712
|
+
sGit.stop(`Branch "${gitBranch.name}" created`);
|
|
630
713
|
} else {
|
|
631
|
-
|
|
714
|
+
sGit.stop("Could not create branch (may already exist)");
|
|
632
715
|
}
|
|
633
716
|
}
|
|
634
717
|
|
|
635
|
-
//
|
|
718
|
+
// 9a: Base files
|
|
636
719
|
const s1 = p.spinner();
|
|
637
720
|
s1.start("Installing workflow commands and templates...");
|
|
638
|
-
const baseSteps = installBaseFiles(installTargetDir, repoRoot,
|
|
721
|
+
const baseSteps = installBaseFiles(installTargetDir, repoRoot, false);
|
|
639
722
|
s1.stop(
|
|
640
723
|
`Installed ${baseSteps.filter((s) => s.status === "created").length} files`,
|
|
641
724
|
);
|
|
642
725
|
|
|
643
|
-
//
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
repoRoot,
|
|
654
|
-
installGlobal,
|
|
655
|
-
);
|
|
656
|
-
configSteps.push(...cSteps);
|
|
657
|
-
s2.stop("Configuration files generated");
|
|
658
|
-
}
|
|
726
|
+
// 9b: Configure
|
|
727
|
+
const s2 = p.spinner();
|
|
728
|
+
s2.start("Generating CLAUDE.md, settings.json, guardrails.md...");
|
|
729
|
+
const configSteps = generateConfiguredFiles(
|
|
730
|
+
config,
|
|
731
|
+
installTargetDir,
|
|
732
|
+
repoRoot,
|
|
733
|
+
false,
|
|
734
|
+
);
|
|
735
|
+
s2.stop("Configuration files generated");
|
|
659
736
|
|
|
660
|
-
//
|
|
661
|
-
if (
|
|
737
|
+
// 9c: MCP servers
|
|
738
|
+
if (finalSetup.mcps.length > 0) {
|
|
662
739
|
const s3 = p.spinner();
|
|
663
740
|
s3.start("Setting up MCP servers...");
|
|
664
|
-
const mcpResults = installMcpServers(
|
|
665
|
-
const settingsPath =
|
|
666
|
-
|
|
667
|
-
|
|
741
|
+
const mcpResults = installMcpServers(finalSetup.mcps);
|
|
742
|
+
const settingsPath = path.join(
|
|
743
|
+
installTargetDir,
|
|
744
|
+
".claude",
|
|
745
|
+
"settings.json",
|
|
746
|
+
);
|
|
668
747
|
addMcpToSettings(settingsPath, mcpResults, installTargetDir);
|
|
669
748
|
const okCount = mcpResults.filter((r) => r.ok).length;
|
|
670
749
|
s3.stop(`${okCount} MCP servers configured`);
|
|
671
750
|
}
|
|
672
751
|
|
|
673
|
-
//
|
|
752
|
+
// 9d: Playwright
|
|
674
753
|
if (wantPlaywright) {
|
|
675
754
|
const s4 = p.spinner();
|
|
676
755
|
s4.start("Installing Playwright browsers...");
|
|
677
756
|
const pwResult = installPlaywrightBrowsers();
|
|
678
|
-
|
|
757
|
+
ensurePlaywrightConfig(process.cwd());
|
|
679
758
|
s4.stop(
|
|
680
759
|
pwResult.ok
|
|
681
760
|
? "Playwright browsers installed"
|
|
@@ -683,13 +762,11 @@ Options:
|
|
|
683
762
|
);
|
|
684
763
|
}
|
|
685
764
|
|
|
686
|
-
//
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
configSteps.push({ status: "created", dest: configPath });
|
|
690
|
-
}
|
|
765
|
+
// 9e: Save config
|
|
766
|
+
const configPath = writeConfig(installTargetDir, config);
|
|
767
|
+
configSteps.push({ status: "created", dest: configPath });
|
|
691
768
|
|
|
692
|
-
// ── Summary
|
|
769
|
+
// ── Summary ───────────────────────────────────────────────────────────────
|
|
693
770
|
const allSteps = [...baseSteps, ...configSteps];
|
|
694
771
|
const allFiles = allSteps
|
|
695
772
|
.filter((s) => s && s.dest)
|
|
@@ -700,10 +777,9 @@ Options:
|
|
|
700
777
|
: path.relative(process.cwd(), s.dest);
|
|
701
778
|
});
|
|
702
779
|
|
|
703
|
-
// Deduplicate for summary
|
|
704
780
|
const uniqueFiles = [...new Set(allFiles)].slice(0, 20);
|
|
705
781
|
showSummary(config, uniqueFiles);
|
|
706
|
-
showOutro(
|
|
782
|
+
showOutro(false);
|
|
707
783
|
}
|
|
708
784
|
|
|
709
785
|
main().catch((err) => {
|