tldr-experts 0.28.0 → 0.29.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/CHANGELOG.md +257 -0
- package/README.md +2 -0
- package/dist/hooks/answer-capture.js +8 -9
- package/dist/hooks/budget-gate.js +47 -11
- package/dist/hooks/{chunk-hws2gnxj.js → chunk-0d4csx4s.js} +1 -1
- package/dist/hooks/{chunk-zrwq6r7k.js → chunk-7xascvee.js} +1 -1
- package/dist/hooks/{chunk-zvxgdssb.js → chunk-857jhjmb.js} +1 -1
- package/dist/hooks/{chunk-c62y35wh.js → chunk-erasdab5.js} +3 -3
- package/dist/hooks/{chunk-veffvzw5.js → chunk-g4d2v4jc.js} +101 -10
- package/dist/hooks/{chunk-kx45kt9n.js → chunk-ggjxes7m.js} +11 -7
- package/dist/hooks/{chunk-48ctk154.js → chunk-gr83pq7x.js} +3 -3
- package/dist/hooks/{chunk-q3d4xdhb.js → chunk-mg3aj0sa.js} +1 -1
- package/dist/hooks/{chunk-tna1bz8r.js → chunk-pne7afnf.js} +1 -1
- package/dist/hooks/{chunk-f6t1xc3f.js → chunk-qmp5p77c.js} +2 -2
- package/dist/hooks/{chunk-abswp1vt.js → chunk-t6gbx3jg.js} +2 -2
- package/dist/hooks/{chunk-xhd77h0p.js → chunk-w3d9kxcp.js} +190 -5
- package/dist/hooks/{chunk-khy21x6e.js → chunk-yanmz0vz.js} +1 -1
- package/dist/hooks/{chunk-khjm356r.js → chunk-z9hv47ps.js} +1 -1
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +9 -10
- package/dist/hooks/no-reask.js +8 -8
- package/dist/hooks/session-start.js +12 -13
- package/dist/hooks/statusline.js +8 -9
- package/dist/tldrx.js +898 -260
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/hooks/chunk-g7wz339n.js +0 -102
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GatePolicyError,
|
|
3
|
-
STAGE_TUNING_DEFAULTS,
|
|
4
|
-
parseWorkflowGates,
|
|
5
|
-
readStageTuning
|
|
6
|
-
} from "./chunk-g7wz339n.js";
|
|
7
1
|
import {
|
|
8
2
|
PROJECT_FRAMEWORK_DIR,
|
|
9
3
|
STAGES_DIR,
|
|
@@ -12,12 +6,109 @@ import {
|
|
|
12
6
|
isRecord,
|
|
13
7
|
loadWorkspace,
|
|
14
8
|
parseYaml
|
|
15
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-qmp5p77c.js";
|
|
16
10
|
|
|
17
11
|
// src/core/run/workflowPreset.ts
|
|
18
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
19
13
|
import { join } from "node:path";
|
|
20
14
|
|
|
15
|
+
// src/core/run/stagePolicy.ts
|
|
16
|
+
function validateStagePolicy(value, stageIds, issues, spec) {
|
|
17
|
+
if (value === undefined || value === null)
|
|
18
|
+
return;
|
|
19
|
+
if (!isRecord(value)) {
|
|
20
|
+
issues.push({
|
|
21
|
+
path: spec.field,
|
|
22
|
+
message: `expected a mapping of stage id -> ${spec.policies.join(" | ")}`
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const known = new Set(stageIds);
|
|
27
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
28
|
+
if (typeof raw !== "string" || !spec.policies.includes(raw)) {
|
|
29
|
+
issues.push({
|
|
30
|
+
path: `${spec.field}.${key}`,
|
|
31
|
+
message: `expected one of ${spec.policies.join(" | ")}, got ${JSON.stringify(raw)}`
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (known.size > 0 && !known.has(key)) {
|
|
35
|
+
issues.push({ path: `${spec.field}.${key}`, message: `names no stage in this run` });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/core/run/gatePolicy.ts
|
|
41
|
+
var GATE_POLICIES = ["human", "auto", "agent"];
|
|
42
|
+
var RESERVED_GATE_KEYS = ["collapse"];
|
|
43
|
+
|
|
44
|
+
class GatePolicyError extends Error {
|
|
45
|
+
}
|
|
46
|
+
function isGatePolicy(value) {
|
|
47
|
+
return typeof value === "string" && GATE_POLICIES.includes(value);
|
|
48
|
+
}
|
|
49
|
+
function parseWorkflowGates(value, stageIds, source) {
|
|
50
|
+
if (value === undefined || value === null)
|
|
51
|
+
return {};
|
|
52
|
+
if (!isRecord(value)) {
|
|
53
|
+
throw new GatePolicyError(`${source}: gates must be a mapping of stage id -> ${GATE_POLICIES.join(" | ")}`);
|
|
54
|
+
}
|
|
55
|
+
const known = new Set(stageIds);
|
|
56
|
+
const out = {};
|
|
57
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
58
|
+
if (RESERVED_GATE_KEYS.includes(key))
|
|
59
|
+
continue;
|
|
60
|
+
if (!known.has(key)) {
|
|
61
|
+
throw new GatePolicyError(`${source}: gates names '${key}', which is not one of this workflow's stages (${stageIds.join(", ")})`);
|
|
62
|
+
}
|
|
63
|
+
if (!isGatePolicy(raw)) {
|
|
64
|
+
throw new GatePolicyError(`${source}: gates.${key} must be ${GATE_POLICIES.join(" | ")}, got ${JSON.stringify(raw)}`);
|
|
65
|
+
}
|
|
66
|
+
out[key] = raw;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function validateGatesPolicy(value, stageIds, issues) {
|
|
71
|
+
validateStagePolicy(value, stageIds, issues, { field: "gates_policy", policies: GATE_POLICIES });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/core/schemas/stageTuning.ts
|
|
75
|
+
var STAGE_TUNING_DEFAULTS = {
|
|
76
|
+
attempts: 2,
|
|
77
|
+
fixlistRounds: 1,
|
|
78
|
+
reviewerShare: 0.25,
|
|
79
|
+
gateSignerShare: 0.25,
|
|
80
|
+
storyCapMultiplier: 3,
|
|
81
|
+
storyCapFloorUsd: 4
|
|
82
|
+
};
|
|
83
|
+
var STAGE_TUNING_RANGES = {
|
|
84
|
+
attempts: { key: "attempts", min: 1, max: 5, integer: true },
|
|
85
|
+
fixlistRounds: { key: "fixlist_rounds", min: 0, max: 3, integer: true },
|
|
86
|
+
reviewerShare: { key: "reviewer_share", min: 0, max: 1, integer: false },
|
|
87
|
+
gateSignerShare: { key: "gate_signer_share", min: 0, max: 1, integer: false },
|
|
88
|
+
storyCapMultiplier: { key: "story_cap_multiplier", min: 1, max: 20, integer: false },
|
|
89
|
+
storyCapFloorUsd: { key: "story_cap_floor_usd", min: 0, max: 200, integer: false }
|
|
90
|
+
};
|
|
91
|
+
var FIELDS = Object.keys(STAGE_TUNING_RANGES);
|
|
92
|
+
function inTuningRange(range, value) {
|
|
93
|
+
if (!Number.isFinite(value))
|
|
94
|
+
return false;
|
|
95
|
+
if (range.integer && !Number.isInteger(value))
|
|
96
|
+
return false;
|
|
97
|
+
return value >= range.min && value <= range.max;
|
|
98
|
+
}
|
|
99
|
+
function readStageTuning(doc) {
|
|
100
|
+
if (!isRecord(doc))
|
|
101
|
+
return STAGE_TUNING_DEFAULTS;
|
|
102
|
+
const out = { ...STAGE_TUNING_DEFAULTS };
|
|
103
|
+
for (const field of FIELDS) {
|
|
104
|
+
const range = STAGE_TUNING_RANGES[field];
|
|
105
|
+
const value = doc[range.key];
|
|
106
|
+
if (typeof value === "number" && inTuningRange(range, value))
|
|
107
|
+
out[field] = value;
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
21
112
|
// src/core/schemas/stage.ts
|
|
22
113
|
var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
23
114
|
function isEffortLevel(value) {
|
|
@@ -30,11 +121,11 @@ var PRECONDITION_TIMEOUT_S = 60;
|
|
|
30
121
|
var PHASE_IDS = ["01-what", "02-how", "03-plan", "04-build", "05-watch"];
|
|
31
122
|
var PHASE_SEGMENT_RE = /^0[1-5]-[a-z]+$/;
|
|
32
123
|
var DEFAULT_TIMEOUT_S = 7200;
|
|
33
|
-
function buildStageDefaults(root, scope) {
|
|
124
|
+
function buildStageDefaults(root, scope, stageId) {
|
|
34
125
|
const fallback = { attempts: STAGE_TUNING_DEFAULTS.attempts, timeoutS: DEFAULT_TIMEOUT_S };
|
|
35
126
|
try {
|
|
36
127
|
const preset = loadWorkflowPreset(root, scope);
|
|
37
|
-
const stage = preset.stages.find((s) => s.phase === PHASE_IDS[3]);
|
|
128
|
+
const stage = (stageId === undefined ? undefined : preset.stages.find((s) => s.id === stageId)) ?? preset.stages.find((s) => s.phase === PHASE_IDS[3]);
|
|
38
129
|
if (stage === undefined)
|
|
39
130
|
return fallback;
|
|
40
131
|
return { attempts: stage.attempts, timeoutS: stage.timeout_s };
|
|
@@ -275,4 +366,4 @@ function isString(value) {
|
|
|
275
366
|
return typeof value === "string";
|
|
276
367
|
}
|
|
277
368
|
|
|
278
|
-
export { PHASE_IDS, DEFAULT_TIMEOUT_S, buildStageDefaults };
|
|
369
|
+
export { validateStagePolicy, GATE_POLICIES, validateGatesPolicy, STAGE_TUNING_DEFAULTS, PHASE_IDS, DEFAULT_TIMEOUT_S, buildStageDefaults };
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
spentBasis,
|
|
10
10
|
tallyOf,
|
|
11
11
|
validateRunBudget
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-w3d9kxcp.js";
|
|
13
13
|
import {
|
|
14
14
|
EventLog,
|
|
15
15
|
OUTCOME_NOT_RECORDED,
|
|
@@ -22,12 +22,12 @@ import {
|
|
|
22
22
|
isTerminal,
|
|
23
23
|
stageAt,
|
|
24
24
|
validateRunFile
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-t6gbx3jg.js";
|
|
26
26
|
import {
|
|
27
27
|
cursorStage,
|
|
28
28
|
isAttendedByHostView,
|
|
29
29
|
openRunViews
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-0d4csx4s.js";
|
|
31
31
|
import {
|
|
32
32
|
backupPathFor,
|
|
33
33
|
isAlive,
|
|
@@ -36,20 +36,21 @@ import {
|
|
|
36
36
|
workspaceRootOfRunDir,
|
|
37
37
|
writeAtomic,
|
|
38
38
|
yamlScalar
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-857jhjmb.js";
|
|
40
40
|
import {
|
|
41
41
|
isAdvisory,
|
|
42
42
|
noteDeprecations,
|
|
43
43
|
openBlocks,
|
|
44
44
|
parseQuestions
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-yanmz0vz.js";
|
|
46
46
|
import {
|
|
47
47
|
FRAMEWORK_ROOT,
|
|
48
|
+
MAX_ITEM_CHARS,
|
|
48
49
|
PLAN_STATUSES,
|
|
49
50
|
listRunDirs,
|
|
50
51
|
parseYaml,
|
|
51
52
|
parseYamlRepairing
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-qmp5p77c.js";
|
|
53
54
|
|
|
54
55
|
// src/core/statusline/runSnapshot.ts
|
|
55
56
|
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
@@ -634,7 +635,7 @@ var WAVE_CAP_REASON_KEY = "wave_cap_reason";
|
|
|
634
635
|
var PLAN_SHAPE_RULES = [
|
|
635
636
|
{
|
|
636
637
|
issue: "#316",
|
|
637
|
-
text: `**At most ${String(MAX_WAVES_PER_RUN)} waves** (patch for ${RUN_SIZE_ISSUES} — the framework's measured ` + "carry today, not a design preference). A later wave waits on every earlier merge, so each extra wave is " + "a stall: a 4-wave plan left 2 stories never attempted. The `plan` check refuses more unless the root of " + `\`${WAVES_FILE}\` records \`${WAVE_CAP_REASON_KEY}: "<why these stories cannot share a wave>"
|
|
638
|
+
text: `**At most ${String(MAX_WAVES_PER_RUN)} waves** (patch for ${RUN_SIZE_ISSUES} — the framework's measured ` + "carry today, not a design preference). A later wave waits on every earlier merge, so each extra wave is " + "a stall: a 4-wave plan left 2 stories never attempted. The `plan` check refuses more unless the root of " + `\`${WAVES_FILE}\` records \`${WAVE_CAP_REASON_KEY}: "<why these stories cannot share a wave>"\` — one sentence ` + `on one line, at most ${String(MAX_ITEM_CHARS)} characters (#328).`
|
|
638
639
|
},
|
|
639
640
|
{
|
|
640
641
|
issue: "#316",
|
|
@@ -663,6 +664,9 @@ var LINE_BREAK_NOTE = `\`${LINE_BREAK_MARK}\` marks a line break inside a quoted
|
|
|
663
664
|
var FINDING_STATUS_RE = new RegExp(`—\\s+(${PLAN_STATUSES.join("|")})\\s+—`);
|
|
664
665
|
var FINDING_REASON_RE = new RegExp(`—\\s+(?:${PLAN_STATUSES.join("|")})\\s+—\\s+[^:]*:\\s*(\\S.*)$`);
|
|
665
666
|
|
|
667
|
+
// src/core/build/dependencyHold.ts
|
|
668
|
+
var NOT_A_HOLD_REASON = "its block is not a dependency hold — a reviewer, a DoD or a developer recorded it, " + "and reopening a dependency does not answer that; `tldrx story reopen <id>` does";
|
|
669
|
+
|
|
666
670
|
// src/core/run/runOutcome.ts
|
|
667
671
|
var BUILD_HANDOFF = join3(BUILD_PHASE, "handoff.md");
|
|
668
672
|
var NO_BUILD_PHASE = "this run's workflow has no Build phase, so no story could be delivered";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
withWorkspaceLock,
|
|
4
4
|
workspaceRootOfFactsPath,
|
|
5
5
|
writeAtomic
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-857jhjmb.js";
|
|
7
7
|
import {
|
|
8
8
|
FACT_CONFIDENCES,
|
|
9
9
|
FACT_DECIDERS,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
isLive,
|
|
16
16
|
isRetired,
|
|
17
17
|
noteDeprecations
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-yanmz0vz.js";
|
|
19
19
|
import {
|
|
20
20
|
SRC_PATTERNS,
|
|
21
21
|
asDocument,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
requireString,
|
|
29
29
|
requireVersion,
|
|
30
30
|
result
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-qmp5p77c.js";
|
|
32
32
|
|
|
33
33
|
// src/core/facts/FactsStore.ts
|
|
34
34
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
EMPTY_SECTION_RULE,
|
|
4
4
|
noneBullet,
|
|
5
5
|
srcRule
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-qmp5p77c.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/lib/messages.ts
|
|
9
9
|
var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
|
|
@@ -7548,7 +7548,7 @@ function requireStringList(value, path, issues, rules = {}) {
|
|
|
7548
7548
|
if (item.length > MAX_ITEM_CHARS) {
|
|
7549
7549
|
issues.push({
|
|
7550
7550
|
path: at,
|
|
7551
|
-
message: `${item.length} characters
|
|
7551
|
+
message: `${item.length} characters (cap ${MAX_ITEM_CHARS}) — split it into several items`
|
|
7552
7552
|
});
|
|
7553
7553
|
}
|
|
7554
7554
|
if (rules.pattern !== undefined && !rules.pattern.test(item)) {
|
|
@@ -9202,4 +9202,4 @@ function noneBullet(lookedAt) {
|
|
|
9202
9202
|
return `- none [src: absent:${lookedAt}]`;
|
|
9203
9203
|
}
|
|
9204
9204
|
|
|
9205
|
-
export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, WORKFLOWS_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, PLAN_STATUSES, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, allowlistIssue, ITERATION_ONLY_SLOT, scopedSlotOf, isScopedTemplate, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
9205
|
+
export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, WORKFLOWS_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, PLAN_STATUSES, MAX_ITEM_CHARS, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, allowlistIssue, ITERATION_ONLY_SLOT, scopedSlotOf, isScopedTemplate, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
GATE_POLICIES,
|
|
3
3
|
validateGatesPolicy,
|
|
4
4
|
validateStagePolicy
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-g4d2v4jc.js";
|
|
6
6
|
import {
|
|
7
7
|
asDocument,
|
|
8
8
|
isRecord,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
requireString,
|
|
16
16
|
requireVersion,
|
|
17
17
|
result
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-qmp5p77c.js";
|
|
19
19
|
|
|
20
20
|
// src/core/events/EventLog.ts
|
|
21
21
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
STAGE_TUNING_DEFAULTS
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-g4d2v4jc.js";
|
|
4
4
|
import {
|
|
5
5
|
MAX_PLAN_STORIES,
|
|
6
6
|
MAX_STORIES_PER_WAVE,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
requireVersion,
|
|
26
26
|
requireVersion1,
|
|
27
27
|
result
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-qmp5p77c.js";
|
|
29
29
|
|
|
30
30
|
// src/hooks/lib/actor.ts
|
|
31
31
|
function currentActor() {
|
|
@@ -737,6 +737,9 @@ var KINDS = {
|
|
|
737
737
|
}
|
|
738
738
|
};
|
|
739
739
|
|
|
740
|
+
// src/core/build/git.ts
|
|
741
|
+
var MARKER_SCAN_MAX_BYTES = 4 * 1024 * 1024;
|
|
742
|
+
|
|
740
743
|
// src/core/build/fixlist.ts
|
|
741
744
|
var MAX_FIXLIST_ROUNDS = STAGE_TUNING_DEFAULTS.fixlistRounds;
|
|
742
745
|
var FINDING_KINDS = ["correctness", "security", "docs", "style"];
|
|
@@ -1245,12 +1248,194 @@ function wouldExceedHostTokens(budget, phaseId, spentTokens) {
|
|
|
1245
1248
|
};
|
|
1246
1249
|
}
|
|
1247
1250
|
|
|
1248
|
-
// src/core/budget/
|
|
1249
|
-
|
|
1251
|
+
// src/core/budget/grant.ts
|
|
1252
|
+
function grantFor(budget, phaseId) {
|
|
1253
|
+
if (phaseId !== undefined && phaseId !== null) {
|
|
1254
|
+
const phase = budget.phases.find((p) => p.id === phaseId);
|
|
1255
|
+
if (phase?.authorized_usd != null && budget.authorized_by !== null) {
|
|
1256
|
+
return { usd: phase.authorized_usd, factId: budget.authorized_by, level: "phase" };
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (budget.authorized_usd === null || budget.authorized_by === null)
|
|
1260
|
+
return null;
|
|
1261
|
+
return { usd: budget.authorized_usd, factId: budget.authorized_by, level: "run" };
|
|
1262
|
+
}
|
|
1263
|
+
function wouldExceedGrant(budget, phaseId, resultingCeilingUsd) {
|
|
1264
|
+
const grant = grantFor(budget, phaseId);
|
|
1265
|
+
if (grant === null) {
|
|
1266
|
+
return { exceeds: false, blocked: false, grant: null, sentence: null };
|
|
1267
|
+
}
|
|
1268
|
+
const exceeds = resultingCeilingUsd > grant.usd + 0.000000001;
|
|
1269
|
+
const policy = budget.on_grant_exceed;
|
|
1270
|
+
if (!exceeds)
|
|
1271
|
+
return { exceeds: false, blocked: false, grant, sentence: null };
|
|
1272
|
+
return {
|
|
1273
|
+
exceeds: true,
|
|
1274
|
+
blocked: policy === "block",
|
|
1275
|
+
grant,
|
|
1276
|
+
sentence: `$${resultingCeilingUsd.toFixed(2)} is above the $${grant.usd.toFixed(2)} authorized ` + `by ${grant.factId} (${grant.level} grant)` + (policy === "block" ? " — refused; raise the grant first, or record a new decision." : ".")
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// src/core/budget/raiseBudget.ts
|
|
1281
|
+
class BudgetRaiseError extends Error {
|
|
1282
|
+
}
|
|
1283
|
+
var MIN_RAISE_USD = 0.01;
|
|
1284
|
+
var MAX_RAISE_USD = 1000;
|
|
1285
|
+
function raiseBudget(budget, request) {
|
|
1286
|
+
const amount = round5(request.amountUsd);
|
|
1287
|
+
if (!Number.isFinite(amount) || amount < MIN_RAISE_USD) {
|
|
1288
|
+
throw new BudgetRaiseError(`the amount must be at least ${money(MIN_RAISE_USD)}, got ${money(request.amountUsd)}`);
|
|
1289
|
+
}
|
|
1290
|
+
if (amount > MAX_RAISE_USD) {
|
|
1291
|
+
throw new BudgetRaiseError(`${money(amount)} exceeds the ${money(MAX_RAISE_USD)} cap for a single raise`);
|
|
1292
|
+
}
|
|
1293
|
+
const target = budget.phases.find((p) => p.id === request.phaseId);
|
|
1294
|
+
if (target === undefined) {
|
|
1295
|
+
throw new BudgetRaiseError(`no phase \`${request.phaseId}\` in budget.yml — it has ${budget.phases.map((p) => p.id).join(", ")}`);
|
|
1296
|
+
}
|
|
1297
|
+
const takeFrom = request.takeFrom ?? null;
|
|
1298
|
+
if (takeFrom !== null && takeFrom === request.phaseId) {
|
|
1299
|
+
throw new BudgetRaiseError(`--take-from ${takeFrom} is the same phase — that moves nothing`);
|
|
1300
|
+
}
|
|
1301
|
+
let donor = null;
|
|
1302
|
+
if (takeFrom !== null) {
|
|
1303
|
+
donor = budget.phases.find((p) => p.id === takeFrom) ?? null;
|
|
1304
|
+
if (donor === null) {
|
|
1305
|
+
throw new BudgetRaiseError(`no phase \`${takeFrom}\` in budget.yml — it has ${budget.phases.map((p) => p.id).join(", ")}`);
|
|
1306
|
+
}
|
|
1307
|
+
const left = round5(donor.ceiling_usd - donor.spent_usd);
|
|
1308
|
+
if (amount > left) {
|
|
1309
|
+
throw new BudgetRaiseError(`${takeFrom} has only ${money(left)} unspent of its ${money(donor.ceiling_usd)} ceiling — ` + `taking ${money(amount)} would put its ceiling below what it has already spent`);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
const phases = budget.phases.map((phase) => {
|
|
1313
|
+
if (phase.id === request.phaseId)
|
|
1314
|
+
return { ...phase, ceiling_usd: round5(phase.ceiling_usd + amount) };
|
|
1315
|
+
if (donor !== null && phase.id === donor.id)
|
|
1316
|
+
return { ...phase, ceiling_usd: round5(phase.ceiling_usd - amount) };
|
|
1317
|
+
return phase;
|
|
1318
|
+
});
|
|
1319
|
+
const sum = round5(phases.reduce((total, p) => total + p.ceiling_usd, 0));
|
|
1320
|
+
const runCeiling = sum > budget.ceiling_usd ? sum : budget.ceiling_usd;
|
|
1321
|
+
const next = { ...budget, ceiling_usd: runCeiling, phases };
|
|
1322
|
+
if (round5(totalSpent(next)) > runCeiling) {
|
|
1323
|
+
throw new BudgetRaiseError("the run has already spent more than the resulting ceiling — raise the run ceiling first");
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
budget: next,
|
|
1327
|
+
phaseId: request.phaseId,
|
|
1328
|
+
amountUsd: amount,
|
|
1329
|
+
takeFrom,
|
|
1330
|
+
phaseCeilingBefore: target.ceiling_usd,
|
|
1331
|
+
phaseCeilingAfter: round5(target.ceiling_usd + amount),
|
|
1332
|
+
takeFromCeilingBefore: donor === null ? null : donor.ceiling_usd,
|
|
1333
|
+
takeFromCeilingAfter: donor === null ? null : round5(donor.ceiling_usd - amount),
|
|
1334
|
+
runCeilingBefore: budget.ceiling_usd,
|
|
1335
|
+
runCeilingAfter: runCeiling,
|
|
1336
|
+
runCeilingGrew: runCeiling > budget.ceiling_usd
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
function raiseGrantVerdict(budget, outcome) {
|
|
1340
|
+
const phaseGrant = grantFor(budget, outcome.phaseId);
|
|
1341
|
+
return phaseGrant !== null && phaseGrant.level === "phase" ? wouldExceedGrant(budget, outcome.phaseId, outcome.phaseCeilingAfter) : wouldExceedGrant(budget, null, outcome.runCeilingAfter);
|
|
1342
|
+
}
|
|
1343
|
+
function money(amount) {
|
|
1344
|
+
return `$${amount.toFixed(2)}`;
|
|
1345
|
+
}
|
|
1346
|
+
function round5(n) {
|
|
1347
|
+
return Math.round(n * 100) / 100;
|
|
1348
|
+
}
|
|
1250
1349
|
|
|
1251
1350
|
// src/core/budget/budgetView.ts
|
|
1252
1351
|
function raiseCommand(runId, phaseId, amountUsd) {
|
|
1253
1352
|
return `tldrx budget raise ${phaseId} ${amountUsd.toFixed(2)} --run ${runId}`;
|
|
1254
1353
|
}
|
|
1255
1354
|
|
|
1256
|
-
|
|
1355
|
+
// src/core/budget/rebalance.ts
|
|
1356
|
+
var REBALANCE_SOURCE = "run auto --rebalance-finished";
|
|
1357
|
+
var SETTLED = new Set(["done", "skipped"]);
|
|
1358
|
+
function planRebalance(budget, run, targetPhaseId, shortUsd) {
|
|
1359
|
+
const donors = [];
|
|
1360
|
+
const excluded = [];
|
|
1361
|
+
for (const phase of budget.phases) {
|
|
1362
|
+
if (phase.id === targetPhaseId)
|
|
1363
|
+
continue;
|
|
1364
|
+
const reason = notADonor(budget, run, phase.id);
|
|
1365
|
+
if (reason !== null) {
|
|
1366
|
+
excluded.push({ phaseId: phase.id, reason });
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
const unspent = round6(phase.ceiling_usd - phase.spent_usd);
|
|
1370
|
+
if (unspent < 0.01) {
|
|
1371
|
+
excluded.push({ phaseId: phase.id, reason: `finished with no unspent ceiling ($${phase.spent_usd.toFixed(2)} of $${phase.ceiling_usd.toFixed(2)})` });
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
donors.push({ phaseId: phase.id, unspentUsd: unspent });
|
|
1375
|
+
}
|
|
1376
|
+
const short = round6(shortUsd);
|
|
1377
|
+
const finishedUnspentUsd = round6(donors.reduce((sum, d) => sum + d.unspentUsd, 0));
|
|
1378
|
+
const uncoveredUsd = round6(Math.max(0, short - finishedUnspentUsd));
|
|
1379
|
+
const moves = [];
|
|
1380
|
+
if (uncoveredUsd === 0 && short > 0) {
|
|
1381
|
+
let left = short;
|
|
1382
|
+
for (const donor of donors) {
|
|
1383
|
+
if (left <= 0)
|
|
1384
|
+
break;
|
|
1385
|
+
const take = round6(Math.min(donor.unspentUsd, left));
|
|
1386
|
+
moves.push({ takeFrom: donor.phaseId, amountUsd: take });
|
|
1387
|
+
left = round6(left - take);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return { targetPhaseId, shortUsd: short, donors, excluded, finishedUnspentUsd, moves, uncoveredUsd };
|
|
1391
|
+
}
|
|
1392
|
+
function applyRebalance(budget, plan) {
|
|
1393
|
+
let current = budget;
|
|
1394
|
+
const outcomes = [];
|
|
1395
|
+
for (const move of plan.moves) {
|
|
1396
|
+
const outcome = raiseBudget(current, { phaseId: plan.targetPhaseId, amountUsd: move.amountUsd, takeFrom: move.takeFrom });
|
|
1397
|
+
outcomes.push(outcome);
|
|
1398
|
+
current = outcome.budget;
|
|
1399
|
+
}
|
|
1400
|
+
return { budget: current, outcomes };
|
|
1401
|
+
}
|
|
1402
|
+
function describeRebalance(plan) {
|
|
1403
|
+
if (plan.donors.length === 0) {
|
|
1404
|
+
return `no finished phase holds unspent ceiling` + (plan.excluded.length === 0 ? "" : ` (${plan.excluded.map((e) => `${e.phaseId}: ${e.reason}`).join("; ")})`);
|
|
1405
|
+
}
|
|
1406
|
+
const held = plan.donors.map((d) => `${d.phaseId} $${d.unspentUsd.toFixed(2)}`).join(", ");
|
|
1407
|
+
return `finished phase(s) hold $${plan.finishedUnspentUsd.toFixed(2)} unspent (${held})`;
|
|
1408
|
+
}
|
|
1409
|
+
function unfinishedReason(run, phaseId) {
|
|
1410
|
+
const phase = run.phases.find((p) => p.id === phaseId);
|
|
1411
|
+
if (phase === undefined || phase.stages.length === 0)
|
|
1412
|
+
return null;
|
|
1413
|
+
const open = phase.stages.filter((s) => !SETTLED.has(s.status));
|
|
1414
|
+
if (open.length > 0) {
|
|
1415
|
+
return `not finished — ${open.map((s) => `${s.id} is ${s.status}`).join(", ")}`;
|
|
1416
|
+
}
|
|
1417
|
+
const stale = phase.stages.filter((s) => s.stale === true);
|
|
1418
|
+
if (stale.length > 0)
|
|
1419
|
+
return `not finished — ${stale.map((s) => s.id).join(", ")} is stale and will run again`;
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
function notADonor(budget, run, phaseId) {
|
|
1423
|
+
const phase = run.phases.find((p) => p.id === phaseId);
|
|
1424
|
+
if (phase === undefined || phase.stages.length === 0)
|
|
1425
|
+
return "no stage of it in run.yml, so nothing proves it finished";
|
|
1426
|
+
const unfinished = unfinishedReason(run, phaseId);
|
|
1427
|
+
if (unfinished !== null)
|
|
1428
|
+
return unfinished;
|
|
1429
|
+
if (economyFor(budget, phaseId) !== "metered-usd")
|
|
1430
|
+
return "priced in host-tokens, which are not dollars";
|
|
1431
|
+
const unmetered = tallyOf(phase.stages.flatMap((s) => s.tasks)).unmetered;
|
|
1432
|
+
if (unmetered > 0) {
|
|
1433
|
+
return `${String(unmetered)} unmetered turn(s), so its spend is a lower bound and its unspent figure is not a proof`;
|
|
1434
|
+
}
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
function round6(n) {
|
|
1438
|
+
return Math.round(n * 100) / 100;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
export { currentActor, nowRfc3339, spentBasis, tallyOf, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ON_GRANT_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, expertsDir, loadExperts, readExpertDomain, pathsIntersect, stackExpertNames, WAVES_FILE, BUILD_PHASE2 as BUILD_PHASE, shortBy, remainingWork, wouldExceed, wouldExceedHostTokens, raiseGrantVerdict, REBALANCE_SOURCE, planRebalance, applyRebalance, describeRebalance, raiseCommand };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wouldBeContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7xascvee.js";
|
|
5
5
|
import {
|
|
6
6
|
claimSourcesDeny,
|
|
7
7
|
claimSourcesEmptySectionDeny,
|
|
8
8
|
claimSourcesMalformedDeny,
|
|
9
9
|
claimSourcesUnresolvedDeny
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-mg3aj0sa.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
postContext,
|
|
17
17
|
readPayload,
|
|
18
18
|
runHook
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import"./chunk-
|
|
19
|
+
} from "./chunk-pne7afnf.js";
|
|
20
|
+
import"./chunk-z9hv47ps.js";
|
|
21
21
|
import {
|
|
22
22
|
isHandoff,
|
|
23
23
|
loadWorkspace,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
toSrcContext,
|
|
26
26
|
validateCitations,
|
|
27
27
|
validateHandoff
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-qmp5p77c.js";
|
|
29
29
|
|
|
30
30
|
// src/hooks/claim-sources.ts
|
|
31
31
|
await runHook("claim-sources", async () => {
|
package/dist/hooks/dod-gate.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wouldBeContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7xascvee.js";
|
|
5
5
|
import {
|
|
6
6
|
dodGateDeny,
|
|
7
7
|
dodGateInternalErrorDeny,
|
|
8
8
|
dodGateMissingBlockDeny,
|
|
9
9
|
dodGateRefusedCommandDeny
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-mg3aj0sa.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -15,16 +15,15 @@ import {
|
|
|
15
15
|
filePathOf,
|
|
16
16
|
isWriteOrEdit,
|
|
17
17
|
readPayload
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import"./chunk-
|
|
18
|
+
} from "./chunk-pne7afnf.js";
|
|
19
|
+
import"./chunk-z9hv47ps.js";
|
|
20
|
+
import {
|
|
21
|
+
loadRunView
|
|
22
|
+
} from "./chunk-0d4csx4s.js";
|
|
20
23
|
import {
|
|
21
24
|
DEFAULT_TIMEOUT_S,
|
|
22
25
|
buildStageDefaults
|
|
23
|
-
} from "./chunk-
|
|
24
|
-
import {
|
|
25
|
-
loadRunView
|
|
26
|
-
} from "./chunk-hws2gnxj.js";
|
|
27
|
-
import"./chunk-g7wz339n.js";
|
|
26
|
+
} from "./chunk-g4d2v4jc.js";
|
|
28
27
|
import {
|
|
29
28
|
DodCommandRefused,
|
|
30
29
|
loadWorkspace,
|
|
@@ -32,7 +31,7 @@ import {
|
|
|
32
31
|
readStory,
|
|
33
32
|
repoPath,
|
|
34
33
|
runDodCommand
|
|
35
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-qmp5p77c.js";
|
|
36
35
|
|
|
37
36
|
// src/hooks/dod-gate.ts
|
|
38
37
|
import { basename, join } from "node:path";
|
package/dist/hooks/no-reask.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wouldBeContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7xascvee.js";
|
|
5
5
|
import {
|
|
6
6
|
noReAskDeny
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-mg3aj0sa.js";
|
|
8
8
|
import {
|
|
9
9
|
FactsStore
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-gr83pq7x.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -15,17 +15,17 @@ import {
|
|
|
15
15
|
isWriteOrEdit,
|
|
16
16
|
readPayload,
|
|
17
17
|
runHook
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import"./chunk-
|
|
20
|
-
import"./chunk-
|
|
18
|
+
} from "./chunk-pne7afnf.js";
|
|
19
|
+
import"./chunk-z9hv47ps.js";
|
|
20
|
+
import"./chunk-857jhjmb.js";
|
|
21
21
|
import {
|
|
22
22
|
openBlocks,
|
|
23
23
|
parseQuestions
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-yanmz0vz.js";
|
|
25
25
|
import {
|
|
26
26
|
factsPath,
|
|
27
27
|
locateWork
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-qmp5p77c.js";
|
|
29
29
|
|
|
30
30
|
// src/hooks/no-reask.ts
|
|
31
31
|
import { existsSync, readFileSync } from "node:fs";
|