tldr-experts 0.28.1 → 0.30.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 +635 -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-abswp1vt.js → chunk-77x6v0gg.js} +4 -2
- 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-fbmj1bk6.js} +3 -3
- package/dist/hooks/{chunk-veffvzw5.js → chunk-g4d2v4jc.js} +101 -10
- 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-fjeefnqr.js → chunk-przta0a0.js} +44 -8
- package/dist/hooks/{chunk-f6t1xc3f.js → chunk-qmp5p77c.js} +2 -2
- 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/{chunk-e618k6te.js → chunk-zpch8e2j.js} +193 -5
- 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 +16 -13
- package/dist/hooks/statusline.js +8 -9
- package/dist/tldrx.js +2597 -990
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/templates/experts/stack/dotnet.md +4 -3
- package/templates/experts/stack/javascript.md +4 -3
- package/templates/experts/stack/overlays/postgres-testcontainers.md +7 -2
- package/templates/experts/stack/python.md +4 -3
- package/templates/experts/stack/typescript.md +4 -3
- package/dist/hooks/chunk-g7wz339n.js +0 -102
package/README.md
CHANGED
|
@@ -335,6 +335,8 @@ back on the registry is 0.3.0.
|
|
|
335
335
|
|
|
336
336
|
| Version | Date | Status | Contains |
|
|
337
337
|
|---|---|---|---|
|
|
338
|
+
| 0.30.0 | 2026-09-15 | `beta` | Seventeen changes that keep an unattended run moving instead of stranding. A cached Build refusal says so and a relaunch re-measures it (#339); a rate-limit warning is read instead of dropped, so a Build parks before the wall (#298); the plan-over-stage advisory speaks only past 2× (#302); a stage death stops recommending a retry the budget refuses (#232); a DoD command is measured twice, a fix-list finding is checked against the epic tip, and an over-long sha is refused by name (#163); a formatting slip gets one bounded repair turn (#288); five Checks stop asking the reviewer for what it can't do (#195); a Codex review no longer dies before it runs (#148); `tldrx init` probes four commands at once (#180); `tldrx ship` refuses a merged diff nobody judged (#311). Docs: the zero-touch recipe pairs `run auto` with `--wait-gates`/`--wait-answers` (#342). |
|
|
339
|
+
| 0.29.0 | 2026-09-15 | `beta` | Sixteen changes that make an unattended run trust its own gates and its own numbers. Verdict integrity: a reviewer's `changes` must cite evidence (#326), a re-reviewed story's `changes` is consumed instead of parking it (#327), and a story blocked on a stale fix list settles `done` once resolved (#329). Fix-list flow: a headless fix list now buys its own fix round, and an approving reviewer's signature closes what it was shown (#327). Money and phase sizing: `run auto` rebalances a blocked phase's shortfall out of finished phases by default (#330), the `budget-gate` hook stops denying the launch that would fix the shortfall (#321), a Build gate no longer stops on paths outside the declared surface but carries them into the PR instead (#331), a parallel wave no longer hands two developers the same unspent money (#325), `budget show`/`run estimate`/the budget-gate hook price remaining work off the stage's own `attempts:` (#214), `run auto --until-done` states a token refusal in tokens rather than invented dollar words (#270), a Watch turn is recorded as unmetered rather than a false `$0.00` (#224), and a Watch spawn now records the ceiling it was given (#190). Dependency and safety: a Build gate names the story a dependent waits on (#303), the conflict-turn marker guard names unreadable paths, bounds its scan, and covers modified files too (#324), a developer's array-valued `notes` is joined instead of silently dropped (#217), and the Plan prompt states its per-item character cap where each field is described so an over-cap finding survives into the retry (#328). |
|
|
338
340
|
| 0.28.1 | 2026-09-15 | `beta` | Two fixes so a reopened story's reason reaches the agents that need it. The developer and reviewer prompts now carry `## Why this story was reopened` — who signed it, whether it's a fix round, the note verbatim — off the same ledger value #308 reads, instead of the note reaching only a report line and #308's check (closes #322). `tldrx note --help` now says an operator note reaches no agent and names `dispatch-notes.md` as the channel that does (closes #151). |
|
|
339
341
|
| 0.28.0 | 2026-09-15 | `beta` | Four changes that keep an unattended run moving instead of stranding on a person. A story whose merge-up to its epic conflicts in at most three of its own touched files gets one automated conflict turn instead of a person, guarded against committing conflict markers (#286). `story reopen` on a blocked story now releases every dependent it alone was holding, in the same command (#312). Seed's `Recommended:` line and the loop's question parser now share one grammar, so a citation-only recommendation no longer parks a run that `seed check` had already passed (#323). And an `absent:` needle over `facts.yml` no longer trips on a fact's own recording metadata, while an `auto` gate refused only by failed checks gets one automatic re-run before waiting for a person (#231). |
|
|
340
342
|
| 0.27.0 | 2026-09-14 | `beta` | Five changes for unattended runs, from a planning audit and live field measurement. The `plan` check now refuses a plan with more waves than the framework carries per run (unless `waves.yml` names a `wave_cap_reason`), refuses a story scheduled before its `depends_on` allows, and flags a dod command siblings carry that one story doesn't (#316–#319). `tldrx run auto --rebalance-finished` moves a blocked phase's exact shortfall out of a finished phase's unspent ceiling before refusing, through `budget raise --take-from` itself, so the run ceiling never grows and no grant is ever assumed; every money refusal now names the unspent total and the exact command to fix it (#314). `tldrx ship` fetches and merges the base into the epic and re-runs the `done` stories' DoD before opening a PR, so a stale or red epic refuses instead of shipping a PR that comes back red (#315). A reviewer's budget floor rises from $1.00 to $2.00, matching what completed reviews actually cost, so a review no longer dies mid-diff on a floor that was never measured (#307). And a developer whose DoD goes red gets the story's next attempt instead of blocking on the first miss, with the kept output handed to the next attempt and the bound counted from `events.jsonl` so it holds across process restarts; a refused developer, a cap death, or a broken dod command still blocks immediately (#313). |
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
conflictOf
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-fbmj1bk6.js";
|
|
5
5
|
import {
|
|
6
6
|
FactsStore,
|
|
7
7
|
formatJaccard
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-gr83pq7x.js";
|
|
9
9
|
import {
|
|
10
10
|
parseHookInput,
|
|
11
11
|
readStdin
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-z9hv47ps.js";
|
|
13
13
|
import {
|
|
14
14
|
EventLog,
|
|
15
15
|
PHASE_ID_RE
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-77x6v0gg.js";
|
|
17
17
|
import {
|
|
18
18
|
PHASE_IDS
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import"./chunk-
|
|
21
|
-
import"./chunk-zvxgdssb.js";
|
|
19
|
+
} from "./chunk-g4d2v4jc.js";
|
|
20
|
+
import"./chunk-857jhjmb.js";
|
|
22
21
|
import {
|
|
23
22
|
ADVISORY_KEY,
|
|
24
23
|
MAX_FACT_CHARS,
|
|
@@ -28,7 +27,7 @@ import {
|
|
|
28
27
|
renderQuestionBlock,
|
|
29
28
|
replaceBlock,
|
|
30
29
|
serializeQuestions
|
|
31
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-yanmz0vz.js";
|
|
32
31
|
import {
|
|
33
32
|
ITERATION_ONLY_SLOT,
|
|
34
33
|
PROJECT_FRAMEWORK_DIR,
|
|
@@ -38,7 +37,7 @@ import {
|
|
|
38
37
|
isScopedTemplate,
|
|
39
38
|
parseYaml,
|
|
40
39
|
scopedSlotOf
|
|
41
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-qmp5p77c.js";
|
|
42
41
|
|
|
43
42
|
// src/hooks/answer-capture.ts
|
|
44
43
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-mg3aj0sa.js";
|
|
5
5
|
import {
|
|
6
6
|
allow,
|
|
7
7
|
deny,
|
|
8
8
|
readPayload,
|
|
9
9
|
runHook,
|
|
10
10
|
toolInput
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import"./chunk-
|
|
11
|
+
} from "./chunk-pne7afnf.js";
|
|
12
|
+
import"./chunk-z9hv47ps.js";
|
|
13
13
|
import {
|
|
14
|
+
REBALANCE_SOURCE,
|
|
15
|
+
applyRebalance,
|
|
14
16
|
asRunBudget,
|
|
15
17
|
currentActor,
|
|
18
|
+
describeRebalance,
|
|
16
19
|
economyFor,
|
|
17
20
|
isHostTokens,
|
|
18
21
|
nowRfc3339,
|
|
22
|
+
planRebalance,
|
|
19
23
|
raiseCommand,
|
|
24
|
+
raiseGrantVerdict,
|
|
20
25
|
remainingWork,
|
|
21
26
|
shortBy,
|
|
22
27
|
validateRunBudget,
|
|
23
28
|
wouldExceed,
|
|
24
29
|
wouldExceedHostTokens
|
|
25
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-zpch8e2j.js";
|
|
26
31
|
import {
|
|
27
32
|
EventLog
|
|
28
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-77x6v0gg.js";
|
|
29
34
|
import {
|
|
30
35
|
cursorStage,
|
|
31
36
|
hostTokensIn,
|
|
@@ -34,18 +39,20 @@ import {
|
|
|
34
39
|
newestActiveRun,
|
|
35
40
|
renderRunEconomies,
|
|
36
41
|
runSpend
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import
|
|
42
|
+
} from "./chunk-0d4csx4s.js";
|
|
43
|
+
import {
|
|
44
|
+
buildStageDefaults
|
|
45
|
+
} from "./chunk-g4d2v4jc.js";
|
|
39
46
|
import {
|
|
40
47
|
noteDeprecations
|
|
41
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-yanmz0vz.js";
|
|
42
49
|
import {
|
|
43
50
|
PROJECT_WORK_DIR,
|
|
44
51
|
findWorkspaceRoot,
|
|
45
52
|
locateWork,
|
|
46
53
|
parseYaml,
|
|
47
54
|
stageYamlPath
|
|
48
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-qmp5p77c.js";
|
|
49
56
|
|
|
50
57
|
// src/hooks/budget-gate.ts
|
|
51
58
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -72,6 +79,8 @@ function loadRunBudget(runDir) {
|
|
|
72
79
|
// src/hooks/budget-gate.ts
|
|
73
80
|
var SPAWN_RE = /^(claude -p|tldrx next|tldrx run auto|tldrx expert train|tldrx seed triage)\b/;
|
|
74
81
|
var RUN_ARG_RE = /--run[= ]([\w.-]+)/;
|
|
82
|
+
var RUN_AUTO_RE = /^tldrx run auto\b/;
|
|
83
|
+
var NO_REBALANCE_RE = /(?:^|\s)--no-rebalance-finished(?:[\s=]|$)/;
|
|
75
84
|
var DEFAULT_TRAIN_USD = 2;
|
|
76
85
|
var DEFAULT_FULL_TRAIN_USD = 3;
|
|
77
86
|
var DEFAULT_TRIAGE_USD = 1;
|
|
@@ -116,6 +125,7 @@ await runHook("budget-gate", async () => {
|
|
|
116
125
|
const attended = isAttendedByHostView(view);
|
|
117
126
|
const stage = cursorStage(view);
|
|
118
127
|
const declared = stage?.budget_usd ?? stageBudgetFromLibrary(root, view.cursor.stage);
|
|
128
|
+
const attempts = buildStageDefaults(root, view.scope, view.cursor.stage).attempts;
|
|
119
129
|
const work = declared === null ? null : remainingWork({
|
|
120
130
|
runDir: view.dir,
|
|
121
131
|
phaseId: view.cursor.phase,
|
|
@@ -124,7 +134,8 @@ await runHook("budget-gate", async () => {
|
|
|
124
134
|
perAgentMaxUsd: budget.per_agent_max_usd,
|
|
125
135
|
maxUsd: null,
|
|
126
136
|
economy: economyFor(budget, view.cursor.phase),
|
|
127
|
-
attended
|
|
137
|
+
attended,
|
|
138
|
+
attempts
|
|
128
139
|
});
|
|
129
140
|
const estimate = estimateFor(command, work === null ? null : work.usd);
|
|
130
141
|
if (estimate <= 0)
|
|
@@ -156,7 +167,7 @@ ${economies}`}`);
|
|
|
156
167
|
`);
|
|
157
168
|
return;
|
|
158
169
|
}
|
|
159
|
-
const decision = wouldExceed(budget, view.cursor.phase, estimate);
|
|
170
|
+
const decision = wouldExceed(budget, view.cursor.phase, estimate, attempts);
|
|
160
171
|
if (!decision.blocked)
|
|
161
172
|
return;
|
|
162
173
|
if (attended) {
|
|
@@ -176,6 +187,15 @@ ${economies}`}`);
|
|
|
176
187
|
`);
|
|
177
188
|
return;
|
|
178
189
|
}
|
|
190
|
+
if (decision.scope === "phase" && RUN_AUTO_RE.test(command) && !NO_REBALANCE_RE.test(command)) {
|
|
191
|
+
const short = shortBy(estimate, decision.remaining);
|
|
192
|
+
const covered = finishedPhasesCover(view.dir, budget, view.cursor.phase, short);
|
|
193
|
+
if (covered !== null) {
|
|
194
|
+
process.stderr.write(`tldrx hook budget-gate: ${view.cursor.phase} has $${decision.remaining.toFixed(2)} left of ` + `$${decision.ceiling.toFixed(2)} and the stage estimate is $${estimate.toFixed(2)} — NOT refusing: ` + `\`run auto\` rebalances finished phases before it refuses, and ${covered}, which covers the ` + `$${short.toFixed(2)} shortfall. The loop makes that move on its own record (\`budget.raised\`, ` + `source: ${REBALANCE_SOURCE}); launch with --no-rebalance-finished to be refused here instead.` + `${economies === null ? "" : ` ${economies}`}
|
|
195
|
+
`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
new EventLog(join2(view.dir, "events.jsonl")).tryAppend({
|
|
180
200
|
ts: nowRfc3339(),
|
|
181
201
|
run: view.run,
|
|
@@ -226,6 +246,22 @@ function estimateFor(command, stageBudget) {
|
|
|
226
246
|
}
|
|
227
247
|
return stageBudget ?? 0;
|
|
228
248
|
}
|
|
249
|
+
function finishedPhasesCover(runDir, budget, phaseId, shortUsd) {
|
|
250
|
+
try {
|
|
251
|
+
const doc = parseYaml(readFileSync2(join2(runDir, "run.yml"), "utf8"));
|
|
252
|
+
if (doc === null || !Array.isArray(doc.phases))
|
|
253
|
+
return null;
|
|
254
|
+
const plan = planRebalance(budget, doc, phaseId, shortUsd);
|
|
255
|
+
if (plan.moves.length === 0)
|
|
256
|
+
return null;
|
|
257
|
+
const applied = applyRebalance(budget, plan);
|
|
258
|
+
if (applied.outcomes.some((outcome) => raiseGrantVerdict(budget, outcome).exceeds))
|
|
259
|
+
return null;
|
|
260
|
+
return describeRebalance(plan);
|
|
261
|
+
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
229
265
|
function failClosed(command, why) {
|
|
230
266
|
deny(`[tldrx] budget-gate: refusing \`${command.slice(0, 80)}\` — this gate could not read the budget ` + `it is supposed to enforce (${why}).
|
|
231
267
|
` + "It fails CLOSED: a spend nothing can check is exactly the one that must not start. Fix the run's " + "budget.yml, or pass `--run <id>` so the gate knows which run to charge.");
|
|
@@ -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";
|
|
@@ -39,6 +39,7 @@ var EVENT_TYPES = [
|
|
|
39
39
|
"task.done",
|
|
40
40
|
"agent.spawned",
|
|
41
41
|
"agent.result",
|
|
42
|
+
"agent.rate_limited",
|
|
42
43
|
"question.asked",
|
|
43
44
|
"question.answered",
|
|
44
45
|
"gate.requested",
|
|
@@ -54,6 +55,7 @@ var EVENT_TYPES = [
|
|
|
54
55
|
"story.review_retried",
|
|
55
56
|
"story.work_rescued",
|
|
56
57
|
"story.touches_widened",
|
|
58
|
+
"plan.fix_round",
|
|
57
59
|
"epic.released",
|
|
58
60
|
"worktree.foreign_work_aside",
|
|
59
61
|
"worktree.foreign_work_restored",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROJECT_FRAMEWORK_DIR,
|
|
3
3
|
PROJECT_WORK_DIR
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-qmp5p77c.js";
|
|
5
5
|
|
|
6
6
|
// src/core/lock/workspaceLock.ts
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync as rmSync2, writeSync, closeSync } from "node:fs";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
findDuplicate
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-gr83pq7x.js";
|
|
4
4
|
import {
|
|
5
5
|
evidencePath,
|
|
6
6
|
gateEvidencePath,
|
|
7
7
|
parseEvidence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-77x6v0gg.js";
|
|
9
9
|
import {
|
|
10
10
|
openBlocks,
|
|
11
11
|
parseQuestions
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-yanmz0vz.js";
|
|
13
13
|
|
|
14
14
|
// src/core/distill/distill.ts
|
|
15
15
|
var CONFLICT_THRESHOLD = 0.6;
|
|
@@ -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 };
|
|
@@ -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>]";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
spentBasis,
|
|
10
10
|
tallyOf,
|
|
11
11
|
validateRunBudget
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-zpch8e2j.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-77x6v0gg.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";
|
|
@@ -877,8 +881,33 @@ function hasPreparedBundle(runDir, stageId) {
|
|
|
877
881
|
return preparedBundles(runDir, stageId).length > 0;
|
|
878
882
|
}
|
|
879
883
|
|
|
884
|
+
// src/core/run/lastStart.ts
|
|
885
|
+
function lastStageStartMode(runDir, stageId) {
|
|
886
|
+
const events = EventLog.forRun(runDir).readAll().events;
|
|
887
|
+
for (let index = events.length - 1;index >= 0; index -= 1) {
|
|
888
|
+
const event = events[index];
|
|
889
|
+
if (event === undefined)
|
|
890
|
+
continue;
|
|
891
|
+
if (event.type !== "stage.started" || event.stage !== stageId)
|
|
892
|
+
continue;
|
|
893
|
+
const mode = event.payload.mode;
|
|
894
|
+
return typeof mode === "string" && mode !== "" ? mode : null;
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
function startedHeadless(runDir, stageId) {
|
|
899
|
+
return lastStageStartMode(runDir, stageId) === "headless";
|
|
900
|
+
}
|
|
901
|
+
|
|
880
902
|
// src/core/run/waiting.ts
|
|
881
|
-
var MOVABLE_KINDS = [
|
|
903
|
+
var MOVABLE_KINDS = [
|
|
904
|
+
"gate",
|
|
905
|
+
"answer",
|
|
906
|
+
"ready",
|
|
907
|
+
"failed",
|
|
908
|
+
"prepared",
|
|
909
|
+
"interrupted"
|
|
910
|
+
];
|
|
882
911
|
function isMovable(kind) {
|
|
883
912
|
return MOVABLE_KINDS.includes(kind);
|
|
884
913
|
}
|
|
@@ -948,6 +977,13 @@ function waitingFor(run, runDir) {
|
|
|
948
977
|
questions: open
|
|
949
978
|
};
|
|
950
979
|
}
|
|
980
|
+
if (startedHeadless(runDir, entry.stage.id)) {
|
|
981
|
+
return {
|
|
982
|
+
kind: "interrupted",
|
|
983
|
+
message: `${entry.phase.id}/${entry.stage.id} was interrupted — its headless turn's process is gone — ` + `\`tldrx run auto ${basename2(runDir)}\``,
|
|
984
|
+
questions: open
|
|
985
|
+
};
|
|
986
|
+
}
|
|
951
987
|
if (hasPreparedBundle(runDir, entry.stage.id)) {
|
|
952
988
|
return {
|
|
953
989
|
kind: "prepared",
|
|
@@ -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 };
|