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
@@ -8,7 +8,7 @@
8
8
  * Constitution Article XVII — Execution Mode Governance.
9
9
  */
10
10
 
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
12
12
  import { join, dirname } from 'path';
13
13
 
14
14
  // ---------------------------------------------------------------------------
@@ -281,7 +281,6 @@ export function getProgress(state) {
281
281
  export function clearBuildState(projectDir) {
282
282
  const statePath = join(projectDir, BUILD_STATE_FILE);
283
283
  if (existsSync(statePath)) {
284
- const { unlinkSync } = require('fs');
285
284
  unlinkSync(statePath);
286
285
  }
287
286
  }
@@ -160,7 +160,7 @@ export function calculateCompletion(pipelineState) {
160
160
  function getStageAgents(stage) {
161
161
  const stages = {
162
162
  DISCOVER: ['greenfield-wu', 'brownfield-wu', 'brief'],
163
- PLAN: ['detail', 'architect', 'ux', 'phases', 'tasks', 'qa-planning'],
163
+ PLAN: ['detail', 'architect', 'ux', 'phases', 'tasks'], // qa-planning runs inside Planning Team; score synced via team dissolution
164
164
  BUILD: ['dev', 'qa-implementation'],
165
165
  DEPLOY: ['devops'],
166
166
  };
@@ -10,13 +10,21 @@
10
10
  import { existsSync, readFileSync } from 'fs';
11
11
  import { join } from 'path';
12
12
  import yaml from 'js-yaml';
13
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Constants
16
17
  // ---------------------------------------------------------------------------
17
18
 
18
- /** Directory relative to project root where customization files are stored. */
19
- const CUSTOMIZE_DIR = 'chati.dev/customize';
19
+ /**
20
+ * Get the customize directory for a given project.
21
+ * Supports both .chati.dev/ and chati.dev/ structures.
22
+ * @param {string} projectDir
23
+ * @returns {string} Relative path from project root
24
+ */
25
+ function getCustomizeDir(projectDir) {
26
+ return `${resolveFrameworkDir(projectDir)}/customize`;
27
+ }
20
28
 
21
29
  /** Valid override fields that customizations can set. */
22
30
  export const VALID_OVERRIDE_FIELDS = [
@@ -57,7 +65,7 @@ export function loadCustomization(projectDir, agentName) {
57
65
  return { found: false, overrides: {} };
58
66
  }
59
67
 
60
- const customizePath = join(projectDir, CUSTOMIZE_DIR, `${agentName}.yaml`);
68
+ const customizePath = join(projectDir, getCustomizeDir(projectDir), `${agentName}.yaml`);
61
69
 
62
70
  if (!existsSync(customizePath)) {
63
71
  return { found: false, overrides: {} };
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Claude Code settings.json generator.
3
3
  *
4
- * Produces .claude/settings.json content that wires the 10 chati.dev hooks
4
+ * Produces .claude/settings.json content that wires the 14 chati.dev hooks
5
5
  * into Claude Code's hook system and applies a baseline permission policy.
6
6
  *
7
7
  * Background: prior to v4.2.2, the installer wrote hooks to
8
- * chati.dev/hooks/settings.json — but Claude Code reads .claude/settings.json,
8
+ * .chati.dev/hooks/settings.json — but Claude Code reads .claude/settings.json,
9
9
  * not the framework's bundled file. As a result, every hook was dormant for
10
10
  * every Claude Code user since the project started. This generator is the fix:
11
- * it produces the file Claude Code actually loads, registers all 10 hooks at
11
+ * it produces the file Claude Code actually loads, registers all 14 hooks at
12
12
  * the canonical events, and applies a generic permission policy modeled on
13
13
  * production Claude Code setups.
14
14
  *
@@ -20,16 +20,23 @@
20
20
 
21
21
  /**
22
22
  * Hook registration map. Each entry is one hook script and the events it
23
- * should fire on. Source of truth lives in chati.dev/hooks/.
23
+ * should fire on. Source of truth lives in .chati.dev/hooks/.
24
24
  */
25
25
  const HOOK_REGISTRY = [
26
26
  // UserPromptSubmit — fires on every user message turn.
27
- // license-guard MUST be first so license check runs before any other work.
28
- { name: 'license-guard', event: 'UserPromptSubmit', matcher: '.*' },
27
+ // NOTE: license-guard is NOT here. UserPromptSubmit blocks are SILENT (prompt
28
+ // is dropped, no message shown to user). License enforcement is done via:
29
+ // 1. Slash command STEP 0 (visible — Claude runs checkLicense() via Bash)
30
+ // 2. PreToolUse catch-all below (backup — Claude sees deny reason, reports it)
29
31
  { name: 'prism-engine', event: 'UserPromptSubmit', matcher: '.*' },
30
32
  { name: 'model-governance', event: 'UserPromptSubmit', matcher: '.*' },
31
33
 
32
34
  // PreToolUse — fires before each tool call. Matcher targets specific tools.
35
+ // license-guard is first on catch-all ".*" — blocks ALL tool calls when
36
+ // license is expired. Claude sees the deny reason and can report it to user.
37
+ // This is the backup defense: even if someone bypasses the slash command,
38
+ // no tool call succeeds with an expired license.
39
+ { name: 'license-guard', event: 'PreToolUse', matcher: '.*' },
33
40
  { name: 'read-protection', event: 'PreToolUse', matcher: 'Read' },
34
41
  { name: 'constitution-guard',event: 'PreToolUse', matcher: 'Bash|Write|Edit' },
35
42
  { name: 'mode-governance', event: 'PreToolUse', matcher: 'Write|Edit' },
@@ -37,6 +44,16 @@ const HOOK_REGISTRY = [
37
44
  { name: 'undercover-guard', event: 'PreToolUse', matcher: 'Write|Edit|Bash' },
38
45
  { name: 'team-quality-gate', event: 'PreToolUse', matcher: 'Write|Edit' },
39
46
 
47
+ // PostToolUse — fires after each tool call. Used to auto-advance the pipeline
48
+ // when an agent finishes writing its handoff. Hook inspects the written path
49
+ // and triggers `chati-router.js advance` if it matches an agent handoff file.
50
+ { name: 'advance-trigger', event: 'PostToolUse', matcher: 'Write' },
51
+ { name: 'reference-trigger', event: 'PostToolUse', matcher: 'Write' },
52
+ { name: 'brief-validator', event: 'PostToolUse', matcher: 'Write' },
53
+ // Autonomous-completion v1 (ADR-AUTO-06): spawns visual-qa.js on
54
+ // dev-handoff write so the next /chati turn already has report.json.
55
+ { name: 'post-dev', event: 'PostToolUse', matcher: 'Write' },
56
+
40
57
  // PreCompact — fires before context compaction (observational).
41
58
  { name: 'session-digest', event: 'PreCompact', matcher: '' },
42
59
  ];
@@ -64,6 +81,9 @@ const PERMISSIONS_ALLOW = [
64
81
  'EnterPlanMode',
65
82
  'ExitPlanMode',
66
83
  'KillShell',
84
+ 'Bash(npx playwright:*)',
85
+ 'Bash(node .chati.dev/scripts/*)',
86
+ 'Bash(node chati.dev/scripts/*)',
67
87
  ];
68
88
 
69
89
  /**
@@ -164,7 +184,7 @@ function buildHooksObject() {
164
184
  }
165
185
  matcherGroups.get(entry.matcher).push({
166
186
  type: 'command',
167
- command: `node chati.dev/hooks/${entry.name}.js`,
187
+ command: `node .chati.dev/hooks/${entry.name}.js`,
168
188
  });
169
189
  }
170
190
 
@@ -13,6 +13,7 @@
13
13
  import { existsSync, readFileSync, writeFileSync } from 'fs';
14
14
  import { join } from 'path';
15
15
  import { getEnabledNonClaudeProviders } from '../utils/config-parser.js';
16
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
16
17
 
17
18
  // ---------------------------------------------------------------------------
18
19
  // Replacement Maps
@@ -48,23 +49,34 @@ const CODEX_STRIP_PATTERNS = [
48
49
  ];
49
50
 
50
51
  /**
51
- * Text replacements for Codex CLI context file.
52
- * Note: Codex has no commands/ or rules/ directories — references point to chati.dev/.
52
+ * Build text replacements for Codex CLI context file.
53
+ * Note: Codex has no commands/ or rules/ directories — references point to the framework dir.
54
+ *
55
+ * @param {string} [frameworkDir='chati.dev'] - Resolved framework directory name (e.g. 'chati.dev' or '.chati.dev')
56
+ * @returns {Array<[string, string]>} Replacement pairs
53
57
  */
54
- const CODEX_REPLACEMENTS = [
55
- ['Claude Code', 'Codex CLI'],
56
- ['claude code', 'Codex CLI'],
57
- ['CLAUDE.md', 'AGENTS.md'],
58
- ['Claude.md', 'AGENTS.md'],
59
- ['.claude/commands/', 'chati.dev/orchestrator/'],
60
- ['.claude/rules/chati/', 'chati.dev/context/'],
61
- ['.claude/rules/', 'chati.dev/context/'],
62
- ['.claude/mcp.json', '.codex/config.toml'],
63
- ['claude --print', 'codex exec'],
64
- ['claude -p', 'codex exec'],
65
- ['CLAUDE.local.md', 'AGENTS.override.md'],
66
- ['/chati', '$chati'],
67
- ];
58
+ function buildCodexReplacements(frameworkDir = 'chati.dev') {
59
+ return [
60
+ ['Claude Code', 'Codex CLI'],
61
+ ['claude code', 'Codex CLI'],
62
+ ['CLAUDE.md', 'AGENTS.md'],
63
+ ['Claude.md', 'AGENTS.md'],
64
+ ['.claude/commands/', `${frameworkDir}/orchestrator/`],
65
+ ['.claude/rules/chati/', `${frameworkDir}/context/`],
66
+ ['.claude/rules/', `${frameworkDir}/context/`],
67
+ ['.claude/mcp.json', '.codex/config.toml'],
68
+ ['claude --print', 'codex exec'],
69
+ ['claude -p', 'codex exec'],
70
+ ['CLAUDE.local.md', 'AGENTS.override.md'],
71
+ ['/chati', '$chati'],
72
+ ];
73
+ }
74
+
75
+ /**
76
+ * Text replacements for Codex CLI context file (legacy constant using default 'chati.dev/' prefix).
77
+ * @deprecated Use buildCodexReplacements(frameworkDir) for correct path resolution.
78
+ */
79
+ const CODEX_REPLACEMENTS = buildCodexReplacements();
68
80
 
69
81
  // ---------------------------------------------------------------------------
70
82
  // Generators
@@ -122,10 +134,12 @@ export function generateGeminiMd(content) {
122
134
  *
123
135
  * @param {string} content - Raw CLAUDE.md content
124
136
  * @param {object} [contextFiles] - Optional inline context { root, governance, protocols, quality }
137
+ * @param {string} [frameworkDir='chati.dev'] - Resolved framework directory name
125
138
  * @returns {string} Adapted content for AGENTS.md
126
139
  */
127
- export function generateAgentsMd(content, contextFiles = null) {
140
+ export function generateAgentsMd(content, contextFiles = null, frameworkDir = 'chati.dev') {
128
141
  let result = content;
142
+ const replacements = buildCodexReplacements(frameworkDir);
129
143
 
130
144
  // Strip hook-related sections (Codex has no hooks support)
131
145
  for (const pattern of CODEX_STRIP_PATTERNS) {
@@ -133,7 +147,7 @@ export function generateAgentsMd(content, contextFiles = null) {
133
147
  }
134
148
 
135
149
  // Apply text replacements
136
- for (const [search, replace] of CODEX_REPLACEMENTS) {
150
+ for (const [search, replace] of replacements) {
137
151
  result = result.replaceAll(search, replace);
138
152
  }
139
153
 
@@ -155,7 +169,7 @@ export function generateAgentsMd(content, contextFiles = null) {
155
169
  if (fileContent) {
156
170
  // Adapt context for Codex (replace Claude-specific refs)
157
171
  let adapted = fileContent;
158
- for (const [search, replace] of CODEX_REPLACEMENTS) {
172
+ for (const [search, replace] of replacements) {
159
173
  adapted = adapted.replaceAll(search, replace);
160
174
  }
161
175
  sections.push(adapted.trim());
@@ -213,6 +227,9 @@ export function generateContextFiles(projectDir, baseContent = null) {
213
227
  // Read context files for inline injection (Codex)
214
228
  const contextFiles = readContextFilesFromDisk(projectDir);
215
229
 
230
+ // Resolve framework directory for dynamic path generation
231
+ const fwDir = resolveFrameworkDir(projectDir);
232
+
216
233
  // Provider-to-generator mapping
217
234
  const generators = {
218
235
  gemini: {
@@ -221,7 +238,7 @@ export function generateContextFiles(projectDir, baseContent = null) {
221
238
  },
222
239
  codex: {
223
240
  filename: 'AGENTS.md',
224
- generate: (content) => generateAgentsMd(content, contextFiles),
241
+ generate: (content) => generateAgentsMd(content, contextFiles, fwDir),
225
242
  },
226
243
  };
227
244
 
@@ -247,7 +264,10 @@ export function generateContextFiles(projectDir, baseContent = null) {
247
264
  * @returns {object|null} { root, governance, protocols, quality } or null
248
265
  */
249
266
  function readContextFilesFromDisk(projectDir) {
250
- const contextDir = join(projectDir, 'chati.dev', 'context');
267
+ // Support both new (.chati.dev/) and legacy (chati.dev/) structures
268
+ const newDir = join(projectDir, '.chati.dev', 'context');
269
+ const legacyDir = join(projectDir, 'chati.dev', 'context');
270
+ const contextDir = existsSync(newDir) ? newDir : legacyDir;
251
271
  if (!existsSync(contextDir)) return null;
252
272
 
253
273
  const files = {};
@@ -31,6 +31,7 @@ export const ADAPTABLE_FILES = new Set([
31
31
  'agents/plan/tasks.md',
32
32
  'agents/quality/qa-planning.md',
33
33
  'agents/quality/qa-implementation.md',
34
+ 'agents/quality/qa-visual.md',
34
35
  'agents/build/dev.md',
35
36
  'agents/deploy/devops.md',
36
37
  'context/governance.md',
@@ -34,6 +34,7 @@ export const HOOK_MAP = {
34
34
  'session-digest': { event: 'PreCompress', description: 'Save session state before context compression' },
35
35
  'undercover-guard': { event: 'BeforeTool', description: 'Advisory: sanitize internal framework references from deliverables' },
36
36
  'style-guard': { event: 'BeforeTool', description: 'Advisory: enforce em-dash and emoji standards' },
37
+ 'team-quality-gate': { event: 'BeforeTool', description: 'Validate team task completion and quality scores before handoff (Article XXI)' },
37
38
  };
38
39
 
39
40
  // ---------------------------------------------------------------------------
@@ -57,6 +58,16 @@ import { fileURLToPath } from 'url';
57
58
  const __filename = fileURLToPath(import.meta.url);
58
59
  const __dirname = join(__filename, '..');
59
60
  const projectRoot = join(__dirname, '..', '..');
61
+
62
+ /**
63
+ * Resolve framework directory (supports .chati.dev/ and legacy chati.dev/).
64
+ * @param {string} cwd - Project root directory
65
+ */
66
+ function resolveFrameworkDir(cwd) {
67
+ const newPath = join(cwd, '.chati.dev');
68
+ if (existsSync(newPath)) return '.chati.dev';
69
+ return 'chati.dev';
70
+ }
60
71
  `;
61
72
 
62
73
  /**
@@ -86,7 +97,7 @@ async function main() {
86
97
  }
87
98
 
88
99
  // Delegate to shared PRISM engine logic
89
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'prism-engine.js');
100
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'prism-engine.js');
90
101
  if (existsSync(hookPath)) {
91
102
  const mod = await import(hookPath);
92
103
  if (typeof mod.buildPrismContext === 'function') {
@@ -141,7 +152,7 @@ async function main() {
141
152
 
142
153
  if (agent) {
143
154
  // Delegate to shared model governance logic
144
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'model-governance.js');
155
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'model-governance.js');
145
156
  if (existsSync(hookPath)) {
146
157
  const mod = await import(hookPath);
147
158
  if (typeof mod.getRecommendedModel === 'function') {
@@ -175,8 +186,9 @@ function generateModeGovernance() {
175
186
  * Blocks write operations outside current mode scope.
176
187
  * Constitution Article XI enforcement.
177
188
  */
189
+ const fwDir = existsSync(join(process.cwd(), '.chati.dev')) ? '.chati.dev' : 'chati.dev';
178
190
  const MODE_SCOPES = {
179
- planning: { allowed: ['chati.dev/', '.chati/', 'chati.dev/artifacts/'] },
191
+ planning: { allowed: [fwDir + '/', '.chati/', 'artifacts/'] },
180
192
  build: { allowed: ['*'] },
181
193
  deploy: { allowed: ['*'] },
182
194
  };
@@ -447,7 +459,7 @@ async function main() {
447
459
  const cwd = event.cwd || process.cwd();
448
460
 
449
461
  // Delegate to shared session digest logic
450
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'session-digest.js');
462
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'session-digest.js');
451
463
  if (existsSync(hookPath)) {
452
464
  const mod = await import(hookPath);
453
465
  if (typeof mod.saveDigest === 'function') {
@@ -495,7 +507,7 @@ async function main() {
495
507
  }
496
508
 
497
509
  // Delegate to shared undercover guard logic
498
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'undercover-guard.js');
510
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'undercover-guard.js');
499
511
  if (!existsSync(hookPath)) {
500
512
  console.log(JSON.stringify({}));
501
513
  return;
@@ -565,7 +577,7 @@ async function main() {
565
577
  }
566
578
 
567
579
  // Delegate to shared style guard logic
568
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'style-guard.js');
580
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'style-guard.js');
569
581
  if (!existsSync(hookPath)) {
570
582
  console.log(JSON.stringify({}));
571
583
  return;
@@ -648,7 +660,7 @@ async function main() {
648
660
  const cwd = event.cwd || process.cwd();
649
661
 
650
662
  // Delegate to canonical license check
651
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'license-guard.js');
663
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'license-guard.js');
652
664
  if (!existsSync(hookPath)) {
653
665
  // No canonical hook present — fail open
654
666
  console.log(JSON.stringify({}));
@@ -42,6 +42,7 @@ export const AGENT_MCP_DEPS = {
42
42
  'qa-planning': { required: [], optional: [] },
43
43
  dev: { required: ['context7'], optional: ['browser'] },
44
44
  'qa-implementation': { required: [], optional: ['browser'] },
45
+ 'qa-visual': { required: [], optional: ['browser'] },
45
46
  devops: { required: [], optional: [] },
46
47
  };
47
48
 
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { loadGlobalDomain, extractRules } from '../domain-loader.js';
9
+ import { resolveFrameworkDir } from '../../utils/framework-dir.js';
9
10
 
10
11
  // Maps session states to governance modes for domain lookup
11
12
  const STATE_TO_GOVERNANCE_MODE = {
@@ -40,7 +41,7 @@ export function processL1(ctx) {
40
41
  rules,
41
42
  mode: sessionMode,
42
43
  modeRules: {
43
- writeScope: modeRules.writeScope || 'chati.dev/',
44
+ writeScope: modeRules.writeScope || resolveFrameworkDir(ctx.projectDir || process.cwd()) + '/',
44
45
  allowedActions: modeRules.allowedActions || [],
45
46
  blockedActions: modeRules.blockedActions || [],
46
47
  },
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, existsSync, readdirSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import yaml from 'js-yaml';
4
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
4
5
 
5
6
  /**
6
7
  * Read all dashboard data from project directory
@@ -31,7 +32,7 @@ export async function readDashboardData(targetDir) {
31
32
  }
32
33
 
33
34
  // Read config.yaml
34
- const configPath = join(targetDir, 'chati.dev', 'config.yaml');
35
+ const configPath = join(targetDir, resolveFrameworkDir(targetDir), 'config.yaml');
35
36
  if (existsSync(configPath)) {
36
37
  try {
37
38
  data.config = yaml.load(readFileSync(configPath, 'utf-8'));
@@ -59,7 +60,7 @@ export async function readDashboardData(targetDir) {
59
60
  }
60
61
 
61
62
  // Read task progress from artifacts
62
- const tasksDir = join(targetDir, 'chati.dev', 'artifacts', '6-Tasks');
63
+ const tasksDir = join(targetDir, 'artifacts', '6-Tasks');
63
64
  if (existsSync(tasksDir)) {
64
65
  try {
65
66
  const files = readdirSync(tasksDir).filter(f => f.endsWith('.yaml') || f.endsWith('.md'));
@@ -70,7 +71,7 @@ export async function readDashboardData(targetDir) {
70
71
  }
71
72
 
72
73
  // Read gotchas
73
- const gotchasPath = join(targetDir, 'chati.dev', 'intelligence', 'gotchas.yaml');
74
+ const gotchasPath = join(targetDir, resolveFrameworkDir(targetDir), 'intelligence', 'gotchas.yaml');
74
75
  if (existsSync(gotchasPath)) {
75
76
  try {
76
77
  const gotchasData = yaml.load(readFileSync(gotchasPath, 'utf-8'));
@@ -62,7 +62,8 @@ export function buildProjectInfo(data) {
62
62
  const name = project.name || 'unknown';
63
63
  const type = project.type === 'brownfield' ? 'Brownfield' : 'Greenfield';
64
64
  const state = formatState(project.state);
65
- const mode = formatMode(session.execution_mode);
65
+ const rawMode = session.execution_mode;
66
+ const mode = formatMode(rawMode && typeof rawMode === 'object' ? rawMode.mode : rawMode);
66
67
  const lang = session.language || 'en';
67
68
  // Show the provider-specific IDE (last in the list) rather than always claude-code
68
69
  const ides = session.ides || [];
@@ -8,6 +8,8 @@
8
8
  import { readFileSync, existsSync } from 'fs';
9
9
  import { join } from 'path';
10
10
  import yaml from 'js-yaml';
11
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
12
+ import { flattenEntitiesFromRegistry } from '../utils/flatten-entities.js';
11
13
 
12
14
  /**
13
15
  * Analyze impact of changing an entity.
@@ -38,12 +40,12 @@ export function analyzeImpact(projectDir, entityPath) {
38
40
  }
39
41
 
40
42
  // Load entity details
41
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
43
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
42
44
  const registry = existsSync(registryPath)
43
45
  ? yaml.load(readFileSync(registryPath, 'utf8'))
44
46
  : { entities: {} };
45
47
 
46
- const allEntities = flattenEntities(registry);
48
+ const allEntities = flattenEntitiesFromRegistry(registry);
47
49
  const entityMap = new Map(allEntities.map(e => [e.path, e]));
48
50
 
49
51
  const affectedEntities = affectedPaths.map(path => {
@@ -68,7 +70,7 @@ export function analyzeImpact(projectDir, entityPath) {
68
70
  * @returns {Map<string, string[]>} Map of entityPath -> [dependentPaths]
69
71
  */
70
72
  export function buildDependencyGraph(projectDir) {
71
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
73
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
72
74
  const graph = new Map();
73
75
 
74
76
  if (!existsSync(registryPath)) {
@@ -76,7 +78,7 @@ export function buildDependencyGraph(projectDir) {
76
78
  }
77
79
 
78
80
  const registry = yaml.load(readFileSync(registryPath, 'utf8'));
79
- const entities = flattenEntities(registry);
81
+ const entities = flattenEntitiesFromRegistry(registry);
80
82
 
81
83
  // Initialize graph with all entities
82
84
  entities.forEach(entity => {
@@ -287,29 +289,3 @@ function tokenize(text) {
287
289
  .filter(w => w.length > 2 && !stopwords.has(w));
288
290
  }
289
291
 
290
- /**
291
- * Flatten entity registry into array of entities.
292
- * @private
293
- * @param {object} registry
294
- * @returns {object[]}
295
- */
296
- function flattenEntities(registry) {
297
- const entities = [];
298
-
299
- if (!registry.entities) {
300
- return entities;
301
- }
302
-
303
- Object.entries(registry.entities).forEach(([type, typeEntities]) => {
304
- if (Array.isArray(typeEntities)) {
305
- typeEntities.forEach(entity => {
306
- entities.push({
307
- ...entity,
308
- type
309
- });
310
- });
311
- }
312
- });
313
-
314
- return entities;
315
- }
@@ -12,6 +12,8 @@
12
12
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
13
13
  import { join } from 'path';
14
14
  import yaml from 'js-yaml';
15
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
16
+ import { flattenEntitiesFromRegistry } from '../utils/flatten-entities.js';
15
17
 
16
18
  /**
17
19
  * Analyze a request against existing entities and recommend action.
@@ -22,7 +24,7 @@ import yaml from 'js-yaml';
22
24
  * @returns {{ recommendation: 'REUSE'|'ADAPT'|'CREATE', score: number, matches: object[], reasoning: string }}
23
25
  */
24
26
  export function analyzeRequest(projectDir, request) {
25
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
27
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
26
28
 
27
29
  if (!existsSync(registryPath)) {
28
30
  return {
@@ -34,7 +36,7 @@ export function analyzeRequest(projectDir, request) {
34
36
  }
35
37
 
36
38
  const registry = yaml.load(readFileSync(registryPath, 'utf8'));
37
- const entities = flattenEntities(registry);
39
+ const entities = flattenEntitiesFromRegistry(registry);
38
40
 
39
41
  // Calculate similarity scores for all entities
40
42
  const scoredMatches = entities
@@ -246,29 +248,3 @@ export function getEngineStats(projectDir) {
246
248
  return stats;
247
249
  }
248
250
 
249
- /**
250
- * Flatten entity registry into array of entities.
251
- * @private
252
- * @param {object} registry
253
- * @returns {object[]}
254
- */
255
- function flattenEntities(registry) {
256
- const entities = [];
257
-
258
- if (!registry.entities) {
259
- return entities;
260
- }
261
-
262
- Object.entries(registry.entities).forEach(([type, typeEntities]) => {
263
- if (Array.isArray(typeEntities)) {
264
- typeEntities.forEach(entity => {
265
- entities.push({
266
- ...entity,
267
- type
268
- });
269
- });
270
- }
271
- });
272
-
273
- return entities;
274
- }
@@ -8,6 +8,7 @@
8
8
  import { readFileSync, writeFileSync, existsSync, statSync, copyFileSync, mkdirSync } from 'fs';
9
9
  import { join, isAbsolute } from 'path';
10
10
  import yaml from 'js-yaml';
11
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
11
12
  import { detectNewEntities, detectRemovedEntities, generateEntityMeta } from './registry-updater.js';
12
13
 
13
14
  /**
@@ -16,7 +17,7 @@ import { detectNewEntities, detectRemovedEntities, generateEntityMeta } from './
16
17
  * @returns {{ registry: object|null, registryPath: string }}
17
18
  */
18
19
  function loadRegistry(projectDir) {
19
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
20
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
20
21
  if (!existsSync(registryPath)) {
21
22
  return { registry: null, registryPath };
22
23
  }
@@ -347,7 +348,7 @@ export function detectCountMismatch(projectDir, preloaded) {
347
348
  * @returns {{ applied: number, backup: string }}
348
349
  */
349
350
  export function applyFixes(projectDir, fixes) {
350
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
351
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
351
352
 
352
353
  if (!existsSync(registryPath)) {
353
354
  return { applied: 0, backup: null };
@@ -8,17 +8,26 @@
8
8
  import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'fs';
9
9
  import { join, basename, relative } from 'path';
10
10
  import yaml from 'js-yaml';
11
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
11
12
 
12
- // Known directories to scan
13
- const KNOWN_DIRS = [
14
- { path: 'chati.dev/agents', type: 'agent' },
15
- { path: 'chati.dev/tasks', type: 'task' },
16
- { path: 'chati.dev/schemas', type: 'schema' },
17
- { path: 'chati.dev/domains', type: 'domain' },
18
- { path: 'chati.dev/hooks', type: 'hook' },
19
- { path: 'chati.dev/workflows', type: 'workflow' },
20
- { path: 'chati.dev/templates', type: 'template' }
21
- ];
13
+ /**
14
+ * Build KNOWN_DIRS using resolveFrameworkDir so paths are correct
15
+ * for both chati.dev/ and .chati.dev/ installations.
16
+ * @param {string} projectDir
17
+ * @returns {{ path: string, type: string }[]}
18
+ */
19
+ function buildKnownDirs(projectDir) {
20
+ const fw = resolveFrameworkDir(projectDir);
21
+ return [
22
+ { path: `${fw}/agents`, type: 'agent' },
23
+ { path: `${fw}/tasks`, type: 'task' },
24
+ { path: `${fw}/schemas`, type: 'schema' },
25
+ { path: `${fw}/domains`, type: 'domain' },
26
+ { path: `${fw}/hooks`, type: 'hook' },
27
+ { path: `${fw}/workflows`, type: 'workflow' },
28
+ { path: `${fw}/templates`, type: 'template' },
29
+ ];
30
+ }
22
31
 
23
32
  /**
24
33
  * Scan project for new/changed files and update registry.
@@ -28,7 +37,7 @@ const KNOWN_DIRS = [
28
37
  */
29
38
  export function updateRegistry(projectDir, options = {}) {
30
39
  const { dryRun = false } = options;
31
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
40
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
32
41
 
33
42
  let registry = {
34
43
  metadata: {
@@ -157,7 +166,7 @@ export function updateRegistry(projectDir, options = {}) {
157
166
  * @returns {object[]} New entity candidates
158
167
  */
159
168
  export function detectNewEntities(projectDir) {
160
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
169
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
161
170
  const existingPaths = new Set();
162
171
 
163
172
  if (existsSync(registryPath)) {
@@ -175,7 +184,7 @@ export function detectNewEntities(projectDir) {
175
184
 
176
185
  const newEntities = [];
177
186
 
178
- KNOWN_DIRS.forEach(({ path: dirPath, type }) => {
187
+ buildKnownDirs(projectDir).forEach(({ path: dirPath, type }) => {
179
188
  const fullPath = join(projectDir, dirPath);
180
189
  if (!existsSync(fullPath)) {
181
190
  return;
@@ -204,7 +213,7 @@ export function detectNewEntities(projectDir) {
204
213
  * @returns {object[]} Removed entity candidates
205
214
  */
206
215
  export function detectRemovedEntities(projectDir) {
207
- const registryPath = join(projectDir, 'chati.dev', 'entity-registry.yaml');
216
+ const registryPath = join(projectDir, resolveFrameworkDir(projectDir), 'entity-registry.yaml');
208
217
  if (!existsSync(registryPath)) {
209
218
  return [];
210
219
  }