chati-dev 4.2.1 → 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 (222) 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 +8 -7
  23. package/framework/constitution.md +127 -43
  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 +91 -36
  35. package/framework/hooks/license-guard.js +129 -190
  36. package/framework/hooks/mode-governance.js +68 -16
  37. package/framework/hooks/model-governance.js +19 -8
  38. package/framework/hooks/package.json +3 -0
  39. package/framework/hooks/post-dev.js +207 -0
  40. package/framework/hooks/prism-engine.js +288 -105
  41. package/framework/hooks/read-protection.js +37 -9
  42. package/framework/hooks/reasoning-escalator.js +371 -0
  43. package/framework/hooks/reference-trigger.js +117 -0
  44. package/framework/hooks/session-digest.js +94 -20
  45. package/framework/hooks/settings.json +32 -1
  46. package/framework/hooks/style-guard.js +52 -13
  47. package/framework/hooks/team-quality-gate.js +58 -25
  48. package/framework/hooks/undercover-guard.js +34 -13
  49. package/framework/i18n/en.yaml +3 -3
  50. package/framework/i18n/es.yaml +3 -3
  51. package/framework/i18n/fr.yaml +3 -3
  52. package/framework/i18n/pt.yaml +3 -3
  53. package/framework/intelligence/context-engine.md +4 -5
  54. package/framework/intelligence/decision-engine.md +1 -1
  55. package/framework/intelligence/hooks-performance.md +3 -3
  56. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  57. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  58. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  59. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  60. package/framework/orchestrator/chati-router.js +387 -0
  61. package/framework/orchestrator/chati-update.md +40 -40
  62. package/framework/orchestrator/chati.md +318 -115
  63. package/framework/scaffold/motion-premium/README.md +89 -0
  64. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  65. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  66. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  67. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  68. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  69. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  70. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  71. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  72. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  73. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  74. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  76. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  77. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  78. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  79. package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
  80. package/framework/scaffold/motion-premium-3d/README.md +80 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  82. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  83. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  88. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  89. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  90. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  91. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  92. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  93. package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
  94. package/framework/schemas/session.schema.json +109 -21
  95. package/framework/scripts/reference-capture.js +430 -0
  96. package/framework/scripts/visual-qa.js +674 -0
  97. package/framework/tasks/orchestrator-handoff.md +1 -1
  98. package/framework/tasks/orchestrator-resume.md +1 -1
  99. package/framework/tasks/orchestrator-route.md +1 -1
  100. package/framework/tasks/orchestrator-status.md +3 -3
  101. package/framework/tasks/qa-planning-gate-define.md +1 -1
  102. package/framework/templates/brandbook-html-tmpl.md +1 -1
  103. package/framework/templates/brandbook-tmpl.yaml +1 -1
  104. package/framework/templates/component-spec-tmpl.yaml +1 -1
  105. package/framework/templates/design-token-tmpl.yaml +1 -1
  106. package/framework/templates/icon-system-tmpl.yaml +1 -1
  107. package/framework/templates/team-planning-tasks.yaml +6 -5
  108. package/framework/workflows/brownfield-discovery.yaml +2 -2
  109. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  110. package/framework/workflows/brownfield-service.yaml +14 -10
  111. package/framework/workflows/brownfield-ui.yaml +15 -11
  112. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  113. package/framework/workflows/quick-flow.yaml +3 -3
  114. package/framework/workflows/standard-flow.yaml +12 -9
  115. package/package.json +10 -5
  116. package/src/autonomy/autonomous-gate.js +1 -0
  117. package/src/autonomy/build-state.js +1 -2
  118. package/src/autonomy/progress-reporter.js +1 -1
  119. package/src/config/agent-customizer.js +11 -3
  120. package/src/config/claude-settings-generator.js +226 -0
  121. package/src/config/context-file-generator.js +41 -21
  122. package/src/config/framework-adapter.js +1 -0
  123. package/src/config/gemini-hooks-generator.js +76 -6
  124. package/src/config/mcp-configs.js +1 -0
  125. package/src/context/layers/l1-global.js +2 -1
  126. package/src/dashboard/data-reader.js +4 -3
  127. package/src/dashboard/layout.js +2 -1
  128. package/src/decision/analyzer.js +6 -30
  129. package/src/decision/engine.js +4 -28
  130. package/src/decision/registry-healer.js +3 -2
  131. package/src/decision/registry-updater.js +23 -14
  132. package/src/extensions/loader.js +2 -8
  133. package/src/gates/g1-planning-complete.js +2 -1
  134. package/src/gates/g2-qa-planning.js +2 -1
  135. package/src/gates/g3-implementation.js +2 -1
  136. package/src/gates/g4-qa-implementation.js +3 -2
  137. package/src/gates/g5-deploy-ready.js +2 -1
  138. package/src/health/engine.js +4 -3
  139. package/src/installer/core.js +557 -81
  140. package/src/installer/preflight.js +131 -0
  141. package/src/installer/provider-overlay.js +3 -3
  142. package/src/installer/scaffold-applier.js +358 -0
  143. package/src/installer/templates.js +52 -31
  144. package/src/installer/validator.js +17 -12
  145. package/src/intelligence/registry-manager.js +22 -29
  146. package/src/intelligence/timeline.js +11 -6
  147. package/src/license/commands.js +1 -1
  148. package/src/license/wait.js +102 -0
  149. package/src/memory/agent-memory.js +81 -0
  150. package/src/memory/dream.js +32 -1
  151. package/src/merger/replace-merger.js +28 -15
  152. package/src/orchestrator/agent-selector.js +2 -1
  153. package/src/orchestrator/cli.js +1908 -80
  154. package/src/orchestrator/doctor.js +270 -0
  155. package/src/orchestrator/handoff-engine.js +4 -3
  156. package/src/orchestrator/index.js +2 -0
  157. package/src/orchestrator/pipeline-manager.js +306 -15
  158. package/src/orchestrator/session-manager.js +331 -6
  159. package/src/tasks/handoff.js +3 -2
  160. package/src/telemetry/config.js +4 -3
  161. package/src/telemetry/schema.js +1 -0
  162. package/src/terminal/collector.js +3 -2
  163. package/src/terminal/index.js +1 -2
  164. package/src/terminal/isolation.js +52 -18
  165. package/src/terminal/prompt-builder.js +42 -25
  166. package/src/terminal/run-parallel.js +1 -1
  167. package/src/terminal/run-team.js +3 -3
  168. package/src/terminal/team-task-list.js +43 -4
  169. package/src/upgrade/backup.js +3 -2
  170. package/src/upgrade/checker.js +3 -2
  171. package/src/upgrade/migrator.js +65 -7
  172. package/src/upgrade/tracked-files-detector.js +86 -0
  173. package/src/upgrade/user-messages.js +94 -0
  174. package/src/utils/config-parser.js +2 -1
  175. package/src/utils/feature-flags.js +2 -1
  176. package/src/utils/flatten-entities.js +69 -0
  177. package/src/utils/framework-dir.js +16 -0
  178. package/src/utils/model-id.js +85 -0
  179. package/src/utils/provider-limits.js +84 -23
  180. package/src/utils/schema-validator.js +1 -1
  181. package/src/wizard/i18n.js +5 -4
  182. package/src/wizard/index.js +14 -0
  183. package/assets/logo - c/303/263pia.png +0 -0
  184. package/assets/logo.svg +0 -42
  185. package/assets/logo2.png +0 -0
  186. package/assets/social-preview.png +0 -0
  187. package/scripts/bundle-framework.js +0 -69
  188. package/scripts/changelog-generator.js +0 -222
  189. package/scripts/codebase-mapper.js +0 -728
  190. package/scripts/commit-message-generator.js +0 -167
  191. package/scripts/coverage-analyzer.js +0 -260
  192. package/scripts/dependency-analyzer.js +0 -280
  193. package/scripts/doctor/checks/agents.js +0 -77
  194. package/scripts/doctor/checks/constitution.js +0 -41
  195. package/scripts/doctor/checks/domain-alignment.js +0 -58
  196. package/scripts/doctor/checks/prism-layers.js +0 -84
  197. package/scripts/doctor/checks/registry.js +0 -55
  198. package/scripts/doctor/checks/schemas.js +0 -61
  199. package/scripts/doctor/fixes/reference-fix.js +0 -100
  200. package/scripts/doctor/fixes/registry-fix.js +0 -56
  201. package/scripts/doctor/index.js +0 -212
  202. package/scripts/framework-analyzer.js +0 -308
  203. package/scripts/generate-constitution-domain.js +0 -253
  204. package/scripts/generate-signing-key.js +0 -33
  205. package/scripts/health-check.js +0 -481
  206. package/scripts/ide-sync.js +0 -326
  207. package/scripts/performance-analyzer.js +0 -325
  208. package/scripts/plan-tracker.js +0 -278
  209. package/scripts/populate-entity-registry.js +0 -481
  210. package/scripts/pr-review.js +0 -317
  211. package/scripts/rollback-manager.js +0 -310
  212. package/scripts/semantic-lint.js +0 -328
  213. package/scripts/sign-manifest.js +0 -53
  214. package/scripts/stuck-detector.js +0 -343
  215. package/scripts/test-quality-assessment.js +0 -257
  216. package/scripts/validate-agents.js +0 -368
  217. package/scripts/validate-package.js +0 -367
  218. package/scripts/validate-tasks.js +0 -465
  219. package/src/autonomy/worktree-manager.js +0 -250
  220. package/src/intelligence/decision-engine.js +0 -256
  221. package/src/intelligence/document-sharder.js +0 -221
  222. package/src/intelligence/elicitation.js +0 -265
@@ -6,36 +6,49 @@ import yaml from 'js-yaml';
6
6
  export function generateSessionYaml(config) {
7
7
  const { projectName, projectType, language, selectedIDEs, selectedMCPs, llmProvider, allProviders } = config;
8
8
 
9
+ // Schema MUST match session-manager.js DEFAULT_SESSION fields,
10
+ // otherwise validateSession() rejects installer-generated sessions.
9
11
  const session = {
10
12
  schema_version: '1.0',
13
+ version: '1.0',
14
+ mode: 'discover',
11
15
  project: {
12
16
  name: projectName,
13
17
  type: projectType,
14
18
  state: 'discover',
15
19
  },
16
- execution_mode: 'interactive',
17
- execution_profile: 'guided',
18
- current_agent: '',
19
20
  language: language,
21
+ project_type: projectType,
22
+ started_at: new Date().toISOString(),
23
+ current_agent: '',
24
+ pipeline_position: 0,
25
+ execution_mode: 'interactive',
26
+ user_level: 'auto',
27
+ user_level_confidence: 0.0,
20
28
  ides: selectedIDEs,
21
29
  mcps: selectedMCPs,
22
30
  providers_enabled: allProviders && allProviders.length > 0 ? allProviders : [llmProvider || 'claude'],
23
31
  primary_provider: llmProvider || 'claude',
24
32
  active_provider: llmProvider || 'claude',
25
- user_level: 'auto',
26
- user_level_confidence: 0.0,
33
+ completed_agents: [],
34
+ agent_results: {},
35
+ mode_transitions: [],
36
+ deviations: [],
27
37
  agents: {},
28
38
  backlog: [],
29
39
  last_handoff: '',
30
- deviations: [],
31
- profile_transitions: [],
40
+ // Article XXIII — Reasoning Tier Governance.
41
+ // Tier starts at 'standard' and is promoted by reasoning-escalator.js
42
+ // in response to friction signals. See chati.dev/hooks/reasoning-escalator.js.
43
+ reasoning_tier: 'standard',
44
+ reasoning_escalations: [],
32
45
  };
33
46
 
34
- // Initialize all 12 agent statuses
47
+ // Initialize all 13 agent statuses
35
48
  const agentNames = [
36
49
  'greenfield-wu', 'brownfield-wu', 'brief', 'detail',
37
50
  'architect', 'ux', 'phases', 'tasks',
38
- 'qa-planning', 'dev', 'qa-implementation', 'devops',
51
+ 'qa-planning', 'dev', 'qa-implementation', 'qa-visual', 'devops',
39
52
  ];
40
53
 
41
54
  for (const agent of agentNames) {
@@ -62,7 +75,7 @@ export const PROVIDER_MODEL_MAPS = {
62
75
  orchestrator: 'sonnet', 'greenfield-wu': 'haiku', 'brownfield-wu': 'opus',
63
76
  brief: 'sonnet', detail: 'opus', architect: 'opus', ux: 'sonnet',
64
77
  phases: 'sonnet', tasks: 'sonnet', 'qa-planning': 'opus',
65
- dev: 'opus', 'qa-implementation': 'opus', devops: 'sonnet',
78
+ dev: 'opus', 'qa-implementation': 'opus', 'qa-visual': 'opus', devops: 'sonnet',
66
79
  },
67
80
  },
68
81
  gemini: {
@@ -71,7 +84,7 @@ export const PROVIDER_MODEL_MAPS = {
71
84
  orchestrator: 'flash', 'greenfield-wu': 'flash', 'brownfield-wu': 'pro',
72
85
  brief: 'flash', detail: 'pro', architect: 'pro', ux: 'flash',
73
86
  phases: 'flash', tasks: 'flash', 'qa-planning': 'pro',
74
- dev: 'pro', 'qa-implementation': 'pro', devops: 'flash',
87
+ dev: 'pro', 'qa-implementation': 'pro', 'qa-visual': 'pro', devops: 'flash',
75
88
  },
76
89
  },
77
90
  codex: {
@@ -80,7 +93,7 @@ export const PROVIDER_MODEL_MAPS = {
80
93
  orchestrator: 'mini', 'greenfield-wu': 'mini', 'brownfield-wu': 'codex',
81
94
  brief: 'mini', detail: 'codex', architect: 'codex', ux: 'mini',
82
95
  phases: 'mini', tasks: 'mini', 'qa-planning': 'codex',
83
- dev: 'codex', 'qa-implementation': 'codex', devops: 'mini',
96
+ dev: 'codex', 'qa-implementation': 'codex', 'qa-visual': 'codex', devops: 'mini',
84
97
  },
85
98
  },
86
99
  };
@@ -89,7 +102,8 @@ export const PROVIDER_MODEL_MAPS = {
89
102
  * Generate config.yaml content
90
103
  */
91
104
  export function generateConfigYaml(config) {
92
- const { version, projectType, language, selectedIDEs, llmProvider, allProviders } = config;
105
+ const { version, projectType, language, llmProvider, allProviders } = config;
106
+ const selectedIDEs = config.selectedIDEs || [];
93
107
  const provider = llmProvider || 'claude';
94
108
  const providers = allProviders && allProviders.length > 0 ? allProviders : [provider];
95
109
 
@@ -134,9 +148,13 @@ export function generateConfigYaml(config) {
134
148
  model_fallback: true,
135
149
  frustration_detection: true,
136
150
  bash_security_checks: true,
137
- // Agent Teams (v4.2.0 — Article XXI)
138
- agent_teams: false,
139
- team_planning_size: 3,
151
+ // Agent Teams (Article XXI) — default ON in v4.2.2.
152
+ // Only effective when provider === 'claude' (gated in cli.js
153
+ // isAgentTeamsEnabled). Gemini and Codex always fall back to sequential
154
+ // pipeline silently. See plan: was previously false for "safe rollout"
155
+ // but the v4.2.0 launch never actually shipped, so this is a fresh start.
156
+ agent_teams: true,
157
+ team_planning_size: 4,
140
158
  team_build_size: 2,
141
159
  team_echo_threshold: 0.92,
142
160
  team_correction_cycles_max: 2,
@@ -189,7 +207,7 @@ Type \`/chati\` to start.
189
207
  * Also reads AGENTS.md for project context (auto-generated by context-file-generator).
190
208
  */
191
209
  export function generateCodexSkill(options = {}) {
192
- const orchPath = options.orchestratorPath || 'chati.dev/orchestrator/chati.md';
210
+ const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
193
211
  return `---
194
212
  name: chati
195
213
  description: >
@@ -227,8 +245,8 @@ Read and execute the full orchestrator at \`${orchPath}\`.
227
245
  **Context to pass:**
228
246
  - \`.chati/session.yaml\` (session state — includes language)
229
247
  - \`AGENTS.md\` (project context)
230
- - \`chati.dev/artifacts/handoffs/\` (latest handoff)
231
- - \`chati.dev/config.yaml\` (version info)
248
+ - \`artifacts/handoffs/\` (latest handoff)
249
+ - \`.chati.dev/config.yaml\` (version info)
232
250
 
233
251
  **User input:** $ARGUMENTS
234
252
  `;
@@ -236,12 +254,9 @@ Read and execute the full orchestrator at \`${orchPath}\`.
236
254
 
237
255
  /**
238
256
  * Generate Gemini CLI TOML command (.gemini/commands/chati.toml)
239
- *
240
- * Gemini CLI custom commands use TOML format with `description` and `prompt` fields.
241
- * Supports {{args}} for user arguments and @{file} for file injection.
242
257
  */
243
258
  export function generateGeminiRouter(options = {}) {
244
- const orchPath = options.orchestratorPath || 'chati.dev/orchestrator/chati.md';
259
+ const orchPath = options.orchestratorPath || '.chati.dev/orchestrator/chati.md';
245
260
  return `description = "Activate Chati.dev orchestrator"
246
261
  prompt = """
247
262
  CRITICAL — Language Override:
@@ -258,8 +273,8 @@ NEVER create or reference CLAUDE.md, CLAUDE.local.md, or .claude/ directories.
258
273
  Context to load:
259
274
  - .chati/session.yaml (session state — includes language)
260
275
  - GEMINI.md (project context)
261
- - chati.dev/artifacts/handoffs/ (latest handoff)
262
- - chati.dev/config.yaml (version info)
276
+ - artifacts/handoffs/ (latest handoff)
277
+ - .chati.dev/config.yaml (version info)
263
278
 
264
279
  User input: {{args}}
265
280
  """
@@ -272,15 +287,17 @@ User input: {{args}}
272
287
  export function generateClaudeLocalMd() {
273
288
  return `# Chati.dev Runtime State
274
289
 
290
+ <!-- chati-lock:start -->
275
291
  ## Session Lock
276
292
  **Status: INACTIVE** — Type \`/chati\` to activate.
293
+ <!-- chati-lock:end -->
277
294
 
278
- <!-- SESSION-LOCK:INACTIVE -->
279
-
295
+ <!-- chati-state:start -->
280
296
  ## Current State
281
297
  - **Agent**: None (ready to start)
282
298
  - **Pipeline**: Pre-start
283
299
  - **Mode**: interactive
300
+ <!-- chati-state:end -->
284
301
 
285
302
  ## Recent Decisions
286
303
  _No decisions yet. Start with /chati._
@@ -297,15 +314,17 @@ _Auto-updated by Chati.dev orchestrator_
297
314
  export function generateGeminiSessionLock() {
298
315
  return `# Chati.dev Runtime State
299
316
 
317
+ <!-- chati-lock:start -->
300
318
  ## Session Lock
301
319
  **Status: INACTIVE** — Type \`/chati\` to activate.
320
+ <!-- chati-lock:end -->
302
321
 
303
- <!-- SESSION-LOCK:INACTIVE -->
304
-
322
+ <!-- chati-state:start -->
305
323
  ## Current State
306
324
  - **Agent**: None (ready to start)
307
325
  - **Pipeline**: Pre-start
308
326
  - **Mode**: interactive
327
+ <!-- chati-state:end -->
309
328
 
310
329
  ## Recent Decisions
311
330
  _No decisions yet. Start with /chati._
@@ -322,15 +341,17 @@ _Auto-updated by Chati.dev orchestrator_
322
341
  export function generateAgentsOverrideMd() {
323
342
  return `# Chati.dev Runtime State
324
343
 
344
+ <!-- chati-lock:start -->
325
345
  ## Session Lock
326
346
  **Status: INACTIVE** — Type \`$chati\` to activate.
347
+ <!-- chati-lock:end -->
327
348
 
328
- <!-- SESSION-LOCK:INACTIVE -->
329
-
349
+ <!-- chati-state:start -->
330
350
  ## Current State
331
351
  - **Agent**: None (ready to start)
332
352
  - **Pipeline**: Pre-start
333
353
  - **Mode**: interactive
354
+ <!-- chati-state:end -->
334
355
 
335
356
  ## Recent Decisions
336
357
  _No decisions yet. Start with $chati._
@@ -6,6 +6,11 @@ import { loadManifest } from './manifest.js';
6
6
  import { ADAPTABLE_FILES } from '../config/framework-adapter.js';
7
7
  import { validateSchema, CONFIG_SCHEMA } from '../utils/schema-validator.js';
8
8
 
9
+ function resolveFrameworkDir(targetDir) {
10
+ if (existsSync(join(targetDir, '.chati.dev'))) return '.chati.dev';
11
+ return 'chati.dev';
12
+ }
13
+
9
14
  /**
10
15
  * Validate chati.dev installation
11
16
  * Checks all primary agents, constitution, session, schemas, etc.
@@ -47,7 +52,7 @@ export async function validateInstallation(targetDir) {
47
52
 
48
53
  let agentCount = 0;
49
54
  for (const file of agentFiles) {
50
- const filePath = join(targetDir, 'chati.dev', file);
55
+ const filePath = join(targetDir, resolveFrameworkDir(targetDir), file);
51
56
  if (existsSync(filePath)) {
52
57
  const content = readFileSync(filePath, 'utf-8');
53
58
  // Check that agent implements protocols (look for key protocol references)
@@ -63,11 +68,11 @@ export async function validateInstallation(targetDir) {
63
68
  if (results.agents.pass) results.passed += 1;
64
69
 
65
70
  // Check constitution
66
- const constitutionPath = join(targetDir, 'chati.dev', 'constitution.md');
71
+ const constitutionPath = join(targetDir, resolveFrameworkDir(targetDir), 'constitution.md');
67
72
  if (existsSync(constitutionPath)) {
68
73
  const content = readFileSync(constitutionPath, 'utf-8');
69
74
  const articleCount = (content.match(/^## Article/gm) || []).length;
70
- results.constitution.pass = articleCount >= 19;
75
+ results.constitution.pass = articleCount >= 22;
71
76
  results.constitution.details.push({ articleCount });
72
77
  }
73
78
  results.total += 1;
@@ -86,7 +91,7 @@ export async function validateInstallation(targetDir) {
86
91
  ];
87
92
  let schemaCount = 0;
88
93
  for (const file of schemaFiles) {
89
- if (existsSync(join(targetDir, 'chati.dev', 'schemas', file))) {
94
+ if (existsSync(join(targetDir, resolveFrameworkDir(targetDir), 'schemas', file))) {
90
95
  schemaCount++;
91
96
  }
92
97
  }
@@ -102,7 +107,7 @@ export async function validateInstallation(targetDir) {
102
107
  ];
103
108
  let workflowCount = 0;
104
109
  for (const file of workflowFiles) {
105
- if (existsSync(join(targetDir, 'chati.dev', 'workflows', file))) {
110
+ if (existsSync(join(targetDir, resolveFrameworkDir(targetDir), 'workflows', file))) {
106
111
  workflowCount++;
107
112
  }
108
113
  }
@@ -118,7 +123,7 @@ export async function validateInstallation(targetDir) {
118
123
  ];
119
124
  let templateCount = 0;
120
125
  for (const file of templateFiles) {
121
- if (existsSync(join(targetDir, 'chati.dev', 'templates', file))) {
126
+ if (existsSync(join(targetDir, resolveFrameworkDir(targetDir), 'templates', file))) {
122
127
  templateCount++;
123
128
  }
124
129
  }
@@ -137,7 +142,7 @@ export async function validateInstallation(targetDir) {
137
142
  ];
138
143
  let intelCount = 0;
139
144
  for (const file of intelligenceFiles) {
140
- if (existsSync(join(targetDir, 'chati.dev', file))) {
145
+ if (existsSync(join(targetDir, resolveFrameworkDir(targetDir), file))) {
141
146
  intelCount++;
142
147
  }
143
148
  }
@@ -147,7 +152,7 @@ export async function validateInstallation(targetDir) {
147
152
  if (results.intelligence.pass) results.passed += 1;
148
153
 
149
154
  // Check entity registry
150
- const registryPath = join(targetDir, 'chati.dev', 'data', 'entity-registry.yaml');
155
+ const registryPath = join(targetDir, resolveFrameworkDir(targetDir), 'data', 'entity-registry.yaml');
151
156
  results.registry.pass = existsSync(registryPath);
152
157
  results.total += 1;
153
158
  if (results.registry.pass) results.passed += 1;
@@ -164,7 +169,7 @@ export async function validateInstallation(targetDir) {
164
169
  let contextCount = 0;
165
170
  for (const file of contextFileNames) {
166
171
  // Check source (chati.dev/context/) OR deployed (.claude/rules/chati/)
167
- const inSource = existsSync(join(targetDir, 'chati.dev', 'context', file));
172
+ const inSource = existsSync(join(targetDir, resolveFrameworkDir(targetDir), 'context', file));
168
173
  const inRules = existsSync(join(targetDir, '.claude', 'rules', 'chati', file));
169
174
  if (inSource || inRules) {
170
175
  contextCount++;
@@ -176,7 +181,7 @@ export async function validateInstallation(targetDir) {
176
181
  if (results.context.pass) results.passed += 1;
177
182
 
178
183
  // Check provider overlays (if multiple providers configured)
179
- const configYamlPath = join(targetDir, 'chati.dev', 'config.yaml');
184
+ const configYamlPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
180
185
  if (existsSync(configYamlPath)) {
181
186
  try {
182
187
  const configContent = readFileSync(configYamlPath, 'utf-8');
@@ -208,7 +213,7 @@ export async function validateInstallation(targetDir) {
208
213
  const missingFiles = [];
209
214
 
210
215
  for (const file of ADAPTABLE_FILES) {
211
- const overlayPath = join(targetDir, 'chati.dev', '.adapted', provider, file);
216
+ const overlayPath = join(targetDir, resolveFrameworkDir(targetDir), '.adapted', provider, file);
212
217
  if (existsSync(overlayPath)) {
213
218
  present++;
214
219
  } else {
@@ -253,7 +258,7 @@ export async function validateInstallation(targetDir) {
253
258
  const mismatches = [];
254
259
 
255
260
  for (const [relPath, entry] of Object.entries(manifest.files)) {
256
- const absPath = join(targetDir, 'chati.dev', relPath);
261
+ const absPath = join(targetDir, resolveFrameworkDir(targetDir), relPath);
257
262
  if (existsSync(absPath)) {
258
263
  const currentHash = hashFile(absPath);
259
264
  if (currentHash === entry.hash) {
@@ -1,14 +1,25 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import yaml from 'js-yaml';
4
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
5
+ import { flattenEntitiesMap } from '../utils/flatten-entities.js';
6
+
7
+ /**
8
+ * Get the entity registry path for a given project.
9
+ * Supports both .chati.dev/ and chati.dev/ structures.
10
+ * @param {string} targetDir
11
+ * @returns {string} Relative path from project root
12
+ */
13
+ function getRegistryPath(targetDir) {
14
+ return `${resolveFrameworkDir(targetDir)}/data/entity-registry.yaml`;
15
+ }
4
16
 
5
- const REGISTRY_PATH = 'chati.dev/data/entity-registry.yaml';
6
17
 
7
18
  /**
8
19
  * Check registry integrity against filesystem
9
20
  */
10
21
  export function checkRegistry(targetDir) {
11
- const registryPath = join(targetDir, REGISTRY_PATH);
22
+ const registryPath = join(targetDir, getRegistryPath(targetDir));
12
23
  if (!existsSync(registryPath)) {
13
24
  return { valid: false, error: 'Entity registry not found', missing: [], orphaned: [] };
14
25
  }
@@ -22,7 +33,7 @@ export function checkRegistry(targetDir) {
22
33
  const found = [];
23
34
 
24
35
  // Check each registered entity exists on disk
25
- const entities = flattenEntities(registry.entities);
36
+ const entities = flattenEntitiesMap(registry.entities);
26
37
  for (const entity of entities) {
27
38
  const filePath = join(targetDir, entity.path);
28
39
  if (existsSync(filePath)) {
@@ -44,7 +55,7 @@ export function checkRegistry(targetDir) {
44
55
  * Get registry statistics
45
56
  */
46
57
  export function getRegistryStats(targetDir) {
47
- const registryPath = join(targetDir, REGISTRY_PATH);
58
+ const registryPath = join(targetDir, getRegistryPath(targetDir));
48
59
  if (!existsSync(registryPath)) {
49
60
  return { exists: false, totalEntities: 0, byType: {} };
50
61
  }
@@ -54,7 +65,7 @@ export function getRegistryStats(targetDir) {
54
65
  return { exists: true, totalEntities: 0, byType: {}, error: 'Parse error' };
55
66
  }
56
67
 
57
- const entities = flattenEntities(registry.entities);
68
+ const entities = flattenEntitiesMap(registry.entities);
58
69
  const byType = {};
59
70
  for (const entity of entities) {
60
71
  const type = entity.type || 'unknown';
@@ -111,9 +122,10 @@ export function runHealthCheck(targetDir) {
111
122
  'session.schema.json', 'config.schema.json', 'task.schema.json',
112
123
  'context.schema.json', 'memory.schema.json',
113
124
  ];
125
+ const fwDir = resolveFrameworkDir(targetDir);
114
126
  let validSchemas = 0;
115
127
  for (const file of schemaFiles) {
116
- const schemaPath = join(targetDir, 'chati.dev', 'schemas', file);
128
+ const schemaPath = join(targetDir, fwDir, 'schemas', file);
117
129
  if (existsSync(schemaPath)) {
118
130
  try {
119
131
  JSON.parse(readFileSync(schemaPath, 'utf-8'));
@@ -127,12 +139,12 @@ export function runHealthCheck(targetDir) {
127
139
  checks.schemas.details = `${validSchemas}/${schemaFiles.length} valid`;
128
140
 
129
141
  // 3. Constitution check
130
- const constitutionPath = join(targetDir, 'chati.dev', 'constitution.md');
142
+ const constitutionPath = join(targetDir, fwDir, 'constitution.md');
131
143
  if (existsSync(constitutionPath)) {
132
144
  const content = readFileSync(constitutionPath, 'utf-8');
133
145
  const articleCount = (content.match(/^## Article/gm) || []).length;
134
- checks.constitution.pass = articleCount >= 19;
135
- checks.constitution.details = `${articleCount}/19 articles`;
146
+ checks.constitution.pass = articleCount >= 22;
147
+ checks.constitution.details = `${articleCount}/23 articles`;
136
148
  } else {
137
149
  checks.constitution.details = 'Not found';
138
150
  }
@@ -149,7 +161,7 @@ export function runHealthCheck(targetDir) {
149
161
  ];
150
162
  let foundAgents = 0;
151
163
  for (const p of agentPaths) {
152
- if (existsSync(join(targetDir, 'chati.dev', p))) foundAgents++;
164
+ if (existsSync(join(targetDir, fwDir, p))) foundAgents++;
153
165
  }
154
166
  checks.agents.pass = foundAgents === 13;
155
167
  checks.agents.details = `${foundAgents}/13 present`;
@@ -181,22 +193,3 @@ function loadRegistry(registryPath) {
181
193
  }
182
194
  }
183
195
 
184
- /**
185
- * Flatten nested entity categories into flat array
186
- */
187
- function flattenEntities(entities) {
188
- if (!entities) return [];
189
- const flat = [];
190
-
191
- for (const [, items] of Object.entries(entities)) {
192
- if (typeof items === 'object' && items !== null) {
193
- for (const [name, entity] of Object.entries(items)) {
194
- if (entity && entity.path) {
195
- flat.push({ name, ...entity });
196
- }
197
- }
198
- }
199
- }
200
-
201
- return flat;
202
- }
@@ -6,11 +6,6 @@
6
6
  * results, handoffs, and deviations.
7
7
  */
8
8
 
9
- /**
10
- * @deprecated Not currently imported by any production module.
11
- * Retained for potential future integration. Review for removal or integration by v5.0.
12
- */
13
-
14
9
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
15
10
  import { join, dirname } from 'path';
16
11
 
@@ -38,6 +33,8 @@ export const EventType = {
38
33
  PROVIDER_SELECTED: 'provider_selected',
39
34
  SESSION_STARTED: 'session_started',
40
35
  SESSION_ENDED: 'session_ended',
36
+ CORRECTION_TRIGGERED: 'correction_triggered',
37
+ CORRECTION_ESCALATED: 'correction_escalated',
41
38
  };
42
39
 
43
40
  const TIMELINE_FILE = '.chati/timeline.json';
@@ -82,8 +79,16 @@ function saveTimeline(projectDir, timeline) {
82
79
  */
83
80
  export function recordEvent(projectDir, type, agent, data = {}) {
84
81
  const timeline = loadTimeline(projectDir);
85
- _counter++;
86
82
 
83
+ // Dedup: skip if the last event of this type already targets the same agent.
84
+ // Prevents duplicate AGENT_ACTIVATED entries when handleAdvance chains into
85
+ // handleNext while the caller also invokes next separately.
86
+ const last = timeline[timeline.length - 1];
87
+ if (last && last.type === type && last.agent === agent) {
88
+ return last;
89
+ }
90
+
91
+ _counter++;
87
92
  const event = {
88
93
  id: `evt-${Date.now()}-${_counter}`,
89
94
  type,
@@ -37,7 +37,7 @@ export async function runActivate(projectDir, keyArg) {
37
37
  const plan = result.plan ?? 'trial';
38
38
  const days = result.days_remaining ?? '?';
39
39
  console.log(`\n✓ Activated! Plan: ${plan} — ${days} day(s) remaining.`);
40
- console.log(` Key saved to chati.dev/config.yaml`);
40
+ console.log(` Key saved to ~/.chati-dev/license.yaml`);
41
41
  console.log(` Run /chati to start.\n`);
42
42
  } catch (err) {
43
43
  console.error(`\n✗ Activation failed: ${err.message}`);
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fileoverview Poll ~/.chati-dev/license.yaml until status=VALID.
3
+ *
4
+ * Part of the autonomous-completion workstream (ADR-AUTO-01): when a /chati
5
+ * session boots with no VALID license, the orchestrator tells the user to
6
+ * run `npx chati-dev activate --key=X` in another terminal, then calls
7
+ * this util to block until the file appears with VALID status.
8
+ *
9
+ * Design invariants:
10
+ * - Never echo the license key to chat (the whole reason we don't use
11
+ * chat-paste). We only check status.
12
+ * - Bounded wait: default 60s timeout, 3s poll interval.
13
+ * - Fast-fail on EXPIRED / INVALID / REVOKED — those need a NEW key,
14
+ * polling won't resolve them.
15
+ * - Pure function on time: cancellation via AbortSignal supported so
16
+ * callers (tests, interactive CLI) can stop the loop.
17
+ */
18
+
19
+ import { existsSync, readFileSync } from 'fs';
20
+ import { join } from 'path';
21
+
22
+ export const DEFAULT_POLL_INTERVAL_MS = 3_000;
23
+ export const DEFAULT_TIMEOUT_MS = 60_000;
24
+
25
+ const TERMINAL_BAD_STATUSES = new Set(['EXPIRED', 'INVALID', 'REVOKED']);
26
+
27
+ /**
28
+ * Read ~/.chati-dev/license.yaml and extract the status field.
29
+ * Pure + synchronous — safe to call from poll loop.
30
+ *
31
+ * @param {string} home
32
+ * @returns {{ present: boolean, status: string|null, reason: string|null, error?: string }}
33
+ */
34
+ export function readLicenseStatus(home) {
35
+ const licensePath = join(home, '.chati-dev', 'license.yaml');
36
+ if (!existsSync(licensePath)) {
37
+ return { present: false, status: null, reason: null };
38
+ }
39
+ try {
40
+ const raw = readFileSync(licensePath, 'utf-8');
41
+ const statusMatch = raw.match(/^\s*status:\s*["']?(\w+)/m);
42
+ const reasonMatch = raw.match(/^\s*reason:\s*["']?([^"'\n]+)/m);
43
+ return {
44
+ present: true,
45
+ status: statusMatch ? statusMatch[1].toUpperCase() : null,
46
+ reason: reasonMatch ? reasonMatch[1].trim() : null,
47
+ };
48
+ } catch (err) {
49
+ return { present: true, status: null, reason: null, error: err.message };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Poll for a VALID license. Resolves when:
55
+ * - Status becomes VALID → { valid: true, status: 'VALID' }
56
+ * - Status is terminal-bad (EXPIRED|INVALID|REVOKED) → { valid: false, status, reason }
57
+ * - Timeout elapses → { valid: false, status: 'TIMEOUT', reason: 'No valid license detected within N seconds' }
58
+ * - Caller aborts → { valid: false, status: 'ABORTED' }
59
+ *
60
+ * @param {object} [opts]
61
+ * @param {string} [opts.home] Override HOME (default: process.env.HOME).
62
+ * @param {number} [opts.intervalMs] Poll period (default 3000).
63
+ * @param {number} [opts.timeoutMs] Total wait (default 60_000).
64
+ * @param {AbortSignal} [opts.signal] Cancel the loop externally.
65
+ * @param {(ms:number)=>Promise<void>} [opts.sleep] Injectable sleep (tests).
66
+ * @returns {Promise<{ valid: boolean, status: string|null, reason: string|null, waitedMs: number }>}
67
+ */
68
+ export async function waitForLicense(opts = {}) {
69
+ const home = opts.home || process.env.HOME || '';
70
+ const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
71
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
72
+ const signal = opts.signal;
73
+ const sleep = opts.sleep || ((ms) => new Promise(r => setTimeout(r, ms)));
74
+
75
+ const startedAt = Date.now();
76
+
77
+ for (;;) {
78
+ if (signal?.aborted) {
79
+ return { valid: false, status: 'ABORTED', reason: 'Poll aborted by caller', waitedMs: Date.now() - startedAt };
80
+ }
81
+
82
+ const snap = readLicenseStatus(home);
83
+ if (snap.status === 'VALID') {
84
+ return { valid: true, status: 'VALID', reason: null, waitedMs: Date.now() - startedAt };
85
+ }
86
+ if (snap.status && TERMINAL_BAD_STATUSES.has(snap.status)) {
87
+ return { valid: false, status: snap.status, reason: snap.reason || `License ${snap.status}`, waitedMs: Date.now() - startedAt };
88
+ }
89
+
90
+ const elapsed = Date.now() - startedAt;
91
+ if (elapsed >= timeoutMs) {
92
+ return {
93
+ valid: false,
94
+ status: 'TIMEOUT',
95
+ reason: `No VALID license detected at ~/.chati-dev/license.yaml within ${Math.round(timeoutMs / 1000)}s.`,
96
+ waitedMs: elapsed,
97
+ };
98
+ }
99
+
100
+ await sleep(Math.min(intervalMs, timeoutMs - elapsed));
101
+ }
102
+ }
@@ -167,6 +167,87 @@ export function writeAgentMemory(projectDir, agentName, entry) {
167
167
  return { saved: true };
168
168
  }
169
169
 
170
+ /**
171
+ * Write a minimal MEMORY.md for an agent that has none, derived from a
172
+ * session digest (memory-bootstrap-v1, F-A5-006).
173
+ *
174
+ * Rules:
175
+ * - Only writes when the target MEMORY.md does not exist or is empty
176
+ * (idempotent; second call on the same agent is a no-op).
177
+ * - Entries are tagged `source: bootstrap` and confidence `low` so
178
+ * dream.js can safely re-classify / deduplicate on a later full run.
179
+ * - The produced file carries a `<!-- bootstrap-write: true -->` HTML
180
+ * comment header for auditability.
181
+ *
182
+ * @param {string} projectDir
183
+ * @param {string} agentName
184
+ * @param {object} digest - Session digest shape from session-digest.js
185
+ * @returns {{ written: boolean, reason?: string, path?: string }}
186
+ */
187
+ export function writeBootstrapMemory(projectDir, agentName, digest) {
188
+ if (!agentName || agentName === 'none') {
189
+ return { written: false, reason: 'no-agent' };
190
+ }
191
+ const memoryPath = getAgentMemoryPath(projectDir, agentName);
192
+
193
+ // Idempotency: skip if a non-empty MEMORY.md already exists.
194
+ if (existsSync(memoryPath)) {
195
+ try {
196
+ const existing = readFileSync(memoryPath, 'utf-8').trim();
197
+ if (existing.length > 0) {
198
+ return { written: false, reason: 'already-populated' };
199
+ }
200
+ } catch { /* fall through to write */ }
201
+ }
202
+
203
+ const dir = dirname(memoryPath);
204
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
205
+
206
+ const header = `<!-- bootstrap-write: true -->\n<!-- source: session-digest bootstrap (memory-bootstrap-v1) -->\n`;
207
+ const entries = [];
208
+
209
+ // Seed Episodic with the current pipeline context.
210
+ if (digest?.mode && digest?.pipelinePosition) {
211
+ entries.push({
212
+ category: 'Episodic',
213
+ content: `Resumed at mode=${digest.mode}, pipeline_position=${digest.pipelinePosition}, workflow=${digest.workflow || 'unknown'}.`,
214
+ confidence: 'low',
215
+ tags: ['source:bootstrap'],
216
+ });
217
+ }
218
+
219
+ // Seed Semantic from active_task (if digest has one).
220
+ if (digest?.active_task) {
221
+ entries.push({
222
+ category: 'Semantic',
223
+ content: `Active task on resume: ${String(digest.active_task).slice(0, 500)}`,
224
+ confidence: 'low',
225
+ tags: ['source:bootstrap'],
226
+ });
227
+ }
228
+
229
+ // Seed Procedural from gotchas_found (capped).
230
+ if (Array.isArray(digest?.gotchas_found) && digest.gotchas_found.length > 0) {
231
+ for (const g of digest.gotchas_found.slice(0, 5)) {
232
+ if (!g?.message) continue;
233
+ entries.push({
234
+ category: 'Procedural',
235
+ content: `Known gotcha [${g.severity || 'info'}] ${g.id || ''}: ${String(g.message).slice(0, 200)}`,
236
+ confidence: 'low',
237
+ tags: ['source:bootstrap', `severity:${g.severity || 'info'}`],
238
+ });
239
+ }
240
+ }
241
+
242
+ if (entries.length === 0) {
243
+ return { written: false, reason: 'no-content' };
244
+ }
245
+
246
+ const body = formatMemoryMarkdown(entries);
247
+ writeFileSync(memoryPath, header + '\n' + body, 'utf-8');
248
+ return { written: true, path: memoryPath };
249
+ }
250
+
170
251
  /**
171
252
  * Search across all agent memories.
172
253
  * @param {string} projectDir - Project directory