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.
Files changed (163) hide show
  1. package/README.md +35 -193
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. 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