chati-dev 4.5.13 → 4.5.15

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 (57) hide show
  1. package/framework/agents/build/dev.md +4 -5
  2. package/framework/agents/deploy/devops.md +9 -10
  3. package/framework/agents/discover/brief.md +7 -13
  4. package/framework/agents/discover/brownfield-wu.md +5 -11
  5. package/framework/agents/discover/greenfield-wu.md +7 -12
  6. package/framework/agents/plan/architect.md +1 -1
  7. package/framework/agents/plan/detail.md +5 -5
  8. package/framework/agents/plan/phases.md +4 -5
  9. package/framework/agents/plan/tasks.md +4 -5
  10. package/framework/agents/plan/ux-brand-architect.md +1 -1
  11. package/framework/agents/plan/ux.md +1 -1
  12. package/framework/agents/quality/qa-implementation.md +4 -5
  13. package/framework/agents/quality/qa-planning.md +4 -5
  14. package/framework/agents/quality/qa-visual.md +4 -4
  15. package/framework/agents/shared/visualizer.md +1 -1
  16. package/framework/config.yaml +5 -5
  17. package/framework/constitution.md +24 -26
  18. package/framework/context/governance.md +6 -6
  19. package/framework/context/root.md +4 -3
  20. package/framework/data/entity-registry.yaml +3 -3
  21. package/framework/data/model-limits.json +5 -2
  22. package/framework/hooks/model-governance.js +3 -2
  23. package/framework/hooks/prism-engine.js +1 -1
  24. package/framework/i18n/en.yaml +6 -2
  25. package/framework/i18n/es.yaml +6 -2
  26. package/framework/i18n/fr.yaml +6 -2
  27. package/framework/i18n/pt.yaml +6 -2
  28. package/framework/intelligence/context-engine.md +12 -16
  29. package/framework/manifest.json +63 -63
  30. package/framework/manifest.sig +1 -1
  31. package/framework/orchestrator/chati-router.js +40 -6
  32. package/framework/orchestrator/chati.md +12 -13
  33. package/framework/schemas/session.schema.json +1 -1
  34. package/package.json +1 -1
  35. package/src/config/context-file-generator.js +54 -246
  36. package/src/config/framework-adapter.js +45 -208
  37. package/src/context/bracket-tracker.js +3 -3
  38. package/src/context/engine.js +1 -1
  39. package/src/installer/core.js +190 -66
  40. package/src/installer/provider-overlay.js +1 -1
  41. package/src/installer/templates.js +55 -65
  42. package/src/installer-v2/index.js +2 -0
  43. package/src/memory/magic-docs.js +45 -26
  44. package/src/orchestrator/cli.js +50 -45
  45. package/src/orchestrator/handoff-engine.js +7 -5
  46. package/src/orchestrator/session-manager.js +24 -3
  47. package/src/terminal/cli-registry.js +5 -5
  48. package/src/terminal/prompt-builder.js +14 -10
  49. package/src/terminal/run-agent.js +10 -6
  50. package/src/terminal/run-parallel.js +6 -2
  51. package/src/terminal/run-team.js +7 -2
  52. package/src/terminal/spawner.js +34 -42
  53. package/src/utils/config-parser.js +2 -2
  54. package/src/utils/provider-limits.js +3 -1
  55. package/src/wizard/feedback.js +11 -0
  56. package/src/wizard/i18n.js +6 -2
  57. package/src/wizard/index.js +17 -7
@@ -38,13 +38,13 @@ After EVERY agent completes and the user approves:
38
38
 
39
39
  ## On Activation
40
40
 
41
- When the user invokes `/chati`, execute this sequence:
41
+ When the user invokes the provider-native Chati entry point, execute this sequence:
42
42
 
43
43
  ### Step 1: Load Context
44
44
 
45
45
  ```
46
46
  1. Read .chati/session.yaml (session state)
47
- 2. Read CLAUDE.md (project context)
47
+ 2. Read .chati/project-context.md (provider-neutral project context)
48
48
  3. Read chati.dev/constitution.md (if first run or FRESH bracket - governance rules)
49
49
  4. Read chati.dev/config.yaml (version info, provider overrides)
50
50
  5. Detect language from session.yaml → respond in that language
@@ -275,7 +275,7 @@ only what executes it, and it is invisible when the feature is off.
275
275
 
276
276
  ## Action: Autonomous Agent
277
277
 
278
- These agents run in separate Claude Code processes.
278
+ These agents run in separate processes using the exact provider and model binding returned by the router.
279
279
 
280
280
  1. Track context bracket internally - do NOT display it.
281
281
  2. Check `handoff_status.valid` from JSON:
@@ -309,10 +309,9 @@ If terminal spawning fails (CLI not found, system error), fall back **silently**
309
309
  6. Continue pipeline normally — the user should not notice any difference
310
310
  ```
311
311
 
312
- **NOTE:** A PostToolUse hook (`advance-trigger.js`) ALSO fires on handoff write,
313
- but ONLY when `execution_mode: autonomous` (Article XVII). In `interactive`
314
- (default), the hook is silent except on QA failure, and Claude's explicit
315
- advance call is the sole forward trigger.
312
+ **NOTE:** A provider-native post-tool hook may also fire on handoff write when
313
+ supported. In interactive mode, the explicit router advance call remains the
314
+ portable source of truth.
316
315
 
317
316
  If the advance response contains `"already_advanced": true`, the hook or a
318
317
  prior call already committed the transition — proceed silently using the
@@ -350,7 +349,7 @@ Planning phase agents (detail, architect, ux) run simultaneously.
350
349
 
351
350
  ## Action: Spawn Team (Article XXI)
352
351
 
353
- Agent Teams enable real-time peer communication between agents. The orchestrator acts as Team Lead, spawning teammates as native Claude Code sub-agents (Agent tool), and coordinating their work through a shared task list and filesystem-based mailbox.
352
+ Agent Teams enable peer communication through a shared task list and filesystem mailbox. A harness profile may use native sub-agents when available; otherwise the same team contract runs through Chati's provider-neutral parallel process runner.
354
353
 
355
354
  ### Step 1: Register Team in Session
356
355
 
@@ -553,7 +552,7 @@ Agent tool call:
553
552
  description: "QA-Visual - visual quality validation"
554
553
  prompt: [Read and follow .chati.dev/agents/quality/qa-visual.md.
555
554
  Phase 1: Run visual-qa.js script on all pages.
556
- Phase 2: Spawn the Visualizer sub-agent (model: sonnet) to analyze build screenshots and compare with references.
555
+ Phase 2: Spawn the Visualizer sub-agent using its router-provided binding to analyze build screenshots and compare with references.
557
556
  Phase 3-4: Verify animation libraries and brandbook compliance from report.json.
558
557
  Scoring: Run qa-visual-score CLI for deterministic score.
559
558
  Write your handoff to artifacts/handoffs/qa-visual-handoff.md when done.]
@@ -675,7 +674,7 @@ review, bounded rework, ClickUp projection and the Release Lane.
675
674
  1. Read the immutable handoff id returned in `rail_execute`.
676
675
  2. Run:
677
676
  ```
678
- node .chati.dev/_cli/bin/chati.js rail-next --handoff-id {handoff_id}
677
+ node .chati.dev/orchestrator/chati-router.js rail-next --handoff-id {handoff_id}
679
678
  ```
680
679
  3. Execute only a dependency-ready task. Use its selected provider and model
681
680
  binding from `.chati/v2/installation.json`. Never substitute an unselected
@@ -770,7 +769,7 @@ Session lock is written by `orchestrate init` and removed by `orchestrate exit`.
770
769
  1. Read `chati.dev/orchestrator/chati.md` and follow its routing logic for EVERY message
771
770
  2. Route ALL user messages through the current agent
772
771
  3. NEVER respond outside of the Chati.dev system - you ARE the orchestrator
773
- 4. NEVER act as generic Claude while session is locked
772
+ 4. NEVER act as a generic assistant while session is locked
774
773
  5. Off-scope requests go to Deviation Protocol, NOT outside the system
775
774
  6. The ONLY way to exit is via explicit exit commands
776
775
 
@@ -959,7 +958,7 @@ When the independent RAIL reviewer classifies a fault:
959
958
  ### ALLOWED
960
959
  - Read any file in the project (for state detection)
961
960
  - Write to .chati/session.yaml (session state)
962
- - Write to CLAUDE.local.md (session lock block)
961
+ - Reconcile every enabled harness's native session-lock file
963
962
  - Present status dashboards
964
963
  - Generate session digests
965
964
 
@@ -996,7 +995,7 @@ Level 2 - Escalate:
996
995
  3. Return to previous agent
997
996
 
998
997
  Level 3 - Session Recovery:
999
- session.yaml corrupted → attempt reconstruction from CLAUDE.md + artifacts
998
+ session.yaml corrupted → attempt reconstruction from .chati/project-context.md + artifacts
1000
999
  If reconstruction fails → suggest re-init preserving artifacts
1001
1000
 
1002
1001
  Level 4 - Graceful Degradation:
@@ -78,7 +78,7 @@
78
78
  "providers_enabled": {
79
79
  "type": "array",
80
80
  "items": { "type": "string" },
81
- "default": ["claude"],
81
+ "default": [],
82
82
  "description": "List of enabled CLI providers"
83
83
  },
84
84
  "current_agent": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.5.13",
3
+ "version": "4.5.15",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System - Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,93 +1,21 @@
1
1
  /**
2
- * @fileoverview Context file generator for multi-CLI support.
2
+ * @fileoverview Provider-native context generation from a neutral Chati core.
3
3
  *
4
- * When alternative CLI providers are enabled, generates provider-specific
5
- * context files (GEMINI.md, AGENTS.md) derived from CLAUDE.md content.
6
- * Constitution Article XIX context file generation is automatic.
7
- *
8
- * Provider conventions (verified Feb 2026):
9
- * - Gemini CLI: auto-loads GEMINI.md, uses .gemini/commands/*.toml, no rules/
10
- * - Codex CLI: auto-loads AGENTS.md, uses .agents/skills/chati/SKILL.md for $chati
4
+ * The canonical project context lives at .chati/project-context.md. Harness
5
+ * files consume that context through their native loading mechanisms. No
6
+ * provider file is derived from another provider's file.
11
7
  */
12
8
 
13
9
  import { existsSync, readFileSync, writeFileSync } from 'fs';
14
10
  import { join } from 'path';
15
- import { getEnabledNonClaudeProviders } from '../utils/config-parser.js';
11
+ import { parseProviderConfig } from '../utils/config-parser.js';
16
12
  import { resolveFrameworkDir } from '../utils/framework-dir.js';
13
+ import { HARNESS_PROFILES } from './framework-adapter.js';
17
14
 
18
- // ---------------------------------------------------------------------------
19
- // Replacement Maps
20
- // ---------------------------------------------------------------------------
21
-
22
- /**
23
- * Text replacements for Gemini CLI context file.
24
- * Transforms Claude Code-specific references into Gemini CLI equivalents.
25
- * Note: Gemini has no rules/ directory — references point to chati.dev/context/.
26
- */
27
- const GEMINI_REPLACEMENTS = [
28
- ['Claude Code', 'Gemini CLI'],
29
- ['claude code', 'Gemini CLI'],
30
- ['CLAUDE.md', 'GEMINI.md'],
31
- ['Claude.md', 'GEMINI.md'],
32
- ['.claude/commands/', '.gemini/commands/'],
33
- ['.claude/rules/chati/', '.gemini/context/'],
34
- ['.claude/rules/', '.gemini/context/'],
35
- ['.mcp.json', '.gemini/settings.json'],
36
- ['claude --print', 'gemini --prompt'],
37
- ['claude -p', 'gemini --prompt'],
38
- ['CLAUDE.local.md', '.gemini/session-lock.md'],
39
- ];
40
-
41
- /**
42
- * Sections to strip from Codex CLI context file.
43
- * Codex does not support hooks, so hook-related content is removed.
44
- */
45
- const CODEX_STRIP_PATTERNS = [
46
- /## (?:Hooks?|Hook System)[\s\S]*?(?=\n## |\n---|\n$)/gi,
47
- /- \*\*Hooks?\*\*:.*\n/gi,
48
- /hook[s]? \(.*?\)/gi,
49
- ];
50
-
51
- /**
52
- * Build text replacements for Codex CLI context file.
53
- * Note: Codex has no commands/ or rules/ directories — references point to the framework dir.
54
- *
55
- * @param {string} [frameworkDir='chati.dev'] - Resolved framework directory name (e.g. 'chati.dev' or '.chati.dev')
56
- * @returns {Array<[string, string]>} Replacement pairs
57
- */
58
- function buildCodexReplacements(frameworkDir = 'chati.dev') {
59
- return [
60
- ['Claude Code', 'Codex CLI'],
61
- ['claude code', 'Codex CLI'],
62
- ['CLAUDE.md', 'AGENTS.md'],
63
- ['Claude.md', 'AGENTS.md'],
64
- ['.claude/commands/', `${frameworkDir}/orchestrator/`],
65
- ['.claude/rules/chati/', `${frameworkDir}/context/`],
66
- ['.claude/rules/', `${frameworkDir}/context/`],
67
- ['.mcp.json', '.codex/config.toml'],
68
- ['claude --print', 'codex exec'],
69
- ['claude -p', 'codex exec'],
70
- ['CLAUDE.local.md', 'AGENTS.override.md'],
71
- ];
72
- }
73
-
74
- /**
75
- * Convert the interactive slash command without touching filesystem paths such
76
- * as `.chati.dev/orchestrator/chati.md`.
77
- */
78
- function adaptCodexInvocation(content) {
79
- return content.replace(/(?<![\w/])\/chati(?=\s|$|[`'".,;:!?)]|\b)/gm, '$chati');
80
- }
15
+ export const PROJECT_CONTEXT_PATH = join('.chati', 'project-context.md');
81
16
 
82
- // ---------------------------------------------------------------------------
83
- // Generators
84
- // ---------------------------------------------------------------------------
17
+ const CONTEXT_FILES = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
85
18
 
86
- /**
87
- * @import directives appended to GEMINI.md.
88
- * Gemini CLI resolves @import automatically, loading the full governance
89
- * context chain + session lock — equivalent to Claude Code's rules/ + CLAUDE.local.md.
90
- */
91
19
  const GEMINI_IMPORTS = [
92
20
  '@import .gemini/context/root.md',
93
21
  '@import .gemini/context/governance.md',
@@ -96,207 +24,87 @@ const GEMINI_IMPORTS = [
96
24
  '@import .gemini/session-lock.md',
97
25
  ];
98
26
 
99
- /**
100
- * Generate GEMINI.md content from CLAUDE.md content.
101
- *
102
- * Adapts the content by replacing Claude Code-specific references with
103
- * Gemini CLI equivalents, then appends @import directives so Gemini CLI
104
- * auto-loads governance rules and session lock (context parity with Claude).
105
- *
106
- * @param {string} content - Raw CLAUDE.md content
107
- * @returns {string} Adapted content for GEMINI.md
108
- */
27
+ /** Generate Gemini's native root context from the neutral Chati context. */
109
28
  export function generateGeminiMd(content) {
110
- let result = content;
111
-
112
- for (const [search, replace] of GEMINI_REPLACEMENTS) {
113
- result = result.replaceAll(search, replace);
114
- }
115
-
116
- // Prepend a header noting this file is auto-generated
117
- const header = [
118
- '<!-- Auto-generated by chati.dev from CLAUDE.md — do not edit manually -->',
29
+ return [
30
+ '<!-- Auto-generated by chati.dev from .chati/project-context.md. -->',
31
+ '',
32
+ content.trim(),
33
+ '',
34
+ ...GEMINI_IMPORTS,
119
35
  '',
120
36
  ].join('\n');
121
-
122
- // Append @import directives for context parity
123
- const imports = '\n' + GEMINI_IMPORTS.join('\n') + '\n';
124
-
125
- return header + result + imports;
126
37
  }
127
38
 
128
39
  /**
129
- * Generate AGENTS.md content from CLAUDE.md content.
130
- *
131
- * Simplifies the content for Codex CLI: strips hook references,
132
- * replaces CLI-specific paths, and produces a leaner context file
133
- * focused on code execution. When contextFiles are provided, inlines
134
- * governance/protocols/quality for full context parity with Claude Code.
135
- *
136
- * @param {string} content - Raw CLAUDE.md content
137
- * @param {object} [contextFiles] - Optional inline context { root, governance, protocols, quality }
138
- * @param {string} [frameworkDir='chati.dev'] - Resolved framework directory name
139
- * @returns {string} Adapted content for AGENTS.md
40
+ * Generate the shared AGENTS.md consumed by Codex and Grok. Governance is
41
+ * inlined because these harnesses do not share Gemini's import mechanism.
140
42
  */
141
- export function generateAgentsMd(content, contextFiles = null, frameworkDir = 'chati.dev') {
142
- let result = content;
143
- const replacements = buildCodexReplacements(frameworkDir);
144
-
145
- // Strip hook-related sections (Codex has no hooks support)
146
- for (const pattern of CODEX_STRIP_PATTERNS) {
147
- result = result.replace(pattern, '');
148
- }
149
-
150
- // Apply text replacements
151
- for (const [search, replace] of replacements) {
152
- result = result.replaceAll(search, replace);
153
- }
154
- result = adaptCodexInvocation(result);
155
-
156
- // Clean up any double blank lines left by stripping
157
- result = result.replace(/\n{3,}/g, '\n\n');
158
-
159
- // Prepend a header noting this file is auto-generated
160
- const header = [
161
- '<!-- Auto-generated by chati.dev from CLAUDE.md — do not edit manually -->',
43
+ export function generateAgentsMd(content, contextFiles = null, enabledProviders = []) {
44
+ const sections = [
45
+ '<!-- Auto-generated by chati.dev from .chati/project-context.md. -->',
162
46
  '',
163
- ].join('\n');
47
+ content.trim(),
48
+ ];
164
49
 
165
- result = header + result;
50
+ const runtimePointers = enabledProviders
51
+ .filter((provider) => provider === 'codex' || provider === 'grok')
52
+ .map((provider) => `- **${provider === 'codex' ? 'Codex' : 'Grok'} runtime lock**: \`${HARNESS_PROFILES[provider].sessionLockFile}\``);
53
+ if (runtimePointers.length > 0) {
54
+ sections.push('', '## Native Runtime State', '', ...runtimePointers);
55
+ }
166
56
 
167
- // Inline context files for full governance parity (Codex has no @import)
168
57
  if (contextFiles) {
169
- const sections = [];
170
- for (const [, fileContent] of Object.entries(contextFiles)) {
171
- if (fileContent) {
172
- // Adapt context for Codex (replace Claude-specific refs)
173
- let adapted = fileContent;
174
- for (const [search, replace] of replacements) {
175
- adapted = adapted.replaceAll(search, replace);
176
- }
177
- adapted = adaptCodexInvocation(adapted);
178
- sections.push(adapted.trim());
179
- }
180
- }
181
- if (sections.length > 0) {
182
- result += '\n---\n\n' + sections.join('\n\n---\n\n') + '\n';
58
+ for (const fileContent of Object.values(contextFiles)) {
59
+ if (fileContent) sections.push('', '---', '', fileContent.trim());
183
60
  }
184
61
  }
185
62
 
186
- return result;
63
+ return sections.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
187
64
  }
188
65
 
189
- // ---------------------------------------------------------------------------
190
- // Orchestrator
191
- // ---------------------------------------------------------------------------
192
-
193
- /**
194
- * Context file names that provide governance parity across CLIs.
195
- * These are read from chati.dev/context/ and inlined into AGENTS.md for Codex.
196
- */
197
- const CONTEXT_FILES = ['root.md', 'governance.md', 'protocols.md', 'quality.md'];
198
-
199
- /**
200
- * Generate context files for all enabled alternative providers.
201
- *
202
- * Reads config.yaml to determine which providers are enabled, reads
203
- * CLAUDE.md from the project root, and writes the appropriate context
204
- * files (GEMINI.md, AGENTS.md) when their providers are active.
205
- *
206
- * For Codex, governance context files are inlined into AGENTS.md to provide
207
- * the same rules awareness that Claude gets from .claude/rules/chati/.
208
- *
209
- * @param {string} projectDir - Project root directory
210
- * @param {string} [baseContent] - Optional base content (used when CLAUDE.md doesn't exist on disk)
211
- * @returns {{ generated: string[], skipped: string[], warning: string|null }}
212
- */
213
- export function generateContextFiles(projectDir, baseContent = null) {
66
+ /** Regenerate every enabled harness context from the neutral canonical file. */
67
+ export function generateContextFiles(projectDir, neutralContent = null) {
214
68
  const result = { generated: [], skipped: [], warning: null };
69
+ const canonicalPath = join(projectDir, PROJECT_CONTEXT_PATH);
215
70
 
216
- // Use provided base content or read CLAUDE.md from disk
217
- let claudeContent = baseContent;
218
- if (!claudeContent) {
219
- const claudeMdPath = join(projectDir, 'CLAUDE.md');
220
- if (!existsSync(claudeMdPath)) {
221
- result.warning = 'CLAUDE.md not found and no base content provided — skipping context file generation';
71
+ let content = neutralContent;
72
+ if (!content) {
73
+ if (!existsSync(canonicalPath)) {
74
+ result.warning = `${PROJECT_CONTEXT_PATH} not found and no neutral content provided`;
222
75
  return result;
223
76
  }
224
- claudeContent = readFileSync(claudeMdPath, 'utf-8');
77
+ content = readFileSync(canonicalPath, 'utf-8');
225
78
  }
226
79
 
227
- // Determine enabled providers from config.yaml
228
- const enabledProviders = resolveEnabledProviders(projectDir);
229
-
230
- // Read context files for inline injection (Codex)
80
+ const { enabled } = parseProviderConfig(projectDir);
231
81
  const contextFiles = readContextFilesFromDisk(projectDir);
232
82
 
233
- // Resolve framework directory for dynamic path generation
234
- const fwDir = resolveFrameworkDir(projectDir);
235
-
236
- // Provider-to-generator mapping
237
- const generators = {
238
- gemini: {
239
- filename: 'GEMINI.md',
240
- generate: (content) => generateGeminiMd(content),
241
- },
242
- codex: {
243
- filename: 'AGENTS.md',
244
- generate: (content) => generateAgentsMd(content, contextFiles, fwDir),
245
- },
246
- };
83
+ if (enabled.includes('gemini')) {
84
+ writeFileSync(join(projectDir, 'GEMINI.md'), generateGeminiMd(content), 'utf-8');
85
+ result.generated.push('GEMINI.md');
86
+ } else {
87
+ result.skipped.push('GEMINI.md');
88
+ }
247
89
 
248
- for (const [provider, config] of Object.entries(generators)) {
249
- if (enabledProviders.includes(provider)) {
250
- const outputPath = join(projectDir, config.filename);
251
- const content = config.generate(claudeContent);
252
- writeFileSync(outputPath, content, 'utf-8');
253
- result.generated.push(config.filename);
254
- } else {
255
- result.skipped.push(config.filename);
256
- }
90
+ if (enabled.includes('codex') || enabled.includes('grok')) {
91
+ writeFileSync(join(projectDir, 'AGENTS.md'), generateAgentsMd(content, contextFiles, enabled), 'utf-8');
92
+ result.generated.push('AGENTS.md');
93
+ } else {
94
+ result.skipped.push('AGENTS.md');
257
95
  }
258
96
 
259
97
  return result;
260
98
  }
261
99
 
262
- /**
263
- * Read context files from chati.dev/context/ for inline injection.
264
- * Returns null if no context files found (backward compat).
265
- *
266
- * @param {string} projectDir - Project root directory
267
- * @returns {object|null} { root, governance, protocols, quality } or null
268
- */
269
100
  function readContextFilesFromDisk(projectDir) {
270
- // Support both new (.chati.dev/) and legacy (chati.dev/) structures
271
- const newDir = join(projectDir, '.chati.dev', 'context');
272
- const legacyDir = join(projectDir, 'chati.dev', 'context');
273
- const contextDir = existsSync(newDir) ? newDir : legacyDir;
101
+ const contextDir = join(projectDir, resolveFrameworkDir(projectDir), 'context');
274
102
  if (!existsSync(contextDir)) return null;
275
103
 
276
104
  const files = {};
277
- let found = false;
278
105
  for (const file of CONTEXT_FILES) {
279
106
  const filePath = join(contextDir, file);
280
- if (existsSync(filePath)) {
281
- const key = file.replace('.md', '');
282
- files[key] = readFileSync(filePath, 'utf-8');
283
- found = true;
284
- }
107
+ if (existsSync(filePath)) files[file.replace('.md', '')] = readFileSync(filePath, 'utf-8');
285
108
  }
286
- return found ? files : null;
287
- }
288
-
289
- // ---------------------------------------------------------------------------
290
- // Helpers
291
- // ---------------------------------------------------------------------------
292
-
293
- /**
294
- * Resolve which alternative providers are enabled from config.yaml.
295
- * Delegates to shared config parser utility.
296
- *
297
- * @param {string} projectDir - Project root directory
298
- * @returns {string[]} List of enabled non-Claude provider names
299
- */
300
- function resolveEnabledProviders(projectDir) {
301
- return getEnabledNonClaudeProviders(projectDir);
109
+ return Object.keys(files).length > 0 ? files : null;
302
110
  }