chati-dev 4.2.2 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +80 -53
  2. package/bin/chati.js +150 -5
  3. package/framework/agents/build/dev.md +509 -76
  4. package/framework/agents/deploy/devops.md +40 -25
  5. package/framework/agents/discover/brief.md +156 -22
  6. package/framework/agents/discover/brownfield-wu.md +24 -14
  7. package/framework/agents/discover/greenfield-wu.md +100 -14
  8. package/framework/agents/plan/architect-data-engineer.md +6 -6
  9. package/framework/agents/plan/architect-system.md +46 -12
  10. package/framework/agents/plan/architect.md +40 -20
  11. package/framework/agents/plan/detail.md +36 -24
  12. package/framework/agents/plan/phases.md +36 -26
  13. package/framework/agents/plan/tasks.md +114 -33
  14. package/framework/agents/plan/ux-brand-architect.md +240 -8
  15. package/framework/agents/plan/ux-component-engineer.md +28 -13
  16. package/framework/agents/plan/ux-researcher.md +7 -6
  17. package/framework/agents/plan/ux.md +55 -22
  18. package/framework/agents/quality/qa-implementation.md +143 -74
  19. package/framework/agents/quality/qa-planning.md +115 -42
  20. package/framework/agents/quality/qa-visual.md +439 -0
  21. package/framework/agents/shared/visualizer.md +128 -0
  22. package/framework/config.yaml +7 -6
  23. package/framework/constitution.md +127 -44
  24. package/framework/context/governance.md +12 -7
  25. package/framework/context/quality.md +6 -5
  26. package/framework/context/root.md +6 -6
  27. package/framework/data/entity-registry.yaml +377 -4
  28. package/framework/data/model-limits.json +19 -0
  29. package/framework/domains/agents/qa-visual.yaml +74 -0
  30. package/framework/domains/constitution.yaml +46 -2
  31. package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
  32. package/framework/hooks/advance-trigger.js +131 -0
  33. package/framework/hooks/brief-validator.js +83 -0
  34. package/framework/hooks/constitution-guard.js +24 -5
  35. package/framework/hooks/license-guard.js +62 -27
  36. package/framework/hooks/mode-governance.js +13 -2
  37. package/framework/hooks/model-governance.js +1 -0
  38. package/framework/hooks/post-dev.js +207 -0
  39. package/framework/hooks/prism-engine.js +274 -105
  40. package/framework/hooks/reasoning-escalator.js +371 -0
  41. package/framework/hooks/reference-trigger.js +117 -0
  42. package/framework/hooks/session-digest.js +50 -1
  43. package/framework/hooks/settings.json +32 -1
  44. package/framework/hooks/style-guard.js +25 -6
  45. package/framework/hooks/team-quality-gate.js +19 -12
  46. package/framework/hooks/undercover-guard.js +4 -2
  47. package/framework/i18n/en.yaml +3 -3
  48. package/framework/i18n/es.yaml +3 -3
  49. package/framework/i18n/fr.yaml +3 -3
  50. package/framework/i18n/pt.yaml +3 -3
  51. package/framework/intelligence/context-engine.md +4 -5
  52. package/framework/intelligence/decision-engine.md +1 -1
  53. package/framework/intelligence/hooks-performance.md +3 -3
  54. package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
  55. package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
  56. package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
  57. package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
  58. package/framework/orchestrator/chati-router.js +387 -0
  59. package/framework/orchestrator/chati-update.md +40 -40
  60. package/framework/orchestrator/chati.md +294 -94
  61. package/framework/scaffold/motion-premium/README.md +89 -0
  62. package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
  63. package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
  64. package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
  65. package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
  66. package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
  67. package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
  68. package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
  69. package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
  70. package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
  71. package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
  72. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
  73. package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
  74. package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
  75. package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
  76. package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
  77. package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
  78. package/framework/scaffold/motion-premium-3d/README.md +80 -0
  79. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
  80. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
  81. package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
  82. package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
  83. package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
  84. package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
  85. package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
  86. package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
  87. package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
  88. package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
  89. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
  90. package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
  91. package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
  92. package/framework/schemas/session.schema.json +109 -21
  93. package/framework/scripts/reference-capture.js +430 -0
  94. package/framework/scripts/visual-qa.js +674 -0
  95. package/framework/tasks/orchestrator-handoff.md +1 -1
  96. package/framework/tasks/orchestrator-resume.md +1 -1
  97. package/framework/tasks/orchestrator-route.md +1 -1
  98. package/framework/tasks/orchestrator-status.md +3 -3
  99. package/framework/tasks/qa-planning-gate-define.md +1 -1
  100. package/framework/templates/brandbook-html-tmpl.md +1 -1
  101. package/framework/templates/brandbook-tmpl.yaml +1 -1
  102. package/framework/templates/component-spec-tmpl.yaml +1 -1
  103. package/framework/templates/design-token-tmpl.yaml +1 -1
  104. package/framework/templates/icon-system-tmpl.yaml +1 -1
  105. package/framework/templates/team-planning-tasks.yaml +6 -5
  106. package/framework/workflows/brownfield-discovery.yaml +2 -2
  107. package/framework/workflows/brownfield-fullstack.yaml +15 -11
  108. package/framework/workflows/brownfield-service.yaml +14 -10
  109. package/framework/workflows/brownfield-ui.yaml +15 -11
  110. package/framework/workflows/greenfield-fullstack.yaml +16 -13
  111. package/framework/workflows/quick-flow.yaml +3 -3
  112. package/framework/workflows/standard-flow.yaml +12 -9
  113. package/package.json +10 -5
  114. package/src/autonomy/autonomous-gate.js +1 -0
  115. package/src/autonomy/build-state.js +1 -2
  116. package/src/autonomy/progress-reporter.js +1 -1
  117. package/src/config/agent-customizer.js +11 -3
  118. package/src/config/claude-settings-generator.js +27 -7
  119. package/src/config/context-file-generator.js +41 -21
  120. package/src/config/framework-adapter.js +1 -0
  121. package/src/config/gemini-hooks-generator.js +19 -7
  122. package/src/config/mcp-configs.js +1 -0
  123. package/src/context/layers/l1-global.js +2 -1
  124. package/src/dashboard/data-reader.js +4 -3
  125. package/src/dashboard/layout.js +2 -1
  126. package/src/decision/analyzer.js +6 -30
  127. package/src/decision/engine.js +4 -28
  128. package/src/decision/registry-healer.js +3 -2
  129. package/src/decision/registry-updater.js +23 -14
  130. package/src/extensions/loader.js +2 -8
  131. package/src/gates/g1-planning-complete.js +2 -1
  132. package/src/gates/g2-qa-planning.js +2 -1
  133. package/src/gates/g3-implementation.js +2 -1
  134. package/src/gates/g4-qa-implementation.js +3 -2
  135. package/src/gates/g5-deploy-ready.js +2 -1
  136. package/src/health/engine.js +4 -3
  137. package/src/installer/core.js +422 -81
  138. package/src/installer/preflight.js +131 -0
  139. package/src/installer/provider-overlay.js +3 -3
  140. package/src/installer/scaffold-applier.js +358 -0
  141. package/src/installer/templates.js +46 -29
  142. package/src/installer/validator.js +17 -12
  143. package/src/intelligence/registry-manager.js +22 -29
  144. package/src/intelligence/timeline.js +11 -6
  145. package/src/license/commands.js +1 -1
  146. package/src/license/wait.js +102 -0
  147. package/src/memory/agent-memory.js +81 -0
  148. package/src/memory/dream.js +32 -1
  149. package/src/merger/replace-merger.js +28 -15
  150. package/src/orchestrator/agent-selector.js +2 -1
  151. package/src/orchestrator/cli.js +1869 -71
  152. package/src/orchestrator/doctor.js +270 -0
  153. package/src/orchestrator/handoff-engine.js +4 -3
  154. package/src/orchestrator/index.js +2 -0
  155. package/src/orchestrator/pipeline-manager.js +306 -15
  156. package/src/orchestrator/session-manager.js +331 -6
  157. package/src/tasks/handoff.js +3 -2
  158. package/src/telemetry/config.js +4 -3
  159. package/src/telemetry/schema.js +1 -0
  160. package/src/terminal/collector.js +3 -2
  161. package/src/terminal/index.js +1 -2
  162. package/src/terminal/isolation.js +52 -18
  163. package/src/terminal/prompt-builder.js +42 -25
  164. package/src/terminal/run-parallel.js +1 -1
  165. package/src/terminal/run-team.js +3 -3
  166. package/src/terminal/team-task-list.js +43 -4
  167. package/src/upgrade/backup.js +3 -2
  168. package/src/upgrade/checker.js +3 -2
  169. package/src/upgrade/migrator.js +65 -7
  170. package/src/upgrade/tracked-files-detector.js +86 -0
  171. package/src/upgrade/user-messages.js +94 -0
  172. package/src/utils/config-parser.js +2 -1
  173. package/src/utils/feature-flags.js +2 -1
  174. package/src/utils/flatten-entities.js +69 -0
  175. package/src/utils/framework-dir.js +16 -0
  176. package/src/utils/model-id.js +85 -0
  177. package/src/utils/provider-limits.js +84 -23
  178. package/src/utils/schema-validator.js +1 -1
  179. package/src/wizard/i18n.js +5 -4
  180. package/src/wizard/index.js +14 -0
  181. package/assets/logo - c/303/263pia.png +0 -0
  182. package/assets/logo.svg +0 -42
  183. package/assets/logo2.png +0 -0
  184. package/assets/social-preview.png +0 -0
  185. package/scripts/bundle-framework.js +0 -69
  186. package/scripts/changelog-generator.js +0 -222
  187. package/scripts/codebase-mapper.js +0 -728
  188. package/scripts/commit-message-generator.js +0 -167
  189. package/scripts/coverage-analyzer.js +0 -260
  190. package/scripts/dependency-analyzer.js +0 -280
  191. package/scripts/doctor/checks/agents.js +0 -77
  192. package/scripts/doctor/checks/constitution.js +0 -41
  193. package/scripts/doctor/checks/domain-alignment.js +0 -58
  194. package/scripts/doctor/checks/prism-layers.js +0 -84
  195. package/scripts/doctor/checks/registry.js +0 -55
  196. package/scripts/doctor/checks/schemas.js +0 -61
  197. package/scripts/doctor/fixes/reference-fix.js +0 -100
  198. package/scripts/doctor/fixes/registry-fix.js +0 -56
  199. package/scripts/doctor/index.js +0 -212
  200. package/scripts/framework-analyzer.js +0 -308
  201. package/scripts/generate-constitution-domain.js +0 -253
  202. package/scripts/generate-signing-key.js +0 -33
  203. package/scripts/health-check.js +0 -481
  204. package/scripts/ide-sync.js +0 -326
  205. package/scripts/performance-analyzer.js +0 -325
  206. package/scripts/plan-tracker.js +0 -278
  207. package/scripts/populate-entity-registry.js +0 -481
  208. package/scripts/pr-review.js +0 -317
  209. package/scripts/rollback-manager.js +0 -310
  210. package/scripts/semantic-lint.js +0 -328
  211. package/scripts/sign-manifest.js +0 -53
  212. package/scripts/stuck-detector.js +0 -343
  213. package/scripts/test-quality-assessment.js +0 -257
  214. package/scripts/validate-agents.js +0 -368
  215. package/scripts/validate-package.js +0 -505
  216. package/scripts/validate-tasks.js +0 -465
  217. package/src/autonomy/worktree-manager.js +0 -250
  218. package/src/intelligence/decision-engine.js +0 -256
  219. package/src/intelligence/document-sharder.js +0 -221
  220. package/src/intelligence/elicitation.js +0 -265
@@ -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
  }
@@ -11,13 +11,7 @@
11
11
  import { existsSync, readdirSync } from 'fs';
12
12
  import { join, extname, basename } from 'path';
13
13
  import { registerExtension, EXTENSION_POINTS } from './registry.js';
14
-
15
- // ---------------------------------------------------------------------------
16
- // Constants
17
- // ---------------------------------------------------------------------------
18
-
19
- /** Directory relative to project root where extensions are stored. */
20
- const EXTENSIONS_DIR = 'chati.dev/extensions';
14
+ import { resolveFrameworkDir } from '../utils/framework-dir.js';
21
15
 
22
16
  /** Required fields in an extension module's default export. */
23
17
  const REQUIRED_FIELDS = ['name', 'point', 'handler'];
@@ -79,7 +73,7 @@ export function discoverExtensions(projectDir) {
79
73
  return { files: [], dir: '' };
80
74
  }
81
75
 
82
- const extDir = join(projectDir, EXTENSIONS_DIR);
76
+ const extDir = join(projectDir, resolveFrameworkDir(projectDir), 'extensions');
83
77
 
84
78
  if (!existsSync(extDir)) {
85
79
  return { files: [], dir: extDir };