chati-dev 4.2.2 → 4.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.
Files changed (220) hide show
  1. package/README.md +80 -53
  2. package/bin/chati.js +150 -5
  3. package/framework/agents/build/dev.md +509 -76
  4. package/framework/agents/deploy/devops.md +40 -25
  5. package/framework/agents/discover/brief.md +156 -22
  6. package/framework/agents/discover/brownfield-wu.md +24 -14
  7. package/framework/agents/discover/greenfield-wu.md +100 -14
  8. package/framework/agents/plan/architect-data-engineer.md +6 -6
  9. package/framework/agents/plan/architect-system.md +46 -12
  10. package/framework/agents/plan/architect.md +40 -20
  11. package/framework/agents/plan/detail.md +36 -24
  12. package/framework/agents/plan/phases.md +36 -26
  13. package/framework/agents/plan/tasks.md +114 -33
  14. package/framework/agents/plan/ux-brand-architect.md +240 -8
  15. package/framework/agents/plan/ux-component-engineer.md +28 -13
  16. package/framework/agents/plan/ux-researcher.md +7 -6
  17. package/framework/agents/plan/ux.md +55 -22
  18. package/framework/agents/quality/qa-implementation.md +143 -74
  19. package/framework/agents/quality/qa-planning.md +115 -42
  20. package/framework/agents/quality/qa-visual.md +439 -0
  21. package/framework/agents/shared/visualizer.md +128 -0
  22. package/framework/config.yaml +7 -6
  23. package/framework/constitution.md +127 -44
  24. package/framework/context/governance.md +12 -7
  25. package/framework/context/quality.md +6 -5
  26. package/framework/context/root.md +6 -6
  27. package/framework/data/entity-registry.yaml +377 -4
  28. package/framework/data/model-limits.json +19 -0
  29. package/framework/domains/agents/qa-visual.yaml +74 -0
  30. package/framework/domains/constitution.yaml +46 -2
  31. package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
  32. package/framework/hooks/advance-trigger.js +131 -0
  33. package/framework/hooks/brief-validator.js +83 -0
  34. package/framework/hooks/constitution-guard.js +24 -5
  35. package/framework/hooks/license-guard.js +62 -27
  36. package/framework/hooks/mode-governance.js +13 -2
  37. package/framework/hooks/model-governance.js +1 -0
  38. package/framework/hooks/post-dev.js +207 -0
  39. package/framework/hooks/prism-engine.js +274 -105
  40. package/framework/hooks/reasoning-escalator.js +371 -0
  41. package/framework/hooks/reference-trigger.js +117 -0
  42. package/framework/hooks/session-digest.js +50 -1
  43. package/framework/hooks/settings.json +32 -1
  44. package/framework/hooks/style-guard.js +25 -6
  45. package/framework/hooks/team-quality-gate.js +19 -12
  46. package/framework/hooks/undercover-guard.js +4 -2
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +4 -5
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/intelligence/hooks-performance.md +3 -3
  54. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  55. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  56. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  57. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  58. package/framework/orchestrator/chati-router.js +387 -0
  59. package/framework/orchestrator/chati-update.md +40 -40
  60. package/framework/orchestrator/chati.md +294 -94
  61. package/framework/scaffold/motion-premium/README.md +89 -0
  62. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  63. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  64. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  65. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  66. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  67. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  68. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  69. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  70. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  71. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  72. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  73. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  74. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  76. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  77. package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
  78. package/framework/scaffold/motion-premium-3d/README.md +80 -0
  79. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  80. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  82. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  83. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  88. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  89. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  90. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  91. package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
  92. package/framework/schemas/session.schema.json +109 -21
  93. package/framework/scripts/reference-capture.js +430 -0
  94. package/framework/scripts/visual-qa.js +674 -0
  95. package/framework/tasks/orchestrator-handoff.md +1 -1
  96. package/framework/tasks/orchestrator-resume.md +1 -1
  97. package/framework/tasks/orchestrator-route.md +1 -1
  98. package/framework/tasks/orchestrator-status.md +3 -3
  99. package/framework/tasks/qa-planning-gate-define.md +1 -1
  100. package/framework/templates/brandbook-html-tmpl.md +1 -1
  101. package/framework/templates/brandbook-tmpl.yaml +1 -1
  102. package/framework/templates/component-spec-tmpl.yaml +1 -1
  103. package/framework/templates/design-token-tmpl.yaml +1 -1
  104. package/framework/templates/icon-system-tmpl.yaml +1 -1
  105. package/framework/templates/team-planning-tasks.yaml +6 -5
  106. package/framework/workflows/brownfield-discovery.yaml +2 -2
  107. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  108. package/framework/workflows/brownfield-service.yaml +14 -10
  109. package/framework/workflows/brownfield-ui.yaml +15 -11
  110. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  111. package/framework/workflows/quick-flow.yaml +3 -3
  112. package/framework/workflows/standard-flow.yaml +12 -9
  113. package/package.json +10 -5
  114. package/src/autonomy/autonomous-gate.js +1 -0
  115. package/src/autonomy/build-state.js +1 -2
  116. package/src/autonomy/progress-reporter.js +1 -1
  117. package/src/config/agent-customizer.js +11 -3
  118. package/src/config/claude-settings-generator.js +27 -7
  119. package/src/config/context-file-generator.js +41 -21
  120. package/src/config/framework-adapter.js +1 -0
  121. package/src/config/gemini-hooks-generator.js +19 -7
  122. package/src/config/mcp-configs.js +1 -0
  123. package/src/context/layers/l1-global.js +2 -1
  124. package/src/dashboard/data-reader.js +4 -3
  125. package/src/dashboard/layout.js +2 -1
  126. package/src/decision/analyzer.js +6 -30
  127. package/src/decision/engine.js +4 -28
  128. package/src/decision/registry-healer.js +3 -2
  129. package/src/decision/registry-updater.js +23 -14
  130. package/src/extensions/loader.js +2 -8
  131. package/src/gates/g1-planning-complete.js +2 -1
  132. package/src/gates/g2-qa-planning.js +2 -1
  133. package/src/gates/g3-implementation.js +2 -1
  134. package/src/gates/g4-qa-implementation.js +3 -2
  135. package/src/gates/g5-deploy-ready.js +2 -1
  136. package/src/health/engine.js +4 -3
  137. package/src/installer/core.js +422 -81
  138. package/src/installer/preflight.js +131 -0
  139. package/src/installer/provider-overlay.js +3 -3
  140. package/src/installer/scaffold-applier.js +358 -0
  141. package/src/installer/templates.js +46 -29
  142. package/src/installer/validator.js +17 -12
  143. package/src/intelligence/registry-manager.js +22 -29
  144. package/src/intelligence/timeline.js +11 -6
  145. package/src/license/commands.js +1 -1
  146. package/src/license/wait.js +102 -0
  147. package/src/memory/agent-memory.js +81 -0
  148. package/src/memory/dream.js +32 -1
  149. package/src/merger/replace-merger.js +28 -15
  150. package/src/orchestrator/agent-selector.js +2 -1
  151. package/src/orchestrator/cli.js +1869 -71
  152. package/src/orchestrator/doctor.js +270 -0
  153. package/src/orchestrator/handoff-engine.js +4 -3
  154. package/src/orchestrator/index.js +2 -0
  155. package/src/orchestrator/pipeline-manager.js +306 -15
  156. package/src/orchestrator/session-manager.js +331 -6
  157. package/src/tasks/handoff.js +3 -2
  158. package/src/telemetry/config.js +4 -3
  159. package/src/telemetry/schema.js +1 -0
  160. package/src/terminal/collector.js +3 -2
  161. package/src/terminal/index.js +1 -2
  162. package/src/terminal/isolation.js +52 -18
  163. package/src/terminal/prompt-builder.js +42 -25
  164. package/src/terminal/run-parallel.js +1 -1
  165. package/src/terminal/run-team.js +3 -3
  166. package/src/terminal/team-task-list.js +43 -4
  167. package/src/upgrade/backup.js +3 -2
  168. package/src/upgrade/checker.js +3 -2
  169. package/src/upgrade/migrator.js +65 -7
  170. package/src/upgrade/tracked-files-detector.js +86 -0
  171. package/src/upgrade/user-messages.js +94 -0
  172. package/src/utils/config-parser.js +2 -1
  173. package/src/utils/feature-flags.js +2 -1
  174. package/src/utils/flatten-entities.js +69 -0
  175. package/src/utils/framework-dir.js +16 -0
  176. package/src/utils/model-id.js +85 -0
  177. package/src/utils/provider-limits.js +84 -23
  178. package/src/utils/schema-validator.js +1 -1
  179. package/src/wizard/i18n.js +5 -4
  180. package/src/wizard/index.js +14 -0
  181. package/assets/logo - c/303/263pia.png +0 -0
  182. package/assets/logo.svg +0 -42
  183. package/assets/logo2.png +0 -0
  184. package/assets/social-preview.png +0 -0
  185. package/scripts/bundle-framework.js +0 -69
  186. package/scripts/changelog-generator.js +0 -222
  187. package/scripts/codebase-mapper.js +0 -728
  188. package/scripts/commit-message-generator.js +0 -167
  189. package/scripts/coverage-analyzer.js +0 -260
  190. package/scripts/dependency-analyzer.js +0 -280
  191. package/scripts/doctor/checks/agents.js +0 -77
  192. package/scripts/doctor/checks/constitution.js +0 -41
  193. package/scripts/doctor/checks/domain-alignment.js +0 -58
  194. package/scripts/doctor/checks/prism-layers.js +0 -84
  195. package/scripts/doctor/checks/registry.js +0 -55
  196. package/scripts/doctor/checks/schemas.js +0 -61
  197. package/scripts/doctor/fixes/reference-fix.js +0 -100
  198. package/scripts/doctor/fixes/registry-fix.js +0 -56
  199. package/scripts/doctor/index.js +0 -212
  200. package/scripts/framework-analyzer.js +0 -308
  201. package/scripts/generate-constitution-domain.js +0 -253
  202. package/scripts/generate-signing-key.js +0 -33
  203. package/scripts/health-check.js +0 -481
  204. package/scripts/ide-sync.js +0 -326
  205. package/scripts/performance-analyzer.js +0 -325
  206. package/scripts/plan-tracker.js +0 -278
  207. package/scripts/populate-entity-registry.js +0 -481
  208. package/scripts/pr-review.js +0 -317
  209. package/scripts/rollback-manager.js +0 -310
  210. package/scripts/semantic-lint.js +0 -328
  211. package/scripts/sign-manifest.js +0 -53
  212. package/scripts/stuck-detector.js +0 -343
  213. package/scripts/test-quality-assessment.js +0 -257
  214. package/scripts/validate-agents.js +0 -368
  215. package/scripts/validate-package.js +0 -505
  216. package/scripts/validate-tasks.js +0 -465
  217. package/src/autonomy/worktree-manager.js +0 -250
  218. package/src/intelligence/decision-engine.js +0 -256
  219. package/src/intelligence/document-sharder.js +0 -221
  220. package/src/intelligence/elicitation.js +0 -265
@@ -1,6 +1,7 @@
1
- import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync, readdirSync } from 'fs';
2
- import { join, dirname } from 'path';
1
+ import { mkdirSync, writeFileSync, copyFileSync, existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, dirname, basename } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { createRequire } from 'module';
4
5
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
5
6
  import { generateClaudeMCPConfig } from '../config/mcp-configs.js';
6
7
  import { generateSessionYaml, generateConfigYaml, generateClaudeMd, generateClaudeLocalMd, generateCodexSkill, generateGeminiRouter, generateGeminiSessionLock, generateAgentsOverrideMd, generateCodexConstitutionGuardRules, generateCodexReadProtectionRules } from './templates.js';
@@ -22,7 +23,15 @@ const FRAMEWORK_SOURCE = existsSync(BUNDLED_SOURCE) ? BUNDLED_SOURCE : MONOREPO_
22
23
  * Install Chati.dev framework into target directory
23
24
  */
24
25
  export async function installFramework(config) {
25
- const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, version, llmProvider, allProviders } = config;
26
+ const { targetDir, projectType, language, selectedIDEs, selectedMCPs, projectName, llmProvider, allProviders } = config;
27
+ // Always derive version from package.json (single source of truth)
28
+ let { version } = config;
29
+ if (!version) {
30
+ try {
31
+ const pkgPath = join(__dirname, '..', '..', 'package.json');
32
+ version = JSON.parse(readFileSync(pkgPath, 'utf-8')).version;
33
+ } catch { version = 'unknown'; }
34
+ }
26
35
 
27
36
  // 0. Verify framework signature (supply chain protection)
28
37
  const manifestPath = join(FRAMEWORK_SOURCE, 'manifest.json');
@@ -40,17 +49,36 @@ export async function installFramework(config) {
40
49
 
41
50
  // 1. Create .chati/ session directory
42
51
  createDir(join(targetDir, '.chati'));
43
- writeFileSync(
44
- join(targetDir, '.chati', 'session.yaml'),
45
- generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders }),
46
- 'utf-8'
47
- );
52
+ const sessionPath = join(targetDir, '.chati', 'session.yaml');
53
+ // Preserve existing session.yaml on reinstall/recovery (don't destroy pipeline state).
54
+ // Only write fresh session if one does not exist.
55
+ if (!existsSync(sessionPath)) {
56
+ writeFileSync(
57
+ sessionPath,
58
+ generateSessionYaml({ projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders }),
59
+ 'utf-8'
60
+ );
61
+ }
48
62
 
49
- // 2. Create chati.dev/ framework directory (copy from source)
50
- const frameworkDir = join(targetDir, 'chati.dev');
63
+ // 2. Create .chati.dev/ framework directory (hidden dot prefix)
64
+ // The source is chati.dev/ in the monorepo; installed as .chati.dev/ in user projects.
65
+ // Text replacement during copy handles all internal path references.
66
+ const frameworkDir = join(targetDir, '.chati.dev');
51
67
  createDir(frameworkDir);
52
68
 
53
- // Copy framework structure
69
+ // 2b. Create artifacts/ at PROJECT ROOT (visible to user — this is their output)
70
+ const artifactsDir = join(targetDir, 'artifacts');
71
+ const artifactDirs = [
72
+ '0-WU', '1-Brief', '2-PRD',
73
+ '3-Architecture', '4-UX', '5-Phases',
74
+ '6-Tasks', '7-QA-Planning', '8-QA-Visual', '9-QA-Implementation', '10-Deploy',
75
+ 'handoffs', 'decisions',
76
+ ];
77
+ for (const dir of artifactDirs) {
78
+ createDir(join(artifactsDir, dir));
79
+ }
80
+
81
+ // Copy framework structure (everything EXCEPT artifacts, which live at root)
54
82
  const frameworkDirs = [
55
83
  'orchestrator',
56
84
  'agents/discover', 'agents/plan', 'agents/quality', 'agents/build', 'agents/deploy',
@@ -58,10 +86,6 @@ export async function installFramework(config) {
58
86
  'schemas', 'frameworks', 'intelligence', 'patterns',
59
87
  'hooks', 'domains', 'domains/agents', 'domains/workflows',
60
88
  'i18n', 'migrations', 'data', 'context',
61
- 'artifacts/0-WU', 'artifacts/1-Brief', 'artifacts/2-PRD',
62
- 'artifacts/3-Architecture', 'artifacts/4-UX', 'artifacts/5-Phases',
63
- 'artifacts/6-Tasks', 'artifacts/7-QA-Planning', 'artifacts/9-QA-Implementation', 'artifacts/10-Deploy',
64
- 'artifacts/handoffs', 'artifacts/decisions',
65
89
  ];
66
90
 
67
91
  for (const dir of frameworkDirs) {
@@ -82,6 +106,7 @@ export async function installFramework(config) {
82
106
  'tasks/durable', 'tasks/daily',
83
107
  'qa-planning/durable', 'qa-planning/daily',
84
108
  'qa-implementation/durable', 'qa-implementation/daily',
109
+ 'qa-visual/durable', 'qa-visual/daily',
85
110
  'dev/durable', 'dev/daily',
86
111
  'devops/durable', 'devops/daily',
87
112
  ];
@@ -97,6 +122,31 @@ export async function installFramework(config) {
97
122
  generateProviderOverlays(targetDir, FRAMEWORK_SOURCE, llmProvider || 'claude', allProviders);
98
123
  }
99
124
 
125
+ // Bundle the CLI source into the framework dir so chati-router.js can run
126
+ // commands locally without depending on `npx chati-dev` (which fetches from
127
+ // the npm registry — version drift causes inconsistent behavior).
128
+ // Result: every install is self-contained; no network/cache dependency at runtime.
129
+ copyCliSource(
130
+ join(__dirname, '..'), // packages/chati-dev/src/
131
+ join(frameworkDir, '_cli') // .chati.dev/_cli/
132
+ );
133
+
134
+ // The bundled CLI imports ../../framework/hooks/model-governance.js relative
135
+ // to _cli/terminal/prompt-builder.js. Starting at .chati.dev/_cli/terminal/,
136
+ // `../../` resolves to .chati.dev/ — so the hooks copy must land at
137
+ // .chati.dev/framework/hooks/ to satisfy the import path.
138
+ copyDirVerbatim(
139
+ join(__dirname, '..', '..', 'framework', 'hooks'),
140
+ join(frameworkDir, 'framework', 'hooks')
141
+ );
142
+
143
+ // Also bundle production deps so the CLI can import js-yaml/chalk/etc. at runtime
144
+ // without relying on a node_modules tree in the user's project.
145
+ copyCliDependencies(
146
+ join(__dirname, '..', '..'), // packages/chati-dev/
147
+ join(frameworkDir, '_cli', 'node_modules') // .chati.dev/_cli/node_modules/
148
+ );
149
+
100
150
  // Write config.yaml
101
151
  writeFileSync(
102
152
  join(frameworkDir, 'config.yaml'),
@@ -110,8 +160,8 @@ export async function installFramework(config) {
110
160
  const ideProvider = IDE_TO_PROVIDER[ideKey];
111
161
  const isSecondary = ideProvider && ideProvider !== primary && allProviders && allProviders.length > 1;
112
162
  const orchestratorPath = isSecondary
113
- ? `chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
114
- : 'chati.dev/orchestrator/chati.md';
163
+ ? `.chati.dev/.adapted/${ideProvider}/orchestrator/chati.md`
164
+ : '.chati.dev/orchestrator/chati.md';
115
165
  await configureIDE(targetDir, ideKey, selectedMCPs, { orchestratorPath, isSecondary, providerName: ideProvider });
116
166
  }
117
167
 
@@ -129,11 +179,22 @@ export async function installFramework(config) {
129
179
  for (const file of contextFiles) {
130
180
  const src = join(FRAMEWORK_SOURCE, 'context', file);
131
181
  if (existsSync(src)) {
132
- copyFileSync(src, join(claudeRulesDir, file));
182
+ // Apply path replacement so rules reference .chati.dev/ not chati.dev/
183
+ let content = readFileSync(src, 'utf-8');
184
+ content = applyPathReplacement(content);
185
+ writeFileSync(join(claudeRulesDir, file), content, 'utf-8');
133
186
  }
134
187
  }
135
188
 
136
- writeFileSync(join(targetDir, 'CLAUDE.md'), baseContent, 'utf-8');
189
+ // Preserve existing CLAUDE.md on reinstall — user or magic-docs may have
190
+ // edited it. Section-aware merge if exists; fresh write if not.
191
+ const claudeMdPath = join(targetDir, 'CLAUDE.md');
192
+ if (existsSync(claudeMdPath)) {
193
+ const existingClaudeMd = readFileSync(claudeMdPath, 'utf-8');
194
+ writeFileSync(claudeMdPath, mergeClaudeMd(existingClaudeMd, baseContent), 'utf-8');
195
+ } else {
196
+ writeFileSync(claudeMdPath, baseContent, 'utf-8');
197
+ }
137
198
  writeFileSync(join(targetDir, 'CLAUDE.local.md'), generateClaudeLocalMd(), 'utf-8');
138
199
  }
139
200
 
@@ -172,6 +233,8 @@ const FRAMEWORK_DIRS_TO_COPY = [
172
233
  'context',
173
234
  'tasks',
174
235
  'presets',
236
+ 'scripts',
237
+ 'scaffold',
175
238
  ];
176
239
 
177
240
  /**
@@ -182,8 +245,39 @@ const FRAMEWORK_ROOT_FILES = [
182
245
  ];
183
246
 
184
247
  /**
185
- * Recursively copy a directory from source to destination, applying provider
186
- * adaptation for files listed in ADAPTABLE_FILES when the provider is not Claude.
248
+ * Text file extensions that get path replacement during copy.
249
+ * Binary files (.png, .woff2, etc.) are copied without modification.
250
+ */
251
+ const TEXT_EXTENSIONS = new Set(['.md', '.js', '.yaml', '.yml', '.json', '.html', '.toml', '.txt', '.rules']);
252
+
253
+ /**
254
+ * Apply installed-path replacement to file content.
255
+ *
256
+ * Source uses `chati.dev/` everywhere. Installed project uses:
257
+ * - `.chati.dev/` for framework files (hidden)
258
+ * - `artifacts/` at project root for user-visible output
259
+ *
260
+ * Order matters: replace `chati.dev/artifacts/` FIRST (→ `artifacts/`),
261
+ * then `chati.dev/` (→ `.chati.dev/`). Reversing would produce `.chati.dev/artifacts/`.
262
+ */
263
+ function applyPathReplacement(content) {
264
+ return content
265
+ // Step 1: artifacts at root (must be before generic replace)
266
+ .replace(/chati\.dev\/artifacts\//g, 'artifacts/')
267
+ // Step 2: path strings with slash — but NOT if already prefixed with dot
268
+ // (negative lookbehind for any char that would create `.chati.dev` already)
269
+ .replace(/(?<!\.)chati\.dev\//g, '.chati.dev/')
270
+ // Step 3: quoted dir name in JS join() calls (WITHOUT slash)
271
+ // Only match 'chati.dev' (the DIRECTORY, with dot) not 'chati-dev' (the NPM PACKAGE, with dash)
272
+ // Also: avoid double-prefixing if already '.chati.dev' in source
273
+ .replace(/(?<!\.)'chati\.dev'/g, "'.chati.dev'")
274
+ .replace(/(?<!\.)"chati\.dev"/g, '".chati.dev"');
275
+ }
276
+
277
+ /**
278
+ * Recursively copy a directory from source to destination, applying:
279
+ * 1. Path replacement (chati.dev/ → .chati.dev/, artifacts/ at root)
280
+ * 2. Provider adaptation for non-Claude providers
187
281
  */
188
282
  function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
189
283
  if (!existsSync(srcDir)) return;
@@ -196,14 +290,24 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
196
290
  const relPath = relBase ? join(relBase, entry.name) : entry.name;
197
291
 
198
292
  if (entry.isDirectory()) {
293
+ // Skip artifacts/ — those live at project root, not inside .chati.dev/
294
+ if (entry.name === 'artifacts') continue;
199
295
  copyDirRecursive(srcPath, destPath, provider, relPath);
200
296
  } else if (entry.isFile()) {
201
- if (provider !== 'claude' && ADAPTABLE_FILES.has(relPath)) {
202
- // Non-Claude provider: read, adapt, write
203
- const content = readFileSync(srcPath, 'utf-8');
204
- writeFileSync(destPath, adaptFrameworkFile(content, relPath, provider), 'utf-8');
297
+ const ext = entry.name.includes('.') ? '.' + entry.name.split('.').pop() : '';
298
+ const isText = TEXT_EXTENSIONS.has(ext);
299
+
300
+ if (isText) {
301
+ let content = readFileSync(srcPath, 'utf-8');
302
+ // Always apply path replacement for installed location
303
+ content = applyPathReplacement(content);
304
+ // Provider adaptation on top (for non-Claude)
305
+ if (provider !== 'claude' && ADAPTABLE_FILES.has(relPath)) {
306
+ content = adaptFrameworkFile(content, relPath, provider);
307
+ }
308
+ writeFileSync(destPath, content, 'utf-8');
205
309
  } else {
206
- // Claude or non-adaptable: direct copy
310
+ // Binary file: direct copy
207
311
  copyFileSync(srcPath, destPath);
208
312
  }
209
313
  }
@@ -222,18 +326,18 @@ function copyDirRecursive(srcDir, destDir, provider, relBase = '') {
222
326
  export function copyFrameworkFiles(destDir, provider = 'claude') {
223
327
  if (!existsSync(FRAMEWORK_SOURCE)) return;
224
328
 
225
- // Copy loose root files
329
+ // Copy loose root files (with path replacement)
226
330
  for (const file of FRAMEWORK_ROOT_FILES) {
227
331
  const src = join(FRAMEWORK_SOURCE, file);
228
332
  if (!existsSync(src)) continue;
229
333
  const dest = join(destDir, file);
230
334
  createDir(dirname(dest));
335
+ let content = readFileSync(src, 'utf-8');
336
+ content = applyPathReplacement(content);
231
337
  if (provider !== 'claude' && ADAPTABLE_FILES.has(file)) {
232
- const content = readFileSync(src, 'utf-8');
233
- writeFileSync(dest, adaptFrameworkFile(content, file, provider), 'utf-8');
234
- } else {
235
- copyFileSync(src, dest);
338
+ content = adaptFrameworkFile(content, file, provider);
236
339
  }
340
+ writeFileSync(dest, content, 'utf-8');
237
341
  }
238
342
 
239
343
  // Copy each framework directory recursively
@@ -257,35 +361,36 @@ async function configureIDE(targetDir, ideKey, selectedMCPs, options = {}) {
257
361
 
258
362
  if (ideKey === 'claude-code') {
259
363
  // Thin router
260
- const routerContent = `# /chati — Thin Router
261
-
262
- ## CRITICAL — Language Override
364
+ const routerContent = `# /chati — Deterministic Router
263
365
 
264
- Read \`.chati/session.yaml\` field \`language\` BEFORE anything else.
265
- ALL responses MUST be in this language. This overrides any global IDE language setting.
366
+ ## STEP 0 Run the deterministic pipeline router (MUST be first)
266
367
 
267
- | Value | Language |
268
- |-------|----------|
269
- | \`en\` | English |
270
- | \`pt\` | Portugues |
271
- | \`es\` | Espanol |
272
- | \`fr\` | Francais |
368
+ Run via Bash:
369
+ \`\`\`
370
+ node .chati.dev/orchestrator/chati-router.js
371
+ \`\`\`
273
372
 
274
- If session.yaml does not exist or has no language field, default to English.
373
+ Parse the JSON output. This single command already handled: license validation, integrity check, session state, pipeline routing, and team registration (if needed). You do NOT need to run any of these separately.
275
374
 
276
- ---
375
+ ### If \`ok\` is false:
277
376
 
278
- ## Load
377
+ | \`error\` value | What to tell the user |
378
+ |---|---|
379
+ | \`license_invalid\` | Display \`license.reason\` as a friendly message. STOP. |
380
+ | \`license_guard_missing\` | "License enforcement is missing. Reinstall with: npx chati-dev init". STOP. |
381
+ | \`integrity_failed\` | "License enforcement was tampered with. Reinstall with: npx chati-dev init". STOP. |
382
+ | anything else | Display error. STOP. |
279
383
 
280
- Read and execute the full orchestrator at \`${orchestratorPath || 'chati.dev/orchestrator/chati.md'}\`.
384
+ ### If \`ok\` is true:
281
385
 
282
- Pass through all context: session state, handoffs, artifacts, and user input.
386
+ 1. Use \`session.language\` for ALL responses (pt = Português, en = English, es = Español, fr = Français).
387
+ 2. Read the full orchestrator at \`${orchestratorPath || '.chati.dev/orchestrator/chati.md'}\`.
388
+ 3. The \`action\` field tells you what to do. Go to the matching "Action:" section in the orchestrator.
389
+ 4. **If \`action\` is \`spawn_team\`**: the team is ALREADY registered (Step 1 already ran as code). The \`team\` object contains \`team_id\`, \`task_list_path\`, \`mailbox_path\`, \`members\`. Go directly to "Action: Spawn Team" Step 2 and use these values in the agent prompts. DO NOT run \`orchestrate spawn-team\` again — it already ran.
390
+ 5. **If \`action\` is \`setup\`**: this is a new project. Follow "Action: Setup".
391
+ 6. For any other action: follow the matching section in the orchestrator.
283
392
 
284
- **Context to pass:**
285
- - \`.chati/session.yaml\` (session state — includes language)
286
- - \`CLAUDE.local.md\` (runtime state — session lock, current agent)
287
- - \`chati.dev/artifacts/handoffs/\` (latest handoff)
288
- - \`chati.dev/config.yaml\` (version info)
393
+ **The \`pipeline\` object contains the full JSON from \`orchestrate next\`.** Use its fields (agent_file, spawn_command, handoff_status, etc.) as documented in the orchestrator.
289
394
 
290
395
  **User input:** $ARGUMENTS
291
396
  `;
@@ -334,7 +439,7 @@ Pass through all context: session state, handoffs, artifacts, and user input.
334
439
  hooks: [
335
440
  {
336
441
  type: 'command',
337
- command: 'node chati.dev/hooks/license-guard.js',
442
+ command: 'node .chati.dev/hooks/license-guard.js',
338
443
  },
339
444
  ],
340
445
  },
@@ -389,32 +494,31 @@ function generateProviderInstructions(providerName) {
389
494
  # This file configures ${providerName} to work with Chati.dev
390
495
 
391
496
  ## System Location
392
- All system content is in the \`chati.dev/\` directory.
497
+ All system content is in the \`.chati.dev/\` directory (hidden). User artifacts are in \`artifacts/\`.
393
498
 
394
499
  ## Session State
395
500
  Runtime session state is in \`.chati/session.yaml\` (IDE-agnostic).
396
501
 
397
502
  ## Getting Started
398
- The orchestrator is at \`chati.dev/orchestrator/chati.md\`.
399
- Read it to understand routing, session management, and agent activation.
503
+ The orchestrator is at \`.chati.dev/orchestrator/chati.md\`.
400
504
 
401
505
  ## Constitution
402
- Governance rules are in \`chati.dev/constitution.md\` (19 Articles).
506
+ Governance rules are in \`.chati.dev/constitution.md\`.
403
507
 
404
508
  ## Pipeline
405
509
  \`\`\`
406
510
  DISCOVER: WU -> Brief
407
- PLAN: Detail -> Architect -> UX -> Phases -> Tasks -> QA-Planning
408
- BUILD: Dev -> QA-Implementation
511
+ PLAN: [Planning Team: Detail + Architect + UX + QA-Planning] -> Phases -> Tasks
512
+ BUILD: [Build Team: Dev + QA-Implementation] -> QA-Visual
409
513
  DEPLOY: DevOps
410
514
  \`\`\`
411
515
 
412
516
  ## Agents
413
- - DISCOVER: chati.dev/agents/discover/ (3 agents)
414
- - PLAN: chati.dev/agents/plan/ (5 agents)
415
- - Quality: chati.dev/agents/quality/ (2 agents)
416
- - BUILD: chati.dev/agents/build/ (1 agent)
417
- - DEPLOY: chati.dev/agents/deploy/ (1 agent)
517
+ - DISCOVER: .chati.dev/agents/discover/
518
+ - PLAN: .chati.dev/agents/plan/
519
+ - Quality: .chati.dev/agents/quality/
520
+ - BUILD: .chati.dev/agents/build/
521
+ - DEPLOY: .chati.dev/agents/deploy/
418
522
  `;
419
523
  }
420
524
 
@@ -422,36 +526,165 @@ DEPLOY: DevOps
422
526
  * Append chati.dev runtime entries to .gitignore.
423
527
  * Session lock files are runtime-only and should never be committed.
424
528
  */
425
- function updateGitignore(targetDir, selectedIDEs) {
426
- const entries = [
529
+ export function updateGitignore(targetDir, selectedIDEs) {
530
+ const START_MARKER = '# BEGIN Chati.dev — do not edit between markers';
531
+ const END_MARKER = '# END Chati.dev';
532
+ // Legacy marker from pre-4.3.0 installs — detected for migration
533
+ const LEGACY_MARKER = '# Chati.dev runtime files';
534
+
535
+ const block = [
536
+ START_MARKER,
537
+ '# Framework files — generated by chati-dev, not part of your project.',
538
+ '# Do NOT remove these entries. If a framework file is already in git history,',
539
+ '# run: git rm -r --cached <path> after upgrading.',
540
+ '',
541
+ '# Framework (all code, prompts, hooks, orchestrator, data)',
542
+ '.chati.dev/',
427
543
  '',
428
- '# Chati.dev runtime files (session lock not committed)',
544
+ '# Runtime state (session, teams, timeline, transient memories)',
545
+ '.chati/session.yaml',
546
+ '.chati/teams/',
547
+ '.chati/timeline.json',
429
548
  '.chati/memories/*/session/',
549
+ '# NOTE: durable/ and daily/ memories ARE committed — they contain project knowledge',
550
+ '',
551
+ '# Claude Code integration (framework portions only)',
552
+ '.claude/commands/chati.md',
553
+ '.claude/rules/chati/',
554
+ '.claude/settings.local.json',
555
+ '# NOTE: .claude/settings.json IS committed — it is shared team config (hooks, MCPs)',
556
+ '',
557
+ '# Session locks (runtime, per-machine)',
558
+ 'CLAUDE.local.md',
559
+ '.gemini/session-lock.md',
560
+ 'AGENTS.override.md',
430
561
  ];
431
562
 
432
- if (selectedIDEs.includes('claude-code')) {
433
- entries.push('CLAUDE.local.md');
434
- }
435
563
  if (selectedIDEs.includes('gemini-cli')) {
436
- entries.push('.gemini/session-lock.md');
564
+ block.push(
565
+ '',
566
+ '# Gemini provider framework files',
567
+ '.gemini/commands/',
568
+ '.gemini/context/',
569
+ '.gemini/hooks/',
570
+ '.gemini/settings.json',
571
+ );
437
572
  }
573
+
438
574
  if (selectedIDEs.includes('codex-cli')) {
439
- entries.push('AGENTS.override.md');
575
+ block.push(
576
+ '',
577
+ '# Codex provider framework files',
578
+ '.codex/',
579
+ '.agents/skills/chati/',
580
+ );
440
581
  }
441
582
 
442
- entries.push('');
583
+ block.push('', END_MARKER);
443
584
 
585
+ const newBlock = block.join('\n');
444
586
  const gitignorePath = join(targetDir, '.gitignore');
445
- const marker = '# Chati.dev runtime files';
446
587
 
447
- if (existsSync(gitignorePath)) {
448
- const existing = readFileSync(gitignorePath, 'utf-8');
449
- // Don't duplicate if already present
450
- if (existing.includes(marker)) return;
451
- writeFileSync(gitignorePath, existing.trimEnd() + '\n' + entries.join('\n'), 'utf-8');
452
- } else {
453
- writeFileSync(gitignorePath, entries.join('\n'), 'utf-8');
588
+ if (!existsSync(gitignorePath)) {
589
+ writeFileSync(gitignorePath, newBlock + '\n', 'utf-8');
590
+ return;
591
+ }
592
+
593
+ let existing = readFileSync(gitignorePath, 'utf-8');
594
+
595
+ // Strip new-style block if present (idempotency)
596
+ const newStartIdx = existing.indexOf(START_MARKER);
597
+ const newEndIdx = existing.indexOf(END_MARKER);
598
+ if (newStartIdx !== -1 && newEndIdx !== -1) {
599
+ existing = existing.slice(0, newStartIdx) + existing.slice(newEndIdx + END_MARKER.length);
600
+ existing = existing.replace(/\n{3,}/g, '\n\n').trimEnd();
601
+ }
602
+
603
+ // Strip legacy block if present (pre-4.3.0)
604
+ if (existing.includes(LEGACY_MARKER)) {
605
+ const lines = existing.split('\n');
606
+ const legacyIdx = lines.findIndex(l => l.includes(LEGACY_MARKER));
607
+ if (legacyIdx !== -1) {
608
+ // Remove legacy marker line + the short known-legacy entries that followed
609
+ const legacyEntries = new Set([
610
+ '.chati/memories/*/session/',
611
+ 'CLAUDE.local.md',
612
+ '.gemini/session-lock.md',
613
+ 'AGENTS.override.md',
614
+ '',
615
+ ]);
616
+ let endIdx = legacyIdx + 1;
617
+ while (endIdx < lines.length && legacyEntries.has(lines[endIdx].trim())) endIdx++;
618
+ lines.splice(legacyIdx, endIdx - legacyIdx);
619
+ existing = lines.join('\n').trimEnd();
620
+ }
454
621
  }
622
+
623
+ writeFileSync(gitignorePath, existing.trimEnd() + '\n\n' + newBlock + '\n', 'utf-8');
624
+ }
625
+
626
+ /**
627
+ * Section-aware merge for CLAUDE.md: preserves user-authored sections and
628
+ * framework-managed sections (those with markers). Framework sections are
629
+ * re-rendered from the fresh template; everything else is kept as-is.
630
+ */
631
+ function mergeClaudeMd(existing, fresh) {
632
+ // Framework-managed sections — these come from the install template AND from
633
+ // magic-docs runtime updates. Replace these from fresh template; preserve all others.
634
+ const frameworkSections = [
635
+ // Install template (templates.js generateClaudeMd)
636
+ '## Project Context',
637
+ '## Chati.dev',
638
+ // Magic-docs runtime sections (when project starts)
639
+ '## Current State',
640
+ '## Session Lock',
641
+ '## Recent Decisions',
642
+ '## Framework',
643
+ '## How to Resume',
644
+ ];
645
+
646
+ // If existing has no framework sections at all, user has a custom CLAUDE.md;
647
+ // just append the framework template separator + fresh template below.
648
+ const hasAnyFwSection = frameworkSections.some(h => existing.includes(h));
649
+ if (!hasAnyFwSection) {
650
+ return existing.trimEnd() + '\n\n<!-- Chati.dev framework context (auto-managed) -->\n\n' + fresh;
651
+ }
652
+
653
+ // Otherwise, do section-level replacement — for each framework section in fresh,
654
+ // replace the corresponding section in existing (or append if missing).
655
+ let merged = existing;
656
+ for (const header of frameworkSections) {
657
+ const freshSection = extractSection(fresh, header);
658
+ if (!freshSection) continue;
659
+ if (merged.includes(header)) {
660
+ merged = replaceSection(merged, header, freshSection);
661
+ } else {
662
+ merged = merged.trimEnd() + '\n\n' + freshSection;
663
+ }
664
+ }
665
+ return merged;
666
+ }
667
+
668
+ function extractSection(content, header) {
669
+ const lines = content.split('\n');
670
+ const startIdx = lines.findIndex(l => l.trim() === header);
671
+ if (startIdx === -1) return null;
672
+ let endIdx = startIdx + 1;
673
+ while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
674
+ return lines.slice(startIdx, endIdx).join('\n');
675
+ }
676
+
677
+ function replaceSection(content, header, newSection) {
678
+ const lines = content.split('\n');
679
+ const startIdx = lines.findIndex(l => l.trim() === header);
680
+ if (startIdx === -1) return content;
681
+ let endIdx = startIdx + 1;
682
+ while (endIdx < lines.length && !lines[endIdx].match(/^##\s/)) endIdx++;
683
+ return [
684
+ ...lines.slice(0, startIdx),
685
+ newSection,
686
+ ...lines.slice(endIdx),
687
+ ].join('\n');
455
688
  }
456
689
 
457
690
  /**
@@ -463,6 +696,114 @@ function createDir(dir) {
463
696
  }
464
697
  }
465
698
 
699
+ /**
700
+ * Recursively copy CLI source (excluding tests, node_modules, hidden files).
701
+ * Used to bundle the CLI source into the user's .chati.dev/_cli/ folder so
702
+ * the framework runs self-contained (no `npx chati-dev` dependency).
703
+ */
704
+ function copyCliSource(srcDir, destDir) {
705
+ if (!existsSync(srcDir)) return;
706
+ createDir(destDir);
707
+ const entries = readdirSync(srcDir);
708
+ for (const entry of entries) {
709
+ if (entry.startsWith('.')) continue;
710
+ if (entry === 'node_modules' || entry === 'test' || entry === 'tests') continue;
711
+ const srcPath = join(srcDir, entry);
712
+ const destPath = join(destDir, entry);
713
+ let stat;
714
+ try { stat = statSync(srcPath); } catch { continue; }
715
+ if (stat.isDirectory()) {
716
+ copyCliSource(srcPath, destPath);
717
+ } else {
718
+ copyFileSync(srcPath, destPath);
719
+ }
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Recursively copy a directory verbatim (used for bundling node_modules trees).
725
+ */
726
+ function copyDirVerbatim(srcDir, destDir) {
727
+ if (!existsSync(srcDir)) return;
728
+ createDir(destDir);
729
+ for (const entry of readdirSync(srcDir)) {
730
+ const srcPath = join(srcDir, entry);
731
+ const destPath = join(destDir, entry);
732
+ let stat;
733
+ try { stat = statSync(srcPath); } catch { continue; }
734
+ if (stat.isDirectory()) {
735
+ copyDirVerbatim(srcPath, destPath);
736
+ } else {
737
+ try { copyFileSync(srcPath, destPath); } catch { /* skip unreadable */ }
738
+ }
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Bundle chati-dev's production dependencies into .chati.dev/_cli/node_modules/
744
+ * so the bundled CLI can import js-yaml, chalk, etc. without relying on a
745
+ * project-level node_modules tree.
746
+ *
747
+ * Uses createRequire to resolve each dep from the installed chati-dev package,
748
+ * which works whether the package is hoisted or nested in the user's
749
+ * node_modules (npx cache, npm install, etc.).
750
+ */
751
+ function copyCliDependencies(pkgDir, destNodeModules) {
752
+ let pkg;
753
+ try {
754
+ pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
755
+ } catch { return; }
756
+ const deps = [
757
+ ...Object.keys(pkg.dependencies || {}),
758
+ ...Object.keys(pkg.optionalDependencies || {}),
759
+ ];
760
+ if (deps.length === 0) return;
761
+
762
+ // Resolve each dep from the package.json location (works for hoisted + nested layouts).
763
+ // eslint-disable-next-line no-undef
764
+ const require = createRequire(join(pkgDir, 'package.json'));
765
+ createDir(destNodeModules);
766
+
767
+ const visited = new Set();
768
+ const queue = [...deps];
769
+
770
+ while (queue.length > 0) {
771
+ const depName = queue.shift();
772
+ if (visited.has(depName)) continue;
773
+ visited.add(depName);
774
+
775
+ let depDir;
776
+ try {
777
+ // Try direct package.json resolve first (fast path)
778
+ depDir = dirname(require.resolve(`${depName}/package.json`));
779
+ } catch {
780
+ // Fallback: resolve the main entry then walk up to find package root.
781
+ // Needed for packages with strict "exports" (chalk, ora, etc.).
782
+ try {
783
+ let p = require.resolve(depName);
784
+ while (p && p !== dirname(p)) {
785
+ p = dirname(p);
786
+ if (existsSync(join(p, 'package.json')) && basename(p) === depName.split('/').pop()) {
787
+ depDir = p;
788
+ break;
789
+ }
790
+ }
791
+ } catch { continue; }
792
+ if (!depDir) continue;
793
+ }
794
+ const depPkgJsonPath = join(depDir, 'package.json');
795
+ copyDirVerbatim(depDir, join(destNodeModules, depName));
796
+
797
+ // Include transitive deps
798
+ try {
799
+ const depPkg = JSON.parse(readFileSync(depPkgJsonPath, 'utf-8'));
800
+ for (const t of Object.keys(depPkg.dependencies || {})) {
801
+ if (!visited.has(t)) queue.push(t);
802
+ }
803
+ } catch { /* continue */ }
804
+ }
805
+ }
806
+
466
807
  /**
467
808
  * Write .claude/settings.json with safe merge against existing user content.
468
809
  *