chati-dev 4.0.10 → 4.1.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 +5 -0
- package/bin/chati.js +23 -2
- 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.md +6 -0
- 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 +8 -2
- package/framework/context/root.md +1 -1
- 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/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/templates/brandbook-html-tmpl.md +107 -0
- package/framework/templates/session-memory-tmpl.yaml +68 -0
- package/package.json +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 +6 -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 +2 -2
- 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 +20 -2
- package/src/license/commands.js +7 -7
- 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 +25 -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 -5
- package/src/telemetry/sender.js +19 -17
- 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 +2 -2
- 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
|
@@ -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
|
|
|
@@ -93,7 +93,7 @@ async function main() {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
console.log(JSON.stringify({}));
|
|
96
|
-
} catch {
|
|
96
|
+
} catch { /* expected: operation may fail gracefully */
|
|
97
97
|
console.log(JSON.stringify({}));
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -149,7 +149,7 @@ async function main() {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
console.log(JSON.stringify({}));
|
|
152
|
-
} catch {
|
|
152
|
+
} catch { /* expected: operation may fail gracefully */
|
|
153
153
|
console.log(JSON.stringify({}));
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -236,7 +236,7 @@ async function main() {
|
|
|
236
236
|
error: \`[Article XI] Cannot write "\${filePath}" in \${mode} mode. Allowed: \${MODE_SCOPES[mode].allowed.join(', ')}\`
|
|
237
237
|
}));
|
|
238
238
|
}
|
|
239
|
-
} catch {
|
|
239
|
+
} catch { /* expected: operation may fail gracefully */
|
|
240
240
|
console.log(JSON.stringify({}));
|
|
241
241
|
}
|
|
242
242
|
}
|
|
@@ -331,7 +331,7 @@ async function main() {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
console.log(JSON.stringify({}));
|
|
334
|
-
} catch {
|
|
334
|
+
} catch { /* expected: operation may fail gracefully */
|
|
335
335
|
console.log(JSON.stringify({}));
|
|
336
336
|
}
|
|
337
337
|
}
|
|
@@ -411,7 +411,7 @@ async function main() {
|
|
|
411
411
|
}
|
|
412
412
|
|
|
413
413
|
console.log(JSON.stringify({}));
|
|
414
|
-
} catch {
|
|
414
|
+
} catch { /* expected: operation may fail gracefully */
|
|
415
415
|
console.log(JSON.stringify({}));
|
|
416
416
|
}
|
|
417
417
|
}
|
|
@@ -450,7 +450,7 @@ async function main() {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
console.log(JSON.stringify({}));
|
|
453
|
-
} catch {
|
|
453
|
+
} catch { /* expected: operation may fail gracefully */
|
|
454
454
|
console.log(JSON.stringify({}));
|
|
455
455
|
}
|
|
456
456
|
}
|
|
@@ -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[] }}
|
package/src/context/formatter.js
CHANGED
|
@@ -52,7 +52,19 @@ export function formatContext(options) {
|
|
|
52
52
|
sections.push(formatKeywords(l5));
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
// Insert STATIC_BOUNDARY between static (L0+L1) and dynamic (L2-L5) sections.
|
|
56
|
+
// This enables prompt cache reuse for the stable prefix (Claude Code pattern).
|
|
57
|
+
const staticSections = sections.slice(0, l1 ? 2 : (l0 ? 1 : 0));
|
|
58
|
+
const dynamicSections = sections.slice(staticSections.length);
|
|
59
|
+
|
|
60
|
+
let body;
|
|
61
|
+
if (dynamicSections.length > 0) {
|
|
62
|
+
body = staticSections.join('\n\n') +
|
|
63
|
+
'\n\n <!-- STATIC_BOUNDARY -->\n\n' +
|
|
64
|
+
dynamicSections.join('\n\n');
|
|
65
|
+
} else {
|
|
66
|
+
body = staticSections.join('\n\n');
|
|
67
|
+
}
|
|
56
68
|
|
|
57
69
|
// Truncate if over budget (rough estimate: 1 token ≈ 4 chars)
|
|
58
70
|
const maxChars = tokenBudget * 4;
|
|
@@ -193,6 +205,38 @@ function truncateByPriority(sections, maxChars) {
|
|
|
193
205
|
return result;
|
|
194
206
|
}
|
|
195
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Format only the static portion (L0 + L1) of the context.
|
|
210
|
+
* Useful for caching the stable prefix separately.
|
|
211
|
+
* @param {object} l0 - L0 Constitution result
|
|
212
|
+
* @param {object} [l1] - L1 Global result
|
|
213
|
+
* @returns {string} Static XML block
|
|
214
|
+
*/
|
|
215
|
+
export function formatStaticBlock(l0, l1) {
|
|
216
|
+
const parts = [];
|
|
217
|
+
if (l0) parts.push(formatConstitution(l0));
|
|
218
|
+
if (l1) parts.push(formatGlobal(l1));
|
|
219
|
+
return parts.join('\n\n');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Format only the dynamic portion (L2-L5) of the context.
|
|
224
|
+
* Changes every turn based on agent, workflow, task, and keywords.
|
|
225
|
+
* @param {object} [l2] - L2 Agent result
|
|
226
|
+
* @param {object} [l3] - L3 Workflow result
|
|
227
|
+
* @param {object} [l4] - L4 Task result
|
|
228
|
+
* @param {object} [l5] - L5 Keywords result
|
|
229
|
+
* @returns {string} Dynamic XML block
|
|
230
|
+
*/
|
|
231
|
+
export function formatDynamicBlock(l2, l3, l4, l5) {
|
|
232
|
+
const parts = [];
|
|
233
|
+
if (l2 && l2.agent) parts.push(formatAgent(l2));
|
|
234
|
+
if (l3 && l3.workflow) parts.push(formatWorkflow(l3));
|
|
235
|
+
if (l4 && l4.taskId) parts.push(formatTask(l4));
|
|
236
|
+
if (l5 && l5.matchCount > 0) parts.push(formatKeywords(l5));
|
|
237
|
+
return parts.join('\n\n');
|
|
238
|
+
}
|
|
239
|
+
|
|
196
240
|
/** Escape XML special characters */
|
|
197
241
|
function esc(str) {
|
|
198
242
|
if (!str) return '';
|
package/src/context/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { runPrism, getPrismInfo } from './engine.js';
|
|
2
|
-
export { calculateBracket, estimateRemaining, isLayerActive, getBracketDefinitions } from './bracket-tracker.js';
|
|
3
|
-
export { formatContext } from './formatter.js';
|
|
1
|
+
export { runPrism, getPrismInfo, clearStaticCache } from './engine.js';
|
|
2
|
+
export { calculateBracket, estimateRemaining, estimateRemainingFromTokens, isLayerActive, getBracketDefinitions } from './bracket-tracker.js';
|
|
3
|
+
export { formatContext, formatStaticBlock, formatDynamicBlock } from './formatter.js';
|
|
4
4
|
export { loadDomainFile, loadAgentDomains, loadWorkflowDomains, extractRules } from './domain-loader.js';
|
|
@@ -25,7 +25,7 @@ export async function readDashboardData(targetDir) {
|
|
|
25
25
|
if (existsSync(sessionPath)) {
|
|
26
26
|
try {
|
|
27
27
|
data.session = yaml.load(readFileSync(sessionPath, 'utf-8'));
|
|
28
|
-
} catch {
|
|
28
|
+
} catch { /* expected: file may not exist */
|
|
29
29
|
data.session = null;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -35,7 +35,7 @@ export async function readDashboardData(targetDir) {
|
|
|
35
35
|
if (existsSync(configPath)) {
|
|
36
36
|
try {
|
|
37
37
|
data.config = yaml.load(readFileSync(configPath, 'utf-8'));
|
|
38
|
-
} catch {
|
|
38
|
+
} catch { /* expected: file may not exist */
|
|
39
39
|
data.config = null;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -64,7 +64,7 @@ export async function readDashboardData(targetDir) {
|
|
|
64
64
|
try {
|
|
65
65
|
const files = readdirSync(tasksDir).filter(f => f.endsWith('.yaml') || f.endsWith('.md'));
|
|
66
66
|
data.taskProgress = { totalFiles: files.length };
|
|
67
|
-
} catch {
|
|
67
|
+
} catch { /* expected: directory may not exist */
|
|
68
68
|
// Ignore
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -75,7 +75,7 @@ export async function readDashboardData(targetDir) {
|
|
|
75
75
|
try {
|
|
76
76
|
const gotchasData = yaml.load(readFileSync(gotchasPath, 'utf-8'));
|
|
77
77
|
data.gotchas = gotchasData?.gotchas || [];
|
|
78
|
-
} catch {
|
|
78
|
+
} catch { /* expected: file may not exist */
|
|
79
79
|
// Ignore
|
|
80
80
|
}
|
|
81
81
|
}
|
|
@@ -102,21 +102,21 @@ export async function readDashboardData(targetDir) {
|
|
|
102
102
|
try {
|
|
103
103
|
const { getMemoryStats } = await import('../intelligence/memory-manager.js');
|
|
104
104
|
data.memoryStats = getMemoryStats(targetDir);
|
|
105
|
-
} catch {
|
|
105
|
+
} catch { /* expected: operation may fail gracefully */
|
|
106
106
|
// Intelligence module not available
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
try {
|
|
110
110
|
const { getContextStatus } = await import('../intelligence/context-status.js');
|
|
111
111
|
data.contextStatus = getContextStatus(targetDir);
|
|
112
|
-
} catch {
|
|
112
|
+
} catch { /* expected: operation may fail gracefully */
|
|
113
113
|
// Intelligence module not available
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
117
|
const { getRegistryStats } = await import('../intelligence/registry-manager.js');
|
|
118
118
|
data.registryStats = getRegistryStats(targetDir);
|
|
119
|
-
} catch {
|
|
119
|
+
} catch { /* expected: operation may fail gracefully */
|
|
120
120
|
// Intelligence module not available
|
|
121
121
|
}
|
|
122
122
|
|
package/src/decision/engine.js
CHANGED
|
@@ -168,7 +168,7 @@ export function getDecisionHistory(projectDir) {
|
|
|
168
168
|
const content = readFileSync(historyPath, 'utf8');
|
|
169
169
|
const data = JSON.parse(content);
|
|
170
170
|
return data.decisions || [];
|
|
171
|
-
} catch {
|
|
171
|
+
} catch { /* expected: file may not exist */
|
|
172
172
|
return [];
|
|
173
173
|
}
|
|
174
174
|
}
|
|
@@ -193,7 +193,7 @@ export function recordDecision(projectDir, decision) {
|
|
|
193
193
|
try {
|
|
194
194
|
const content = readFileSync(historyPath, 'utf8');
|
|
195
195
|
history = JSON.parse(content);
|
|
196
|
-
} catch {
|
|
196
|
+
} catch { /* expected: file may not exist */
|
|
197
197
|
// Keep empty history if parse fails
|
|
198
198
|
}
|
|
199
199
|
}
|
|
@@ -290,7 +290,7 @@ export function generateEntityMeta(projectDir, filePath) {
|
|
|
290
290
|
.slice(0, 5)
|
|
291
291
|
.map(([word]) => word);
|
|
292
292
|
meta.keywords.push(...topWords);
|
|
293
|
-
} catch {
|
|
293
|
+
} catch { /* expected: operation may fail gracefully */
|
|
294
294
|
// Ignore read errors
|
|
295
295
|
}
|
|
296
296
|
}
|
|
@@ -322,7 +322,7 @@ function scanDirectory(dirPath) {
|
|
|
322
322
|
files.push(fullPath);
|
|
323
323
|
}
|
|
324
324
|
});
|
|
325
|
-
} catch {
|
|
325
|
+
} catch { /* expected: operation may fail gracefully */
|
|
326
326
|
// Ignore read errors
|
|
327
327
|
}
|
|
328
328
|
|