chati-dev 4.0.11 → 4.1.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.
- package/README.md +14 -9
- package/bin/chati.js +17 -1
- package/framework/agents/build/dev.md +43 -10
- package/framework/agents/discover/brief.md +38 -8
- package/framework/agents/discover/brownfield-wu.md +44 -3
- package/framework/agents/discover/greenfield-wu.md +14 -0
- package/framework/agents/plan/architect-data-engineer.md +278 -0
- package/framework/agents/plan/architect-system.md +174 -0
- package/framework/agents/plan/architect.md +97 -301
- package/framework/agents/plan/detail.md +25 -0
- package/framework/agents/plan/tasks.md +29 -1
- package/framework/agents/plan/ux-brand-architect.md +215 -0
- package/framework/agents/plan/ux-component-engineer.md +289 -0
- package/framework/agents/plan/ux-researcher.md +166 -0
- package/framework/agents/plan/ux.md +126 -1008
- package/framework/agents/quality/qa-implementation.md +121 -22
- package/framework/agents/quality/qa-planning.md +18 -0
- package/framework/config.yaml +15 -4
- package/framework/constitution.md +9 -3
- package/framework/context/root.md +2 -2
- package/framework/data/entity-registry.yaml +59 -3
- package/framework/hooks/constitution-guard.js +67 -1
- package/framework/hooks/license-guard.js +4 -4
- package/framework/hooks/model-governance.js +2 -1
- package/framework/hooks/prism-engine.js +74 -6
- package/framework/hooks/read-protection.js +1 -1
- package/framework/hooks/session-digest.js +159 -7
- package/framework/hooks/settings.json +16 -4
- package/framework/hooks/style-guard.js +134 -0
- package/framework/hooks/undercover-guard.js +220 -0
- package/framework/i18n/en.yaml +2 -2
- package/framework/i18n/es.yaml +2 -2
- package/framework/i18n/fr.yaml +2 -2
- package/framework/i18n/pt.yaml +2 -2
- package/framework/intelligence/context-engine.md +21 -0
- package/framework/intelligence/hooks-performance.md +54 -0
- package/framework/intelligence/memory-layer.md +47 -0
- package/framework/migrations/v4.0-to-v4.1.yaml +165 -0
- package/framework/orchestrator/chati.md +327 -1067
- package/framework/schemas/session.schema.json +1 -1
- package/framework/templates/brandbook-html-tmpl.md +107 -0
- package/framework/templates/session-memory-tmpl.yaml +68 -0
- package/package.json +1 -1
- package/scripts/doctor/checks/agents.js +1 -1
- package/scripts/health-check.js +1 -1
- package/src/autonomy/build-state.js +1 -1
- package/src/autonomy/worktree-manager.js +13 -13
- package/src/config/agent-customizer.js +1 -1
- package/src/config/gemini-hooks-generator.js +149 -6
- package/src/config/ide-configs.js +1 -1
- package/src/context/bracket-tracker.js +25 -4
- package/src/context/engine.js +37 -7
- package/src/context/formatter.js +45 -1
- package/src/context/index.js +3 -3
- package/src/dashboard/data-reader.js +7 -7
- package/src/decision/engine.js +2 -2
- package/src/decision/registry-healer.js +1 -1
- package/src/decision/registry-updater.js +2 -2
- package/src/extensions/loader.js +1 -1
- package/src/gates/g1-planning-complete.js +1 -1
- package/src/gates/g2-qa-planning.js +1 -1
- package/src/gates/g3-implementation.js +4 -4
- package/src/gates/g4-qa-implementation.js +2 -2
- package/src/health/auto-fix.js +3 -3
- package/src/health/engine.js +2 -2
- package/src/installer/brownfield-upgrader.js +3 -3
- package/src/installer/manifest.js +1 -1
- package/src/installer/transaction.js +1 -1
- package/src/installer/validator.js +4 -4
- package/src/intelligence/decision-engine.js +1 -1
- package/src/intelligence/memory-manager.js +1 -1
- package/src/intelligence/registry-manager.js +2 -2
- package/src/intelligence/timeline.js +1 -1
- package/src/license/client.js +1 -1
- package/src/license/commands.js +2 -2
- package/src/memory/agent-memory.js +3 -3
- package/src/memory/daily-digest.js +170 -0
- package/src/memory/dream.js +254 -0
- package/src/memory/gotchas.js +2 -2
- package/src/memory/index.js +18 -0
- package/src/memory/magic-docs.js +98 -0
- package/src/memory/memory-extractor.js +163 -0
- package/src/memory/session-digest.js +144 -6
- package/src/merger/yaml-merger.js +1 -1
- package/src/orchestrator/cli.js +980 -0
- package/src/orchestrator/handoff-engine.js +31 -4
- package/src/orchestrator/index.js +9 -0
- package/src/orchestrator/session-manager.js +1 -1
- package/src/preview/detector.js +3 -3
- package/src/preview/launcher.js +2 -2
- package/src/quality/metrics-collector.js +1 -1
- package/src/quality/test-runner.js +2 -4
- package/src/scanning/density-scanner.js +51 -0
- package/src/scanning/env-scanner.js +97 -0
- package/src/scanning/index.js +7 -0
- package/src/scanning/leakage-scanner.js +54 -0
- package/src/scanning/placeholder-scanner.js +40 -0
- package/src/scanning/security-scanner.js +94 -0
- package/src/security/bash-security.js +335 -0
- package/src/security/index.js +9 -0
- package/src/telemetry/config.js +3 -3
- package/src/telemetry/sender.js +1 -1
- package/src/terminal/cli-registry.js +1 -1
- package/src/terminal/cost-tracker.js +1 -2
- package/src/terminal/prompt-builder.js +11 -27
- package/src/terminal/rate-limiter.js +1 -2
- package/src/terminal/run-agent.js +3 -3
- package/src/terminal/spawner.js +41 -4
- package/src/upgrade/checker.js +2 -2
- package/src/upgrade/migrator.js +1 -1
- package/src/utils/config-parser.js +1 -1
- package/src/utils/file-lock.js +3 -3
- package/src/utils/provider-limits.js +35 -4
- package/src/wizard/i18n.js +4 -4
- package/src/wizard/index.js +2 -1
- package/src/wizard/questions.js +4 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Brandbook HTML Template Reference
|
|
2
|
+
|
|
3
|
+
This document describes the mandatory structure for the `brandbook.html` visual style guide
|
|
4
|
+
produced by the UX agent in Phase 0. Each project gets a unique brandbook.html based on its
|
|
5
|
+
brand identity. This is NOT a literal HTML template to copy — it's a specification of what
|
|
6
|
+
sections must exist and what each must contain.
|
|
7
|
+
|
|
8
|
+
## File Requirements
|
|
9
|
+
|
|
10
|
+
- **Format**: Standalone HTML5 document
|
|
11
|
+
- **CSS**: All styles in a single `<style>` tag (no external stylesheets)
|
|
12
|
+
- **Fonts**: Google Fonts via `<link>` tags (only external dependency allowed)
|
|
13
|
+
- **JavaScript**: Minimal (only for theme toggle and smooth scroll)
|
|
14
|
+
- **Output path**: `chati.dev/artifacts/4-UX/brandbook.html`
|
|
15
|
+
|
|
16
|
+
## Mandatory Sections (14)
|
|
17
|
+
|
|
18
|
+
### 1. Hero
|
|
19
|
+
- Project name as wordmark (using the primary display font)
|
|
20
|
+
- Brand tagline or description
|
|
21
|
+
- Metadata: version, date, author/team
|
|
22
|
+
|
|
23
|
+
### 2. Navigation
|
|
24
|
+
- Sticky top bar with links to all sections
|
|
25
|
+
- Backdrop blur effect
|
|
26
|
+
- Active section highlighting on scroll
|
|
27
|
+
|
|
28
|
+
### 3. Identity
|
|
29
|
+
- Brand principles (3-5 core values)
|
|
30
|
+
- Personality traits
|
|
31
|
+
- Design philosophy statement
|
|
32
|
+
|
|
33
|
+
### 4. Colors
|
|
34
|
+
- Primary palette: visual swatches with hex, RGB, and CSS custom property name
|
|
35
|
+
- Semantic colors: success, warning, error, info with swatches
|
|
36
|
+
- Dark mode palette: show both themes side by side
|
|
37
|
+
- Contrast ratio badges (WCAG AA pass/fail)
|
|
38
|
+
|
|
39
|
+
### 5. Typography
|
|
40
|
+
- Primary font: rendered specimen at all weights (300-900)
|
|
41
|
+
- Secondary font: same treatment
|
|
42
|
+
- Monospace font: same treatment
|
|
43
|
+
- Heading scale: H1 through H6 rendered at actual sizes
|
|
44
|
+
- Body text specimen
|
|
45
|
+
- Line height and letter spacing values
|
|
46
|
+
|
|
47
|
+
### 6. Spacing
|
|
48
|
+
- Base unit and scale (4px, 8px, 12px, 16px, 24px, 32px, 48px, 64px, 96px)
|
|
49
|
+
- Visual blocks showing each spacing value
|
|
50
|
+
- Named tokens (--space-xs through --space-3xl)
|
|
51
|
+
|
|
52
|
+
### 7. Border Radius
|
|
53
|
+
- Radius scale with visual examples on sample boxes
|
|
54
|
+
- Named tokens (--radius-sm, --radius-md, --radius-lg, --radius-full)
|
|
55
|
+
|
|
56
|
+
### 8. Shadows
|
|
57
|
+
- Elevation levels with visual examples (shadow-sm, shadow-md, shadow-lg, shadow-xl)
|
|
58
|
+
- Each shadow shown on a card element
|
|
59
|
+
|
|
60
|
+
### 9. Icons
|
|
61
|
+
- Selected icon library name and rationale
|
|
62
|
+
- Sample icons at each size (xs, sm, md, lg, xl)
|
|
63
|
+
- Usage guidelines (with text, standalone, touch targets)
|
|
64
|
+
|
|
65
|
+
### 10. Components
|
|
66
|
+
- Buttons: primary, secondary, ghost, destructive in all states (default, hover, active, focus, disabled)
|
|
67
|
+
- Inputs: text, select, checkbox, radio in all states
|
|
68
|
+
- Cards: basic, interactive, featured
|
|
69
|
+
- Badges: status variants (success, warning, error, info)
|
|
70
|
+
|
|
71
|
+
### 11. States
|
|
72
|
+
- Loading: skeleton, spinner, progress bar patterns
|
|
73
|
+
- Error: error message, error boundary, form validation
|
|
74
|
+
- Empty: empty state illustration guidelines
|
|
75
|
+
- Success: confirmation patterns
|
|
76
|
+
|
|
77
|
+
### 12. Motion
|
|
78
|
+
- Timing scale table (fast, normal, slow durations)
|
|
79
|
+
- Easing curves (ease-in, ease-out, ease-in-out, spring)
|
|
80
|
+
- Micro-interaction notes (hover, focus, click feedback)
|
|
81
|
+
- Scroll animation personality description
|
|
82
|
+
|
|
83
|
+
### 13. Accessibility
|
|
84
|
+
- Contrast ratio table for all color pairs
|
|
85
|
+
- WCAG AA compliance checklist
|
|
86
|
+
- Focus indicator specifications
|
|
87
|
+
- Reduced motion alternatives
|
|
88
|
+
|
|
89
|
+
### 14. Brand Voice
|
|
90
|
+
- Tone spectrum (formal to casual, technical to friendly)
|
|
91
|
+
- Writing dos and donts
|
|
92
|
+
- Example phrases in brand voice
|
|
93
|
+
- Forbidden patterns (what the brand never says)
|
|
94
|
+
|
|
95
|
+
## Visual Quality Standards
|
|
96
|
+
|
|
97
|
+
- Professional design quality (minimum 8.5/10 visual polish)
|
|
98
|
+
- No placeholder content (lorem ipsum, John Doe, example.com)
|
|
99
|
+
- No emojis (use icons from the approved library)
|
|
100
|
+
- No generic SaaS blue unless brand explicitly requires it
|
|
101
|
+
- Responsive: must work on viewport widths from 375px to 1920px
|
|
102
|
+
- Print: page breaks between major sections, simplified colors
|
|
103
|
+
|
|
104
|
+
## Reference
|
|
105
|
+
|
|
106
|
+
See `/Users/ogabrielalonso/code/focus-ai/ping/docs/brand/brandbook.html` for a production
|
|
107
|
+
example with 14 sections, sticky nav, dark mode toggle, and complete token rendering.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Session Memory Template — 8-Section Structured Digest
|
|
2
|
+
# Inspired by Claude Code's structured session memory (10 sections, 12K tokens).
|
|
3
|
+
# Total budget: ~10K tokens (40,000 chars at 4 chars/token).
|
|
4
|
+
#
|
|
5
|
+
# Used by session-digest hook (PreCompact) and buildStructuredDigest() to
|
|
6
|
+
# capture rich session context that survives context compaction.
|
|
7
|
+
|
|
8
|
+
version: "2.0"
|
|
9
|
+
|
|
10
|
+
sections:
|
|
11
|
+
- id: current_agent
|
|
12
|
+
description: "Currently active agent in the pipeline"
|
|
13
|
+
max_tokens: 100
|
|
14
|
+
priority: 1
|
|
15
|
+
required: true
|
|
16
|
+
|
|
17
|
+
- id: pipeline_position
|
|
18
|
+
description: "Current position in the DISCOVER→PLAN→BUILD→DEPLOY pipeline"
|
|
19
|
+
max_tokens: 100
|
|
20
|
+
priority: 2
|
|
21
|
+
required: true
|
|
22
|
+
|
|
23
|
+
- id: active_task
|
|
24
|
+
description: "Active task ID, title, and acceptance criteria (Given-When-Then)"
|
|
25
|
+
max_tokens: 500
|
|
26
|
+
priority: 3
|
|
27
|
+
required: false
|
|
28
|
+
|
|
29
|
+
- id: files_changed
|
|
30
|
+
description: "Files created, modified, or deleted during this session"
|
|
31
|
+
max_tokens: 1000
|
|
32
|
+
priority: 7
|
|
33
|
+
required: false
|
|
34
|
+
|
|
35
|
+
- id: blockers
|
|
36
|
+
description: "Active blockers preventing progress (C01-C15 code, G01-G08 general)"
|
|
37
|
+
max_tokens: 1000
|
|
38
|
+
priority: 4
|
|
39
|
+
required: false
|
|
40
|
+
|
|
41
|
+
- id: decisions_made
|
|
42
|
+
description: "Key decisions with rationale (what + why) made during session"
|
|
43
|
+
max_tokens: 3000
|
|
44
|
+
priority: 5
|
|
45
|
+
required: false
|
|
46
|
+
|
|
47
|
+
- id: gotchas_found
|
|
48
|
+
description: "Error patterns and gotchas discovered during session"
|
|
49
|
+
max_tokens: 2000
|
|
50
|
+
priority: 6
|
|
51
|
+
required: false
|
|
52
|
+
|
|
53
|
+
- id: next_steps
|
|
54
|
+
description: "Planned next steps and pending actions"
|
|
55
|
+
max_tokens: 2300
|
|
56
|
+
priority: 8
|
|
57
|
+
required: false
|
|
58
|
+
|
|
59
|
+
total_budget_tokens: 10000
|
|
60
|
+
|
|
61
|
+
truncation_order:
|
|
62
|
+
- next_steps
|
|
63
|
+
- gotchas_found
|
|
64
|
+
- files_changed
|
|
65
|
+
- blockers
|
|
66
|
+
- decisions_made
|
|
67
|
+
- active_task
|
|
68
|
+
# current_agent and pipeline_position are never truncated
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Doctor check: Agent definition files.
|
|
3
3
|
*
|
|
4
|
-
* Validates that all
|
|
4
|
+
* Validates that all primary agent definition files exist with valid structure.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { readFileSync, existsSync } from 'fs';
|
package/scripts/health-check.js
CHANGED
|
@@ -139,7 +139,7 @@ function checkConstitution(frameworkDir) {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
* Check that all
|
|
142
|
+
* Check that all primary agent definition files exist with valid structure.
|
|
143
143
|
*/
|
|
144
144
|
function checkAgents(frameworkDir) {
|
|
145
145
|
const agentsDir = join(frameworkDir, 'agents');
|
|
@@ -64,7 +64,7 @@ export function isGitRepo(dir) {
|
|
|
64
64
|
try {
|
|
65
65
|
git('rev-parse --is-inside-work-tree', dir);
|
|
66
66
|
return true;
|
|
67
|
-
} catch {
|
|
67
|
+
} catch { /* expected: content may be malformed */
|
|
68
68
|
return false;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -100,7 +100,7 @@ export function createWorktree(projectDir, taskId, attempt) {
|
|
|
100
100
|
if (existsSync(worktreePath)) {
|
|
101
101
|
try {
|
|
102
102
|
git(`worktree remove "${worktreePath}" --force`, absProject);
|
|
103
|
-
} catch {
|
|
103
|
+
} catch { /* expected: operation may fail gracefully */
|
|
104
104
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -108,7 +108,7 @@ export function createWorktree(projectDir, taskId, attempt) {
|
|
|
108
108
|
// Delete branch if it exists from a previous run
|
|
109
109
|
try {
|
|
110
110
|
git(`branch -D "${branch}"`, absProject);
|
|
111
|
-
} catch {
|
|
111
|
+
} catch { /* expected: operation may fail gracefully */
|
|
112
112
|
// Branch doesn't exist — fine
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -118,12 +118,12 @@ export function createWorktree(projectDir, taskId, attempt) {
|
|
|
118
118
|
const cleanup = () => {
|
|
119
119
|
try {
|
|
120
120
|
git(`worktree remove "${worktreePath}" --force`, absProject);
|
|
121
|
-
} catch {
|
|
121
|
+
} catch { /* expected: operation may fail gracefully */
|
|
122
122
|
// Worktree already removed — ignore
|
|
123
123
|
}
|
|
124
124
|
try {
|
|
125
125
|
git(`branch -D "${branch}"`, absProject);
|
|
126
|
-
} catch {
|
|
126
|
+
} catch { /* expected: operation may fail gracefully */
|
|
127
127
|
// Branch already deleted — ignore
|
|
128
128
|
}
|
|
129
129
|
};
|
|
@@ -146,7 +146,7 @@ export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
|
|
|
146
146
|
if (!mainBranch) {
|
|
147
147
|
try {
|
|
148
148
|
mainBranch = git('rev-parse --abbrev-ref HEAD', absProject);
|
|
149
|
-
} catch {
|
|
149
|
+
} catch { /* expected: content may be malformed */
|
|
150
150
|
mainBranch = 'main';
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -154,7 +154,7 @@ export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
|
|
|
154
154
|
// Ensure we're on the main branch
|
|
155
155
|
try {
|
|
156
156
|
git(`checkout "${mainBranch}"`, absProject);
|
|
157
|
-
} catch {
|
|
157
|
+
} catch { /* expected: operation may fail gracefully */
|
|
158
158
|
return { success: false, conflicts: [`Failed to checkout ${mainBranch}`] };
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -162,7 +162,7 @@ export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
|
|
|
162
162
|
try {
|
|
163
163
|
git(`merge "${worktreeBranch}" --no-edit`, absProject);
|
|
164
164
|
return { success: true, conflicts: [] };
|
|
165
|
-
} catch {
|
|
165
|
+
} catch { /* expected: operation may fail gracefully */
|
|
166
166
|
// Parse conflicts from error output
|
|
167
167
|
const conflicts = [];
|
|
168
168
|
try {
|
|
@@ -170,14 +170,14 @@ export function mergeWorktree(projectDir, worktreeBranch, mainBranch) {
|
|
|
170
170
|
if (status) {
|
|
171
171
|
conflicts.push(...status.split('\n').filter(Boolean));
|
|
172
172
|
}
|
|
173
|
-
} catch {
|
|
173
|
+
} catch { /* expected: operation may fail gracefully */
|
|
174
174
|
conflicts.push('Unknown conflict — check git status');
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
// Abort the failed merge
|
|
178
178
|
try {
|
|
179
179
|
git('merge --abort', absProject);
|
|
180
|
-
} catch {
|
|
180
|
+
} catch { /* expected: operation may fail gracefully */
|
|
181
181
|
// Already aborted or not in merging state
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -207,7 +207,7 @@ export function cleanupWorktrees(projectDir, options = {}) {
|
|
|
207
207
|
let entries;
|
|
208
208
|
try {
|
|
209
209
|
entries = readdirSync(worktreeBase);
|
|
210
|
-
} catch {
|
|
210
|
+
} catch { /* expected: directory may not exist */
|
|
211
211
|
return { cleaned, errors: ['Failed to read worktree directory'] };
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -222,7 +222,7 @@ export function cleanupWorktrees(projectDir, options = {}) {
|
|
|
222
222
|
if (age > maxAge) {
|
|
223
223
|
try {
|
|
224
224
|
git(`worktree remove "${entryPath}" --force`, absProject);
|
|
225
|
-
} catch {
|
|
225
|
+
} catch { /* expected: operation may fail gracefully */
|
|
226
226
|
rmSync(entryPath, { recursive: true, force: true });
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -230,7 +230,7 @@ export function cleanupWorktrees(projectDir, options = {}) {
|
|
|
230
230
|
const branch = `chati/${entry}`;
|
|
231
231
|
try {
|
|
232
232
|
git(`branch -D "${branch}"`, absProject);
|
|
233
|
-
} catch {
|
|
233
|
+
} catch { /* expected: operation may fail gracefully */
|
|
234
234
|
// Branch already deleted
|
|
235
235
|
}
|
|
236
236
|
|
|
@@ -28,6 +28,8 @@ export const HOOK_MAP = {
|
|
|
28
28
|
'constitution-guard': { event: 'BeforeTool', description: 'Block destructive commands and secret writes' },
|
|
29
29
|
'read-protection': { event: 'BeforeTool', description: 'Block reads of sensitive files' },
|
|
30
30
|
'session-digest': { event: 'PreCompress', description: 'Save session state before context compression' },
|
|
31
|
+
'undercover-guard': { event: 'BeforeTool', description: 'Advisory: sanitize internal framework references from deliverables' },
|
|
32
|
+
'style-guard': { event: 'BeforeTool', description: 'Advisory: enforce em-dash and emoji standards' },
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
// ---------------------------------------------------------------------------
|
|
@@ -93,7 +95,7 @@ async function main() {
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
console.log(JSON.stringify({}));
|
|
96
|
-
} catch {
|
|
98
|
+
} catch { /* expected: operation may fail gracefully */
|
|
97
99
|
console.log(JSON.stringify({}));
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -149,7 +151,7 @@ async function main() {
|
|
|
149
151
|
}
|
|
150
152
|
|
|
151
153
|
console.log(JSON.stringify({}));
|
|
152
|
-
} catch {
|
|
154
|
+
} catch { /* expected: operation may fail gracefully */
|
|
153
155
|
console.log(JSON.stringify({}));
|
|
154
156
|
}
|
|
155
157
|
}
|
|
@@ -236,7 +238,7 @@ async function main() {
|
|
|
236
238
|
error: \`[Article XI] Cannot write "\${filePath}" in \${mode} mode. Allowed: \${MODE_SCOPES[mode].allowed.join(', ')}\`
|
|
237
239
|
}));
|
|
238
240
|
}
|
|
239
|
-
} catch {
|
|
241
|
+
} catch { /* expected: operation may fail gracefully */
|
|
240
242
|
console.log(JSON.stringify({}));
|
|
241
243
|
}
|
|
242
244
|
}
|
|
@@ -331,7 +333,7 @@ async function main() {
|
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
console.log(JSON.stringify({}));
|
|
334
|
-
} catch {
|
|
336
|
+
} catch { /* expected: operation may fail gracefully */
|
|
335
337
|
console.log(JSON.stringify({}));
|
|
336
338
|
}
|
|
337
339
|
}
|
|
@@ -411,7 +413,7 @@ async function main() {
|
|
|
411
413
|
}
|
|
412
414
|
|
|
413
415
|
console.log(JSON.stringify({}));
|
|
414
|
-
} catch {
|
|
416
|
+
} catch { /* expected: operation may fail gracefully */
|
|
415
417
|
console.log(JSON.stringify({}));
|
|
416
418
|
}
|
|
417
419
|
}
|
|
@@ -450,7 +452,146 @@ async function main() {
|
|
|
450
452
|
}
|
|
451
453
|
|
|
452
454
|
console.log(JSON.stringify({}));
|
|
453
|
-
} catch {
|
|
455
|
+
} catch { /* expected: operation may fail gracefully */
|
|
456
|
+
console.log(JSON.stringify({}));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
main();
|
|
461
|
+
`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Generate the undercover guard hook for Gemini CLI.
|
|
466
|
+
* BeforeTool event -- advisory: sanitizes internal framework references from deliverables.
|
|
467
|
+
*/
|
|
468
|
+
function generateUndercoverGuard() {
|
|
469
|
+
return `${HOOK_HEADER}
|
|
470
|
+
/**
|
|
471
|
+
* Undercover Guard -- BeforeTool
|
|
472
|
+
* Advisory: sanitizes internal chati.dev references from generated code,
|
|
473
|
+
* commits, and PRs so deliverables don't expose framework internals.
|
|
474
|
+
*/
|
|
475
|
+
async function main() {
|
|
476
|
+
let input = '';
|
|
477
|
+
for await (const chunk of process.stdin) {
|
|
478
|
+
input += chunk;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
const event = JSON.parse(input);
|
|
483
|
+
const cwd = event.cwd || process.cwd();
|
|
484
|
+
const toolName = event.tool_name || '';
|
|
485
|
+
const toolInput = event.tool_input || {};
|
|
486
|
+
|
|
487
|
+
// Only process write/edit/bash operations
|
|
488
|
+
if (!['WriteFile', 'EditFile', 'Write', 'Edit', 'Bash', 'RunCommand'].includes(toolName)) {
|
|
489
|
+
console.log(JSON.stringify({}));
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Delegate to shared undercover guard logic
|
|
494
|
+
const hookPath = join(cwd, 'chati.dev', 'hooks', 'undercover-guard.js');
|
|
495
|
+
if (!existsSync(hookPath)) {
|
|
496
|
+
console.log(JSON.stringify({}));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const mod = await import(hookPath);
|
|
501
|
+
let scan = { found: false, matches: [] };
|
|
502
|
+
|
|
503
|
+
if (['Write', 'Edit', 'WriteFile', 'EditFile'].includes(toolName)) {
|
|
504
|
+
const content = toolInput.content || toolInput.new_string || '';
|
|
505
|
+
if (typeof mod.scanForInternals === 'function') {
|
|
506
|
+
scan = mod.scanForInternals(content);
|
|
507
|
+
}
|
|
508
|
+
} else if (['Bash', 'RunCommand'].includes(toolName)) {
|
|
509
|
+
const command = toolInput.command || '';
|
|
510
|
+
if (typeof mod.scanBashForInternals === 'function') {
|
|
511
|
+
scan = mod.scanBashForInternals(command);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (scan.found) {
|
|
516
|
+
const terms = scan.matches.map(m => \`"\${m.term}" -> "\${m.replacement}"\`).join(', ');
|
|
517
|
+
console.log(JSON.stringify({
|
|
518
|
+
decision: 'allow',
|
|
519
|
+
reason: \`[Undercover] Internal references detected: \${terms}. Please sanitize before shipping.\`,
|
|
520
|
+
}));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
console.log(JSON.stringify({}));
|
|
525
|
+
} catch { /* expected: operation may fail gracefully */
|
|
526
|
+
console.log(JSON.stringify({}));
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
main();
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Generate the style guard hook for Gemini CLI.
|
|
536
|
+
* BeforeTool event -- advisory: enforces em-dash and emoji standards.
|
|
537
|
+
*/
|
|
538
|
+
function generateStyleGuard() {
|
|
539
|
+
return `${HOOK_HEADER}
|
|
540
|
+
/**
|
|
541
|
+
* Style Guard -- BeforeTool
|
|
542
|
+
* Advisory: enforces Constitution Article V writing standards
|
|
543
|
+
* (no em-dashes, no emojis in generated content).
|
|
544
|
+
*/
|
|
545
|
+
async function main() {
|
|
546
|
+
let input = '';
|
|
547
|
+
for await (const chunk of process.stdin) {
|
|
548
|
+
input += chunk;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
const event = JSON.parse(input);
|
|
553
|
+
const cwd = event.cwd || process.cwd();
|
|
554
|
+
const toolName = event.tool_name || '';
|
|
555
|
+
const toolInput = event.tool_input || {};
|
|
556
|
+
|
|
557
|
+
// Only process write/edit/bash operations
|
|
558
|
+
if (!['WriteFile', 'EditFile', 'Write', 'Edit', 'Bash', 'RunCommand'].includes(toolName)) {
|
|
559
|
+
console.log(JSON.stringify({}));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Delegate to shared style guard logic
|
|
564
|
+
const hookPath = join(cwd, 'chati.dev', 'hooks', 'style-guard.js');
|
|
565
|
+
if (!existsSync(hookPath)) {
|
|
566
|
+
console.log(JSON.stringify({}));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const mod = await import(hookPath);
|
|
571
|
+
let result = { violations: [] };
|
|
572
|
+
|
|
573
|
+
if (['Write', 'Edit', 'WriteFile', 'EditFile'].includes(toolName)) {
|
|
574
|
+
const content = toolInput.content || toolInput.new_string || '';
|
|
575
|
+
if (typeof mod.checkStyle === 'function') {
|
|
576
|
+
result = mod.checkStyle(content);
|
|
577
|
+
}
|
|
578
|
+
} else if (['Bash', 'RunCommand'].includes(toolName)) {
|
|
579
|
+
const command = toolInput.command || '';
|
|
580
|
+
if (typeof mod.checkBashStyle === 'function') {
|
|
581
|
+
result = mod.checkBashStyle(command);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (result.violations && result.violations.length > 0) {
|
|
586
|
+
console.log(JSON.stringify({
|
|
587
|
+
decision: 'allow',
|
|
588
|
+
reason: \`[Style Guard] \${result.violations.join(' ')}\`,
|
|
589
|
+
}));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
console.log(JSON.stringify({}));
|
|
594
|
+
} catch { /* expected: operation may fail gracefully */
|
|
454
595
|
console.log(JSON.stringify({}));
|
|
455
596
|
}
|
|
456
597
|
}
|
|
@@ -476,6 +617,8 @@ export function generateAllGeminiHooks() {
|
|
|
476
617
|
'constitution-guard.js': generateConstitutionGuard(),
|
|
477
618
|
'read-protection.js': generateReadProtection(),
|
|
478
619
|
'session-digest.js': generateSessionDigest(),
|
|
620
|
+
'undercover-guard.js': generateUndercoverGuard(),
|
|
621
|
+
'style-guard.js': generateStyleGuard(),
|
|
479
622
|
};
|
|
480
623
|
}
|
|
481
624
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* CRITICAL = 5.0% → maximum reinforcement (last interactions before handoff)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { resolveContextLimit } from '../utils/provider-limits.js';
|
|
22
22
|
import { BRACKETS } from '../utils/brackets.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -40,9 +40,10 @@ const MEMORY_LEVELS = {
|
|
|
40
40
|
* Calculate bracket from remaining context percentage.
|
|
41
41
|
* @param {number} remainingPercent - 0 to 100
|
|
42
42
|
* @param {string} [provider='claude'] - Provider name for context window scaling
|
|
43
|
+
* @param {string} [model] - Model tier (opus, sonnet, haiku) for precise context limits
|
|
43
44
|
* @returns {{ bracket: string, activeLayers: string[], tokenBudget: number, budgetRatio: number, memoryLevel: string, handoffRequired: boolean, provider: string }}
|
|
44
45
|
*/
|
|
45
|
-
export function calculateBracket(remainingPercent, provider = 'claude') {
|
|
46
|
+
export function calculateBracket(remainingPercent, provider = 'claude', model) {
|
|
46
47
|
const pct = Math.max(0, Math.min(100, remainingPercent));
|
|
47
48
|
|
|
48
49
|
let name = 'CRITICAL';
|
|
@@ -51,8 +52,8 @@ export function calculateBracket(remainingPercent, provider = 'claude') {
|
|
|
51
52
|
else if (pct >= 25) name = 'DEPLETED';
|
|
52
53
|
|
|
53
54
|
const def = BRACKETS[name];
|
|
54
|
-
const
|
|
55
|
-
const tokenBudget = Math.round(def.budgetRatio *
|
|
55
|
+
const contextLimit = resolveContextLimit(model, provider);
|
|
56
|
+
const tokenBudget = Math.round(def.budgetRatio * contextLimit);
|
|
56
57
|
|
|
57
58
|
return {
|
|
58
59
|
bracket: name,
|
|
@@ -90,6 +91,26 @@ export function isLayerActive(bracket, layer) {
|
|
|
90
91
|
return def.layers.includes(layer);
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Estimate remaining context percentage from actual prompt content size.
|
|
96
|
+
* Uses the heuristic that 1 token ≈ 4 characters.
|
|
97
|
+
*
|
|
98
|
+
* This is more accurate than turn-count estimation because it measures
|
|
99
|
+
* actual content rather than guessing from conversation length.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} promptText - The full prompt text to estimate
|
|
102
|
+
* @param {string} [provider='claude'] - Provider name for context window scaling
|
|
103
|
+
* @param {string} [model] - Model tier (opus, sonnet, haiku) for precise limits
|
|
104
|
+
* @returns {number} Estimated remaining percentage (0-100)
|
|
105
|
+
*/
|
|
106
|
+
export function estimateRemainingFromTokens(promptText, provider = 'claude', model) {
|
|
107
|
+
if (!promptText) return 100;
|
|
108
|
+
const estimatedTokens = Math.ceil(promptText.length / 4);
|
|
109
|
+
const limit = resolveContextLimit(model, provider);
|
|
110
|
+
const used = Math.min(estimatedTokens, limit) / limit;
|
|
111
|
+
return Math.round((1 - used) * 100);
|
|
112
|
+
}
|
|
113
|
+
|
|
93
114
|
/**
|
|
94
115
|
* Get all bracket definitions (for display/dashboard).
|
|
95
116
|
* @returns {Record<string, { min: number, max: number, layers: string[], tokenBudget: number }>}
|
package/src/context/engine.js
CHANGED
|
@@ -18,6 +18,14 @@ import { formatContext } from './formatter.js';
|
|
|
18
18
|
|
|
19
19
|
const LAYER_TIMEOUT_MS = 100;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Static cache for L0+L1 layers.
|
|
23
|
+
* These layers never change mid-session (constitution and global rules are stable),
|
|
24
|
+
* so we cache them to improve prompt cache hit rates.
|
|
25
|
+
* Inspired by Claude Code's SYSTEM_PROMPT_DYNAMIC_BOUNDARY pattern.
|
|
26
|
+
*/
|
|
27
|
+
let _staticCache = { key: null, l0: null, l1: null };
|
|
28
|
+
|
|
21
29
|
/**
|
|
22
30
|
* Run the PRISM context engine pipeline.
|
|
23
31
|
*
|
|
@@ -61,14 +69,29 @@ export function runPrism(input) {
|
|
|
61
69
|
// 3. Process each active layer with timeout protection
|
|
62
70
|
const layerResults = {};
|
|
63
71
|
|
|
64
|
-
// L0 —
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
72
|
+
// L0 + L1 — Static layers (cacheable, never change mid-session)
|
|
73
|
+
const cacheKey = `${input.domainsDir || ''}:${ctx.mode}`;
|
|
74
|
+
if (_staticCache.key === cacheKey && _staticCache.l0) {
|
|
75
|
+
layerResults.l0 = _staticCache.l0;
|
|
76
|
+
layers.push(_staticCache.l0);
|
|
77
|
+
if (_staticCache.l1) {
|
|
78
|
+
layerResults.l1 = _staticCache.l1;
|
|
79
|
+
layers.push(_staticCache.l1);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// L0 — Always active
|
|
83
|
+
const l0 = safeProcess('L0', () => processL0(ctx), errors);
|
|
84
|
+
if (l0) { layers.push(l0); layerResults.l0 = l0; }
|
|
85
|
+
|
|
86
|
+
// L1 — Always active
|
|
87
|
+
let l1 = null;
|
|
88
|
+
if (isLayerActive(bracket.bracket, 'L1')) {
|
|
89
|
+
l1 = safeProcess('L1', () => processL1(ctx), errors);
|
|
90
|
+
if (l1) { layers.push(l1); layerResults.l1 = l1; }
|
|
91
|
+
}
|
|
67
92
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const l1 = safeProcess('L1', () => processL1(ctx), errors);
|
|
71
|
-
if (l1) { layers.push(l1); layerResults.l1 = l1; }
|
|
93
|
+
// Cache static layers
|
|
94
|
+
_staticCache = { key: cacheKey, l0: layerResults.l0 || null, l1: l1 };
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
// L2 — Agent layer
|
|
@@ -132,6 +155,13 @@ function safeProcess(layerName, processFn, errors) {
|
|
|
132
155
|
}
|
|
133
156
|
}
|
|
134
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Clear the static L0+L1 cache. Used when mode changes or for testing.
|
|
160
|
+
*/
|
|
161
|
+
export function clearStaticCache() {
|
|
162
|
+
_staticCache = { key: null, l0: null, l1: null };
|
|
163
|
+
}
|
|
164
|
+
|
|
135
165
|
/**
|
|
136
166
|
* Get a summary of PRISM capabilities for display.
|
|
137
167
|
* @returns {{ layers: number, brackets: string[], features: string[] }}
|