tldr-experts 0.3.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 +913 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/hooks/answer-capture.js +174 -0
- package/dist/hooks/budget-gate.js +173 -0
- package/dist/hooks/chunk-0bt6yb2q.js +88 -0
- package/dist/hooks/chunk-1zwcxd3f.js +66 -0
- package/dist/hooks/chunk-9gb21660.js +66 -0
- package/dist/hooks/chunk-a8p2rc94.js +20 -0
- package/dist/hooks/chunk-ae6bkfs5.js +0 -0
- package/dist/hooks/chunk-azctppjh.js +198 -0
- package/dist/hooks/chunk-g395gk7e.js +503 -0
- package/dist/hooks/chunk-j234zf0t.js +339 -0
- package/dist/hooks/chunk-kw4tffzf.js +139 -0
- package/dist/hooks/chunk-p274ckxv.js +7435 -0
- package/dist/hooks/chunk-sdjnnmzz.js +497 -0
- package/dist/hooks/chunk-t56k6146.js +14 -0
- package/dist/hooks/chunk-t8tdv11p.js +35 -0
- package/dist/hooks/chunk-x98qs959.js +302 -0
- package/dist/hooks/chunk-y0jdr3et.js +627 -0
- package/dist/hooks/claim-sources.js +72 -0
- package/dist/hooks/dod-gate.js +198 -0
- package/dist/hooks/no-reask.js +67 -0
- package/dist/hooks/session-start.js +1348 -0
- package/dist/hooks/statusline.js +116 -0
- package/dist/tldrx.js +34219 -0
- package/env.yml +79 -0
- package/package.json +60 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/README.md +113 -0
- package/plugin/agents/README.md +22 -0
- package/plugin/hooks/hooks.json +107 -0
- package/plugin/skills/tldrx/SKILL.md +170 -0
- package/stages/build/stage.md +70 -0
- package/stages/build/stage.yml +54 -0
- package/stages/how/stage.md +89 -0
- package/stages/how/stage.yml +53 -0
- package/stages/plan/stage.md +89 -0
- package/stages/plan/stage.yml +48 -0
- package/stages/watch/stage.md +85 -0
- package/stages/watch/stage.yml +61 -0
- package/stages/what/stage.md +89 -0
- package/stages/what/stage.yml +61 -0
- package/templates/budget.yml +17 -0
- package/templates/competencies.yml +14 -0
- package/templates/env.yml +19 -0
- package/templates/epic.md +38 -0
- package/templates/expert.md +51 -0
- package/templates/experts/architect.md +77 -0
- package/templates/experts/delivery.md +76 -0
- package/templates/experts/developer.md +78 -0
- package/templates/experts/operations.md +74 -0
- package/templates/experts/product.md +78 -0
- package/templates/facts.yml +18 -0
- package/templates/handoff.md +63 -0
- package/templates/process.yml +33 -0
- package/templates/questions.md +63 -0
- package/templates/run.yml +24 -0
- package/templates/story.md +55 -0
- package/templates/watcher.md +68 -0
- package/templates/waves.yml +17 -0
- package/templates/workspace.yml +30 -0
- package/workflows/bugfix.yml +26 -0
- package/workflows/docs.yml +23 -0
- package/workflows/feature.yml +22 -0
- package/workflows/hotfix.yml +24 -0
- package/workflows/integration.yml +24 -0
- package/workflows/migration.yml +24 -0
- package/workflows/performance.yml +23 -0
- package/workflows/prototype.yml +24 -0
- package/workflows/refactor.yml +24 -0
- package/workflows/retro.yml +20 -0
- package/workflows/security-patch.yml +23 -0
- package/workflows/spike.yml +23 -0
- package/workflows/upgrade.yml +23 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
wouldBeContent
|
|
4
|
+
} from "./chunk-t8tdv11p.js";
|
|
5
|
+
import {
|
|
6
|
+
dodGateDeny,
|
|
7
|
+
dodGateInternalErrorDeny,
|
|
8
|
+
dodGateMissingBlockDeny,
|
|
9
|
+
dodGateRefusedCommandDeny
|
|
10
|
+
} from "./chunk-9gb21660.js";
|
|
11
|
+
import {
|
|
12
|
+
allow,
|
|
13
|
+
deny,
|
|
14
|
+
failOpen,
|
|
15
|
+
filePathOf,
|
|
16
|
+
isWriteOrEdit,
|
|
17
|
+
readPayload
|
|
18
|
+
} from "./chunk-1zwcxd3f.js";
|
|
19
|
+
import"./chunk-a8p2rc94.js";
|
|
20
|
+
import"./chunk-ae6bkfs5.js";
|
|
21
|
+
import"./chunk-0bt6yb2q.js";
|
|
22
|
+
import {
|
|
23
|
+
loadWorkspace,
|
|
24
|
+
locateWork,
|
|
25
|
+
repoPath,
|
|
26
|
+
runtime
|
|
27
|
+
} from "./chunk-p274ckxv.js";
|
|
28
|
+
|
|
29
|
+
// src/hooks/dod-gate.ts
|
|
30
|
+
import { basename, join } from "node:path";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
|
|
33
|
+
// src/core/schemas/story.ts
|
|
34
|
+
var FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
35
|
+
var FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
36
|
+
function parseDodBlock(text) {
|
|
37
|
+
const commands = [];
|
|
38
|
+
let present = false;
|
|
39
|
+
let inside = false;
|
|
40
|
+
for (const line of text.split(`
|
|
41
|
+
`)) {
|
|
42
|
+
if (inside) {
|
|
43
|
+
if (FENCE_CLOSE_RE.test(line)) {
|
|
44
|
+
inside = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const command = line.trim();
|
|
48
|
+
if (command !== "" && !command.startsWith("#"))
|
|
49
|
+
commands.push(command);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (FENCE_OPEN_RE.test(line)) {
|
|
53
|
+
present = true;
|
|
54
|
+
inside = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { present, commands };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/hooks/lib/story.ts
|
|
61
|
+
var DONE_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?status(?:\*\*)?\s*:\s*["'`]?done["'`]?\.?\s*$/i;
|
|
62
|
+
var REPO_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?repo(?:\*\*)?\s*:\s*["'`]?([A-Za-z0-9._-]{1,64})["'`]?\s*$/i;
|
|
63
|
+
var TIMEOUT_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?timeout_s(?:\*\*)?\s*:\s*(\d{1,6})\s*$/i;
|
|
64
|
+
var FENCE_OPEN_RE2 = /^\s*```+\s*dod\s*$/i;
|
|
65
|
+
var FENCE_CLOSE_RE2 = /^\s*```+\s*$/;
|
|
66
|
+
function readStory(text) {
|
|
67
|
+
const lines = text.split(`
|
|
68
|
+
`);
|
|
69
|
+
const dod = parseDodBlock(text);
|
|
70
|
+
let setsDone = false;
|
|
71
|
+
let repo = null;
|
|
72
|
+
let timeoutS = null;
|
|
73
|
+
let inDod = false;
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (inDod) {
|
|
76
|
+
if (FENCE_CLOSE_RE2.test(line))
|
|
77
|
+
inDod = false;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (FENCE_OPEN_RE2.test(line)) {
|
|
81
|
+
inDod = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!setsDone && DONE_RE.test(line))
|
|
85
|
+
setsDone = true;
|
|
86
|
+
if (repo === null) {
|
|
87
|
+
const m = REPO_RE.exec(line);
|
|
88
|
+
if (m?.[1] !== undefined)
|
|
89
|
+
repo = m[1];
|
|
90
|
+
}
|
|
91
|
+
if (timeoutS === null) {
|
|
92
|
+
const m = TIMEOUT_RE.exec(line);
|
|
93
|
+
if (m?.[1] !== undefined)
|
|
94
|
+
timeoutS = Number(m[1]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { setsDone, repo, dodCommands: dod.commands, hasDodBlock: dod.present, timeoutS };
|
|
98
|
+
}
|
|
99
|
+
function isAllowedDodCommand(command, allowed) {
|
|
100
|
+
return allowed.has(command);
|
|
101
|
+
}
|
|
102
|
+
var META_RE = /[|&;<>$`(){}*?~\\]/;
|
|
103
|
+
function splitArgv(command) {
|
|
104
|
+
if (/[\n\r]/.test(command))
|
|
105
|
+
return null;
|
|
106
|
+
const argv = [];
|
|
107
|
+
const pattern = /"([^"]*)"|'([^']*)'|([^\s"']+)/g;
|
|
108
|
+
for (const match of command.matchAll(pattern)) {
|
|
109
|
+
if (match[1] !== undefined || match[2] !== undefined) {
|
|
110
|
+
argv.push(match[1] ?? match[2] ?? "");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const bare = match[3] ?? "";
|
|
114
|
+
if (META_RE.test(bare))
|
|
115
|
+
return null;
|
|
116
|
+
argv.push(bare);
|
|
117
|
+
}
|
|
118
|
+
return argv.length === 0 ? null : argv;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class DodCommandRefused extends Error {
|
|
122
|
+
}
|
|
123
|
+
async function runDodCommand(command, cwd, timeoutMs, allowed) {
|
|
124
|
+
if (!isAllowedDodCommand(command, allowed)) {
|
|
125
|
+
throw new DodCommandRefused(allowed.size === 0 ? `\`${command}\` cannot be run: .tldrx/workspace.yml declares no commands, so there is nothing ` + "to check it against. An empty allowlist is not a permit." : `\`${command}\` is not one of .tldrx/workspace.yml's commands. Declared: ` + `${[...allowed].map((c) => `\`${c}\``).join(", ")}.`);
|
|
126
|
+
}
|
|
127
|
+
const argv = splitArgv(command);
|
|
128
|
+
if (argv === null) {
|
|
129
|
+
throw new DodCommandRefused(`\`${command}\` needs a shell to run (it contains a metacharacter), and this gate does not open one. ` + "Put it in a script, declare the script under the repo's `commands:`, and cite that.");
|
|
130
|
+
}
|
|
131
|
+
const head = argv[0] ?? "";
|
|
132
|
+
const { exitCode, stdout, stderr, timedOut } = await runtime.spawn(head, argv.slice(1), {
|
|
133
|
+
cwd,
|
|
134
|
+
timeoutMs
|
|
135
|
+
});
|
|
136
|
+
return { command, exitCode, timedOut, tail: lastMeaningfulLine(`${stdout}
|
|
137
|
+
${stderr}`) };
|
|
138
|
+
}
|
|
139
|
+
function lastMeaningfulLine(output, max = 200) {
|
|
140
|
+
const lines = output.split(`
|
|
141
|
+
`).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
142
|
+
const last = lines[lines.length - 1] ?? "";
|
|
143
|
+
return last.length > max ? `${last.slice(0, max - 1)}…` : last;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/hooks/dod-gate.ts
|
|
147
|
+
var DEFAULT_TIMEOUT_S = 900;
|
|
148
|
+
var storyId = "?";
|
|
149
|
+
try {
|
|
150
|
+
const payload = await readPayload();
|
|
151
|
+
if (!isWriteOrEdit(payload))
|
|
152
|
+
allow();
|
|
153
|
+
const filePath = filePathOf(payload);
|
|
154
|
+
const location = locateWork(filePath);
|
|
155
|
+
if (location === null || !filePath.endsWith(".md") || !location.relative.includes("stories/")) {
|
|
156
|
+
allow();
|
|
157
|
+
}
|
|
158
|
+
const wouldBe = wouldBeContent(payload, filePath);
|
|
159
|
+
if (wouldBe.kind !== "content")
|
|
160
|
+
allow();
|
|
161
|
+
const story = readStory(wouldBe.text);
|
|
162
|
+
if (!story.setsDone)
|
|
163
|
+
allow();
|
|
164
|
+
storyId = basename(filePath, ".md");
|
|
165
|
+
const relPath = `tldrx-work/${location.run}/${location.relative}`;
|
|
166
|
+
if (!story.hasDodBlock || story.dodCommands.length === 0) {
|
|
167
|
+
deny(dodGateMissingBlockDeny(storyId, relPath));
|
|
168
|
+
}
|
|
169
|
+
const workspace = loadWorkspace(location.root);
|
|
170
|
+
const repoName = story.repo ?? "";
|
|
171
|
+
const cwd = repoName === "" ? location.root : repoPath(workspace, repoName) ?? join(location.root, repoName);
|
|
172
|
+
if (!existsSync(cwd)) {
|
|
173
|
+
deny(dodGateInternalErrorDeny(storyId, `repo \`${repoName}\` resolves to ${cwd}, which does not exist`));
|
|
174
|
+
}
|
|
175
|
+
const timeoutMs = (story.timeoutS ?? DEFAULT_TIMEOUT_S) * 1000;
|
|
176
|
+
for (const command of story.dodCommands) {
|
|
177
|
+
let outcome;
|
|
178
|
+
try {
|
|
179
|
+
outcome = await runDodCommand(command, cwd, timeoutMs, workspace.commands);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error instanceof DodCommandRefused) {
|
|
182
|
+
deny(dodGateRefusedCommandDeny(storyId, command, error.message));
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
if (outcome.timedOut) {
|
|
187
|
+
deny(dodGateDeny(storyId, command, repoName === "" ? "(root)" : repoName, outcome.exitCode, `timed out after ${story.timeoutS ?? DEFAULT_TIMEOUT_S}s. ${outcome.tail}`));
|
|
188
|
+
}
|
|
189
|
+
if (outcome.exitCode !== 0) {
|
|
190
|
+
deny(dodGateDeny(storyId, command, repoName === "" ? "(root)" : repoName, outcome.exitCode, outcome.tail));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (storyId === "?")
|
|
195
|
+
failOpen("dod-gate", error);
|
|
196
|
+
deny(dodGateInternalErrorDeny(storyId, error instanceof Error ? error.message : String(error)));
|
|
197
|
+
}
|
|
198
|
+
allow();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
wouldBeContent
|
|
4
|
+
} from "./chunk-t8tdv11p.js";
|
|
5
|
+
import {
|
|
6
|
+
noReAskDeny
|
|
7
|
+
} from "./chunk-9gb21660.js";
|
|
8
|
+
import {
|
|
9
|
+
FactsStore
|
|
10
|
+
} from "./chunk-x98qs959.js";
|
|
11
|
+
import {
|
|
12
|
+
allow,
|
|
13
|
+
deny,
|
|
14
|
+
filePathOf,
|
|
15
|
+
isWriteOrEdit,
|
|
16
|
+
readPayload,
|
|
17
|
+
runHook
|
|
18
|
+
} from "./chunk-1zwcxd3f.js";
|
|
19
|
+
import"./chunk-a8p2rc94.js";
|
|
20
|
+
import {
|
|
21
|
+
openBlocks,
|
|
22
|
+
parseQuestions
|
|
23
|
+
} from "./chunk-j234zf0t.js";
|
|
24
|
+
import"./chunk-t56k6146.js";
|
|
25
|
+
import"./chunk-g395gk7e.js";
|
|
26
|
+
import"./chunk-0bt6yb2q.js";
|
|
27
|
+
import {
|
|
28
|
+
factsPath,
|
|
29
|
+
locateWork
|
|
30
|
+
} from "./chunk-p274ckxv.js";
|
|
31
|
+
|
|
32
|
+
// src/hooks/no-reask.ts
|
|
33
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
34
|
+
await runHook("no-reask", async () => {
|
|
35
|
+
const payload = await readPayload();
|
|
36
|
+
if (!isWriteOrEdit(payload))
|
|
37
|
+
return;
|
|
38
|
+
const filePath = filePathOf(payload);
|
|
39
|
+
if (!filePath.endsWith("questions.md"))
|
|
40
|
+
return;
|
|
41
|
+
const location = locateWork(filePath);
|
|
42
|
+
if (location === null)
|
|
43
|
+
return;
|
|
44
|
+
const wouldBe = wouldBeContent(payload, filePath);
|
|
45
|
+
if (wouldBe.kind !== "content")
|
|
46
|
+
return;
|
|
47
|
+
const facts = factsPath(location.root);
|
|
48
|
+
if (!existsSync(facts))
|
|
49
|
+
return;
|
|
50
|
+
const store = FactsStore.load(facts);
|
|
51
|
+
if (store.active.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
const existing = new Set;
|
|
54
|
+
if (existsSync(filePath)) {
|
|
55
|
+
for (const block of parseQuestions(readFileSync(filePath, "utf8")).blocks)
|
|
56
|
+
existing.add(block.id);
|
|
57
|
+
}
|
|
58
|
+
for (const block of openBlocks(parseQuestions(wouldBe.text).blocks)) {
|
|
59
|
+
if (existing.has(block.id))
|
|
60
|
+
continue;
|
|
61
|
+
const area = block.metadata?.area ?? "";
|
|
62
|
+
const hit = store.findDuplicate(block.title, area);
|
|
63
|
+
if (hit !== null)
|
|
64
|
+
deny(noReAskDeny(block.id, block.title, hit.fact));
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
allow();
|