dual-brain 4.2.0 → 4.5.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/CLAUDE.md +130 -35
- package/README.md +171 -44
- package/hooks/agent-chains.mjs +369 -0
- package/hooks/agent-templates.mjs +441 -0
- package/hooks/atomic-write.mjs +5 -3
- package/hooks/config-validator.mjs +156 -0
- package/hooks/confirmation-policy.mjs +167 -0
- package/hooks/cost-logger.mjs +32 -12
- package/hooks/cost-report.mjs +60 -114
- package/hooks/decision-ledger.mjs +3 -2
- package/hooks/dual-brain-review.mjs +249 -2
- package/hooks/dual-brain-think.mjs +294 -25
- package/hooks/enforce-tier.mjs +246 -87
- package/hooks/error-channel.mjs +68 -0
- package/hooks/failure-detector.mjs +2 -1
- package/hooks/health-check.mjs +16 -17
- package/hooks/risk-classifier.mjs +135 -2
- package/hooks/session-report.mjs +41 -71
- package/hooks/ship-captain.mjs +1176 -0
- package/hooks/ship-gate.mjs +971 -0
- package/hooks/summary-checkpoint.mjs +31 -4
- package/hooks/test-orchestrator.mjs +1975 -11
- package/install.mjs +1064 -31
- package/orchestrator.json +73 -96
- package/package.json +7 -2
|
@@ -21,6 +21,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
21
21
|
import { dirname, join } from 'path';
|
|
22
22
|
import { fileURLToPath } from 'url';
|
|
23
23
|
import { atomicWriteJSON } from './atomic-write.mjs';
|
|
24
|
+
import { logHookError } from './error-channel.mjs';
|
|
24
25
|
|
|
25
26
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
27
|
|
|
@@ -43,7 +44,9 @@ function emptySummary() {
|
|
|
43
44
|
|
|
44
45
|
totals: {
|
|
45
46
|
calls: 0,
|
|
46
|
-
|
|
47
|
+
activity_score: 0, // token-weighted 0-100 scale, not dollars
|
|
48
|
+
activity_raw: 0, // raw weighted token count before normalization
|
|
49
|
+
activity_basis: 'none', // 'actual' | 'estimated' | 'mixed'
|
|
47
50
|
by_tier: {},
|
|
48
51
|
by_provider: {},
|
|
49
52
|
by_model: {},
|
|
@@ -79,7 +82,14 @@ function emptySummary() {
|
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
// Tier-based fallback weights when actual token counts are unavailable (legacy entries).
|
|
86
|
+
// These are unitless activity weights, NOT dollar costs.
|
|
87
|
+
const TIER_ACTIVITY_WEIGHTS = { search: 3, execute: 10, think: 25 };
|
|
88
|
+
|
|
89
|
+
// Activity formula: (input_tokens * 1) + (output_tokens * 3), normalized to 0-100 per session.
|
|
90
|
+
// SESSION_ACTIVITY_CEILING is the raw token-weighted value that maps to score 100.
|
|
91
|
+
// Calibrated to ~200 moderate tool calls in a session.
|
|
92
|
+
const SESSION_ACTIVITY_CEILING = 5_000_000;
|
|
83
93
|
|
|
84
94
|
/** @deprecated Use atomicWriteJSON directly. Kept as re-export for backward compat. */
|
|
85
95
|
function atomicWrite(path, data) {
|
|
@@ -127,10 +137,27 @@ function applyEntry(summary, entry) {
|
|
|
127
137
|
const tier = entry.tier || 'execute';
|
|
128
138
|
const provider = entry.provider || 'claude';
|
|
129
139
|
const model = entry.model || 'unknown';
|
|
130
|
-
|
|
140
|
+
|
|
141
|
+
// Compute activity from actual tokens when available, else use tier-based fallback
|
|
142
|
+
const hasTokens = entry.input_tokens != null && entry.output_tokens != null;
|
|
143
|
+
const rawActivity = hasTokens
|
|
144
|
+
? (entry.input_tokens * 1) + (entry.output_tokens * 3)
|
|
145
|
+
: TIER_ACTIVITY_WEIGHTS[tier] || TIER_ACTIVITY_WEIGHTS.execute;
|
|
131
146
|
|
|
132
147
|
summary.totals.calls++;
|
|
133
|
-
summary.totals.
|
|
148
|
+
summary.totals.activity_raw = (summary.totals.activity_raw || 0) + rawActivity;
|
|
149
|
+
summary.totals.activity_score = Math.min(100,
|
|
150
|
+
Math.round((summary.totals.activity_raw / SESSION_ACTIVITY_CEILING) * 100));
|
|
151
|
+
|
|
152
|
+
// Track whether scores are based on actual tokens or estimates
|
|
153
|
+
const prevBasis = summary.totals.activity_basis || 'none';
|
|
154
|
+
if (prevBasis === 'none') {
|
|
155
|
+
summary.totals.activity_basis = hasTokens ? 'actual' : 'estimated';
|
|
156
|
+
} else if (prevBasis === 'actual' && !hasTokens) {
|
|
157
|
+
summary.totals.activity_basis = 'mixed';
|
|
158
|
+
} else if (prevBasis === 'estimated' && hasTokens) {
|
|
159
|
+
summary.totals.activity_basis = 'mixed';
|
|
160
|
+
}
|
|
134
161
|
|
|
135
162
|
summary.totals.by_tier[tier] = (summary.totals.by_tier[tier] || 0) + 1;
|
|
136
163
|
summary.totals.by_provider[provider] = (summary.totals.by_provider[provider] || 0) + 1;
|