chati-dev 4.3.0 → 4.4.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 (156) hide show
  1. package/README.md +18 -13
  2. package/bin/chati.js +32 -4
  3. package/framework/agents/build/dev.md +9 -5
  4. package/framework/agents/discover/brief.md +2 -0
  5. package/framework/agents/discover/brownfield-wu.md +2 -0
  6. package/framework/agents/discover/greenfield-wu.md +18 -1
  7. package/framework/agents/plan/detail.md +2 -0
  8. package/framework/agents/plan/tasks.md +31 -0
  9. package/framework/agents/plan/ux-brand-architect.md +21 -2
  10. package/framework/agents/plan/ux-component-engineer.md +10 -0
  11. package/framework/agents/quality/qa-implementation.md +1 -1
  12. package/framework/agents/quality/qa-planning.md +1 -1
  13. package/framework/agents/quality/qa-visual.md +33 -21
  14. package/framework/config.yaml +5 -4
  15. package/framework/constitution.md +58 -11
  16. package/framework/context/governance.md +12 -10
  17. package/framework/context/root.md +2 -2
  18. package/framework/data/entity-registry.yaml +12 -4
  19. package/framework/data/qa-rubrics.yaml +335 -0
  20. package/framework/domains/constitution.yaml +3 -3
  21. package/framework/executors/json-validate.js +80 -0
  22. package/framework/executors/npm-script.js +72 -0
  23. package/framework/executors/path-check.js +67 -0
  24. package/framework/executors/registry.yaml +36 -0
  25. package/framework/hooks/advance-trigger.js +47 -3
  26. package/framework/hooks/constitution-guard.js +9 -4
  27. package/framework/hooks/git-push-authority.js +113 -0
  28. package/framework/hooks/license-guard.js +59 -9
  29. package/framework/hooks/model-governance.js +40 -17
  30. package/framework/hooks/prism-engine.js +78 -11
  31. package/framework/hooks/session-digest.js +12 -3
  32. package/framework/hooks/settings.json +7 -3
  33. package/framework/hooks/style-guard.js +52 -3
  34. package/framework/hooks/team-quality-gate.js +43 -5
  35. package/framework/i18n/en.yaml +3 -3
  36. package/framework/i18n/es.yaml +3 -3
  37. package/framework/i18n/fr.yaml +3 -3
  38. package/framework/i18n/pt.yaml +3 -3
  39. package/framework/intelligence/confidence.yaml +85 -6
  40. package/framework/intelligence/context-engine.md +9 -5
  41. package/framework/intelligence/decision-engine.md +1 -1
  42. package/framework/orchestrator/chati-router.js +59 -5
  43. package/framework/orchestrator/chati-update.md +19 -3
  44. package/framework/orchestrator/chati.md +20 -1
  45. package/framework/quality-gates/planning-gate.md +2 -0
  46. package/framework/rules/human-writing-style.md +47 -0
  47. package/framework/scaffold/motion-premium/README.md +12 -2
  48. package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +1 -1
  49. package/framework/scaffold/motion-premium/scaffold.yaml +6 -1
  50. package/framework/scaffold/motion-premium-3d/README.md +10 -0
  51. package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +1 -1
  52. package/framework/scaffold/motion-premium-3d/scaffold.yaml +3 -0
  53. package/framework/scaffold/saas-dashboard/README.md +58 -0
  54. package/framework/scaffold/saas-dashboard/app/dashboard/page.tsx.template +546 -0
  55. package/framework/scaffold/saas-dashboard/app/globals.css.template +746 -0
  56. package/framework/scaffold/saas-dashboard/app/layout.tsx.template +192 -0
  57. package/framework/scaffold/saas-dashboard/app/shell.css.template +1070 -0
  58. package/framework/scaffold/saas-dashboard/components/data/Chart.tsx.template +725 -0
  59. package/framework/scaffold/saas-dashboard/components/data/DataTable.tsx.template +623 -0
  60. package/framework/scaffold/saas-dashboard/components/data/DataTable.types.ts.template +104 -0
  61. package/framework/scaffold/saas-dashboard/components/data/DataTablePagination.tsx.template +234 -0
  62. package/framework/scaffold/saas-dashboard/components/data/SkeletonBlock.tsx.template +111 -0
  63. package/framework/scaffold/saas-dashboard/components/data/StatCard.tsx.template +393 -0
  64. package/framework/scaffold/saas-dashboard/components/data/StatCardGrid.tsx.template +121 -0
  65. package/framework/scaffold/saas-dashboard/components/feedback/EmptyState.tsx.template +281 -0
  66. package/framework/scaffold/saas-dashboard/components/interaction/CommandPalette.tsx.template +767 -0
  67. package/framework/scaffold/saas-dashboard/components/interaction/Field.tsx.template +351 -0
  68. package/framework/scaffold/saas-dashboard/components/interaction/Form.tsx.template +424 -0
  69. package/framework/scaffold/saas-dashboard/components/primitives/Badge.tsx.template +193 -0
  70. package/framework/scaffold/saas-dashboard/components/primitives/Button.tsx.template +261 -0
  71. package/framework/scaffold/saas-dashboard/components/primitives/Card.tsx.template +287 -0
  72. package/framework/scaffold/saas-dashboard/components/primitives/Input.tsx.template +310 -0
  73. package/framework/scaffold/saas-dashboard/components/primitives/Spinner.tsx.template +135 -0
  74. package/framework/scaffold/saas-dashboard/components/primitives/index.ts.template +60 -0
  75. package/framework/scaffold/saas-dashboard/components/shell/AppShell.tsx.template +174 -0
  76. package/framework/scaffold/saas-dashboard/components/shell/Breadcrumb.tsx.template +186 -0
  77. package/framework/scaffold/saas-dashboard/components/shell/Container.tsx.template +86 -0
  78. package/framework/scaffold/saas-dashboard/components/shell/NavItem.tsx.template +147 -0
  79. package/framework/scaffold/saas-dashboard/components/shell/Sidebar.tsx.template +378 -0
  80. package/framework/scaffold/saas-dashboard/components/shell/SidebarContext.tsx.template +169 -0
  81. package/framework/scaffold/saas-dashboard/components/shell/ThemeToggle.tsx.template +128 -0
  82. package/framework/scaffold/saas-dashboard/components/shell/Topbar.tsx.template +301 -0
  83. package/framework/scaffold/saas-dashboard/lib/brand.ts.template +324 -0
  84. package/framework/scaffold/saas-dashboard/scaffold.yaml +225 -0
  85. package/framework/schemas/session.schema.json +5 -0
  86. package/framework/schemas/task.schema.json +9 -0
  87. package/framework/scripts/visual-qa.js +101 -3
  88. package/framework/tasks/orchestrator-health.md +4 -4
  89. package/framework/tasks/qa-impl-verdict.md +11 -5
  90. package/framework/templates/brandbook-html-tmpl.md +1 -1
  91. package/framework/templates/qa-gate-tmpl.yaml +24 -11
  92. package/package.json +4 -3
  93. package/src/config/context-file-generator.js +0 -6
  94. package/src/dashboard/renderer.js +0 -36
  95. package/src/executors/runner.js +204 -0
  96. package/src/installer/core.js +13 -3
  97. package/src/installer/templates.js +5 -6
  98. package/src/intelligence/registry-manager.js +1 -1
  99. package/src/license/client.js +9 -3
  100. package/src/license/commands.js +12 -2
  101. package/src/license/machine-id.js +42 -1
  102. package/src/memory/gotchas.js +58 -40
  103. package/src/memory/magic-docs.js +1 -1
  104. package/src/memory/session-digest.js +9 -4
  105. package/src/orchestrator/cli.js +186 -15
  106. package/src/orchestrator/doctor.js +98 -16
  107. package/src/orchestrator/pipeline-manager.js +44 -22
  108. package/src/orchestrator/session-manager.js +64 -8
  109. package/src/telemetry/sender.js +7 -19
  110. package/src/terminal/run-team.js +3 -3
  111. package/src/upgrade/tracked-files-detector.js +34 -16
  112. package/src/utils/feature-flags.js +1 -1
  113. package/src/utils/flatten-entities.js +4 -40
  114. package/src/utils/schema-validator.js +0 -14
  115. package/src/wizard/i18n.js +3 -3
  116. package/src/api/index.js +0 -120
  117. package/src/autonomy/autonomous-gate.js +0 -294
  118. package/src/autonomy/build-loop.js +0 -281
  119. package/src/autonomy/build-state.js +0 -286
  120. package/src/autonomy/cause-analyzer.js +0 -177
  121. package/src/autonomy/escalation.js +0 -214
  122. package/src/autonomy/index.js +0 -51
  123. package/src/autonomy/mode-manager.js +0 -225
  124. package/src/autonomy/mode-suggester.js +0 -283
  125. package/src/autonomy/progress-reporter.js +0 -275
  126. package/src/autonomy/safety-net.js +0 -370
  127. package/src/config/agent-customizer.js +0 -231
  128. package/src/decision/analyzer.js +0 -291
  129. package/src/decision/engine.js +0 -250
  130. package/src/decision/index.js +0 -38
  131. package/src/decision/registry-healer.js +0 -468
  132. package/src/decision/registry-updater.js +0 -339
  133. package/src/extensions/loader.js +0 -145
  134. package/src/extensions/registry.js +0 -134
  135. package/src/gates/circuit-breaker.js +0 -151
  136. package/src/gates/g1-planning-complete.js +0 -154
  137. package/src/gates/g2-qa-planning.js +0 -156
  138. package/src/gates/g3-implementation.js +0 -215
  139. package/src/gates/g4-qa-implementation.js +0 -240
  140. package/src/gates/g5-deploy-ready.js +0 -181
  141. package/src/gates/gate-base.js +0 -185
  142. package/src/gates/index.js +0 -46
  143. package/src/health/auto-fix.js +0 -216
  144. package/src/health/engine.js +0 -246
  145. package/src/merger/semantic-merger.js +0 -292
  146. package/src/preview/detector.js +0 -238
  147. package/src/preview/index.js +0 -20
  148. package/src/preview/launcher.js +0 -235
  149. package/src/preview/log-buffer.js +0 -103
  150. package/src/quality/metrics-collector.js +0 -281
  151. package/src/quality/test-runner.js +0 -366
  152. package/src/tasks/executor.js +0 -195
  153. package/src/tasks/index.js +0 -4
  154. package/src/tasks/loader.js +0 -210
  155. package/src/tasks/router.js +0 -182
  156. package/src/utils/event-bus.js +0 -126
@@ -43,20 +43,6 @@ const AGENT_MODELS = {
43
43
  devops: { provider: 'claude', model: 'sonnet', tier: 'sonnet' },
44
44
  };
45
45
 
46
- /**
47
- * Upgrade conditions per agent. When context matches, the model
48
- * should be upgraded to the specified target.
49
- */
50
- const UPGRADE_CONDITIONS = {
51
- orchestrator: { to: 'opus', condition: 'complex routing or deviation handling' },
52
- 'greenfield-wu': { to: 'sonnet', condition: 'multi-stack or enterprise' },
53
- brief: { to: 'opus', condition: '10+ integrations' },
54
- ux: { to: 'opus', condition: 'design system from scratch' },
55
- phases: { to: 'opus', condition: '20+ requirements' },
56
- tasks: { to: 'opus', condition: '50+ tasks' },
57
- devops: { to: 'opus', condition: 'multi-environment or IaC' },
58
- };
59
-
60
46
  function getCurrentAgent(projectDir) {
61
47
  const sessionPath = join(projectDir, '.chati', 'session.yaml');
62
48
  if (!existsSync(sessionPath)) return null;
@@ -66,6 +52,27 @@ function getCurrentAgent(projectDir) {
66
52
  return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
67
53
  }
68
54
 
55
+ function getSessionField(projectDir, key) {
56
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
57
+ if (!existsSync(sessionPath)) return null;
58
+ const raw = readFileSync(sessionPath, 'utf-8');
59
+ const match = raw.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
60
+ if (!match) return null;
61
+ const v = match[1].trim().replace(/^["']|["']$/g, '');
62
+ return v === 'null' || v === '' ? null : v;
63
+ }
64
+
65
+ // Collapse a model id to its family so opus and opus[1m] count as the same
66
+ // model for parity purposes (Article XXIV).
67
+ function modelFamily(m) {
68
+ if (!m) return null;
69
+ const s = String(m).toLowerCase();
70
+ for (const fam of ['opus', 'sonnet', 'haiku', 'pro', 'flash', 'codex', 'gemini', 'gpt']) {
71
+ if (s.includes(fam)) return fam;
72
+ }
73
+ return s;
74
+ }
75
+
69
76
  async function main() {
70
77
  let input = '';
71
78
  for await (const chunk of process.stdin) {
@@ -77,15 +84,31 @@ async function main() {
77
84
  const projectDir = event.cwd || process.cwd();
78
85
  const agent = getCurrentAgent(projectDir);
79
86
 
87
+ // Advisory context injection via the canonical UserPromptSubmit schema.
88
+ const lines = [];
89
+
80
90
  if (agent && AGENT_MODELS[agent]) {
81
91
  const assignment = AGENT_MODELS[agent];
82
92
  const expected = assignment.model || assignment;
83
93
  const provider = assignment.provider || 'claude';
84
- // Advisory context injection via canonical UserPromptSubmit schema.
94
+ lines.push(`[Article XVI] Agent "${agent}" assigned model: ${expected} (provider: ${provider})`);
95
+ }
96
+
97
+ // Article XXIV: Model-Harness Parity. When the active model family differs
98
+ // from the model at the last harness audit, advise a re-audit. Advisory
99
+ // only, never blocks; the family comparison ignores variant changes such
100
+ // as opus vs opus[1m].
101
+ const activeModel = getSessionField(projectDir, 'active_model');
102
+ const modelAtLastAudit = getSessionField(projectDir, 'model_at_last_audit');
103
+ if (activeModel && modelAtLastAudit && modelFamily(activeModel) !== modelFamily(modelAtLastAudit)) {
104
+ lines.push(`[Article XXIV] Active model "${activeModel}" differs from the model at the last harness audit ("${modelAtLastAudit}"). Consider running /chati-update to re-audit the harness for this model. This is advisory and does not block your work.`);
105
+ }
106
+
107
+ if (lines.length > 0) {
85
108
  process.stdout.write(JSON.stringify({
86
109
  hookSpecificOutput: {
87
110
  hookEventName: 'UserPromptSubmit',
88
- additionalContext: `[Article XVI] Agent "${agent}" assigned model: ${expected} (provider: ${provider})`,
111
+ additionalContext: lines.join('\n'),
89
112
  },
90
113
  }));
91
114
  } else {
@@ -97,7 +120,7 @@ async function main() {
97
120
  }
98
121
  }
99
122
 
100
- export { AGENT_MODELS, UPGRADE_CONDITIONS, getCurrentAgent };
123
+ export { AGENT_MODELS, getCurrentAgent };
101
124
 
102
125
  // Only run main when executed directly (not imported by tests).
103
126
  // realpathSync resolves symlinks on both sides — important on macOS where
@@ -32,9 +32,26 @@
32
32
  * Claude Code Hook: triggers on every user message submission.
33
33
  */
34
34
 
35
- import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
35
+ import { existsSync, readFileSync, writeFileSync, renameSync, statSync } from 'fs';
36
36
  import { join } from 'path';
37
37
 
38
+ // mtime-keyed read cache. This is a no-op for the per-prompt hook process
39
+ // (it starts fresh each invocation and must read at least once), but it
40
+ // spares repeated disk reads for long-lived importers of these helpers
41
+ // (status --watch, context-status, tests) that call them many times in one
42
+ // process. The mtime key guarantees a changed file is always re-read, so the
43
+ // cache can never serve stale state.
44
+ const _fileReadCache = new Map(); // absPath -> { mtimeMs, content }
45
+ function readFileCached(absPath) {
46
+ let mtimeMs;
47
+ try { mtimeMs = statSync(absPath).mtimeMs; } catch { return null; }
48
+ const hit = _fileReadCache.get(absPath);
49
+ if (hit && hit.mtimeMs === mtimeMs) return hit.content;
50
+ const content = readFileSync(absPath, 'utf-8');
51
+ _fileReadCache.set(absPath, { mtimeMs, content });
52
+ return content;
53
+ }
54
+
38
55
  // ---------------------------------------------------------------------------
39
56
  // Context window limits — load from shared JSON, fall back to embedded.
40
57
  // ---------------------------------------------------------------------------
@@ -47,18 +64,23 @@ function loadLimits(projectDir) {
47
64
  for (const candidate of candidates) {
48
65
  if (existsSync(candidate)) {
49
66
  try {
50
- return JSON.parse(readFileSync(candidate, 'utf-8'));
67
+ const content = readFileCached(candidate);
68
+ if (content != null) return JSON.parse(content);
51
69
  } catch { /* malformed — fall through */ }
52
70
  }
53
71
  }
54
72
  // Last-resort fallback. Matches chati.dev/data/model-limits.json verbatim.
55
- // Scan-stale check #25 guards parity between this block and the JSON.
73
+ // Scan-stale check #25 guards parity between this block AND the JSON.
74
+ // 2026-04-18 (context-window-auto-detect-v2): opus and sonnet families
75
+ // default to 1M; haiku stays at 200K. The prior `opus: 200_000` here was a
76
+ // stale regression-lock from PR #3 that silently degraded users to a 5x
77
+ // smaller context budget whenever model-limits.json was unreadable.
56
78
  return {
57
- providers: { claude: 200_000, gemini: 1_000_000, codex: 128_000 },
79
+ providers: { claude: 1_000_000, gemini: 1_000_000, codex: 128_000 },
58
80
  models: {
59
- opus: 200_000,
81
+ opus: 1_000_000,
60
82
  'opus[1m]': 1_000_000,
61
- sonnet: 200_000,
83
+ sonnet: 1_000_000,
62
84
  'sonnet[1m]': 1_000_000,
63
85
  haiku: 200_000,
64
86
  pro: 1_000_000,
@@ -117,7 +139,8 @@ function readSessionState(projectDir) {
117
139
  const sessionPath = join(projectDir, '.chati', 'session.yaml');
118
140
  if (!existsSync(sessionPath)) return null;
119
141
 
120
- const raw = readFileSync(sessionPath, 'utf-8');
142
+ const raw = readFileCached(sessionPath);
143
+ if (raw == null) return null;
121
144
  const extract = (key) => {
122
145
  const match = raw.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
123
146
  if (!match) return null;
@@ -152,6 +175,7 @@ function readSessionState(projectDir) {
152
175
  provider: extract('active_provider') || extract('provider') || 'claude',
153
176
  // v1.2 context detection fields.
154
177
  activeModel: extract('active_model'),
178
+ modelAtLastAudit: extract('model_at_last_audit'),
155
179
  contextTokensUsed: extractNum('context_tokens_used') || 0,
156
180
  contextWindowTokens: extractNum('context_window_tokens'),
157
181
  activeTeamId,
@@ -302,6 +326,12 @@ async function main() {
302
326
  context_last_bracket: bracket,
303
327
  // If we detected a model and the session did not have one, cache it.
304
328
  ...(detectedModel && !session.activeModel ? { active_model: detectedModel } : {}),
329
+ // Seed the harness-audit baseline once (Article XXIV). Only when unset,
330
+ // so a later model change leaves the old baseline in place for the
331
+ // model-governance advisory to detect.
332
+ ...((detectedModel || session.activeModel) && !session.modelAtLastAudit
333
+ ? { model_at_last_audit: detectedModel || session.activeModel }
334
+ : {}),
305
335
  });
306
336
  } catch (err) {
307
337
  process.stderr.write(`[chati] prism-engine write-back failed (non-fatal): ${err.message}\n`);
@@ -319,6 +349,35 @@ async function main() {
319
349
  }
320
350
  }
321
351
 
352
+ // 5b. Gotchas block. Surface the most relevant known gotchas to every agent
353
+ // per turn. This is the reliable per-turn, all-agent delivery path; gotchas
354
+ // attached to a handoff alone do not reach interactive agents and do not
355
+ // survive the first agent memory write. Read straight from the shared store;
356
+ // no src/ import (hooks are standalone).
357
+ let gotchasBlock = '';
358
+ if (session.currentAgent && bracket !== 'CRITICAL') {
359
+ const gotchasPath = join(projectDir, '.chati', 'memories', 'shared', 'gotchas.json');
360
+ const rawGotchas = readFileCached(gotchasPath);
361
+ if (rawGotchas) {
362
+ try {
363
+ const all = JSON.parse(rawGotchas);
364
+ const SEV = { CRITICAL: 3, WARNING: 2, INFO: 1 };
365
+ const ranked = (Array.isArray(all) ? all : [])
366
+ .filter((g) => g && !g.resolution)
367
+ .map((g) => ({ g, score: (g.agent === session.currentAgent ? 10 : 0) + (SEV[g.severity] || 1) + Math.min(5, g.count || 1) }))
368
+ .sort((a, b) => b.score - a.score)
369
+ .slice(0, 3);
370
+ if (ranked.length) {
371
+ const items = ranked.map(({ g }) => {
372
+ const msg = String(g.original_message || g.message || '').replace(/[<>]/g, '').replace(/\s+/g, ' ').slice(0, 160);
373
+ return ` <gotcha id="${g.id}" severity="${g.severity || 'INFO'}">${msg}</gotcha>`;
374
+ });
375
+ gotchasBlock = [' <known-gotchas>', ...items, ' </known-gotchas>'].join('\n');
376
+ }
377
+ } catch { /* malformed gotchas store — skip */ }
378
+ }
379
+ }
380
+
322
381
  // 6. Team context block (unchanged).
323
382
  let teamContextBlock = '';
324
383
  if (session.activeTeamId && bracket !== 'CRITICAL') {
@@ -335,10 +394,16 @@ async function main() {
335
394
 
336
395
  // 7. Advisories.
337
396
  const frustrationDetected = detectFrustration(promptText);
338
- const microcompactAdvisory =
339
- (bracket === 'DEPLETED' || bracket === 'CRITICAL')
340
- ? ' <advisory priority="medium">Context constrained. Avoid re-reading files already in context. Summarize stale outputs before proceeding.</advisory>'
341
- : '';
397
+ const lowContext = bracket === 'DEPLETED' || bracket === 'CRITICAL';
398
+ const microcompactAdvisory = lowContext
399
+ ? ' <advisory priority="medium">Context constrained. Avoid re-reading files already in context. Summarize stale outputs before proceeding.</advisory>'
400
+ : '';
401
+ // During BUILD at low context, prefer a clean session reset (handoff + fresh
402
+ // session) over in-place compaction: compaction keeps the model wrapping up
403
+ // prematurely while code is being written (Article XII reset preference).
404
+ const buildResetAdvisory = (lowContext && session.mode === 'build')
405
+ ? ' <advisory priority="high">BUILD phase at low context. Prefer a clean reset: write a handoff and continue in a fresh session, rather than compacting in place.</advisory>'
406
+ : '';
342
407
 
343
408
  // 8. Compose the context block.
344
409
  const contextBlock = [
@@ -347,9 +412,11 @@ async function main() {
347
412
  session.currentAgent ? ` <agent>${session.currentAgent}</agent>` : '',
348
413
  session.pipelinePosition ? ` <pipeline-position>${session.pipelinePosition}</pipeline-position>` : '',
349
414
  memoryBlock,
415
+ gotchasBlock,
350
416
  teamContextBlock,
351
417
  bracket === 'CRITICAL' ? ' <advisory>Context running low. Consider handoff or summary.</advisory>' : '',
352
418
  microcompactAdvisory,
419
+ buildResetAdvisory,
353
420
  frustrationDetected ? ' <advisory priority="high">User shows signs of frustration. Be more direct, acknowledge the issue explicitly, focus on the solution, avoid repeating previous suggestions.</advisory>' : '',
354
421
  '</chati-context>',
355
422
  ].filter(Boolean).join('\n');
@@ -6,7 +6,8 @@
6
6
  * recovered after context compaction. Writes to .chati/memories/shared/session/.
7
7
  */
8
8
 
9
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
9
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, chmodSync } from 'fs';
10
+ import { createHash } from 'crypto';
10
11
  import { join } from 'path';
11
12
 
12
13
  /**
@@ -250,11 +251,19 @@ async function main() {
250
251
  content += newEntry;
251
252
  }
252
253
  writeFileSync(pingsPath, content, 'utf-8');
254
+ // pings.yaml records the installation and private project paths;
255
+ // restrict to owner-only.
256
+ try { chmodSync(pingsPath, 0o600); } catch { /* no-op on Windows / read-only FS */ }
253
257
 
254
- // Fire-and-forget telemetry (3s timeout, never blocks)
258
+ // Fire-and-forget telemetry (3s timeout, never blocks).
259
+ // The raw license key is never transmitted. The body carries only a
260
+ // non-reversible installation_id (sha256 of the key, 16 hex) so the
261
+ // server can group sessions per install without ever seeing, logging
262
+ // or storing the key.
255
263
  try {
264
+ const installationId = createHash('sha256').update(licenseKey).digest('hex').slice(0, 16);
256
265
  const body = JSON.stringify({
257
- license_key: licenseKey,
266
+ installation_id: installationId,
258
267
  project_name: digest.currentAgent || 'unknown',
259
268
  events: [{ type: 'session_active', timestamp: nowIso, properties: { pipeline_phase: digest.mode, current_agent: digest.currentAgent } }],
260
269
  });
@@ -41,15 +41,15 @@
41
41
  "hooks": [
42
42
  {
43
43
  "type": "command",
44
- "command": "node chati.dev/hooks/advance-trigger.js"
44
+ "command": "node chati.dev/hooks/brief-validator.js"
45
45
  },
46
46
  {
47
47
  "type": "command",
48
- "command": "node chati.dev/hooks/reference-trigger.js"
48
+ "command": "node chati.dev/hooks/advance-trigger.js"
49
49
  },
50
50
  {
51
51
  "type": "command",
52
- "command": "node chati.dev/hooks/brief-validator.js"
52
+ "command": "node chati.dev/hooks/reference-trigger.js"
53
53
  },
54
54
  {
55
55
  "type": "command",
@@ -104,6 +104,10 @@
104
104
  "type": "command",
105
105
  "command": "node chati.dev/hooks/constitution-guard.js"
106
106
  },
107
+ {
108
+ "type": "command",
109
+ "command": "node chati.dev/hooks/git-push-authority.js"
110
+ },
107
111
  {
108
112
  "type": "command",
109
113
  "command": "node chati.dev/hooks/undercover-guard.js"
@@ -21,6 +21,35 @@ const EM_DASH_PATTERN = /[\u2013\u2014]/;
21
21
  /** Emoji unicode ranges (comprehensive) */
22
22
  const EMOJI_PATTERN = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{231A}-\u{231B}\u{23E9}-\u{23F3}\u{23F8}-\u{23FA}\u{25AA}-\u{25AB}\u{25B6}\u{25C0}\u{25FB}-\u{25FE}\u{2614}-\u{2615}\u{2648}-\u{2653}\u{267F}\u{2693}\u{26A1}\u{26AA}-\u{26AB}\u{26BD}-\u{26BE}\u{26C4}-\u{26C5}\u{26CE}\u{26D4}\u{26EA}\u{26F2}-\u{26F3}\u{26F5}\u{26FA}\u{26FD}\u{2702}\u{2705}\u{2708}-\u{270D}\u{270F}]/u;
23
23
 
24
+ /**
25
+ * AI-slop phrasing (Article V: no AI-detectable writing patterns). Advisory,
26
+ * not a block: warn so the agent rewrites in specific, concrete language with a
27
+ * real voice. Tuned to the clear tells (formulaic openers, marketing filler,
28
+ * empty intensifiers) to keep false positives low.
29
+ */
30
+ const SLOP_PHRASES = [
31
+ /\bdelve into\b/i,
32
+ /\bdive into\b/i,
33
+ /\bit'?s important to note\b/i,
34
+ /\bit is important to note\b/i,
35
+ /\bin today'?s (world|fast[- ]paced|digital|landscape)\b/i,
36
+ /\bin the realm of\b/i,
37
+ /\ba testament to\b/i,
38
+ /\bunlock (the|your) (power|potential)\b/i,
39
+ /\bharness the power\b/i,
40
+ /\belevate your\b/i,
41
+ /\bseamless(ly)?\b/i,
42
+ /\bgame[- ]?changer\b/i,
43
+ /\bcutting[- ]edge\b/i,
44
+ /\bto the next level\b/i,
45
+ /\blook no further\b/i,
46
+ /\bembark on (a|your) journey\b/i,
47
+ /\bsupercharge\b/i,
48
+ /\brevolutioniz(e|ing)\b/i,
49
+ /\bbest[- ]in[- ]class\b/i,
50
+ /\bunparalleled\b/i,
51
+ ];
52
+
24
53
  /**
25
54
  * Paths exempt from style enforcement.
26
55
  * Framework files, configs, and context files may legitimately contain special chars.
@@ -54,20 +83,32 @@ function isExemptPath(filePath, projectDir) {
54
83
  * @returns {{ violations: string[], hasDash: boolean, hasEmoji: boolean }}
55
84
  */
56
85
  function checkStyle(content) {
57
- if (!content || typeof content !== 'string') return { violations: [], hasDash: false, hasEmoji: false };
86
+ if (!content || typeof content !== 'string') return { violations: [], hasDash: false, hasEmoji: false, slopPhrases: [] };
58
87
 
59
88
  const violations = [];
60
89
  const hasDash = EM_DASH_PATTERN.test(content);
61
90
  const hasEmoji = EMOJI_PATTERN.test(content);
62
91
 
92
+ const slopPhrases = [];
93
+ for (const re of SLOP_PHRASES) {
94
+ const m = content.match(re);
95
+ if (m) {
96
+ const hit = m[0].toLowerCase();
97
+ if (!slopPhrases.includes(hit)) slopPhrases.push(hit);
98
+ }
99
+ }
100
+
63
101
  if (hasDash) {
64
102
  violations.push('Em-dash or en-dash detected. Use commas, semicolons, colons, or periods instead (Article V).');
65
103
  }
66
104
  if (hasEmoji) {
67
105
  violations.push('Emoji detected. Use icons from approved libraries instead (Directive 1).');
68
106
  }
107
+ if (slopPhrases.length) {
108
+ violations.push(`AI-slop phrasing detected: ${slopPhrases.slice(0, 5).join(', ')}. Rewrite in specific, concrete language (Article V).`);
109
+ }
69
110
 
70
- return { violations, hasDash, hasEmoji };
111
+ return { violations, hasDash, hasEmoji, slopPhrases };
71
112
  }
72
113
 
73
114
  /**
@@ -129,6 +170,14 @@ async function main() {
129
170
  return;
130
171
  }
131
172
 
173
+ if (result.slopPhrases && result.slopPhrases.length) {
174
+ // AI-slop phrasing is advisory — warn but allow so the agent rewrites.
175
+ process.stdout.write(JSON.stringify(allowOutput(
176
+ `[Style Guard] AI-slop phrasing detected (${result.slopPhrases.slice(0, 5).join(', ')}). Rewrite in specific, concrete language with a real voice. Avoid formulaic marketing filler (Article V).`
177
+ )));
178
+ return;
179
+ }
180
+
132
181
  process.stdout.write(JSON.stringify(allowOutput()));
133
182
  } catch (err) {
134
183
  process.stderr.write(`[chati] style-guard: ${err.message}\n`);
@@ -157,7 +206,7 @@ function allowOutput(reason) {
157
206
  return out;
158
207
  }
159
208
 
160
- export { checkStyle, checkBashStyle, isExemptPath, EM_DASH_PATTERN, EMOJI_PATTERN };
209
+ export { checkStyle, checkBashStyle, isExemptPath, EM_DASH_PATTERN, EMOJI_PATTERN, SLOP_PHRASES };
161
210
 
162
211
  // Only run main when executed directly (not imported by tests).
163
212
  // realpathSync resolves symlinks on both sides — important on macOS where
@@ -83,9 +83,13 @@ async function main() {
83
83
  const event = JSON.parse(input);
84
84
  const projectDir = event.cwd || process.cwd();
85
85
 
86
- // Quick exit: no team active allow everything
87
- const teamId = process.env.CHATI_TEAM_ID;
88
- if (!teamId) {
86
+ // The env var is the fast path, but it does not always propagate to a
87
+ // parallel hook process. Fall back to the session file: if the env var is
88
+ // absent AND no team is active in session.yaml, this is genuinely solo mode
89
+ // and we allow. If a team IS active, we enforce isolation even without the
90
+ // env var, otherwise cross-member overwrites slip through unblocked.
91
+ const teamActive = !!process.env.CHATI_TEAM_ID || hasActiveTeamInSession(projectDir);
92
+ if (!teamActive) {
89
93
  process.stdout.write(JSON.stringify(allowOutput()));
90
94
  return;
91
95
  }
@@ -130,9 +134,25 @@ async function main() {
130
134
  // Path not owned by anyone specific → allow
131
135
  process.stdout.write(JSON.stringify(allowOutput()));
132
136
  } catch (err) {
133
- // Fail-open: hook errors should not block execution
134
137
  process.stderr.write(`[chati] team-quality-gate: ${err.message}\n`);
135
- process.stdout.write(JSON.stringify(allowOutput()));
138
+ // Fail CLOSED while a team is active. A crash in the write-scope guard must
139
+ // not silently permit cross-member overwrites during parallel team
140
+ // execution. Outside team mode this gate guards nothing, so a crash there
141
+ // allows. Re-derive team state defensively from env + session.
142
+ let teamActive = !!process.env.CHATI_TEAM_ID;
143
+ if (!teamActive) {
144
+ try {
145
+ const parsed = JSON.parse(input);
146
+ teamActive = hasActiveTeamInSession(parsed.cwd || process.cwd());
147
+ } catch { /* input unparseable: fall back to the env-only signal */ }
148
+ }
149
+ if (teamActive) {
150
+ process.stdout.write(JSON.stringify(denyOutput(
151
+ `[Team Quality Gate] internal error while enforcing team write isolation (${err.message}). Write blocked as a precaution to prevent cross-member overwrites. Retry once resolved.`
152
+ )));
153
+ } else {
154
+ process.stdout.write(JSON.stringify(allowOutput()));
155
+ }
136
156
  }
137
157
  }
138
158
 
@@ -163,6 +183,24 @@ function detectAgentFromSession(projectDir) {
163
183
  return match ? match[1].trim().replace(/^["']|["']$/g, '') : null;
164
184
  }
165
185
 
186
+ /**
187
+ * Detect whether a team is active from session.yaml alone, for the case where
188
+ * CHATI_TEAM_ID did not propagate to this hook process. An active team is an
189
+ * entry in teams[] whose status is forming, active or
190
+ * completing. preview.status uses a different vocabulary (inactive/running/
191
+ * approved/adjusting), so matching these specific values is team-specific.
192
+ */
193
+ function hasActiveTeamInSession(projectDir) {
194
+ const sessionPath = join(projectDir, '.chati', 'session.yaml');
195
+ if (!existsSync(sessionPath)) return false;
196
+ try {
197
+ const raw = readFileSync(sessionPath, 'utf-8');
198
+ return /team_id:\s*\S/.test(raw) && /^\s*status:\s*(active|forming|completing)\b/m.test(raw);
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+
166
204
  // Only run main when executed directly (not imported by tests).
167
205
  // realpathSync resolves symlinks on both sides — important on macOS where
168
206
  // /tmp is a symlink to /private/tmp. Without realpath the comparison fails
@@ -31,7 +31,7 @@ installer:
31
31
  agents_count: "Agent definitions across DISCOVER, PLAN, BUILD, DEPLOY phases"
32
32
  workflows_count: "7 workflow blueprints"
33
33
  templates_count: "10 templates (PRD, Brownfield PRD, Architecture, Task, QA Gate, Quick Brief, Brandbook, Design Tokens, Component Spec, Icon System)"
34
- constitution: "Constitution (23 Articles + Preamble)"
34
+ constitution: "Constitution (25 Articles + Preamble)"
35
35
  session_mgmt: "Session management system"
36
36
  quality_gates: "Quality gates (3-tier verdicts)"
37
37
  proceed: "Proceed with installation?"
@@ -39,7 +39,7 @@ installer:
39
39
  created_chati: "Created .chati/ session directory"
40
40
  created_framework: "Created chati.dev/ system directory (agents, templates, workflows)"
41
41
  created_commands: "Created .claude/commands/ (thin router)"
42
- installed_constitution: "Installed Constitution (Articles I-XXIII)"
42
+ installed_constitution: "Installed Constitution (Articles I-XXV)"
43
43
  created_session: "Created session.yaml schema"
44
44
  created_claude_md: "Created CLAUDE.md"
45
45
  configured_mcps: "Configured MCPs:"
@@ -47,7 +47,7 @@ installer:
47
47
  agents_valid: "All agents implement 8 protocols"
48
48
  handoff_ok: "Handoff protocol: OK"
49
49
  validation_ok: "Self-validation criteria: OK"
50
- constitution_ok: "Constitution: 23 articles verified"
50
+ constitution_ok: "Constitution: 25 articles verified"
51
51
  created_memories: "Created .chati/memories/ (Memory Layer)"
52
52
  installed_intelligence: "Installed Intelligence Layer (Context Engine, Memory, Registry)"
53
53
  intelligence_valid: "Intelligence: 6 spec files verified"
@@ -31,7 +31,7 @@ installer:
31
31
  agents_count: "Definiciones de agentes en las fases DISCOVER, PLAN, BUILD, DEPLOY"
32
32
  workflows_count: "7 blueprints de workflow"
33
33
  templates_count: "10 templates (PRD, PRD Brownfield, Arquitectura, Task, QA Gate, Quick Brief, Brandbook, Design Tokens, Component Spec, Icon System)"
34
- constitution: "Constitucion (22 Articulos + Preambulo)"
34
+ constitution: "Constitucion (25 Articulos + Preambulo)"
35
35
  session_mgmt: "Sistema de gestion de sesion"
36
36
  quality_gates: "Quality gates (veredictos en 3 niveles)"
37
37
  proceed: "Proceder con la instalacion?"
@@ -39,7 +39,7 @@ installer:
39
39
  created_chati: "Creado directorio de sesion .chati/"
40
40
  created_framework: "Creado directorio del sistema chati.dev/ (agentes, templates, workflows)"
41
41
  created_commands: "Creado .claude/commands/ (enrutador)"
42
- installed_constitution: "Instalada Constitucion (Artículos I-XXIII)"
42
+ installed_constitution: "Instalada Constitucion (Artículos I-XXV)"
43
43
  created_session: "Creado schema session.yaml"
44
44
  created_claude_md: "Creado CLAUDE.md"
45
45
  configured_mcps: "MCPs configurados:"
@@ -47,7 +47,7 @@ installer:
47
47
  agents_valid: "Todos los agentes implementan 8 protocolos"
48
48
  handoff_ok: "Protocolo de handoff: OK"
49
49
  validation_ok: "Criterios de auto-validacion: OK"
50
- constitution_ok: "Constitucion: 22 articulos verificados"
50
+ constitution_ok: "Constitucion: 25 articulos verificados"
51
51
  created_memories: "Creado .chati/memories/ (Memory Layer)"
52
52
  installed_intelligence: "Instalada Intelligence Layer (Context Engine, Memory, Registry)"
53
53
  intelligence_valid: "Intelligence: 6 archivos de spec verificados"
@@ -31,7 +31,7 @@ installer:
31
31
  agents_count: "Definitions d'agents dans les phases DISCOVER, PLAN, BUILD, DEPLOY"
32
32
  workflows_count: "7 blueprints de workflow"
33
33
  templates_count: "10 templates (PRD, PRD Brownfield, Architecture, Task, QA Gate, Quick Brief, Brandbook, Design Tokens, Component Spec, Icon System)"
34
- constitution: "Constitution (23 Articles + Preambule)"
34
+ constitution: "Constitution (25 Articles + Preambule)"
35
35
  session_mgmt: "Systeme de gestion de session"
36
36
  quality_gates: "Quality gates (verdicts en 3 niveaux)"
37
37
  proceed: "Proceder a l'installation?"
@@ -39,7 +39,7 @@ installer:
39
39
  created_chati: "Cree le repertoire de session .chati/"
40
40
  created_framework: "Cree le repertoire du systeme chati.dev/ (agents, templates, workflows)"
41
41
  created_commands: "Cree .claude/commands/ (routeur)"
42
- installed_constitution: "Installee la Constitution (Articles I-XXIII)"
42
+ installed_constitution: "Installee la Constitution (Articles I-XXV)"
43
43
  created_session: "Cree le schema session.yaml"
44
44
  created_claude_md: "Cree CLAUDE.md"
45
45
  configured_mcps: "MCPs configures:"
@@ -47,7 +47,7 @@ installer:
47
47
  agents_valid: "Tous les agents implementent 8 protocoles"
48
48
  handoff_ok: "Protocole de passation: OK"
49
49
  validation_ok: "Criteres d'auto-validation: OK"
50
- constitution_ok: "Constitution: 23 articles verifies"
50
+ constitution_ok: "Constitution: 25 articles verifies"
51
51
  created_memories: "Cree .chati/memories/ (Memory Layer)"
52
52
  installed_intelligence: "Installee Intelligence Layer (Context Engine, Memory, Registry)"
53
53
  intelligence_valid: "Intelligence: 6 fichiers de spec verifies"
@@ -31,7 +31,7 @@ installer:
31
31
  agents_count: "Definicoes de agentes nas fases DISCOVER, PLAN, BUILD, DEPLOY"
32
32
  workflows_count: "7 blueprints de workflow"
33
33
  templates_count: "10 templates (PRD, PRD Brownfield, Arquitetura, Task, QA Gate, Quick Brief, Brandbook, Design Tokens, Component Spec, Icon System)"
34
- constitution: "Constituicao (22 Artigos + Preambulo)"
34
+ constitution: "Constituicao (25 Artigos + Preambulo)"
35
35
  session_mgmt: "Sistema de gerenciamento de sessao"
36
36
  quality_gates: "Quality gates (veredictos em 3 niveis)"
37
37
  proceed: "Prosseguir com a instalacao?"
@@ -39,7 +39,7 @@ installer:
39
39
  created_chati: "Criado diretorio de sessao .chati/"
40
40
  created_framework: "Criado diretorio do sistema chati.dev/ (agentes, templates, workflows)"
41
41
  created_commands: "Criado .claude/commands/ (roteador)"
42
- installed_constitution: "Instalada Constituicao (Artigos I-XXIII)"
42
+ installed_constitution: "Instalada Constituicao (Artigos I-XXV)"
43
43
  created_session: "Criado schema session.yaml"
44
44
  created_claude_md: "Criado CLAUDE.md"
45
45
  configured_mcps: "MCPs configurados:"
@@ -47,7 +47,7 @@ installer:
47
47
  agents_valid: "Todos os agentes implementam 8 protocolos"
48
48
  handoff_ok: "Protocolo de handoff: OK"
49
49
  validation_ok: "Criterios de auto-validacao: OK"
50
- constitution_ok: "Constituicao: 22 artigos verificados"
50
+ constitution_ok: "Constituicao: 25 artigos verificados"
51
51
  created_memories: "Criado .chati/memories/ (Memory Layer)"
52
52
  installed_intelligence: "Instalada Intelligence Layer (Context Engine, Memory, Registry)"
53
53
  intelligence_valid: "Intelligence: 6 arquivos de spec verificados"