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.
Files changed (75) hide show
  1. package/CHANGELOG.md +913 -0
  2. package/LICENSE +21 -0
  3. package/README.md +174 -0
  4. package/dist/hooks/answer-capture.js +174 -0
  5. package/dist/hooks/budget-gate.js +173 -0
  6. package/dist/hooks/chunk-0bt6yb2q.js +88 -0
  7. package/dist/hooks/chunk-1zwcxd3f.js +66 -0
  8. package/dist/hooks/chunk-9gb21660.js +66 -0
  9. package/dist/hooks/chunk-a8p2rc94.js +20 -0
  10. package/dist/hooks/chunk-ae6bkfs5.js +0 -0
  11. package/dist/hooks/chunk-azctppjh.js +198 -0
  12. package/dist/hooks/chunk-g395gk7e.js +503 -0
  13. package/dist/hooks/chunk-j234zf0t.js +339 -0
  14. package/dist/hooks/chunk-kw4tffzf.js +139 -0
  15. package/dist/hooks/chunk-p274ckxv.js +7435 -0
  16. package/dist/hooks/chunk-sdjnnmzz.js +497 -0
  17. package/dist/hooks/chunk-t56k6146.js +14 -0
  18. package/dist/hooks/chunk-t8tdv11p.js +35 -0
  19. package/dist/hooks/chunk-x98qs959.js +302 -0
  20. package/dist/hooks/chunk-y0jdr3et.js +627 -0
  21. package/dist/hooks/claim-sources.js +72 -0
  22. package/dist/hooks/dod-gate.js +198 -0
  23. package/dist/hooks/no-reask.js +67 -0
  24. package/dist/hooks/session-start.js +1348 -0
  25. package/dist/hooks/statusline.js +116 -0
  26. package/dist/tldrx.js +34219 -0
  27. package/env.yml +79 -0
  28. package/package.json +60 -0
  29. package/plugin/.claude-plugin/plugin.json +9 -0
  30. package/plugin/README.md +113 -0
  31. package/plugin/agents/README.md +22 -0
  32. package/plugin/hooks/hooks.json +107 -0
  33. package/plugin/skills/tldrx/SKILL.md +170 -0
  34. package/stages/build/stage.md +70 -0
  35. package/stages/build/stage.yml +54 -0
  36. package/stages/how/stage.md +89 -0
  37. package/stages/how/stage.yml +53 -0
  38. package/stages/plan/stage.md +89 -0
  39. package/stages/plan/stage.yml +48 -0
  40. package/stages/watch/stage.md +85 -0
  41. package/stages/watch/stage.yml +61 -0
  42. package/stages/what/stage.md +89 -0
  43. package/stages/what/stage.yml +61 -0
  44. package/templates/budget.yml +17 -0
  45. package/templates/competencies.yml +14 -0
  46. package/templates/env.yml +19 -0
  47. package/templates/epic.md +38 -0
  48. package/templates/expert.md +51 -0
  49. package/templates/experts/architect.md +77 -0
  50. package/templates/experts/delivery.md +76 -0
  51. package/templates/experts/developer.md +78 -0
  52. package/templates/experts/operations.md +74 -0
  53. package/templates/experts/product.md +78 -0
  54. package/templates/facts.yml +18 -0
  55. package/templates/handoff.md +63 -0
  56. package/templates/process.yml +33 -0
  57. package/templates/questions.md +63 -0
  58. package/templates/run.yml +24 -0
  59. package/templates/story.md +55 -0
  60. package/templates/watcher.md +68 -0
  61. package/templates/waves.yml +17 -0
  62. package/templates/workspace.yml +30 -0
  63. package/workflows/bugfix.yml +26 -0
  64. package/workflows/docs.yml +23 -0
  65. package/workflows/feature.yml +22 -0
  66. package/workflows/hotfix.yml +24 -0
  67. package/workflows/integration.yml +24 -0
  68. package/workflows/migration.yml +24 -0
  69. package/workflows/performance.yml +23 -0
  70. package/workflows/prototype.yml +24 -0
  71. package/workflows/refactor.yml +24 -0
  72. package/workflows/retro.yml +20 -0
  73. package/workflows/security-patch.yml +23 -0
  74. package/workflows/spike.yml +23 -0
  75. 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();