specrails-core 6.2.2 → 6.3.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.
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import yaml from 'js-yaml';
3
3
  import { FilesystemError, InstallerError } from '../util/errors.js';
4
4
  import { pathExists, readTextFile, writeFileLf } from '../util/fs.js';
5
+ import { warn } from '../util/logger.js';
5
6
  const PROVIDER_DEFAULT_MODELS = {
6
7
  claude: 'sonnet',
7
8
  codex: 'gpt-5.5-mini',
@@ -25,7 +26,8 @@ const SAFE_KIMI_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._/:-]{0,127}$/;
25
26
  * Resolve a preset into provider-native identifiers. Kimi currently exposes no
26
27
  * Core-defined cost/capability tier mapping, so every named preset resolves to
27
28
  * its explicit `k3` default. User-supplied exact identifiers are handled later
28
- * and are never interpreted as Claude aliases.
29
+ * and are never interpreted as Claude aliases. No preset carries per-role
30
+ * overrides: Core ships no role ids, so `models.overrides` is Desktop's to fill.
29
31
  */
30
32
  export function resolveProviderModelConfig(provider, preset = 'balanced') {
31
33
  if (provider === 'claude') {
@@ -33,11 +35,7 @@ export function resolveProviderModelConfig(provider, preset = 'balanced') {
33
35
  return { preset, defaults: { model: 'haiku' }, overrides: {} };
34
36
  }
35
37
  if (preset === 'max') {
36
- return {
37
- preset,
38
- defaults: { model: 'sonnet' },
39
- overrides: { 'sr-architect': 'opus' },
40
- };
38
+ return { preset, defaults: { model: 'sonnet' }, overrides: {} };
41
39
  }
42
40
  }
43
41
  return {
@@ -109,13 +107,18 @@ export function validateInstallConfig(raw) {
109
107
  // NOTE: a legacy `tier` field (`full`/`quick`) may still be present in pre-v5
110
108
  // configs. It is intentionally ignored (not rejected and not carried forward)
111
109
  // — v5 has no install tiers.
110
+ // The `agents` section is OPTIONAL and deprecated: roles are runtime-defined
111
+ // since Core 6.3. When present it is still validated for shape (safe ids, no
112
+ // overlap, known preset) so a malformed file fails fast, and a non-empty
113
+ // selection logs one warning instead of being rejected — Desktop builds
114
+ // paired with an older Core still write it.
112
115
  const agents = doc.agents;
113
116
  let selectedAgents = [];
114
117
  let excludedAgents;
115
- if (!agents || typeof agents !== 'object') {
116
- errors.push(`missing required 'agents' section with 'selected' list`);
118
+ if (doc.agents !== undefined && (!agents || typeof agents !== 'object' || Array.isArray(agents))) {
119
+ errors.push(`'agents' must be a mapping`);
117
120
  }
118
- else {
121
+ else if (agents) {
119
122
  if (!Array.isArray(agents.selected)) {
120
123
  errors.push(`'agents.selected' must be a list`);
121
124
  }
@@ -182,6 +185,10 @@ export function validateInstallConfig(raw) {
182
185
  if (errors.length > 0) {
183
186
  throw new InvalidConfigError(errors);
184
187
  }
188
+ if (selectedAgents.length > 0) {
189
+ warn(`install-config.yaml 'agents.selected' is ignored since Core 6.3: roles are ` +
190
+ `runtime-defined and no role file is installed (${selectedAgents.join(', ')}).`);
191
+ }
185
192
  const preset = (modelPreset ?? agents?.preset ?? 'balanced');
186
193
  const resolvedModels = resolveProviderModelConfig(provider, preset);
187
194
  if (typeof modelDefaults?.model === 'string') {
@@ -193,18 +200,18 @@ export function validateInstallConfig(raw) {
193
200
  const result = {
194
201
  version: 1,
195
202
  provider,
196
- agents: {
197
- selected: selectedAgents,
198
- },
199
203
  models: resolvedModels,
200
204
  };
201
205
  // NOTE: a legacy `agent_teams` field may still be present in older configs.
202
206
  // It is intentionally ignored (not rejected) for backward compatibility.
203
- if (agents?.preset !== undefined) {
204
- result.agents.preset = agents.preset;
205
- }
206
- if (excludedAgents !== undefined) {
207
- result.agents.excluded = excludedAgents;
207
+ if (agents) {
208
+ result.agents = { selected: selectedAgents };
209
+ if (agents.preset !== undefined) {
210
+ result.agents.preset = agents.preset;
211
+ }
212
+ if (excludedAgents !== undefined) {
213
+ result.agents.excluded = excludedAgents;
214
+ }
208
215
  }
209
216
  return result;
210
217
  }
@@ -1,22 +1,4 @@
1
1
  import type { Provider } from './provider-detect.js';
2
- /**
3
- * The three baseline agents — the COMPLETE set of agents the installer
4
- * ships. The implement pipeline depends on all three. Any additional agent
5
- * comes from a user-authored profile (`custom-*`), never the installer.
6
- *
7
- * Mirrors the required built-in roles in schemas/agent-runtime.schema.json
8
- * (`agents`, with the sr- prefix in installed files); keep both in sync.
9
- */
10
- export declare const CORE_AGENTS: Set<string>;
11
- /**
12
- * Rewrite every literal `Skill("opsx:<id>"[, …])` call in a Claude-authored agent
13
- * body into the Gemini `activate_skill(name="…")` form. Positional skill input
14
- * (e.g. `"<specName>"`) is dropped because `activate_skill` takes only `name` and
15
- * the surrounding persona prose already carries the context. Unknown ids are left
16
- * untouched (better a visible stale ref than a silent `name="undefined"`). This
17
- * runs ONLY on the gemini render path; Claude/Codex keep the `Skill(...)` form.
18
- */
19
- export declare function translateOpsxSkillCallsForGemini(body: string): string;
20
2
  /**
21
3
  * Port shared Claude-authored prose to Kimi's directory-skill contract.
22
4
  *
@@ -28,7 +10,6 @@ export declare function translateOpsxSkillCallsForGemini(body: string): string;
28
10
  * This is render-only; canonical Claude/Codex/Gemini templates stay unchanged.
29
11
  */
30
12
  export declare function translateClaudeTextForKimi(body: string): string;
31
- export declare function geminiAgentLimitMetadata(env?: NodeJS.ProcessEnv): string[];
32
13
  /**
33
14
  * Phase 2 + Phase 3 of init:
34
15
  * - Detect prior installation state (.claude/.codex/openspec already present).
@@ -37,9 +18,11 @@ export declare function geminiAgentLimitMetadata(env?: NodeJS.ProcessEnv): strin
37
18
  * staging dir that placement copies from and `update` diffs against).
38
19
  * - Ensure `.gitignore` excludes the runtime artefacts.
39
20
  *
40
- * Placement (`placeArtefacts`) then copies the staged templates directly into
41
- * the user's live `.claude/agents/` and `.claude/commands/specrails/` dirs so
42
- * the installer finishes in one pass — no follow-up wizard required.
21
+ * Placement (`placeArtefacts`) then renders the staged command templates
22
+ * directly into the user's live provider dir (`.claude/commands/specrails/`,
23
+ * `.codex/skills/`, …) so the installer finishes in one pass — no follow-up
24
+ * wizard required. Roles are never placed: the programmatic runtime defines
25
+ * them (`src/agent-runtime/prompts.ts`).
43
26
  */
44
27
  interface ScaffoldInput {
45
28
  /** Absolute path to the specrails-core package (installed via npx). */
@@ -60,25 +43,6 @@ interface ScaffoldInput {
60
43
  provider: Provider;
61
44
  /** Derived directory name (`.claude` or `.codex`). */
62
45
  providerDir: string;
63
- /** Optional explicit allow-list used by config-driven installs. */
64
- selectedAgents?: string[];
65
- /**
66
- * When false, the static-placement helpers do NOT create the per-workspace
67
- * mutable seeds (agent-memory dirs, gemini headless acknowledgments). Used by
68
- * `installFramework` so the SHARED framework copy stays purely provider-static
69
- * — the project layer is seeded separately by `assembleProjectWorkspace`.
70
- * Defaults to true (legacy in-place behaviour for `scaffoldInstallation`).
71
- */
72
- seedProjectDirs?: boolean;
73
- /**
74
- * When true, place EVERY agent template (the full superset) regardless of
75
- * `selectedAgents`. Used by `installFramework` so
76
- * the SHARED framework store is a superset that ANY project's selection can
77
- * link from — per-project agent filtering then happens at the workspace LINK
78
- * step (`linkAgentFiles`), not at materialization. Defaults to false (legacy
79
- * selection-honouring placement for in-place `scaffoldInstallation`).
80
- */
81
- materializeAllAgents?: boolean;
82
46
  }
83
47
  interface ScaffoldResult {
84
48
  existingSetup: boolean;
@@ -106,8 +70,6 @@ interface InstallFrameworkInput {
106
70
  providerDir: string;
107
71
  /** Framework version (the `<version>/` segment). */
108
72
  version: string;
109
- /** Optional explicit agent allow-list (kept for parity with scaffold). */
110
- selectedAgents?: string[];
111
73
  }
112
74
  interface InstallFrameworkResult {
113
75
  /** `<frameworkDir>/<version>/<providerDir>` — root of the static subtree. */
@@ -130,8 +92,8 @@ export declare function frameworkMaterializationProblem(versionDir: string, vers
130
92
  * `<frameworkDir>/<version>/<providerDir>/` (+ `<version>/setup-templates/`).
131
93
  * Idempotent: when the providerDir already exists with a matching stamp it is a
132
94
  * no-op (the second workspace assemble re-uses the same copy). Writes NO
133
- * per-workspace state (no agent-memory, no acks, no project-named instruction
134
- * files) — those are seeded by `assembleProjectWorkspace`.
95
+ * per-workspace state (no project-named instruction files) — those are seeded
96
+ * by `assembleProjectWorkspace`.
135
97
  */
136
98
  export declare function installFramework(input: InstallFrameworkInput): InstallFrameworkResult;
137
99
  /**
@@ -150,26 +112,18 @@ interface AssembleProjectWorkspaceInput {
150
112
  providerDir: string;
151
113
  /** Framework version (used for the manifest record). */
152
114
  version: string;
153
- /** The user's real repo (drives PROJECT_NAME + gemini ack keying). */
115
+ /** The user's real repo (drives PROJECT_NAME). */
154
116
  codeRoot: string;
155
117
  /** specrails-core package dir (for the manifest hash sources). */
156
118
  scriptDir: string;
157
119
  /**
158
- * Optional agent allow-list. Drives BOTH which framework agents are LINKED
159
- * into the workspace (`linkAgentFiles`) AND which agent-memory dirs are seeded.
160
- * Undefined = the CORE trio only (the lean default). The SHARED framework store
161
- * is always the full superset; this is where per-project filtering happens.
162
- */
163
- selectedAgents?: string[];
164
- /**
165
- * When true, the static provider subtrees (`agents`/`commands`/`skills`/`rules`)
120
+ * When true, the static provider subtrees (`commands`/`skills`/`specrails`)
166
121
  * and the settings file are COPIED as real files from the framework store into
167
122
  * the workspace instead of SYMLINKED. Used by the in-repo standalone install
168
123
  * (`init`/`update` with `artifactRoot === codeRoot`) so the repo gets real,
169
124
  * committable files — a symlink into `$HOME/.specrails/framework` would be
170
125
  * invisible to a standalone user's `claude`/`codex`/`gemini`/`kimi` running in the
171
- * repo. The PROJECT layer (agent-memory, manifest, instruction files) is real
172
- * either way. Defaults to false (relocated workspaces symlink — the desktop /
126
+ * repo. The PROJECT layer (manifest, instruction files) is real either way. Defaults to false (relocated workspaces symlink — the desktop /
173
127
  * `--relocate` path).
174
128
  */
175
129
  copyStatics?: boolean;
@@ -177,34 +131,28 @@ interface AssembleProjectWorkspaceInput {
177
131
  interface AssembleProjectWorkspaceResult {
178
132
  /** Per-linked-subtree mechanism, for diagnostics (copy-fallback loses O(1) swap). */
179
133
  links: Record<string, 'symlink' | 'junction' | 'copy'>;
180
- /** Agent ids whose memory dirs were seeded as real writable dirs. */
181
- seededMemoryAgents: string[];
182
134
  }
183
135
  /**
184
136
  * Assemble a project workspace with NO network and NO re-materialization: (a)
185
- * SYMLINK the static providerDir subtrees from `<frameworkDir>/current/
186
- * <providerDir>/` into `<workspace>/<providerDir>/`, then (b) seed the PROJECT
187
- * layer as real writable files (agent-memory dirs, the manifest, project-named
188
- * instruction/settings files, gemini headless acks re-hashed against the LINKED
189
- * files). `agent-memory/` is NEVER linked.
137
+ * prune the framework-owned `sr-*` role artifacts an older Core linked or
138
+ * copied into the workspace, (b) SYMLINK the static providerDir subtrees from
139
+ * `<frameworkDir>/current/<providerDir>/` into `<workspace>/<providerDir>/`,
140
+ * then (c) seed the PROJECT layer as real writable files (the manifest,
141
+ * project-named instruction/settings files). Reserved `custom-*` files are
142
+ * never touched.
190
143
  */
191
144
  export declare function assembleProjectWorkspace(input: AssembleProjectWorkspaceInput): AssembleProjectWorkspaceResult;
192
145
  /**
193
- * Pre-acknowledge the generated gemini subagents so they load in HEADLESS
194
- * (`gemini -p`) runs. gemini 0.46+ DISCOVERS `.gemini/agents/*.md` but only
195
- * ENABLES a project's custom agents after the interactive "New Agents Discovered
196
- * → Acknowledge and Enable" prompt — which never fires headless, so
197
- * `invoke_agent sr-architect` returns "Subagent not found" and the implement
198
- * orchestrator silently falls back to a generic agent (the specialised personas
199
- * never run, the pipeline degrades). The acknowledgment is a user-global file
200
- * `~/.gemini/acknowledgments/agents.json` shaped
201
- * `{ [projectRoot]: { [agentName]: <sha256-hex of the agent .md file> } }`
202
- * (hash algorithm verified empirically against gemini 0.47 = sha256 of the full
203
- * file). Writing it at install time makes the freshly-generated agents trusted
204
- * with no prompt, for both `gemini` CLI and the desktop's headless spawns. The
205
- * file is MERGED — other projects' and other agents' entries are preserved.
206
- * Best-effort: any failure is swallowed by the caller (agents still work once
207
- * acknowledged interactively).
146
+ * Remove the role artifacts an older Core linked or copied into a workspace:
147
+ * - claude, gemini: `agents/sr-*.md` (symlink or copy-fallback regular file)
148
+ * - codex: `skills/rails/sr-*` and the `rails/` container once empty
149
+ * - kimi: `skills/sr-*`
150
+ *
151
+ * Keyed strictly by NAME, never by content: `custom-*` (the reserved
152
+ * user/Desktop region) and any unknown name are left byte-untouched. A
153
+ * subtree that is itself a symlink into the shared framework store is skipped —
154
+ * the store is Core-owned and rebuilt by `installFramework`, so pruning through
155
+ * the link would only reach into another version's files.
208
156
  */
209
- export declare function writeGeminiAgentAcknowledgments(repoRoot: string, agentIds: string[], agentsBaseDir?: string): void;
157
+ export declare function pruneStaleRoleArtifacts(workspaceProviderDir: string, provider: Provider): void;
210
158
  export {};