compounding-loop 0.4.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.
Files changed (128) hide show
  1. package/README.md +152 -128
  2. package/dist/cli/draft.js +69 -0
  3. package/dist/cli/draft.js.map +1 -0
  4. package/dist/cli/eval.js +186 -0
  5. package/dist/cli/eval.js.map +1 -0
  6. package/dist/cli/hook.js +92 -0
  7. package/dist/cli/hook.js.map +1 -0
  8. package/dist/cli/index.js +336 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +41 -4
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/lanesInit.js +8 -2
  13. package/dist/cli/lanesInit.js.map +1 -1
  14. package/dist/cli/pace.js +41 -0
  15. package/dist/cli/pace.js.map +1 -0
  16. package/dist/cli/pause.js +3 -3
  17. package/dist/cli/pause.js.map +1 -1
  18. package/dist/cli/recipe.js +89 -0
  19. package/dist/cli/recipe.js.map +1 -0
  20. package/dist/cli/recipeDraft.js +64 -0
  21. package/dist/cli/recipeDraft.js.map +1 -0
  22. package/dist/cli/runs.js +49 -11
  23. package/dist/cli/runs.js.map +1 -1
  24. package/dist/cli/schedule.js +177 -0
  25. package/dist/cli/schedule.js.map +1 -0
  26. package/dist/core/brief.js +35 -12
  27. package/dist/core/brief.js.map +1 -1
  28. package/dist/core/draftBrief.js +68 -0
  29. package/dist/core/draftBrief.js.map +1 -0
  30. package/dist/core/eval.js +123 -0
  31. package/dist/core/eval.js.map +1 -0
  32. package/dist/core/hooksInit.js +54 -0
  33. package/dist/core/hooksInit.js.map +1 -0
  34. package/dist/core/laneRound.js +28 -7
  35. package/dist/core/laneRound.js.map +1 -1
  36. package/dist/core/lanes.js +3 -0
  37. package/dist/core/lanes.js.map +1 -1
  38. package/dist/core/lanesInit.js +19 -0
  39. package/dist/core/lanesInit.js.map +1 -1
  40. package/dist/core/parse/index.js +8 -0
  41. package/dist/core/parse/index.js.map +1 -1
  42. package/dist/core/parse/outcomes.js +51 -0
  43. package/dist/core/parse/outcomes.js.map +1 -0
  44. package/dist/core/parse/policy.js +56 -0
  45. package/dist/core/parse/policy.js.map +1 -0
  46. package/dist/core/parse/runs.js +1 -0
  47. package/dist/core/parse/runs.js.map +1 -1
  48. package/dist/core/parse/schedule.js +101 -0
  49. package/dist/core/parse/schedule.js.map +1 -0
  50. package/dist/core/parse/steer.js +68 -0
  51. package/dist/core/parse/steer.js.map +1 -0
  52. package/dist/core/recipeDraft.js +37 -0
  53. package/dist/core/recipeDraft.js.map +1 -0
  54. package/dist/core/recipes.js +185 -0
  55. package/dist/core/recipes.js.map +1 -0
  56. package/dist/core/repo.js +16 -1
  57. package/dist/core/repo.js.map +1 -1
  58. package/dist/core/triggers.js +137 -0
  59. package/dist/core/triggers.js.map +1 -0
  60. package/dist/server/actionBody.js +54 -0
  61. package/dist/server/actionBody.js.map +1 -0
  62. package/dist/server/checkLabels.js +7 -2
  63. package/dist/server/checkLabels.js.map +1 -1
  64. package/dist/server/claude.js +18 -0
  65. package/dist/server/claude.js.map +1 -0
  66. package/dist/server/controls.js +28 -1
  67. package/dist/server/controls.js.map +1 -1
  68. package/dist/server/data.js +8 -3
  69. package/dist/server/data.js.map +1 -1
  70. package/dist/server/draftInputs.js +33 -0
  71. package/dist/server/draftInputs.js.map +1 -0
  72. package/dist/server/health.js +3 -3
  73. package/dist/server/health.js.map +1 -1
  74. package/dist/server/index.js +55 -0
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/laneChecks.js +1 -1
  77. package/dist/server/laneChecks.js.map +1 -1
  78. package/dist/server/laneSchedule.js +15 -0
  79. package/dist/server/laneSchedule.js.map +1 -0
  80. package/dist/server/live/build.js +25 -10
  81. package/dist/server/live/build.js.map +1 -1
  82. package/dist/server/live/index.js +4 -2
  83. package/dist/server/live/index.js.map +1 -1
  84. package/dist/server/live/urls.js +3 -1
  85. package/dist/server/live/urls.js.map +1 -1
  86. package/dist/server/loops.js +145 -20
  87. package/dist/server/loops.js.map +1 -1
  88. package/dist/server/memory.js +115 -0
  89. package/dist/server/memory.js.map +1 -0
  90. package/dist/server/memory.json +398 -0
  91. package/dist/server/paceNote.js +13 -0
  92. package/dist/server/paceNote.js.map +1 -0
  93. package/dist/server/roundState.js +26 -2
  94. package/dist/server/roundState.js.map +1 -1
  95. package/dist/server/runs.js +72 -9
  96. package/dist/server/runs.js.map +1 -1
  97. package/dist/server/shipped.js +35 -0
  98. package/dist/server/shipped.js.map +1 -0
  99. package/dist/server/static-export.js +8 -2
  100. package/dist/server/static-export.js.map +1 -1
  101. package/dist/server/this-repo.json +2367 -858
  102. package/dist/web/assets/index-C5q9d5-n.css +2 -0
  103. package/dist/web/assets/index-DDEAcJFt.js +123 -0
  104. package/dist/web/index.html +2 -2
  105. package/method/COMPOUNDING_LOOP.md +33 -6
  106. package/method/operating-card.md +1 -1
  107. package/method/retro.md +4 -3
  108. package/package.json +4 -4
  109. package/plugin/.claude-plugin/plugin.json +1 -1
  110. package/plugin/commands/loop-init.md +1 -0
  111. package/plugin/commands/retro.md +3 -2
  112. package/plugin/hooks/hooks.json +62 -0
  113. package/runners/github-actions/lanes.yml +2 -2
  114. package/runners/routine/README.md +4 -0
  115. package/runners/routine/control-room-prompt.md +4 -14
  116. package/runners/routine/lane-prompt.md +4 -10
  117. package/templates/lanes/README.md +5 -1
  118. package/templates/lanes/claude-section.md +1 -0
  119. package/templates/lanes/control-run.md +37 -0
  120. package/templates/lanes/lane-run.md +33 -0
  121. package/templates/lanes/schedule.json +4 -0
  122. package/templates/lanes/steer.md +3 -0
  123. package/templates/recipe.md +39 -0
  124. package/templates/recipes/cli-command-json.md +47 -0
  125. package/templates/recipes/static-calculator-site.md +50 -0
  126. package/templates/static-site/CLAUDE.md +1 -1
  127. package/dist/web/assets/index-BOPytDNU.js +0 -53
  128. package/dist/web/assets/index-BSHEI7fa.css +0 -2
@@ -0,0 +1,37 @@
1
+ // The prompt that asks Claude to draft a recipe from finished work (IDEA.md Round 6 A4). Pure: no Node imports.
2
+ import { RECIPE_CAP_BYTES, RECIPE_FIELDS, RECIPE_SECTIONS } from './recipes.js';
3
+ const clip = (text, max) => (text.length <= max ? text : `${text.slice(0, max)}\n[…cut]`);
4
+ const block = (text, max) => (text?.trim() ? clip(text.trim(), max) : '(none)');
5
+ export function buildRecipeDraftPrompt(input) {
6
+ const notes = input.notes.filter((n) => n.body.trim()).map((n) => `### ${n.heading}\n${clip(n.body.trim(), 2000)}`).join('\n\n') || '(none)';
7
+ const idRule = input.id
8
+ ? `Use id "${input.id}".${input.existingIds.includes(input.id) ? ' It already exists: answer with a DELTA (below), not a full recipe.' : ''}`
9
+ : 'Pick an id "R-<slug>" that is not in this list: ' + (input.existingIds.join(', ') || '(no recipes yet)') + '. If the work extends one of those, answer with a DELTA (below) for that id.';
10
+ return `You are distilling a recipe from work that passed its checks in ${input.source}. A recipe is the smallest exact context that lets a smaller model repeat the procedure in one pass. Use only the inputs below; do not edit files or run commands. Answer with the recipe as Markdown and nothing else.
11
+
12
+ ## Inputs
13
+
14
+ ### DONE.md
15
+ ${block(input.done, 3000)}
16
+
17
+ ### Task notes
18
+ ${notes}
19
+
20
+ ### Knowledge entries
21
+ ${input.knowledge.length ? input.knowledge.map((k) => `- ${k}`).join('\n') : '(none)'}
22
+
23
+ ### Recent runs
24
+ ${input.runs.length ? input.runs.slice(-10).join('\n') : '(none)'}
25
+
26
+ ## Format (\`loop recipe check\` scores it; the draft is refused above ${RECIPE_CAP_BYTES} bytes)
27
+ - YAML front matter between --- lines with: ${RECIPE_FIELDS.join(', ')}. \`kind\` is project or feature; \`stack\` lists name@major (e.g. node@20); \`status\` is GUESS unless the checks of this very work proved it; \`source\` is the repo and commit; \`uses\` and \`passes\` start at 0; \`updated\` is today's date.
28
+ - Then "# <title>" and these sections, in this order: ${RECIPE_SECTIONS.map((s) => `"## ${s}"`).join(', ')}.
29
+ - Steps are numbered and each one ends with the command or test that proves it in backticks. Traps are one line each and cite the knowledge id or trigger that covers them. Checks are the commands that decide done. Start from names templates or files to copy.
30
+ - Keep only what a repeat of this work needs. No absolute paths, emails or secrets. Do not describe what happened; describe what to do.
31
+ - ${idRule}
32
+
33
+ ## Delta rule (ACE)
34
+ If the id already exists, do not rewrite the recipe. Answer only with a list under the headings "### Add" and "### Change", one item per line, each with the section it belongs to and the evidence (a test, command or knowledge id). Never drop an existing item without evidence.
35
+ `;
36
+ }
37
+ //# sourceMappingURL=recipeDraft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipeDraft.js","sourceRoot":"","sources":["../../src/core/recipeDraft.ts"],"names":[],"mappings":"AAAA,gHAAgH;AAChH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAmBhF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,GAAW,EAAU,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;AAClH,MAAM,KAAK,GAAG,CAAC,IAAwB,EAAE,GAAW,EAAU,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEpH,MAAM,UAAU,sBAAsB,CAAC,KAAwB;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC;IAC7I,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE;QACrB,CAAC,CAAC,WAAW,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,qEAAqE,CAAC,CAAC,CAAC,EAAE,EAAE;QAC7I,CAAC,CAAC,kDAAkD,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,GAAG,8EAA8E,CAAC;IAC/L,OAAO,mEAAmE,KAAK,CAAC,MAAM;;;;;EAKtF,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC;;;EAGvB,KAAK;;;EAGL,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ;;;EAGnF,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ;;yEAEQ,gBAAgB;8CAC3C,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;wDACd,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;IAGtG,MAAM;;;;CAIT,CAAC;AACF,CAAC"}
@@ -0,0 +1,185 @@
1
+ // Recipes (IDEA.md Round 6 A): one small Markdown file per repeatable procedure. Pure and browser-safe.
2
+ import { parse as parseYaml } from 'yaml';
3
+ export const RECIPE_CAP_BYTES = 6144;
4
+ export const RECIPE_SECTIONS = ['When to use', 'Steps', 'Traps', 'Checks', 'Start from'];
5
+ export const RECIPE_FIELDS = ['id', 'title', 'kind', 'stack', 'tags', 'status', 'source', 'uses', 'passes', 'updated'];
6
+ /** Splits `---` front matter from the body; undefined when there is none or it is not a YAML mapping. */
7
+ export function splitRecipe(text) {
8
+ const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/.exec(text.replace(/^/, ''));
9
+ if (!m)
10
+ return undefined;
11
+ try {
12
+ const data = parseYaml(m[1] ?? '');
13
+ if (!data || typeof data !== 'object' || Array.isArray(data))
14
+ return undefined;
15
+ return { data: data, body: text.slice(text.indexOf(m[0]) + m[0].length) };
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ const sectionsOf = (body) => {
22
+ const out = {};
23
+ let current;
24
+ for (const line of body.split(/\r?\n/)) {
25
+ const h = /^##\s+(.+?)\s*$/.exec(line);
26
+ if (h)
27
+ out[h[1] ?? ''] = current = [];
28
+ else
29
+ current?.push(line);
30
+ }
31
+ return out;
32
+ };
33
+ const strings = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : []);
34
+ const whole = (v) => (typeof v === 'number' && Number.isInteger(v) && v >= 0 ? v : 0);
35
+ const isStep = (line) => /^\s*\d+[.)]\s/.test(line);
36
+ const isItem = (line) => /^\s*(?:[-*]|\d+[.)])\s/.test(line);
37
+ /** Reads a recipe leniently; undefined without a usable id and title. `checkRecipe` says what is wrong with it. */
38
+ export function parseRecipe(text) {
39
+ const parts = splitRecipe(text);
40
+ if (!parts || typeof parts.data.id !== 'string' || typeof parts.data.title !== 'string')
41
+ return undefined;
42
+ const { data, body } = parts;
43
+ return {
44
+ id: data.id,
45
+ title: data.title,
46
+ kind: data.kind === 'feature' ? 'feature' : 'project',
47
+ stack: strings(data.stack),
48
+ tags: strings(data.tags),
49
+ status: data.status === 'GUESS' ? 'GUESS' : 'CONFIRMED',
50
+ source: typeof data.source === 'string' ? data.source : '',
51
+ uses: whole(data.uses),
52
+ passes: whole(data.passes),
53
+ updated: data.updated instanceof Date ? data.updated.toISOString().slice(0, 10) : String(data.updated ?? ''),
54
+ body,
55
+ sections: sectionsOf(body),
56
+ bytes: new TextEncoder().encode(text).length,
57
+ };
58
+ }
59
+ export const stepsOf = (r) => (r.sections.Steps ?? []).filter(isStep);
60
+ export const checksOf = (r) => (r.sections.Checks ?? []).filter(isItem);
61
+ /** The card shape the dashboard uses. */
62
+ export const summarizeRecipe = (r, store, displayedPath) => ({
63
+ id: r.id, title: r.title, kind: r.kind, stack: r.stack, tags: r.tags, status: r.status, uses: r.uses, passes: r.passes,
64
+ steps: stepsOf(r).length, checks: checksOf(r).length, bytes: r.bytes, store, path: displayedPath,
65
+ });
66
+ /** The block a model receives: front matter dropped, sections kept. */
67
+ export function recipeContext(text) {
68
+ const parts = splitRecipe(text);
69
+ return (parts ? parts.body : text).replace(/^\s+/, '').trimEnd() + '\n';
70
+ }
71
+ const SECRET = /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,}|eyJ[A-Za-z0-9_-]{15,}\.[A-Za-z0-9_-]{10,})/;
72
+ const EMAIL = /[\w.+-]+@[\w-]+\.[\w.-]+[A-Za-z]/;
73
+ const ABS_PATH = /(?:^|[\s`'"(])(?:\/(?:home|Users|root|tmp|var|etc)\/|[A-Za-z]:\\)/;
74
+ /** Lint messages with a fix for each; empty means the recipe is valid. */
75
+ export function checkRecipe(text) {
76
+ const problems = [];
77
+ const parts = splitRecipe(text);
78
+ if (!parts)
79
+ return ['No YAML front matter. Start the file with a --- block holding ' + RECIPE_FIELDS.join(', ') + '.'];
80
+ const { data } = parts;
81
+ for (const field of RECIPE_FIELDS)
82
+ if (data[field] === undefined || data[field] === null)
83
+ problems.push(`Front matter is missing "${field}". Add it.`);
84
+ if (typeof data.id === 'string' && !/^R-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(data.id))
85
+ problems.push(`id "${data.id}" must look like R-<slug> (lowercase letters, digits, hyphens).`);
86
+ if (data.kind !== undefined && data.kind !== 'project' && data.kind !== 'feature')
87
+ problems.push('kind must be "project" or "feature".');
88
+ if (data.status !== undefined && data.status !== 'CONFIRMED' && data.status !== 'GUESS')
89
+ problems.push('status must be CONFIRMED or GUESS.');
90
+ for (const field of ['stack', 'tags'])
91
+ if (data[field] !== undefined && !Array.isArray(data[field]))
92
+ problems.push(`${field} must be a list.`);
93
+ for (const item of strings(data.stack))
94
+ if (!/^[^@\s]+@\d+$/.test(item))
95
+ problems.push(`stack item "${item}" must look like name@major, e.g. node@20.`);
96
+ for (const field of ['uses', 'passes'])
97
+ if (data[field] !== undefined && whole(data[field]) !== data[field])
98
+ problems.push(`${field} must be a whole number, 0 or more.`);
99
+ const recipe = parseRecipe(text);
100
+ if (recipe) {
101
+ const names = Object.keys(recipe.sections);
102
+ for (const section of RECIPE_SECTIONS)
103
+ if (!names.includes(section))
104
+ problems.push(`Missing section "## ${section}". Add it.`);
105
+ const present = RECIPE_SECTIONS.filter((s) => names.includes(s));
106
+ if (present.join('|') !== names.filter((n) => RECIPE_SECTIONS.includes(n)).join('|'))
107
+ problems.push(`Sections are out of order. Use: ${RECIPE_SECTIONS.join(', ')}.`);
108
+ const steps = stepsOf(recipe);
109
+ if (names.includes('Steps') && steps.length === 0)
110
+ problems.push('"## Steps" has no numbered steps. Number them "1. ...".');
111
+ steps.forEach((step, i) => {
112
+ if (!/`[^`]+`/.test(step))
113
+ problems.push(`Step ${i + 1} names no command or test that proves it. Add the proof in backticks, e.g. "(proof: \`npm test\`)".`);
114
+ });
115
+ if (names.includes('Checks') && checksOf(recipe).length === 0)
116
+ problems.push('"## Checks" lists no commands. Add one bullet per command that decides done.');
117
+ }
118
+ const bytes = new TextEncoder().encode(text).length;
119
+ if (bytes > RECIPE_CAP_BYTES)
120
+ problems.push(`Recipe is ${bytes} bytes; the cap is ${RECIPE_CAP_BYTES}. Cut the least useful steps or traps.`);
121
+ if (ABS_PATH.test(text))
122
+ problems.push('Contains an absolute path. Use a path relative to the repo or ~.');
123
+ if (EMAIL.test(text.replace(/`[^`\n]*@\d+[^`\n]*`/g, '')))
124
+ problems.push('Contains an email address. Remove it.');
125
+ if (SECRET.test(text))
126
+ problems.push('Contains a token-like string. Remove it; recipes hold no secrets.');
127
+ return problems;
128
+ }
129
+ /** Dependency name → major version from a package.json text (`^4.2.1` → `4`); `engines.node` gives `node`. */
130
+ export function dependencyMajors(packageJson) {
131
+ const majors = {};
132
+ if (packageJson === undefined)
133
+ return majors;
134
+ try {
135
+ const pkg = JSON.parse(packageJson);
136
+ const node = /(\d+)/.exec(String(pkg.engines?.node ?? ''))?.[1];
137
+ if (node)
138
+ majors.node = node;
139
+ for (const field of ['dependencies', 'devDependencies']) {
140
+ const group = pkg[field];
141
+ if (typeof group !== 'object' || group === null)
142
+ continue;
143
+ for (const [name, range] of Object.entries(group)) {
144
+ const major = /(\d+)/.exec(String(range))?.[1];
145
+ if (major)
146
+ majors[name] = major;
147
+ }
148
+ }
149
+ }
150
+ catch {
151
+ // not JSON: no dependencies
152
+ }
153
+ return majors;
154
+ }
155
+ const words = (text) => new Set(text.toLowerCase().match(/[a-z0-9]+/g) ?? []);
156
+ /**
157
+ * Ranks recipes: +1 per stack item whose name and major match, -1 per name with another major, +1 per tag found in the task text.
158
+ * Zero and negative scores drop out; ties go to more passes, then id.
159
+ */
160
+ export function matchRecipes(recipes, deps, task = '') {
161
+ const taskWords = words(task);
162
+ const out = [];
163
+ for (const recipe of recipes) {
164
+ let matched = 0;
165
+ const mismatched = [];
166
+ for (const item of recipe.stack) {
167
+ const [name = '', major] = item.split('@');
168
+ const have = deps[name];
169
+ if (have === undefined)
170
+ continue;
171
+ if (have === major)
172
+ matched++;
173
+ else
174
+ mismatched.push(name);
175
+ }
176
+ const tags = recipe.tags.filter((t) => [...words(t)].every((w) => taskWords.has(w)) && words(t).size > 0);
177
+ const score = matched - mismatched.length + tags.length;
178
+ if (score <= 0)
179
+ continue;
180
+ const reason = [`stack ${matched}/${recipe.stack.length}`, ...(mismatched.length ? [`major differs: ${mismatched.join(', ')}`] : []), ...(tags.length ? [`tags: ${tags.join(', ')}`] : [])].join(', ');
181
+ out.push({ recipe, score, reason });
182
+ }
183
+ return out.sort((a, b) => b.score - a.score || b.recipe.passes - a.recipe.passes || a.recipe.id.localeCompare(b.recipe.id));
184
+ }
185
+ //# sourceMappingURL=recipes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recipes.js","sourceRoot":"","sources":["../../src/core/recipes.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAG1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,CAAU,CAAC;AAClG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;AAqBhI,yGAAyG;AACzG,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,CAAC,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACtF,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAC/E,OAAO,EAAE,IAAI,EAAE,IAA+B,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;IACvG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAA4B,EAAE;IAC5D,MAAM,GAAG,GAA6B,EAAE,CAAC;IACzC,IAAI,OAA6B,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,GAAG,EAAE,CAAC;;YACjC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,CAAU,EAAY,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AACxH,MAAM,KAAK,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvG,MAAM,MAAM,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrE,MAAM,MAAM,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAE9E,mHAAmH;AACnH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1G,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAC7B,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAY;QACrB,KAAK,EAAE,IAAI,CAAC,KAAe;QAC3B,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;QACrD,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW;QACvD,MAAM,EAAE,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QAC1D,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACtB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,YAAY,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5G,IAAI;QACJ,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC;QAC1B,KAAK,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM;KAC7C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAY,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAY,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAE1F,yCAAyC;AACzC,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAS,EAAE,KAAyB,EAAE,aAAqB,EAAiB,EAAE,CAAC,CAAC;IAC9G,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM;IACtH,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa;CACjG,CAAC,CAAC;AAEH,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;AAC1E,CAAC;AAED,MAAM,MAAM,GAAG,gJAAgJ,CAAC;AAChK,MAAM,KAAK,GAAG,kCAAkC,CAAC;AACjD,MAAM,QAAQ,GAAG,mEAAmE,CAAC;AAErF,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,gEAAgE,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;IACvH,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,KAAK,MAAM,KAAK,IAAI,aAAa;QAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,4BAA4B,KAAK,YAAY,CAAC,CAAC;IACvJ,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,EAAE,iEAAiE,CAAC,CAAC;IACjL,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,QAAQ,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACzI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,MAAM,KAAK,OAAO;QAAE,QAAQ,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IAC7I,KAAK,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE,MAAM,CAAU;QAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,kBAAkB,CAAC,CAAC;IACxJ,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,eAAe,IAAI,4CAA4C,CAAC,CAAC;IACxJ,KAAK,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAU;QAAE,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,qCAAqC,CAAC,CAAC;IACnL,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3C,KAAK,MAAM,OAAO,IAAI,eAAe;YAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,uBAAuB,OAAO,YAAY,CAAC,CAAC;QAC/H,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAE,eAAqC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,mCAAmC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7L,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;QAC5H,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;YACxB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,qGAAqG,CAAC,CAAC;QAC/J,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,8EAA8E,CAAC,CAAC;IAC/J,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACpD,IAAI,KAAK,GAAG,gBAAgB;QAAE,QAAQ,CAAC,IAAI,CAAC,aAAa,KAAK,sBAAsB,gBAAgB,wCAAwC,CAAC,CAAC;IAC9I,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC3G,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IAClH,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,mEAAmE,CAAC,CAAC;IAC1G,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,gBAAgB,CAAC,WAA+B;IAC9D,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAA4B,CAAC;QAC/D,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAE,GAAG,CAAC,OAAyC,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACnG,IAAI,IAAI;YAAE,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,CAAC,cAAc,EAAE,iBAAiB,CAAC,EAAE,CAAC;YACxD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;gBAAE,SAAS;YAC1D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC/C,IAAI,KAAK;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AASD,MAAM,KAAK,GAAG,CAAC,IAAY,EAAe,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;AAEnG;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAA0B,EAAE,IAAsC,EAAE,IAAI,GAAG,EAAE;IACxG,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,MAAM,UAAU,GAAa,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,GAAG,EAAE,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,IAAI,IAAI,KAAK,KAAK;gBAAE,OAAO,EAAE,CAAC;;gBACzB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC1G,MAAM,KAAK,GAAG,OAAO,GAAG,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QACxD,IAAI,KAAK,IAAI,CAAC;YAAE,SAAS;QACzB,MAAM,MAAM,GAAG,CAAC,SAAS,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,kBAAkB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvM,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9H,CAAC"}
package/dist/core/repo.js CHANGED
@@ -19,6 +19,21 @@ export function parseLockTime(lockText) {
19
19
  export function countDoneRounds(fileNames) {
20
20
  return fileNames.filter((name) => /^done-v\d+\.md$/.test(name)).length;
21
21
  }
22
+ /**
23
+ * Finished rounds. A brief that has `## Round N` headings is the source: every round heading not marked "current"
24
+ * counts once. Without such headings (a single-loop repo) the `done-vN.md` files are counted, since a handoff note
25
+ * named `done-v5.md` must not make a fifth round.
26
+ */
27
+ export function countRounds(briefText, doneFileNames) {
28
+ const rounds = new Set();
29
+ for (const line of (briefText ?? '').split(/\r?\n/)) {
30
+ const m = /^#{1,3}\s+Round\s+(\d+)\b(.*)$/i.exec(line);
31
+ // "### Round 5 done when" is a checklist inside a round, not a finished round.
32
+ if (m && !/\bcurrent\b/i.test(m[2] ?? '') && !/^\s*done when\b/i.test(m[2] ?? ''))
33
+ rounds.add(Number(m[1]));
34
+ }
35
+ return rounds.size > 0 ? rounds.size : countDoneRounds(doneFileNames);
36
+ }
22
37
  async function exists(file) {
23
38
  try {
24
39
  await stat(file);
@@ -62,7 +77,7 @@ export async function readLoopFacts(dir) {
62
77
  // The stop files sit at the repo root, per CLAUDE.md "Unattended mode".
63
78
  hasBlocked: await exists(path.join(dir, 'BLOCKED.md')),
64
79
  hasDone: await exists(path.join(dir, 'DONE.md')),
65
- roundsDone: countDoneRounds(aiFiles),
80
+ roundsDone: countRounds(await readOptional(path.join(dir, 'IDEA.md')), aiFiles),
66
81
  ...(lockAt && { lockAt }),
67
82
  ...(lastCommitAt && { lastCommitAt }),
68
83
  healthFailures: [],
@@ -1 +1 @@
1
- {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIlD,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,CAAC;AAG3B,yGAAyG;AACzG,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,gGAAgG;IAChG,MAAM,OAAO,GAAG,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,eAAe,CAAC,SAA4B;IAC1D,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,SAA6B,EAAE,aAAgC;IACzF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,+EAA+E;QAC/E,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;AACxE,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,6FAA6F;AAC7F,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE;YAC3E,GAAG,EAAE,GAAG;YACR,6FAA6F;YAC7F,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,uBAAuB,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE;SAClF,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAc,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC5E,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACjD,OAAO;QACL,wEAAwE;QACxE,UAAU,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACtD,OAAO,EAAE,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAChD,UAAU,EAAE,WAAW,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC;QAC/E,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;QACzB,GAAG,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,CAAC;QACrC,cAAc,EAAE,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;IAClE,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,137 @@
1
+ // Triggers (IDEA.md Round 6 B): `.ai/triggers.yaml` entries that fire at the moment they matter. Pure and browser-safe.
2
+ import { parse as parseYaml } from 'yaml';
3
+ import { matchGlob } from './lanes.js';
4
+ export const TRIGGERS_FILE = '.ai/triggers.yaml';
5
+ export const TRIGGER_LOG_FILE = '.ai/.trigger-log.jsonl';
6
+ /** All injected text of one hook call together. */
7
+ export const INJECT_CAP = 2000;
8
+ /** The part of a knowledge entry a trigger injects. */
9
+ export const ENTRY_CAP = 1200;
10
+ export const TRIGGER_KINDS = ['error', 'file', 'command', 'topic'];
11
+ /** Reads `.ai/triggers.yaml`; bad entries are skipped and reported, never thrown. */
12
+ export function parseTriggers(text) {
13
+ const out = { triggers: [], problems: [] };
14
+ let raw;
15
+ try {
16
+ raw = parseYaml(text);
17
+ }
18
+ catch (error) {
19
+ out.problems.push(`triggers.yaml is not valid YAML: ${error.message.split('\n')[0]}`);
20
+ return out;
21
+ }
22
+ if (raw === null || raw === undefined)
23
+ return out;
24
+ if (!Array.isArray(raw)) {
25
+ out.problems.push('triggers.yaml must be a list of entries, each starting with "- id:".');
26
+ return out;
27
+ }
28
+ const seen = new Set();
29
+ raw.forEach((item, i) => {
30
+ const at = `entry ${i + 1}`;
31
+ const e = item && typeof item === 'object' && !Array.isArray(item) ? item : undefined;
32
+ const id = e && typeof e.id === 'string' ? e.id : undefined;
33
+ const label = id ?? at;
34
+ const matches = e ? (Array.isArray(e.match) ? e.match : [e.match]).filter((m) => typeof m === 'string' && m !== '') : [];
35
+ if (!e || !id)
36
+ out.problems.push(`${at} has no "id". Add one, e.g. K-0042.`);
37
+ else if (!TRIGGER_KINDS.includes(e.on))
38
+ out.problems.push(`${label}: "on" must be one of ${TRIGGER_KINDS.join(', ')}.`);
39
+ else if (matches.length === 0)
40
+ out.problems.push(`${label}: "match" is empty. Give a string or a list of strings.`);
41
+ else if (typeof e.inject !== 'string' || !e.inject.trim())
42
+ out.problems.push(`${label}: "inject" is empty. Give text or a path to a knowledge entry.`);
43
+ else if (seen.has(id))
44
+ out.problems.push(`${label}: duplicate id. Ids must be unique.`);
45
+ else {
46
+ seen.add(id);
47
+ out.triggers.push({ id, on: e.on, match: matches, inject: e.inject });
48
+ }
49
+ });
50
+ return out;
51
+ }
52
+ /** `/pattern/` or `/pattern/i` is a regex; undefined if it is not that form or does not compile. */
53
+ function asRegExp(pattern) {
54
+ const m = /^\/(.+)\/([a-z]*)$/s.exec(pattern);
55
+ if (!m)
56
+ return undefined;
57
+ try {
58
+ return new RegExp(m[1] ?? '', m[2]?.replace(/[^imsu]/g, ''));
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ const posix = (p) => p.replace(/\\/g, '/').replace(/^\.\//, '');
65
+ /** Does one pattern match this value for this kind? error: substring or regex; file: glob; command: prefix or regex; topic: keyword in the prompt. */
66
+ export function matches(kind, pattern, value) {
67
+ const re = asRegExp(pattern);
68
+ if (re)
69
+ return re.test(value);
70
+ switch (kind) {
71
+ case 'error': return value.toLowerCase().includes(pattern.toLowerCase());
72
+ case 'file': return matchGlob(posix(pattern), posix(value));
73
+ case 'command': return value.trimStart().startsWith(pattern);
74
+ case 'topic': return value.toLowerCase().includes(pattern.toLowerCase());
75
+ }
76
+ }
77
+ /** Which trigger kinds each hook event may fire (IDEA.md B2). */
78
+ export const EVENT_KINDS = {
79
+ PostToolUseFailure: ['error', 'file'],
80
+ PostToolUse: ['file'],
81
+ PreToolUse: ['command'],
82
+ UserPromptSubmit: ['topic'],
83
+ };
84
+ const valueFor = (kind, facts) => ({ error: facts.error, file: facts.file, command: facts.command, topic: facts.prompt })[kind];
85
+ /** Triggers that fire for this event, in file order, minus ids already injected in this session. */
86
+ export function matchTriggers(triggers, facts, alreadyFired = new Set()) {
87
+ const kinds = EVENT_KINDS[facts.event] ?? [];
88
+ return triggers.filter((t) => {
89
+ if (alreadyFired.has(t.id) || !kinds.includes(t.on))
90
+ return false;
91
+ const value = valueFor(t.on, facts);
92
+ return value !== undefined && value !== '' && t.match.some((p) => matches(t.on, p, value));
93
+ });
94
+ }
95
+ /** The `hookSpecificOutput` object Claude Code reads from stdout; undefined when there is nothing to say. Text is capped as a whole. */
96
+ export function hookOutput(event, texts) {
97
+ const joined = texts.map((t) => t.trim()).filter(Boolean).join('\n\n');
98
+ if (!joined)
99
+ return undefined;
100
+ const additionalContext = joined.length <= INJECT_CAP ? joined : `${joined.slice(0, INJECT_CAP - 1)}…`;
101
+ return { hookSpecificOutput: { hookEventName: event, additionalContext } };
102
+ }
103
+ /** Text of a knowledge entry to inject: front matter dropped, first 1,200 characters. */
104
+ export function entryText(entry) {
105
+ const body = entry.replace(/^?---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, '').trim();
106
+ return body.length <= ENTRY_CAP ? body : `${body.slice(0, ENTRY_CAP - 1)}…`;
107
+ }
108
+ /** A trigger's `inject` is a path (ends in .md, no spaces) or literal text. */
109
+ export const injectPath = (inject) => (/^[^\s]+\.md$/.test(inject.trim()) ? inject.trim() : undefined);
110
+ /** Parses `.ai/.trigger-log.jsonl`; broken lines are skipped. */
111
+ export function parseTriggerLog(text) {
112
+ const out = [];
113
+ for (const line of text.split(/\r?\n/)) {
114
+ try {
115
+ const v = JSON.parse(line);
116
+ if (v && typeof v.at === 'string' && typeof v.session === 'string' && typeof v.id === 'string' && typeof v.event === 'string')
117
+ out.push({ at: v.at, session: v.session, id: v.id, event: v.event });
118
+ }
119
+ catch {
120
+ // skip
121
+ }
122
+ }
123
+ return out;
124
+ }
125
+ /** Fired counts per id: every defined trigger (0 if never), then ids only the log knows. Most fired first. */
126
+ export function triggerStats(triggers, log) {
127
+ const stats = new Map(triggers.map((t) => [t.id, { id: t.id, fired: 0, defined: true }]));
128
+ for (const r of log) {
129
+ const s = stats.get(r.id) ?? { id: r.id, fired: 0, defined: false };
130
+ s.fired++;
131
+ if (!s.lastFired || r.at > s.lastFired)
132
+ s.lastFired = r.at;
133
+ stats.set(r.id, s);
134
+ }
135
+ return [...stats.values()].sort((a, b) => b.fired - a.fired || a.id.localeCompare(b.id));
136
+ }
137
+ //# sourceMappingURL=triggers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"triggers.js","sourceRoot":"","sources":["../../src/core/triggers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ // Pure: turns the JSON body of a steer or schedule POST into a typed action, or says what is wrong with it (HTTP 400).
2
+ import { PACES, isPace } from '../core/parse/policy.js';
3
+ const nonEmpty = (value) => typeof value === 'string' && value.trim() !== '';
4
+ const asRecord = (body) => (typeof body === 'object' && body !== null ? body : {});
5
+ export function parseSteerBody(body) {
6
+ const { lane, text, standing, author } = asRecord(body);
7
+ if (!nonEmpty(lane))
8
+ return { ok: false, error: 'lane must be a non-empty string' };
9
+ // `all` only means every lane for a schedule wake; it is never a lane to steer.
10
+ if (lane === 'all')
11
+ return { ok: false, error: 'steer one lane at a time; "all" is not a lane' };
12
+ if (!nonEmpty(text))
13
+ return { ok: false, error: 'text must be a non-empty string' };
14
+ if (standing !== undefined && typeof standing !== 'boolean')
15
+ return { ok: false, error: 'standing must be true or false' };
16
+ return { ok: true, value: { lane, text, standing: standing === true, ...(nonEmpty(author) && { author }) } };
17
+ }
18
+ export function parseScheduleBody(body) {
19
+ const { action, lane, until, reason } = asRecord(body);
20
+ if (!nonEmpty(lane))
21
+ return { ok: false, error: 'lane must be a non-empty string' };
22
+ const why = nonEmpty(reason) ? { reason } : {};
23
+ switch (action) {
24
+ case 'pause':
25
+ return { ok: true, value: { action, lane, ...why } };
26
+ case 'snooze':
27
+ if (!nonEmpty(until))
28
+ return { ok: false, error: 'until must be an ISO time, a date or a span such as 3h' };
29
+ return { ok: true, value: { action, lane, until, ...why } };
30
+ case 'resume':
31
+ case 'wake':
32
+ return { ok: true, value: { action, lane } };
33
+ default:
34
+ return { ok: false, error: 'action must be pause, resume, snooze or wake' };
35
+ }
36
+ }
37
+ export function parsePaceBody(body) {
38
+ const { pace, lane } = asRecord(body);
39
+ if (!isPace(pace))
40
+ return { ok: false, error: `pace must be one of: ${PACES.join(', ')}` };
41
+ if (lane !== undefined && (!nonEmpty(lane) || lane === 'all'))
42
+ return { ok: false, error: 'lane must be a lane name; leave it out to set the whole loop' };
43
+ return { ok: true, value: { pace, ...(lane !== undefined && { lane }) } };
44
+ }
45
+ /** An optional `model` for `claude -p --model`; it is an argument, never a shell string, but keep it to model-id characters anyway. */
46
+ export function parseDraftBody(body) {
47
+ const { model } = asRecord(body);
48
+ if (model === undefined)
49
+ return { ok: true, value: {} };
50
+ if (typeof model !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:[\]-]{0,63}$/.test(model))
51
+ return { ok: false, error: 'model must be a model id such as claude-opus-5-5' };
52
+ return { ok: true, value: { model } };
53
+ }
54
+ //# sourceMappingURL=actionBody.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionBody.js","sourceRoot":"","sources":["../../src/server/actionBody.ts"],"names":[],"mappings":"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"}
@@ -20,8 +20,13 @@ export function formatUtc(iso) {
20
20
  return String(iso);
21
21
  return `${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} ${two(d.getUTCHours())}:${two(d.getUTCMinutes())} UTC`;
22
22
  }
23
- /** Adds the result-stating `label` to a check (with or without its `loopId`); an existing label is kept. */
23
+ /** The three-way result: an explicit `state` wins, else it follows `ok`. */
24
+ export function checkState(check) {
25
+ return check.state ?? (check.ok ? 'pass' : 'fail');
26
+ }
27
+ /** Adds the result-stating `label` and the `state` to a check (with or without its `loopId`); an existing label is kept. */
24
28
  export function withLabel(check) {
25
- return { ...check, label: check.label ?? checkLabel(check.kind, check.ok) };
29
+ const state = checkState(check);
30
+ return { ...check, state, label: check.label ?? (state === 'unchecked' ? 'Not checked' : checkLabel(check.kind, check.ok)) };
26
31
  }
27
32
  //# sourceMappingURL=checkLabels.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"checkLabels.js","sourceRoot":"","sources":["../../src/server/checkLabels.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,GAAuE;IACjF,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC;IAChC,YAAY,EAAE,CAAC,eAAe,EAAE,YAAY,CAAC;IAC7C,YAAY,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;IAC1D,eAAe,EAAE,CAAC,YAAY,EAAE,eAAe,CAAC;IAChD,eAAe,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC;IACnD,cAAc,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;IAChD,0BAA0B,EAAE,CAAC,kCAAkC,EAAE,+BAA+B,CAAC;CAClG,CAAC;AAEF,0GAA0G;AAC1G,MAAM,UAAU,UAAU,CAAC,IAAyB,EAAE,EAAW;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAC7G,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAEtD,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,GAAkB;IAC1C,MAAM,CAAC,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC;AAC9G,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,SAAS,CAAuE,KAAQ;IACtG,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9E,CAAC"}
1
+ {"version":3,"file":"checkLabels.js","sourceRoot":"","sources":["../../src/server/checkLabels.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,GAAuE;IACjF,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC;IAChC,YAAY,EAAE,CAAC,eAAe,EAAE,YAAY,CAAC;IAC7C,YAAY,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;IAC1D,eAAe,EAAE,CAAC,YAAY,EAAE,eAAe,CAAC;IAChD,eAAe,EAAE,CAAC,eAAe,EAAE,eAAe,CAAC;IACnD,cAAc,EAAE,CAAC,cAAc,EAAE,cAAc,CAAC;IAChD,0BAA0B,EAAE,CAAC,kCAAkC,EAAE,+BAA+B,CAAC;CAClG,CAAC;AAEF,0GAA0G;AAC1G,MAAM,UAAU,UAAU,CAAC,IAAyB,EAAE,EAAW;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAC7G,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAEtD,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,GAAkB;IAC1C,MAAM,CAAC,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC;AAC9G,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,KAAiE;IAC1F,OAAO,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,4HAA4H;AAC5H,MAAM,UAAU,SAAS,CAAkH,KAAQ;IACjJ,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;AAC/H,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { resolveInvocation } from '../cli/run.js';
3
+ export const abortableClaude = async (rawArgs, cwd, signal) => {
4
+ if (signal?.aborted)
5
+ throw new Error('The draft was stopped.');
6
+ const { command, args } = await resolveInvocation('claude', rawArgs);
7
+ return new Promise((resolve, reject) => {
8
+ execFile(command, args, { cwd, maxBuffer: 16 * 1024 * 1024, timeout: 15 * 60_000, ...(signal && { signal }) }, (error, stdout) => {
9
+ if (!error)
10
+ resolve(stdout);
11
+ else if (signal?.aborted)
12
+ reject(new Error('The draft was stopped.'));
13
+ else
14
+ reject(new Error(`Could not run \`claude -p\`: ${error.message}. Is Claude Code installed, on your PATH and signed in? \`loop next-round --draft --print-prompt\` prints the prompt to paste yourself.`));
15
+ });
16
+ });
17
+ };
18
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/server/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAKlD,MAAM,CAAC,MAAM,eAAe,GAAoB,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE;IAC7E,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC/D,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC/H,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,MAAM,CAAC,CAAC;iBACvB,IAAI,MAAM,EAAE,OAAO;gBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;;gBACjE,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,KAAK,CAAC,OAAO,yIAAyI,CAAC,CAAC,CAAC;QACjN,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -1,11 +1,12 @@
1
1
  // Pure (no Node imports): the GitHub pages the static dashboard opens instead of holding a token. Every URL is a
2
2
  // function of the repo slug, so a user with write access to the repo is the only one who can finish the action.
3
- import { formatPaused, PAUSED_FILE } from '../core/parse/index.js';
3
+ import { POLICY_FILE, formatPaused, formatPolicy, formatSchedule, withPace, formatSteerEntry, PAUSED_FILE, SCHEDULE_FILE, steerPath } from '../core/parse/index.js';
4
4
  import { isRepoSlug } from './live/urls.js';
5
5
  export const RELEASE_WORKFLOW = 'release.yml';
6
6
  export const NEXT_ROUND_FILE = '.ai/next-round.md';
7
7
  const GITHUB = 'https://github.com';
8
8
  const API = 'https://api.github.com';
9
+ export const NPM_REGISTRY = 'https://registry.npmjs.org';
9
10
  function checkedSlug(slug) {
10
11
  if (!isRepoSlug(slug))
11
12
  throw new Error(`not a GitHub repo slug: ${slug}`);
@@ -17,6 +18,12 @@ function checkedPath(file) {
17
18
  throw new Error(`not a repo file path: ${file}`);
18
19
  return file;
19
20
  }
21
+ /** A lane name is a single path segment of simple characters (the same rule core applies). */
22
+ function checkedLane(lane) {
23
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(lane))
24
+ throw new Error(`not a lane name: ${lane}`);
25
+ return lane;
26
+ }
20
27
  /** GitHub answers 414 above roughly 8 KB; stay well under it. */
21
28
  export const MAX_URL_LENGTH = 7500;
22
29
  /** A branch name keeps its slashes (`feature/x`) and encodes every other character. */
@@ -49,12 +56,32 @@ export const resumeUrl = (slug, branch = 'main') => deleteFileUrl(slug, PAUSED_F
49
56
  export function nextRoundUrl(slug, brief, branch = 'main') {
50
57
  return newFileUrl(slug, NEXT_ROUND_FILE, brief.endsWith('\n') ? brief : `${brief}\n`, branch);
51
58
  }
59
+ /** Steer, file missing: the new-file page for the lane's `steer.md` holding the intro line and the first entry. */
60
+ export function steerNewFileUrl(slug, lane, entry, branch = 'main') {
61
+ return newFileUrl(slug, steerPath(checkedLane(lane)), `# Steering for the ${lane} lane\n\n${formatSteerEntry(entry)}`, branch);
62
+ }
63
+ /** Steer, file exists: GitHub cannot prefill an edit, so the UI copies `formatSteerEntry` and opens this editor page. */
64
+ export const steerEditUrl = (slug, lane, branch = 'main') => editFileUrl(slug, steerPath(checkedLane(lane)), branch);
65
+ /** Schedule edit: the editor page for `.ai/schedule.json`; the UI copies `scheduleEditText` to paste over the file. */
66
+ export const scheduleEditUrl = (slug, branch = 'main') => editFileUrl(slug, SCHEDULE_FILE, branch);
67
+ /** The whole `schedule.json` text after the edit, in the exact format `loop schedule` writes. */
68
+ export const scheduleEditText = (schedule) => formatSchedule(schedule);
69
+ /** Pace edit: the editor page for `.ai/policy.json`; the UI copies `paceEditText` to paste over the file. */
70
+ export const paceEditUrl = (slug, branch = 'main') => editFileUrl(slug, POLICY_FILE, branch);
71
+ /** The whole `policy.json` text after setting the loop's pace (or one lane's), in the exact format `loop pace` writes. */
72
+ export const paceEditText = (policy, pace, lane) => formatPolicy(withPace(policy, pace, lane));
52
73
  /** The Actions page where the owner presses "Run workflow" for the release workflow. */
53
74
  export const releaseWorkflowUrl = (slug) => `${GITHUB}/${checkedSlug(slug)}/actions/workflows/${RELEASE_WORKFLOW}`;
54
75
  /** API URL for the latest run of the release workflow (the dashboard shows its result). */
55
76
  export const latestReleaseRunApiUrl = (slug) => `${API}/repos/${checkedSlug(slug)}/actions/workflows/${RELEASE_WORKFLOW}/runs?per_page=1`;
56
77
  /** API URL listing the repo's tags (for the ship-check "tag does not exist yet" item). */
57
78
  export const tagsApiUrl = (slug) => `${API}/repos/${checkedSlug(slug)}/tags?per_page=100`;
79
+ /** The npm registry's package document (`dist-tags`, `versions`); it sends `Access-Control-Allow-Origin: *`, so the static demo may read it. Scoped names keep their `@` and encode the slash. */
80
+ export const npmPackageUrl = (name) => {
81
+ if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(name))
82
+ throw new Error(`not an npm package name: ${name}`);
83
+ return `${NPM_REGISTRY}/${name.replace('/', '%2f')}`;
84
+ };
58
85
  /** API URL matching one release tag by prefix; unlike `tagsApiUrl` it is not capped by the repo's first 100 tags. Response items carry `ref: "refs/tags/<name>"`. */
59
86
  export const tagLookupApiUrl = (slug, version) => `${API}/repos/${checkedSlug(slug)}/git/matching-refs/tags/v${encodeURIComponent(version)}`;
60
87
  /** True when `nextRoundUrl` can carry this brief; otherwise the UI offers Copy and the blank new-file page. */