chati-dev 4.5.13 → 4.5.14

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 (55) 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 +2 -2
  25. package/framework/i18n/es.yaml +2 -2
  26. package/framework/i18n/fr.yaml +2 -2
  27. package/framework/i18n/pt.yaml +2 -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/i18n.js +2 -2
@@ -5,9 +5,9 @@ import { fileURLToPath } from 'url';
5
5
  import { createRequire } from 'module';
6
6
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
7
7
  import { mergeClaudeMCPConfig, upsertManagedMcpToml } from '../config/mcp-configs.js';
8
- import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGrokRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
8
+ import { generateSessionYaml, generateConfigYaml, generateProjectContext, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGrokRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
9
9
  import { generateContextFiles } from '../config/context-file-generator.js';
10
- import { adaptFrameworkFile, ADAPTABLE_FILES } from '../config/framework-adapter.js';
10
+ import { adaptFrameworkFile, ADAPTABLE_FILES, stripLegacyBuildSurface } from '../config/framework-adapter.js';
11
11
  import { generateClaudeSettings } from '../config/claude-settings-generator.js';
12
12
  import { generateProviderOverlays } from './provider-overlay.js';
13
13
  import { applyInstalledPathReplacement } from './path-replacement.js';
@@ -27,6 +27,9 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
27
27
  */
28
28
  export async function installFramework(config) {
29
29
  const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, allProviders } = config;
30
+ const enabledProviders = allProviders?.length
31
+ ? [...new Set(allProviders)]
32
+ : [...new Set((selectedIDEs || []).map(ide => IDE_TO_PROVIDER[ide]).filter(Boolean))];
30
33
  // Always derive version from package.json (single source of truth)
31
34
  let { version } = config;
32
35
  if (!version) {
@@ -58,7 +61,7 @@ export async function installFramework(config) {
58
61
  if (!existsSync(sessionPath)) {
59
62
  writeFileSync(
60
63
  sessionPath,
61
- generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, allProviders }),
64
+ generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, allProviders: enabledProviders }),
62
65
  'utf-8'
63
66
  );
64
67
  }
@@ -128,17 +131,17 @@ export async function installFramework(config) {
128
131
  createDir(join(memoriesBase, dir));
129
132
  }
130
133
 
131
- // Copy the canonical framework as a provider-neutral fallback. Every enabled
132
- // CLI receives its own explicit adapted overlay below.
133
- copyFrameworkFiles(frameworkDir, 'claude');
134
+ // Copy the provider-neutral canonical framework. Every enabled CLI receives
135
+ // an explicit native overlay below, but no provider owns the source core.
136
+ copyFrameworkFiles(frameworkDir, null);
134
137
 
135
138
  // Generate symmetric provider overlays. No CLI is permanently primary.
136
- if (allProviders && allProviders.length > 0) {
137
- generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, allProviders);
139
+ if (enabledProviders.length > 0) {
140
+ generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, enabledProviders);
138
141
  }
139
142
 
140
143
  if (isRuntimeV2) {
141
- pruneLegacyExecutionSurface(frameworkDir, allProviders || []);
144
+ pruneLegacyExecutionSurface(frameworkDir, enabledProviders);
142
145
  pruneLegacyRegistryEntities(frameworkDir);
143
146
  }
144
147
 
@@ -179,7 +182,7 @@ export async function installFramework(config) {
179
182
  // Write config.yaml
180
183
  writeFileSync(
181
184
  join(frameworkDir, 'config.yaml'),
182
- generateConfigYaml({ version, projectType, language, selectedIDEs, allProviders, runtimeVersion: config.runtimeVersion }),
185
+ generateConfigYaml({ version, projectType, language, selectedIDEs, allProviders: enabledProviders, runtimeVersion: config.runtimeVersion }),
183
186
  'utf-8'
184
187
  );
185
188
 
@@ -187,7 +190,7 @@ export async function installFramework(config) {
187
190
  // session becomes that session's orchestrator.
188
191
  for (const ideKey of selectedIDEs) {
189
192
  const ideProvider = IDE_TO_PROVIDER[ideKey];
190
- const hasProviderOverlay = ideProvider && allProviders?.includes(ideProvider);
193
+ const hasProviderOverlay = ideProvider && enabledProviders.includes(ideProvider);
191
194
  const orchestratorPath = hasProviderOverlay
192
195
  ? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
193
196
  : '.chati.dev/orchestrator/chati.md';
@@ -200,10 +203,30 @@ export async function installFramework(config) {
200
203
  }
201
204
 
202
205
  const hasClaude = selectedIDEs.includes('claude-code');
203
- const hasNonClaude = selectedIDEs.some(ide => ide !== 'claude-code');
204
-
205
- // 4. Generate base content (used as source for all provider context files)
206
- const baseContent = generateClaudeMd({ projectName, projectType, language });
206
+ // 4. Generate the provider-neutral project context. Harness files consume
207
+ // this artifact and never become the source for one another.
208
+ const contextConfig = { projectName, projectType, language };
209
+ const projectContext = generateProjectContext(contextConfig);
210
+ const projectContextPath = join(targetDir, '.chati', 'project-context.md');
211
+ const legacyClaudePath = join(targetDir, 'CLAUDE.md');
212
+ const legacyClaudeContent = existsSync(legacyClaudePath)
213
+ ? readFileSync(legacyClaudePath, 'utf-8')
214
+ : null;
215
+ const installedProjectContext = existsSync(projectContextPath)
216
+ ? mergeProjectContext(readFileSync(projectContextPath, 'utf-8'), projectContext)
217
+ : seedProjectContextFromLegacyClaude(projectContext, legacyClaudeContent);
218
+ writeFileSync(projectContextPath, installedProjectContext, 'utf-8');
219
+
220
+ // Once legacy Chati state has been promoted into the neutral context, do
221
+ // not leave a frozen second source of truth behind just because Claude is
222
+ // not one of the selected harnesses. User-authored CLAUDE.md content remains
223
+ // untouched; only recognized legacy Chati state sections are removed.
224
+ if (!hasClaude && legacyClaudeContent && looksLikeLegacyChatiContext(legacyClaudeContent)) {
225
+ const sanitizedClaude = stripLegacyStateSections(legacyClaudeContent);
226
+ if (sanitizedClaude !== legacyClaudeContent) {
227
+ writeFileSync(legacyClaudePath, sanitizedClaude, 'utf-8');
228
+ }
229
+ }
207
230
 
208
231
  // 5. Claude Code specific: .claude/rules/chati/, CLAUDE.md, CLAUDE.local.md
209
232
  if (hasClaude) {
@@ -223,19 +246,18 @@ export async function installFramework(config) {
223
246
  // Preserve existing CLAUDE.md on reinstall — user or magic-docs may have
224
247
  // edited it. Section-aware merge if exists; fresh write if not.
225
248
  const claudeMdPath = join(targetDir, 'CLAUDE.md');
249
+ const claudeContent = generateClaudeMd(contextConfig);
226
250
  if (existsSync(claudeMdPath)) {
227
251
  const existingClaudeMd = readFileSync(claudeMdPath, 'utf-8');
228
- writeFileSync(claudeMdPath, mergeClaudeMd(existingClaudeMd, baseContent), 'utf-8');
252
+ writeFileSync(claudeMdPath, mergeClaudeMd(existingClaudeMd, claudeContent), 'utf-8');
229
253
  } else {
230
- writeFileSync(claudeMdPath, baseContent, 'utf-8');
254
+ writeFileSync(claudeMdPath, claudeContent, 'utf-8');
231
255
  }
232
256
  writeFileSync(join(targetDir, 'CLAUDE.local.md'), generateClaudeLocalMd(), 'utf-8');
233
257
  }
234
258
 
235
- // 6. Non-Claude providers: generate context files (GEMINI.md, AGENTS.md) from base content
236
- if (hasNonClaude) {
237
- generateContextFiles(targetDir, baseContent);
238
- }
259
+ // 6. Generate every non-Claude native context from the neutral artifact.
260
+ generateContextFiles(targetDir, installedProjectContext);
239
261
 
240
262
  // 7. Update .gitignore with runtime session lock files
241
263
  updateGitignore(targetDir, selectedIDEs);
@@ -292,6 +314,12 @@ function pruneLegacyExecutionSurface(frameworkDir, providers) {
292
314
  }
293
315
  }
294
316
  }
317
+ const orchestratorPath = join(root, 'orchestrator', 'chati.md');
318
+ if (existsSync(orchestratorPath)) {
319
+ const content = readFileSync(orchestratorPath, 'utf-8');
320
+ const pruned = stripLegacyBuildSurface(content);
321
+ if (pruned !== content) writeFileSync(orchestratorPath, pruned, 'utf-8');
322
+ }
295
323
  }
296
324
  }
297
325
 
@@ -387,8 +415,8 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
387
415
  let content = readFileSync(srcPath, 'utf-8');
388
416
  // Always apply path replacement for installed location
389
417
  content = applyInstalledPathReplacement(content);
390
- // Provider adaptation on top (for non-Claude)
391
- if (provider !== 'claude' && ADAPTABLE_FILES.has(relPath)) {
418
+ // Provider-native overlay rendering on top of the neutral core.
419
+ if (provider && ADAPTABLE_FILES.has(relPath)) {
392
420
  content = adaptFrameworkFile(content, relPath, provider);
393
421
  }
394
422
  writeFileSync(destPath, content, 'utf-8');
@@ -409,7 +437,7 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
409
437
  * @param {string} destDir - Target framework directory (e.g., {project}/chati.dev/)
410
438
  * @param {string} provider - Primary provider for text adaptation ('claude' | 'gemini' | 'codex')
411
439
  */
412
- export function copyFrameworkFiles(destDir, provider = 'claude') {
440
+ export function copyFrameworkFiles(destDir, provider = null) {
413
441
  if (!existsSync(FRAMEWORK_SOURCE)) return;
414
442
 
415
443
  // Copy loose root files (with path replacement)
@@ -420,7 +448,7 @@ export function copyFrameworkFiles(destDir, provider = 'claude') {
420
448
  createDir(dirname(dest));
421
449
  let content = readFileSync(src, 'utf-8');
422
450
  content = applyInstalledPathReplacement(content);
423
- if (provider !== 'claude' && ADAPTABLE_FILES.has(file)) {
451
+ if (provider && ADAPTABLE_FILES.has(file)) {
424
452
  content = adaptFrameworkFile(content, file, provider);
425
453
  }
426
454
  writeFileSync(dest, content, 'utf-8');
@@ -764,62 +792,158 @@ export function updateGitignore(targetDir, selectedIDEs) {
764
792
  * re-rendered from the fresh template; everything else is kept as-is.
765
793
  */
766
794
  function mergeClaudeMd(existing, fresh) {
767
- // Framework-managed sections these come from the install template AND from
768
- // magic-docs runtime updates. Replace these from fresh template; preserve all others.
769
- const frameworkSections = [
770
- // Install template (templates.js generateClaudeMd)
771
- '## Project Context',
772
- '## Chati.dev',
773
- // Magic-docs runtime sections (when project starts)
774
- '## Current State',
775
- '## Session Lock',
776
- '## Recent Decisions',
777
- '## Framework',
778
- '## How to Resume',
779
- ];
795
+ const startMarker = '<!-- chati-claude:start -->';
796
+ const endMarker = '<!-- chati-claude:end -->';
797
+ const preserved = looksLikeLegacyChatiContext(existing)
798
+ ? stripLegacyStateSections(existing)
799
+ : existing;
800
+ return mergeManagedBlock(preserved, fresh, startMarker, endMarker);
801
+ }
780
802
 
781
- // If existing has no framework sections at all, user has a custom CLAUDE.md;
782
- // just append the framework template separator + fresh template below.
783
- const hasAnyFwSection = frameworkSections.some(h => existing.includes(h));
784
- if (!hasAnyFwSection) {
785
- return existing.trimEnd() + '\n\n<!-- Chati.dev framework context (auto-managed) -->\n\n' + fresh;
803
+ function mergeProjectContext(existing, fresh) {
804
+ const startMarker = '<!-- chati-context:start -->';
805
+ const endMarker = '<!-- chati-context:end -->';
806
+ const start = existing.indexOf(startMarker);
807
+ const end = existing.indexOf(endMarker);
808
+ if (start !== -1 && end !== -1 && end > start) {
809
+ return mergeManagedBlock(existing, fresh, startMarker, endMarker);
786
810
  }
787
811
 
788
- // Otherwise, do section-level replacement for each framework section in fresh,
789
- // replace the corresponding section in existing (or append if missing).
790
- let merged = existing;
791
- for (const header of frameworkSections) {
792
- const freshSection = extractSection(fresh, header);
793
- if (!freshSection) continue;
794
- if (merged.includes(header)) {
795
- merged = replaceSection(merged, header, freshSection);
796
- } else {
797
- merged = merged.trimEnd() + '\n\n' + freshSection;
812
+ // Repair pre-install Magic Docs files and partially damaged managed blocks.
813
+ // Keep runtime state and custom notes, but remove orphaned managed metadata
814
+ // before placing one valid canonical block at the top.
815
+ const hadManagedMetadata = existing.includes(startMarker)
816
+ || existing.includes(endMarker)
817
+ || (existing.includes('## Project Context') && existing.includes('No primary harness is configured'));
818
+ let preserved = existing
819
+ .replaceAll(startMarker, '')
820
+ .replaceAll(endMarker, '');
821
+ if (hadManagedMetadata) {
822
+ preserved = preserved.replace(/^# [^\n]+\n+(?=## Project Context)/m, '');
823
+ }
824
+ preserved = removeSection(preserved, '## Project Context');
825
+ preserved = removeSection(preserved, '## Chati.dev');
826
+ preserved = preserved.replace(/^\s+/, '').replace(/\n{3,}/g, '\n\n').trimEnd();
827
+ return mergeManagedBlock(preserved, fresh, startMarker, endMarker);
828
+ }
829
+
830
+ function mergeManagedBlock(existing, fresh, startMarker, endMarker) {
831
+ const existingStart = existing.indexOf(startMarker);
832
+ const existingEnd = existing.indexOf(endMarker);
833
+ if (existingStart !== -1 && existingEnd !== -1 && existingEnd > existingStart) {
834
+ return existing.slice(0, existingStart)
835
+ + fresh.trimEnd()
836
+ + existing.slice(existingEnd + endMarker.length);
837
+ }
838
+ const preserved = existing.trimEnd();
839
+ return preserved ? `${fresh.trimEnd()}\n\n${preserved}\n` : fresh;
840
+ }
841
+
842
+ const LEGACY_MANAGED_HEADERS = [
843
+ '## Project Context',
844
+ '## Session Lock',
845
+ '## Current State',
846
+ '## Recent Decisions',
847
+ '## How to Resume',
848
+ '## Framework',
849
+ '## Chati.dev',
850
+ ];
851
+
852
+ const LEGACY_ROOT_ONLY_HEADERS = [
853
+ '## Project Overview',
854
+ '## Quick Start',
855
+ '## Key References',
856
+ '## Pipeline',
857
+ ];
858
+
859
+ const LEGACY_APPEND_MARKER = '<!-- Chati.dev framework context (auto-managed) -->';
860
+
861
+ function looksLikeLegacyChatiContext(content) {
862
+ if (content.includes('<!-- chati-claude:start -->')) return false;
863
+ const exactLegacyRoot = /^# Chati\.dev Project Context\s*$/m.test(content);
864
+ const hasRuntimeFingerprint = content.includes('.chati/session.yaml')
865
+ || content.includes('<!-- SESSION-LOCK:')
866
+ || /(?<![.\w])\/chati(?![-/\w])/.test(content);
867
+ const hasLegacySection = content.includes('## Chati.dev')
868
+ || content.includes('Chati.dev framework context');
869
+ return (exactLegacyRoot && hasRuntimeFingerprint)
870
+ || (hasLegacySection && hasRuntimeFingerprint);
871
+ }
872
+
873
+ function stripLegacyStateSections(content) {
874
+ const markerIndex = content.indexOf(LEGACY_APPEND_MARKER);
875
+ if (markerIndex !== -1) {
876
+ let authoredPrefix = content.slice(0, markerIndex).trimEnd();
877
+ const currentState = extractSection(authoredPrefix, '## Current State');
878
+ // Magic Docs v1 inserted its generated state before the first authored ##
879
+ // heading. With an appended legacy Chati block this placed managed state
880
+ // above the marker, so dropping only the suffix left a frozen second source
881
+ // of truth in CLAUDE.md. Remove only the unmistakable generated shape and
882
+ // preserve a user-authored Current State section.
883
+ if (isLegacyMagicDocsState(currentState)) {
884
+ authoredPrefix = authoredPrefix.replace(currentState, '')
885
+ .replace(/\n{3,}/g, '\n\n')
886
+ .trimEnd();
798
887
  }
888
+ return authoredPrefix ? `${authoredPrefix}\n` : '';
799
889
  }
800
- return merged;
890
+
891
+ const headers = /^# Chati\.dev Project Context\s*$/m.test(content)
892
+ ? [...LEGACY_MANAGED_HEADERS, ...LEGACY_ROOT_ONLY_HEADERS]
893
+ : LEGACY_MANAGED_HEADERS;
894
+ return headers
895
+ .reduce((result, header) => removeSection(result, header), content)
896
+ .replace(/^# Chati\.dev Project Context\s*\n?/m, '')
897
+ .replace(/\n?---\s*\n\*This file is auto-updated by each agent[^\n]*\*\s*$/m, '')
898
+ .replace(/\n{3,}/g, '\n\n')
899
+ .trim() + '\n';
900
+ }
901
+
902
+ function isLegacyMagicDocsState(section) {
903
+ if (!section) return false;
904
+ const hasTimestamp = /^- \*\*Last Updated\*\*: \d{4}-\d{2}-\d{2}\s*$/m.test(section);
905
+ const hasRuntimeField = /^- \*\*(?:Agent|Pipeline|Task|Progress)\*\*:/m.test(section);
906
+ return hasTimestamp && hasRuntimeField;
801
907
  }
802
908
 
803
909
  function extractSection(content, header) {
910
+ if (!content) return null;
804
911
  const lines = content.split('\n');
805
912
  const startIdx = lines.findIndex(l => l.trim() === header);
806
913
  if (startIdx === -1) return null;
807
914
  let endIdx = startIdx + 1;
808
915
  while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
809
- return lines.slice(startIdx, endIdx).join('\n');
916
+ return lines.slice(startIdx, endIdx).join('\n').trimEnd();
810
917
  }
811
918
 
812
- function replaceSection(content, header, newSection) {
813
- const lines = content.split('\n');
814
- const startIdx = lines.findIndex(l => l.trim() === header);
815
- if (startIdx === -1) return content;
816
- let endIdx = startIdx + 1;
817
- while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
818
- return [
819
- ...lines.slice(0, startIdx),
820
- newSection,
821
- ...lines.slice(endIdx),
822
- ].join('\n');
919
+ function removeSection(content, header) {
920
+ const section = extractSection(content, header);
921
+ if (!section) return content;
922
+ return content.replace(section, '').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n';
923
+ }
924
+
925
+ function seedProjectContextFromLegacyClaude(fresh, legacyClaudeContent) {
926
+ if (!legacyClaudeContent || !looksLikeLegacyChatiContext(legacyClaudeContent)) return fresh;
927
+ const markerIndex = legacyClaudeContent.indexOf(LEGACY_APPEND_MARKER);
928
+ let currentState;
929
+ let recentDecisions;
930
+ if (markerIndex !== -1) {
931
+ const authoredPrefix = legacyClaudeContent.slice(0, markerIndex);
932
+ const managedSuffix = legacyClaudeContent.slice(markerIndex + LEGACY_APPEND_MARKER.length);
933
+ const prefixState = extractSection(authoredPrefix, '## Current State');
934
+ currentState = isLegacyMagicDocsState(prefixState)
935
+ ? prefixState
936
+ : extractSection(managedSuffix, '## Current State');
937
+ recentDecisions = extractSection(managedSuffix, '## Recent Decisions');
938
+ } else {
939
+ currentState = extractSection(legacyClaudeContent, '## Current State');
940
+ recentDecisions = extractSection(legacyClaudeContent, '## Recent Decisions');
941
+ }
942
+ const migrated = [
943
+ currentState,
944
+ recentDecisions,
945
+ ].filter(Boolean);
946
+ return migrated.length > 0 ? `${fresh.trimEnd()}\n\n${migrated.join('\n\n')}\n` : fresh;
823
947
  }
824
948
 
825
949
  /**
@@ -40,7 +40,7 @@ export function generateProviderOverlays(targetDir, frameworkSource, allProvider
40
40
  const srcPath = join(frameworkSource, file);
41
41
  if (!existsSync(srcPath)) continue;
42
42
 
43
- // Read CANONICAL (Claude) content from source, then adapt for target provider
43
+ // Read provider-neutral canonical content, then add native harness mechanics.
44
44
  const canonicalContent = readFileSync(srcPath, 'utf-8');
45
45
  const installedContent = applyInstalledPathReplacement(canonicalContent);
46
46
  const adapted = adaptFrameworkFile(installedContent, file, provider);
@@ -1,10 +1,25 @@
1
1
  import * as yaml from 'js-yaml';
2
2
 
3
+ const IDE_PROVIDER_MAP = Object.freeze({
4
+ 'claude-code': 'claude',
5
+ 'gemini-cli': 'gemini',
6
+ 'codex-cli': 'codex',
7
+ 'grok-cli': 'grok',
8
+ });
9
+
10
+ function configuredProviders(config) {
11
+ if (Array.isArray(config.allProviders) && config.allProviders.length > 0) {
12
+ return [...new Set(config.allProviders)];
13
+ }
14
+ return [...new Set((config.selectedIDEs || []).map(ide => IDE_PROVIDER_MAP[ide]).filter(Boolean))];
15
+ }
16
+
3
17
  /**
4
18
  * Generate session.yaml content
5
19
  */
6
20
  export function generateSessionYaml(config) {
7
- const { projectName, projectType, language, selectedIDEs, selectedMCPs, allProviders } = config;
21
+ const { projectName, projectType, language, selectedIDEs, selectedMCPs } = config;
22
+ const providers = configuredProviders(config);
8
23
 
9
24
  // Schema MUST match session-manager.js DEFAULT_SESSION fields,
10
25
  // otherwise validateSession() rejects installer-generated sessions.
@@ -27,7 +42,7 @@ export function generateSessionYaml(config) {
27
42
  user_level_confidence: 0.0,
28
43
  ides: selectedIDEs,
29
44
  mcps: selectedMCPs,
30
- providers_enabled: allProviders && allProviders.length > 0 ? allProviders : ['claude'],
45
+ providers_enabled: providers,
31
46
  completed_agents: [],
32
47
  agent_results: {},
33
48
  mode_transitions: [],
@@ -82,57 +97,13 @@ export function generateSessionYaml(config) {
82
97
  return yaml.dump(session, { lineWidth: -1, quoteStyle: 'double', forceQuotes: false });
83
98
  }
84
99
 
85
- /**
86
- * Optimal model assignments per provider.
87
- * Deep reasoning agents (architect, qa, dev, detail, brownfield-wu) get the top model.
88
- * Lightweight agents (brief, phases, ux, greenfield-wu, devops, orchestrator) get the fast model.
89
- */
90
- export const PROVIDER_MODEL_MAPS = {
91
- claude: {
92
- deep: 'opus', light: 'sonnet', minimal: 'haiku',
93
- agents: {
94
- orchestrator: 'sonnet', 'greenfield-wu': 'haiku', 'brownfield-wu': 'opus',
95
- brief: 'sonnet', detail: 'opus', architect: 'opus', ux: 'sonnet',
96
- phases: 'sonnet', tasks: 'sonnet', 'qa-planning': 'opus',
97
- dev: 'opus', 'qa-implementation': 'opus', 'qa-visual': 'opus', devops: 'sonnet',
98
- },
99
- },
100
- gemini: {
101
- deep: 'pro', light: 'flash', minimal: 'flash',
102
- agents: {
103
- orchestrator: 'flash', 'greenfield-wu': 'flash', 'brownfield-wu': 'pro',
104
- brief: 'flash', detail: 'pro', architect: 'pro', ux: 'flash',
105
- phases: 'flash', tasks: 'flash', 'qa-planning': 'pro',
106
- dev: 'pro', 'qa-implementation': 'pro', 'qa-visual': 'pro', devops: 'flash',
107
- },
108
- },
109
- codex: {
110
- deep: 'codex', light: 'mini', minimal: 'mini',
111
- agents: {
112
- orchestrator: 'mini', 'greenfield-wu': 'mini', 'brownfield-wu': 'codex',
113
- brief: 'mini', detail: 'codex', architect: 'codex', ux: 'mini',
114
- phases: 'mini', tasks: 'mini', 'qa-planning': 'codex',
115
- dev: 'codex', 'qa-implementation': 'codex', 'qa-visual': 'codex', devops: 'mini',
116
- },
117
- },
118
- grok: {
119
- deep: 'grok-4.6', light: 'grok-4.5', minimal: 'grok-4.3',
120
- agents: {
121
- orchestrator: 'grok-4.5', 'greenfield-wu': 'grok-4.3', 'brownfield-wu': 'grok-4.6',
122
- brief: 'grok-4.5', detail: 'grok-4.6', architect: 'grok-4.6', ux: 'grok-4.5',
123
- phases: 'grok-4.5', tasks: 'grok-4.5', 'qa-planning': 'grok-4.6',
124
- dev: 'grok-4.6', 'qa-implementation': 'grok-4.6', 'qa-visual': 'grok-4.6', devops: 'grok-4.5',
125
- },
126
- },
127
- };
128
-
129
100
  /**
130
101
  * Generate config.yaml content
131
102
  */
132
103
  export function generateConfigYaml(config) {
133
- const { version, projectType, language, allProviders } = config;
104
+ const { version, projectType, language } = config;
134
105
  const selectedIDEs = config.selectedIDEs || [];
135
- const providers = allProviders && allProviders.length > 0 ? allProviders : ['claude'];
106
+ const providers = configuredProviders(config);
136
107
 
137
108
  const configData = {
138
109
  version: version,
@@ -164,7 +135,7 @@ export function generateConfigYaml(config) {
164
135
  tech_presets: true,
165
136
  doctor_autofix: true,
166
137
  brandbook: true,
167
- // Intelligence Upgrade (v4.0.5 — Claude Code patterns)
138
+ // Intelligence Upgrade
168
139
  undercover_mode: true,
169
140
  memory_extraction: true,
170
141
  memory_consolidation: true,
@@ -175,11 +146,9 @@ export function generateConfigYaml(config) {
175
146
  model_fallback: true,
176
147
  frustration_detection: true,
177
148
  bash_security_checks: true,
178
- // Agent Teams are planning-only in runtime v2.
179
- // Only effective when the active session runs in Claude (gated in cli.js
180
- // isAgentTeamsEnabled). Gemini and Codex always fall back to sequential
181
- // pipeline silently. See plan: was previously false for "safe rollout"
182
- // but the v4.2.0 launch never actually shipped, so this is a fresh start.
149
+ // Runtime v2 uses the strict provider-neutral parallel runner. The legacy
150
+ // native team path is available only for an explicitly active harness that
151
+ // supports it.
183
152
  agent_teams: true,
184
153
  team_planning_size: 4,
185
154
  ...(config.runtimeVersion === 2 ? {} : { team_build_size: 2 }),
@@ -197,20 +166,39 @@ export function generateConfigYaml(config) {
197
166
  }
198
167
 
199
168
  /**
200
- * Generate CLAUDE.md content (minimal framework rules auto-loaded from .claude/rules/chati/)
169
+ * Generate the provider-neutral project context owned by Chati.dev.
170
+ * Provider harness files consume this artifact but never become its source.
201
171
  */
202
- export function generateClaudeMd(config) {
172
+ export function generateProjectContext(config) {
203
173
  const { projectName, projectType, language } = config;
204
174
 
205
- return `# ${projectName}
175
+ return `<!-- chati-context:start -->
176
+ # ${projectName}
206
177
 
207
178
  ## Project Context
208
179
  - **Type**: ${projectType === 'greenfield' ? 'Greenfield (new project)' : 'Brownfield (existing project)'}
209
180
  - **Language**: ${language}
210
181
 
211
182
  ## Chati.dev
212
- Framework rules loaded from \`.claude/rules/chati/\`. Runtime state in \`CLAUDE.local.md\`.
213
- Type \`/chati\` to start.
183
+ Provider and model routing is resolved per task from the signed installation
184
+ capability snapshot. No primary harness is configured.
185
+ <!-- chati-context:end -->
186
+ `;
187
+ }
188
+
189
+ /**
190
+ * Generate the native Claude Code entry context. The shared project context
191
+ * remains in .chati/project-context.md and is imported instead of duplicated.
192
+ */
193
+ export function generateClaudeMd(_config) {
194
+ return `<!-- chati-claude:start -->
195
+ @.chati/project-context.md
196
+
197
+ ## Claude Code integration
198
+ Framework rules are loaded from \`.claude/rules/chati/\`.
199
+ Runtime session state is stored in \`CLAUDE.local.md\`.
200
+ Type \`/chati\` to start or resume Chati.dev.
201
+ <!-- chati-claude:end -->
214
202
  `;
215
203
  }
216
204
 
@@ -256,14 +244,15 @@ Parse the JSON output before responding:
256
244
  - When the action includes \`pipeline.spawn_command\`, execute that exact command.
257
245
  Never replace it with an inferred provider check or a manually constructed command.
258
246
  - A router-provided spawn command starts an external provider CLI. Execute it
259
- with native escalated execution: set \`sandbox_permissions\` to
260
- \`require_escalated\`, preserve the exact command and working directory, and
261
- offer the narrow reusable prefix
262
- \`["node", "<absolute path from spawn_command to run-agent.js>"]\`.
247
+ with the active harness's native process permissions, preserving the exact
248
+ command and working directory. If the harness sandboxes OS credentials,
249
+ request its native narrow approval for that command. Never invent a tool
250
+ parameter that the active harness does not support.
263
251
  - Do not run that provider command inside the workspace sandbox first. A
264
252
  \`PROVIDER_NOT_READY\` or \`PROVIDER_NOT_AUTHENTICATED\` result from a sandboxed
265
- attempt is not proof that the user is logged out. Rerun it once with native
266
- escalation before reporting an authentication failure.
253
+ attempt is not proof that the user is logged out. Rerun it once through the
254
+ harness's native approved execution path before reporting an authentication
255
+ failure.
267
256
 
268
257
  ## CRITICAL: Language Override
269
258
 
@@ -343,6 +332,7 @@ execute the full orchestrator at \`${orchPath}\`.
343
332
  Load this context:
344
333
  - \`.chati/session.yaml\`
345
334
  - \`AGENTS.md\`
335
+ - \`.grok/session-lock.md\`
346
336
  - \`artifacts/handoffs/\`
347
337
  - \`.chati.dev/config.yaml\`
348
338
 
@@ -50,6 +50,8 @@ const ARTIFACT_KEYS = new Set([
50
50
  ]);
51
51
  const INSTALLATION_KEYS = new Set([
52
52
  'installation_id', 'profile', 'enabled_providers', 'capability_snapshot_ref',
53
+ // Read-only compatibility for early v2 artifacts. Current installers never
54
+ // emit these fields and runtime routing does not consume them.
53
55
  'policy_ref', 'primary_binding', 'primary_harness', 'external_integrations',
54
56
  ]);
55
57
  const BINDING_KEYS = new Set([