chati-dev 4.2.2 → 4.3.1

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