specrails-core 4.8.1 → 4.9.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/bin/specrails-core.mjs +5 -1
- package/bin/tui-installer.mjs +87 -65
- package/dist/installer/cli.js +46 -6
- package/dist/installer/cli.js.map +1 -1
- package/dist/installer/commands/doctor.js +14 -5
- package/dist/installer/commands/doctor.js.map +1 -1
- package/dist/installer/commands/framework.js +134 -0
- package/dist/installer/commands/framework.js.map +1 -0
- package/dist/installer/commands/init.js +107 -32
- package/dist/installer/commands/init.js.map +1 -1
- package/dist/installer/commands/update.js +60 -34
- package/dist/installer/commands/update.js.map +1 -1
- package/dist/installer/phases/scaffold.js +592 -67
- package/dist/installer/phases/scaffold.js.map +1 -1
- package/dist/installer/util/fs.js +143 -1
- package/dist/installer/util/fs.js.map +1 -1
- package/dist/installer/util/registry.js +339 -0
- package/dist/installer/util/registry.js.map +1 -0
- package/package.json +1 -1
- package/pinned-versions.json +1 -1
- package/templates/agents/sr-architect.md +14 -10
- package/templates/agents/sr-backend-developer.md +4 -2
- package/templates/agents/sr-developer.md +20 -8
- package/templates/agents/sr-frontend-developer.md +4 -2
- package/templates/agents/sr-reviewer.md +10 -6
- package/templates/codex-skills/implement/SKILL.md +19 -10
- package/templates/codex-skills/rails/sr-architect/SKILL.md +17 -8
- package/templates/codex-skills/rails/sr-backend-developer/SKILL.md +4 -1
- package/templates/codex-skills/rails/sr-developer/SKILL.md +13 -4
- package/templates/codex-skills/rails/sr-doc-sync/SKILL.md +3 -2
- package/templates/codex-skills/rails/sr-frontend-developer/SKILL.md +4 -1
- package/templates/codex-skills/rails/sr-product-manager/SKILL.md +9 -7
- package/templates/codex-skills/rails/sr-reviewer/SKILL.md +13 -7
- package/templates/codex-skills/retry/SKILL.md +10 -5
- package/templates/commands/specrails/implement.md +41 -23
- package/templates/commands/specrails/retry.md +3 -1
- package/templates/gemini-commands/implement.toml +76 -21
package/bin/specrails-core.mjs
CHANGED
|
@@ -50,6 +50,8 @@ const KNOWN_SUBCOMMANDS = new Set([
|
|
|
50
50
|
'init',
|
|
51
51
|
'update',
|
|
52
52
|
'doctor',
|
|
53
|
+
'install-framework',
|
|
54
|
+
'assemble',
|
|
53
55
|
'enrich',
|
|
54
56
|
'version',
|
|
55
57
|
'profile',
|
|
@@ -58,7 +60,9 @@ const KNOWN_SUBCOMMANDS = new Set([
|
|
|
58
60
|
|
|
59
61
|
if (!KNOWN_SUBCOMMANDS.has(subcommand)) {
|
|
60
62
|
console.error(`Unknown command: ${subcommand}\n`)
|
|
61
|
-
console.error(
|
|
63
|
+
console.error(
|
|
64
|
+
'Available commands: init, update, doctor, install-framework, assemble, enrich, version, profile, help',
|
|
65
|
+
)
|
|
62
66
|
process.exit(1)
|
|
63
67
|
}
|
|
64
68
|
|
package/bin/tui-installer.mjs
CHANGED
|
@@ -72,7 +72,10 @@ const DEFAULT_SELECTED = new Set([
|
|
|
72
72
|
...CORE_AGENTS,
|
|
73
73
|
]);
|
|
74
74
|
|
|
75
|
-
// ─── Model presets
|
|
75
|
+
// ─── Model presets (Claude only — see PROVIDER_DEFAULT_MODEL) ────────────────────
|
|
76
|
+
// Claude has real cost/quality tiers (sonnet/haiku/opus). Codex and gemini are
|
|
77
|
+
// single-model in the scaffold (it hardcodes the per-agent model and ignores the
|
|
78
|
+
// install-config preset), so these presets apply to Claude only.
|
|
76
79
|
|
|
77
80
|
const MODEL_PRESETS = {
|
|
78
81
|
balanced: {
|
|
@@ -92,30 +95,51 @@ const MODEL_PRESETS = {
|
|
|
92
95
|
},
|
|
93
96
|
};
|
|
94
97
|
|
|
98
|
+
// Per-provider default agent model. For codex/gemini the scaffold hardcodes this
|
|
99
|
+
// (the install-config model is advisory), so the TUI writes the provider's real
|
|
100
|
+
// model instead of a Claude-flavoured preset. Keep in sync with scaffold.ts
|
|
101
|
+
// (GEMINI_DEFAULT_MODEL / the codex config.toml model).
|
|
102
|
+
const PROVIDER_DEFAULT_MODEL = {
|
|
103
|
+
claude: 'sonnet',
|
|
104
|
+
codex: 'gpt-5.5-mini',
|
|
105
|
+
gemini: 'gemini-3.5-flash',
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ─── Provider registry ─────────────────────────────────────────────────────────
|
|
109
|
+
// Single source of truth for the AI CLIs the TUI can target. Add a provider here
|
|
110
|
+
// and it flows through detection, the --provider flag, and the interactive picker.
|
|
111
|
+
const PROVIDERS = [
|
|
112
|
+
{ id: 'claude', label: 'Claude Code (recommended)', versionCmd: 'claude --version', installLabel: 'Claude Code', installUrl: 'https://claude.ai/download' },
|
|
113
|
+
{ id: 'codex', label: 'Codex (OpenAI)', versionCmd: 'codex --version', installLabel: 'Codex CLI', installUrl: 'https://developers.openai.com/codex' },
|
|
114
|
+
{ id: 'gemini', label: 'Gemini CLI (Google)', versionCmd: 'gemini --version', installLabel: 'Gemini CLI', installUrl: 'https://github.com/google-gemini/gemini-cli' },
|
|
115
|
+
];
|
|
116
|
+
const VALID_PROVIDER_IDS = new Set(PROVIDERS.map(p => p.id));
|
|
117
|
+
|
|
95
118
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
// Returns the set of provider ids whose CLI responds to `<bin> --version`.
|
|
121
|
+
function detectInstalledProviders() {
|
|
122
|
+
const installed = new Set();
|
|
123
|
+
for (const p of PROVIDERS) {
|
|
124
|
+
try { execSync(p.versionCmd, { stdio: 'ignore' }); installed.add(p.id); } catch { /* not installed */ }
|
|
125
|
+
}
|
|
126
|
+
return installed;
|
|
103
127
|
}
|
|
104
128
|
|
|
105
|
-
// Parse `--provider <id>` or `--provider=<id>` from process.argv. Returns
|
|
106
|
-
//
|
|
107
|
-
//
|
|
129
|
+
// Parse `--provider <id>` or `--provider=<id>` from process.argv. Returns a valid
|
|
130
|
+
// provider id or null (unknown values warn-then-null so the TUI falls back to the
|
|
131
|
+
// interactive picker).
|
|
108
132
|
function parseProviderArg() {
|
|
109
133
|
const args = process.argv.slice(2);
|
|
110
134
|
for (let i = 0; i < args.length; i++) {
|
|
111
135
|
const a = args[i];
|
|
112
136
|
if (a === '--provider' && args[i + 1]) {
|
|
113
137
|
const v = args[i + 1].trim().toLowerCase();
|
|
114
|
-
return v
|
|
138
|
+
return VALID_PROVIDER_IDS.has(v) ? v : null;
|
|
115
139
|
}
|
|
116
140
|
if (a.startsWith('--provider=')) {
|
|
117
141
|
const v = a.slice('--provider='.length).trim().toLowerCase();
|
|
118
|
-
return v
|
|
142
|
+
return VALID_PROVIDER_IDS.has(v) ? v : null;
|
|
119
143
|
}
|
|
120
144
|
}
|
|
121
145
|
return null;
|
|
@@ -188,7 +212,7 @@ function writeDefaultConfig(specrailsDir, provider) {
|
|
|
188
212
|
selectedAgents: defaultSelected,
|
|
189
213
|
excludedAgents: defaultExcluded,
|
|
190
214
|
modelPreset: 'balanced',
|
|
191
|
-
modelDefaults: 'sonnet',
|
|
215
|
+
modelDefaults: PROVIDER_DEFAULT_MODEL[provider] ?? 'sonnet',
|
|
192
216
|
modelOverrides: {},
|
|
193
217
|
agentTeams: false,
|
|
194
218
|
});
|
|
@@ -251,36 +275,29 @@ async function run() {
|
|
|
251
275
|
// Honours an explicit --provider <id> argv flag when present; otherwise
|
|
252
276
|
// picks claude → codex → first-detected. Errors only when none detected.
|
|
253
277
|
if (autoYes) {
|
|
254
|
-
const
|
|
278
|
+
const installed = detectInstalledProviders();
|
|
255
279
|
const argvProvider = parseProviderArg();
|
|
256
280
|
let provider;
|
|
257
281
|
if (argvProvider) {
|
|
258
|
-
if (argvProvider
|
|
282
|
+
if (!installed.has(argvProvider)) {
|
|
283
|
+
const p = PROVIDERS.find(x => x.id === argvProvider);
|
|
259
284
|
console.error('');
|
|
260
|
-
console.error(
|
|
261
|
-
console.error(
|
|
285
|
+
console.error(` ⚠ --provider ${argvProvider} requested but ${p.installLabel} is not installed.`);
|
|
286
|
+
console.error(` Install: ${p.installUrl}`);
|
|
262
287
|
console.error('');
|
|
263
288
|
process.exit(1);
|
|
264
289
|
}
|
|
265
|
-
|
|
290
|
+
provider = argvProvider;
|
|
291
|
+
} else {
|
|
292
|
+
// No flag: pick the first installed provider in registry order.
|
|
293
|
+
provider = PROVIDERS.find(p => installed.has(p.id))?.id;
|
|
294
|
+
if (!provider) {
|
|
266
295
|
console.error('');
|
|
267
|
-
console.error(' ⚠
|
|
268
|
-
console.error(
|
|
296
|
+
console.error(' ⚠ No supported AI CLI detected on PATH (Claude Code, Codex, or Gemini CLI).');
|
|
297
|
+
for (const p of PROVIDERS) console.error(` Install ${p.installLabel}: ${p.installUrl}`);
|
|
269
298
|
console.error('');
|
|
270
299
|
process.exit(1);
|
|
271
300
|
}
|
|
272
|
-
provider = argvProvider;
|
|
273
|
-
} else if (hasClaude) {
|
|
274
|
-
provider = 'claude';
|
|
275
|
-
} else if (hasCodex) {
|
|
276
|
-
provider = 'codex';
|
|
277
|
-
} else {
|
|
278
|
-
console.error('');
|
|
279
|
-
console.error(' ⚠ Neither Claude Code nor Codex CLI detected on PATH.');
|
|
280
|
-
console.error(' Install Claude: https://claude.ai/download');
|
|
281
|
-
console.error(' Install Codex: https://developers.openai.com/codex');
|
|
282
|
-
console.error('');
|
|
283
|
-
process.exit(1);
|
|
284
301
|
}
|
|
285
302
|
writeDefaultConfig(specrailsDir, provider);
|
|
286
303
|
console.log(` ✓ Default config written to .specrails/install-config.yaml`);
|
|
@@ -312,40 +329,37 @@ async function run() {
|
|
|
312
329
|
|
|
313
330
|
// ── Step 1: Provider ────────────────────────────────────────────────────────
|
|
314
331
|
|
|
315
|
-
const
|
|
332
|
+
const installed = detectInstalledProviders();
|
|
316
333
|
const argvProvider = parseProviderArg();
|
|
317
334
|
let provider;
|
|
318
335
|
|
|
319
336
|
if (argvProvider) {
|
|
320
|
-
if (argvProvider
|
|
321
|
-
exitFullscreen();
|
|
322
|
-
console.error('\n ⚠ --provider claude requested but Claude Code is not installed.\n Install: https://claude.ai/download\n');
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
if (argvProvider === 'codex' && !hasCodex) {
|
|
337
|
+
if (!installed.has(argvProvider)) {
|
|
326
338
|
exitFullscreen();
|
|
327
|
-
|
|
339
|
+
const p = PROVIDERS.find(x => x.id === argvProvider);
|
|
340
|
+
console.error(`\n ⚠ --provider ${argvProvider} requested but ${p.installLabel} is not installed.\n Install: ${p.installUrl}\n`);
|
|
328
341
|
process.exit(1);
|
|
329
342
|
}
|
|
330
343
|
provider = argvProvider;
|
|
331
344
|
console.log(` → Provider: ${provider} (from --provider flag)\n`);
|
|
332
|
-
} else if (hasClaude && hasCodex) {
|
|
333
|
-
provider = await select({
|
|
334
|
-
message: 'Which AI provider will you use?',
|
|
335
|
-
choices: [
|
|
336
|
-
{ value: 'claude', name: 'Claude Code (recommended)' },
|
|
337
|
-
{ value: 'codex', name: 'Codex (OpenAI)' },
|
|
338
|
-
],
|
|
339
|
-
});
|
|
340
|
-
} else if (hasCodex) {
|
|
341
|
-
provider = 'codex';
|
|
342
|
-
console.log(' → Provider: codex (auto-detected — Claude Code not found)\n');
|
|
343
|
-
} else if (hasClaude) {
|
|
344
|
-
provider = 'claude';
|
|
345
|
-
console.log(' → Provider: claude (auto-detected)\n');
|
|
346
345
|
} else {
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
const detected = PROVIDERS.filter(p => installed.has(p.id));
|
|
347
|
+
if (detected.length > 1) {
|
|
348
|
+
provider = await select({
|
|
349
|
+
message: 'Which AI provider will you use?',
|
|
350
|
+
choices: detected.map(p => ({ value: p.id, name: p.label })),
|
|
351
|
+
});
|
|
352
|
+
} else if (detected.length === 1) {
|
|
353
|
+
provider = detected[0].id;
|
|
354
|
+
console.log(` → Provider: ${provider} (auto-detected)\n`);
|
|
355
|
+
} else {
|
|
356
|
+
// None detected — still offer the full list (a CLI may work despite a
|
|
357
|
+
// failed --version probe, e.g. a PATH quirk).
|
|
358
|
+
provider = await select({
|
|
359
|
+
message: 'No AI CLI detected on PATH — which will you use?',
|
|
360
|
+
choices: PROVIDERS.map(p => ({ value: p.id, name: p.label })),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
349
363
|
}
|
|
350
364
|
|
|
351
365
|
// ── Step 2: Installation tier ───────────────────────────────────────────────
|
|
@@ -397,15 +411,23 @@ async function run() {
|
|
|
397
411
|
clearScreen();
|
|
398
412
|
console.log(` Provider: ${provider} | Tier: ${tier} | Agents: ${selectedAgents.length}/${ALL_AGENT_IDS.length}\n`);
|
|
399
413
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
414
|
+
// Claude exposes real model tiers; codex/gemini are single-model in the scaffold,
|
|
415
|
+
// so skip the (Claude-flavoured) preset picker for them and use the fixed model.
|
|
416
|
+
let modelPreset = 'balanced';
|
|
417
|
+
let modelDefaults = PROVIDER_DEFAULT_MODEL[provider] ?? 'sonnet';
|
|
418
|
+
let modelOverrides = {};
|
|
419
|
+
if (provider === 'claude') {
|
|
420
|
+
modelPreset = await select({
|
|
421
|
+
message: 'Model configuration:',
|
|
422
|
+
choices: Object.entries(MODEL_PRESETS).map(([key, val]) => ({
|
|
423
|
+
value: key,
|
|
424
|
+
name: `${key.padEnd(10)} ${val.label}`,
|
|
425
|
+
})),
|
|
426
|
+
});
|
|
427
|
+
({ defaults: modelDefaults, overrides: modelOverrides } = MODEL_PRESETS[modelPreset]);
|
|
428
|
+
} else {
|
|
429
|
+
console.log(` → Model: ${modelDefaults} (${provider} uses one model for all agents)\n`);
|
|
430
|
+
}
|
|
409
431
|
|
|
410
432
|
// ── Step 5: Agent Teams (Claude only) ──────────────────────────────────────
|
|
411
433
|
|
package/dist/installer/cli.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { readFileSync } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
14
|
import { runDoctor } from './commands/doctor.js';
|
|
15
|
+
import { runAssemble, runInstallFramework, runSwapCurrent, } from './commands/framework.js';
|
|
15
16
|
import { runInit } from './commands/init.js';
|
|
16
17
|
import { runUpdate } from './commands/update.js';
|
|
17
18
|
import { isInstallerError } from './util/errors.js';
|
|
@@ -78,11 +79,14 @@ function usageText() {
|
|
|
78
79
|
' specrails-core <command> [options]',
|
|
79
80
|
'',
|
|
80
81
|
'Commands:',
|
|
81
|
-
' init
|
|
82
|
-
' update
|
|
83
|
-
' doctor
|
|
84
|
-
'
|
|
85
|
-
'
|
|
82
|
+
' init Install specrails into a repository',
|
|
83
|
+
' update Update an existing specrails installation',
|
|
84
|
+
' doctor Diagnose the health of an existing installation',
|
|
85
|
+
' install-framework Materialize the versioned framework (offline)',
|
|
86
|
+
' swap-current Point framework/current at a version (offline)',
|
|
87
|
+
' assemble Symlink the framework into a workspace (offline)',
|
|
88
|
+
' help Show this help message',
|
|
89
|
+
' version Print the installed version',
|
|
86
90
|
'',
|
|
87
91
|
'Global options:',
|
|
88
92
|
' -h, --help Show this help',
|
|
@@ -126,6 +130,15 @@ async function dispatch(subcommand, flags, _positionals) {
|
|
|
126
130
|
const result = await runDoctor(flags);
|
|
127
131
|
return result.failed === 0 ? 0 : 1;
|
|
128
132
|
}
|
|
133
|
+
case 'install-framework':
|
|
134
|
+
await runInstallFramework(flags);
|
|
135
|
+
return 0;
|
|
136
|
+
case 'swap-current':
|
|
137
|
+
await runSwapCurrent(flags);
|
|
138
|
+
return 0;
|
|
139
|
+
case 'assemble':
|
|
140
|
+
await runAssemble(flags);
|
|
141
|
+
return 0;
|
|
129
142
|
case 'help':
|
|
130
143
|
case '':
|
|
131
144
|
process.stdout.write(usageText());
|
|
@@ -162,4 +175,31 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
162
175
|
return 1;
|
|
163
176
|
}
|
|
164
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Auto-run guard: when this module is executed DIRECTLY as a script
|
|
180
|
+
* (`node dist/installer/cli.js <subcommand>`), run `main()` and propagate its
|
|
181
|
+
* exit code. specrails-desktop's bundled-core path (server/framework-manager.ts)
|
|
182
|
+
* spawns `node <core>/dist/installer/cli.js install-framework|assemble …` and
|
|
183
|
+
* relies on this — the legacy bin dispatcher (bin/specrails-core.mjs) imports
|
|
184
|
+
* `main` and is unaffected (it never executes this file as argv[1]).
|
|
185
|
+
*
|
|
186
|
+
* Comparing `import.meta.url` against `process.argv[1]` keeps the guard inert on
|
|
187
|
+
* import (so unit tests that `import { main }` never trigger a process.exit).
|
|
188
|
+
*/
|
|
189
|
+
const isDirectRun = (() => {
|
|
190
|
+
try {
|
|
191
|
+
const entry = process.argv[1];
|
|
192
|
+
if (!entry)
|
|
193
|
+
return false;
|
|
194
|
+
return import.meta.url === pathToFileURL(entry).href;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
})();
|
|
200
|
+
if (isDirectRun) {
|
|
201
|
+
void main().then((code) => {
|
|
202
|
+
process.exitCode = code;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
165
205
|
//# sourceMappingURL=cli.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/installer/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/installer/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,8 +6,17 @@ import { commandExists, runCommand } from '../util/exec.js';
|
|
|
6
6
|
import { isDir, isFile, listDir, mkdirp } from '../util/fs.js';
|
|
7
7
|
import { assertClaudeAuthenticated } from '../phases/provider-detect.js';
|
|
8
8
|
import { derivedPaths } from '../phases/provider-detect.js';
|
|
9
|
+
import { resolveArtifacts } from '../util/registry.js';
|
|
9
10
|
export async function runDoctor(flags = {}) {
|
|
10
11
|
const projectRoot = path.resolve(typeof flags['root-dir'] === 'string' ? flags['root-dir'] : process.cwd());
|
|
12
|
+
// Relocate-always: Specrails artifacts (provider dir, agent files, instructions
|
|
13
|
+
// file) live under `artifactRoot`. Git stays in the repo (codeRoot). Readers
|
|
14
|
+
// pass allocate:false → falls back to the in-repo legacy layout when there is
|
|
15
|
+
// no registry entry (so a never-installed / legacy repo still reports cleanly).
|
|
16
|
+
const { artifactRoot, codeRoot } = resolveArtifacts(projectRoot, {
|
|
17
|
+
allocate: false,
|
|
18
|
+
home: process.env.SPECRAILS_REGISTRY_HOME,
|
|
19
|
+
});
|
|
11
20
|
rawOut('\nspecrails doctor\n\n');
|
|
12
21
|
const results = [];
|
|
13
22
|
const addPass = (message) => {
|
|
@@ -16,7 +25,7 @@ export async function runDoctor(flags = {}) {
|
|
|
16
25
|
const addFail = (message, fix) => {
|
|
17
26
|
results.push({ kind: 'fail', message, fix });
|
|
18
27
|
};
|
|
19
|
-
const provider = resolveInstalledProvider(
|
|
28
|
+
const provider = resolveInstalledProvider(artifactRoot);
|
|
20
29
|
const { providerDir, instructionsFile } = derivedPaths(provider);
|
|
21
30
|
// Check 1: Claude Code CLI
|
|
22
31
|
if (await commandExists('claude')) {
|
|
@@ -37,7 +46,7 @@ export async function runDoctor(flags = {}) {
|
|
|
37
46
|
}
|
|
38
47
|
}
|
|
39
48
|
// Check 3: Agent files present in the active provider directory.
|
|
40
|
-
const agentsDir = path.join(
|
|
49
|
+
const agentsDir = path.join(artifactRoot, providerDir, 'agents');
|
|
41
50
|
if (isDir(agentsDir)) {
|
|
42
51
|
const agentFiles = findInstalledAgentFiles(agentsDir, provider);
|
|
43
52
|
if (agentFiles.length >= 1) {
|
|
@@ -54,7 +63,7 @@ export async function runDoctor(flags = {}) {
|
|
|
54
63
|
addFail(`Agent files: ${providerDir}/agents directory not found`, 'Run specrails-core init to set up agents');
|
|
55
64
|
}
|
|
56
65
|
// Check 4: provider instructions file present
|
|
57
|
-
if (isFile(path.join(
|
|
66
|
+
if (isFile(path.join(artifactRoot, instructionsFile))) {
|
|
58
67
|
addPass(`${instructionsFile}: present`);
|
|
59
68
|
}
|
|
60
69
|
else {
|
|
@@ -62,8 +71,8 @@ export async function runDoctor(flags = {}) {
|
|
|
62
71
|
? 'Run specrails-core init to regenerate the provider instructions.'
|
|
63
72
|
: 'Run /specrails:enrich inside Claude Code to regenerate.');
|
|
64
73
|
}
|
|
65
|
-
// Check 5: Git initialized
|
|
66
|
-
if (isDir(path.join(
|
|
74
|
+
// Check 5: Git initialized (in the repo — codeRoot, not the artifact root)
|
|
75
|
+
if (isDir(path.join(codeRoot, '.git'))) {
|
|
67
76
|
addPass('Git: initialized');
|
|
68
77
|
}
|
|
69
78
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/installer/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AACrE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAA;AACxE,OAAO,EAAE,YAAY,EAAiB,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/installer/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { InstallerError } from '../util/errors.js';
|
|
4
|
+
import { ok, step } from '../util/logger.js';
|
|
5
|
+
import { pathExists, readTextFile } from '../util/fs.js';
|
|
6
|
+
import { derivedPaths } from '../phases/provider-detect.js';
|
|
7
|
+
import { assembleProjectWorkspace, ensureCurrentSymlink } from '../phases/scaffold.js';
|
|
8
|
+
import { ensureFramework } from './init.js';
|
|
9
|
+
function requireString(value, flagName) {
|
|
10
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
11
|
+
throw new InstallerError(`--${flagName} is required`, 40);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function parseProvider(value) {
|
|
16
|
+
const v = requireString(value, 'provider');
|
|
17
|
+
if (v !== 'claude' && v !== 'codex' && v !== 'gemini') {
|
|
18
|
+
throw new InstallerError(`--provider value must be 'claude', 'codex', or 'gemini', got: ${v}`, 40);
|
|
19
|
+
}
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the specrails-core package root (where templates/ + commands/ live).
|
|
24
|
+
* - Published: `<pkg>/dist/installer/commands/framework.js`
|
|
25
|
+
* - Source: `<repo>/src/installer/commands/framework.ts`
|
|
26
|
+
* Both are three levels deep (commands → installer → src|dist → root). The
|
|
27
|
+
* SPECRAILS_CORE_SCRIPT_DIR env var overrides for tests / desktop bundling.
|
|
28
|
+
*/
|
|
29
|
+
function resolveScriptDir() {
|
|
30
|
+
const override = process.env.SPECRAILS_CORE_SCRIPT_DIR;
|
|
31
|
+
if (override && override.length > 0)
|
|
32
|
+
return path.resolve(override);
|
|
33
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
return path.resolve(here, '..', '..', '..');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `specrails-core install-framework` — materialize the provider-invariant
|
|
38
|
+
* framework subtree ONCE under `<framework-dir>/<version>/<providerDir>/` and
|
|
39
|
+
* point `<framework-dir>/current` at it. Idempotent + offline.
|
|
40
|
+
*/
|
|
41
|
+
export async function runInstallFramework(flags) {
|
|
42
|
+
const scriptDir = resolveScriptDir();
|
|
43
|
+
const frameworkDir = path.resolve(requireString(flags['framework-dir'], 'framework-dir'));
|
|
44
|
+
const provider = parseProvider(flags.provider);
|
|
45
|
+
const version = requireString(flags.version, 'version');
|
|
46
|
+
const { providerDir } = derivedPaths(provider);
|
|
47
|
+
const swapCurrent = flags['no-swap'] !== true;
|
|
48
|
+
step(`Materializing framework ${version} (${provider}) → ${frameworkDir}`);
|
|
49
|
+
ensureFramework({
|
|
50
|
+
scriptDir,
|
|
51
|
+
frameworkDir,
|
|
52
|
+
provider,
|
|
53
|
+
providerDir,
|
|
54
|
+
version,
|
|
55
|
+
agentTeams: flags['agent-teams'] === true,
|
|
56
|
+
swapCurrent,
|
|
57
|
+
});
|
|
58
|
+
if (swapCurrent) {
|
|
59
|
+
ok(`Framework ${version} ready (${providerDir}) + current → ${version}`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
ok(`Framework ${version} ready (${providerDir}) — current NOT swapped (--no-swap)`);
|
|
63
|
+
}
|
|
64
|
+
return { frameworkDir, provider, version, providerDir, swapped: swapCurrent };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `specrails-core swap-current` — atomically point `<framework-dir>/current` at
|
|
68
|
+
* `<version>`. The multi-provider "materialize-all-then-swap-once" finaliser:
|
|
69
|
+
* after EVERY provider was materialized with `--no-swap`, this single call makes
|
|
70
|
+
* the version visible. Idempotent + offline.
|
|
71
|
+
*/
|
|
72
|
+
export async function runSwapCurrent(flags) {
|
|
73
|
+
const frameworkDir = path.resolve(requireString(flags['framework-dir'], 'framework-dir'));
|
|
74
|
+
const version = requireString(flags.version, 'version');
|
|
75
|
+
step(`Swapping framework current → ${version} (${frameworkDir})`);
|
|
76
|
+
ensureCurrentSymlink(frameworkDir, version);
|
|
77
|
+
ok(`current → ${version}`);
|
|
78
|
+
return { frameworkDir, version };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `specrails-core assemble` — SYMLINK the materialized framework subtrees into a
|
|
82
|
+
* project workspace and seed the per-project layer (agent-memory, manifest,
|
|
83
|
+
* instruction/settings files, gemini acks). NO network, NO openspec init.
|
|
84
|
+
*/
|
|
85
|
+
export async function runAssemble(flags) {
|
|
86
|
+
const scriptDir = resolveScriptDir();
|
|
87
|
+
const workspace = path.resolve(requireString(flags.workspace, 'workspace'));
|
|
88
|
+
const frameworkDir = path.resolve(requireString(flags['framework-dir'], 'framework-dir'));
|
|
89
|
+
const provider = parseProvider(flags.provider);
|
|
90
|
+
const version = requireString(flags.version, 'version');
|
|
91
|
+
const codeRoot = path.resolve(requireString(flags['code-root'], 'code-root'));
|
|
92
|
+
const { providerDir } = derivedPaths(provider);
|
|
93
|
+
// Defence-in-depth: the framework must be materialized + `current` pointed at
|
|
94
|
+
// `<version>` BEFORE assemble can link from it. The desktop FrameworkManager
|
|
95
|
+
// always calls install-framework first; this guard surfaces a clear error if
|
|
96
|
+
// a caller skips it.
|
|
97
|
+
const currentProviderDir = path.join(frameworkDir, 'current', providerDir);
|
|
98
|
+
if (!pathExists(currentProviderDir)) {
|
|
99
|
+
throw new InstallerError(`framework not materialized: ${currentProviderDir} is missing — run install-framework first`, 41);
|
|
100
|
+
}
|
|
101
|
+
const selectedAgentsFlag = flags['selected-agents'];
|
|
102
|
+
const selectedAgents = typeof selectedAgentsFlag === 'string' && selectedAgentsFlag.length > 0
|
|
103
|
+
? selectedAgentsFlag.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
|
|
104
|
+
: undefined;
|
|
105
|
+
step(`Assembling workspace ${workspace} ← framework ${version} (${provider})`);
|
|
106
|
+
assembleProjectWorkspace({
|
|
107
|
+
workspace,
|
|
108
|
+
frameworkDir,
|
|
109
|
+
provider,
|
|
110
|
+
providerDir,
|
|
111
|
+
version,
|
|
112
|
+
codeRoot,
|
|
113
|
+
scriptDir,
|
|
114
|
+
selectedAgents,
|
|
115
|
+
agentTeams: flags['agent-teams'] === true,
|
|
116
|
+
});
|
|
117
|
+
ok(`Linked ${providerDir}/ from framework ${version} + seeded project layer`);
|
|
118
|
+
return { workspace, frameworkDir, provider, version, codeRoot, providerDir };
|
|
119
|
+
}
|
|
120
|
+
/** Read the package version (the version the desktop should materialize). */
|
|
121
|
+
export function readPackageVersion(scriptDir) {
|
|
122
|
+
const dir = scriptDir ?? resolveScriptDir();
|
|
123
|
+
const p = path.join(dir, 'package.json');
|
|
124
|
+
if (!pathExists(p))
|
|
125
|
+
return 'unknown';
|
|
126
|
+
try {
|
|
127
|
+
const pkg = JSON.parse(readTextFile(p));
|
|
128
|
+
return pkg.version?.trim() || 'unknown';
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return 'unknown';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=framework.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework.js","sourceRoot":"","sources":["../../../src/installer/commands/framework.ts"],"names":[],"mappings":"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"}
|