tldr-experts 0.5.0 → 0.6.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 +792 -0
- package/README.md +19 -5
- package/dist/hooks/answer-capture.js +136 -12
- package/dist/hooks/budget-gate.js +9 -9
- package/dist/hooks/{chunk-889tybxc.js → chunk-17gv74sr.js} +3 -3
- package/dist/hooks/{chunk-3w55tp71.js → chunk-5k4dggq0.js} +51 -17
- package/dist/hooks/{chunk-14zn51kh.js → chunk-7eqhfddw.js} +28 -569
- package/dist/hooks/{chunk-nadqsr3w.js → chunk-97ncnegd.js} +1 -1
- package/dist/hooks/{chunk-a6rpj2cp.js → chunk-9w64mxdh.js} +345 -22
- package/dist/hooks/{chunk-a5dq2dcp.js → chunk-dhbxzjfs.js} +2 -4
- package/dist/hooks/{chunk-hcrbr430.js → chunk-g4db8rbs.js} +2 -30
- package/dist/hooks/chunk-h7151g8w.js +803 -0
- package/dist/hooks/{chunk-5556vjt5.js → chunk-ka6bkb64.js} +1 -1
- package/dist/hooks/chunk-rrkdfk7s.js +30 -0
- package/dist/hooks/{chunk-qw73rdbr.js → chunk-v4ay50nq.js} +1 -1
- package/dist/hooks/{chunk-v1c1hpb8.js → chunk-vrhczzxs.js} +1 -1
- package/dist/hooks/{chunk-bvm6vjrt.js → chunk-x6n8b5fp.js} +1 -1
- package/dist/hooks/claim-sources.js +8 -10
- package/dist/hooks/dod-gate.js +7 -7
- package/dist/hooks/no-reask.js +8 -9
- package/dist/hooks/session-start.js +23 -18
- package/dist/hooks/statusline.js +10 -10
- package/dist/tldrx.js +2539 -1006
- package/env.yml +9 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/tldrx/SKILL.md +8 -1
- package/templates/questions.md +8 -0
- package/dist/hooks/chunk-q8d3sff9.js +0 -220
- package/dist/hooks/chunk-sae7sqty.js +0 -206
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/tldr-experts) [](https://github.com/ederwii/tldr-experts/actions/workflows/ci.yml) 
|
|
4
4
|
|
|
5
|
-
**An evidence-first, file-based AI development framework: five stages, a gate on every one, and every claim cited or refused.** Open source
|
|
5
|
+
**An evidence-first, file-based AI development framework: five stages, a gate on every one, and every claim cited or refused.** Open source. The workflow and the persisted state format are provider-independent; the automated runner currently supports Claude Code. **Beta:** every command is implemented and verified by running it, the `version: 1` file formats only grow from here, and `tldrx --help` is the authoritative command surface.
|
|
6
6
|
|
|
7
7
|
One loop — *Investigate → Handoff → Interview → Gate* — five phases, **what · how · plan · build ·
|
|
8
8
|
watch**, one stage per command, each stopping at a gate you own; the files ARE the state, the
|
|
@@ -41,8 +41,10 @@ tldrx run new payments --scope feature --seed docs/payments/ --budget 25
|
|
|
41
41
|
tldrx run auto # `next`, over and over, until something actually needs you
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
**
|
|
45
|
-
dependencies,
|
|
44
|
+
**To run it: Node ≥ 20, and nothing else** — the published package is a pre-built bundle with
|
|
45
|
+
zero runtime dependencies, and `dist/` is a Node bundle (Bun runs it too, if you have it).
|
|
46
|
+
**To build or contribute: Bun ≥ 1.3**, which compiles that bundle and runs the test suite.
|
|
47
|
+
`tldrx doctor` is the authority on the rest. Full walkthrough:
|
|
46
48
|
[`docs/guide/01-quick-start.md`](docs/guide/01-quick-start.md).
|
|
47
49
|
|
|
48
50
|
## Trying it: three ways to run
|
|
@@ -247,14 +249,25 @@ their answers, `run.yml`, `budget.yml`, `events.jsonl`, the handoffs, the plan
|
|
|
247
249
|
the repo gets the run. The block `tldrx init` appends to `.gitignore` excludes eight paths and nothing
|
|
248
250
|
else, because those eight are machine-local, regenerated, or a backup git already holds the history
|
|
249
251
|
of: `.tldrx/graphify-out/`, `.tldrx/cache/`, `.tldrx/worktrees/`, `tldrx-work/*/.lock`,
|
|
250
|
-
`tldrx-work/*/.agent/`, `tldrx-work
|
|
252
|
+
`tldrx-work/*/.agent/`, `tldrx-work/**/*.bak`, `.tldrx/**/*.bak` and
|
|
251
253
|
`.claude/settings.json.bak-tldrx-*`.
|
|
252
254
|
|
|
255
|
+
**You do not have to commit them by hand.** Closing a run — `tldrx approve` on the last gate,
|
|
256
|
+
`tldrx next`, or `tldrx run cancel` — commits `tldrx-work/<run>/` and `.tldrx/memory/` in the
|
|
257
|
+
workspace checkout, on the branch that checkout is on, and prints one line saying where they went.
|
|
258
|
+
Only those paths: anything else you had staged is still staged afterwards. It is deliberately never
|
|
259
|
+
the epic branch — an epic under review carries feature code, and run state on it collides with the
|
|
260
|
+
same live files in your working tree the moment the PR merges, which is why `tldrx ship` refuses an
|
|
261
|
+
epic that carries any (#102).
|
|
262
|
+
|
|
253
263
|
## Documentation
|
|
254
264
|
|
|
255
265
|
**[The documentation site](https://ederwii.github.io/tldr-experts/)** is the place to start if you have
|
|
256
266
|
never used this: a landing page, a Quickstart and one short page per concept, written for a reader
|
|
257
|
-
rather than for an agent. Source in [`docs-site/`](docs-site/).
|
|
267
|
+
rather than for an agent. Source in [`docs-site/`](docs-site/). It carries a
|
|
268
|
+
**[live demo of `tldrx dashboard`](https://ederwii.github.io/tldr-experts/demo)** — a real export,
|
|
269
|
+
rendered from the test suite's synthetic fixtures on every deploy, so you can see what the tool
|
|
270
|
+
draws before installing anything.
|
|
258
271
|
|
|
259
272
|
The reference guide, in `docs/guide/`: [1 Quick start](docs/guide/01-quick-start.md) ·
|
|
260
273
|
[2 The loop](docs/guide/02-the-loop.md) (the four steps, what a stage file controls, the two execution modes) ·
|
|
@@ -285,6 +298,7 @@ back on the registry is 0.3.0.
|
|
|
285
298
|
|
|
286
299
|
| Version | Date | Status | Contains |
|
|
287
300
|
|---|---|---|---|
|
|
301
|
+
| 0.6.0 | 2026-09-02 | `beta` | the dashboard suite — a "Now" hero strip, `--serve` live refresh, stage durations and gate notes on page and CLI, and a public demo generated from fixtures; honest dual-economy spend (metered + host, lower-bound named); gate provenance — `executed_by` + `authority` so a delegated signature never reads as a personal one, machine-signed reporting fixed; ONE `absent:` semantic shared by claim-sources and the auto gate; superseded stamps when an owner answer flips an earlier phase doc; run close commits its state and `ship` refuses a state-carrying epic; merge-wave gates `docs:build`, survives interruption and self-rewrite; a public-surface drift guard; env.yml validation (unique ids, tool cap) |
|
|
288
302
|
| 0.5.0 | 2026-09-02 | `beta` | `tldrx drive` and its own preflight, `watch check` / `watch arm`, `questions cards`, `plan schema`, `retro --all` (with its findings fed back into every reviewer prompt) and `story reopen --for-fix`; `tldrx update` plus a cached newer-version notice; the dashboard reads `budget.yml` and `events.jsonl` — operator notes, reopens and retries, the per-phase budget panel, and a host-attended run metered in tokens against `ceiling_host_tokens`; a rejected review envelope no longer burns a story attempt; `ship` opens one PR per repo; merge-wave lock + ref guard; five golden-transcript evals, one per stage; `CONTRIBUTING.md` and a model-provider contract |
|
|
289
303
|
| 0.4.0 | 2026-09-01 | `beta` | FIRST BETA — 40-issue hardening burn (DoD pre-flight + `plan sync-dod`, merge-wave lock + gated-HEAD, load-aware tests, claim-sources across all outputs), `tldrx learn` 8-chapter sandbox tutorial (cold-player QA), `tldrx ship` / `tldrx note` / `run gates set`, budget policies + dual-economy wiring, single integration branch for chained epics, epic worktrees live to run close, bilingual docs site |
|
|
290
304
|
| 0.3.1 | 2026-08-31 | `alpha` | Unattended mode (gates_policy agent, review handshake, fixlist, decision cards, dual economy), 6 contact fixes from the first feature-scope runs, colored init, training repair round |
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
FactsStore
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-17gv74sr.js";
|
|
5
5
|
import {
|
|
6
6
|
parseHookInput,
|
|
7
7
|
readStdin
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-vrhczzxs.js";
|
|
9
9
|
import {
|
|
10
|
-
EventLog
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
EventLog,
|
|
11
|
+
PHASE_ID_RE
|
|
12
|
+
} from "./chunk-h7151g8w.js";
|
|
13
|
+
import"./chunk-g4db8rbs.js";
|
|
14
|
+
import"./chunk-v4ay50nq.js";
|
|
13
15
|
import {
|
|
14
16
|
MAX_FACT_CHARS,
|
|
15
17
|
detectAnswered,
|
|
@@ -17,15 +19,15 @@ import {
|
|
|
17
19
|
recordAnswer,
|
|
18
20
|
replaceBlock,
|
|
19
21
|
serializeQuestions
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-97ncnegd.js";
|
|
21
23
|
import"./chunk-39zh2e44.js";
|
|
22
24
|
import {
|
|
23
25
|
PROJECT_WORK_DIR,
|
|
24
26
|
factsPath
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-9w64mxdh.js";
|
|
26
28
|
|
|
27
29
|
// src/hooks/answer-capture.ts
|
|
28
|
-
import { existsSync as
|
|
30
|
+
import { existsSync as existsSync3 } from "fs";
|
|
29
31
|
|
|
30
32
|
// src/hooks/lib/decide.ts
|
|
31
33
|
function allow() {
|
|
@@ -97,7 +99,95 @@ function nowRfc3339() {
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
// src/core/answers/captureAnswers.ts
|
|
100
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
102
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
103
|
+
|
|
104
|
+
// src/core/answers/stampSuperseded.ts
|
|
105
|
+
import { appendFileSync, existsSync, readFileSync, statSync } from "node:fs";
|
|
106
|
+
import { isAbsolute as isAbsolute2, join as join2, relative } from "node:path";
|
|
107
|
+
var STAMP_MARKER = "tldrx:superseded";
|
|
108
|
+
var AFFECTS_KEY = "affects";
|
|
109
|
+
function isStamped(text, fact) {
|
|
110
|
+
return text.includes(`${STAMP_MARKER} ${fact} `);
|
|
111
|
+
}
|
|
112
|
+
function stampText(fact, q, questionsRel, at) {
|
|
113
|
+
return `<!-- ${STAMP_MARKER} ${fact} | q: ${q} | at: ${at} | see: ${questionsRel} -->
|
|
114
|
+
` + `> **Superseded in part by ${fact}** — ${q} was answered after this document was written; ` + `the answer is in \`${questionsRel}\` and \`.tldrx/memory/facts.yml\`. ` + `This document was not reconciled: where the two disagree, the fact is what the workspace believes.
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
function affectedDocs(runDir, questionsPath, block) {
|
|
118
|
+
const self = runRelative(runDir, questionsPath);
|
|
119
|
+
const askedIn = self === null ? null : phaseIdOf(self);
|
|
120
|
+
const found = new Map;
|
|
121
|
+
const consider = (raw, by) => {
|
|
122
|
+
const rel = resolveInRun(runDir, raw);
|
|
123
|
+
if (rel === null || rel === self || !rel.endsWith(".md"))
|
|
124
|
+
return;
|
|
125
|
+
const phase = phaseIdOf(rel);
|
|
126
|
+
if (phase === null)
|
|
127
|
+
return;
|
|
128
|
+
if (by === "cited" && (askedIn === null || !isEarlier(phase, askedIn)))
|
|
129
|
+
return;
|
|
130
|
+
if (!found.has(rel))
|
|
131
|
+
found.set(rel, { rel, abs: join2(runDir, rel), by });
|
|
132
|
+
};
|
|
133
|
+
for (const ref of block.whySrc?.refs ?? []) {
|
|
134
|
+
if (ref.kind === "file")
|
|
135
|
+
consider(ref.path, "cited");
|
|
136
|
+
}
|
|
137
|
+
for (const raw of declaredAffects(block))
|
|
138
|
+
consider(raw, "affects");
|
|
139
|
+
return [...found.values()];
|
|
140
|
+
}
|
|
141
|
+
function stampSuperseded(runDir, questionsPath, block, fact, at) {
|
|
142
|
+
const questionsRel = runRelative(runDir, questionsPath) ?? "questions.md";
|
|
143
|
+
const stamped = [];
|
|
144
|
+
for (const doc of affectedDocs(runDir, questionsPath, block)) {
|
|
145
|
+
const text = readFileSync(doc.abs, "utf8");
|
|
146
|
+
if (isStamped(text, fact))
|
|
147
|
+
continue;
|
|
148
|
+
const lead = text === "" ? "" : text.endsWith(`
|
|
149
|
+
`) ? `
|
|
150
|
+
` : `
|
|
151
|
+
|
|
152
|
+
`;
|
|
153
|
+
appendFileSync(doc.abs, lead + stampText(fact, block.id, questionsRel, at), "utf8");
|
|
154
|
+
stamped.push(doc);
|
|
155
|
+
}
|
|
156
|
+
return stamped;
|
|
157
|
+
}
|
|
158
|
+
function declaredAffects(block) {
|
|
159
|
+
const raw = block.metadata?.extra.find(([key]) => key === AFFECTS_KEY)?.[1] ?? "";
|
|
160
|
+
return raw.split(/[,\s]+/).map((part) => part.trim()).filter((part) => part !== "");
|
|
161
|
+
}
|
|
162
|
+
function resolveInRun(runDir, raw) {
|
|
163
|
+
if (raw === "" || isAbsolute2(raw))
|
|
164
|
+
return null;
|
|
165
|
+
const root = join2(runDir, "..", "..");
|
|
166
|
+
for (const candidate of [join2(runDir, raw), join2(root, raw)]) {
|
|
167
|
+
const rel = runRelative(runDir, candidate);
|
|
168
|
+
if (rel === null)
|
|
169
|
+
continue;
|
|
170
|
+
if (!existsSync(candidate) || !statSync(candidate).isFile())
|
|
171
|
+
continue;
|
|
172
|
+
return rel;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
function runRelative(runDir, path) {
|
|
177
|
+
const rel = relative(runDir, path);
|
|
178
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute2(rel))
|
|
179
|
+
return null;
|
|
180
|
+
return rel.split(/[\\/]/).join("/");
|
|
181
|
+
}
|
|
182
|
+
function phaseIdOf(rel) {
|
|
183
|
+
const first = rel.split("/")[0] ?? "";
|
|
184
|
+
return PHASE_ID_RE.test(first) ? first : null;
|
|
185
|
+
}
|
|
186
|
+
function isEarlier(phase, than) {
|
|
187
|
+
return Number(phase.slice(0, 2)) < Number(than.slice(0, 2));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/core/answers/captureAnswers.ts
|
|
101
191
|
var TRUNCATION_MARK = " …";
|
|
102
192
|
function factTextFor(title, answer) {
|
|
103
193
|
const whole = `${title} — ${answer}`;
|
|
@@ -109,14 +199,15 @@ function factWasTruncated(title, answer) {
|
|
|
109
199
|
return `${title} — ${answer}`.length > MAX_FACT_CHARS;
|
|
110
200
|
}
|
|
111
201
|
function captureAnswers(questionsPath, ctx) {
|
|
112
|
-
if (!
|
|
202
|
+
if (!existsSync2(questionsPath))
|
|
113
203
|
return [];
|
|
114
|
-
let doc = parseQuestions(
|
|
204
|
+
let doc = parseQuestions(readFileSync2(questionsPath, "utf8"));
|
|
115
205
|
const answered = detectAnswered(doc.blocks);
|
|
116
206
|
if (answered.length === 0)
|
|
117
207
|
return [];
|
|
118
208
|
const log = EventLog.forRun(ctx.runDir);
|
|
119
209
|
const captured = [];
|
|
210
|
+
const recorded = [];
|
|
120
211
|
FactsStore.update(factsPath(ctx.root), (store) => {
|
|
121
212
|
for (const block of answered) {
|
|
122
213
|
const area = block.metadata?.area ?? "unscoped";
|
|
@@ -150,11 +241,44 @@ function captureAnswers(questionsPath, ctx) {
|
|
|
150
241
|
payload: { fact: fact.id, area: fact.area, kind: fact.kind, q: block.id }
|
|
151
242
|
});
|
|
152
243
|
captured.push({ q: block.id, fact: fact.id, answer: block.answer, area });
|
|
244
|
+
recorded.push({ block, fact: fact.id });
|
|
153
245
|
}
|
|
154
246
|
});
|
|
155
247
|
writeFileSync(questionsPath, serializeQuestions(doc), "utf8");
|
|
248
|
+
for (const item of recorded) {
|
|
249
|
+
markSuperseded(log, ctx, questionsPath, item.block, item.fact);
|
|
250
|
+
}
|
|
156
251
|
return captured;
|
|
157
252
|
}
|
|
253
|
+
function markSuperseded(log, ctx, questionsPath, block, fact) {
|
|
254
|
+
try {
|
|
255
|
+
for (const doc of stampSuperseded(ctx.runDir, questionsPath, block, fact, ctx.at)) {
|
|
256
|
+
log.tryAppend({
|
|
257
|
+
ts: ctx.at,
|
|
258
|
+
run: ctx.run,
|
|
259
|
+
stage: null,
|
|
260
|
+
type: "doc.superseded",
|
|
261
|
+
actor: ctx.actor,
|
|
262
|
+
cost_usd: 0,
|
|
263
|
+
payload: { doc: doc.rel, fact, q: block.id, by: doc.by }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
log.tryAppend({
|
|
268
|
+
ts: ctx.at,
|
|
269
|
+
run: ctx.run,
|
|
270
|
+
stage: null,
|
|
271
|
+
type: "error",
|
|
272
|
+
actor: ctx.actor,
|
|
273
|
+
cost_usd: 0,
|
|
274
|
+
payload: {
|
|
275
|
+
message: `could not stamp the documents ${block.id} supersedes: ` + (error instanceof Error ? error.message : String(error)),
|
|
276
|
+
q: block.id,
|
|
277
|
+
fact
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
158
282
|
|
|
159
283
|
// src/hooks/answer-capture.ts
|
|
160
284
|
await runHook("answer-capture", async () => {
|
|
@@ -163,7 +287,7 @@ await runHook("answer-capture", async () => {
|
|
|
163
287
|
if (event === "PostToolUse" && payload.tool_name !== "Write" && payload.tool_name !== "Edit")
|
|
164
288
|
return;
|
|
165
289
|
const filePath = filePathOf(payload);
|
|
166
|
-
if (!filePath.endsWith("questions.md") || !
|
|
290
|
+
if (!filePath.endsWith("questions.md") || !existsSync3(filePath))
|
|
167
291
|
return;
|
|
168
292
|
const location = locateWork(filePath);
|
|
169
293
|
if (location === null)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-dhbxzjfs.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-x6n8b5fp.js";
|
|
12
|
+
import"./chunk-vrhczzxs.js";
|
|
13
13
|
import {
|
|
14
14
|
asRunBudget,
|
|
15
15
|
currentActor,
|
|
@@ -29,23 +29,23 @@ import {
|
|
|
29
29
|
validateRunBudget,
|
|
30
30
|
wouldExceed,
|
|
31
31
|
wouldExceedHostTokens
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-7eqhfddw.js";
|
|
33
33
|
import {
|
|
34
34
|
EventLog
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import"./chunk-
|
|
35
|
+
} from "./chunk-h7151g8w.js";
|
|
36
|
+
import"./chunk-rrkdfk7s.js";
|
|
37
|
+
import"./chunk-g4db8rbs.js";
|
|
37
38
|
import {
|
|
38
39
|
noteDeprecations
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-97ncnegd.js";
|
|
40
41
|
import"./chunk-39zh2e44.js";
|
|
41
|
-
import"./chunk-q8d3sff9.js";
|
|
42
42
|
import {
|
|
43
43
|
PROJECT_WORK_DIR,
|
|
44
44
|
findWorkspaceRoot,
|
|
45
45
|
locateWork,
|
|
46
46
|
parseYaml,
|
|
47
47
|
stageYamlPath
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-9w64mxdh.js";
|
|
49
49
|
|
|
50
50
|
// src/hooks/budget-gate.ts
|
|
51
51
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
withWorkspaceLock,
|
|
4
4
|
workspaceRootOfFactsPath,
|
|
5
5
|
writeAtomic
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-v4ay50nq.js";
|
|
7
7
|
import {
|
|
8
8
|
FACT_CONFIDENCES,
|
|
9
9
|
FACT_KINDS,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isLive,
|
|
15
15
|
isRetired,
|
|
16
16
|
noteDeprecations
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-97ncnegd.js";
|
|
18
18
|
import {
|
|
19
19
|
asDocument,
|
|
20
20
|
isRecord,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
SRC_PATTERNS,
|
|
30
30
|
parseYaml,
|
|
31
31
|
readableSource
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-9w64mxdh.js";
|
|
33
33
|
|
|
34
34
|
// src/core/facts/FactsStore.ts
|
|
35
35
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -2,24 +2,24 @@ import {
|
|
|
2
2
|
DEFAULT_ECONOMY,
|
|
3
3
|
DEFAULT_ON_HOST_TOKENS_EXCEED,
|
|
4
4
|
asRunBudget,
|
|
5
|
-
asRunFile,
|
|
6
5
|
cursorStage,
|
|
6
|
+
isAttendedByHostView,
|
|
7
|
+
nowRfc3339,
|
|
8
|
+
openRunViews,
|
|
9
|
+
validateRunBudget
|
|
10
|
+
} from "./chunk-7eqhfddw.js";
|
|
11
|
+
import {
|
|
12
|
+
EventLog,
|
|
13
|
+
asRunFile,
|
|
7
14
|
derivePhaseStatus,
|
|
8
15
|
deriveRunStatus,
|
|
9
16
|
flatten,
|
|
10
17
|
isAttendedByHost,
|
|
11
|
-
isAttendedByHostView,
|
|
12
18
|
isFinished,
|
|
13
19
|
isTerminal,
|
|
14
|
-
nowRfc3339,
|
|
15
|
-
openRunViews,
|
|
16
20
|
stageAt,
|
|
17
|
-
validateRunBudget,
|
|
18
21
|
validateRunFile
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import {
|
|
21
|
-
EventLog
|
|
22
|
-
} from "./chunk-sae7sqty.js";
|
|
22
|
+
} from "./chunk-h7151g8w.js";
|
|
23
23
|
import {
|
|
24
24
|
backupPathFor,
|
|
25
25
|
isAlive,
|
|
@@ -28,17 +28,17 @@ import {
|
|
|
28
28
|
workspaceRootOfRunDir,
|
|
29
29
|
writeAtomic,
|
|
30
30
|
yamlScalar
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-v4ay50nq.js";
|
|
32
32
|
import {
|
|
33
33
|
noteDeprecations,
|
|
34
34
|
openBlocks,
|
|
35
35
|
parseQuestions
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-97ncnegd.js";
|
|
37
37
|
import {
|
|
38
38
|
listRunDirs,
|
|
39
39
|
parseYaml,
|
|
40
40
|
parseYamlRepairing
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-9w64mxdh.js";
|
|
42
42
|
|
|
43
43
|
// src/core/statusline/runSnapshot.ts
|
|
44
44
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
@@ -62,9 +62,18 @@ function gatesPolicy(policy) {
|
|
|
62
62
|
function gateEvidence(e) {
|
|
63
63
|
return `{path: ${yamlScalar(e.path)}, role: ${yamlScalar(e.role)}, verdict: ${yamlScalar(e.verdict)}, ` + `sampled: ${String(e.sampled)}, of: ${String(e.of)}, resolved: ${String(e.resolved)}, ` + `refuted: ${String(e.refuted)}, outside_surface: ${String(e.outside_surface)}}`;
|
|
64
64
|
}
|
|
65
|
+
function gateExecutor(e) {
|
|
66
|
+
const id = e.id === undefined ? "" : `, id: ${yamlScalar(e.id)}`;
|
|
67
|
+
return `{type: ${yamlScalar(e.type)}${id}}`;
|
|
68
|
+
}
|
|
69
|
+
function gateAuthority(a) {
|
|
70
|
+
return `{type: ${yamlScalar(a.type)}, policy: ${yamlScalar(a.policy)}, ` + `authorized_by: ${yamlScalar(a.authorized_by)}, source: ${yamlScalar(a.source)}}`;
|
|
71
|
+
}
|
|
65
72
|
function gate(g) {
|
|
66
73
|
const evidence = g.evidence === undefined ? "" : `, evidence: ${gateEvidence(g.evidence)}`;
|
|
67
|
-
|
|
74
|
+
const executor = g.executed_by === undefined ? "" : `, executed_by: ${gateExecutor(g.executed_by)}`;
|
|
75
|
+
const authority = g.authority === undefined ? "" : `, authority: ${gateAuthority(g.authority)}`;
|
|
76
|
+
return `{type: ${yamlScalar(g.type)}, status: ${yamlScalar(g.status)}, by: ${yamlScalar(g.by)}, ` + `at: ${yamlScalar(g.at)}, note: ${yamlScalar(g.note)}${evidence}${executor}${authority}}`;
|
|
68
77
|
}
|
|
69
78
|
function task(t, indent) {
|
|
70
79
|
const inner = `${indent} `;
|
|
@@ -407,6 +416,32 @@ function round(n) {
|
|
|
407
416
|
return Math.round(n * 100) / 100;
|
|
408
417
|
}
|
|
409
418
|
|
|
419
|
+
// src/core/run/autoGate.ts
|
|
420
|
+
var AUTO_GATE_ACTOR = "auto";
|
|
421
|
+
|
|
422
|
+
// src/core/run/gateAuthority.ts
|
|
423
|
+
var UNRECORDED_AUTHORIZER = "unrecorded";
|
|
424
|
+
function closedByMachine(sig) {
|
|
425
|
+
const executed = sig.executed_by ?? null;
|
|
426
|
+
if (executed !== null)
|
|
427
|
+
return executed.type !== "human";
|
|
428
|
+
return sig.by === AUTO_GATE_ACTOR;
|
|
429
|
+
}
|
|
430
|
+
function describeGateSignature(sig) {
|
|
431
|
+
const by = sig.by ?? "?";
|
|
432
|
+
const executed = sig.executed_by ?? null;
|
|
433
|
+
if (executed === null)
|
|
434
|
+
return by;
|
|
435
|
+
const authority = sig.authority ?? null;
|
|
436
|
+
if (executed.type === "human" && (authority === null || authority.type === "direct"))
|
|
437
|
+
return by;
|
|
438
|
+
const who = executed.type === "auto" ? "auto" : `${executed.type} ${executed.id ?? by}`;
|
|
439
|
+
if (authority === null)
|
|
440
|
+
return who;
|
|
441
|
+
const granter = authority.authorized_by ?? UNRECORDED_AUTHORIZER;
|
|
442
|
+
return `${who} (${authority.type} by ${granter}, policy: ${authority.policy})`;
|
|
443
|
+
}
|
|
444
|
+
|
|
410
445
|
// src/core/facilitator/skipIf.ts
|
|
411
446
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
412
447
|
function phaseDirs(runDir) {
|
|
@@ -427,7 +462,6 @@ function openQuestionIds(path) {
|
|
|
427
462
|
}
|
|
428
463
|
|
|
429
464
|
// src/core/statusline/runSnapshot.ts
|
|
430
|
-
var AUTO = "auto";
|
|
431
465
|
function runSnapshot(root) {
|
|
432
466
|
return fromStore(root) ?? fromTolerantRead(root);
|
|
433
467
|
}
|
|
@@ -466,7 +500,7 @@ function fromStore(root) {
|
|
|
466
500
|
ceilingUsd: run.budget.ceiling_usd,
|
|
467
501
|
spentUsd: run.budget.spent_usd,
|
|
468
502
|
openCount: open.length,
|
|
469
|
-
|
|
503
|
+
machineGates: stages.filter((s) => s.gate.status === "approved" && closedByMachine(s.gate)).length,
|
|
470
504
|
staleStages: stages.filter((s) => s.stale === true).length,
|
|
471
505
|
attendedByHost: isAttendedByHost(run),
|
|
472
506
|
source: "run-store"
|
|
@@ -495,7 +529,7 @@ function fromTolerantRead(root) {
|
|
|
495
529
|
ceilingUsd: budget.ceiling_usd,
|
|
496
530
|
spentUsd: budget.spent_usd,
|
|
497
531
|
openCount: open.length,
|
|
498
|
-
|
|
532
|
+
machineGates: 0,
|
|
499
533
|
staleStages: 0,
|
|
500
534
|
attendedByHost: isAttendedByHostView(view),
|
|
501
535
|
source: "tolerant"
|
|
@@ -685,4 +719,4 @@ function whatIsWaiting(run, runDir) {
|
|
|
685
719
|
return waitingFor(run, runDir);
|
|
686
720
|
}
|
|
687
721
|
|
|
688
|
-
export { RunStore, runSnapshot, openQuestions, isMovable, bar, whatIsWaiting };
|
|
722
|
+
export { RunStore, closedByMachine, describeGateSignature, runSnapshot, openQuestions, isMovable, bar, whatIsWaiting };
|