specrails-core 4.12.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -78
- package/bin/specrails-core.mjs +18 -98
- package/bin/tui-installer.mjs +22 -105
- package/commands/doctor.md +1 -1
- package/dist/installer/cli.js +12 -2
- package/dist/installer/cli.js.map +1 -1
- package/dist/installer/commands/doctor.js +3 -5
- package/dist/installer/commands/doctor.js.map +1 -1
- package/dist/installer/commands/init.js +23 -19
- package/dist/installer/commands/init.js.map +1 -1
- package/dist/installer/commands/update.js +17 -16
- package/dist/installer/commands/update.js.map +1 -1
- package/dist/installer/commands/v5-migration.js +119 -0
- package/dist/installer/commands/v5-migration.js.map +1 -0
- package/dist/installer/phases/install-config.js +3 -6
- package/dist/installer/phases/install-config.js.map +1 -1
- package/dist/installer/phases/manifest.js +2 -6
- package/dist/installer/phases/manifest.js.map +1 -1
- package/dist/installer/phases/prereqs.js +0 -1
- package/dist/installer/phases/prereqs.js.map +1 -1
- package/dist/installer/phases/scaffold.js +38 -148
- package/dist/installer/phases/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/schemas/profile.v1.json +1 -1
- package/templates/agents/sr-architect.md +30 -0
- package/templates/agents/sr-developer.md +21 -8
- package/templates/agents/sr-reviewer.md +44 -31
- package/templates/codex-skills/batch-implement/SKILL.md +9 -32
- package/templates/codex-skills/implement/SKILL.md +61 -143
- package/templates/codex-skills/rails/sr-architect/SKILL.md +38 -20
- package/templates/codex-skills/rails/sr-developer/SKILL.md +29 -10
- package/templates/codex-skills/rails/sr-reviewer/SKILL.md +21 -10
- package/templates/commands/specrails/doctor.md +1 -1
- package/templates/commands/specrails/implement.md +117 -288
- package/templates/commands/specrails/memory-inspect.md +6 -4
- package/templates/commands/specrails/propose-spec.md +1 -1
- package/templates/commands/specrails/refactor-recommender.md +8 -51
- package/templates/commands/specrails/retry.md +12 -48
- package/templates/commands/specrails/telemetry.md +1 -1
- package/templates/gemini-commands/implement.toml +9 -0
- package/templates/profiles/default.json +5 -18
- package/commands/enrich.md +0 -1456
- package/templates/agents/sr-backend-developer.md +0 -91
- package/templates/agents/sr-backend-reviewer.md +0 -152
- package/templates/agents/sr-doc-sync.md +0 -247
- package/templates/agents/sr-frontend-developer.md +0 -85
- package/templates/agents/sr-frontend-reviewer.md +0 -145
- package/templates/agents/sr-merge-resolver.md +0 -195
- package/templates/agents/sr-performance-reviewer.md +0 -186
- package/templates/agents/sr-product-analyst.md +0 -36
- package/templates/agents/sr-product-manager.md +0 -148
- package/templates/agents/sr-security-reviewer.md +0 -191
- package/templates/agents/sr-test-writer.md +0 -176
- package/templates/codex-skills/enrich/SKILL.md +0 -191
- package/templates/codex-skills/merge-resolve/SKILL.md +0 -88
- package/templates/codex-skills/rails/sr-backend-developer/SKILL.md +0 -93
- package/templates/codex-skills/rails/sr-backend-reviewer/SKILL.md +0 -120
- package/templates/codex-skills/rails/sr-doc-sync/SKILL.md +0 -124
- package/templates/codex-skills/rails/sr-frontend-developer/SKILL.md +0 -106
- package/templates/codex-skills/rails/sr-frontend-reviewer/SKILL.md +0 -111
- package/templates/codex-skills/rails/sr-merge-resolver/SKILL.md +0 -156
- package/templates/codex-skills/rails/sr-performance-reviewer/SKILL.md +0 -109
- package/templates/codex-skills/rails/sr-product-analyst/SKILL.md +0 -85
- package/templates/codex-skills/rails/sr-product-manager/SKILL.md +0 -131
- package/templates/codex-skills/rails/sr-security-reviewer/SKILL.md +0 -121
- package/templates/codex-skills/rails/sr-test-writer/SKILL.md +0 -115
- package/templates/commands/specrails/auto-propose-backlog-specs.md +0 -312
- package/templates/commands/specrails/enrich.md +0 -1456
- package/templates/commands/specrails/get-backlog-specs.md +0 -226
- package/templates/commands/specrails/merge-resolve.md +0 -172
- package/templates/commands/specrails/reconfig.md +0 -80
- package/templates/commands/specrails/vpc-drift.md +0 -405
- package/templates/commands/test.md +0 -58
- package/templates/personas/persona.md +0 -43
- package/templates/personas/the-maintainer.md +0 -98
- package/templates/settings/perf-thresholds.yml +0 -25
|
@@ -6,10 +6,9 @@ import { atomicSymlinkSwap, copyDir, copyFile, isDir, isSymlink, listDir, mkdirp
|
|
|
6
6
|
import { info, ok, warn } from '../util/logger.js';
|
|
7
7
|
import { buildManifest, writeManifestFiles } from './manifest.js';
|
|
8
8
|
/**
|
|
9
|
-
* The three baseline agents
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* agent are optional add-ons selected at install time.
|
|
9
|
+
* The three baseline agents — the COMPLETE set of agents the installer
|
|
10
|
+
* ships. The implement pipeline depends on all three. Any additional agent
|
|
11
|
+
* comes from a user-authored profile (`custom-*`), never the installer.
|
|
13
12
|
*
|
|
14
13
|
* Mirrors the `allOf` baseline in schemas/profile.v1.json — update
|
|
15
14
|
* both files together if this set ever changes.
|
|
@@ -19,11 +18,6 @@ export const CORE_AGENTS = new Set([
|
|
|
19
18
|
'sr-developer',
|
|
20
19
|
'sr-reviewer',
|
|
21
20
|
]);
|
|
22
|
-
/**
|
|
23
|
-
* Agents excluded from the quick tier because they require a full
|
|
24
|
-
* /specrails:enrich persona pass to function correctly.
|
|
25
|
-
*/
|
|
26
|
-
const QUICK_EXCLUDED_AGENTS = new Set(['sr-product-manager', 'sr-product-analyst']);
|
|
27
21
|
/**
|
|
28
22
|
* Gemini built-in tool ids granted to every `.gemini/agents/sr-*.md` subagent.
|
|
29
23
|
* (Validated headless in the desktop spike — read/write/shell/glob/grep.) Because
|
|
@@ -141,14 +135,6 @@ const GEMINI_DEFAULT_MODEL = 'gemini-3.5-flash';
|
|
|
141
135
|
// one with `max_turns: 40` → not found, one without → loads). The 30-turn default
|
|
142
136
|
// cap is instead absorbed by the implement.toml MAX_TURNS → re-delegate/resume
|
|
143
137
|
// contract. Re-introduce only if a future gemini build is reconfirmed to accept it.
|
|
144
|
-
/**
|
|
145
|
-
* Skills excluded from the quick tier because they depend on
|
|
146
|
-
* VPC-only agents (sr-product-manager, sr-product-analyst).
|
|
147
|
-
*/
|
|
148
|
-
const QUICK_EXCLUDED_SKILLS = new Set([
|
|
149
|
-
'sr-auto-propose-backlog-specs',
|
|
150
|
-
'sr-get-backlog-specs',
|
|
151
|
-
]);
|
|
152
138
|
/**
|
|
153
139
|
* Claude top-level `sr-*` skills, GENERATED at install time from their
|
|
154
140
|
* canonical slash-command body under `templates/commands/specrails/<command>.md`.
|
|
@@ -178,26 +164,7 @@ const SKILL_FROM_COMMAND = {
|
|
|
178
164
|
command: 'why',
|
|
179
165
|
description: 'sr:why — Search explanation records written by specrails agents during the OpenSpec implementation pipeline.',
|
|
180
166
|
},
|
|
181
|
-
'sr-get-backlog-specs': {
|
|
182
|
-
command: 'get-backlog-specs',
|
|
183
|
-
description: 'sr:get-backlog-specs — View product-driven backlog from GitHub Issues and propose top 3 for implementation.',
|
|
184
|
-
},
|
|
185
|
-
'sr-auto-propose-backlog-specs': {
|
|
186
|
-
command: 'auto-propose-backlog-specs',
|
|
187
|
-
description: 'sr:auto-propose-backlog-specs — Generate new feature ideas through product discovery, create GitHub Issues.',
|
|
188
|
-
},
|
|
189
167
|
};
|
|
190
|
-
/**
|
|
191
|
-
* Command → required agent dependency map. A command is excluded
|
|
192
|
-
* from the quick tier when its required agent was excluded (i.e.
|
|
193
|
-
* VPC-dependent) or when the feature flag (Agent Teams) is off.
|
|
194
|
-
*/
|
|
195
|
-
const COMMAND_AGENT_DEPENDENCIES = [
|
|
196
|
-
{ command: 'auto-propose-backlog-specs', requires: ['sr-product-manager'] },
|
|
197
|
-
{ command: 'vpc-drift', requires: ['sr-product-manager', 'sr-product-analyst'] },
|
|
198
|
-
{ command: 'get-backlog-specs', requires: ['sr-product-analyst'] },
|
|
199
|
-
{ command: 'merge-resolve', requires: ['sr-merge-resolver'] },
|
|
200
|
-
];
|
|
201
168
|
/**
|
|
202
169
|
* Agents that write "explanation" memory records. When any of them
|
|
203
170
|
* ships we also need a shared `.claude/agent-memory/explanations/`
|
|
@@ -267,7 +234,6 @@ export function scaffoldInstallation(input) {
|
|
|
267
234
|
// Codex skills live under <providerDir>/skills/ (e.g. .codex/skills/).
|
|
268
235
|
// The pre-§18 code wrote to `.agents/skills/` which codex doesn't read;
|
|
269
236
|
// that was a placeholder name from the gated state.
|
|
270
|
-
mk(path.join(input.artifactRoot, input.providerDir, 'skills', 'enrich'));
|
|
271
237
|
mk(path.join(input.artifactRoot, input.providerDir, 'skills', 'doctor'));
|
|
272
238
|
mk(path.join(input.artifactRoot, input.providerDir, 'skills', 'rails'));
|
|
273
239
|
}
|
|
@@ -291,7 +257,6 @@ export function scaffoldInstallation(input) {
|
|
|
291
257
|
mk(path.join(setupTemplates, 'commands'));
|
|
292
258
|
mk(path.join(setupTemplates, 'skills'));
|
|
293
259
|
mk(path.join(setupTemplates, 'rules'));
|
|
294
|
-
mk(path.join(setupTemplates, 'personas'));
|
|
295
260
|
mk(path.join(setupTemplates, 'claude-md'));
|
|
296
261
|
mk(path.join(setupTemplates, 'settings'));
|
|
297
262
|
// --- .gitignore hygiene ---
|
|
@@ -327,19 +292,18 @@ export function scaffoldInstallation(input) {
|
|
|
327
292
|
else {
|
|
328
293
|
warn(`templates/ not found at ${templatesSrc} — skipping template copy`);
|
|
329
294
|
}
|
|
330
|
-
// --- Write bundled commands (
|
|
295
|
+
// --- Write bundled commands (doctor.md) ---
|
|
331
296
|
copyBundledCommands({ ...input, copiedIncrement: (n) => (copiedFiles += n) });
|
|
332
297
|
pruneLegacyArtifacts(input);
|
|
333
298
|
if (input.provider === 'kimi') {
|
|
334
299
|
copiedFiles += placeKimiSkillRunner(input);
|
|
335
300
|
}
|
|
336
|
-
// ---
|
|
337
|
-
|
|
338
|
-
const placed =
|
|
301
|
+
// --- Direct placement (the only path) ---
|
|
302
|
+
{
|
|
303
|
+
const placed = placeArtefacts({ ...input });
|
|
339
304
|
copiedFiles += placed.agents + placed.commands + placed.rules;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
`${placed.rules} rule file(s) directly into ${input.providerDir}/${skippedNote}`);
|
|
305
|
+
info(`Placed ${placed.agents} agent(s) + ${placed.commands} command(s) + ` +
|
|
306
|
+
`${placed.rules} rule file(s) directly into ${input.providerDir}/`);
|
|
343
307
|
}
|
|
344
308
|
// --- Skills placement (both tiers, both providers) ---
|
|
345
309
|
// Claude: top-level `sr-*` skills are generated from their canonical
|
|
@@ -349,10 +313,9 @@ export function scaffoldInstallation(input) {
|
|
|
349
313
|
{
|
|
350
314
|
const skills = placeSkills(input);
|
|
351
315
|
copiedFiles += skills.filesCopied;
|
|
352
|
-
const skillSkipNote = skills.skipped > 0 ? ` (skipped ${skills.skipped} VPC-dependent)` : '';
|
|
353
316
|
const skillsLabel = input.provider === 'gemini' ? 'agent' : 'skill';
|
|
354
317
|
const skillsSubdir = input.provider === 'gemini' ? 'agents' : 'skills';
|
|
355
|
-
info(`Placed ${skills.placed} ${skillsLabel}(s) into ${input.providerDir}/${skillsSubdir}
|
|
318
|
+
info(`Placed ${skills.placed} ${skillsLabel}(s) into ${input.providerDir}/${skillsSubdir}/`);
|
|
356
319
|
}
|
|
357
320
|
// --- Codex provider settings + AGENTS.md initial content ---
|
|
358
321
|
if (input.provider === 'codex') {
|
|
@@ -376,20 +339,6 @@ export function scaffoldInstallation(input) {
|
|
|
376
339
|
info(`Kimi provider: wrote ${written} setting file(s) (.kimi-code/AGENTS.md, mcp.json)`);
|
|
377
340
|
}
|
|
378
341
|
}
|
|
379
|
-
// --- Full-tier hint: enrich is required to generate VPC artefacts ---
|
|
380
|
-
if (input.tier === 'full') {
|
|
381
|
-
const cliName = input.provider === 'codex'
|
|
382
|
-
? 'Codex CLI'
|
|
383
|
-
: input.provider === 'gemini'
|
|
384
|
-
? 'Gemini CLI'
|
|
385
|
-
: input.provider === 'kimi'
|
|
386
|
-
? 'Kimi CLI'
|
|
387
|
-
: 'Claude Code';
|
|
388
|
-
const enrichCommand = input.provider === 'kimi' ? '/skill:specrails-enrich' : '/specrails:enrich';
|
|
389
|
-
info(`Full tier staged. Run \`${enrichCommand}\` in ${cliName} to generate ` +
|
|
390
|
-
'VPC personas and adapt agents (including sr-product-manager and ' +
|
|
391
|
-
'sr-product-analyst) to this codebase.');
|
|
392
|
-
}
|
|
393
342
|
ok(`Created ${createdDirs.length} directories, copied ${copiedFiles} files`);
|
|
394
343
|
return {
|
|
395
344
|
existingSetup: detectExistingSetup({
|
|
@@ -553,7 +502,6 @@ export function installFramework(input) {
|
|
|
553
502
|
codeRoot: versionDir,
|
|
554
503
|
provider: input.provider,
|
|
555
504
|
providerDir: input.providerDir,
|
|
556
|
-
tier: 'quick',
|
|
557
505
|
selectedAgents: undefined,
|
|
558
506
|
materializeAllAgents: true,
|
|
559
507
|
seedProjectDirs: false,
|
|
@@ -697,7 +645,7 @@ function seedProjectLayer(input, currentProviderDir) {
|
|
|
697
645
|
if (!name.endsWith('.md'))
|
|
698
646
|
continue;
|
|
699
647
|
const id = name.slice(0, -3);
|
|
700
|
-
if (selected.has(id)
|
|
648
|
+
if (selected.has(id))
|
|
701
649
|
placedAgentIds.push(id);
|
|
702
650
|
}
|
|
703
651
|
}
|
|
@@ -743,7 +691,6 @@ function seedProjectLayer(input, currentProviderDir) {
|
|
|
743
691
|
const id = path.basename(roleDir);
|
|
744
692
|
if (/^sr-[a-z0-9-]+$/.test(id) &&
|
|
745
693
|
selected.has(id) &&
|
|
746
|
-
!QUICK_EXCLUDED_AGENTS.has(id) &&
|
|
747
694
|
pathExists(path.join(roleDir, 'SKILL.md'))) {
|
|
748
695
|
placedAgentIds.push(id);
|
|
749
696
|
}
|
|
@@ -799,10 +746,10 @@ function seedKimiMcpFile(mcpPath) {
|
|
|
799
746
|
* every SELECTED framework-owned agent at the shared read-only copy.
|
|
800
747
|
*
|
|
801
748
|
* `selectedIds` is the per-project agent allow-list (already unioned with the
|
|
802
|
-
* CORE trio by the caller). Only framework agents whose id is in it
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
749
|
+
* CORE trio by the caller). Only framework agents whose id is in it are linked —
|
|
750
|
+
* the shared framework store is the full superset, so this is where per-project
|
|
751
|
+
* filtering lands. `undefined` ⇒ link every framework agent (used by the legacy
|
|
752
|
+
* callers / parity tests).
|
|
806
753
|
*
|
|
807
754
|
* When `preferCopy` is true each agent is COPIED as a real file rather than
|
|
808
755
|
* symlinked (the in-repo standalone install — so a standalone user's CLI finds
|
|
@@ -826,7 +773,7 @@ function linkAgentFiles(frameworkAgentsDir, workspaceAgentsDir, selectedIds, pre
|
|
|
826
773
|
continue;
|
|
827
774
|
frameworkProvided.add(name);
|
|
828
775
|
const id = name.slice(0, -3);
|
|
829
|
-
if (selectedIds &&
|
|
776
|
+
if (selectedIds && !selectedIds.has(id))
|
|
830
777
|
continue;
|
|
831
778
|
linkedNames.add(name);
|
|
832
779
|
const m = symlinkOrCopy(src, path.join(workspaceAgentsDir, name), preferCopy);
|
|
@@ -886,8 +833,7 @@ function linkKimiSkillDirectories(frameworkSkillsDir, workspaceSkillsDir, select
|
|
|
886
833
|
// it, but never expose nested roles if a caller supplies one directly.
|
|
887
834
|
continue;
|
|
888
835
|
}
|
|
889
|
-
if (/^sr-[a-z0-9-]+$/.test(name) &&
|
|
890
|
-
(!selectedRoleIds.has(name) || QUICK_EXCLUDED_AGENTS.has(name))) {
|
|
836
|
+
if (/^sr-[a-z0-9-]+$/.test(name) && !selectedRoleIds.has(name)) {
|
|
891
837
|
continue;
|
|
892
838
|
}
|
|
893
839
|
linkedFrameworkNames.add(name);
|
|
@@ -1008,9 +954,8 @@ function copyBundledCommands(input) {
|
|
|
1008
954
|
const destDir = path.join(input.artifactRoot, input.providerDir, 'skills', skillName);
|
|
1009
955
|
// A codex-native override (written for spawn_agent semantics + the
|
|
1010
956
|
// correct `.codex/skills/rails/` layout) wins over the claude port.
|
|
1011
|
-
// This is the ONLY codex command-placement pass
|
|
1012
|
-
//
|
|
1013
|
-
// body — e.g. enrich's obsolete `.codex/agents/*.toml` model.
|
|
957
|
+
// This is the ONLY codex command-placement pass, so without the override
|
|
958
|
+
// check codex users get the claude body with no codex-native semantics.
|
|
1014
959
|
const overrideSkill = path.join(codexOverrides, skillName, 'SKILL.md');
|
|
1015
960
|
if (pathExists(overrideSkill)) {
|
|
1016
961
|
copyDir(path.join(codexOverrides, skillName), destDir);
|
|
@@ -1385,7 +1330,7 @@ function adaptKimiWorkflowBody(commandName, body) {
|
|
|
1385
1330
|
}
|
|
1386
1331
|
if (commandName !== 'implement')
|
|
1387
1332
|
return body;
|
|
1388
|
-
let adapted = replaceMarkdownSection(body, '##### Apply per-agent model overrides (profile
|
|
1333
|
+
let adapted = replaceMarkdownSection(body, '##### Apply per-agent model overrides (only when a profile declares them)', '##### Agent roles', [
|
|
1389
1334
|
'##### Resolve per-role model overrides (profile mode only)',
|
|
1390
1335
|
'',
|
|
1391
1336
|
'Keep each `AGENT_MODEL[id]` value exactly as declared in the profile.',
|
|
@@ -1399,24 +1344,6 @@ function adaptKimiWorkflowBody(commandName, body) {
|
|
|
1399
1344
|
'`kimi-code/` prefix at the CLI boundary. Never map Claude aliases.',
|
|
1400
1345
|
'',
|
|
1401
1346
|
].join('\n'));
|
|
1402
|
-
adapted = replaceMarkdownSection(adapted, '##### Legacy mode — preserve current behavior', '##### Agent roles (both modes)', [
|
|
1403
|
-
'##### Legacy mode — preserve current behavior',
|
|
1404
|
-
'',
|
|
1405
|
-
'If no profile is active, discover role directories without mutating them:',
|
|
1406
|
-
'',
|
|
1407
|
-
'```bash',
|
|
1408
|
-
'AVAILABLE_AGENTS="$(find -L .kimi-code/skills -mindepth 2 -maxdepth 2 \\',
|
|
1409
|
-
' -type f -name SKILL.md -print 2>/dev/null | sed \'s|.*/skills/||;s|/SKILL.md$||\' \\',
|
|
1410
|
-
' | grep -E \'^(sr|custom)-\' | sort)"',
|
|
1411
|
-
'PROFILE_MODE="legacy"',
|
|
1412
|
-
'PROFILE_NAME=""',
|
|
1413
|
-
'```',
|
|
1414
|
-
'',
|
|
1415
|
-
'Per-role model overrides are empty in legacy mode. External role',
|
|
1416
|
-
'processes use the explicit Kimi default (`k3`, launched as',
|
|
1417
|
-
'`kimi-code/k3`).',
|
|
1418
|
-
'',
|
|
1419
|
-
].join('\n'));
|
|
1420
1347
|
adapted = replaceMarkdownSection(adapted, '#### Merge Algorithm', '**Step 4: Record outcomes**', [
|
|
1421
1348
|
'#### Kimi role-wave merge algorithm',
|
|
1422
1349
|
'',
|
|
@@ -1910,7 +1837,6 @@ function placeGeminiAgents(input) {
|
|
|
1910
1837
|
const placeholders = {
|
|
1911
1838
|
PROJECT_NAME: path.basename(input.codeRoot),
|
|
1912
1839
|
SECURITY_EXEMPTIONS_PATH: '.gemini/security-exemptions.yaml',
|
|
1913
|
-
PERSONA_DIR: '.gemini/agents/personas/',
|
|
1914
1840
|
};
|
|
1915
1841
|
const placedIds = [];
|
|
1916
1842
|
for (const src of listDir(agentsSrc)) {
|
|
@@ -1922,10 +1848,6 @@ function placeGeminiAgents(input) {
|
|
|
1922
1848
|
// filtering happens at the workspace LINK step (linkAgentFiles).
|
|
1923
1849
|
if (!input.materializeAllAgents && !selectedAgents.has(agentId))
|
|
1924
1850
|
continue;
|
|
1925
|
-
if (!input.materializeAllAgents && QUICK_EXCLUDED_AGENTS.has(agentId)) {
|
|
1926
|
-
result.skipped++;
|
|
1927
|
-
continue;
|
|
1928
|
-
}
|
|
1929
1851
|
writeGeminiAgentFromTemplate({
|
|
1930
1852
|
artifactRoot: input.artifactRoot,
|
|
1931
1853
|
src,
|
|
@@ -2202,20 +2124,19 @@ function pruneLegacyArtifacts(input) {
|
|
|
2202
2124
|
}
|
|
2203
2125
|
}
|
|
2204
2126
|
/**
|
|
2205
|
-
*
|
|
2127
|
+
* Direct placement: copy agents / commands / rules from the
|
|
2206
2128
|
* .specrails/setup-templates/ staging directory into the live
|
|
2207
|
-
* provider directory, substituting template placeholders
|
|
2208
|
-
*
|
|
2129
|
+
* provider directory, substituting template placeholders. This is the
|
|
2130
|
+
* only placement path — there are no install tiers.
|
|
2209
2131
|
*
|
|
2210
2132
|
* Source is setup-templates/ (not scriptDir/templates/) so the pipeline
|
|
2211
2133
|
* is: scriptDir/templates/ → setup-templates/ (earlier scaffold step)
|
|
2212
|
-
* → <providerDir>/ (this function). The intermediate hop
|
|
2213
|
-
*
|
|
2214
|
-
*
|
|
2215
|
-
* canonical staging dir.
|
|
2134
|
+
* → <providerDir>/ (this function). The intermediate hop lets downstream
|
|
2135
|
+
* consumers (specrails-desktop's deployTemplates, update flow) read from a
|
|
2136
|
+
* single canonical staging dir.
|
|
2216
2137
|
*/
|
|
2217
|
-
function
|
|
2218
|
-
// Codex projects: the
|
|
2138
|
+
function placeArtefacts(input) {
|
|
2139
|
+
// Codex projects: the `agents/` + `rules/` placement is
|
|
2219
2140
|
// skipped (handled by `placeSkills` rail-skills + `applyCodexSettings`).
|
|
2220
2141
|
// The slash-command catalogue under `setup-templates/commands/specrails/`
|
|
2221
2142
|
// IS ported, but to `.codex/skills/<name>/SKILL.md` instead of
|
|
@@ -2322,18 +2243,16 @@ function placeQuickTierArtefacts(input) {
|
|
|
2322
2243
|
const placeholders = {
|
|
2323
2244
|
PROJECT_NAME: projectName,
|
|
2324
2245
|
SECURITY_EXEMPTIONS_PATH: `${input.providerDir}/security-exemptions.yaml`,
|
|
2325
|
-
PERSONA_DIR: `${input.providerDir}/agents/personas/`,
|
|
2326
2246
|
};
|
|
2327
2247
|
// --- Agents ---
|
|
2328
2248
|
const agentsSrc = path.join(setupTemplates, 'agents');
|
|
2329
2249
|
const agentsDest = path.join(providerDirAbs, 'agents');
|
|
2330
2250
|
let agentsPlaced = 0;
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
//
|
|
2334
|
-
//
|
|
2335
|
-
//
|
|
2336
|
-
// product agents) are explicitly opt-in via the TUI or install-config.
|
|
2251
|
+
const agentsSkipped = 0;
|
|
2252
|
+
// The only shipped agents are the three core agents. A profile-driven
|
|
2253
|
+
// install may pass a selection; anything outside CORE_AGENTS has no template
|
|
2254
|
+
// to place, so the intersection is always the core trio (extension happens
|
|
2255
|
+
// via user-owned custom-*.md agents, never through the installer).
|
|
2337
2256
|
const selectedAgents = input.selectedAgents
|
|
2338
2257
|
? new Set([...input.selectedAgents, ...CORE_AGENTS])
|
|
2339
2258
|
: new Set([...CORE_AGENTS]);
|
|
@@ -2349,10 +2268,6 @@ function placeQuickTierArtefacts(input) {
|
|
|
2349
2268
|
// filtering happens at the workspace LINK step, not here.
|
|
2350
2269
|
if (!input.materializeAllAgents && selectedAgents && !selectedAgents.has(agentId))
|
|
2351
2270
|
continue;
|
|
2352
|
-
if (!input.materializeAllAgents && QUICK_EXCLUDED_AGENTS.has(agentId)) {
|
|
2353
|
-
agentsSkipped++;
|
|
2354
|
-
continue;
|
|
2355
|
-
}
|
|
2356
2271
|
const dest = path.join(agentsDest, name);
|
|
2357
2272
|
const rendered = renderPlaceholders(readTextFile(src), {
|
|
2358
2273
|
...placeholders,
|
|
@@ -2360,7 +2275,6 @@ function placeQuickTierArtefacts(input) {
|
|
|
2360
2275
|
});
|
|
2361
2276
|
writeFileLf(dest, rendered);
|
|
2362
2277
|
agentsPlaced++;
|
|
2363
|
-
installedAgentNames.add(agentId);
|
|
2364
2278
|
// Per-agent memory directory. Created even when empty so the first run of
|
|
2365
2279
|
// the agent doesn't error on ENOENT. Skipped when materializing the SHARED
|
|
2366
2280
|
// framework (`seedProjectDirs === false`): agent-memory is per-workspace
|
|
@@ -2375,13 +2289,6 @@ function placeQuickTierArtefacts(input) {
|
|
|
2375
2289
|
}
|
|
2376
2290
|
}
|
|
2377
2291
|
// --- Commands ---
|
|
2378
|
-
// Skip commands whose required agents were excluded.
|
|
2379
|
-
const excludedCommands = new Set();
|
|
2380
|
-
for (const dep of COMMAND_AGENT_DEPENDENCIES) {
|
|
2381
|
-
const hasAllRequired = dep.requires.every((a) => installedAgentNames.has(a));
|
|
2382
|
-
if (!hasAllRequired)
|
|
2383
|
-
excludedCommands.add(dep.command);
|
|
2384
|
-
}
|
|
2385
2292
|
const commandsSrc = path.join(setupTemplates, 'commands', 'specrails');
|
|
2386
2293
|
const commandsDest = path.join(providerDirAbs, 'commands', 'specrails');
|
|
2387
2294
|
let commandsPlaced = 0;
|
|
@@ -2391,9 +2298,6 @@ function placeQuickTierArtefacts(input) {
|
|
|
2391
2298
|
const name = path.basename(src);
|
|
2392
2299
|
if (!name.endsWith('.md'))
|
|
2393
2300
|
continue;
|
|
2394
|
-
const cmdId = name.slice(0, -3);
|
|
2395
|
-
if (excludedCommands.has(cmdId))
|
|
2396
|
-
continue;
|
|
2397
2301
|
const dest = path.join(commandsDest, name);
|
|
2398
2302
|
const rendered = renderPlaceholders(readTextFile(src), {
|
|
2399
2303
|
...placeholders,
|
|
@@ -2445,7 +2349,7 @@ function applyCodexSettings(input) {
|
|
|
2445
2349
|
written++;
|
|
2446
2350
|
}
|
|
2447
2351
|
// AGENTS.md — top-level instructions file the codex CLI loads on startup.
|
|
2448
|
-
// Written with a sentinel block so update
|
|
2352
|
+
// Written with a sentinel block so update passes can refresh the
|
|
2449
2353
|
// managed content while preserving anything the user added outside it.
|
|
2450
2354
|
const agentsMdPath = path.join(input.artifactRoot, 'AGENTS.md');
|
|
2451
2355
|
const agentsMdContent = renderInitialAgentsMd(input.codeRoot);
|
|
@@ -2513,7 +2417,7 @@ function upsertAgentsMdManagedBlock(existing, managedBlock) {
|
|
|
2513
2417
|
// CLAUDE: the top-level `sr-*` skills (sr-implement, sr-why, …) are GENERATED
|
|
2514
2418
|
// from their canonical slash-command body (`templates/commands/specrails/
|
|
2515
2419
|
// <command>.md`) — the command is the single source of truth, so the skill can
|
|
2516
|
-
// never drift from it.
|
|
2420
|
+
// never drift from it.
|
|
2517
2421
|
//
|
|
2518
2422
|
// CODEX: top-level skills are NOT placed — every one has a command counterpart
|
|
2519
2423
|
// that the command path ports to `.codex/skills/<name>/` (with codex-native
|
|
@@ -2529,10 +2433,6 @@ function placeSkills(input) {
|
|
|
2529
2433
|
const commandsSrc = path.join(input.artifactRoot, '.specrails', 'setup-templates', 'commands', 'specrails');
|
|
2530
2434
|
const skillEntries = Object.entries(SKILL_FROM_COMMAND);
|
|
2531
2435
|
for (const [skillName, spec] of skillEntries) {
|
|
2532
|
-
if (input.tier === 'quick' && QUICK_EXCLUDED_SKILLS.has(skillName)) {
|
|
2533
|
-
result.skipped++;
|
|
2534
|
-
continue;
|
|
2535
|
-
}
|
|
2536
2436
|
const src = path.join(commandsSrc, `${spec.command}.md`);
|
|
2537
2437
|
if (!pathExists(src))
|
|
2538
2438
|
continue;
|
|
@@ -2555,8 +2455,7 @@ function placeSkills(input) {
|
|
|
2555
2455
|
// copies were vestigial — unused on Claude, always overridden on codex,
|
|
2556
2456
|
// and shipped with unsubstituted placeholders — and were removed).
|
|
2557
2457
|
//
|
|
2558
|
-
// Only the three CORE_AGENTS
|
|
2559
|
-
// every layer specialist are placed only when selectedAgents includes them.
|
|
2458
|
+
// Only the three CORE_AGENTS ship, so only their rail skills exist to place.
|
|
2560
2459
|
const codexRailsOverridesDir = input.provider === 'codex'
|
|
2561
2460
|
? path.join(input.scriptDir, 'templates', 'codex-skills', 'rails')
|
|
2562
2461
|
: null;
|
|
@@ -2612,10 +2511,6 @@ function placeSkills(input) {
|
|
|
2612
2511
|
const roleId = name.slice(0, -3);
|
|
2613
2512
|
if (!input.materializeAllAgents && !selectedAgents.has(roleId))
|
|
2614
2513
|
continue;
|
|
2615
|
-
if (!input.materializeAllAgents && QUICK_EXCLUDED_AGENTS.has(roleId)) {
|
|
2616
|
-
result.skipped++;
|
|
2617
|
-
continue;
|
|
2618
|
-
}
|
|
2619
2514
|
writeKimiRoleSkill({
|
|
2620
2515
|
src,
|
|
2621
2516
|
dest: path.join(destBase, roleId, 'SKILL.md'),
|
|
@@ -2630,14 +2525,9 @@ function placeSkills(input) {
|
|
|
2630
2525
|
}
|
|
2631
2526
|
}
|
|
2632
2527
|
}
|
|
2528
|
+
// v5 ships only the core trio; every bundled command's role dependencies
|
|
2529
|
+
// are always satisfied, so no command exclusion set is needed.
|
|
2633
2530
|
const excludedCommands = new Set();
|
|
2634
|
-
if (!input.materializeAllAgents) {
|
|
2635
|
-
for (const dep of COMMAND_AGENT_DEPENDENCIES) {
|
|
2636
|
-
if (!dep.requires.every((role) => placedRoleIds.has(role))) {
|
|
2637
|
-
excludedCommands.add(dep.command);
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
2531
|
if (isDir(commandsSrc)) {
|
|
2642
2532
|
for (const src of listDir(commandsSrc)) {
|
|
2643
2533
|
const name = path.basename(src);
|