codehere 1.0.1 → 1.0.3
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 +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Write-pass target resolver · v0.9 S6 part B.
|
|
3
|
-
*
|
|
4
|
-
* Turns a W-PASS target like `linear://INFRA-247` into a concrete
|
|
5
|
-
* HTTPS request the daemon can fire on the user's behalf, AFTER the
|
|
6
|
-
* user has approved the payload. The resolver layer mirrors the
|
|
7
|
-
* context-source registry · same scheme namespace, opposite direction
|
|
8
|
-
* (read vs write).
|
|
9
|
-
*
|
|
10
|
-
* Resolved request shape:
|
|
11
|
-
* - url · absolute HTTPS
|
|
12
|
-
* - method · same as the intent (POST/PUT/PATCH/DELETE)
|
|
13
|
-
* - headers · auth + content-type · loaded from ctx.config[scheme]
|
|
14
|
-
* - body · JSON-stringified payload
|
|
15
|
-
*
|
|
16
|
-
* Tests pass a fake httpRequest · production uses node's fetch. Same
|
|
17
|
-
* seam as context-source so adapters look symmetric.
|
|
18
|
-
*
|
|
19
|
-
* S6 ships ONE resolver (Linear) to prove the shape. github + figma +
|
|
20
|
-
* drawio + notion follow in subsequent slices · each is a self-contained
|
|
21
|
-
* function in this file, so adding one is a copy-paste-modify, not an
|
|
22
|
-
* architectural change.
|
|
23
|
-
*/
|
|
24
|
-
import { gradeDomain, hostOf } from '../../application/use-cases/domain-trust.js';
|
|
25
|
-
export class WritePassResolveError extends Error {
|
|
26
|
-
code;
|
|
27
|
-
constructor(code, message) {
|
|
28
|
-
super(`[w-pass resolve] ${code}: ${message}`);
|
|
29
|
-
this.code = code;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Resolve an intent + effective payload into a concrete HTTP request.
|
|
34
|
-
* Throws WritePassResolveError if the scheme isn't supported OR auth
|
|
35
|
-
* is missing · the daemon turns those into a 4xx response BEFORE
|
|
36
|
-
* touching the network.
|
|
37
|
-
*/
|
|
38
|
-
export function resolveWriteRequest(intent, effectivePayload, config) {
|
|
39
|
-
const m = intent.target.match(/^([a-z][a-z0-9+.-]*):\/\/(.+)$/);
|
|
40
|
-
if (!m) {
|
|
41
|
-
throw new WritePassResolveError('invalid_target', `bad URL: ${intent.target}`);
|
|
42
|
-
}
|
|
43
|
-
const scheme = m[1];
|
|
44
|
-
const rest = m[2];
|
|
45
|
-
const built = scheme === 'linear'
|
|
46
|
-
? resolveLinear(rest, intent.method, effectivePayload, config.linear)
|
|
47
|
-
: scheme === 'github'
|
|
48
|
-
? resolveGitHub(rest, intent.method, effectivePayload, config.github)
|
|
49
|
-
: null;
|
|
50
|
-
if (built) {
|
|
51
|
-
return { ...built, trust: gradeDomain(hostOf(built.url)) };
|
|
52
|
-
}
|
|
53
|
-
throw new WritePassResolveError('unsupported_scheme', `no W-PASS resolver for "${scheme}" yet · supported in S6: linear, github`);
|
|
54
|
-
}
|
|
55
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
56
|
-
/* Linear */
|
|
57
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
58
|
-
function resolveLinear(rest, method, payload, cfg) {
|
|
59
|
-
const auth = cfg?.oauth?.accessToken ? `Bearer ${cfg.oauth.accessToken}` : cfg?.apiKey;
|
|
60
|
-
if (!auth) {
|
|
61
|
-
throw new WritePassResolveError('auth_missing', 'no Linear apiKey or OAuth token in config["linear"] · agent cannot write without explicit credentials');
|
|
62
|
-
}
|
|
63
|
-
// Linear's API is GraphQL · we send mutations via POST. The agent's
|
|
64
|
-
// intent.payload is expected to be { query, variables } or { mutation, variables }.
|
|
65
|
-
// We pass it through verbatim · the human saw + approved it.
|
|
66
|
-
const endpoint = cfg?.endpoint ?? 'https://api.linear.app/graphql';
|
|
67
|
-
return {
|
|
68
|
-
url: endpoint,
|
|
69
|
-
// Linear writes always POST (it's GraphQL) · intent.method is recorded
|
|
70
|
-
// for audit but doesn't change the transport. We log a one-line audit
|
|
71
|
-
// hint when method != POST so the user sees the mediation.
|
|
72
|
-
method: 'POST',
|
|
73
|
-
headers: {
|
|
74
|
-
Authorization: auth,
|
|
75
|
-
'Content-Type': 'application/json',
|
|
76
|
-
'X-Codehere-Mediated': 'w-pass',
|
|
77
|
-
'X-Codehere-Original-Method': method,
|
|
78
|
-
'X-Codehere-Identifier': rest,
|
|
79
|
-
},
|
|
80
|
-
body: JSON.stringify(payload),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
84
|
-
/* GitHub */
|
|
85
|
-
/* ──────────────────────────────────────────────────────────────────────── */
|
|
86
|
-
function resolveGitHub(rest, method, payload, cfg) {
|
|
87
|
-
if (!cfg?.token) {
|
|
88
|
-
throw new WritePassResolveError('auth_missing', 'no GitHub token in config["github"] · public-by-URL is READ only · writes need an explicit PAT');
|
|
89
|
-
}
|
|
90
|
-
// rest is e.g. owner/repo/issues/42/comments or owner/repo/issues/42
|
|
91
|
-
const endpoint = cfg.endpoint ?? 'https://api.github.com';
|
|
92
|
-
return {
|
|
93
|
-
url: `${endpoint}/repos/${rest}`,
|
|
94
|
-
method,
|
|
95
|
-
headers: {
|
|
96
|
-
Authorization: `Bearer ${cfg.token}`,
|
|
97
|
-
Accept: 'application/vnd.github+json',
|
|
98
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
99
|
-
'Content-Type': 'application/json',
|
|
100
|
-
'User-Agent': 'codehere/0.9 w-pass',
|
|
101
|
-
'X-Codehere-Mediated': 'w-pass',
|
|
102
|
-
},
|
|
103
|
-
body: JSON.stringify(payload),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=write-pass-resolver.js.map
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { writeFileAtomic } from '../storage/atomic-write.js';
|
|
2
|
-
/**
|
|
3
|
-
* v0.6 S3 slice 2 — daily spend budget config.
|
|
4
|
-
*
|
|
5
|
-
* Reads ~/.codehere/budget.yaml. The pre-spawn check in /api/task compares
|
|
6
|
-
* computeSpendToday().totalUsd against `daily_usd`; over-budget refuses with
|
|
7
|
-
* a friendly message instead of silently spawning another paid call.
|
|
8
|
-
*
|
|
9
|
-
* Why this is the v1 promise · claim_market_position v3 lists "cost-bounded
|
|
10
|
-
* execution" as one of the few capture-strategy edges still possibly open.
|
|
11
|
-
* Slice 1 surfaced spend; slice 2 enforces it. Together they make the edge
|
|
12
|
-
* demonstrable in 60 seconds: set a $1 budget, spawn until you hit it,
|
|
13
|
-
* watch codehere refuse the next spawn.
|
|
14
|
-
*
|
|
15
|
-
* Schema (~/.codehere/budget.yaml):
|
|
16
|
-
*
|
|
17
|
-
* daily_usd: 5.00 # null / omitted = no budget enforcement
|
|
18
|
-
* warn_at_pct: 70 # default 70 — chip turns yellow at 70%
|
|
19
|
-
*
|
|
20
|
-
* Single-file, hand-editable, plain YAML. Per claim_user_persona HIGH:
|
|
21
|
-
* non-technical users can edit it directly. Per overconfidence-guard:
|
|
22
|
-
* the budget is a USER ceiling, not a codehere guarantee — we can't
|
|
23
|
-
* see the actual provider invoice in real time.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
26
|
-
import { dirname, join } from 'path';
|
|
27
|
-
import { homedir } from 'os';
|
|
28
|
-
const DEFAULT_CONFIG = {
|
|
29
|
-
dailyUsd: null,
|
|
30
|
-
warnAtPct: 70,
|
|
31
|
-
};
|
|
32
|
-
function configFile() {
|
|
33
|
-
return join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'budget.yaml');
|
|
34
|
-
}
|
|
35
|
-
/** Parse a tiny subset of YAML — `key: value` pairs, # comments, blank lines.
|
|
36
|
-
* No nesting, no lists, no multi-line. Same approach as permissions-config:
|
|
37
|
-
* keep the dependency surface zero, schema simple. */
|
|
38
|
-
function parseYaml(content) {
|
|
39
|
-
const out = {};
|
|
40
|
-
for (const rawLine of content.split('\n')) {
|
|
41
|
-
const line = rawLine.replace(/#.*$/, '').trim();
|
|
42
|
-
if (!line)
|
|
43
|
-
continue;
|
|
44
|
-
const colonIdx = line.indexOf(':');
|
|
45
|
-
if (colonIdx === -1)
|
|
46
|
-
continue;
|
|
47
|
-
const key = line.slice(0, colonIdx).trim();
|
|
48
|
-
const val = line.slice(colonIdx + 1).trim();
|
|
49
|
-
if (key)
|
|
50
|
-
out[key] = val;
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
/** Read the budget config from disk. Missing file or parse failure → defaults
|
|
55
|
-
* (no enforcement). Best-effort: never throws — a corrupt file should not
|
|
56
|
-
* break the spawn path. */
|
|
57
|
-
export function loadBudget() {
|
|
58
|
-
const path = configFile();
|
|
59
|
-
if (!existsSync(path))
|
|
60
|
-
return { ...DEFAULT_CONFIG };
|
|
61
|
-
try {
|
|
62
|
-
const raw = readFileSync(path, 'utf-8');
|
|
63
|
-
const parsed = parseYaml(raw);
|
|
64
|
-
const cfg = { ...DEFAULT_CONFIG };
|
|
65
|
-
if ('daily_usd' in parsed) {
|
|
66
|
-
const n = Number(parsed.daily_usd);
|
|
67
|
-
cfg.dailyUsd = Number.isFinite(n) && n > 0 ? n : null;
|
|
68
|
-
}
|
|
69
|
-
if ('warn_at_pct' in parsed) {
|
|
70
|
-
const n = Number(parsed.warn_at_pct);
|
|
71
|
-
cfg.warnAtPct = Number.isFinite(n) && n > 0 && n <= 100 ? n : DEFAULT_CONFIG.warnAtPct;
|
|
72
|
-
}
|
|
73
|
-
return cfg;
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return { ...DEFAULT_CONFIG };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/** Persist the budget config. Creates ~/.codehere/ if missing. Comments
|
|
80
|
-
* in the emitted YAML help a non-technical user edit it later. */
|
|
81
|
-
export function saveBudget(cfg) {
|
|
82
|
-
const path = configFile();
|
|
83
|
-
const dir = dirname(path);
|
|
84
|
-
if (!existsSync(dir))
|
|
85
|
-
mkdirSync(dir, { recursive: true });
|
|
86
|
-
const lines = [
|
|
87
|
-
'# codehere daily-spend budget. Edit by hand or via `codehere budget set <amount>`.',
|
|
88
|
-
"# daily_usd: hard ceiling for today's estimated spend. Set to a number to enable.",
|
|
89
|
-
'# Leave commented out / omit to disable budget enforcement entirely.',
|
|
90
|
-
];
|
|
91
|
-
if (cfg.dailyUsd !== null) {
|
|
92
|
-
lines.push(`daily_usd: ${cfg.dailyUsd}`);
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
lines.push('# daily_usd: 5.00');
|
|
96
|
-
}
|
|
97
|
-
lines.push(`warn_at_pct: ${cfg.warnAtPct}`);
|
|
98
|
-
lines.push('');
|
|
99
|
-
writeFileAtomic(path, lines.join('\n'));
|
|
100
|
-
}
|
|
101
|
-
/** True when today's estimated spend has hit or exceeded the daily ceiling.
|
|
102
|
-
* Returns false when no budget is set (no enforcement). */
|
|
103
|
-
export function isOverBudget(spentTodayUsd, cfg = loadBudget()) {
|
|
104
|
-
if (cfg.dailyUsd === null)
|
|
105
|
-
return false;
|
|
106
|
-
return spentTodayUsd >= cfg.dailyUsd;
|
|
107
|
-
}
|
|
108
|
-
/** Percentage of today's budget consumed (0-100, or null when no budget set). */
|
|
109
|
-
export function budgetUsedPct(spentTodayUsd, cfg = loadBudget()) {
|
|
110
|
-
if (cfg.dailyUsd === null || cfg.dailyUsd === 0)
|
|
111
|
-
return null;
|
|
112
|
-
return Math.min(100, Math.round((spentTodayUsd / cfg.dailyUsd) * 100));
|
|
113
|
-
}
|
|
114
|
-
//# sourceMappingURL=budget-config.js.map
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cost Router
|
|
3
|
-
*
|
|
4
|
-
* Given a trust score and task complexity, recommends the cheapest model
|
|
5
|
-
* that's safe to use. The 42x cost difference between Haiku and Opus means
|
|
6
|
-
* routing decisions have real financial impact at scale.
|
|
7
|
-
*
|
|
8
|
-
* Model tiers (approximate $/M output tokens, 2026):
|
|
9
|
-
* fast tier (claude-haiku-4-5) $1.25 — cheap, good for simple tasks
|
|
10
|
-
* balanced tier (claude-sonnet-4-6) $15.00 — good for most tasks
|
|
11
|
-
* max tier (claude-fable-5) $50.00 — strongest (Anthropic's current flagship,
|
|
12
|
-
* Mythos-class; 1M ctx); use sparingly
|
|
13
|
-
*
|
|
14
|
-
* Routing logic:
|
|
15
|
-
* trust >= 90, simple task → fast tier (60x cheaper than max tier)
|
|
16
|
-
* trust >= 75, any task → balanced tier (5x cheaper than max tier)
|
|
17
|
-
* trust < 75, or critical → balanced tier with human review flag
|
|
18
|
-
* critical findings present → block before reaching model
|
|
19
|
-
*/
|
|
20
|
-
export const MODEL_COSTS = {
|
|
21
|
-
haiku: 1.25,
|
|
22
|
-
sonnet: 15.0,
|
|
23
|
-
opus: 50.0,
|
|
24
|
-
};
|
|
25
|
-
// NB: the `opus` key is the internal name for the MAX / strongest tier. Its model
|
|
26
|
-
// is now Claude Fable 5 (Anthropic's current flagship, released 2026-06-09 —
|
|
27
|
-
// stronger than Opus 4.8, which it falls back to on high-risk queries). User-facing
|
|
28
|
-
// surfaces show the tier label ("max tier") + this modelId, so the key name is
|
|
29
|
-
// internal-only.
|
|
30
|
-
const MODEL_IDS = {
|
|
31
|
-
haiku: 'claude-haiku-4-5-20251001',
|
|
32
|
-
sonnet: 'claude-sonnet-4-6',
|
|
33
|
-
opus: 'claude-fable-5',
|
|
34
|
-
};
|
|
35
|
-
const TIER_LABELS = {
|
|
36
|
-
haiku: 'fast tier',
|
|
37
|
-
sonnet: 'balanced tier',
|
|
38
|
-
opus: 'max tier',
|
|
39
|
-
};
|
|
40
|
-
function savingsLabel(chosen) {
|
|
41
|
-
if (chosen === 'opus')
|
|
42
|
-
return 'max tier (baseline)';
|
|
43
|
-
const ratio = MODEL_COSTS.opus / MODEL_COSTS[chosen];
|
|
44
|
-
return `${TIER_LABELS[chosen]} · ${Math.round(ratio)}x cheaper than max tier`;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Recommend the optimal model for a given trust score and task complexity.
|
|
48
|
-
*/
|
|
49
|
-
export function recommendModel(trustScore, complexity = 'standard', criticalFindings = 0) {
|
|
50
|
-
// Critical findings → require human review regardless of model
|
|
51
|
-
if (criticalFindings > 0) {
|
|
52
|
-
return {
|
|
53
|
-
model: 'sonnet',
|
|
54
|
-
modelId: MODEL_IDS.sonnet,
|
|
55
|
-
reason: `${criticalFindings} critical finding${criticalFindings !== 1 ? 's' : ''} — human review required before applying any edit`,
|
|
56
|
-
costPer1MTokens: MODEL_COSTS.sonnet,
|
|
57
|
-
savingsVsOpus: savingsLabel('sonnet'),
|
|
58
|
-
humanReview: true,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
// High-trust simple tasks → Haiku
|
|
62
|
-
if (trustScore >= 90 && complexity === 'simple') {
|
|
63
|
-
return {
|
|
64
|
-
model: 'haiku',
|
|
65
|
-
modelId: MODEL_IDS.haiku,
|
|
66
|
-
reason: `Trust ${trustScore}/100 · simple task — safe for fastest, cheapest model`,
|
|
67
|
-
costPer1MTokens: MODEL_COSTS.haiku,
|
|
68
|
-
savingsVsOpus: savingsLabel('haiku'),
|
|
69
|
-
humanReview: false,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
// Good trust, standard task → Sonnet
|
|
73
|
-
if (trustScore >= 75) {
|
|
74
|
-
return {
|
|
75
|
-
model: 'sonnet',
|
|
76
|
-
modelId: MODEL_IDS.sonnet,
|
|
77
|
-
reason: `Trust ${trustScore}/100 · ${complexity} task — Sonnet is the right balance`,
|
|
78
|
-
costPer1MTokens: MODEL_COSTS.sonnet,
|
|
79
|
-
savingsVsOpus: savingsLabel('sonnet'),
|
|
80
|
-
humanReview: false,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
// Low trust → Sonnet with human review
|
|
84
|
-
return {
|
|
85
|
-
model: 'sonnet',
|
|
86
|
-
modelId: MODEL_IDS.sonnet,
|
|
87
|
-
reason: `Trust ${trustScore}/100 — lower confidence; recommend reviewing the output`,
|
|
88
|
-
costPer1MTokens: MODEL_COSTS.sonnet,
|
|
89
|
-
savingsVsOpus: savingsLabel('sonnet'),
|
|
90
|
-
humanReview: true,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
// MED #29 (atomic audit) · estimateMonthlySavings deleted: a pure projection
|
|
94
|
-
// with zero production callers whose output ("~83% savings") had been quoted
|
|
95
|
-
// as a shipped outcome. The honest claim now lives in PRODUCT_STATE (🟡,
|
|
96
|
-
// projection); the budget gate carries the cost-bounded pillar.
|
|
97
|
-
//# sourceMappingURL=cost-router.js.map
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v0.6 S3 slice 1 — spend-today aggregator.
|
|
3
|
-
*
|
|
4
|
-
* Reads ~/.codehere/sessions.jsonl (the same append-only log session-persistence
|
|
5
|
-
* writes), filters completed records to today (local timezone), and estimates
|
|
6
|
-
* dollar spend per agent based on token usage × per-tier output-rate ceilings.
|
|
7
|
-
*
|
|
8
|
-
* Why · claim_market_position v3 lists "cost-bounded execution" as one of the
|
|
9
|
-
* few capture-strategy edges still possibly open. Demonstrating cost awareness
|
|
10
|
-
* starts here: surface what the user has actually spent today across all spawned
|
|
11
|
-
* agents, then (slice 2) enforce a per-day ceiling.
|
|
12
|
-
*
|
|
13
|
-
* Why estimates not exact · codehere doesn't see provider invoices. Adapters
|
|
14
|
-
* report token counts via TaskArtifacts.usage; we multiply by published tier
|
|
15
|
-
* ceilings. Per feedback_overconfidence_guard.md, copy frames this as
|
|
16
|
-
* "estimate" and points to vendor dashboards as authoritative.
|
|
17
|
-
*
|
|
18
|
-
* Cost table · output-tier ceilings (input is rougher; we use a 5x ratio).
|
|
19
|
-
* All values 2026 published rates. When a model isn't surfaced in usage,
|
|
20
|
-
* default to the agent's typical tier.
|
|
21
|
-
*/
|
|
22
|
-
import { existsSync, readFileSync } from 'fs';
|
|
23
|
-
import { homedir } from 'os';
|
|
24
|
-
import { basename, join } from 'path';
|
|
25
|
-
/** Resolved at call time, not module load, so test setups that swap $HOME
|
|
26
|
-
* after import still get a fresh path on each invocation. */
|
|
27
|
-
function sessionsFile() {
|
|
28
|
-
return join(process.env.CODEHERE_HOME || homedir(), '.codehere', 'sessions.jsonl');
|
|
29
|
-
}
|
|
30
|
-
/** USD per million OUTPUT tokens at the agent's typical 2026 tier. Input is
|
|
31
|
-
* estimated at 1/5 of output rate (Anthropic + OpenAI both publish ~5x
|
|
32
|
-
* output:input ratios on their flagship coding tiers). Cache reads ~1/10
|
|
33
|
-
* of input. Free-tier agents → 0.
|
|
34
|
-
*
|
|
35
|
-
* Conservative-ceiling philosophy: better to over-estimate spend than
|
|
36
|
-
* under-promise and miss a real bill. */
|
|
37
|
-
export const COST_PER_M_OUTPUT_TOKENS_USD = {
|
|
38
|
-
'claude-code': 15.0, // Sonnet 4.6 baseline
|
|
39
|
-
codex: 10.0, // GPT-5 / codex baseline
|
|
40
|
-
aider: 15.0, // typically Sonnet
|
|
41
|
-
gemini: 0, // free tier (Google AI Studio)
|
|
42
|
-
opencode: 0, // typically free local / NIM-routed
|
|
43
|
-
conductor: 15.0, // routes to Claude Code by default
|
|
44
|
-
};
|
|
45
|
-
const INPUT_RATE_RATIO = 0.2; // input ≈ 1/5 of output
|
|
46
|
-
const CACHE_READ_RATIO = 0.1; // cache reads ≈ 1/10 of input (so 1/50 output)
|
|
47
|
-
/** Per-MODEL published rates (USD per million tokens), 2026. Matched by substring
|
|
48
|
-
* so version suffixes resolve (claude-opus-4-x, gpt-5-codex, gemini-2.5-pro).
|
|
49
|
-
* This is what makes an Opus run cost more than a Haiku run instead of one flat
|
|
50
|
-
* per-agent tier, and prices a paid model even when the agent's default is free.
|
|
51
|
-
* Still an estimate, not an invoice — see the file header. */
|
|
52
|
-
const MODEL_RATES = [
|
|
53
|
-
// Anthropic — verified against platform.claude.com rate card (2026-07). Standard
|
|
54
|
-
// (post-introductory) rates; Sonnet 5's Aug-2026 intro price is lower, so the
|
|
55
|
-
// standard rate keeps the estimate conservative rather than under-billing.
|
|
56
|
-
{ match: /fable|mythos/i, inputUsd: 10, outputUsd: 50 }, // Claude 5 flagship tier
|
|
57
|
-
{ match: /opus/i, inputUsd: 5, outputUsd: 25 },
|
|
58
|
-
{ match: /sonnet/i, inputUsd: 3, outputUsd: 15 },
|
|
59
|
-
{ match: /haiku/i, inputUsd: 1, outputUsd: 5 },
|
|
60
|
-
{ match: /gpt-5|codex/i, inputUsd: 1.25, outputUsd: 10 },
|
|
61
|
-
{ match: /gemini.*(2\.5.*pro|1\.5.*pro|-pro)/i, inputUsd: 1.25, outputUsd: 10 }, // paid Gemini Pro tiers
|
|
62
|
-
];
|
|
63
|
-
/** Self-hosted / local backends cost nothing per token — never bill them at a
|
|
64
|
-
* cloud tier just because the agent (e.g. aider) usually runs on a paid model. */
|
|
65
|
-
const LOCAL_MODEL = /(?:^|[/:])(?:ollama(?:_chat)?|lmstudio|lm-studio|llama[._-]?cpp|local)\b/i;
|
|
66
|
-
export function modelRate(model) {
|
|
67
|
-
if (!model)
|
|
68
|
-
return undefined;
|
|
69
|
-
for (const r of MODEL_RATES)
|
|
70
|
-
if (r.match.test(model))
|
|
71
|
-
return { inputUsd: r.inputUsd, outputUsd: r.outputUsd };
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Estimate dollar cost for one session's token usage. Per-model rates when the
|
|
76
|
-
* adapter surfaced the real model (accurate input AND output); otherwise the
|
|
77
|
-
* agent's typical output tier (input ≈ 1/5). Returns 0 when neither is priced.
|
|
78
|
-
*/
|
|
79
|
-
export function estimateCostUsd(agent, usage) {
|
|
80
|
-
if (!usage)
|
|
81
|
-
return 0;
|
|
82
|
-
const tokens = {
|
|
83
|
-
input: Math.max(0, usage.inputTokens || 0),
|
|
84
|
-
output: Math.max(0, usage.outputTokens || 0),
|
|
85
|
-
cacheRead: Math.max(0, usage.cacheReadTokens || 0),
|
|
86
|
-
cacheCreate: Math.max(0, usage.cacheCreationTokens || 0),
|
|
87
|
-
};
|
|
88
|
-
// Local/self-hosted models are free to call — don't bill them at cloud rates.
|
|
89
|
-
// EXCEPT ollama's `:cloud`-tagged models: no local weights, proxied to Ollama's
|
|
90
|
-
// billed cloud — "ollama/glm-5.2:cloud" must NOT be recorded as $0.
|
|
91
|
-
if (usage.model && LOCAL_MODEL.test(usage.model) && !/:cloud$/i.test(usage.model))
|
|
92
|
-
return 0;
|
|
93
|
-
// Per-model when the real model is known — also prices a paid model whose agent
|
|
94
|
-
// default tier is 0 (closes the gemini/opencode $0-on-a-paid-backend hole).
|
|
95
|
-
const mr = modelRate(usage.model);
|
|
96
|
-
if (mr) {
|
|
97
|
-
return ((tokens.input / 1_000_000) * mr.inputUsd +
|
|
98
|
-
(tokens.output / 1_000_000) * mr.outputUsd +
|
|
99
|
-
(tokens.cacheRead / 1_000_000) * (mr.inputUsd * CACHE_READ_RATIO) +
|
|
100
|
-
(tokens.cacheCreate / 1_000_000) * mr.inputUsd);
|
|
101
|
-
}
|
|
102
|
-
// Fallback: the agent's typical tier. No regression for sessions without a model.
|
|
103
|
-
const outputRate = COST_PER_M_OUTPUT_TOKENS_USD[agent];
|
|
104
|
-
if (outputRate === undefined || outputRate === 0)
|
|
105
|
-
return 0;
|
|
106
|
-
const inputRate = outputRate * INPUT_RATE_RATIO;
|
|
107
|
-
return ((tokens.input / 1_000_000) * inputRate +
|
|
108
|
-
(tokens.output / 1_000_000) * outputRate +
|
|
109
|
-
(tokens.cacheRead / 1_000_000) * (inputRate * CACHE_READ_RATIO) +
|
|
110
|
-
(tokens.cacheCreate / 1_000_000) * inputRate);
|
|
111
|
-
}
|
|
112
|
-
/** Return the millisecond timestamp for the start of "today" in local time. */
|
|
113
|
-
function startOfTodayLocalMs(now = new Date()) {
|
|
114
|
-
const d = new Date(now);
|
|
115
|
-
d.setHours(0, 0, 0, 0);
|
|
116
|
-
return d.getTime();
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Walk sessions.jsonl, sum estimated USD spend for sessions whose
|
|
120
|
-
* `completedAt` falls within today (local). Best-effort: missing file =
|
|
121
|
-
* empty report; parse errors per-line ignored. Pairs spawned + completed
|
|
122
|
-
* by id so we know which agent spent what.
|
|
123
|
-
*/
|
|
124
|
-
export function computeSpendToday(now = new Date()) {
|
|
125
|
-
const cutoff = startOfTodayLocalMs(now);
|
|
126
|
-
const empty = {
|
|
127
|
-
totalUsd: 0,
|
|
128
|
-
tokens: { input: 0, output: 0, cache: 0 },
|
|
129
|
-
sessionCount: 0,
|
|
130
|
-
byAgent: {},
|
|
131
|
-
byProject: {},
|
|
132
|
-
practice: { usd: 0, sessions: 0 },
|
|
133
|
-
windowStartedAt: cutoff,
|
|
134
|
-
};
|
|
135
|
-
if (!existsSync(sessionsFile()))
|
|
136
|
-
return empty;
|
|
137
|
-
let content;
|
|
138
|
-
try {
|
|
139
|
-
content = readFileSync(sessionsFile(), 'utf-8');
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return empty;
|
|
143
|
-
}
|
|
144
|
-
// Pair spawned (carries agent + cwd + mockRun) with completed (carries usage + completedAt).
|
|
145
|
-
const spawnedAgent = new Map();
|
|
146
|
-
const spawnedProject = new Map();
|
|
147
|
-
const spawnedMock = new Set();
|
|
148
|
-
const completedById = new Map();
|
|
149
|
-
const report = { ...empty, byAgent: {}, byProject: {}, practice: { usd: 0, sessions: 0 } };
|
|
150
|
-
for (const line of content.split('\n')) {
|
|
151
|
-
const trimmed = line.trim();
|
|
152
|
-
if (!trimmed)
|
|
153
|
-
continue;
|
|
154
|
-
let rec;
|
|
155
|
-
try {
|
|
156
|
-
rec = JSON.parse(trimmed);
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
if (rec.t === 'spawned' && rec.id && rec.agent) {
|
|
162
|
-
spawnedAgent.set(rec.id, rec.agent);
|
|
163
|
-
if (rec.cwd)
|
|
164
|
-
spawnedProject.set(rec.id, basename(rec.cwd));
|
|
165
|
-
if (rec.mockRun === true)
|
|
166
|
-
spawnedMock.add(rec.id);
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
if (rec.t !== 'completed' || !rec.id)
|
|
170
|
-
continue;
|
|
171
|
-
if (typeof rec.completedAt !== 'number' || rec.completedAt < cutoff)
|
|
172
|
-
continue;
|
|
173
|
-
if (!spawnedAgent.has(rec.id))
|
|
174
|
-
continue;
|
|
175
|
-
// ONE session = ONE count: a session legitimately carries MULTIPLE
|
|
176
|
-
// completed rows (the exit handler writes one, the async test-observation
|
|
177
|
-
// re-persists a corrected one — loadRecent dedups by id, later wins).
|
|
178
|
-
// Summing every row DOUBLED cost + session counts on the team record
|
|
179
|
-
// (found live 2026-07-07: 2 real runs read as 4 sessions / 2x spend).
|
|
180
|
-
// Same semantics as the reader: keep the LAST row per id.
|
|
181
|
-
completedById.set(rec.id, rec);
|
|
182
|
-
}
|
|
183
|
-
for (const [id, rec] of completedById) {
|
|
184
|
-
const agent = spawnedAgent.get(id);
|
|
185
|
-
const usage = rec.artifacts?.usage;
|
|
186
|
-
const usd = estimateCostUsd(agent, usage);
|
|
187
|
-
// Practice runs divert to their own bucket — a mock's fabricated "cost"
|
|
188
|
-
// never joins the real totals, the by-* breakdowns, or the budget's view.
|
|
189
|
-
if (spawnedMock.has(id)) {
|
|
190
|
-
report.practice.usd += usd;
|
|
191
|
-
report.practice.sessions += 1;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
report.sessionCount += 1;
|
|
195
|
-
if (usage) {
|
|
196
|
-
report.tokens.input += usage.inputTokens || 0;
|
|
197
|
-
report.tokens.output += usage.outputTokens || 0;
|
|
198
|
-
report.tokens.cache += (usage.cacheReadTokens || 0) + (usage.cacheCreationTokens || 0);
|
|
199
|
-
}
|
|
200
|
-
report.totalUsd += usd;
|
|
201
|
-
const cur = report.byAgent[agent] || { usd: 0, sessions: 0 };
|
|
202
|
-
cur.usd += usd;
|
|
203
|
-
cur.sessions += 1;
|
|
204
|
-
report.byAgent[agent] = cur;
|
|
205
|
-
const project = spawnedProject.get(id) || 'unknown';
|
|
206
|
-
const pcur = report.byProject[project] || { usd: 0, sessions: 0 };
|
|
207
|
-
pcur.usd += usd;
|
|
208
|
-
pcur.sessions += 1;
|
|
209
|
-
report.byProject[project] = pcur;
|
|
210
|
-
}
|
|
211
|
-
// Round totals to 4 decimals (1/100 of a cent) — final display rounds to 2.
|
|
212
|
-
report.totalUsd = Math.round(report.totalUsd * 10_000) / 10_000;
|
|
213
|
-
for (const k of Object.keys(report.byAgent)) {
|
|
214
|
-
report.byAgent[k].usd = Math.round(report.byAgent[k].usd * 10_000) / 10_000;
|
|
215
|
-
}
|
|
216
|
-
for (const k of Object.keys(report.byProject)) {
|
|
217
|
-
report.byProject[k].usd = Math.round(report.byProject[k].usd * 10_000) / 10_000;
|
|
218
|
-
}
|
|
219
|
-
report.practice.usd = Math.round(report.practice.usd * 10_000) / 10_000;
|
|
220
|
-
return report;
|
|
221
|
-
}
|
|
222
|
-
//# sourceMappingURL=spend-aggregator.js.map
|