specrails-core 3.5.3 → 3.7.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/README.md +11 -11
- package/bin/specrails-core.js +106 -13
- package/bin/tui-installer.mjs +309 -0
- package/commands/enrich.md +1565 -0
- package/docs/README.md +1 -1
- package/docs/changelog.md +17 -0
- package/docs/customization.md +37 -7
- package/docs/getting-started.md +5 -5
- package/docs/installation.md +61 -15
- package/docs/migration-guide.md +3 -3
- package/docs/plugin-architecture.md +13 -11
- package/docs/user-docs/faq.md +11 -11
- package/docs/user-docs/getting-started-codex.md +6 -6
- package/docs/user-docs/installation.md +9 -7
- package/docs/user-docs/quick-start.md +5 -3
- package/install.sh +328 -63
- package/package.json +5 -3
- package/templates/commands/specrails/enrich.md +1637 -0
- package/templates/commands/specrails/reconfig.md +89 -0
- package/templates/commands/specrails/setup.md +75 -3
- package/templates/settings/integration-contract.json +88 -0
- package/update.sh +20 -14
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ One command gives your repo a full team of specialized AI agents: architect, dev
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
claude plugin install sr # Claude Code plugin (recommended)
|
|
16
|
-
/specrails:
|
|
16
|
+
/specrails:enrich # configure for your project
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
> **Requirements:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or [Codex CLI](https://github.com/openai/codex) (choose one), git
|
|
@@ -39,7 +39,7 @@ Every artifact (agents, rules, personas) is generated **specifically for your pr
|
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
claude plugin install sr # install the sr plugin
|
|
42
|
-
/specrails:
|
|
42
|
+
/specrails:enrich # run the 5-phase wizard (~5 min)
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Updates automatically: `claude plugin update sr`
|
|
@@ -47,8 +47,8 @@ Updates automatically: `claude plugin update sr`
|
|
|
47
47
|
### Scaffold method (alternative)
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
npx specrails-core@latest init --root-dir . #
|
|
51
|
-
/specrails:
|
|
50
|
+
npx specrails-core@latest init --root-dir . # TUI agent selection + install files
|
|
51
|
+
/specrails:enrich --from-config # run AI analysis using your config
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### Start building
|
|
@@ -71,10 +71,10 @@ That's it. The pipeline takes over.
|
|
|
71
71
|
| **What it contains** | Agents, skills, hooks, references | Nothing — project data only |
|
|
72
72
|
| **Install** | `claude plugin install sr` | `npx specrails-core@latest init` |
|
|
73
73
|
| **Updates** | `claude plugin update sr` | Re-run npx |
|
|
74
|
-
| **Project data** | Generated by `/specrails:
|
|
74
|
+
| **Project data** | Generated by `/specrails:enrich` into `.specrails/` | Same |
|
|
75
75
|
| **Use case** | Recommended for most projects | When you need full offline control |
|
|
76
76
|
|
|
77
|
-
The plugin contains the logic (agent prompts, skills, commands). Running `/specrails:
|
|
77
|
+
The plugin contains the logic (agent prompts, skills, commands). Running `/specrails:enrich` generates the project-specific data files (~8–10 files in `.specrails/`) — config, personas, memory, pipeline state. These are yours to edit and commit.
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
|
@@ -89,7 +89,7 @@ The plugin contains the logic (agent prompts, skills, commands). Running `/specr
|
|
|
89
89
|
| **Skills** | Plugin | OpenSpec skills (`/opsx:*`) included |
|
|
90
90
|
| **Hooks & References** | Plugin | Agent hooks, reference docs |
|
|
91
91
|
|
|
92
|
-
### Project data (generated by `/specrails:
|
|
92
|
+
### Project data (generated by `/specrails:enrich` — lives in your repo)
|
|
93
93
|
|
|
94
94
|
| Category | Files | Purpose |
|
|
95
95
|
|----------|-------|---------|
|
|
@@ -198,7 +198,7 @@ specrails-core ships with a built-in ticket system — no GitHub account or exte
|
|
|
198
198
|
|
|
199
199
|
Tickets live in `.specrails/local-tickets.json` alongside your code. They're plain JSON and git-friendly.
|
|
200
200
|
|
|
201
|
-
**Local tickets are the default.** The `/specrails:
|
|
201
|
+
**Local tickets are the default.** The `/specrails:enrich` wizard defaults to local tickets and skips GitHub/JIRA credential setup unless you opt in.
|
|
202
202
|
|
|
203
203
|
```bash
|
|
204
204
|
/specrails:implement #1, #4 # implement by ticket ID
|
|
@@ -246,7 +246,7 @@ The plugin method has no Node.js requirement. The scaffold method checks for pre
|
|
|
246
246
|
|
|
247
247
|
## Supported stacks
|
|
248
248
|
|
|
249
|
-
Stack-agnostic. The `/specrails:
|
|
249
|
+
Stack-agnostic. The `/specrails:enrich` wizard detects and adapts to whatever you're running:
|
|
250
250
|
|
|
251
251
|
**Backend:** Python/FastAPI, Node/Express, Go/Gin, Rust/Actix, Java/Spring, Ruby/Rails, .NET
|
|
252
252
|
**Frontend:** React, Vue, Angular, Svelte, Next.js, Nuxt
|
|
@@ -272,8 +272,8 @@ Stack-agnostic. The `/specrails:setup` wizard detects and adapts to whatever you
|
|
|
272
272
|
**Can I customize the agents after installation?**
|
|
273
273
|
With the plugin method, project data files in `.specrails/` are yours to edit — personas, rules, config. With the scaffold method, `.claude/` agent files are also editable. To update plugin logic, run `claude plugin update sr`.
|
|
274
274
|
|
|
275
|
-
**Can I re-run
|
|
276
|
-
Run `/specrails:
|
|
275
|
+
**Can I re-run the wizard?**
|
|
276
|
+
Run `/specrails:enrich` again at any time to regenerate or update project data files.
|
|
277
277
|
|
|
278
278
|
**Plugin vs scaffold — which should I use?**
|
|
279
279
|
Plugin is recommended: it has no Node.js requirement, updates automatically, and separates logic from project data. Use scaffold if you need full offline control or want to version the agent prompts themselves.
|
package/bin/specrails-core.js
CHANGED
|
@@ -9,6 +9,7 @@ const COMMANDS = {
|
|
|
9
9
|
update: "update.sh",
|
|
10
10
|
doctor: "bin/doctor.sh",
|
|
11
11
|
"perf-check": "bin/perf-check.sh",
|
|
12
|
+
enrich: null,
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const args = process.argv.slice(2);
|
|
@@ -18,31 +19,38 @@ if (!subcommand) {
|
|
|
18
19
|
console.log(`specrails-core — Agent Workflow System for Claude Code
|
|
19
20
|
|
|
20
21
|
Usage:
|
|
21
|
-
specrails-core init [--root-dir <path>]
|
|
22
|
-
specrails-core update [--only <component>]
|
|
23
|
-
specrails-core doctor
|
|
24
|
-
specrails-core perf-check [--files <list>]
|
|
22
|
+
specrails-core init [--root-dir <path>] [--yes|-y] Install into a repository
|
|
23
|
+
specrails-core update [--only <component>] Update an existing installation
|
|
24
|
+
specrails-core doctor Run health checks
|
|
25
|
+
specrails-core perf-check [--files <list>] Performance regression check (CI)
|
|
26
|
+
specrails-core enrich [--from-config <path>] Run /specrails:enrich via Claude CLI
|
|
27
|
+
|
|
28
|
+
Flags for init:
|
|
29
|
+
--root-dir <path> Target repository path (default: current directory)
|
|
30
|
+
--yes | -y Non-interactive; use defaults, skip TUI
|
|
31
|
+
--provider <value> Force provider: claude or codex
|
|
32
|
+
--no-direct Skip TUI; use the legacy interactive bash installer
|
|
33
|
+
--from-config Skip TUI; use existing .specrails/install-config.yaml
|
|
25
34
|
|
|
26
35
|
More info: https://github.com/fjpulidop/specrails-core`);
|
|
27
36
|
process.exit(0);
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!script) {
|
|
39
|
+
if (!(subcommand in COMMANDS)) {
|
|
33
40
|
console.error(`Unknown command: ${subcommand}\n`);
|
|
34
|
-
console.error("Available commands: init, update, doctor, perf-check");
|
|
41
|
+
console.error("Available commands: init, update, doctor, perf-check, enrich");
|
|
35
42
|
process.exit(1);
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
45
|
+
const script = COMMANDS[subcommand];
|
|
46
|
+
|
|
47
|
+
// Allowlisted flags per subcommand
|
|
41
48
|
const ALLOWED_FLAGS = {
|
|
42
|
-
init: ["--root-dir", "--yes", "-y"],
|
|
49
|
+
init: ["--root-dir", "--yes", "-y", "--provider", "--no-direct", "--from-config", "--quick", "--hub-json", "--agent-teams"],
|
|
43
50
|
update: ["--only"],
|
|
44
51
|
doctor: [],
|
|
45
52
|
"perf-check": ["--files", "--context"],
|
|
53
|
+
enrich: ["--from-config", "--quick"],
|
|
46
54
|
};
|
|
47
55
|
|
|
48
56
|
const subargs = args.slice(1);
|
|
@@ -55,7 +63,92 @@ for (const arg of subargs) {
|
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
// ─── enrich subcommand ───────────────────────────────────────────────────────
|
|
67
|
+
// Launches `claude --command "/specrails:enrich [flags]"` so the AI-powered
|
|
68
|
+
// enrichment runs inside Claude Code with full model access.
|
|
69
|
+
|
|
70
|
+
if (subcommand === "enrich") {
|
|
71
|
+
const enrichFlags = subargs.join(" ");
|
|
72
|
+
const claudeCmd = `/specrails:enrich${enrichFlags ? " " + enrichFlags : ""}`;
|
|
73
|
+
const claudeResult = spawnSync("claude", ["--command", claudeCmd, "--dangerously-skip-permissions"], {
|
|
74
|
+
stdio: "inherit",
|
|
75
|
+
cwd: process.cwd(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (claudeResult.error) {
|
|
79
|
+
console.error(
|
|
80
|
+
"\nFailed to launch Claude CLI for enrich:",
|
|
81
|
+
claudeResult.error.message,
|
|
82
|
+
"\nEnsure Claude Code is installed: npm install -g @anthropic-ai/claude-code\n"
|
|
83
|
+
);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.exit(claudeResult.status ?? (claudeResult.error ? 1 : 0));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Direct mode (TUI) for `init` ─────────────────────────────────────────────
|
|
91
|
+
//
|
|
92
|
+
// Default behaviour: run the Node.js TUI to collect agent/model configuration,
|
|
93
|
+
// write .specrails/install-config.yaml, then hand off to install.sh.
|
|
94
|
+
//
|
|
95
|
+
// Opt-out with: --no-direct (legacy interactive bash installer)
|
|
96
|
+
// --from-config (config already on disk; skip TUI)
|
|
97
|
+
// --yes / -y (write default config, no prompts)
|
|
98
|
+
|
|
99
|
+
const isInit = subcommand === "init";
|
|
100
|
+
const hasNoTui = subargs.includes("--no-direct") || subargs.includes("--from-config");
|
|
101
|
+
const autoYes = subargs.includes("--yes") || subargs.includes("-y");
|
|
102
|
+
const useTui = isInit && !hasNoTui;
|
|
103
|
+
|
|
104
|
+
if (useTui) {
|
|
105
|
+
// Resolve the target directory for the TUI
|
|
106
|
+
const rootDirIdx = subargs.indexOf("--root-dir");
|
|
107
|
+
const rootDir = rootDirIdx >= 0 ? resolve(subargs[rootDirIdx + 1]) : process.cwd();
|
|
108
|
+
|
|
109
|
+
// Build TUI args: pass rootDir + --yes if set
|
|
110
|
+
const tuiArgs = [resolve(ROOT, "bin/tui-installer.mjs"), rootDir];
|
|
111
|
+
if (autoYes) tuiArgs.push("--yes");
|
|
112
|
+
|
|
113
|
+
const tuiResult = spawnSync("node", tuiArgs, {
|
|
114
|
+
stdio: "inherit",
|
|
115
|
+
cwd: process.cwd(),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (tuiResult.error) {
|
|
119
|
+
// @inquirer/prompts not installed (e.g. during development without npm install)
|
|
120
|
+
console.error(
|
|
121
|
+
"\nFailed to launch TUI installer:",
|
|
122
|
+
tuiResult.error.message,
|
|
123
|
+
"\nRun: npm install or use --no-direct for the legacy installer.\n"
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (tuiResult.status !== 0) {
|
|
129
|
+
process.exit(tuiResult.status ?? 1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// TUI succeeded — run install.sh with --from-config so it reads provider/
|
|
133
|
+
// agent_teams from install-config.yaml rather than prompting interactively.
|
|
134
|
+
const installArgs = subargs
|
|
135
|
+
.filter(a => a !== "--no-direct") // strip internal flags
|
|
136
|
+
.concat(["--yes", "--from-config"]);
|
|
137
|
+
|
|
138
|
+
const result = spawnSync("bash", [resolve(ROOT, script), ...installArgs], {
|
|
139
|
+
stdio: "inherit",
|
|
140
|
+
cwd: process.cwd(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
process.exit(result.status ?? (result.error ? 1 : 0));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── Legacy / non-TUI path ────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
// Strip only --no-direct (internal flag) before passing args to the shell script
|
|
149
|
+
const cleanArgs = subargs.filter(a => a !== "--no-direct");
|
|
150
|
+
|
|
151
|
+
const result = spawnSync("bash", [resolve(ROOT, script), ...cleanArgs], {
|
|
59
152
|
stdio: "inherit",
|
|
60
153
|
cwd: process.cwd(),
|
|
61
154
|
});
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* specrails TUI installer — Phase 1
|
|
4
|
+
* Interactive agent selection + model configuration using @inquirer/prompts.
|
|
5
|
+
* Writes .specrails/install-config.yaml to the target repo directory.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node bin/tui-installer.mjs [rootDir] [--yes]
|
|
8
|
+
* rootDir - target repo directory (default: cwd)
|
|
9
|
+
* --yes - skip TUI, write defaults immediately
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { checkbox, select, input, Separator } from '@inquirer/prompts';
|
|
13
|
+
import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
|
|
17
|
+
// ─── Agent registry ───────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const AGENTS = [
|
|
20
|
+
// Architecture
|
|
21
|
+
{ id: 'sr-architect', category: 'Architecture', description: 'Architecture design, change specs, implementation planning' },
|
|
22
|
+
// Development
|
|
23
|
+
{ id: 'sr-developer', category: 'Development', description: 'Full-stack implementation across all layers' },
|
|
24
|
+
{ id: 'sr-frontend-developer', category: 'Development', description: 'Frontend implementation (React, Vue, Angular, etc.)' },
|
|
25
|
+
{ id: 'sr-backend-developer', category: 'Development', description: 'Backend specialization (APIs, databases, services)' },
|
|
26
|
+
// Review
|
|
27
|
+
{ id: 'sr-reviewer', category: 'Review', description: 'General code review — the final quality gate' },
|
|
28
|
+
{ id: 'sr-frontend-reviewer', category: 'Review', description: 'Frontend review (UI, accessibility, performance)' },
|
|
29
|
+
{ id: 'sr-backend-reviewer', category: 'Review', description: 'Backend review (APIs, security, scalability)' },
|
|
30
|
+
{ id: 'sr-security-reviewer', category: 'Review', description: 'Security analysis — OWASP, vulnerabilities, hardening' },
|
|
31
|
+
{ id: 'sr-performance-reviewer', category: 'Review', description: 'Performance analysis — profiling, bottlenecks, optimization' },
|
|
32
|
+
// Product
|
|
33
|
+
{ id: 'sr-product-manager', category: 'Product', description: 'Product discovery, VPC personas, backlog management' },
|
|
34
|
+
{ id: 'sr-product-analyst', category: 'Product', description: 'Backlog analysis, spec gap analysis, reporting' },
|
|
35
|
+
// Utilities
|
|
36
|
+
{ id: 'sr-test-writer', category: 'Utilities', description: 'Comprehensive test generation (unit, integration, E2E)' },
|
|
37
|
+
{ id: 'sr-doc-sync', category: 'Utilities', description: 'Documentation sync — keeps docs aligned with code' },
|
|
38
|
+
{ id: 'sr-merge-resolver', category: 'Utilities', description: 'Merge conflict resolution with context awareness' },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const ALL_AGENT_IDS = AGENTS.map(a => a.id);
|
|
42
|
+
|
|
43
|
+
const DEFAULT_SELECTED = new Set([
|
|
44
|
+
'sr-architect',
|
|
45
|
+
'sr-developer',
|
|
46
|
+
'sr-reviewer',
|
|
47
|
+
'sr-test-writer',
|
|
48
|
+
'sr-product-manager',
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// ─── Model presets ────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const MODEL_PRESETS = {
|
|
54
|
+
balanced: {
|
|
55
|
+
label: 'Balanced (recommended) — Sonnet for all agents',
|
|
56
|
+
defaults: 'sonnet',
|
|
57
|
+
overrides: {},
|
|
58
|
+
},
|
|
59
|
+
budget: {
|
|
60
|
+
label: 'Budget — Haiku for all agents (3× cheaper, faster)',
|
|
61
|
+
defaults: 'haiku',
|
|
62
|
+
overrides: {},
|
|
63
|
+
},
|
|
64
|
+
max: {
|
|
65
|
+
label: 'Max quality — Opus for architect + PM, Sonnet for rest',
|
|
66
|
+
defaults: 'sonnet',
|
|
67
|
+
overrides: { 'sr-architect': 'opus', 'sr-product-manager': 'opus' },
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
function detectProvider() {
|
|
74
|
+
let hasClaude = false;
|
|
75
|
+
let hasCodex = false;
|
|
76
|
+
try { execSync('claude --version', { stdio: 'ignore' }); hasClaude = true; } catch { /* not installed */ }
|
|
77
|
+
try { execSync('codex --version', { stdio: 'ignore' }); hasCodex = true; } catch { /* not installed */ }
|
|
78
|
+
return { hasClaude, hasCodex };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectGitRoot(dir) {
|
|
82
|
+
try {
|
|
83
|
+
return execSync('git rev-parse --show-toplevel', { cwd: dir, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
84
|
+
.toString()
|
|
85
|
+
.trim();
|
|
86
|
+
} catch {
|
|
87
|
+
return dir;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildCheckboxChoices() {
|
|
92
|
+
const choices = [];
|
|
93
|
+
let currentCategory = null;
|
|
94
|
+
for (const agent of AGENTS) {
|
|
95
|
+
if (agent.category !== currentCategory) {
|
|
96
|
+
if (currentCategory !== null) choices.push(new Separator(''));
|
|
97
|
+
choices.push(new Separator(`── ${agent.category} ${'─'.repeat(Math.max(0, 46 - agent.category.length))}`));
|
|
98
|
+
currentCategory = agent.category;
|
|
99
|
+
}
|
|
100
|
+
choices.push({
|
|
101
|
+
value: agent.id,
|
|
102
|
+
name: `${agent.id.padEnd(28)} ${agent.description}`,
|
|
103
|
+
checked: DEFAULT_SELECTED.has(agent.id),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return choices;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeInstallConfig(specrailsDir, cfg) {
|
|
110
|
+
if (!existsSync(specrailsDir)) mkdirSync(specrailsDir, { recursive: true });
|
|
111
|
+
|
|
112
|
+
const overridesEntries = Object.entries(cfg.modelOverrides);
|
|
113
|
+
const overridesYaml = overridesEntries.length > 0
|
|
114
|
+
? '\n' + overridesEntries.map(([k, v]) => ` ${k}: ${v}`).join('\n')
|
|
115
|
+
: ' {}';
|
|
116
|
+
|
|
117
|
+
const yaml = [
|
|
118
|
+
'# specrails install config — generated by TUI installer',
|
|
119
|
+
'# Re-run: npx specrails-core@latest init to regenerate',
|
|
120
|
+
`version: 1`,
|
|
121
|
+
`provider: ${cfg.provider}`,
|
|
122
|
+
`tier: ${cfg.tier}`,
|
|
123
|
+
`agents:`,
|
|
124
|
+
` selected: [${cfg.selectedAgents.join(', ')}]`,
|
|
125
|
+
` excluded: [${cfg.excludedAgents.join(', ')}]`,
|
|
126
|
+
`models:`,
|
|
127
|
+
` preset: ${cfg.modelPreset}`,
|
|
128
|
+
` defaults: { model: ${cfg.modelDefaults} }`,
|
|
129
|
+
` overrides:${overridesYaml}`,
|
|
130
|
+
`quick_context:`,
|
|
131
|
+
` product_description: "${cfg.productDescription.replace(/"/g, '\\"')}"`,
|
|
132
|
+
` target_users: "${cfg.targetUsers.replace(/"/g, '\\"')}"`,
|
|
133
|
+
`agent_teams: ${cfg.agentTeams}`,
|
|
134
|
+
'',
|
|
135
|
+
].join('\n');
|
|
136
|
+
|
|
137
|
+
writeFileSync(resolve(specrailsDir, 'install-config.yaml'), yaml, 'utf8');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function writeDefaultConfig(specrailsDir, provider) {
|
|
141
|
+
const defaultSelected = [...DEFAULT_SELECTED];
|
|
142
|
+
const defaultExcluded = ALL_AGENT_IDS.filter(id => !DEFAULT_SELECTED.has(id));
|
|
143
|
+
writeInstallConfig(specrailsDir, {
|
|
144
|
+
provider,
|
|
145
|
+
tier: 'full',
|
|
146
|
+
selectedAgents: defaultSelected,
|
|
147
|
+
excludedAgents: defaultExcluded,
|
|
148
|
+
modelPreset: 'balanced',
|
|
149
|
+
modelDefaults: 'sonnet',
|
|
150
|
+
modelOverrides: {},
|
|
151
|
+
productDescription: '',
|
|
152
|
+
targetUsers: '',
|
|
153
|
+
agentTeams: false,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
async function run() {
|
|
160
|
+
const rawArgs = process.argv.slice(2);
|
|
161
|
+
const autoYes = rawArgs.includes('--yes') || rawArgs.includes('-y');
|
|
162
|
+
const rootArg = rawArgs.find(a => !a.startsWith('-'));
|
|
163
|
+
const inputDir = rootArg ? resolve(rootArg) : process.cwd();
|
|
164
|
+
const rootDir = detectGitRoot(inputDir);
|
|
165
|
+
|
|
166
|
+
const specrailsDir = resolve(rootDir, '.specrails');
|
|
167
|
+
|
|
168
|
+
console.log('\n╔══════════════════════════════════════════════╗');
|
|
169
|
+
console.log('║ specrails — Direct Install (v1) ║');
|
|
170
|
+
console.log('║ Configure your AI agent workflow system ║');
|
|
171
|
+
console.log('╚══════════════════════════════════════════════╝\n');
|
|
172
|
+
|
|
173
|
+
// Auto-yes: write defaults and exit
|
|
174
|
+
if (autoYes) {
|
|
175
|
+
const { hasClaude, hasCodex } = detectProvider();
|
|
176
|
+
const provider = hasCodex && !hasClaude ? 'codex' : 'claude';
|
|
177
|
+
writeDefaultConfig(specrailsDir, provider);
|
|
178
|
+
console.log(` ✓ Default config written to .specrails/install-config.yaml`);
|
|
179
|
+
console.log(` ✓ Provider: ${provider}, Tier: full, Agents: ${DEFAULT_SELECTED.size}/${ALL_AGENT_IDS.length}, Preset: balanced\n`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ── Step 1: Provider ────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
const { hasClaude, hasCodex } = detectProvider();
|
|
186
|
+
let provider;
|
|
187
|
+
|
|
188
|
+
if (hasClaude && hasCodex) {
|
|
189
|
+
provider = await select({
|
|
190
|
+
message: 'Which AI provider will you use?',
|
|
191
|
+
choices: [
|
|
192
|
+
{ value: 'claude', name: 'Claude Code (recommended)' },
|
|
193
|
+
{ value: 'codex', name: 'Codex' },
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
} else if (hasCodex) {
|
|
197
|
+
provider = 'codex';
|
|
198
|
+
console.log(' → Provider: codex (auto-detected)\n');
|
|
199
|
+
} else {
|
|
200
|
+
provider = 'claude';
|
|
201
|
+
if (hasClaude) {
|
|
202
|
+
console.log(' → Provider: claude (auto-detected)\n');
|
|
203
|
+
} else {
|
|
204
|
+
console.log(' → Provider: claude (default — claude CLI not found, install it at claude.ai/download)\n');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ── Step 2: Installation tier ───────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
const tier = await select({
|
|
211
|
+
message: 'Installation tier:',
|
|
212
|
+
choices: [
|
|
213
|
+
{
|
|
214
|
+
value: 'full',
|
|
215
|
+
name: 'Full — AI-powered setup (recommended)',
|
|
216
|
+
description: 'After install, run /specrails:enrich to AI-customize all agents for your codebase',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
value: 'quick',
|
|
220
|
+
name: 'Quick — Template-only install',
|
|
221
|
+
description: 'Agents installed with sensible defaults. No AI step required.',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ── Step 3: Agent selection ─────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
console.log('\n Select agents to install. Space = toggle, a = all/none, Enter = confirm.\n');
|
|
229
|
+
|
|
230
|
+
const selectedAgents = await checkbox({
|
|
231
|
+
message: 'Agents to install:',
|
|
232
|
+
choices: buildCheckboxChoices(),
|
|
233
|
+
validate: (selected) => selected.length > 0 || 'Select at least one agent.',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const excludedAgents = ALL_AGENT_IDS.filter(id => !selectedAgents.includes(id));
|
|
237
|
+
|
|
238
|
+
// ── Step 4: Model preset ────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
const modelPreset = await select({
|
|
241
|
+
message: 'Model configuration:',
|
|
242
|
+
choices: Object.entries(MODEL_PRESETS).map(([key, val]) => ({
|
|
243
|
+
value: key,
|
|
244
|
+
name: `${key.padEnd(10)} ${val.label}`,
|
|
245
|
+
})),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const { defaults: modelDefaults, overrides: modelOverrides } = MODEL_PRESETS[modelPreset];
|
|
249
|
+
|
|
250
|
+
// ── Step 5: Agent Teams (Claude only) ──────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
let agentTeams = false;
|
|
253
|
+
if (provider === 'claude') {
|
|
254
|
+
agentTeams = await select({
|
|
255
|
+
message: 'Install Agent Teams commands? (experimental)',
|
|
256
|
+
choices: [
|
|
257
|
+
{ value: false, name: 'No — standard single-agent workflow (recommended)' },
|
|
258
|
+
{ value: true, name: 'Yes — /specrails:team-review and :team-debug (requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1)' },
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── Step 6: Quick context ───────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
console.log('\n Quick context helps specrails personalize agents for your project.\n');
|
|
266
|
+
|
|
267
|
+
const productDescription = await input({
|
|
268
|
+
message: 'Product description (2–3 sentences):',
|
|
269
|
+
validate: (v) => v.trim().length > 0 || 'Please enter a brief product description.',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const targetUsers = await input({
|
|
273
|
+
message: 'Target users (who will use this product?):',
|
|
274
|
+
validate: (v) => v.trim().length > 0 || 'Please describe your target users.',
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ── Write config ────────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
writeInstallConfig(specrailsDir, {
|
|
280
|
+
provider,
|
|
281
|
+
tier,
|
|
282
|
+
selectedAgents,
|
|
283
|
+
excludedAgents,
|
|
284
|
+
modelPreset,
|
|
285
|
+
modelDefaults,
|
|
286
|
+
modelOverrides,
|
|
287
|
+
productDescription: productDescription.trim(),
|
|
288
|
+
targetUsers: targetUsers.trim(),
|
|
289
|
+
agentTeams,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
console.log(`\n ✓ Config written to .specrails/install-config.yaml`);
|
|
293
|
+
console.log(` ✓ Provider: ${provider} | Tier: ${tier} | Agents: ${selectedAgents.length}/${ALL_AGENT_IDS.length} | Preset: ${modelPreset}`);
|
|
294
|
+
if (tier === 'full') {
|
|
295
|
+
console.log(`\n Next: run /specrails:enrich --from-config inside ${provider} to AI-customize your agents.\n`);
|
|
296
|
+
} else {
|
|
297
|
+
console.log(`\n Agents will be installed with template defaults.\n`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
run().catch(err => {
|
|
302
|
+
// Graceful Ctrl+C handling
|
|
303
|
+
if (err.name === 'ExitPromptError' || (err.message && err.message.includes('User force closed'))) {
|
|
304
|
+
console.error('\n Installation cancelled by user.\n');
|
|
305
|
+
process.exit(130);
|
|
306
|
+
}
|
|
307
|
+
console.error('\n TUI error:', err.message);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
});
|