tldr-experts 0.9.2 → 0.11.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 +286 -0
- package/README.md +6 -2
- package/dist/hooks/answer-capture.js +297 -25
- package/dist/hooks/budget-gate.js +7 -10
- package/dist/hooks/{chunk-hg43zexg.js → chunk-1c43349s.js} +65 -51
- package/dist/hooks/{chunk-x3zy7zrn.js → chunk-6c84kjd7.js} +1 -1
- package/dist/hooks/{chunk-2y3kwfrx.js → chunk-6ws778wq.js} +1 -1
- package/dist/hooks/{chunk-fv66tdsm.js → chunk-9v45j71e.js} +23 -8
- package/dist/hooks/chunk-bk7rkdzw.js +103 -0
- package/dist/hooks/{chunk-4w03q2kj.js → chunk-d20x2aew.js} +27 -19
- package/dist/hooks/{chunk-rk1nm39z.js → chunk-ecwes7kd.js} +1 -1
- package/dist/hooks/{chunk-f6484ykg.js → chunk-kqvbzyc0.js} +24 -10
- package/dist/hooks/{chunk-y3cxqvnx.js → chunk-r1efatxk.js} +411 -10
- package/dist/hooks/{chunk-cf4az0j5.js → chunk-stw0dxna.js} +1 -1
- package/dist/hooks/{chunk-8awygmqw.js → chunk-tg7ppsxr.js} +4 -1
- package/dist/hooks/{chunk-vyvjbbmb.js → chunk-wzzywf6c.js} +32 -2
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +8 -99
- package/dist/hooks/no-reask.js +8 -9
- package/dist/hooks/session-start.js +38 -123
- package/dist/hooks/statusline.js +6 -9
- package/dist/tldrx.js +3503 -1648
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/build/stage.yml +14 -0
- package/stages/how/stage.yml +6 -0
- package/stages/plan/stage.yml +6 -0
- package/stages/watch/stage.yml +6 -0
- package/stages/what/stage.yml +6 -0
- package/templates/experts/stack/dotnet.md +4 -3
- package/templates/experts/stack/javascript.md +4 -3
- package/templates/experts/stack/python.md +4 -3
- package/templates/experts/stack/typescript.md +4 -3
- package/templates/workspace.yml +6 -0
- package/workflows/bugfix.yml +7 -0
- package/workflows/docs.yml +7 -0
- package/workflows/feature.yml +7 -0
- package/workflows/hotfix.yml +7 -0
- package/workflows/integration.yml +7 -0
- package/workflows/migration.yml +7 -0
- package/workflows/performance.yml +7 -0
- package/workflows/prototype.yml +7 -0
- package/workflows/refactor.yml +7 -0
- package/workflows/retro.yml +7 -0
- package/workflows/security-patch.yml +7 -0
- package/workflows/spike.yml +7 -0
- package/workflows/upgrade.yml +7 -0
- package/dist/hooks/chunk-39zh2e44.js +0 -97
- package/dist/hooks/chunk-5w5eg00k.js +0 -136
- package/dist/hooks/chunk-rrkdfk7s.js +0 -30
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
conflictOf
|
|
4
|
+
} from "./chunk-bk7rkdzw.js";
|
|
5
|
+
import {
|
|
6
|
+
FactsStore,
|
|
7
|
+
formatJaccard
|
|
8
|
+
} from "./chunk-kqvbzyc0.js";
|
|
5
9
|
import {
|
|
6
10
|
parseHookInput,
|
|
7
11
|
readStdin
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6c84kjd7.js";
|
|
9
13
|
import {
|
|
10
14
|
EventLog,
|
|
11
15
|
PHASE_ID_RE
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import"./chunk-
|
|
14
|
-
import"./chunk-8awygmqw.js";
|
|
16
|
+
} from "./chunk-9v45j71e.js";
|
|
17
|
+
import"./chunk-tg7ppsxr.js";
|
|
15
18
|
import {
|
|
19
|
+
ADVISORY_KEY,
|
|
16
20
|
MAX_FACT_CHARS,
|
|
17
21
|
detectAnswered,
|
|
18
22
|
parseQuestions,
|
|
19
23
|
recordAnswer,
|
|
24
|
+
renderQuestionBlock,
|
|
20
25
|
replaceBlock,
|
|
21
26
|
serializeQuestions
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import"./chunk-39zh2e44.js";
|
|
27
|
+
} from "./chunk-wzzywf6c.js";
|
|
24
28
|
import {
|
|
29
|
+
ITERATION_ONLY_SLOT,
|
|
30
|
+
PROJECT_FRAMEWORK_DIR,
|
|
25
31
|
PROJECT_WORK_DIR,
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
commandProbeIssues,
|
|
33
|
+
factsPath,
|
|
34
|
+
parseYaml
|
|
35
|
+
} from "./chunk-r1efatxk.js";
|
|
28
36
|
|
|
29
37
|
// src/hooks/answer-capture.ts
|
|
30
|
-
import { existsSync as
|
|
38
|
+
import { existsSync as existsSync5 } from "fs";
|
|
31
39
|
|
|
32
40
|
// src/hooks/lib/decide.ts
|
|
33
41
|
function allow() {
|
|
@@ -69,6 +77,7 @@ function filePathOf(payload) {
|
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
// src/hooks/lib/workspace.ts
|
|
80
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
72
81
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
73
82
|
function locateWork(filePath) {
|
|
74
83
|
if (filePath === "")
|
|
@@ -88,6 +97,111 @@ function locateWork(filePath) {
|
|
|
88
97
|
relative: parts.slice(at + 2).join("/")
|
|
89
98
|
};
|
|
90
99
|
}
|
|
100
|
+
var FALLBACK_DEFAULT_BRANCH = "main";
|
|
101
|
+
function commandRolesOf(doc) {
|
|
102
|
+
const out = new Map;
|
|
103
|
+
const list = doc?.repos;
|
|
104
|
+
if (!Array.isArray(list))
|
|
105
|
+
return out;
|
|
106
|
+
for (const entry of list) {
|
|
107
|
+
if (typeof entry?.name !== "string")
|
|
108
|
+
continue;
|
|
109
|
+
const roles = new Map;
|
|
110
|
+
const cmds = entry.commands;
|
|
111
|
+
if (cmds !== null && typeof cmds === "object") {
|
|
112
|
+
for (const [role, value] of Object.entries(cmds)) {
|
|
113
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
114
|
+
continue;
|
|
115
|
+
roles.set(role, value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
out.set(entry.name, roles);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
function commandProbesOf(doc) {
|
|
123
|
+
const out = new Map;
|
|
124
|
+
const list = doc?.repos;
|
|
125
|
+
if (!Array.isArray(list))
|
|
126
|
+
return out;
|
|
127
|
+
for (const entry of list) {
|
|
128
|
+
if (typeof entry?.name !== "string")
|
|
129
|
+
continue;
|
|
130
|
+
const probes = new Map;
|
|
131
|
+
const raw = entry.command_probes;
|
|
132
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
133
|
+
for (const [slot, value] of Object.entries(raw)) {
|
|
134
|
+
if (commandProbeIssues(value, slot).length > 0)
|
|
135
|
+
continue;
|
|
136
|
+
probes.set(slot, value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
out.set(entry.name, probes);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
function loadWorkspace(root) {
|
|
144
|
+
const repos = new Map;
|
|
145
|
+
const commands = new Set;
|
|
146
|
+
const repoCommands = new Map;
|
|
147
|
+
const commandRoles = new Map;
|
|
148
|
+
const commandProbes = new Map;
|
|
149
|
+
const iterationCommands = new Set;
|
|
150
|
+
const defaultBranches = new Map;
|
|
151
|
+
let seedTriageThresholdTokens = null;
|
|
152
|
+
const empty = () => ({
|
|
153
|
+
root,
|
|
154
|
+
repos,
|
|
155
|
+
commands,
|
|
156
|
+
repoCommands,
|
|
157
|
+
commandRoles,
|
|
158
|
+
commandProbes,
|
|
159
|
+
iterationCommands,
|
|
160
|
+
defaultBranches,
|
|
161
|
+
seedTriageThresholdTokens
|
|
162
|
+
});
|
|
163
|
+
const path = join(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
|
|
164
|
+
if (!existsSync(path))
|
|
165
|
+
return empty();
|
|
166
|
+
let doc;
|
|
167
|
+
try {
|
|
168
|
+
doc = parseYaml(readFileSync(path, "utf8"));
|
|
169
|
+
} catch {
|
|
170
|
+
return empty();
|
|
171
|
+
}
|
|
172
|
+
const triage = doc?.seed_triage;
|
|
173
|
+
if (triage !== null && typeof triage === "object") {
|
|
174
|
+
const tokens = triage.threshold_tokens;
|
|
175
|
+
if (typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0) {
|
|
176
|
+
seedTriageThresholdTokens = tokens;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const list = doc?.repos;
|
|
180
|
+
if (!Array.isArray(list))
|
|
181
|
+
return empty();
|
|
182
|
+
const declared = commandRolesOf(doc);
|
|
183
|
+
const measured = commandProbesOf(doc);
|
|
184
|
+
for (const entry of list) {
|
|
185
|
+
if (typeof entry?.name !== "string")
|
|
186
|
+
continue;
|
|
187
|
+
repos.set(entry.name, typeof entry.path === "string" ? entry.path : ".");
|
|
188
|
+
defaultBranches.set(entry.name, typeof entry.default_branch === "string" && entry.default_branch !== "" ? entry.default_branch : FALLBACK_DEFAULT_BRANCH);
|
|
189
|
+
const own = [];
|
|
190
|
+
const roles = declared.get(entry.name) ?? new Map;
|
|
191
|
+
for (const value of roles.values()) {
|
|
192
|
+
commands.add(value);
|
|
193
|
+
if (!own.includes(value))
|
|
194
|
+
own.push(value);
|
|
195
|
+
}
|
|
196
|
+
repoCommands.set(entry.name, own);
|
|
197
|
+
commandRoles.set(entry.name, roles);
|
|
198
|
+
const fast = roles.get(ITERATION_ONLY_SLOT);
|
|
199
|
+
if (fast !== undefined)
|
|
200
|
+
iterationCommands.add(fast);
|
|
201
|
+
commandProbes.set(entry.name, measured.get(entry.name) ?? new Map);
|
|
202
|
+
}
|
|
203
|
+
return empty();
|
|
204
|
+
}
|
|
91
205
|
|
|
92
206
|
// src/hooks/lib/actor.ts
|
|
93
207
|
function currentActor() {
|
|
@@ -99,10 +213,10 @@ function nowRfc3339() {
|
|
|
99
213
|
}
|
|
100
214
|
|
|
101
215
|
// src/core/answers/captureAnswers.ts
|
|
102
|
-
import { existsSync as
|
|
216
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
103
217
|
|
|
104
218
|
// src/core/answers/stampSuperseded.ts
|
|
105
|
-
import { appendFileSync, existsSync, readFileSync, statSync } from "node:fs";
|
|
219
|
+
import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
106
220
|
import { isAbsolute as isAbsolute2, join as join2, relative } from "node:path";
|
|
107
221
|
var STAMP_MARKER = "tldrx:superseded";
|
|
108
222
|
var AFFECTS_KEY = "affects";
|
|
@@ -142,7 +256,7 @@ function stampSuperseded(runDir, questionsPath, block, fact, at) {
|
|
|
142
256
|
const questionsRel = runRelative(runDir, questionsPath) ?? "questions.md";
|
|
143
257
|
const stamped = [];
|
|
144
258
|
for (const doc of affectedDocs(runDir, questionsPath, block)) {
|
|
145
|
-
const text =
|
|
259
|
+
const text = readFileSync2(doc.abs, "utf8");
|
|
146
260
|
if (isStamped(text, fact))
|
|
147
261
|
continue;
|
|
148
262
|
const lead = text === "" ? "" : text.endsWith(`
|
|
@@ -167,7 +281,7 @@ function resolveInRun(runDir, raw) {
|
|
|
167
281
|
const rel = runRelative(runDir, candidate);
|
|
168
282
|
if (rel === null)
|
|
169
283
|
continue;
|
|
170
|
-
if (!
|
|
284
|
+
if (!existsSync2(candidate) || !statSync2(candidate).isFile())
|
|
171
285
|
continue;
|
|
172
286
|
return rel;
|
|
173
287
|
}
|
|
@@ -187,7 +301,113 @@ function isEarlier(phase, than) {
|
|
|
187
301
|
return Number(phase.slice(0, 2)) < Number(than.slice(0, 2));
|
|
188
302
|
}
|
|
189
303
|
|
|
304
|
+
// src/core/answers/reposFromAffects.ts
|
|
305
|
+
function uniqueRepos(names) {
|
|
306
|
+
return [...new Set(names)];
|
|
307
|
+
}
|
|
308
|
+
function reposFromAffects(affects, repoNames) {
|
|
309
|
+
const repos = [];
|
|
310
|
+
const unresolved = [];
|
|
311
|
+
for (const raw of affects) {
|
|
312
|
+
const entry = raw.trim();
|
|
313
|
+
if (entry === "")
|
|
314
|
+
continue;
|
|
315
|
+
if (repoNames.has(entry)) {
|
|
316
|
+
repos.push(entry);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const colon = entry.indexOf(":");
|
|
320
|
+
if (colon <= 0)
|
|
321
|
+
continue;
|
|
322
|
+
const prefix = entry.slice(0, colon);
|
|
323
|
+
if (repoNames.has(prefix)) {
|
|
324
|
+
repos.push(prefix);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
unresolved.push(entry);
|
|
328
|
+
}
|
|
329
|
+
return { repos: uniqueRepos(repos), unresolved };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/core/answers/raiseConflict.ts
|
|
333
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
334
|
+
import { join as join3 } from "node:path";
|
|
335
|
+
|
|
336
|
+
// src/core/run/questionCards.ts
|
|
337
|
+
var QUESTION_PHASES = ["01-what", "02-how", "03-plan", "04-build", "05-watch"];
|
|
338
|
+
|
|
339
|
+
// src/core/answers/raiseConflict.ts
|
|
340
|
+
var RAISED_BY = "tldrx";
|
|
341
|
+
function nextQuestionId(runDir) {
|
|
342
|
+
let highest = 0;
|
|
343
|
+
for (const phase of QUESTION_PHASES) {
|
|
344
|
+
const path = join3(runDir, phase, "questions.md");
|
|
345
|
+
if (!existsSync3(path))
|
|
346
|
+
continue;
|
|
347
|
+
let blocks;
|
|
348
|
+
try {
|
|
349
|
+
blocks = parseQuestions(readFileSync3(path, "utf8")).blocks;
|
|
350
|
+
} catch {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
for (const block of blocks) {
|
|
354
|
+
const n = Number.parseInt(block.id.slice(1), 10);
|
|
355
|
+
if (Number.isFinite(n) && n > highest)
|
|
356
|
+
highest = n;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return `Q${String(highest + 1)}`;
|
|
360
|
+
}
|
|
361
|
+
function raiseConflictQuestion(args) {
|
|
362
|
+
const id = nextQuestionId(args.runDir);
|
|
363
|
+
const block = renderQuestionBlock({
|
|
364
|
+
id,
|
|
365
|
+
title: `Which is right about ${args.area}: ${args.newFactId} or ${args.oldFactId}?`,
|
|
366
|
+
metadata: {
|
|
367
|
+
id,
|
|
368
|
+
status: "open",
|
|
369
|
+
area: args.area,
|
|
370
|
+
asked_by: RAISED_BY,
|
|
371
|
+
asked_at: args.at,
|
|
372
|
+
extra: [[ADVISORY_KEY, "true"]]
|
|
373
|
+
},
|
|
374
|
+
metadataIndex: -1,
|
|
375
|
+
whyAsked: `answering ${args.answeredQ} recorded ${args.newFactId}, which overlaps ${args.oldFactId}'s ` + `text at Jaccard ${formatJaccard(args.score)} on the question's wording, in the same area, ` + `and answers it differently — ${args.oldFactId}: "${oneLine(args.oldFactText)}" ` + `[src: ${args.oldFactId}]`,
|
|
376
|
+
whySrc: null,
|
|
377
|
+
options: [
|
|
378
|
+
{ letter: "A", text: `${args.newFactId} is right — supersede ${args.oldFactId}` },
|
|
379
|
+
{ letter: "B", text: `${args.oldFactId} is right — supersede ${args.newFactId}` },
|
|
380
|
+
{ letter: "C", text: "Both are partly right — write the correction below" }
|
|
381
|
+
],
|
|
382
|
+
answer: "",
|
|
383
|
+
answerIndex: -1,
|
|
384
|
+
footer: null,
|
|
385
|
+
startLine: -1,
|
|
386
|
+
lines: []
|
|
387
|
+
});
|
|
388
|
+
appendFileSync2(args.questionsPath, `
|
|
389
|
+
${block}
|
|
390
|
+
`, "utf8");
|
|
391
|
+
return { q: id, fact: args.newFactId, conflictsWith: args.oldFactId, score: args.score };
|
|
392
|
+
}
|
|
393
|
+
function oneLine(text) {
|
|
394
|
+
return text.replace(/\s+/g, " ").trim();
|
|
395
|
+
}
|
|
396
|
+
|
|
190
397
|
// src/core/answers/captureAnswers.ts
|
|
398
|
+
function answerProvenance(block, overrides, repoNames, fallbackRepos) {
|
|
399
|
+
const override = overrides?.get(block.id);
|
|
400
|
+
const named = reposFromAffects(declaredAffects(block), repoNames ?? new Set);
|
|
401
|
+
const repos = override?.repos ?? (named.repos.length > 0 ? named.repos : fallbackRepos);
|
|
402
|
+
return {
|
|
403
|
+
repos,
|
|
404
|
+
unresolved: named.unresolved,
|
|
405
|
+
source: override?.decidedBy === undefined ? {} : { decided_by: override.decidedBy }
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function unresolvedEntries(captured) {
|
|
409
|
+
return captured.flatMap((c) => c.unresolvedAffects.map((entry) => `${c.q}: affects: ${entry} names no repo in this workspace — it scoped nothing`));
|
|
410
|
+
}
|
|
191
411
|
var TRUNCATION_MARK = " …";
|
|
192
412
|
function factTextFor(title, answer) {
|
|
193
413
|
const whole = `${title} — ${answer}`;
|
|
@@ -199,28 +419,35 @@ function factWasTruncated(title, answer) {
|
|
|
199
419
|
return `${title} — ${answer}`.length > MAX_FACT_CHARS;
|
|
200
420
|
}
|
|
201
421
|
function captureAnswers(questionsPath, ctx) {
|
|
202
|
-
if (!
|
|
422
|
+
if (!existsSync4(questionsPath))
|
|
203
423
|
return [];
|
|
204
|
-
let doc = parseQuestions(
|
|
424
|
+
let doc = parseQuestions(readFileSync4(questionsPath, "utf8"));
|
|
205
425
|
const answered = detectAnswered(doc.blocks);
|
|
206
426
|
if (answered.length === 0)
|
|
207
427
|
return [];
|
|
208
428
|
const log = EventLog.forRun(ctx.runDir);
|
|
209
429
|
const captured = [];
|
|
210
430
|
const recorded = [];
|
|
431
|
+
const clashes = [];
|
|
211
432
|
FactsStore.update(factsPath(ctx.root), (store) => {
|
|
212
433
|
for (const block of answered) {
|
|
213
434
|
const area = block.metadata?.area ?? "unscoped";
|
|
214
435
|
const truncated = factWasTruncated(block.title, block.answer);
|
|
436
|
+
const prov = answerProvenance(block, ctx.overrides, ctx.repoNames, []);
|
|
437
|
+
const text = factTextFor(block.title, block.answer);
|
|
438
|
+
const clash = conflictOf({ match: block.title, area, text }, store.active);
|
|
215
439
|
const fact = store.append({
|
|
216
|
-
fact:
|
|
440
|
+
fact: text,
|
|
217
441
|
...truncated ? { truncated: true } : {},
|
|
442
|
+
...clash === null ? {} : { conflicts_with: [clash.fact.id] },
|
|
218
443
|
area,
|
|
219
|
-
repos:
|
|
444
|
+
repos: prov.repos,
|
|
220
445
|
kind: "answer",
|
|
221
446
|
confidence: "stated",
|
|
222
|
-
source: { who: ctx.actor, when: ctx.at, run: ctx.run, q: block.id }
|
|
447
|
+
source: { who: ctx.actor, when: ctx.at, run: ctx.run, q: block.id, ...prov.source }
|
|
223
448
|
});
|
|
449
|
+
if (clash !== null)
|
|
450
|
+
clashes.push({ block, area, factId: fact.id, hit: clash });
|
|
224
451
|
doc = replaceBlock(doc, recordAnswer(block, { answered_by: ctx.actor, answered_at: ctx.at, fact: fact.id }));
|
|
225
452
|
log.tryAppend({
|
|
226
453
|
ts: ctx.at,
|
|
@@ -240,15 +467,58 @@ function captureAnswers(questionsPath, ctx) {
|
|
|
240
467
|
cost_usd: 0,
|
|
241
468
|
payload: { fact: fact.id, area: fact.area, kind: fact.kind, q: block.id }
|
|
242
469
|
});
|
|
243
|
-
captured.push({
|
|
470
|
+
captured.push({
|
|
471
|
+
q: block.id,
|
|
472
|
+
fact: fact.id,
|
|
473
|
+
answer: block.answer,
|
|
474
|
+
area,
|
|
475
|
+
unresolvedAffects: prov.unresolved
|
|
476
|
+
});
|
|
244
477
|
recorded.push({ block, fact: fact.id });
|
|
245
478
|
}
|
|
246
479
|
});
|
|
247
480
|
writeFileSync(questionsPath, serializeQuestions(doc), "utf8");
|
|
481
|
+
const raised = raiseAll(log, ctx, questionsPath, clashes);
|
|
248
482
|
for (const item of recorded) {
|
|
249
483
|
markSuperseded(log, ctx, questionsPath, item.block, item.fact);
|
|
250
484
|
}
|
|
251
|
-
return captured
|
|
485
|
+
return captured.map((answer) => {
|
|
486
|
+
const conflict = raised.get(answer.q);
|
|
487
|
+
return conflict === undefined ? answer : { ...answer, conflict };
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function raiseAll(log, ctx, questionsPath, clashes) {
|
|
491
|
+
const raised = new Map;
|
|
492
|
+
for (const clash of clashes) {
|
|
493
|
+
const conflict = raiseConflictQuestion({
|
|
494
|
+
runDir: ctx.runDir,
|
|
495
|
+
questionsPath,
|
|
496
|
+
area: clash.area,
|
|
497
|
+
at: ctx.at,
|
|
498
|
+
newFactId: clash.factId,
|
|
499
|
+
oldFactId: clash.hit.fact.id,
|
|
500
|
+
oldFactText: clash.hit.fact.fact,
|
|
501
|
+
score: clash.hit.score,
|
|
502
|
+
answeredQ: clash.block.id
|
|
503
|
+
});
|
|
504
|
+
raised.set(clash.block.id, conflict);
|
|
505
|
+
log.tryAppend({
|
|
506
|
+
ts: ctx.at,
|
|
507
|
+
run: ctx.run,
|
|
508
|
+
stage: null,
|
|
509
|
+
type: "fact.conflict_raised",
|
|
510
|
+
actor: ctx.actor,
|
|
511
|
+
cost_usd: 0,
|
|
512
|
+
payload: {
|
|
513
|
+
fact: clash.factId,
|
|
514
|
+
conflicts_with: clash.hit.fact.id,
|
|
515
|
+
score: clash.hit.score,
|
|
516
|
+
q: clash.block.id,
|
|
517
|
+
raised: conflict.q
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return raised;
|
|
252
522
|
}
|
|
253
523
|
function markSuperseded(log, ctx, questionsPath, block, fact) {
|
|
254
524
|
try {
|
|
@@ -287,7 +557,7 @@ await runHook("answer-capture", async () => {
|
|
|
287
557
|
if (event === "PostToolUse" && payload.tool_name !== "Write" && payload.tool_name !== "Edit")
|
|
288
558
|
return;
|
|
289
559
|
const filePath = filePathOf(payload);
|
|
290
|
-
if (!filePath.endsWith("questions.md") || !
|
|
560
|
+
if (!filePath.endsWith("questions.md") || !existsSync5(filePath))
|
|
291
561
|
return;
|
|
292
562
|
const location = locateWork(filePath);
|
|
293
563
|
if (location === null)
|
|
@@ -297,10 +567,12 @@ await runHook("answer-capture", async () => {
|
|
|
297
567
|
runDir: location.runDir,
|
|
298
568
|
run: location.run,
|
|
299
569
|
actor: currentActor(),
|
|
300
|
-
at: nowRfc3339()
|
|
570
|
+
at: nowRfc3339(),
|
|
571
|
+
repoNames: new Set(loadWorkspace(location.root).repos.keys())
|
|
301
572
|
});
|
|
302
573
|
if (captured.length === 0)
|
|
303
574
|
return;
|
|
304
|
-
|
|
575
|
+
const unresolved = unresolvedEntries(captured);
|
|
576
|
+
postContext(`tldrx: recorded ${captured.map((c) => `${c.q} \u2192 ${c.fact}`).join(", ")}` + (unresolved.length === 0 ? "" : ` \u2014 ${unresolved.join("; ")}`));
|
|
305
577
|
});
|
|
306
578
|
allow();
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
budgetGateDeny
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-stw0dxna.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-6ws778wq.js";
|
|
12
|
+
import"./chunk-6c84kjd7.js";
|
|
13
13
|
import {
|
|
14
14
|
asRunBudget,
|
|
15
15
|
currentActor,
|
|
@@ -29,23 +29,20 @@ import {
|
|
|
29
29
|
validateRunBudget,
|
|
30
30
|
wouldExceed,
|
|
31
31
|
wouldExceedHostTokens
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-1c43349s.js";
|
|
33
33
|
import {
|
|
34
34
|
EventLog
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import"./chunk-rrkdfk7s.js";
|
|
37
|
-
import"./chunk-5w5eg00k.js";
|
|
35
|
+
} from "./chunk-9v45j71e.js";
|
|
38
36
|
import {
|
|
39
37
|
noteDeprecations
|
|
40
|
-
} from "./chunk-
|
|
41
|
-
import"./chunk-39zh2e44.js";
|
|
38
|
+
} from "./chunk-wzzywf6c.js";
|
|
42
39
|
import {
|
|
43
40
|
PROJECT_WORK_DIR,
|
|
44
41
|
findWorkspaceRoot,
|
|
45
42
|
locateWork,
|
|
46
43
|
parseYaml,
|
|
47
44
|
stageYamlPath
|
|
48
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-r1efatxk.js";
|
|
49
46
|
|
|
50
47
|
// src/hooks/budget-gate.ts
|
|
51
48
|
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|