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.
- package/framework/agents/build/dev.md +4 -5
- package/framework/agents/deploy/devops.md +9 -10
- package/framework/agents/discover/brief.md +7 -13
- package/framework/agents/discover/brownfield-wu.md +5 -11
- package/framework/agents/discover/greenfield-wu.md +7 -12
- package/framework/agents/plan/architect.md +1 -1
- package/framework/agents/plan/detail.md +5 -5
- package/framework/agents/plan/phases.md +4 -5
- package/framework/agents/plan/tasks.md +4 -5
- package/framework/agents/plan/ux-brand-architect.md +1 -1
- package/framework/agents/plan/ux.md +1 -1
- package/framework/agents/quality/qa-implementation.md +4 -5
- package/framework/agents/quality/qa-planning.md +4 -5
- package/framework/agents/quality/qa-visual.md +4 -4
- package/framework/agents/shared/visualizer.md +1 -1
- package/framework/config.yaml +5 -5
- package/framework/constitution.md +24 -26
- package/framework/context/governance.md +6 -6
- package/framework/context/root.md +4 -3
- package/framework/data/entity-registry.yaml +3 -3
- package/framework/data/model-limits.json +5 -2
- package/framework/hooks/model-governance.js +3 -2
- package/framework/hooks/prism-engine.js +1 -1
- package/framework/i18n/en.yaml +2 -2
- package/framework/i18n/es.yaml +2 -2
- package/framework/i18n/fr.yaml +2 -2
- package/framework/i18n/pt.yaml +2 -2
- package/framework/intelligence/context-engine.md +12 -16
- package/framework/manifest.json +63 -63
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +40 -6
- package/framework/orchestrator/chati.md +12 -13
- package/framework/schemas/session.schema.json +1 -1
- package/package.json +1 -1
- package/src/config/context-file-generator.js +54 -246
- package/src/config/framework-adapter.js +45 -208
- package/src/context/bracket-tracker.js +3 -3
- package/src/context/engine.js +1 -1
- package/src/installer/core.js +190 -66
- package/src/installer/provider-overlay.js +1 -1
- package/src/installer/templates.js +55 -65
- package/src/installer-v2/index.js +2 -0
- package/src/memory/magic-docs.js +45 -26
- package/src/orchestrator/cli.js +50 -45
- package/src/orchestrator/handoff-engine.js +7 -5
- package/src/orchestrator/session-manager.js +24 -3
- package/src/terminal/cli-registry.js +5 -5
- package/src/terminal/prompt-builder.js +14 -10
- package/src/terminal/run-agent.js +10 -6
- package/src/terminal/run-parallel.js +6 -2
- package/src/terminal/run-team.js +7 -2
- package/src/terminal/spawner.js +34 -42
- package/src/utils/config-parser.js +2 -2
- package/src/utils/provider-limits.js +3 -1
- package/src/wizard/i18n.js +2 -2
package/src/installer/core.js
CHANGED
|
@@ -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
|
|
132
|
-
//
|
|
133
|
-
copyFrameworkFiles(frameworkDir,
|
|
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 (
|
|
137
|
-
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE,
|
|
139
|
+
if (enabledProviders.length > 0) {
|
|
140
|
+
generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, enabledProviders);
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
if (isRuntimeV2) {
|
|
141
|
-
pruneLegacyExecutionSurface(frameworkDir,
|
|
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 &&
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
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,
|
|
252
|
+
writeFileSync(claudeMdPath, mergeClaudeMd(existingClaudeMd, claudeContent), 'utf-8');
|
|
229
253
|
} else {
|
|
230
|
-
writeFileSync(claudeMdPath,
|
|
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.
|
|
236
|
-
|
|
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
|
|
391
|
-
if (provider
|
|
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 =
|
|
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
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
//
|
|
789
|
-
//
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
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
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
104
|
+
const { version, projectType, language } = config;
|
|
134
105
|
const selectedIDEs = config.selectedIDEs || [];
|
|
135
|
-
const providers =
|
|
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
|
|
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
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
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
|
|
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
|
|
172
|
+
export function generateProjectContext(config) {
|
|
203
173
|
const { projectName, projectType, language } = config;
|
|
204
174
|
|
|
205
|
-
return
|
|
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
|
-
|
|
213
|
-
|
|
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
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
266
|
-
|
|
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([
|