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
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reasoning Escalator Hook — Article XXIII enforcement.
4
+ *
5
+ * Two entry points via event-type branching:
6
+ *
7
+ * UserPromptSubmit:
8
+ * Read the user message, evaluate escalation triggers, update
9
+ * session.reasoning_tier. Non-blocking — always allows the prompt
10
+ * through. Side effect only.
11
+ *
12
+ * PreToolUse (matcher Write|Edit|NotebookEdit):
13
+ * If current tier is 'deliberate' AND the target is a source file
14
+ * AND no recent plan exists in .planning/, DENY the tool call with
15
+ * a message telling the agent to write a plan first. Otherwise allow.
16
+ *
17
+ * Pure evaluation logic is exported for unit tests. The main() entry
18
+ * point handles stdin/stdout and session persistence (regex-based YAML
19
+ * mutation — no js-yaml dependency).
20
+ */
21
+
22
+ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
23
+ import { join, isAbsolute, relative } from 'path';
24
+ import { execSync } from 'child_process';
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Pure helpers (exported for testing)
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export const TIERS = ['standard', 'deep', 'deliberate'];
31
+ export const TIER_RANK = { standard: 0, deep: 1, deliberate: 2 };
32
+
33
+ // English-canonical friction keywords — always active regardless of
34
+ // session.language. Article VII mandates English for all framework source
35
+ // material; per-language patterns live in FRICTION_KEYWORDS_LOCALES and are
36
+ // merged on top when session.language selects them.
37
+ export const FRICTION_KEYWORDS_EN = [
38
+ /\bredesign\b/i,
39
+ /\brefactor(?:ing|s)?\b/i,
40
+ /\bstuck\b/i,
41
+ /\b(?:in a |a )?loop\b/i,
42
+ /\bnot working\b/i,
43
+ /\bbreak(?:ing|s)?\b/i,
44
+ ];
45
+
46
+ // Locale-specific patterns merged on top of EN when session.language matches.
47
+ // Each array MUST cover only that locale's phrasings of the same friction
48
+ // concepts (refactoring / not working / stuck). Adding a locale does not
49
+ // mutate the EN set.
50
+ export const FRICTION_KEYWORDS_LOCALES = {
51
+ pt: [
52
+ /\brefatora[çc][aã]o\b/i,
53
+ /\bn[aã]o funciona\b/i,
54
+ /\btravad[oa]\b/i,
55
+ ],
56
+ es: [
57
+ /\brefactorizaci[oó]n\b/i,
58
+ /\bno funciona\b/i,
59
+ /\batascad[oa]\b/i,
60
+ ],
61
+ fr: [
62
+ /\brefactorisation\b/i,
63
+ /\bne marche pas\b/i,
64
+ /\bbloqu[eé]\b/i,
65
+ ],
66
+ };
67
+
68
+ /**
69
+ * Build the active friction-keyword set for a given language.
70
+ * EN is always included; the locale layer is appended if recognised.
71
+ *
72
+ * @param {string} [language='en'] ISO 639-1 language code from session.language
73
+ * @returns {RegExp[]}
74
+ */
75
+ export function getFrictionKeywords(language = 'en') {
76
+ const locale = FRICTION_KEYWORDS_LOCALES[language] ?? [];
77
+ return [...FRICTION_KEYWORDS_EN, ...locale];
78
+ }
79
+
80
+ // Backwards-compat default export: English-only set. Callers that care about
81
+ // locale coverage should use getFrictionKeywords(session.language) instead.
82
+ export const FRICTION_KEYWORDS = getFrictionKeywords('en');
83
+
84
+ export const SOURCE_FILE_EXT = /\.(?:js|jsx|ts|tsx|mjs|cjs|py|rb|go|rs|java|cs|php|swift|kt|scala|c|cc|cpp|h|hpp|css|scss|sass|html|vue|svelte|astro|sql)$/i;
85
+
86
+ /**
87
+ * Parse slash-command style explicit tier commands from a user message.
88
+ * Returns one of 'ultraplan' | 'deliberate' | 'quick' | null.
89
+ */
90
+ export function detectExplicitCommand(msg) {
91
+ if (!msg || typeof msg !== 'string') return null;
92
+ const m = msg.match(/(^|\s)\/(ultraplan|deliberate|quick)\b/i);
93
+ return m ? m[2].toLowerCase() : null;
94
+ }
95
+
96
+ /**
97
+ * Pure tier-escalation evaluator. All inputs are explicit so the function
98
+ * is fully deterministic and testable.
99
+ *
100
+ * @param {object} input
101
+ * @param {'standard'|'deep'|'deliberate'} [input.currentTier]
102
+ * @param {number} [input.correctionCycles]
103
+ * @param {number} [input.decisionTrailOpen]
104
+ * @param {number} [input.filesModifiedCount]
105
+ * @param {number} [input.linesModified]
106
+ * @param {string} [input.userMessage]
107
+ * @param {boolean} [input.crossViewportRegression]
108
+ * @param {boolean} [input.scopeEscalation]
109
+ * @param {string} [input.language='en'] ISO 639-1 session language; selects the
110
+ * locale-specific friction-keyword layer on top of the English canonical set.
111
+ * @returns {{ tier: string, triggers: string[], reason: string }}
112
+ */
113
+ export function evaluateTier(input = {}) {
114
+ const {
115
+ currentTier = 'standard',
116
+ correctionCycles = 0,
117
+ decisionTrailOpen = 0,
118
+ filesModifiedCount = 0,
119
+ linesModified = 0,
120
+ userMessage = '',
121
+ crossViewportRegression = false,
122
+ scopeEscalation = false,
123
+ language = 'en',
124
+ } = input;
125
+
126
+ // Explicit user command always wins.
127
+ const explicit = detectExplicitCommand(userMessage);
128
+ if (explicit === 'quick') {
129
+ return { tier: 'standard', triggers: ['user:/quick'], reason: 'User requested quick mode; tier forced to standard.' };
130
+ }
131
+ if (explicit === 'ultraplan' || explicit === 'deliberate') {
132
+ return { tier: 'deliberate', triggers: [`user:/${explicit}`], reason: 'User requested deliberate mode.' };
133
+ }
134
+
135
+ const triggers = [];
136
+ let targetTier = 'standard';
137
+ const promote = (toTier, label) => {
138
+ triggers.push(label);
139
+ if (TIER_RANK[toTier] > TIER_RANK[targetTier]) targetTier = toTier;
140
+ };
141
+
142
+ if (correctionCycles >= 2) promote('deliberate', `correction_cycles=${correctionCycles}`);
143
+ if (scopeEscalation) promote('deliberate', 'scope_escalation');
144
+ if (filesModifiedCount > 5) promote('deep', `files_modified=${filesModifiedCount}`);
145
+ if (linesModified > 200) promote('deep', `lines_modified=${linesModified}`);
146
+ if (decisionTrailOpen > 2) promote('deep', `decision_trail_open=${decisionTrailOpen}`);
147
+ if (crossViewportRegression) promote('deep', 'cross_viewport_regression');
148
+ const frictionSet = getFrictionKeywords(language);
149
+ if (userMessage && frictionSet.some(r => r.test(userMessage))) {
150
+ const matched = frictionSet.filter(r => r.test(userMessage)).length;
151
+ promote('deep', `friction_keywords=${matched}`);
152
+ }
153
+
154
+ // Monotonic: never auto-demote within a task.
155
+ const finalTier = TIER_RANK[targetTier] > TIER_RANK[currentTier] ? targetTier : currentTier;
156
+ const reason = triggers.length === 0
157
+ ? `No escalation signals. Keeping tier "${finalTier}".`
158
+ : `Triggers (${triggers.length}): ${triggers.join(', ')}`;
159
+ return { tier: finalTier, triggers, reason };
160
+ }
161
+
162
+ /**
163
+ * Check whether a deliberate-tier tool call must be blocked pending a plan.
164
+ *
165
+ * @param {object} input
166
+ * @param {string} input.tier
167
+ * @param {string} input.toolName
168
+ * @param {object} input.toolInput
169
+ * @param {string} input.projectDir
170
+ * @param {number} [input.sessionStartedAt] - ms epoch; plans older than this do not count
171
+ * @returns {{ block: boolean, reason?: string, message?: string }}
172
+ */
173
+ export function checkDeliberatePlanRequirement(input) {
174
+ const { tier, toolName, toolInput, projectDir, sessionStartedAt = 0 } = input || {};
175
+ if (tier !== 'deliberate') return { block: false };
176
+ if (!['Edit', 'Write', 'NotebookEdit'].includes(toolName)) return { block: false };
177
+
178
+ const rawTarget = (toolInput && (toolInput.file_path || toolInput.path)) || '';
179
+ if (!rawTarget) return { block: false };
180
+
181
+ const target = String(rawTarget);
182
+
183
+ // Writing TO a plan file is permitted (that's how the agent satisfies the gate).
184
+ if (/(^|\/)\.planning\//.test(target)) return { block: false };
185
+ // Writing to non-source files (configs, metadata) is permitted.
186
+ if (!SOURCE_FILE_EXT.test(target)) return { block: false };
187
+
188
+ const planningDir = projectDir ? join(projectDir, '.planning') : null;
189
+ if (!planningDir || !existsSync(planningDir)) {
190
+ return {
191
+ block: true,
192
+ reason: 'deliberate_tier_requires_plan',
193
+ message: 'Reasoning tier is DELIBERATE (Article XXIII). Create .planning/<task>-plan.md with approach, alternatives, risks, and acceptance criteria BEFORE editing code.',
194
+ };
195
+ }
196
+
197
+ let entries = [];
198
+ try { entries = readdirSync(planningDir); } catch { /* ignore */ }
199
+ const hasRecentPlan = entries.some(f => {
200
+ if (!f.endsWith('.md')) return false;
201
+ try {
202
+ const s = statSync(join(planningDir, f));
203
+ return s.mtimeMs >= sessionStartedAt;
204
+ } catch { return false; }
205
+ });
206
+ if (hasRecentPlan) return { block: false };
207
+
208
+ return {
209
+ block: true,
210
+ reason: 'deliberate_tier_requires_plan',
211
+ message: 'Reasoning tier is DELIBERATE (Article XXIII). No plan file in .planning/ newer than the session start. Write the plan before editing code.',
212
+ };
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // I/O wrappers (not exercised in unit tests; covered by integration)
217
+ // ---------------------------------------------------------------------------
218
+
219
+ /**
220
+ * Parse the reasoning_tier and started_at fields out of session.yaml using
221
+ * regex. Stays dependency-free (hooks do not import js-yaml).
222
+ */
223
+ export function readSessionTier(projectDir) {
224
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
225
+ if (!existsSync(sessionPath)) return { tier: 'standard', startedAt: 0, raw: null };
226
+ let raw = '';
227
+ try { raw = readFileSync(sessionPath, 'utf-8'); } catch { return { tier: 'standard', startedAt: 0, raw: null }; }
228
+ const tierMatch = raw.match(/^\s*reasoning_tier:\s*(["']?)(\w+)\1\s*$/m);
229
+ const startedMatch = raw.match(/^\s*started_at:\s*(["']?)([^"'\n]+)\1\s*$/m);
230
+ let startedAt = 0;
231
+ if (startedMatch) {
232
+ const t = Date.parse(startedMatch[2]);
233
+ if (!Number.isNaN(t)) startedAt = t;
234
+ }
235
+ const tier = tierMatch ? tierMatch[2] : 'standard';
236
+ return { tier: TIERS.includes(tier) ? tier : 'standard', startedAt, raw };
237
+ }
238
+
239
+ /**
240
+ * Parse session.language out of session.yaml. Defaults to 'en' if the key is
241
+ * missing or unreadable. Kept regex-based to avoid a js-yaml dep in hooks.
242
+ */
243
+ export function readSessionLanguage(projectDir) {
244
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
245
+ if (!existsSync(sessionPath)) return 'en';
246
+ let raw = '';
247
+ try { raw = readFileSync(sessionPath, 'utf-8'); } catch { return 'en'; }
248
+ const m = raw.match(/^\s*language:\s*(["']?)([a-z]{2})\1\s*$/m);
249
+ return m ? m[2] : 'en';
250
+ }
251
+
252
+ /**
253
+ * Update session.yaml with a new reasoning_tier value. Append `reasoning_tier:`
254
+ * if missing; replace in place if present. Silent on failure.
255
+ */
256
+ export function writeSessionTier(projectDir, tier) {
257
+ if (!TIERS.includes(tier)) return false;
258
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
259
+ if (!existsSync(sessionPath)) return false;
260
+ let raw = '';
261
+ try { raw = readFileSync(sessionPath, 'utf-8'); } catch { return false; }
262
+ const line = `reasoning_tier: ${tier}`;
263
+ let next;
264
+ if (/^\s*reasoning_tier:\s*\w+\s*$/m.test(raw)) {
265
+ next = raw.replace(/^\s*reasoning_tier:\s*\w+\s*$/m, line);
266
+ } else {
267
+ next = raw.trimEnd() + '\n' + line + '\n';
268
+ }
269
+ try { writeFileSync(sessionPath, next, 'utf-8'); return true; } catch { return false; }
270
+ }
271
+
272
+ /**
273
+ * Count files modified since HEAD via git. Returns -1 when git is unavailable.
274
+ */
275
+ export function gitDiffStats(projectDir) {
276
+ if (!projectDir) return { files: -1, lines: -1 };
277
+ try {
278
+ const files = execSync('git diff --name-only HEAD 2>/dev/null', {
279
+ cwd: projectDir, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
280
+ }).trim().split('\n').filter(Boolean);
281
+ let lines = 0;
282
+ try {
283
+ const stat = execSync('git diff --shortstat HEAD 2>/dev/null', {
284
+ cwd: projectDir, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'],
285
+ }).trim();
286
+ const m = stat.match(/(\d+)\s+insertion.*?(\d+)\s+deletion/);
287
+ if (m) lines = parseInt(m[1], 10) + parseInt(m[2], 10);
288
+ else {
289
+ const ins = stat.match(/(\d+)\s+insertion/);
290
+ if (ins) lines = parseInt(ins[1], 10);
291
+ }
292
+ } catch { /* ignore */ }
293
+ return { files: files.length, lines };
294
+ } catch {
295
+ return { files: -1, lines: -1 };
296
+ }
297
+ }
298
+
299
+ function allowOutput() {
300
+ return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
301
+ }
302
+ function denyOutput(reason) {
303
+ return {
304
+ hookSpecificOutput: {
305
+ hookEventName: 'PreToolUse',
306
+ permissionDecision: 'deny',
307
+ permissionDecisionReason: reason,
308
+ },
309
+ };
310
+ }
311
+ function userPromptPassthrough() {
312
+ // UserPromptSubmit hooks emit a different shape; nothing to block here.
313
+ return {};
314
+ }
315
+
316
+ async function main() {
317
+ let input = '';
318
+ for await (const chunk of process.stdin) input += chunk;
319
+
320
+ try {
321
+ const event = JSON.parse(input || '{}');
322
+ const projectDir = event.cwd || process.cwd();
323
+ const hookEventName = event.hook_event_name || event.hookEventName || '';
324
+
325
+ if (hookEventName === 'UserPromptSubmit') {
326
+ const userMessage = event.user_message || event.prompt || '';
327
+ const { tier: currentTier, startedAt: _s } = readSessionTier(projectDir);
328
+ const language = readSessionLanguage(projectDir);
329
+ const { files: filesModifiedCount, lines: linesModified } = gitDiffStats(projectDir);
330
+ const result = evaluateTier({
331
+ currentTier,
332
+ userMessage,
333
+ filesModifiedCount: Math.max(0, filesModifiedCount),
334
+ linesModified: Math.max(0, linesModified),
335
+ language,
336
+ });
337
+ if (result.tier !== currentTier) {
338
+ writeSessionTier(projectDir, result.tier);
339
+ }
340
+ process.stdout.write(JSON.stringify(userPromptPassthrough()));
341
+ return;
342
+ }
343
+
344
+ if (hookEventName === 'PreToolUse') {
345
+ const toolName = event.tool_name || '';
346
+ const toolInput = event.tool_input || {};
347
+ const { tier, startedAt } = readSessionTier(projectDir);
348
+ const verdict = checkDeliberatePlanRequirement({
349
+ tier, toolName, toolInput, projectDir, sessionStartedAt: startedAt,
350
+ });
351
+ if (verdict.block) {
352
+ process.stdout.write(JSON.stringify(denyOutput(`[Article XXIII] ${verdict.message}`)));
353
+ } else {
354
+ process.stdout.write(JSON.stringify(allowOutput()));
355
+ }
356
+ return;
357
+ }
358
+
359
+ // Unknown event — pass through.
360
+ process.stdout.write(JSON.stringify(allowOutput()));
361
+ } catch (err) {
362
+ // Fail-OPEN: hook errors must not block user work.
363
+ process.stderr.write(`[chati-hook-error] reasoning-escalator: ${err?.message || 'unknown'}\n`);
364
+ process.stdout.write(JSON.stringify(allowOutput()));
365
+ }
366
+ }
367
+
368
+ // Only run main() when invoked as a script (tests can import pure helpers).
369
+ if (import.meta.url === `file://${process.argv[1] || ''}`) {
370
+ main();
371
+ }
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Reference Trigger Hook — PostToolUse (runs after Write tool)
4
+ *
5
+ * When the Brief agent writes brief-report.md, this hook:
6
+ * 1. Extracts visual_references URLs
7
+ * 2. Launches reference-capture.js in BACKGROUND (detached)
8
+ * 3. Appends a "Visual Reference Captures" note to brief-report.md
9
+ *
10
+ * The capture runs in background so Claude doesn't wait ~9 min.
11
+ * The script writes capture-complete.json when ALL sites finish.
12
+ * Brand Architect polls for this file before spawning Visualizer.
13
+ */
14
+
15
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
16
+ import { join } from 'path';
17
+ import { spawn } from 'child_process';
18
+
19
+ async function readStdin() {
20
+ let data = '';
21
+ for await (const chunk of process.stdin) data += chunk;
22
+ return data;
23
+ }
24
+
25
+ async function main() {
26
+ let input;
27
+ try {
28
+ input = JSON.parse(await readStdin() || '{}');
29
+ } catch {
30
+ process.stdout.write('{}\n');
31
+ return;
32
+ }
33
+
34
+ const toolName = input.tool_name || '';
35
+ const toolInput = input.tool_input || {};
36
+ const filePath = toolInput.file_path || '';
37
+ const cwd = input.cwd || process.cwd();
38
+
39
+ process.stdout.write('{}\n');
40
+
41
+ if (toolName !== 'Write') return;
42
+ if (!filePath.endsWith('1-Brief/brief-report.md')) return;
43
+ if (!existsSync(filePath)) return;
44
+
45
+ let content;
46
+ try { content = readFileSync(filePath, 'utf-8'); } catch { return; }
47
+
48
+ // Don't re-run if already triggered (idempotency)
49
+ if (content.includes('## Visual Reference Captures')) return;
50
+
51
+ // Extract URLs — support both `url: "https://..."` YAML format and bare `- https://...` list items
52
+ const urlPatterns = [
53
+ /url:\s*["']?(https?:\/\/[^\s"']+)["']?/g,
54
+ /^\s*-\s+(https?:\/\/[^\s"']+)/gm,
55
+ ];
56
+ const urlSet = new Set();
57
+ for (const pattern of urlPatterns) {
58
+ for (const match of content.matchAll(pattern)) {
59
+ urlSet.add(match[1]);
60
+ }
61
+ }
62
+ const urls = [...urlSet];
63
+ if (urls.length === 0) return;
64
+
65
+ // Find script + playwright
66
+ const fwCandidates = ['.chati.dev', 'chati.dev'];
67
+ let scriptPath = null;
68
+ let nodePath = process.env.NODE_PATH || '';
69
+ for (const fw of fwCandidates) {
70
+ const p = join(cwd, fw, 'scripts', 'reference-capture.js');
71
+ if (existsSync(p)) { scriptPath = p; break; }
72
+ }
73
+ if (!scriptPath) return;
74
+ for (const fw of fwCandidates) {
75
+ const cliModules = join(cwd, fw, '_cli', 'node_modules');
76
+ if (existsSync(join(cliModules, 'playwright'))) {
77
+ nodePath = nodePath ? `${cliModules}:${nodePath}` : cliModules;
78
+ break;
79
+ }
80
+ }
81
+
82
+ const refOutput = join(cwd, 'artifacts', '4-UX', 'references');
83
+ mkdirSync(refOutput, { recursive: true });
84
+
85
+ // Launch capture in BACKGROUND — don't block Claude.
86
+ // The script writes capture-complete.json when done.
87
+ const child = spawn('node', [scriptPath, '--urls', ...urls, '--output', refOutput], {
88
+ cwd,
89
+ detached: true,
90
+ stdio: 'ignore',
91
+ env: { ...process.env, NODE_PATH: nodePath },
92
+ });
93
+ child.unref();
94
+
95
+ // Append note to brief-report.md immediately (capture runs in background)
96
+ const captureSection = `\n\n## Visual Reference Captures
97
+
98
+ > Visual reference capture is running in background for ${urls.length} site(s).
99
+ > Screenshots, scroll videos, and CSS tokens are being captured via Playwright.
100
+ >
101
+ > **IMPORTANT for planning agents:** This section contains preliminary data.
102
+ > A deeper visual analysis of the scroll experience, animation patterns, and design DNA
103
+ > will be performed by the **UX Brand Architect** using the Visualizer agent during planning.
104
+ > Do NOT base final design decisions solely on this summary — wait for the full
105
+ > Visual Experience Analysis in \`artifacts/4-UX/reference-analysis.md\`.
106
+ >
107
+ > **Sites being captured:** ${urls.map(u => `\`${u}\``).join(', ')}
108
+ > **Output:** \`artifacts/4-UX/references/\`
109
+ > **Status:** Check \`artifacts/4-UX/references/capture-complete.json\` for completion status.
110
+ `;
111
+
112
+ try {
113
+ writeFileSync(filePath, content + captureSection, 'utf-8');
114
+ } catch { /* ignore */ }
115
+ }
116
+
117
+ main().catch(() => process.stdout.write('{}\n'));
@@ -96,6 +96,55 @@ async function main() {
96
96
 
97
97
  writeFileSync(join(digestDir, fileName), lines.join('\n') + '\n', 'utf-8');
98
98
 
99
+ // Memory-bootstrap-v1 (F-A5-006): when the current agent has no
100
+ // MEMORY.md yet (or it is empty), write a minimal one from this digest
101
+ // so prism-engine's L4 memory layer is never empty in early sessions.
102
+ // Inline here instead of importing from src/memory/agent-memory.js
103
+ // because hooks run via raw Node and cannot reliably import the
104
+ // package source on installed projects. The shape mirrors the
105
+ // writeBootstrapMemory() contract in agent-memory.js.
106
+ try {
107
+ const agent = digest.currentAgent;
108
+ if (agent && agent !== 'none') {
109
+ const memDir = join(projectDir, '.chati', 'memories', agent);
110
+ const memPath = join(memDir, 'MEMORY.md');
111
+ let isEmpty = true;
112
+ if (existsSync(memPath)) {
113
+ try { isEmpty = readFileSync(memPath, 'utf-8').trim().length === 0; }
114
+ catch { /* treat as empty */ }
115
+ }
116
+ if (isEmpty) {
117
+ const bootstrap = [];
118
+ bootstrap.push('<!-- bootstrap-write: true -->');
119
+ bootstrap.push('<!-- source: session-digest bootstrap (memory-bootstrap-v1) -->');
120
+ bootstrap.push('');
121
+ bootstrap.push('## Episodic');
122
+ bootstrap.push('');
123
+ bootstrap.push(`- Resumed at mode=${digest.mode}, pipeline_position=${digest.pipelinePosition}, workflow=${digest.workflow || 'unknown'}. (low) [source:bootstrap]`);
124
+ if (digest.active_task) {
125
+ bootstrap.push('');
126
+ bootstrap.push('## Semantic');
127
+ bootstrap.push('');
128
+ bootstrap.push(`- Active task on resume: ${String(digest.active_task).slice(0, 500)} (low) [source:bootstrap]`);
129
+ }
130
+ if (Array.isArray(digest.gotchas_found) && digest.gotchas_found.length > 0) {
131
+ bootstrap.push('');
132
+ bootstrap.push('## Procedural');
133
+ bootstrap.push('');
134
+ for (const g of digest.gotchas_found.slice(0, 5)) {
135
+ if (!g?.message) continue;
136
+ bootstrap.push(`- Known gotcha [${g.severity || 'info'}] ${g.id || ''}: ${String(g.message).slice(0, 200)} (low) [source:bootstrap, severity:${g.severity || 'info'}]`);
137
+ }
138
+ }
139
+ bootstrap.push('');
140
+ mkdirSync(memDir, { recursive: true });
141
+ writeFileSync(memPath, bootstrap.join('\n'), 'utf-8');
142
+ }
143
+ }
144
+ } catch (err) {
145
+ process.stderr.write(`[chati] session-digest bootstrap: ${err.message}\n`);
146
+ }
147
+
99
148
  // Auto daily digest: append session entry on every PreCompact
100
149
  try {
101
150
  const dailyDir = join(projectDir, '.chati', 'memories', 'shared', 'daily');
@@ -139,7 +188,7 @@ async function main() {
139
188
  try {
140
189
  const child = spawn('node', [
141
190
  '-e',
142
- `import('./packages/chati-dev/src/memory/dream.js').then(m => m.runDreamConsolidation(${JSON.stringify(projectDir)}))`,
191
+ `import(${JSON.stringify(projectDir + '/node_modules/chati-dev/src/memory/dream.js')}).then(m => m.runDreamConsolidation(${JSON.stringify(projectDir)}))`,
143
192
  ], {
144
193
  cwd: projectDir,
145
194
  detached: true,
@@ -16,6 +16,10 @@
16
16
  {
17
17
  "type": "command",
18
18
  "command": "node chati.dev/hooks/model-governance.js"
19
+ },
20
+ {
21
+ "type": "command",
22
+ "command": "node chati.dev/hooks/reasoning-escalator.js"
19
23
  }
20
24
  ]
21
25
  }
@@ -31,6 +35,29 @@
31
35
  ]
32
36
  }
33
37
  ],
38
+ "PostToolUse": [
39
+ {
40
+ "matcher": "Write",
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "node chati.dev/hooks/advance-trigger.js"
45
+ },
46
+ {
47
+ "type": "command",
48
+ "command": "node chati.dev/hooks/reference-trigger.js"
49
+ },
50
+ {
51
+ "type": "command",
52
+ "command": "node chati.dev/hooks/brief-validator.js"
53
+ },
54
+ {
55
+ "type": "command",
56
+ "command": "node chati.dev/hooks/post-dev.js"
57
+ }
58
+ ]
59
+ }
60
+ ],
34
61
  "PreToolUse": [
35
62
  {
36
63
  "matcher": "Read",
@@ -42,7 +69,7 @@
42
69
  ]
43
70
  },
44
71
  {
45
- "matcher": "Write|Edit",
72
+ "matcher": "Write|Edit|NotebookEdit",
46
73
  "hooks": [
47
74
  {
48
75
  "type": "command",
@@ -63,6 +90,10 @@
63
90
  {
64
91
  "type": "command",
65
92
  "command": "node chati.dev/hooks/team-quality-gate.js"
93
+ },
94
+ {
95
+ "type": "command",
96
+ "command": "node chati.dev/hooks/reasoning-escalator.js"
66
97
  }
67
98
  ]
68
99
  },
@@ -6,7 +6,7 @@
6
6
  * - No em-dashes (U+2013 EN DASH, U+2014 EM DASH)
7
7
  * - No emojis in generated content
8
8
  *
9
- * Mode: Advisory (warn, not block). Agent receives warning and should self-correct.
9
+ * Mode: Em-dashes are HARD BLOCKED (deny). Emojis are advisory (warn).
10
10
  * Feature-flag gated: respects anti_dash and no_emojis in config.yaml.
11
11
  *
12
12
  * Exempt paths: framework files (chati.dev/, .chati/), YAML configs, CLAUDE.md
@@ -35,8 +35,9 @@ const EXEMPT_PATTERNS = [
35
35
  /AGENTS\.md$/,
36
36
  /\.yaml$/,
37
37
  /\.yml$/,
38
- /\.json$/,
39
38
  /package\.json$/,
39
+ /package-lock\.json$/,
40
+ /tsconfig\.json$/,
40
41
  /\.lock$/,
41
42
  ];
42
43
 
@@ -111,11 +112,19 @@ async function main() {
111
112
  result = checkBashStyle(command);
112
113
  }
113
114
 
114
- if (result.violations.length > 0) {
115
- // Advisory: surface as reason but allow. Style is a soft preference,
116
- // not a security boundary hard-blocking blocks legitimate markdown.
115
+ if (result.hasDash) {
116
+ // Em-dashes are HARD BLOCKED they are never acceptable in project output.
117
+ // Use commas, semicolons, colons, or periods instead.
118
+ process.stdout.write(JSON.stringify(denyOutput(
119
+ `[Style Guard] Em-dash or en-dash detected. REWRITE without em-dashes. Use commas, semicolons, colons, or periods instead. This is non-negotiable.`
120
+ )));
121
+ return;
122
+ }
123
+
124
+ if (result.hasEmoji) {
125
+ // Emojis are advisory — warn but allow
117
126
  process.stdout.write(JSON.stringify(allowOutput(
118
- `[Style Guard] ${result.violations.join(' ')} Consider rewriting without em-dashes and emojis (Article V).`
127
+ `[Style Guard] Emoji detected. Consider using icons from approved libraries instead.`
119
128
  )));
120
129
  return;
121
130
  }
@@ -127,6 +136,16 @@ async function main() {
127
136
  }
128
137
  }
129
138
 
139
+ function denyOutput(reason) {
140
+ return {
141
+ hookSpecificOutput: {
142
+ hookEventName: 'PreToolUse',
143
+ permissionDecision: 'deny',
144
+ permissionDecisionReason: reason,
145
+ },
146
+ };
147
+ }
148
+
130
149
  function allowOutput(reason) {
131
150
  const out = {
132
151
  hookSpecificOutput: {