chati-dev 4.2.2 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -53
- package/bin/chati.js +150 -5
- package/framework/agents/build/dev.md +509 -76
- package/framework/agents/deploy/devops.md +40 -25
- package/framework/agents/discover/brief.md +156 -22
- package/framework/agents/discover/brownfield-wu.md +24 -14
- package/framework/agents/discover/greenfield-wu.md +100 -14
- package/framework/agents/plan/architect-data-engineer.md +6 -6
- package/framework/agents/plan/architect-system.md +46 -12
- package/framework/agents/plan/architect.md +40 -20
- package/framework/agents/plan/detail.md +36 -24
- package/framework/agents/plan/phases.md +36 -26
- package/framework/agents/plan/tasks.md +114 -33
- package/framework/agents/plan/ux-brand-architect.md +240 -8
- package/framework/agents/plan/ux-component-engineer.md +28 -13
- package/framework/agents/plan/ux-researcher.md +7 -6
- package/framework/agents/plan/ux.md +55 -22
- package/framework/agents/quality/qa-implementation.md +143 -74
- package/framework/agents/quality/qa-planning.md +115 -42
- package/framework/agents/quality/qa-visual.md +439 -0
- package/framework/agents/shared/visualizer.md +128 -0
- package/framework/config.yaml +7 -6
- package/framework/constitution.md +127 -44
- package/framework/context/governance.md +12 -7
- package/framework/context/quality.md +6 -5
- package/framework/context/root.md +6 -6
- package/framework/data/entity-registry.yaml +377 -4
- package/framework/data/model-limits.json +19 -0
- package/framework/domains/agents/qa-visual.yaml +74 -0
- package/framework/domains/constitution.yaml +46 -2
- package/framework/domains/workflows/greenfield-fullstack.yaml +2 -2
- package/framework/hooks/advance-trigger.js +131 -0
- package/framework/hooks/brief-validator.js +83 -0
- package/framework/hooks/constitution-guard.js +24 -5
- package/framework/hooks/license-guard.js +62 -27
- package/framework/hooks/mode-governance.js +13 -2
- package/framework/hooks/model-governance.js +1 -0
- package/framework/hooks/post-dev.js +207 -0
- package/framework/hooks/prism-engine.js +274 -105
- package/framework/hooks/reasoning-escalator.js +371 -0
- package/framework/hooks/reference-trigger.js +117 -0
- package/framework/hooks/session-digest.js +50 -1
- package/framework/hooks/settings.json +32 -1
- package/framework/hooks/style-guard.js +25 -6
- package/framework/hooks/team-quality-gate.js +19 -12
- package/framework/hooks/undercover-guard.js +4 -2
- package/framework/i18n/en.yaml +3 -3
- package/framework/i18n/es.yaml +3 -3
- package/framework/i18n/fr.yaml +3 -3
- package/framework/i18n/pt.yaml +3 -3
- package/framework/intelligence/context-engine.md +4 -5
- package/framework/intelligence/decision-engine.md +1 -1
- package/framework/intelligence/hooks-performance.md +3 -3
- package/framework/migrations/v1.0-to-v1.1.yaml +1 -1
- package/framework/migrations/v1.4-to-v2.0.yaml +11 -11
- package/framework/migrations/v4.0-to-v4.1.yaml +2 -2
- package/framework/migrations/v4.2-to-v4.3.yaml +29 -0
- package/framework/orchestrator/chati-router.js +387 -0
- package/framework/orchestrator/chati-update.md +40 -40
- package/framework/orchestrator/chati.md +294 -94
- package/framework/scaffold/motion-premium/README.md +89 -0
- package/framework/scaffold/motion-premium/app/globals.css.template +400 -0
- package/framework/scaffold/motion-premium/app/layout.tsx.template +110 -0
- package/framework/scaffold/motion-premium/components/animation/BackgroundCrossfadeProvider.tsx.template +170 -0
- package/framework/scaffold/motion-premium/components/animation/LenisProvider.tsx.template +49 -0
- package/framework/scaffold/motion-premium/components/animation/PageTransitionWrapper.tsx.template +83 -0
- package/framework/scaffold/motion-premium/components/animation/Preloader.tsx.template +171 -0
- package/framework/scaffold/motion-premium/components/ui/Container.tsx.template +69 -0
- package/framework/scaffold/motion-premium/components/ui/PageSection.tsx.template +74 -0
- package/framework/scaffold/motion-premium/lib/animations/gsap.ts.template +112 -0
- package/framework/scaffold/motion-premium/lib/animations/refreshCoordinator.ts.template +75 -0
- package/framework/scaffold/motion-premium/lib/animations/tokens.ts.template +119 -0
- package/framework/scaffold/motion-premium/lib/animations/useGsapContext.ts.template +92 -0
- package/framework/scaffold/motion-premium/lib/animations/useScrollSnapStepper.ts.template +265 -0
- package/framework/scaffold/motion-premium/lib/animations/useSmoothScroll.ts.template +67 -0
- package/framework/scaffold/motion-premium/lib/brand.ts.template +43 -0
- package/framework/scaffold/motion-premium/scaffold.yaml +174 -0
- package/framework/scaffold/motion-premium-3d/README.md +80 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoCanvas.tsx.template +81 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/ScrollDemoClient.tsx.template +75 -0
- package/framework/scaffold/motion-premium-3d/app/(3d)/scroll-demo/page.tsx.template +26 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CameraRig.tsx.template +100 -0
- package/framework/scaffold/motion-premium-3d/components/3d/CanvasProvider.tsx.template +85 -0
- package/framework/scaffold/motion-premium-3d/components/3d/InvalidateOnScroll.tsx.template +51 -0
- package/framework/scaffold/motion-premium-3d/components/3d/MeshCrossfade.tsx.template +79 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollCrossfade.tsx.template +88 -0
- package/framework/scaffold/motion-premium-3d/components/3d/ScrollScene.tsx.template +121 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/SceneFallback.tsx.template +65 -0
- package/framework/scaffold/motion-premium-3d/components/webgl/WebGLContext.tsx.template +68 -0
- package/framework/scaffold/motion-premium-3d/lib/webgl/detect.ts.template +69 -0
- package/framework/scaffold/motion-premium-3d/scaffold.yaml +133 -0
- package/framework/schemas/session.schema.json +109 -21
- package/framework/scripts/reference-capture.js +430 -0
- package/framework/scripts/visual-qa.js +674 -0
- package/framework/tasks/orchestrator-handoff.md +1 -1
- package/framework/tasks/orchestrator-resume.md +1 -1
- package/framework/tasks/orchestrator-route.md +1 -1
- package/framework/tasks/orchestrator-status.md +3 -3
- package/framework/tasks/qa-planning-gate-define.md +1 -1
- package/framework/templates/brandbook-html-tmpl.md +1 -1
- package/framework/templates/brandbook-tmpl.yaml +1 -1
- package/framework/templates/component-spec-tmpl.yaml +1 -1
- package/framework/templates/design-token-tmpl.yaml +1 -1
- package/framework/templates/icon-system-tmpl.yaml +1 -1
- package/framework/templates/team-planning-tasks.yaml +6 -5
- package/framework/workflows/brownfield-discovery.yaml +2 -2
- package/framework/workflows/brownfield-fullstack.yaml +15 -11
- package/framework/workflows/brownfield-service.yaml +14 -10
- package/framework/workflows/brownfield-ui.yaml +15 -11
- package/framework/workflows/greenfield-fullstack.yaml +16 -13
- package/framework/workflows/quick-flow.yaml +3 -3
- package/framework/workflows/standard-flow.yaml +12 -9
- package/package.json +10 -5
- package/src/autonomy/autonomous-gate.js +1 -0
- package/src/autonomy/build-state.js +1 -2
- package/src/autonomy/progress-reporter.js +1 -1
- package/src/config/agent-customizer.js +11 -3
- package/src/config/claude-settings-generator.js +27 -7
- package/src/config/context-file-generator.js +41 -21
- package/src/config/framework-adapter.js +1 -0
- package/src/config/gemini-hooks-generator.js +19 -7
- package/src/config/mcp-configs.js +1 -0
- package/src/context/layers/l1-global.js +2 -1
- package/src/dashboard/data-reader.js +4 -3
- package/src/dashboard/layout.js +2 -1
- package/src/decision/analyzer.js +6 -30
- package/src/decision/engine.js +4 -28
- package/src/decision/registry-healer.js +3 -2
- package/src/decision/registry-updater.js +23 -14
- package/src/extensions/loader.js +2 -8
- package/src/gates/g1-planning-complete.js +2 -1
- package/src/gates/g2-qa-planning.js +2 -1
- package/src/gates/g3-implementation.js +2 -1
- package/src/gates/g4-qa-implementation.js +3 -2
- package/src/gates/g5-deploy-ready.js +2 -1
- package/src/health/engine.js +4 -3
- package/src/installer/core.js +422 -81
- package/src/installer/preflight.js +131 -0
- package/src/installer/provider-overlay.js +3 -3
- package/src/installer/scaffold-applier.js +358 -0
- package/src/installer/templates.js +46 -29
- package/src/installer/validator.js +17 -12
- package/src/intelligence/registry-manager.js +22 -29
- package/src/intelligence/timeline.js +11 -6
- package/src/license/commands.js +1 -1
- package/src/license/wait.js +102 -0
- package/src/memory/agent-memory.js +81 -0
- package/src/memory/dream.js +32 -1
- package/src/merger/replace-merger.js +28 -15
- package/src/orchestrator/agent-selector.js +2 -1
- package/src/orchestrator/cli.js +1869 -71
- package/src/orchestrator/doctor.js +270 -0
- package/src/orchestrator/handoff-engine.js +4 -3
- package/src/orchestrator/index.js +2 -0
- package/src/orchestrator/pipeline-manager.js +306 -15
- package/src/orchestrator/session-manager.js +331 -6
- package/src/tasks/handoff.js +3 -2
- package/src/telemetry/config.js +4 -3
- package/src/telemetry/schema.js +1 -0
- package/src/terminal/collector.js +3 -2
- package/src/terminal/index.js +1 -2
- package/src/terminal/isolation.js +52 -18
- package/src/terminal/prompt-builder.js +42 -25
- package/src/terminal/run-parallel.js +1 -1
- package/src/terminal/run-team.js +3 -3
- package/src/terminal/team-task-list.js +43 -4
- package/src/upgrade/backup.js +3 -2
- package/src/upgrade/checker.js +3 -2
- package/src/upgrade/migrator.js +65 -7
- package/src/upgrade/tracked-files-detector.js +86 -0
- package/src/upgrade/user-messages.js +94 -0
- package/src/utils/config-parser.js +2 -1
- package/src/utils/feature-flags.js +2 -1
- package/src/utils/flatten-entities.js +69 -0
- package/src/utils/framework-dir.js +16 -0
- package/src/utils/model-id.js +85 -0
- package/src/utils/provider-limits.js +84 -23
- package/src/utils/schema-validator.js +1 -1
- package/src/wizard/i18n.js +5 -4
- package/src/wizard/index.js +14 -0
- package/assets/logo - c/303/263pia.png +0 -0
- package/assets/logo.svg +0 -42
- package/assets/logo2.png +0 -0
- package/assets/social-preview.png +0 -0
- package/scripts/bundle-framework.js +0 -69
- package/scripts/changelog-generator.js +0 -222
- package/scripts/codebase-mapper.js +0 -728
- package/scripts/commit-message-generator.js +0 -167
- package/scripts/coverage-analyzer.js +0 -260
- package/scripts/dependency-analyzer.js +0 -280
- package/scripts/doctor/checks/agents.js +0 -77
- package/scripts/doctor/checks/constitution.js +0 -41
- package/scripts/doctor/checks/domain-alignment.js +0 -58
- package/scripts/doctor/checks/prism-layers.js +0 -84
- package/scripts/doctor/checks/registry.js +0 -55
- package/scripts/doctor/checks/schemas.js +0 -61
- package/scripts/doctor/fixes/reference-fix.js +0 -100
- package/scripts/doctor/fixes/registry-fix.js +0 -56
- package/scripts/doctor/index.js +0 -212
- package/scripts/framework-analyzer.js +0 -308
- package/scripts/generate-constitution-domain.js +0 -253
- package/scripts/generate-signing-key.js +0 -33
- package/scripts/health-check.js +0 -481
- package/scripts/ide-sync.js +0 -326
- package/scripts/performance-analyzer.js +0 -325
- package/scripts/plan-tracker.js +0 -278
- package/scripts/populate-entity-registry.js +0 -481
- package/scripts/pr-review.js +0 -317
- package/scripts/rollback-manager.js +0 -310
- package/scripts/semantic-lint.js +0 -328
- package/scripts/sign-manifest.js +0 -53
- package/scripts/stuck-detector.js +0 -343
- package/scripts/test-quality-assessment.js +0 -257
- package/scripts/validate-agents.js +0 -368
- package/scripts/validate-package.js +0 -505
- package/scripts/validate-tasks.js +0 -465
- package/src/autonomy/worktree-manager.js +0 -250
- package/src/intelligence/decision-engine.js +0 -256
- package/src/intelligence/document-sharder.js +0 -221
- package/src/intelligence/elicitation.js +0 -265
|
@@ -2,28 +2,133 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* PRISM Engine Hook — UserPromptSubmit
|
|
4
4
|
*
|
|
5
|
-
* Injects PRISM context block into every user prompt
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Injects PRISM context block into every user prompt, and — since the
|
|
6
|
+
* context-window-detection v1 refactor — maintains the cumulative token
|
|
7
|
+
* counter and detected model state on session.yaml so downstream consumers
|
|
8
|
+
* (/chati status, session-digest) see a token-accurate picture.
|
|
9
|
+
*
|
|
10
|
+
* Why this rewrite:
|
|
11
|
+
* Gabriel reported false DEPLETED alerts at 13.6% of an Opus 1M window.
|
|
12
|
+
* The old primary path used turn-count (`session.turnCount / maxTurns`)
|
|
13
|
+
* scaled to a fixed 40-turn ceiling — token-blind and model-blind.
|
|
14
|
+
* On a 1M window, 40 turns ≈ 10% of context, but the heuristic reported
|
|
15
|
+
* 0%. This rewrite replaces the primary metric with token-based
|
|
16
|
+
* cumulative estimation and resolves the window from the active model.
|
|
17
|
+
*
|
|
18
|
+
* Detection chain for the active model (first hit wins):
|
|
19
|
+
* 1. process.env.CLAUDE_MODEL (Claude Code set)
|
|
20
|
+
* 2. event.session.model (future-proof for event payload)
|
|
21
|
+
* 3. session.active_model (written by /chati boot)
|
|
22
|
+
* 4. HOOK_AGENT_MODELS[currentAgent] (last-resort default)
|
|
23
|
+
* 5. null (provider fallback takes over)
|
|
24
|
+
*
|
|
25
|
+
* Context limits come from the shared JSON table at either
|
|
26
|
+
* `.chati.dev/data/model-limits.json` (installed) or
|
|
27
|
+
* `chati.dev/data/model-limits.json` (monorepo/dev). The table maps
|
|
28
|
+
* normalized keys — `opus`, `opus[1m]`, `sonnet`, `sonnet[1m]`, `haiku`,
|
|
29
|
+
* `pro`, `flash`, `codex` — to window sizes, distinguishing Opus default
|
|
30
|
+
* (200K) from the 1M variant explicitly.
|
|
8
31
|
*
|
|
9
32
|
* Claude Code Hook: triggers on every user message submission.
|
|
10
33
|
*/
|
|
11
34
|
|
|
12
|
-
import { existsSync, readFileSync } from 'fs';
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
13
36
|
import { join } from 'path';
|
|
14
37
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Context window limits — load from shared JSON, fall back to embedded.
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
function loadLimits(projectDir) {
|
|
43
|
+
const candidates = [
|
|
44
|
+
join(projectDir, '.chati.dev', 'data', 'model-limits.json'),
|
|
45
|
+
join(projectDir, 'chati.dev', 'data', 'model-limits.json'),
|
|
46
|
+
];
|
|
47
|
+
for (const candidate of candidates) {
|
|
48
|
+
if (existsSync(candidate)) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
51
|
+
} catch { /* malformed — fall through */ }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Last-resort fallback. Matches chati.dev/data/model-limits.json verbatim.
|
|
55
|
+
// Scan-stale check #25 guards parity between this block and the JSON.
|
|
56
|
+
return {
|
|
57
|
+
providers: { claude: 200_000, gemini: 1_000_000, codex: 128_000 },
|
|
58
|
+
models: {
|
|
59
|
+
opus: 200_000,
|
|
60
|
+
'opus[1m]': 1_000_000,
|
|
61
|
+
sonnet: 200_000,
|
|
62
|
+
'sonnet[1m]': 1_000_000,
|
|
63
|
+
haiku: 200_000,
|
|
64
|
+
pro: 1_000_000,
|
|
65
|
+
flash: 1_000_000,
|
|
66
|
+
codex: 128_000,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Model normalization (duplicate of src/utils/model-id.js — hooks cannot
|
|
73
|
+
// import package modules. Keep in sync if parser changes.)
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const FAMILIES = new Set(['opus', 'sonnet', 'haiku']);
|
|
77
|
+
|
|
78
|
+
function parseClaudeModelId(raw) {
|
|
79
|
+
if (typeof raw !== 'string') return null;
|
|
80
|
+
const s = raw.trim().toLowerCase();
|
|
81
|
+
if (!s) return null;
|
|
82
|
+
let variant = 'default';
|
|
83
|
+
let body = s;
|
|
84
|
+
const variantMatch = body.match(/[[(:]\s*(1m)\s*\)?\]?$/);
|
|
85
|
+
if (variantMatch) {
|
|
86
|
+
variant = variantMatch[1];
|
|
87
|
+
body = body.slice(0, variantMatch.index).trim();
|
|
88
|
+
}
|
|
89
|
+
let family = null;
|
|
90
|
+
for (const candidate of FAMILIES) {
|
|
91
|
+
if (new RegExp(`(^|-)${candidate}(-|$)`).test(body)) {
|
|
92
|
+
family = candidate;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!family) return null;
|
|
97
|
+
const normalized = variant === 'default' ? family : `${family}[${variant}]`;
|
|
98
|
+
return { family, variant, normalized, provider: 'claude' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveLimit(limits, modelKey, provider) {
|
|
102
|
+
if (modelKey && limits.models[modelKey] != null) return limits.models[modelKey];
|
|
103
|
+
if (modelKey) {
|
|
104
|
+
const parsed = parseClaudeModelId(modelKey);
|
|
105
|
+
if (parsed && limits.models[parsed.normalized] != null) return limits.models[parsed.normalized];
|
|
106
|
+
if (parsed && limits.models[parsed.family] != null) return limits.models[parsed.family];
|
|
107
|
+
}
|
|
108
|
+
if (provider && limits.providers[provider] != null) return limits.providers[provider];
|
|
109
|
+
return 200_000;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Session read — lightweight YAML extraction (avoids js-yaml dependency).
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
18
116
|
function readSessionState(projectDir) {
|
|
19
117
|
const sessionPath = join(projectDir, '.chati', 'session.yaml');
|
|
20
118
|
if (!existsSync(sessionPath)) return null;
|
|
21
119
|
|
|
22
120
|
const raw = readFileSync(sessionPath, 'utf-8');
|
|
23
|
-
// Lightweight YAML extraction (avoid dependency)
|
|
24
121
|
const extract = (key) => {
|
|
25
122
|
const match = raw.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, 'm'));
|
|
26
|
-
|
|
123
|
+
if (!match) return null;
|
|
124
|
+
const v = match[1].trim().replace(/^["']|["']$/g, '');
|
|
125
|
+
return v === 'null' || v === '' ? null : v;
|
|
126
|
+
};
|
|
127
|
+
const extractNum = (key) => {
|
|
128
|
+
const v = extract(key);
|
|
129
|
+
if (v === null) return null;
|
|
130
|
+
const n = parseInt(v, 10);
|
|
131
|
+
return Number.isFinite(n) ? n : null;
|
|
27
132
|
};
|
|
28
133
|
|
|
29
134
|
// Detect active team (Article XXI)
|
|
@@ -38,26 +143,131 @@ function readSessionState(projectDir) {
|
|
|
38
143
|
}
|
|
39
144
|
|
|
40
145
|
return {
|
|
146
|
+
raw,
|
|
147
|
+
sessionPath,
|
|
41
148
|
mode: extract('mode') || 'discover',
|
|
42
149
|
currentAgent: extract('current_agent') || null,
|
|
43
150
|
workflow: extract('workflow') || null,
|
|
44
151
|
pipelinePosition: extract('pipeline_position') || null,
|
|
45
|
-
|
|
46
|
-
|
|
152
|
+
provider: extract('active_provider') || extract('provider') || 'claude',
|
|
153
|
+
// v1.2 context detection fields.
|
|
154
|
+
activeModel: extract('active_model'),
|
|
155
|
+
contextTokensUsed: extractNum('context_tokens_used') || 0,
|
|
156
|
+
contextWindowTokens: extractNum('context_window_tokens'),
|
|
47
157
|
activeTeamId,
|
|
48
158
|
activeTeamPhase,
|
|
49
159
|
};
|
|
50
160
|
}
|
|
51
161
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Session write — targeted line replacement on session.yaml.
|
|
164
|
+
// Atomic via tmp+rename. Only touches the fields we own.
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
function writeBackSession(sessionPath, rawContent, updates) {
|
|
168
|
+
let out = rawContent;
|
|
169
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
170
|
+
const serialized =
|
|
171
|
+
value === null || value === undefined ? 'null' :
|
|
172
|
+
typeof value === 'number' ? String(value) :
|
|
173
|
+
typeof value === 'string' ? value :
|
|
174
|
+
JSON.stringify(value);
|
|
175
|
+
const re = new RegExp(`^(\\s*${key}:\\s*)(.*)$`, 'm');
|
|
176
|
+
if (re.test(out)) {
|
|
177
|
+
out = out.replace(re, `$1${serialized}`);
|
|
178
|
+
} else {
|
|
179
|
+
// Key not present — append at end (migration path).
|
|
180
|
+
out += (out.endsWith('\n') ? '' : '\n') + `${key}: ${serialized}\n`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const tmpPath = sessionPath + '.tmp';
|
|
184
|
+
writeFileSync(tmpPath, out, 'utf-8');
|
|
185
|
+
renameSync(tmpPath, sessionPath);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Model detection chain.
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
const HOOK_AGENT_MODELS = {
|
|
193
|
+
orchestrator: 'sonnet', 'greenfield-wu': 'haiku', 'brownfield-wu': 'opus',
|
|
194
|
+
brief: 'sonnet', detail: 'opus', architect: 'opus', ux: 'sonnet',
|
|
195
|
+
phases: 'sonnet', tasks: 'sonnet', 'qa-planning': 'opus',
|
|
196
|
+
'qa-implementation': 'opus', 'qa-visual': 'opus', dev: 'opus', devops: 'sonnet',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
function detectModel(event, session) {
|
|
200
|
+
// 1. Environment override — Claude Code or user-set.
|
|
201
|
+
if (process.env.CLAUDE_MODEL) {
|
|
202
|
+
const parsed = parseClaudeModelId(process.env.CLAUDE_MODEL);
|
|
203
|
+
if (parsed) return parsed.normalized;
|
|
204
|
+
return process.env.CLAUDE_MODEL; // trust the raw string
|
|
205
|
+
}
|
|
206
|
+
// 2. Event payload (future-proof — Claude Code may populate this).
|
|
207
|
+
if (event && event.session && event.session.model) {
|
|
208
|
+
const parsed = parseClaudeModelId(event.session.model);
|
|
209
|
+
if (parsed) return parsed.normalized;
|
|
210
|
+
}
|
|
211
|
+
// 3. Session-recorded active model — written at /chati boot.
|
|
212
|
+
if (session && session.activeModel) return session.activeModel;
|
|
213
|
+
// 4. Agent default (last resort, matches historic behavior).
|
|
214
|
+
if (session && session.currentAgent && HOOK_AGENT_MODELS[session.currentAgent]) {
|
|
215
|
+
return HOOK_AGENT_MODELS[session.currentAgent];
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Token accounting.
|
|
222
|
+
// Token-length heuristic: 1 token ≈ 4 characters (Claude's own rough rule).
|
|
223
|
+
// Tool-output multiplier: each UserPromptSubmit represents roughly one
|
|
224
|
+
// user-prompt + a mixed bag of preceding tool responses. We add a 3x
|
|
225
|
+
// multiplier on the user prompt to approximate that overhead, clamped at
|
|
226
|
+
// the window size.
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
const TOOL_OUTPUT_MULTIPLIER = 3;
|
|
230
|
+
|
|
231
|
+
function estimatePromptDelta(promptText) {
|
|
232
|
+
if (!promptText) return 0;
|
|
233
|
+
const baseTokens = Math.ceil(promptText.length / 4);
|
|
234
|
+
return baseTokens * (1 + TOOL_OUTPUT_MULTIPLIER);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function computeBracket(remainingPercent) {
|
|
238
|
+
if (remainingPercent < 25) return 'CRITICAL';
|
|
239
|
+
if (remainingPercent < 40) return 'DEPLETED';
|
|
240
|
+
if (remainingPercent < 60) return 'MODERATE';
|
|
241
|
+
return 'FRESH';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Frustration detection — unchanged.
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
const FRUSTRATION_PATTERNS = [
|
|
249
|
+
/this (is|isn't|isnt) working/i,
|
|
250
|
+
/I (already|just) (told|said|asked)/i,
|
|
251
|
+
/why (won't|doesn't|can't|isn't|wont|doesnt|cant|isnt)/i,
|
|
252
|
+
/stop (doing|repeating|ignoring)/i,
|
|
253
|
+
/you('re| are) (not|never) (listening|reading|understanding)/i,
|
|
254
|
+
/for the (second|third|fourth|last) time/i,
|
|
255
|
+
/!!+/,
|
|
256
|
+
/\b(WTF|WHAT THE|FFS|OMG)\b/i,
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
function detectFrustration(prompt) {
|
|
260
|
+
if (!prompt || prompt.length < 5) return false;
|
|
261
|
+
return FRUSTRATION_PATTERNS.some(p => p.test(prompt));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Main handler.
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
56
268
|
async function main() {
|
|
57
269
|
let input = '';
|
|
58
|
-
for await (const chunk of process.stdin)
|
|
59
|
-
input += chunk;
|
|
60
|
-
}
|
|
270
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
61
271
|
|
|
62
272
|
try {
|
|
63
273
|
const event = JSON.parse(input);
|
|
@@ -65,72 +275,55 @@ async function main() {
|
|
|
65
275
|
const session = readSessionState(projectDir);
|
|
66
276
|
|
|
67
277
|
if (!session) {
|
|
68
|
-
// No active session — don't inject anything (empty output = allow)
|
|
69
278
|
process.stdout.write('{}');
|
|
70
279
|
return;
|
|
71
280
|
}
|
|
72
281
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
// Infer model from current agent via AGENT_MODELS map
|
|
80
|
-
const HOOK_AGENT_MODELS = {
|
|
81
|
-
orchestrator: 'sonnet', 'greenfield-wu': 'haiku', 'brownfield-wu': 'opus',
|
|
82
|
-
brief: 'sonnet', detail: 'opus', architect: 'opus', ux: 'sonnet',
|
|
83
|
-
phases: 'sonnet', tasks: 'sonnet', 'qa-planning': 'opus',
|
|
84
|
-
'qa-implementation': 'opus', dev: 'opus', devops: 'sonnet',
|
|
85
|
-
};
|
|
86
|
-
const inferredModel = session.currentAgent ? HOOK_AGENT_MODELS[session.currentAgent] : null;
|
|
87
|
-
|
|
88
|
-
// Resolve context limit: model > provider > default
|
|
89
|
-
function hookResolveLimit(model, provider) {
|
|
90
|
-
if (model && HOOK_MODEL_LIMITS[model]) return HOOK_MODEL_LIMITS[model];
|
|
91
|
-
if (provider && HOOK_PROVIDER_LIMITS[provider]) return HOOK_PROVIDER_LIMITS[provider];
|
|
92
|
-
return 200_000;
|
|
93
|
-
}
|
|
282
|
+
const limits = loadLimits(projectDir);
|
|
283
|
+
|
|
284
|
+
// 1. Model + window detection.
|
|
285
|
+
const detectedModel = detectModel(event, session);
|
|
286
|
+
const contextLimit = resolveLimit(limits, detectedModel, session.provider);
|
|
94
287
|
|
|
95
|
-
//
|
|
96
|
-
// Fallback: prompt-length for first turn or missing session data.
|
|
288
|
+
// 2. Token accounting — cumulative, replaces turn-count.
|
|
97
289
|
const promptText = event.prompt || '';
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const contextLimit = hookResolveLimit(inferredModel, session.provider);
|
|
105
|
-
remainingPercent = Math.max(0, Math.round((1 - estimatedTokens / contextLimit) * 100));
|
|
106
|
-
}
|
|
290
|
+
const delta = estimatePromptDelta(promptText);
|
|
291
|
+
const tokensUsed = Math.min(contextLimit, session.contextTokensUsed + delta);
|
|
292
|
+
const remainingPercent = Math.max(0, Math.round((1 - tokensUsed / contextLimit) * 100));
|
|
293
|
+
|
|
294
|
+
// 3. Bracket computation (percentage-based, model-scaled).
|
|
295
|
+
const bracket = computeBracket(remainingPercent);
|
|
107
296
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
297
|
+
// 4. Persist back to session.yaml (best-effort, non-fatal on write error).
|
|
298
|
+
try {
|
|
299
|
+
writeBackSession(session.sessionPath, session.raw, {
|
|
300
|
+
context_tokens_used: tokensUsed,
|
|
301
|
+
context_window_tokens: contextLimit,
|
|
302
|
+
context_last_bracket: bracket,
|
|
303
|
+
// If we detected a model and the session did not have one, cache it.
|
|
304
|
+
...(detectedModel && !session.activeModel ? { active_model: detectedModel } : {}),
|
|
305
|
+
});
|
|
306
|
+
} catch (err) {
|
|
307
|
+
process.stderr.write(`[chati] prism-engine write-back failed (non-fatal): ${err.message}\n`);
|
|
308
|
+
}
|
|
113
309
|
|
|
114
|
-
//
|
|
310
|
+
// 5. Agent-local memory block (unchanged).
|
|
115
311
|
let memoryBlock = '';
|
|
116
312
|
if (session.currentAgent) {
|
|
117
313
|
const memoryPath = join(projectDir, '.chati', 'memories', session.currentAgent, 'MEMORY.md');
|
|
118
314
|
if (existsSync(memoryPath)) {
|
|
119
315
|
const raw = readFileSync(memoryPath, 'utf-8').trim();
|
|
120
316
|
if (raw) {
|
|
121
|
-
|
|
122
|
-
memoryBlock = ` <agent-memory agent="${session.currentAgent}">\n${trimmed}\n </agent-memory>`;
|
|
317
|
+
memoryBlock = ` <agent-memory agent="${session.currentAgent}">\n${raw.slice(0, 500)}\n </agent-memory>`;
|
|
123
318
|
}
|
|
124
319
|
}
|
|
125
320
|
}
|
|
126
321
|
|
|
127
|
-
//
|
|
128
|
-
// Max 500 tokens, flat budget. Contains team metadata + shared task status only.
|
|
322
|
+
// 6. Team context block (unchanged).
|
|
129
323
|
let teamContextBlock = '';
|
|
130
324
|
if (session.activeTeamId && bracket !== 'CRITICAL') {
|
|
131
325
|
const taskListPath = join(projectDir, '.chati', 'teams', session.activeTeamId, 'tasks.yaml');
|
|
132
326
|
if (existsSync(taskListPath)) {
|
|
133
|
-
// Read and cap at 800 chars (~200 tokens) to stay within 500-token budget
|
|
134
327
|
const rawTasks = readFileSync(taskListPath, 'utf-8').trim().slice(0, 800);
|
|
135
328
|
teamContextBlock = [
|
|
136
329
|
` <team-context team-id="${session.activeTeamId}" phase="${session.activeTeamPhase}">`,
|
|
@@ -140,18 +333,16 @@ async function main() {
|
|
|
140
333
|
}
|
|
141
334
|
}
|
|
142
335
|
|
|
143
|
-
//
|
|
336
|
+
// 7. Advisories.
|
|
144
337
|
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
|
+
: '';
|
|
145
342
|
|
|
146
|
-
//
|
|
147
|
-
let microcompactAdvisory = '';
|
|
148
|
-
if ((bracket === 'DEPLETED' || bracket === 'CRITICAL') && session.turnCount > 10) {
|
|
149
|
-
microcompactAdvisory = ' <advisory priority="medium">Context constrained. Avoid re-reading files already in context. Summarize stale outputs before proceeding.</advisory>';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Build minimal context block (full PRISM pipeline is used by orchestrator internally)
|
|
343
|
+
// 8. Compose the context block.
|
|
153
344
|
const contextBlock = [
|
|
154
|
-
`<chati-context bracket="${bracket}">`,
|
|
345
|
+
`<chati-context bracket="${bracket}" model="${detectedModel || 'unknown'}" window="${contextLimit}" used="${tokensUsed}">`,
|
|
155
346
|
` <mode>${session.mode}</mode>`,
|
|
156
347
|
session.currentAgent ? ` <agent>${session.currentAgent}</agent>` : '',
|
|
157
348
|
session.pipelinePosition ? ` <pipeline-position>${session.pipelinePosition}</pipeline-position>` : '',
|
|
@@ -163,9 +354,6 @@ async function main() {
|
|
|
163
354
|
'</chati-context>',
|
|
164
355
|
].filter(Boolean).join('\n');
|
|
165
356
|
|
|
166
|
-
// UserPromptSubmit context injection schema:
|
|
167
|
-
// hookSpecificOutput.additionalContext is the canonical field for
|
|
168
|
-
// injecting text into Claude's view of the current prompt.
|
|
169
357
|
process.stdout.write(JSON.stringify({
|
|
170
358
|
hookSpecificOutput: {
|
|
171
359
|
hookEventName: 'UserPromptSubmit',
|
|
@@ -174,43 +362,24 @@ async function main() {
|
|
|
174
362
|
}));
|
|
175
363
|
} catch (err) {
|
|
176
364
|
process.stderr.write(`[chati] prism-engine: ${err.message}\n`);
|
|
177
|
-
// Silent allow on error — empty object = no injection, no block.
|
|
178
365
|
process.stdout.write('{}');
|
|
179
366
|
}
|
|
180
367
|
}
|
|
181
368
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
/!!+/,
|
|
195
|
-
/\b(WTF|WHAT THE|FFS|OMG)\b/i,
|
|
196
|
-
];
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Detect user frustration from prompt text.
|
|
200
|
-
* @param {string} prompt - User prompt text
|
|
201
|
-
* @returns {boolean} True if frustration patterns detected
|
|
202
|
-
*/
|
|
203
|
-
function detectFrustration(prompt) {
|
|
204
|
-
if (!prompt || prompt.length < 5) return false;
|
|
205
|
-
return FRUSTRATION_PATTERNS.some(p => p.test(prompt));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export { readSessionState, detectFrustration };
|
|
369
|
+
export {
|
|
370
|
+
readSessionState,
|
|
371
|
+
detectFrustration,
|
|
372
|
+
detectModel,
|
|
373
|
+
parseClaudeModelId,
|
|
374
|
+
resolveLimit,
|
|
375
|
+
loadLimits,
|
|
376
|
+
estimatePromptDelta,
|
|
377
|
+
computeBracket,
|
|
378
|
+
writeBackSession,
|
|
379
|
+
TOOL_OUTPUT_MULTIPLIER,
|
|
380
|
+
};
|
|
209
381
|
|
|
210
382
|
// Only run main when executed directly (not imported by tests).
|
|
211
|
-
// realpathSync resolves symlinks on both sides — important on macOS where
|
|
212
|
-
// /tmp is a symlink to /private/tmp. Without realpath the comparison fails
|
|
213
|
-
// silently and main() never runs.
|
|
214
383
|
import { fileURLToPath } from 'url';
|
|
215
384
|
import { realpathSync } from 'fs';
|
|
216
385
|
if (process.argv[1]) {
|