@juzser/blacksmith 0.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/.claude/agents/auditor.md +146 -0
- package/.claude/agents/coder.md +164 -0
- package/.claude/agents/grader.md +110 -0
- package/.claude/agents/merger.md +107 -0
- package/.claude/agents/planner.md +92 -0
- package/.claude/agents/researcher.md +105 -0
- package/.claude/agents/reviewer.md +171 -0
- package/.claude/agents/scribe.md +72 -0
- package/.claude/agents/security-reviewer.md +130 -0
- package/.claude/agents/spec-reviewer.md +215 -0
- package/.claude/agents/tester.md +121 -0
- package/.claude/agents/uiux.md +126 -0
- package/.claude/agents/verifier.md +103 -0
- package/.claude/agents/wave-runner.md +74 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/factory/orchestrator/dist/actors.js +46 -0
- package/factory/orchestrator/dist/agents-registry.js +308 -0
- package/factory/orchestrator/dist/args.js +84 -0
- package/factory/orchestrator/dist/artifacts.js +111 -0
- package/factory/orchestrator/dist/attribution.js +160 -0
- package/factory/orchestrator/dist/audit.js +812 -0
- package/factory/orchestrator/dist/autonomy.js +151 -0
- package/factory/orchestrator/dist/budgetAlarm.js +366 -0
- package/factory/orchestrator/dist/budgets.js +196 -0
- package/factory/orchestrator/dist/claims.js +633 -0
- package/factory/orchestrator/dist/cli.js +3185 -0
- package/factory/orchestrator/dist/commit.js +152 -0
- package/factory/orchestrator/dist/coverage.js +188 -0
- package/factory/orchestrator/dist/crossFinding.js +479 -0
- package/factory/orchestrator/dist/crosscheck.js +396 -0
- package/factory/orchestrator/dist/daemon.js +707 -0
- package/factory/orchestrator/dist/db/projector.js +1317 -0
- package/factory/orchestrator/dist/db/queries.js +1714 -0
- package/factory/orchestrator/dist/db/schema.js +305 -0
- package/factory/orchestrator/dist/delegation.js +400 -0
- package/factory/orchestrator/dist/diffstat.js +164 -0
- package/factory/orchestrator/dist/dispatchAudit.js +275 -0
- package/factory/orchestrator/dist/dotenv.js +72 -0
- package/factory/orchestrator/dist/effort.js +202 -0
- package/factory/orchestrator/dist/epic.js +1018 -0
- package/factory/orchestrator/dist/epicWidth.js +238 -0
- package/factory/orchestrator/dist/errorIssues.js +249 -0
- package/factory/orchestrator/dist/errors.js +11 -0
- package/factory/orchestrator/dist/escalation.js +500 -0
- package/factory/orchestrator/dist/events.js +1015 -0
- package/factory/orchestrator/dist/findingAge.js +91 -0
- package/factory/orchestrator/dist/findingContext.js +103 -0
- package/factory/orchestrator/dist/findings.js +1156 -0
- package/factory/orchestrator/dist/gate.js +911 -0
- package/factory/orchestrator/dist/gh.js +146 -0
- package/factory/orchestrator/dist/git.js +87 -0
- package/factory/orchestrator/dist/goalCheck.js +380 -0
- package/factory/orchestrator/dist/graph.js +61 -0
- package/factory/orchestrator/dist/hookDecision.js +106 -0
- package/factory/orchestrator/dist/immutability.js +171 -0
- package/factory/orchestrator/dist/impact.js +280 -0
- package/factory/orchestrator/dist/integration.js +141 -0
- package/factory/orchestrator/dist/judgePreflight.js +155 -0
- package/factory/orchestrator/dist/judges.js +310 -0
- package/factory/orchestrator/dist/lessonAudit.js +399 -0
- package/factory/orchestrator/dist/lessons.js +1329 -0
- package/factory/orchestrator/dist/mcp.js +555 -0
- package/factory/orchestrator/dist/paths.js +82 -0
- package/factory/orchestrator/dist/plan.js +502 -0
- package/factory/orchestrator/dist/planQuorum.js +421 -0
- package/factory/orchestrator/dist/policy.js +1308 -0
- package/factory/orchestrator/dist/policyHook.js +28 -0
- package/factory/orchestrator/dist/preconditions.js +47 -0
- package/factory/orchestrator/dist/projects.js +134 -0
- package/factory/orchestrator/dist/prompts.js +57 -0
- package/factory/orchestrator/dist/provenance.js +201 -0
- package/factory/orchestrator/dist/providers/api-transport.js +117 -0
- package/factory/orchestrator/dist/providers/cli-transport.js +208 -0
- package/factory/orchestrator/dist/providers/index.js +54 -0
- package/factory/orchestrator/dist/providers/schema-validate.js +152 -0
- package/factory/orchestrator/dist/providers/types.js +49 -0
- package/factory/orchestrator/dist/queue.js +263 -0
- package/factory/orchestrator/dist/quorum.js +347 -0
- package/factory/orchestrator/dist/quorumEscalations.js +132 -0
- package/factory/orchestrator/dist/results.js +65 -0
- package/factory/orchestrator/dist/roadmap.js +437 -0
- package/factory/orchestrator/dist/runtime.js +86 -0
- package/factory/orchestrator/dist/sameMistakeKpi.js +243 -0
- package/factory/orchestrator/dist/sandbox.js +165 -0
- package/factory/orchestrator/dist/scaffold.js +373 -0
- package/factory/orchestrator/dist/scheduler.js +408 -0
- package/factory/orchestrator/dist/schemas.js +195 -0
- package/factory/orchestrator/dist/security.js +222 -0
- package/factory/orchestrator/dist/severity.js +293 -0
- package/factory/orchestrator/dist/spec.js +376 -0
- package/factory/orchestrator/dist/specChange.js +497 -0
- package/factory/orchestrator/dist/stack.js +324 -0
- package/factory/orchestrator/dist/symbols.js +966 -0
- package/factory/orchestrator/dist/taskEvents.js +399 -0
- package/factory/orchestrator/dist/taskId.js +126 -0
- package/factory/orchestrator/dist/taxonomy.js +113 -0
- package/factory/orchestrator/dist/testSelect.js +129 -0
- package/factory/orchestrator/dist/testerAudit.js +264 -0
- package/factory/orchestrator/dist/testgate.js +171 -0
- package/factory/orchestrator/dist/usage.js +874 -0
- package/factory/orchestrator/dist/waivers.js +228 -0
- package/factory/orchestrator/dist/waveBudget.js +223 -0
- package/factory/orchestrator/dist/waveConcurrency.js +250 -0
- package/factory/orchestrator/dist/waveNext.js +210 -0
- package/factory/orchestrator/dist/waveSchedule.js +146 -0
- package/factory/orchestrator/dist/worktree.js +207 -0
- package/factory/orchestrator/drizzle/0000_broken_rockslide.sql +183 -0
- package/factory/orchestrator/drizzle/0001_luxuriant_zaladane.sql +1 -0
- package/factory/orchestrator/drizzle/0002_yellow_wild_pack.sql +8 -0
- package/factory/orchestrator/drizzle/0003_damp_riptide.sql +12 -0
- package/factory/orchestrator/drizzle/0004_broad_lady_vermin.sql +1 -0
- package/factory/orchestrator/drizzle/0005_romantic_jocasta.sql +2 -0
- package/factory/orchestrator/drizzle/0006_bent_madelyne_pryor.sql +16 -0
- package/factory/orchestrator/drizzle/0007_overjoyed_goliath.sql +1 -0
- package/factory/orchestrator/drizzle/0008_smart_iron_patriot.sql +2 -0
- package/factory/orchestrator/drizzle/0009_agents_epic_id.sql +2 -0
- package/factory/orchestrator/drizzle/0010_milestone_kind.sql +1 -0
- package/factory/orchestrator/drizzle/meta/_journal.json +83 -0
- package/factory/policies/budgets.yml +262 -0
- package/factory/policies/crosscheck.yml +308 -0
- package/factory/policies/delegation.yml +44 -0
- package/factory/policies/effort.yml +142 -0
- package/factory/policies/guardrails.yml +306 -0
- package/factory/policies/lessons.md +239 -0
- package/factory/policies/scheduler.yml +90 -0
- package/factory/policies/sensitive-paths.yml +148 -0
- package/factory/policies/severity.yml +83 -0
- package/factory/policies/stack.yml +60 -0
- package/factory/policies/taxonomy.yml +340 -0
- package/factory/policies/worktree.yml +95 -0
- package/factory/scaffold/base/.github/workflows/ci.yml +34 -0
- package/factory/scaffold/base/.gitignore.tmpl +11 -0
- package/factory/scaffold/base/AGENTS.md +33 -0
- package/factory/scaffold/base/README.md +25 -0
- package/factory/scaffold/base/biome.json.tmpl +30 -0
- package/factory/scaffold/base/package.json +25 -0
- package/factory/scaffold/base/pnpm-workspace.yaml.tmpl +16 -0
- package/factory/scaffold/base/src/index.ts +4 -0
- package/factory/scaffold/base/test/index.test.ts +8 -0
- package/factory/scaffold/base/tsconfig.json +20 -0
- package/factory/scaffold/base/tsconfig.test.json +9 -0
- package/factory/scaffold/base/vitest.config.ts +29 -0
- package/factory/scaffold/mcp/mcp.manifest.json +22 -0
- package/factory/scaffold/mcp/package.fragment.json +9 -0
- package/factory/scaffold/mcp/src/mcp/README.md +63 -0
- package/factory/scaffold/mcp/src/mcp/guard.ts +110 -0
- package/factory/scaffold/mcp/src/mcp/redact.ts +69 -0
- package/factory/scaffold/mcp/src/mcp/server.ts +55 -0
- package/factory/scaffold/mcp/src/mcp/tools/health.ts +60 -0
- package/factory/scaffold/mcp/test/mcp/guard.test.ts +108 -0
- package/factory/scaffold/mcp/test/mcp/redact.test.ts +61 -0
- package/factory/scaffold/mcp/test/mcp/surface.test.ts +100 -0
- package/factory/scaffold/ui/index.html +12 -0
- package/factory/scaffold/ui/package.fragment.json +13 -0
- package/factory/scaffold/ui/src/App.vue +10 -0
- package/factory/scaffold/ui/src/main.ts +5 -0
- package/factory/scaffold/ui/src/shims.d.ts +11 -0
- package/factory/scaffold/ui/vite.config.ts +20 -0
- package/factory/scaffold/ui-tailwind/package.fragment.json +6 -0
- package/factory/scaffold/ui-tailwind/vite.config.ts +26 -0
- package/factory/specs/schema/event.schema.json +82 -0
- package/factory/specs/schema/finding-evidence.schema.json +45 -0
- package/factory/specs/schema/finding.schema.json +115 -0
- package/factory/specs/schema/grader-verdict.schema.json +50 -0
- package/factory/specs/schema/judge-verdict.schema.json +19 -0
- package/factory/specs/schema/lesson.schema.json +85 -0
- package/factory/specs/schema/mcp-manifest.schema.json +230 -0
- package/factory/specs/schema/result.schema.json +81 -0
- package/factory/specs/schema/spec-change-request.schema.json +46 -0
- package/factory/specs/schema/task-spec.schema.json +134 -0
- package/package.json +98 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// factory/policies/budgets.yml loader. Same parse-with-defaults pattern as
|
|
2
|
+
// crosscheck.ts/scheduler.ts: a RawXYaml on-disk snake_case shape mapped onto a
|
|
3
|
+
// typed camelCase policy.
|
|
4
|
+
//
|
|
5
|
+
// `epic.cap_tokens` is the field src/planQuorum.ts's budget trigger
|
|
6
|
+
// (crosscheck.yml plan_quorum.budget_ratio) reads, via the sum of *declared*
|
|
7
|
+
// task budgets. The per-role caps below are modeled as of P9-18 so the gate can
|
|
8
|
+
// compare a declared task budget against what the task actually spent — see
|
|
9
|
+
// checkTaskBudget. `escalation_ladder` is parsed as of P9-32 and asserted
|
|
10
|
+
// against the event log by src/escalation.ts. `context_window` and
|
|
11
|
+
// `effort_scaling` are still prompt-level: agents read them from their own
|
|
12
|
+
// templates, and nothing here parses them.
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { parse as parseYaml } from 'yaml';
|
|
15
|
+
import { SmithError } from './errors.js';
|
|
16
|
+
import { BUDGETS_POLICY_PATH } from './paths.js';
|
|
17
|
+
export class BudgetError extends SmithError {
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Deliberately NOT the number budgets.yml declares (4,000,000 since
|
|
21
|
+
* 2026-08-11) — the one default here that does not mirror the policy file.
|
|
22
|
+
*
|
|
23
|
+
* The other DEFAULT_* constants below match budgets.yml because nobody has
|
|
24
|
+
* ever had cause to diverge them. This one has cause: the repo's cap was
|
|
25
|
+
* raised by an operator decision sized from a single dogfood epic that
|
|
26
|
+
* measured 1,529,963 tokens for two of four tasks, a figure inflated by
|
|
27
|
+
* rework `smith escalation check` recorded as ladder violations. That is a
|
|
28
|
+
* judgement about this repo's epics, not a floor to hand a project that
|
|
29
|
+
* shipped no policy at all — such a project gets the conservative number and
|
|
30
|
+
* an alarm that fires early, which is the failure direction to prefer.
|
|
31
|
+
*
|
|
32
|
+
* Do not "fix" the mismatch by syncing them.
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_EPIC_CAP_TOKENS = 2_000_000;
|
|
35
|
+
const DEFAULT_EPIC_ALARM_RATIO = 0.7;
|
|
36
|
+
const DEFAULT_CODER_CAP_TOKENS = 150_000;
|
|
37
|
+
const DEFAULT_CODER_CAP_DIFF_LINES = 400;
|
|
38
|
+
const DEFAULT_RESEARCHER_CAP_TOKENS = 60_000;
|
|
39
|
+
const DEFAULT_JUDGES_CAP_TOKENS = 40_000;
|
|
40
|
+
const DEFAULT_PRE_CODE_SHARE_MAX = 0.15;
|
|
41
|
+
function optionalString(value) {
|
|
42
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The ladder in ascending threshold order.
|
|
46
|
+
*
|
|
47
|
+
* There is no default ladder. A budgets.yml that declares none yields `[]`,
|
|
48
|
+
* and src/escalation.ts reports that as unverifiable — inventing the three
|
|
49
|
+
* rungs here would let a policy file nobody wrote produce a clean audit.
|
|
50
|
+
* Rungs with no numeric threshold sort last, after every rung that has one.
|
|
51
|
+
*/
|
|
52
|
+
function parseEscalationLadder(raw) {
|
|
53
|
+
const rungs = (raw ?? []).map((entry, index) => ({
|
|
54
|
+
rung: typeof entry.rung === 'number' ? entry.rung : index + 1,
|
|
55
|
+
failedRounds: typeof entry.failed_rounds === 'number' ? entry.failed_rounds : null,
|
|
56
|
+
trigger: optionalString(entry.trigger) ?? '',
|
|
57
|
+
action: optionalString(entry.action),
|
|
58
|
+
enforce: optionalString(entry.enforce),
|
|
59
|
+
}));
|
|
60
|
+
return rungs.sort((a, b) => {
|
|
61
|
+
if (a.failedRounds === b.failedRounds)
|
|
62
|
+
return a.rung - b.rung;
|
|
63
|
+
if (a.failedRounds === null)
|
|
64
|
+
return 1;
|
|
65
|
+
if (b.failedRounds === null)
|
|
66
|
+
return -1;
|
|
67
|
+
return a.failedRounds - b.failedRounds;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Every cap, checked rather than trusted -- the same guard crosscheck.ts and
|
|
72
|
+
* scheduler.ts carry for their own policy files, and the likeliest of the
|
|
73
|
+
* three to actually be typed: `200k` and `80%` are how a human writes these
|
|
74
|
+
* numbers everywhere except here.
|
|
75
|
+
*
|
|
76
|
+
* `??` fills in on null/undefined only, so a YAML typo arrived at the
|
|
77
|
+
* comparison as itself while the declared type still said `number`. Nothing
|
|
78
|
+
* threw, and a cap that no comparison can read is not a loose cap -- it is no
|
|
79
|
+
* cap at all:
|
|
80
|
+
*
|
|
81
|
+
* - `cap_tokens: 200k` -- `10_000_000 >= '200k'` is false, so an epic ten
|
|
82
|
+
* times over budget is reported `under`.
|
|
83
|
+
* - `alarm_ratio: 80%` -- checkBudgetAlarm computes
|
|
84
|
+
* `Math.floor(capTokens * alarmRatio)`, so the alarm threshold is NaN and
|
|
85
|
+
* every comparison against it is false.
|
|
86
|
+
* - `cap_diff_lines: four hundred` -- a 99,999-line diff is under cap.
|
|
87
|
+
*
|
|
88
|
+
* The alarm case is the one that outlives its typo: NaN serialises to null,
|
|
89
|
+
* so the report records that there was no alarm threshold rather than that
|
|
90
|
+
* the policy could not be read. Same reason cli.ts refuses a `--now` it
|
|
91
|
+
* cannot parse instead of writing `Math.floor(NaN)` into an append-only log
|
|
92
|
+
* (D-209).
|
|
93
|
+
*
|
|
94
|
+
* A wrong *number* still passes: a 10-token cap stops the very first task and
|
|
95
|
+
* says so. A wrong *type* stops nothing and says nothing.
|
|
96
|
+
*/
|
|
97
|
+
const NO_CAP_AT_ALL = 'A cap the comparison cannot read is not a loose cap, it is no cap at all.';
|
|
98
|
+
function capNumber(field, value) {
|
|
99
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
100
|
+
throw new BudgetError('budgets.invalid-policy', `budgets.yml ${field} must be a finite number; got ${JSON.stringify(value)}. ${NO_CAP_AT_ALL}`, { field, value });
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Same guard as capNumber, for the one field allowed to say "no cap" rather
|
|
106
|
+
* than declare a number: `epic.max_in_flight_tasks` is null by default (see
|
|
107
|
+
* budgets.yml's own comment on the field), and null has to reach
|
|
108
|
+
* checkWaveBudget as the deliberate "no fan-out limit" it is, not as a value
|
|
109
|
+
* that failed capNumber's finite-number check. Anything else that is not a
|
|
110
|
+
* finite number — `"none"`, `80%`, a typo'd string — is the same "no cap at
|
|
111
|
+
* all" failure capNumber guards against, so it throws the same way.
|
|
112
|
+
*/
|
|
113
|
+
function capNumberOrNull(field, value) {
|
|
114
|
+
if (value === null)
|
|
115
|
+
return null;
|
|
116
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
117
|
+
throw new BudgetError('budgets.invalid-policy', `budgets.yml ${field} must be a finite number or null; got ${JSON.stringify(value)}. ${NO_CAP_AT_ALL}`, { field, value });
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
export function parseBudgetPolicy(yamlText) {
|
|
122
|
+
const doc = (parseYaml(yamlText) ?? {});
|
|
123
|
+
return {
|
|
124
|
+
epic: {
|
|
125
|
+
capTokens: capNumber('epic.cap_tokens', doc.epic?.cap_tokens ?? DEFAULT_EPIC_CAP_TOKENS),
|
|
126
|
+
alarmRatio: capNumber('epic.alarm_ratio', doc.epic?.alarm_ratio ?? DEFAULT_EPIC_ALARM_RATIO),
|
|
127
|
+
maxInFlightTasks: capNumberOrNull('epic.max_in_flight_tasks', doc.epic?.max_in_flight_tasks ?? null),
|
|
128
|
+
},
|
|
129
|
+
task: {
|
|
130
|
+
coder: {
|
|
131
|
+
capTokens: capNumber('task.coder.cap_tokens', doc.task?.coder?.cap_tokens ?? DEFAULT_CODER_CAP_TOKENS),
|
|
132
|
+
capDiffLines: capNumber('task.coder.cap_diff_lines', doc.task?.coder?.cap_diff_lines ?? DEFAULT_CODER_CAP_DIFF_LINES),
|
|
133
|
+
},
|
|
134
|
+
researcher: {
|
|
135
|
+
capTokens: capNumber('task.researcher.cap_tokens', doc.task?.researcher?.cap_tokens ?? DEFAULT_RESEARCHER_CAP_TOKENS),
|
|
136
|
+
},
|
|
137
|
+
judges: {
|
|
138
|
+
capTokens: capNumber('task.judges.cap_tokens', doc.task?.judges?.cap_tokens ?? DEFAULT_JUDGES_CAP_TOKENS),
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
preCodeBudget: {
|
|
142
|
+
shareOfEpicBudgetMax: capNumber('pre_code_budget.share_of_epic_budget_max', doc.pre_code_budget?.share_of_epic_budget_max ?? DEFAULT_PRE_CODE_SHARE_MAX),
|
|
143
|
+
},
|
|
144
|
+
escalationLadder: parseEscalationLadder(doc.escalation_ladder?.rungs),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export function loadBudgetPolicy(filePath = BUDGETS_POLICY_PATH) {
|
|
148
|
+
return parseBudgetPolicy(readFileSync(filePath, 'utf8'));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* For each field a task budget may declare, the mechanism that reads it — or
|
|
152
|
+
* `null` when nothing does.
|
|
153
|
+
*
|
|
154
|
+
* This registry exists so the answer is checkable rather than remembered.
|
|
155
|
+
* D-29's finding was that all three fields looked enforced and none were; the
|
|
156
|
+
* fix is not only to wire two of them up but to make the third's emptiness
|
|
157
|
+
* visible, so plan validation can say "you wrote down a limit nobody can
|
|
158
|
+
* apply" instead of the plan quietly implying otherwise.
|
|
159
|
+
*
|
|
160
|
+
* `max_turns` is null on purpose, and not because the wiring is pending: a turn
|
|
161
|
+
* limit has to live in the dispatch harness, and the Agent tool exposes no
|
|
162
|
+
* turns parameter (agent-interviews.md M-4, answered (c) on 2026-08-05 — the
|
|
163
|
+
* number is carried into the dispatch prompt by the /bs skill, where it is a
|
|
164
|
+
* request to the agent, not a limit on it).
|
|
165
|
+
*/
|
|
166
|
+
export const TASK_BUDGET_FIELD_READERS = Object.freeze({
|
|
167
|
+
tokens: 'gate: compared against the result’s token_usage.total_tokens',
|
|
168
|
+
diff_lines: 'gate: compared against the measured git diff (src/diffstat.ts)',
|
|
169
|
+
max_turns: null,
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* Which of the fields this budget declares have no mechanical reader. Order
|
|
173
|
+
* follows TASK_BUDGET_FIELD_READERS so the message is stable.
|
|
174
|
+
*/
|
|
175
|
+
export function unreadTaskBudgetFields(budget) {
|
|
176
|
+
return Object.keys(TASK_BUDGET_FIELD_READERS).filter((field) => budget[field] !== undefined && TASK_BUDGET_FIELD_READERS[field] === null);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Declared budget vs. what the task actually spent.
|
|
180
|
+
*
|
|
181
|
+
* A field with no measurement is skipped rather than compared against zero: an
|
|
182
|
+
* unmeasured budget and a budget that came in at nothing are different facts,
|
|
183
|
+
* and only one of them is evidence of anything. Spend exactly at the cap is
|
|
184
|
+
* within it — a cap of 400 permits 400.
|
|
185
|
+
*/
|
|
186
|
+
export function checkTaskBudget(input) {
|
|
187
|
+
const overruns = [];
|
|
188
|
+
const { budget, tokensUsed, diffLines } = input;
|
|
189
|
+
if (tokensUsed !== undefined && tokensUsed > budget.tokens) {
|
|
190
|
+
overruns.push({ field: 'tokens', cap: budget.tokens, measured: tokensUsed });
|
|
191
|
+
}
|
|
192
|
+
if (diffLines !== undefined && diffLines > budget.diff_lines) {
|
|
193
|
+
overruns.push({ field: 'diff_lines', cap: budget.diff_lines, measured: diffLines });
|
|
194
|
+
}
|
|
195
|
+
return overruns;
|
|
196
|
+
}
|