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
@@ -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: {} };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Claude Code settings.json generator.
3
+ *
4
+ * Produces .claude/settings.json content that wires the 14 chati.dev hooks
5
+ * into Claude Code's hook system and applies a baseline permission policy.
6
+ *
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,
9
+ * not the framework's bundled file. As a result, every hook was dormant for
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 14 hooks at
12
+ * the canonical events, and applies a generic permission policy modeled on
13
+ * production Claude Code setups.
14
+ *
15
+ * Hook output schema (v4.2.2): all PreToolUse hooks now use the canonical
16
+ * `hookSpecificOutput.permissionDecision` field per Anthropic docs at
17
+ * https://code.claude.com/docs/en/hooks.md. The previous `decision` field
18
+ * was silently no-op'd by Claude Code.
19
+ */
20
+
21
+ /**
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/.
24
+ */
25
+ const HOOK_REGISTRY = [
26
+ // UserPromptSubmit — fires on every user message turn.
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)
31
+ { name: 'prism-engine', event: 'UserPromptSubmit', matcher: '.*' },
32
+ { name: 'model-governance', event: 'UserPromptSubmit', matcher: '.*' },
33
+
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: '.*' },
40
+ { name: 'read-protection', event: 'PreToolUse', matcher: 'Read' },
41
+ { name: 'constitution-guard',event: 'PreToolUse', matcher: 'Bash|Write|Edit' },
42
+ { name: 'mode-governance', event: 'PreToolUse', matcher: 'Write|Edit' },
43
+ { name: 'style-guard', event: 'PreToolUse', matcher: 'Write|Edit|Bash' },
44
+ { name: 'undercover-guard', event: 'PreToolUse', matcher: 'Write|Edit|Bash' },
45
+ { name: 'team-quality-gate', event: 'PreToolUse', matcher: 'Write|Edit' },
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
+
57
+ // PreCompact — fires before context compaction (observational).
58
+ { name: 'session-digest', event: 'PreCompact', matcher: '' },
59
+ ];
60
+
61
+ /**
62
+ * Baseline permission allow list.
63
+ * Modeled on production Claude Code setups: lets the assistant work freely
64
+ * inside the project without prompting for every tool call. Personal MCP
65
+ * permissions are intentionally NOT included — those are user-specific.
66
+ */
67
+ const PERMISSIONS_ALLOW = [
68
+ 'Read(**/*)',
69
+ 'Write(**/*)',
70
+ 'Edit(**/*)',
71
+ 'Bash',
72
+ 'WebFetch',
73
+ 'WebSearch',
74
+ 'Task',
75
+ 'Glob',
76
+ 'Grep',
77
+ 'NotebookEdit',
78
+ 'Skill(*)',
79
+ 'TodoWrite',
80
+ 'AskUserQuestion',
81
+ 'EnterPlanMode',
82
+ 'ExitPlanMode',
83
+ 'KillShell',
84
+ 'Bash(npx playwright:*)',
85
+ 'Bash(node .chati.dev/scripts/*)',
86
+ 'Bash(node chati.dev/scripts/*)',
87
+ ];
88
+
89
+ /**
90
+ * Baseline permission deny list.
91
+ *
92
+ * Categories:
93
+ * - Catastrophic filesystem: deletes that hit root, mass-format, recursive chmod
94
+ * - Git destructive: force push, hard reset, .git removal, hook bypass
95
+ * - System path writes: /etc, /usr, /System, /bin, /sbin (note `//` prefix
96
+ * means absolute filesystem path, not project-relative)
97
+ * - Secret reads: SSH keys, AWS/GCP credentials, npm tokens, GitHub tokens
98
+ * - Accidental publish: npm/yarn/pnpm/pip/cargo publish commands
99
+ *
100
+ * Path syntax (from Claude Code docs):
101
+ * - `Bash(cmd:*)` matches "cmd" with any args
102
+ * - `~/path/**` expands ~ to home dir, ** matches recursive
103
+ * - `//etc/**` double-slash means absolute filesystem path
104
+ * - `/path/**` single-slash is project-root-relative (NOT absolute)
105
+ */
106
+ const PERMISSIONS_DENY = [
107
+ // Catastrophic filesystem
108
+ 'Bash(rm -rf /)',
109
+ 'Bash(rm -rf /*)',
110
+ 'Bash(rm -rf ~)',
111
+ 'Bash(rm -rf ~/*)',
112
+ 'Bash(rm -rf $HOME)',
113
+ 'Bash(rm -rf $HOME/*)',
114
+ 'Bash(sudo rm -rf:*)',
115
+ 'Bash(mkfs:*)',
116
+ 'Bash(dd if=/dev/zero:*)',
117
+ 'Bash(dd if=/dev/random:*)',
118
+ 'Bash(chmod -R 777 /)',
119
+ 'Bash(chown -R:* /)',
120
+
121
+ // Git destructive
122
+ 'Bash(git push --force:*)',
123
+ 'Bash(git push -f:*)',
124
+ 'Bash(git push --force-with-lease:*)',
125
+ 'Bash(git reset --hard:*)',
126
+ 'Bash(git clean -fd:*)',
127
+ 'Bash(git clean -fdx:*)',
128
+ 'Bash(git clean -fx:*)',
129
+ 'Bash(rm -rf .git)',
130
+ 'Bash(rm -rf .git/*)',
131
+
132
+ // Bypass hooks (always suspect)
133
+ 'Bash(git commit --no-verify:*)',
134
+ 'Bash(git commit -n:*)',
135
+ 'Bash(git commit --no-gpg-sign:*)',
136
+ 'Bash(git rebase --no-verify:*)',
137
+
138
+ // System path writes — `//` prefix = absolute filesystem
139
+ 'Write(//etc/**)',
140
+ 'Write(//usr/**)',
141
+ 'Write(//System/**)',
142
+ 'Write(//bin/**)',
143
+ 'Write(//sbin/**)',
144
+ 'Write(//Library/**)',
145
+ 'Edit(//etc/**)',
146
+ 'Edit(//usr/**)',
147
+ 'Edit(//System/**)',
148
+ 'Edit(//bin/**)',
149
+ 'Edit(//sbin/**)',
150
+ 'Edit(//Library/**)',
151
+
152
+ // Credentials and secrets
153
+ 'Read(~/.ssh/**)',
154
+ 'Read(~/.aws/credentials)',
155
+ 'Read(~/.aws/config)',
156
+ 'Read(~/.gnupg/**)',
157
+ 'Read(~/.config/gh/hosts.yml)',
158
+ 'Read(~/.docker/config.json)',
159
+ 'Read(~/.npmrc)',
160
+ 'Read(~/.pypirc)',
161
+ 'Read(~/.netrc)',
162
+
163
+ // Accidental publish
164
+ 'Bash(npm publish:*)',
165
+ 'Bash(yarn publish:*)',
166
+ 'Bash(pnpm publish:*)',
167
+ 'Bash(pip upload:*)',
168
+ 'Bash(twine upload:*)',
169
+ 'Bash(cargo publish:*)',
170
+ ];
171
+
172
+ /**
173
+ * Group HOOK_REGISTRY entries by event, then by matcher within each event.
174
+ * Returns the shape Claude Code expects under the top-level "hooks" key.
175
+ */
176
+ function buildHooksObject() {
177
+ const events = {};
178
+
179
+ for (const entry of HOOK_REGISTRY) {
180
+ if (!events[entry.event]) events[entry.event] = new Map();
181
+ const matcherGroups = events[entry.event];
182
+ if (!matcherGroups.has(entry.matcher)) {
183
+ matcherGroups.set(entry.matcher, []);
184
+ }
185
+ matcherGroups.get(entry.matcher).push({
186
+ type: 'command',
187
+ command: `node .chati.dev/hooks/${entry.name}.js`,
188
+ });
189
+ }
190
+
191
+ const result = {};
192
+ for (const [event, matcherGroups] of Object.entries(events)) {
193
+ result[event] = [];
194
+ for (const [matcher, hooks] of matcherGroups.entries()) {
195
+ const group = { hooks };
196
+ if (matcher) group.matcher = matcher;
197
+ result[event].push(group);
198
+ }
199
+ }
200
+ return result;
201
+ }
202
+
203
+ /**
204
+ * Generate the full settings.json content.
205
+ *
206
+ * @returns {string} JSON string with trailing newline, ready to write to disk.
207
+ */
208
+ export function generateClaudeSettings() {
209
+ const settings = {
210
+ $schema: 'https://json.schemastore.org/claude-code-settings.json',
211
+ permissions: {
212
+ allow: PERMISSIONS_ALLOW,
213
+ deny: PERMISSIONS_DENY,
214
+ defaultMode: 'default',
215
+ },
216
+ hooks: buildHooksObject(),
217
+ };
218
+
219
+ return JSON.stringify(settings, null, 2) + '\n';
220
+ }
221
+
222
+ export {
223
+ HOOK_REGISTRY,
224
+ PERMISSIONS_ALLOW,
225
+ PERMISSIONS_DENY,
226
+ };
@@ -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',
@@ -22,6 +22,10 @@
22
22
  * Maps each chati.dev hook to its Gemini CLI equivalent event.
23
23
  */
24
24
  export const HOOK_MAP = {
25
+ // license-guard MUST be first on BeforeModel so license check runs before
26
+ // any model call. Without this, an expired trial could keep using Gemini
27
+ // indefinitely from a long-running session.
28
+ 'license-guard': { event: 'BeforeModel', description: 'Validate license on every turn (chati.dev governance)' },
25
29
  'prism-engine': { event: 'BeforeModel', description: 'Inject PRISM context into model prompt' },
26
30
  'model-governance': { event: 'BeforeModel', description: 'Advisory: recommended model per agent' },
27
31
  'mode-governance': { event: 'BeforeTool', description: 'Block writes outside current mode scope' },
@@ -30,6 +34,7 @@ export const HOOK_MAP = {
30
34
  'session-digest': { event: 'PreCompress', description: 'Save session state before context compression' },
31
35
  'undercover-guard': { event: 'BeforeTool', description: 'Advisory: sanitize internal framework references from deliverables' },
32
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)' },
33
38
  };
34
39
 
35
40
  // ---------------------------------------------------------------------------
@@ -53,6 +58,16 @@ import { fileURLToPath } from 'url';
53
58
  const __filename = fileURLToPath(import.meta.url);
54
59
  const __dirname = join(__filename, '..');
55
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
+ }
56
71
  `;
57
72
 
58
73
  /**
@@ -82,7 +97,7 @@ async function main() {
82
97
  }
83
98
 
84
99
  // Delegate to shared PRISM engine logic
85
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'prism-engine.js');
100
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'prism-engine.js');
86
101
  if (existsSync(hookPath)) {
87
102
  const mod = await import(hookPath);
88
103
  if (typeof mod.buildPrismContext === 'function') {
@@ -137,7 +152,7 @@ async function main() {
137
152
 
138
153
  if (agent) {
139
154
  // Delegate to shared model governance logic
140
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'model-governance.js');
155
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'model-governance.js');
141
156
  if (existsSync(hookPath)) {
142
157
  const mod = await import(hookPath);
143
158
  if (typeof mod.getRecommendedModel === 'function') {
@@ -171,8 +186,9 @@ function generateModeGovernance() {
171
186
  * Blocks write operations outside current mode scope.
172
187
  * Constitution Article XI enforcement.
173
188
  */
189
+ const fwDir = existsSync(join(process.cwd(), '.chati.dev')) ? '.chati.dev' : 'chati.dev';
174
190
  const MODE_SCOPES = {
175
- planning: { allowed: ['chati.dev/', '.chati/', 'chati.dev/artifacts/'] },
191
+ planning: { allowed: [fwDir + '/', '.chati/', 'artifacts/'] },
176
192
  build: { allowed: ['*'] },
177
193
  deploy: { allowed: ['*'] },
178
194
  };
@@ -443,7 +459,7 @@ async function main() {
443
459
  const cwd = event.cwd || process.cwd();
444
460
 
445
461
  // Delegate to shared session digest logic
446
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'session-digest.js');
462
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'session-digest.js');
447
463
  if (existsSync(hookPath)) {
448
464
  const mod = await import(hookPath);
449
465
  if (typeof mod.saveDigest === 'function') {
@@ -491,7 +507,7 @@ async function main() {
491
507
  }
492
508
 
493
509
  // Delegate to shared undercover guard logic
494
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'undercover-guard.js');
510
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'undercover-guard.js');
495
511
  if (!existsSync(hookPath)) {
496
512
  console.log(JSON.stringify({}));
497
513
  return;
@@ -561,7 +577,7 @@ async function main() {
561
577
  }
562
578
 
563
579
  // Delegate to shared style guard logic
564
- const hookPath = join(cwd, 'chati.dev', 'hooks', 'style-guard.js');
580
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'style-guard.js');
565
581
  if (!existsSync(hookPath)) {
566
582
  console.log(JSON.stringify({}));
567
583
  return;
@@ -611,6 +627,7 @@ main();
611
627
  */
612
628
  export function generateAllGeminiHooks() {
613
629
  return {
630
+ 'license-guard.js': generateLicenseGuard(),
614
631
  'prism-engine.js': generatePrismEngine(),
615
632
  'model-governance.js': generateModelGovernance(),
616
633
  'mode-governance.js': generateModeGovernance(),
@@ -622,6 +639,59 @@ export function generateAllGeminiHooks() {
622
639
  };
623
640
  }
624
641
 
642
+ /**
643
+ * Generate the license guard hook for Gemini CLI.
644
+ * BeforeModel event — validates license on every turn.
645
+ *
646
+ * Delegates to chati.dev/hooks/license-guard.js (canonical implementation)
647
+ * via dynamic import of checkLicense(). Translates the verdict object to
648
+ * Gemini's blocking schema (exit code 2 + stderr is the universal block).
649
+ */
650
+ function generateLicenseGuard() {
651
+ return `${HOOK_HEADER}
652
+ async function main() {
653
+ let input = '';
654
+ for await (const chunk of process.stdin) {
655
+ input += chunk;
656
+ }
657
+
658
+ try {
659
+ const event = JSON.parse(input);
660
+ const cwd = event.cwd || process.cwd();
661
+
662
+ // Delegate to canonical license check
663
+ const hookPath = join(cwd, resolveFrameworkDir(cwd), 'hooks', 'license-guard.js');
664
+ if (!existsSync(hookPath)) {
665
+ // No canonical hook present — fail open
666
+ console.log(JSON.stringify({}));
667
+ return;
668
+ }
669
+
670
+ const mod = await import(hookPath);
671
+ if (typeof mod.checkLicense !== 'function') {
672
+ console.log(JSON.stringify({}));
673
+ return;
674
+ }
675
+
676
+ const result = await mod.checkLicense();
677
+ if (result.valid) {
678
+ console.log(JSON.stringify({}));
679
+ return;
680
+ }
681
+
682
+ // Block via exit code 2 (universal Gemini block) + stderr message.
683
+ process.stderr.write(result.reason || 'License invalid');
684
+ process.exit(2);
685
+ } catch {
686
+ // Fail open on any parse/import error — never block on bugs.
687
+ console.log(JSON.stringify({}));
688
+ }
689
+ }
690
+
691
+ main();
692
+ `;
693
+ }
694
+
625
695
  /**
626
696
  * Generate .gemini/settings.json content with hook configuration.
627
697
  *
@@ -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 || [];