specrails-core 4.11.2 โ†’ 4.12.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.
Files changed (56) hide show
  1. package/README.md +56 -20
  2. package/bin/specrails-core.mjs +343 -20
  3. package/bin/tui-installer.mjs +103 -52
  4. package/dist/installer/cli.js +1 -1
  5. package/dist/installer/cli.js.map +1 -1
  6. package/dist/installer/commands/doctor.js +486 -24
  7. package/dist/installer/commands/doctor.js.map +1 -1
  8. package/dist/installer/commands/framework.js +49 -7
  9. package/dist/installer/commands/framework.js.map +1 -1
  10. package/dist/installer/commands/init.js +423 -25
  11. package/dist/installer/commands/init.js.map +1 -1
  12. package/dist/installer/commands/update.js +36 -9
  13. package/dist/installer/commands/update.js.map +1 -1
  14. package/dist/installer/phases/framework-lifecycle.js +125 -0
  15. package/dist/installer/phases/framework-lifecycle.js.map +1 -0
  16. package/dist/installer/phases/install-config.js +157 -5
  17. package/dist/installer/phases/install-config.js.map +1 -1
  18. package/dist/installer/phases/manifest.js +27 -2
  19. package/dist/installer/phases/manifest.js.map +1 -1
  20. package/dist/installer/phases/prereqs.js +57 -2
  21. package/dist/installer/phases/prereqs.js.map +1 -1
  22. package/dist/installer/phases/provider-detect.js +116 -6
  23. package/dist/installer/phases/provider-detect.js.map +1 -1
  24. package/dist/installer/phases/scaffold.js +1222 -12
  25. package/dist/installer/phases/scaffold.js.map +1 -1
  26. package/dist/installer/runtime/kimi.js +255 -0
  27. package/dist/installer/runtime/kimi.js.map +1 -0
  28. package/dist/installer/util/paths.js +12 -0
  29. package/dist/installer/util/paths.js.map +1 -1
  30. package/dist/installer/util/registry.js +234 -14
  31. package/dist/installer/util/registry.js.map +1 -1
  32. package/docs/README.md +1 -0
  33. package/docs/deployment.md +6 -7
  34. package/docs/getting-started.md +11 -7
  35. package/docs/installation.md +34 -16
  36. package/docs/plugin-architecture.md +11 -8
  37. package/docs/updating.md +21 -3
  38. package/docs/user-docs/cli-reference.md +43 -22
  39. package/docs/user-docs/codex-vs-claude-code.md +11 -9
  40. package/docs/user-docs/faq.md +1 -1
  41. package/docs/user-docs/getting-started-codex.md +5 -8
  42. package/docs/user-docs/getting-started-kimi.md +423 -0
  43. package/docs/user-docs/installation.md +49 -14
  44. package/docs/user-docs/quick-start.md +11 -8
  45. package/docs/windows.md +29 -4
  46. package/integration-contract.json +85 -13
  47. package/package.json +9 -5
  48. package/schemas/profile.v1.json +67 -5
  49. package/templates/codex-skills/batch-implement/SKILL.md +4 -0
  50. package/templates/commands/specrails/batch-implement.md +11 -0
  51. package/templates/gemini-commands/batch-implement.toml +4 -0
  52. package/templates/kimi/specrails/run-skill.mjs +3005 -0
  53. package/templates/kimi/specrails/vendor/js-yaml/LICENSE +21 -0
  54. package/templates/kimi/specrails/vendor/js-yaml/NOTICE.md +16 -0
  55. package/templates/kimi/specrails/vendor/js-yaml/js-yaml.mjs +3856 -0
  56. package/templates/profiles/kimi-default.json +15 -0
package/README.md CHANGED
@@ -4,8 +4,7 @@
4
4
  [![GitHub Stars](https://img.shields.io/github/stars/fjpulidop/specrails-core?style=social)](https://github.com/fjpulidop/specrails-core)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![npm downloads](https://img.shields.io/npm/dw/specrails-core.svg)](https://www.npmjs.com/package/specrails-core)
7
- [![Claude Code](https://img.shields.io/badge/Built%20for-Claude%20Code-blueviolet)](https://docs.anthropic.com/en/docs/claude-code)
8
- [![Codex โ€” Coming Soon](https://img.shields.io/badge/OpenAI%20Codex-Coming%20Soon%20(in%20lab)-lightgrey)](https://github.com/openai/codex)
7
+ [![AI providers](https://img.shields.io/badge/providers-Claude%20%7C%20Codex%20%7C%20Gemini%20%7C%20Kimi-6f42c1)](#provider-support)
9
8
 
10
9
  **Your agentic development team. From idea to production code.**
11
10
 
@@ -13,12 +12,12 @@ One command turns your repo into a spec-driven pipeline with a team of specializ
13
12
 
14
13
  ```bash
15
14
  npx specrails-core@latest init # install into the current repo
16
- /specrails:enrich # optional: deep codebase analysis
15
+ /specrails:enrich # Claude/Gemini (Kimi: /skill:specrails-enrich)
17
16
  ```
18
17
 
19
- > **Requirements:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), git, Node 20+. Cross-platform: macOS, Linux, Windows.
20
- >
21
- > **๐Ÿงช Codex (OpenAI) support โ€” Coming Soon:** We are testing Codex integration in our lab. Installation is disabled for now, but the feature will be available shortly. Follow the repo for updates.
18
+ > **Requirements:** one supported AI CLI, git, and Node 20.19.0+. Cross-platform:
19
+ > macOS, Linux, and Windows. Use `--provider claude|codex|gemini|kimi` to
20
+ > override auto-detection.
22
21
 
23
22
  ---
24
23
 
@@ -44,7 +43,8 @@ npx specrails-core@latest init
44
43
 
45
44
  The TUI asks you to pick a tier:
46
45
 
47
- - **Quick** (default) โ€” agents and commands installed straight to `.claude/`, ready to use immediately. No AI interaction.
46
+ - **Quick** (default) โ€” provider-native agents/skills and commands are installed
47
+ under `.claude/`, `.codex/`, `.gemini/`, or `.kimi-code/`. No AI interaction.
48
48
  - **Full** โ€” same as Quick plus `/specrails:enrich` (5-phase deep analysis: stack detection, VPC personas, competitive research). ~5 min.
49
49
 
50
50
  ```bash
@@ -61,15 +61,34 @@ That's it. The pipeline takes over.
61
61
 
62
62
  ---
63
63
 
64
+ ## Provider support
65
+
66
+ | Provider | Runtime command | Project surface | Workflow syntax |
67
+ |----------|-----------------|-----------------|-----------------|
68
+ | Claude Code | `claude` | `.claude/` | `/specrails:<command>` |
69
+ | Codex CLI | `codex` | `.codex/` | provider-native skills |
70
+ | Gemini CLI | `gemini` | `.gemini/` | `/specrails:<command>` |
71
+ | Kimi Code | managed Node skill runner โ†’ external `kimi -p` | `.kimi-code/` | `/skill:specrails-<command>` in the TUI |
72
+
73
+ Kimi is an external CLI dependency, just like the other providers. SpecRails
74
+ does not bundle a Kimi binary, start `kimi web`, or own a Kimi server. Install
75
+ Kimi Code separately, run `kimi login` once, then select it explicitly or let
76
+ the installer detect it. Parallel Kimi roles are submitted as one bounded
77
+ foreground wave; Core creates/reuses their git worktrees, attributes each
78
+ child stream, and waits for aggregate completionโ€”without a server. See the
79
+ [Kimi setup guide](./docs/user-docs/getting-started-kimi.md).
80
+
64
81
  ## What gets installed
65
82
 
66
83
  Everything lands in your repo โ€” nothing auto-updates, nothing phones home. You own it, you commit it.
67
84
 
68
85
  | Category | Location | Purpose |
69
86
  |----------|----------|---------|
70
- | **Agents** | `.claude/agents/` | 14 specialised AI agents |
71
- | **Commands** | `.claude/commands/specrails/` | 17 workflow commands (`/specrails:implement`, `/specrails:get-backlog-specs`, `/specrails:why`, โ€ฆ) |
72
- | **OpenSpec skills** | `.claude/commands/opsx/` | `/opsx:*` commands for spec artefacts |
87
+ | **Provider workflows** | `.claude/`, `.codex/`, `.gemini/`, or `.kimi-code/` | Provider-native SpecRails commands and role definitions |
88
+ | **Kimi workflow skills** | `.kimi-code/skills/specrails-*/SKILL.md` | `/skill:specrails-*` directory-form skills |
89
+ | **Kimi role skills** | `.kimi-code/skills/sr-*/SKILL.md` | Specialized role prompts used by Kimi workflows; Kimi discovers only direct skill children |
90
+ | **Kimi headless runner** | `.kimi-code/specrails/run-skill.mjs` | Materializes Kimi's native skill-activation prompt before launching the external CLI |
91
+ | **OpenSpec skills** | Provider-native skills directory | Structured proposal/design/tasks/apply workflows |
73
92
  | **Config** | `.specrails/config.yaml` | Stack, CI commands, git workflow |
74
93
  | **Personas** | `.specrails/personas/*.md` | VPC user profiles, generated from your users |
75
94
  | **Rules** | `.specrails/rules/*.md` | Per-layer coding conventions |
@@ -204,7 +223,8 @@ Profiles are declarative JSON files that tell `/specrails:implement` which agent
204
223
  When running the pipeline, the active profile is resolved in this order:
205
224
 
206
225
  1. `$SPECRAILS_PROFILE_PATH` environment variable (absolute path to a JSON snapshot)
207
- 2. `<cwd>/.specrails/profiles/project-default.json`
226
+ 2. Provider default: `<cwd>/.specrails/profiles/project-default.json` for
227
+ Claude, or `<cwd>/.specrails/profiles/kimi-default.json` for Kimi
208
228
  3. No profile โ€” legacy behavior (identical to pre-4.1.0)
209
229
 
210
230
  Tools such as [specrails-desktop](https://github.com/fjpulidop/specrails-desktop) set `$SPECRAILS_PROFILE_PATH` to a job-scoped snapshot so concurrent rails can run independent profiles.
@@ -240,6 +260,9 @@ The following paths are **reserved** โ€” `specrails-core update` will never crea
240
260
 
241
261
  - `.specrails/profiles/**` โ€” profile JSON files (yours and desktop-authored).
242
262
  - `.claude/agents/custom-*.md` โ€” your custom agents. Use the `custom-` prefix to opt in to this protection.
263
+ - `.kimi-code/skills/custom-*/**` โ€” your custom Kimi role skills. Pre-release
264
+ `.kimi-code/skills/rails/custom-*` roles are also reserved while Core safely
265
+ migrates them into this discoverable direct-child layout.
243
266
 
244
267
  This contract is what lets you safely hand-author (or let specrails-desktop author) profiles and custom agents without fear of the next `update` overwriting your work. Other paths managed by specrails-core (`.specrails/install-config.yaml`, `.specrails/specrails-version`, etc.) remain under update's control. Audited by `src/installer/__tests__/reserved-paths.test.ts` on every CI run.
245
268
 
@@ -287,10 +310,10 @@ Each persona scores features 0โ€“5. Features are ranked by score / effort ratio.
287
310
 
288
311
  | Tool | Required | Purpose |
289
312
  |------|----------|---------|
290
- | **Claude Code** | Yes | AI agent runtime |
291
- | **Codex CLI** _(coming soon โ€” in lab)_ | ๐Ÿงช Not yet | OpenAI Codex support is being tested in our lab and will be available shortly. |
313
+ | **One supported AI CLI** | Yes | Claude Code, Codex CLI, Gemini CLI, or Kimi Code |
314
+ | **Kimi Code 0.27.0+** | For Kimi projects | Install from the [official Kimi Code guide](https://www.kimi.com/code/docs/en/kimi-code-cli/guides/getting-started), then run `kimi login` |
292
315
  | **git** | Yes | Repository detection |
293
- | **Node 20+** | Yes | Needed for `npx specrails-core@latest init`. Cross-platform: macOS, Linux, Windows (10/11, x64 + ARM64 via emulation). |
316
+ | **Node 20.19.0+** | Yes | Needed for `npx specrails-core@latest init` (the floor required by the pinned OpenSpec 1.4.1 CLI). Cross-platform: macOS, Linux, Windows (10/11, x64 + ARM64 via emulation). |
294
317
  | **GitHub CLI** (`gh`) | Optional | Backlog sync to GitHub Issues, PR creation. Not needed with local tickets. |
295
318
  | **JIRA CLI** (`jira`) | Optional | Backlog sync to JIRA. Not needed with local tickets. |
296
319
 
@@ -324,7 +347,9 @@ Stack-agnostic. The `/specrails:enrich` wizard detects and adapts to whatever yo
324
347
  ## FAQ
325
348
 
326
349
  **Can I customise the agents after installation?**
327
- Yes. Everything under `.claude/` and `.specrails/` is yours to edit โ€” agent prompts, personas, rules, config. Commit what makes sense, gitignore what's transient.
350
+ Yes. Everything in the selected provider tree and `.specrails/` is yours to
351
+ edit. For Kimi, customize `.kimi-code/skills/`, `.kimi-code/rules/`, and the
352
+ managed block in `.kimi-code/AGENTS.md`; `custom-*` role skills are preserved.
328
353
 
329
354
  **Can I re-run the wizard?**
330
355
  Run `/specrails:enrich` again at any time to regenerate or update project data files. Re-running `npx specrails-core@latest init` refreshes the agents/commands without touching `.specrails/`.
@@ -336,13 +361,24 @@ Yes. Local tickets are the default and need no external tools. `/specrails:imple
336
361
  Not simultaneously for the same project โ€” backlog commands use one active provider at a time. You can migrate from GitHub Issues to local tickets using the [migration guide](./docs/migration-guide.md).
337
362
 
338
363
  **How much does it cost to run?**
339
- A full `/specrails:implement` cycle for one feature typically costs a few dollars in Claude API usage. The sr-product-manager uses Opus; all other agents use Sonnet or Haiku.
364
+ Cost depends on the selected provider, model, and workload. SpecRails does not
365
+ add a model surcharge. Kimi's stream output does not currently report a native
366
+ USD cost, so consumers must display it as unavailable rather than inventing an
367
+ estimate.
340
368
 
341
369
  **Does it work with private repos?**
342
- Yes. Everything runs locally through Claude Code. No external services beyond the model API.
343
-
344
- **How do I use specrails with Codex?**
345
- ๐Ÿงช **Coming Soon โ€” in lab.** OpenAI Codex support is currently being tested in our lab and will be available shortly. The install path will remain the same (`npx specrails-core@latest init --root-dir .`) โ€” the installer will detect Codex and adjust the agent configuration automatically. See [docs/user-docs/getting-started-codex.md](./docs/user-docs/getting-started-codex.md) for the preview documentation.
370
+ Yes. Orchestration runs through the selected local CLI. The provider still
371
+ connects to its model API and any MCP/integration endpoints you configure.
372
+
373
+ **How do I use specrails with Kimi?**
374
+ Install and authenticate Kimi Code, then run
375
+ `npx specrails-core@latest init --provider kimi`. Invoke the generated workflows
376
+ as `/skill:specrails-implement`, `/skill:specrails-enrich`, and so on in Kimi's
377
+ interactive TUI. Headless callers use the managed
378
+ `.kimi-code/specrails/run-skill.mjs` helper: Kimi 0.27 sends a slash command
379
+ passed directly to `kimi -p` as literal text, so the helper first renders the
380
+ same skill prompt as Kimi's native activation path and then starts external
381
+ `kimi -p --output-format stream-json`. No server installation is required.
346
382
 
347
383
  ---
348
384
 
@@ -6,8 +6,8 @@
6
6
  * in-process via the Node installer under dist/installer/. This file
7
7
  * only keeps logic that is local to the dispatcher:
8
8
  * - `profile validate` / `profile show` โ€” schema validation via ajv.
9
- * - `enrich` โ€” spawns Claude Code with
10
- * the /specrails:enrich command.
9
+ * - `enrich` โ€” launches the installed provider's
10
+ * native enrich workflow.
11
11
  * - `init` TUI short-circuit โ€” spawns tui-installer.mjs
12
12
  * then re-enters init with
13
13
  * --from-config.
@@ -31,7 +31,11 @@ const subcommand = args[0]
31
31
 
32
32
  // โ”€โ”€โ”€ Global --version / -V flag โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
33
33
 
34
- if (args.includes('--version') || args.includes('-V')) {
34
+ // Treat version as a GLOBAL flag only when it is the command itself. Offline
35
+ // lifecycle commands also carry a required `--version <target>` option; scanning
36
+ // the whole argv would intercept `swap-current --version 4.12.0` here and exit 0
37
+ // without ever validating or moving the framework pointer.
38
+ if (subcommand === '--version' || subcommand === '-V') {
35
39
  const pkg = require_(path.resolve(ROOT, 'package.json'))
36
40
  console.log(`specrails-core v${pkg.version}`)
37
41
  process.exit(0)
@@ -51,6 +55,7 @@ const KNOWN_SUBCOMMANDS = new Set([
51
55
  'update',
52
56
  'doctor',
53
57
  'install-framework',
58
+ 'swap-current',
54
59
  'assemble',
55
60
  'enrich',
56
61
  'version',
@@ -61,7 +66,7 @@ const KNOWN_SUBCOMMANDS = new Set([
61
66
  if (!KNOWN_SUBCOMMANDS.has(subcommand)) {
62
67
  console.error(`Unknown command: ${subcommand}\n`)
63
68
  console.error(
64
- 'Available commands: init, update, doctor, install-framework, assemble, enrich, version, profile, help',
69
+ 'Available commands: init, update, doctor, install-framework, swap-current, assemble, enrich, version, profile, help',
65
70
  )
66
71
  process.exit(1)
67
72
  }
@@ -90,25 +95,91 @@ if (subcommand === 'profile') {
90
95
  }
91
96
 
92
97
  // โ”€โ”€โ”€ enrich โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
93
- // Launches Claude Code with the /specrails:enrich slash command.
98
+ // Launches the configured provider's native enrich workflow.
94
99
 
95
100
  if (subcommand === 'enrich') {
96
- const enrichFlags = subargs.join(' ')
97
- const claudeCmd = `/specrails:enrich${enrichFlags ? ' ' + enrichFlags : ''}`
101
+ const workspace = await resolveEnrichWorkspace(process.cwd())
102
+ const { provider, model: explicitModel, workflowArgs } = resolveEnrichOptions(
103
+ workspace.codeRoot,
104
+ subargs,
105
+ workspace.artifactRoot,
106
+ )
107
+ const model =
108
+ explicitModel ??
109
+ resolveConfiguredEnrichModel(workspace.codeRoot, workspace.artifactRoot) ??
110
+ 'k3'
111
+ const enrichFlags = serializeWorkflowArgs(workflowArgs)
112
+ const launch =
113
+ provider === 'kimi'
114
+ ? {
115
+ command: process.execPath,
116
+ args: [
117
+ path.resolve(
118
+ workspace.artifactRoot,
119
+ '.kimi-code',
120
+ 'specrails',
121
+ 'run-skill.mjs',
122
+ ),
123
+ '--skill',
124
+ 'specrails-enrich',
125
+ '--model',
126
+ model,
127
+ '--add-dir',
128
+ workspace.codeRoot,
129
+ ...(enrichFlags ? ['--args', enrichFlags] : []),
130
+ ],
131
+ label: 'Kimi Code',
132
+ }
133
+ : provider === 'gemini'
134
+ ? {
135
+ command: 'gemini',
136
+ args: [
137
+ '-p',
138
+ `/specrails:enrich${enrichFlags ? ` ${enrichFlags}` : ''}`,
139
+ '--output-format',
140
+ 'stream-json',
141
+ ],
142
+ label: 'Gemini CLI',
143
+ }
144
+ : provider === 'codex'
145
+ ? {
146
+ command: 'codex',
147
+ args: [
148
+ 'exec',
149
+ `run enrich${enrichFlags ? ` ${enrichFlags}` : ''}`,
150
+ ],
151
+ label: 'Codex CLI',
152
+ }
153
+ : {
154
+ command: 'claude',
155
+ args: [
156
+ '--command',
157
+ `/specrails:enrich${enrichFlags ? ` ${enrichFlags}` : ''}`,
158
+ '--dangerously-skip-permissions',
159
+ ],
160
+ label: 'Claude Code',
161
+ }
98
162
  const result = spawnSync(
99
- 'claude',
100
- ['--command', claudeCmd, '--dangerously-skip-permissions'],
163
+ launch.command,
164
+ launch.args,
101
165
  {
102
166
  stdio: 'inherit',
103
- cwd: process.cwd(),
104
- shell: process.platform === 'win32',
167
+ cwd: provider === 'kimi' ? workspace.artifactRoot : process.cwd(),
168
+ env:
169
+ provider === 'kimi'
170
+ ? {
171
+ ...process.env,
172
+ SPECRAILS_REPO_DIR: workspace.codeRoot,
173
+ }
174
+ : process.env,
175
+ shell: process.platform === 'win32' && provider !== 'kimi',
105
176
  },
106
177
  )
107
178
  if (result.error) {
108
179
  console.error(
109
- '\nFailed to launch Claude CLI for enrich:',
180
+ `\nFailed to launch ${launch.label} for enrich:`,
110
181
  result.error.message,
111
- '\nEnsure Claude Code is installed: npm install -g @anthropic-ai/claude-code\n',
182
+ `\nEnsure the configured ${provider} provider is installed and initialized.\n`,
112
183
  )
113
184
  process.exit(1)
114
185
  }
@@ -144,6 +215,7 @@ if (subcommand === 'init') {
144
215
  const tuiArgs = [path.resolve(ROOT, 'bin', 'tui-installer.mjs'), rootDir]
145
216
  if (providerVal) tuiArgs.push('--provider', providerVal)
146
217
  else if (providerEqArg) tuiArgs.push(providerEqArg)
218
+ if (subargs.includes('--with-profiles')) tuiArgs.push('--with-profiles')
147
219
  const tuiResult = spawnSync('node', tuiArgs, {
148
220
  stdio: 'inherit',
149
221
  cwd: process.cwd(),
@@ -189,20 +261,25 @@ process.exit(0)
189
261
  // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
190
262
 
191
263
  function printUsage() {
192
- console.log(`specrails-core โ€” Agent Workflow System for Claude Code
264
+ console.log(`specrails-core โ€” Provider-independent AI agent workflow system
193
265
 
194
266
  Usage:
195
267
  specrails-core init [--root-dir <path>] [--yes|-y] [--no-tui] Install into a repository
196
268
  specrails-core update [--only <component>] [--dry-run] Update an existing installation
197
269
  specrails-core doctor Run health checks
198
- specrails-core enrich [--from-config <path>] Run /specrails:enrich via Claude CLI
270
+ specrails-core install-framework --framework-dir <path> --provider <value> --version <value>
271
+ Materialize an offline framework version
272
+ specrails-core swap-current --framework-dir <path> --version <value> [--providers <csv>]
273
+ Validate and atomically expose a framework version
274
+ specrails-core assemble --workspace <path> --framework-dir <path> Assemble a workspace from the framework
275
+ specrails-core enrich [--provider <value>] [workflow flags] Run the configured provider's enrich workflow
199
276
  specrails-core profile <validate|show> [<path>] Validate or pretty-print a profile JSON
200
277
  specrails-core version Show installed version
201
278
 
202
279
  Flags for init:
203
280
  --root-dir <path> Target repository path (default: current directory)
204
281
  --yes | -y Non-interactive; use defaults, skip TUI
205
- --provider <value> Force provider: claude (codex coming soon)
282
+ --provider <value> Force provider: claude, codex, gemini, or kimi
206
283
  --no-tui Skip TUI; use defaults / flags directly
207
284
  --from-config Skip TUI; use existing .specrails/install-config.yaml
208
285
 
@@ -212,6 +289,174 @@ Global flags:
212
289
  More info: https://github.com/fjpulidop/specrails-core`)
213
290
  }
214
291
 
292
+ function resolveEnrichOptions(cwd, argv, artifactRoot = cwd) {
293
+ const workflowArgs = []
294
+ let explicitProvider
295
+ let model
296
+ for (let index = 0; index < argv.length; index++) {
297
+ const token = argv[index]
298
+ if (token === '--provider' || token === '--model') {
299
+ const value = argv[++index]
300
+ if (!value) {
301
+ console.error(`${token} requires a value`)
302
+ process.exit(1)
303
+ }
304
+ if (token === '--provider') explicitProvider = value
305
+ else model = value
306
+ continue
307
+ }
308
+ if (token.startsWith('--provider=')) {
309
+ explicitProvider = token.slice('--provider='.length)
310
+ continue
311
+ }
312
+ if (token.startsWith('--model=')) {
313
+ model = token.slice('--model='.length)
314
+ continue
315
+ }
316
+ workflowArgs.push(token)
317
+ }
318
+
319
+ const supported = new Set(['claude', 'codex', 'gemini', 'kimi'])
320
+ if (explicitProvider && !supported.has(explicitProvider)) {
321
+ console.error(
322
+ `Unsupported provider "${explicitProvider}". Expected claude, codex, gemini, or kimi.`,
323
+ )
324
+ process.exit(1)
325
+ }
326
+
327
+ let provider = explicitProvider
328
+ if (!provider) {
329
+ for (const root of [cwd, artifactRoot]) {
330
+ const manifestPath = path.join(
331
+ root,
332
+ '.specrails',
333
+ 'specrails-manifest.json',
334
+ )
335
+ if (existsSync(manifestPath)) {
336
+ try {
337
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
338
+ if (supported.has(manifest.primary_provider)) {
339
+ provider = manifest.primary_provider
340
+ break
341
+ }
342
+ } catch {
343
+ // A malformed manifest is diagnosed by `specrails-core doctor`;
344
+ // enrich continues to config/directory fallback.
345
+ }
346
+ }
347
+ }
348
+ }
349
+ if (!provider) {
350
+ for (const root of [cwd, artifactRoot]) {
351
+ const configPath = path.join(root, '.specrails', 'install-config.yaml')
352
+ if (!existsSync(configPath)) continue
353
+ const match = readFileSync(configPath, 'utf8').match(
354
+ /^provider:\s*(claude|codex|gemini|kimi)\s*$/m,
355
+ )
356
+ if (match) {
357
+ provider = match[1]
358
+ break
359
+ }
360
+ }
361
+ }
362
+ if (!provider) {
363
+ provider =
364
+ ['claude', 'codex', 'gemini', 'kimi'].find((candidate) =>
365
+ existsSync(
366
+ path.join(
367
+ artifactRoot,
368
+ candidate === 'kimi' ? '.kimi-code' : `.${candidate}`,
369
+ ),
370
+ ),
371
+ ) ?? 'claude'
372
+ }
373
+ return { provider, model, workflowArgs }
374
+ }
375
+
376
+ async function resolveEnrichWorkspace(cwd) {
377
+ const registryModulePath = path.resolve(
378
+ ROOT,
379
+ 'dist',
380
+ 'installer',
381
+ 'util',
382
+ 'registry.js',
383
+ )
384
+ if (!existsSync(registryModulePath)) {
385
+ return { artifactRoot: cwd, codeRoot: cwd }
386
+ }
387
+ try {
388
+ const registry = await import(pathToFileURL(registryModulePath).href)
389
+ const resolution = registry.resolveArtifacts(cwd, { allocate: false })
390
+ return {
391
+ artifactRoot: path.resolve(resolution.artifactRoot),
392
+ codeRoot: path.resolve(resolution.codeRoot),
393
+ }
394
+ } catch {
395
+ // Doctor reports malformed registry state. Enrich retains the legacy
396
+ // in-repository fallback instead of guessing another relocated workspace.
397
+ return { artifactRoot: cwd, codeRoot: cwd }
398
+ }
399
+ }
400
+
401
+ function resolveConfiguredEnrichModel(codeRoot, artifactRoot) {
402
+ const activeProfile = process.env.SPECRAILS_PROFILE_PATH
403
+ if (activeProfile) {
404
+ const activePath = path.isAbsolute(activeProfile)
405
+ ? activeProfile
406
+ : path.resolve(codeRoot, activeProfile)
407
+ const activeModel = readProfileOrchestratorModel(activePath)
408
+ if (activeModel) return activeModel
409
+ }
410
+ const configCandidates = [
411
+ path.join(codeRoot, '.specrails', 'install-config.yaml'),
412
+ path.join(artifactRoot, '.specrails', 'install-config.yaml'),
413
+ ]
414
+ for (const configPath of configCandidates) {
415
+ if (!existsSync(configPath)) continue
416
+ try {
417
+ const config = require_('js-yaml').load(readFileSync(configPath, 'utf8'))
418
+ const configured = config?.models?.defaults?.model
419
+ if (typeof configured === 'string' && configured.trim() !== '') {
420
+ return configured.trim()
421
+ }
422
+ } catch {
423
+ // The installer/doctor owns full config diagnostics. Continue to a
424
+ // provider profile rather than extracting a value from malformed YAML.
425
+ }
426
+ }
427
+ const profileCandidates = [
428
+ path.join(artifactRoot, '.specrails', 'profiles', 'kimi-default.json'),
429
+ path.join(codeRoot, '.specrails', 'profiles', 'kimi-default.json'),
430
+ ]
431
+ for (const profilePath of profileCandidates) {
432
+ const configured = readProfileOrchestratorModel(profilePath)
433
+ if (configured) return configured
434
+ }
435
+ return null
436
+ }
437
+
438
+ function readProfileOrchestratorModel(profilePath) {
439
+ if (!existsSync(profilePath)) return null
440
+ try {
441
+ const profile = JSON.parse(readFileSync(profilePath, 'utf8'))
442
+ const configured = profile?.orchestrator?.model
443
+ return typeof configured === 'string' && configured.trim() !== ''
444
+ ? configured.trim()
445
+ : null
446
+ } catch {
447
+ // Profile validation provides the actionable error. Fall through.
448
+ return null
449
+ }
450
+ }
451
+
452
+ function serializeWorkflowArgs(argv) {
453
+ return argv
454
+ .map((value) =>
455
+ value === '' || /\s|["']/u.test(value) ? JSON.stringify(value) : value,
456
+ )
457
+ .join(' ')
458
+ }
459
+
215
460
  /**
216
461
  * Loads the compiled Node CLI (dist/installer/cli.js) and invokes its
217
462
  * `main` function with the given argv array. Propagates the returned
@@ -244,11 +489,33 @@ async function runProfile(subargs) {
244
489
  process.exit(1)
245
490
  }
246
491
 
492
+ const workspace = await resolveEnrichWorkspace(process.cwd())
247
493
  const resolveProfilePath = () => {
248
494
  if (pathArg) return path.resolve(pathArg)
249
495
  if (process.env.SPECRAILS_PROFILE_PATH) return path.resolve(process.env.SPECRAILS_PROFILE_PATH)
250
- const projectDefault = path.resolve(process.cwd(), '.specrails', 'profiles', 'project-default.json')
251
- if (existsSync(projectDefault)) return projectDefault
496
+ const configRoots = [workspace.codeRoot, workspace.artifactRoot]
497
+ let provider
498
+ for (const root of configRoots) {
499
+ const configPath = path.join(root, '.specrails', 'install-config.yaml')
500
+ if (!existsSync(configPath)) continue
501
+ const match = readFileSync(configPath, 'utf8').match(
502
+ /^provider:\s*(claude|codex|gemini|kimi)\s*$/m,
503
+ )
504
+ if (match) {
505
+ provider = match[1]
506
+ break
507
+ }
508
+ }
509
+ const names =
510
+ provider === 'kimi'
511
+ ? ['kimi-default.json', 'project-default.json']
512
+ : ['project-default.json', 'kimi-default.json']
513
+ for (const root of [workspace.artifactRoot, workspace.codeRoot]) {
514
+ for (const name of names) {
515
+ const candidate = path.join(root, '.specrails', 'profiles', name)
516
+ if (existsSync(candidate)) return candidate
517
+ }
518
+ }
252
519
  return null
253
520
  }
254
521
 
@@ -256,7 +523,9 @@ async function runProfile(subargs) {
256
523
  if (!profilePath) {
257
524
  console.error('No profile path given and none could be resolved.')
258
525
  console.error('Pass an explicit path or set SPECRAILS_PROFILE_PATH, or place a profile at')
259
- console.error(' .specrails/profiles/project-default.json')
526
+ console.error(
527
+ ' .specrails/profiles/project-default.json (or kimi-default.json for Kimi)',
528
+ )
260
529
  process.exit(1)
261
530
  }
262
531
  if (!existsSync(profilePath)) {
@@ -298,8 +567,12 @@ async function runProfile(subargs) {
298
567
  const schema = JSON.parse(readFileSync(schemaPath, 'utf8'))
299
568
  const ajv = new Ajv({ allErrors: true, strict: false })
300
569
  const validate = ajv.compile(schema)
570
+ const schemaValid = validate(profile)
571
+ const semanticErrors = schemaValid
572
+ ? validateProfileSemantics(profile)
573
+ : []
301
574
 
302
- if (validate(profile)) {
575
+ if (schemaValid && semanticErrors.length === 0) {
303
576
  console.log(`โœ“ ${profilePath} is a valid v1 profile.`)
304
577
  process.exit(0)
305
578
  }
@@ -307,5 +580,55 @@ async function runProfile(subargs) {
307
580
  for (const err of validate.errors || []) {
308
581
  console.error(` ${err.instancePath || '/'} ${err.message} (${JSON.stringify(err.params)})`)
309
582
  }
583
+ for (const err of semanticErrors) {
584
+ console.error(` ${err}`)
585
+ }
310
586
  process.exit(1)
311
587
  }
588
+
589
+ /**
590
+ * JSON Schema validates each profile entry in isolation. These invariants span
591
+ * multiple array entries and therefore require a second, semantic validation
592
+ * pass after AJV has established the profile's structural shape.
593
+ */
594
+ function validateProfileSemantics(profile) {
595
+ const errors = []
596
+ const firstAgentIndex = new Map()
597
+
598
+ for (const [index, agent] of profile.agents.entries()) {
599
+ const previous = firstAgentIndex.get(agent.id)
600
+ if (previous !== undefined) {
601
+ errors.push(
602
+ `/agents/${index}/id duplicates agent id "${agent.id}" first declared at /agents/${previous}/id`,
603
+ )
604
+ } else {
605
+ firstAgentIndex.set(agent.id, index)
606
+ }
607
+ }
608
+
609
+ const knownAgents = new Set(firstAgentIndex.keys())
610
+ const defaultIndices = []
611
+ for (const [index, rule] of profile.routing.entries()) {
612
+ if (!knownAgents.has(rule.agent)) {
613
+ errors.push(
614
+ `/routing/${index}/agent references unknown agent "${rule.agent}"`,
615
+ )
616
+ }
617
+ if (rule.default === true) {
618
+ defaultIndices.push(index)
619
+ }
620
+ }
621
+
622
+ if (defaultIndices.length > 1) {
623
+ errors.push(
624
+ `/routing must contain at most one default rule (found ${defaultIndices.length})`,
625
+ )
626
+ }
627
+ for (const index of defaultIndices) {
628
+ if (index !== profile.routing.length - 1) {
629
+ errors.push(`/routing/${index} default rule must be the last routing entry`)
630
+ }
631
+ }
632
+
633
+ return errors
634
+ }