ll-skills 2.0.2 → 3.0.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 (115) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +42 -20
  3. package/agents/ll-executor.md +1 -0
  4. package/assets/preamble.md +29 -35
  5. package/bin/install.js +4 -1
  6. package/hooks/ll-precompact.js +29 -1
  7. package/hooks/ll-skills-check-update.js +6 -6
  8. package/hooks/ll-state.js +30 -2
  9. package/package.json +3 -2
  10. package/scripts/evals/README.md +57 -0
  11. package/scripts/evals/cases/auto-dry-run/assert.sh +35 -0
  12. package/scripts/evals/cases/auto-dry-run/case.json +8 -0
  13. package/scripts/evals/cases/auto-dry-run/prompt.txt +1 -0
  14. package/scripts/evals/cases/auto-empty-repo/assert.sh +25 -0
  15. package/scripts/evals/cases/auto-empty-repo/case.json +8 -0
  16. package/scripts/evals/cases/auto-empty-repo/fixture/.gitkeep +0 -0
  17. package/scripts/evals/cases/auto-empty-repo/prompt.txt +1 -0
  18. package/scripts/evals/cases/decide-final-round/assert.sh +32 -0
  19. package/scripts/evals/cases/decide-final-round/case.json +8 -0
  20. package/scripts/evals/cases/decide-final-round/fixture/README.md +3 -0
  21. package/scripts/evals/cases/decide-final-round/prompt.txt +1 -0
  22. package/scripts/evals/cases/executor-block/assert.sh +33 -0
  23. package/scripts/evals/cases/executor-block/case.json +8 -0
  24. package/scripts/evals/cases/executor-block/prompt.txt +14 -0
  25. package/scripts/evals/cases/goal-autonomous/assert.sh +35 -0
  26. package/scripts/evals/cases/goal-autonomous/case.json +8 -0
  27. package/scripts/evals/cases/goal-autonomous/fixture/PLAN.md +42 -0
  28. package/scripts/evals/cases/goal-autonomous/fixture/PROGRESS.md +20 -0
  29. package/scripts/evals/cases/goal-autonomous/fixture/ROADMAP.md +29 -0
  30. package/scripts/evals/cases/goal-autonomous/fixture/package.json +8 -0
  31. package/scripts/evals/cases/goal-autonomous/fixture/src/money.js +6 -0
  32. package/scripts/evals/cases/goal-autonomous/fixture/test/reconcile.test.js +8 -0
  33. package/scripts/evals/cases/goal-autonomous/prompt.txt +1 -0
  34. package/scripts/evals/cases/implement-review-gate/assert.sh +35 -0
  35. package/scripts/evals/cases/implement-review-gate/case.json +8 -0
  36. package/scripts/evals/cases/implement-review-gate/prompt.txt +1 -0
  37. package/scripts/evals/cases/implement-stops-at-next/assert.sh +39 -0
  38. package/scripts/evals/cases/implement-stops-at-next/case.json +9 -0
  39. package/scripts/evals/cases/implement-stops-at-next/prompt.txt +1 -0
  40. package/scripts/evals/cases/preamble-no-ritual/assert.sh +17 -0
  41. package/scripts/evals/cases/preamble-no-ritual/case.json +8 -0
  42. package/scripts/evals/cases/preamble-no-ritual/fixture/README.md +3 -0
  43. package/scripts/evals/cases/preamble-no-ritual/fixture/src/a.ts +3 -0
  44. package/scripts/evals/cases/preamble-no-ritual/prompt.txt +1 -0
  45. package/scripts/evals/cases/router-execute/assert.sh +12 -0
  46. package/scripts/evals/cases/router-execute/case.json +8 -0
  47. package/scripts/evals/cases/router-execute/prompt.txt +1 -0
  48. package/scripts/evals/cases/router-research/assert.sh +11 -0
  49. package/scripts/evals/cases/router-research/case.json +8 -0
  50. package/scripts/evals/cases/router-research/fixture/README.md +3 -0
  51. package/scripts/evals/cases/router-research/prompt.txt +1 -0
  52. package/scripts/evals/cases/router-small/assert.sh +21 -0
  53. package/scripts/evals/cases/router-small/case.json +8 -0
  54. package/scripts/evals/cases/router-small/fixture/README.md +17 -0
  55. package/scripts/evals/cases/router-small/prompt.txt +1 -0
  56. package/scripts/evals/cases/scout-no-plan/assert.sh +41 -0
  57. package/scripts/evals/cases/scout-no-plan/case.json +8 -0
  58. package/scripts/evals/cases/scout-no-plan/prompt.txt +8 -0
  59. package/scripts/evals/cases/verifier-weakened-test/assert.sh +19 -0
  60. package/scripts/evals/cases/verifier-weakened-test/case.json +8 -0
  61. package/scripts/evals/cases/verifier-weakened-test/prompt.txt +13 -0
  62. package/scripts/evals/cases/verifier-weakened-test/setup.sh +19 -0
  63. package/scripts/evals/fixtures/manual-contract/out.json +29 -0
  64. package/scripts/evals/fixtures/manual-contract/out.txt +5 -0
  65. package/scripts/evals/fixtures/manual-contract/with-skill.json +46 -0
  66. package/scripts/evals/lib/assert.sh +107 -0
  67. package/scripts/evals/lib/extract.js +73 -0
  68. package/scripts/evals/run.sh +369 -0
  69. package/scripts/fixtures/auto-closed/PLAN.md +5 -0
  70. package/scripts/fixtures/auto-closed/PROGRESS.md +20 -0
  71. package/scripts/fixtures/auto-closed/ROADMAP.md +6 -0
  72. package/scripts/fixtures/auto-closed/docs/DELIVERY.md +3 -0
  73. package/scripts/fixtures/auto-decisions/decisions/DEC-0001-taken-alone.md +13 -0
  74. package/scripts/fixtures/auto-decisions/decisions/DEC-0002-owner.md +13 -0
  75. package/scripts/fixtures/auto-noroadmap/PLAN.md +20 -0
  76. package/scripts/fixtures/auto-noroadmap/PROGRESS.md +11 -0
  77. package/scripts/fixtures/auto-verify-next/PLAN.md +5 -0
  78. package/scripts/fixtures/auto-verify-next/PROGRESS.md +18 -0
  79. package/scripts/fixtures/auto-verify-next/ROADMAP.md +5 -0
  80. package/scripts/fixtures/auto-verify-next/phases/01/PLAN.md +6 -0
  81. package/scripts/fixtures/evals-auto/auto-dry-run/pass.txt +18 -0
  82. package/scripts/fixtures/evals-auto/auto-empty-repo/pass.txt +2 -0
  83. package/scripts/fixtures/evals-auto/goal-autonomous/pass.txt +29 -0
  84. package/scripts/fixtures/lint-bad/folded-description/SKILL.md +13 -0
  85. package/scripts/fixtures/lint-bad/model-invocation-false/SKILL.md +10 -0
  86. package/scripts/fixtures/next-bad/skills/ll-bad/SKILL.md +30 -0
  87. package/scripts/fixtures/next-good/skills/ll-good/SKILL.md +26 -0
  88. package/scripts/fixtures/project/PROGRESS.md +4 -0
  89. package/scripts/lint-contract.cjs +495 -0
  90. package/scripts/lint-prompts.sh +396 -0
  91. package/scripts/ll-tools.js +465 -447
  92. package/scripts/smoke-test.sh +380 -1
  93. package/skills/ll-auto/SKILL.md +74 -0
  94. package/skills/ll-auto/references/run.md +75 -0
  95. package/skills/ll-auto/references/stages.md +66 -0
  96. package/skills/ll-auto/scripts/ll-auto.js +345 -0
  97. package/skills/ll-brainstorm/SKILL.md +5 -4
  98. package/skills/ll-brainstorm/references/decision-policy.md +3 -0
  99. package/skills/ll-close/SKILL.md +5 -5
  100. package/skills/ll-close/references/delivery.md +3 -1
  101. package/skills/ll-decide/SKILL.md +13 -11
  102. package/skills/ll-decide/references/decision-policy.md +3 -0
  103. package/skills/ll-decide/references/interview.md +10 -0
  104. package/skills/ll-decide/references/plan-skeleton.md +14 -14
  105. package/skills/ll-decide/references/premise-gate.md +7 -0
  106. package/skills/ll-goal/SKILL.md +22 -4
  107. package/skills/ll-goal/references/goal-template.md +57 -0
  108. package/skills/ll-implement/SKILL.md +4 -2
  109. package/skills/ll-implement/references/decision-policy.md +3 -0
  110. package/skills/ll-oncall/SKILL.md +3 -2
  111. package/skills/ll-refine/SKILL.md +3 -2
  112. package/skills/ll-research/SKILL.md +3 -2
  113. package/skills/ll-resume/SKILL.md +4 -3
  114. package/skills/ll-update/SKILL.md +6 -1
  115. package/skills/ll-verify/SKILL.md +2 -1
@@ -0,0 +1,75 @@
1
+ # The run — `docs/AUTO.md`, decisions, end block
2
+
3
+ ## The file
4
+
5
+ `ll-auto.js auto-md --objective "<objective>" --flags "<the arguments>"` prints the body; step 1
6
+ redirects it to `docs/AUTO.md`. Five sections, no frontmatter:
7
+
8
+ ```
9
+ # AUTO — autonomous run
10
+
11
+ ## Objective
12
+
13
+ <the objective, on its own line>
14
+
15
+ ## Flags
16
+
17
+ <the flags string, on its own line>
18
+
19
+ ## Roteiro
20
+
21
+ | # | stage | command | status | evidence |
22
+ |---|---|---|---|---|
23
+ | 1 | phase-08 | ll-implement 08 --no-talk | todo | ROADMAP row: PLANNED |
24
+
25
+ ## Decisions taken alone
26
+
27
+ (filled at the end of the run)
28
+
29
+ ## Log
30
+
31
+ (one dated line per stage transition)
32
+ ```
33
+
34
+ Status of a row: `todo` · `half` · `running` · `done` · `skipped` · `waiting`. Exactly one row is
35
+ `running` at a time. The `evidence` column is the one `detect` gave for that stage, refreshed when
36
+ the row is marked `done` or `half`.
37
+
38
+ A log line is `<YYYY-MM-DD HH:MM> <stage> <status> — <evidence or the stage's last line>`, appended,
39
+ never rewritten. The `## Flags` section is what `--resume` reads, so it is written once and left
40
+ alone; a run restarted with different flags rewrites the whole file.
41
+
42
+ ## Decisions only the owner can take
43
+
44
+ After every stage, list the `decisions/*.md` whose status is `WAITING`.
45
+
46
+ Without `--auto-decision`:
47
+ - the run continues through the stages that do not depend on them; a phase whose ROADMAP section
48
+ carries `stop: owner` is skipped and its row is marked `skipped`;
49
+ - the rows that depend on a WAITING decision are marked `waiting`;
50
+ - when nothing else can run, print the WAITING list — one line per file, with the question and the
51
+ recommended option — and stop with `▶ Next — /clear, then ll-auto --resume`.
52
+
53
+ With `--auto-decision`:
54
+ - append to each WAITING decision file a dated line
55
+ `status: DECIDED — <the recommended option> [decided by absence — revisable]`;
56
+ - clear the `stop: owner` marks those decisions held in ROADMAP;
57
+ - continue the roteiro; the marker is what the end block lists.
58
+
59
+ Nothing is decided outside these two paths: a decision that has no recommended option stays
60
+ `WAITING` and stops the run even under `--auto-decision`, because there is nothing to pick.
61
+
62
+ ## The end block
63
+
64
+ `ll-auto.js report --json` answers `{"decisions":[{file, title, line}]}` for every `decisions/*.md`
65
+ carrying `[decided by absence — revisable]`. Print it, and write the same lines into the
66
+ `## Decisions taken alone` section:
67
+
68
+ ```
69
+ Decisions taken alone — review them:
70
+ - decisions/DEC-0007-retry-policy.md — retry policy for the provider adapter (line 12)
71
+ - decisions/DEC-0009-report-window.md — the window the report counts (line 9)
72
+ ```
73
+
74
+ An empty list is printed as `Decisions taken alone: none`. Then the count line of the Completion
75
+ criterion, then the handover. The run ends there: the next command belongs to the owner.
@@ -0,0 +1,66 @@
1
+ # Stages — detection, commands, flags
2
+
3
+ ## Detection
4
+
5
+ `ll-auto.js detect` reads only the files below and is deterministic: the same tree gives the same
6
+ table twice. Every entry answers `{id, status, evidence}` with status `todo`, `half` or `done`.
7
+
8
+ | stage | done when | half when | evidence field |
9
+ |---|---|---|---|
10
+ | `research` | a `docs/research-*/SUMMARY.md` exists | never | the path, or what is missing |
11
+ | `brainstorm` | `docs/decide/OPENING.md` or any `phases/NN/DECISIONS.md` exists | never | the path, or what is missing |
12
+ | `decide` | `PLAN.md` at the root and `PROGRESS.md` with an `ll-state` block | never | `PLAN.md + PROGRESS.md ll-state block` |
13
+ | `phase-NN` | the ROADMAP row state starts with `DONE`, or `## Epilogue — phase NN` exists over a board that is for another phase or all `passes: true` | `phases/NN/PLAN.md` exists and the phase is not done | the ROADMAP row, or the epilogue and the milestones still red |
14
+ | `verify-NN` | `phases/NN/VERIFICATION.md` exists | never | the path, or what is missing |
15
+ | `close` | `docs/DELIVERY.md` exists and the last ROADMAP phase has an epilogue | never | the path, or what is missing |
16
+
17
+ One `phase-NN` per row of the ROADMAP table; with no ROADMAP.md, the phases come from the same
18
+ table inline at `PLAN.md` §8, same columns (`| phase | name | depends_on | requirements | state |`);
19
+ with no `PLAN.md`, none. One `verify-NN` per phase row. `close` is always last.
20
+
21
+ A phase with an epilogue over a board that still shows a milestone `passes: false` is `half`, not
22
+ `done`: the epilogue was written, the phase was not finished. `ll-implement NN` resumes it from the
23
+ board, which is why a `half` phase enters the roteiro with the same command as a `todo` one.
24
+
25
+ ## The command per stage
26
+
27
+ | stage | command | note |
28
+ |---|---|---|
29
+ | `research` | `ll-research "<objective>"` | only with `--research`; the objective is the run's |
30
+ | `brainstorm` | `ll-brainstorm project --no-talk` | only with `--brainstorm`; `--interactive` drops `--no-talk` |
31
+ | `decide` | `ll-decide project --no-talk` | enters whenever it is `todo` |
32
+ | `phase-NN` | `ll-implement NN --no-talk` | `--interactive` drops `--no-talk`; a `half` phase resumes with the same command |
33
+ | `verify-NN` | `ll-verify NN` | never `--external`: that flag hands the audit to another session, which this run is not |
34
+ | `close` | `ll-close --no-talk` | `--milestone <name>` when the ROADMAP is complete |
35
+
36
+ The number is written as the ROADMAP writes it (`08`, not `8`). The roteiro entry carries the
37
+ command ready to follow; the stage's own skill decides everything inside it.
38
+
39
+ ## Which stages enter the roteiro
40
+
41
+ `research` and `brainstorm` enter only when their flag is set and their status is not `done`.
42
+ `decide` enters when it is `todo`. Every phase row that is not `done` enters, filtered by
43
+ `--from/--to/--only`, the `half` ones first as a resume. `verify-NN` enters right after its phase
44
+ when `--verify all` is set or when the phase's epilogue names `ll-verify NN` in its handover line.
45
+ `close` enters last, unless `--only` or `--to` cut the roteiro before the last row.
46
+
47
+ ## Flags
48
+
49
+ | flag | effect |
50
+ |---|---|
51
+ | `"<objective>"` | the run's objective: the argument of `ll-research`, and the `## Objective` of `docs/AUTO.md` |
52
+ | `--research` | put `research` in the roteiro when it is not done |
53
+ | `--brainstorm` | put `brainstorm` in the roteiro when it is not done |
54
+ | `--interactive` | drop `--no-talk` from `ll-brainstorm` and `ll-implement`: those stages talk to the owner |
55
+ | `--auto-decision` | resolve every owner-only decision to its recommended option and continue; without it the run stops when one blocks the way |
56
+ | `--pause-at <stage\|N>` | stop after that stage or phase, with `▶ Next — /clear, then ll-auto --resume` |
57
+ | `--from N` | skip the phase rows below N |
58
+ | `--to N` | skip the phase rows above N |
59
+ | `--only N` | phase N alone; implies `--from N --to N` and cuts `close` |
60
+ | `--verify all` | a `verify-NN` after every phase, whatever the epilogue says |
61
+ | `--redo <stage>` | force a `done` stage back to `todo` in the roteiro; repeatable |
62
+ | `--dry-run` | print the roteiro table and stop, before writing `docs/AUTO.md` |
63
+ | `--resume` | read the flags from the `## Flags` section of `docs/AUTO.md` and continue from the first row that is not `done` |
64
+
65
+ `--pause-at` and `--redo` are repeatable. A plain invocation in a repository that already carries
66
+ `docs/AUTO.md` behaves as `--resume`: the flags of the file are the flags of the run.
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env node
2
+ // ll-auto helper — reads the cycle state from disk. Self-contained: `fs` and `path` only.
3
+ // Read commands never fail: on a broken input they print {"ok":false,"reason":…} and exit 0.
4
+ 'use strict';
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const J = path.join;
8
+ function readText(f) { try { return fs.readFileSync(f, 'utf8'); } catch { return null; } }
9
+ function isDir(p) { try { return fs.statSync(p).isDirectory(); } catch { return false; } }
10
+ function exists(p) { try { fs.statSync(p); return true; } catch { return false; } }
11
+ function ls(d) { try { return fs.readdirSync(d).sort(); } catch { return []; } }
12
+ function nn(v) { return String(v).trim().padStart(2, '0'); }
13
+
14
+ // --- state block reader, copied from scripts/ll-tools.js (<ll-shared:state>) -----------------
15
+ function readAnchoredBlock(file, name) {
16
+ let txt;
17
+ try { txt = fs.readFileSync(file, 'utf8'); } catch { return null; }
18
+ const all = txt.split('\n');
19
+ const open = new RegExp('^<!--\\s*' + name + '\\s*-->\\s*$');
20
+ const close = new RegExp('^<!--\\s*/' + name + '\\s*-->\\s*$');
21
+ let start = -1, end = -1;
22
+ for (let i = 0; i < all.length; i++) {
23
+ if (start < 0) { if (open.test(all[i])) start = i; } else if (close.test(all[i])) { end = i; break; }
24
+ }
25
+ if (start < 0 || end < 0) return null;
26
+ return { file, start, end, all, lines: all.slice(start + 1, end) };
27
+ }
28
+ function llSplitFlow(str) { // top-level commas only
29
+ const out = [];
30
+ let cur = '', depth = 0, q = null;
31
+ for (const ch of str) {
32
+ if (q) { cur += ch; if (ch === q) q = null; continue; }
33
+ if (ch === '"' || ch === "'") { q = ch; cur += ch; continue; }
34
+ if (ch === '[' || ch === '{') depth++;
35
+ else if (ch === ']' || ch === '}') depth--;
36
+ else if (ch === ',' && depth === 0) { out.push(cur); cur = ''; continue; }
37
+ cur += ch;
38
+ }
39
+ if (cur.trim() !== '') out.push(cur);
40
+ return out;
41
+ }
42
+ function llScalar(raw) {
43
+ const s = String(raw).trim();
44
+ if (/^\[[\s\S]*\]$/.test(s)) return llSplitFlow(s.slice(1, -1)).map(llScalar);
45
+ if (/^\{[\s\S]*\}$/.test(s)) {
46
+ const o = {};
47
+ for (const part of llSplitFlow(s.slice(1, -1))) {
48
+ const m = /^\s*([A-Za-z_][\w-]*)\s*:\s*([\s\S]*)$/.exec(part);
49
+ if (m) o[m[1]] = llScalar(m[2]);
50
+ }
51
+ return o;
52
+ }
53
+ if (/^(true|yes)$/i.test(s)) return true;
54
+ if (/^(false|no)$/i.test(s)) return false;
55
+ if (/^".*"$/.test(s) || /^'.*'$/.test(s)) return s.slice(1, -1);
56
+ return s;
57
+ }
58
+ function parseStateBlockLite(bl) {
59
+ const st = { phase: null, milestones: {}, order: [] };
60
+ let inM = false;
61
+ for (let i = 0; i < bl.length; i++) {
62
+ const line = bl[i].replace(/\s+$/, '');
63
+ if (!line.trim() || /^\s*#/.test(line)) continue;
64
+ let m = /^phase:\s*(.*)$/.exec(line);
65
+ if (m) { st.phase = String(llScalar(m[1])); inM = false; continue; }
66
+ if (/^milestones:\s*(\{\s*\})?$/.test(line)) { inM = true; continue; }
67
+ m = /^(\s*)(M\d+|G-\d+):\s*\{(.*)\}\s*$/.exec(line);
68
+ if (m && inM) {
69
+ st.milestones[m[2]] = llScalar('{' + m[3] + '}');
70
+ st.order.push({ id: m[2], index: i, indent: m[1] });
71
+ } else if (/^\S/.test(line)) inM = false;
72
+ }
73
+ return st;
74
+ }
75
+
76
+ // --- phase rows ------------------------------------------------------------------------------
77
+ // Rows of the ROADMAP phase table: a header carrying `phase` and numeric first cells.
78
+ function phaseRows(md) {
79
+ const rows = [], seen = new Set();
80
+ let cols = null;
81
+ for (const line of String(md || '').split('\n')) {
82
+ if (!/^\s*\|/.test(line)) { cols = null; continue; }
83
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
84
+ if (/^:?-{2,}/.test(cells[0] || '')) continue; // separator row
85
+ if (!/^\d{1,3}$/.test(cells[0] || '')) { cols = cells.map((c) => c.toLowerCase()); continue; }
86
+ if (!cols || cols.indexOf('phase') < 0) continue;
87
+ const i = cols.indexOf('state');
88
+ const id = nn(cells[0]);
89
+ if (seen.has(id)) continue;
90
+ seen.add(id);
91
+ rows.push({ nn: id, state: (i >= 0 ? cells[i] : cells[cells.length - 1]) || '' });
92
+ }
93
+ return rows;
94
+ }
95
+ // Without ROADMAP.md the rows live in the `## §8 Phases` table of PLAN.md (`| NN | name | … |`,
96
+ // a header cell `phase` and, when there is one, a `state` cell); without PLAN.md there are no
97
+ // phases at all. The §8 slice runs to the next `## ` heading — `$` under /m ends at every line.
98
+ function rowsOf(root) {
99
+ const rm = readText(J(root, 'ROADMAP.md'));
100
+ if (rm !== null) return phaseRows(rm);
101
+ const plan = readText(J(root, 'PLAN.md'));
102
+ if (plan === null) return [];
103
+ const h = /^##\s*§?8\b[^\n]*\n/m.exec(plan);
104
+ if (!h) return [];
105
+ const rest = plan.slice(h.index + h[0].length);
106
+ const next = rest.search(/^##\s/m);
107
+ return phaseRows(next < 0 ? rest : rest.slice(0, next));
108
+ }
109
+
110
+ // --- detect ----------------------------------------------------------------------------------
111
+ function phaseStage(root, row, ctx) {
112
+ if (/^DONE/.test(row.state)) return { status: 'done', evidence: 'ROADMAP row: ' + row.state };
113
+ const plan = 'phases/' + row.nn + '/PLAN.md';
114
+ if (ctx.epilogues.has(row.nn)) {
115
+ const own = ctx.state && nn(ctx.state.phase || '') === row.nn;
116
+ const red = own ? Object.keys(ctx.state.milestones).filter((k) => ctx.state.milestones[k].passes !== true) : [];
117
+ if (!own || red.length === 0) return { status: 'done', evidence: 'PROGRESS.md: ## Epilogue — phase ' + row.nn };
118
+ if (exists(J(root, 'phases', row.nn, 'PLAN.md'))) {
119
+ return { status: 'half', evidence: 'epilogue over a board with ' + red.join(', ') + ' not passing' };
120
+ }
121
+ }
122
+ if (exists(J(root, 'phases', row.nn, 'PLAN.md'))) return { status: 'half', evidence: plan };
123
+ return { status: 'todo', evidence: 'ROADMAP row: ' + (row.state || '-') };
124
+ }
125
+
126
+ function detect(root) {
127
+ const stages = [];
128
+ const add = (id, status, evidence) => stages.push({ id, status, evidence });
129
+ const research = ls(J(root, 'docs')).filter((n) => /^research-/.test(n) && exists(J(root, 'docs', n, 'SUMMARY.md')));
130
+ add('research', research.length ? 'done' : 'todo',
131
+ research.length ? 'docs/' + research[0] + '/SUMMARY.md' : 'no docs/research-*/SUMMARY.md');
132
+
133
+ const opening = exists(J(root, 'docs', 'decide', 'OPENING.md'));
134
+ const decided = ls(J(root, 'phases')).filter((n) => exists(J(root, 'phases', n, 'DECISIONS.md')));
135
+ add('brainstorm', opening || decided.length ? 'done' : 'todo',
136
+ opening ? 'docs/decide/OPENING.md'
137
+ : decided.length ? 'phases/' + decided[0] + '/DECISIONS.md' : 'no docs/decide/OPENING.md');
138
+
139
+ const progress = J(root, 'PROGRESS.md');
140
+ const block = readAnchoredBlock(progress, 'll-state');
141
+ const state = block ? parseStateBlockLite(block.lines) : null;
142
+ const hasPlan = exists(J(root, 'PLAN.md'));
143
+ add('decide', hasPlan && state ? 'done' : 'todo',
144
+ hasPlan && state ? 'PLAN.md + PROGRESS.md ll-state block'
145
+ : hasPlan ? 'PROGRESS.md carries no ll-state block' : 'no PLAN.md at the root');
146
+ const epilogues = new Set();
147
+ for (const line of (readText(progress) || '').split('\n')) {
148
+ const h = /^## Epilogue — phase (\S+)/.exec(line);
149
+ if (h) epilogues.add(nn(h[1]));
150
+ }
151
+ const rows = rowsOf(root);
152
+ const ctx = { epilogues, state };
153
+ for (const row of rows) {
154
+ const s = phaseStage(root, row, ctx);
155
+ add('phase-' + row.nn, s.status, s.evidence);
156
+ }
157
+ for (const row of rows) {
158
+ const v = 'phases/' + row.nn + '/VERIFICATION.md';
159
+ const ok = exists(J(root, v));
160
+ add('verify-' + row.nn, ok ? 'done' : 'todo', ok ? v : 'no ' + v);
161
+ }
162
+ const last = rows.length ? rows[rows.length - 1].nn : null;
163
+ const delivery = exists(J(root, 'docs', 'DELIVERY.md'));
164
+ const closed = delivery && last !== null && epilogues.has(last);
165
+ add('close', closed ? 'done' : 'todo',
166
+ closed ? 'docs/DELIVERY.md + epilogue for phase ' + last
167
+ : delivery ? 'docs/DELIVERY.md without an epilogue for the last phase' : 'no docs/DELIVERY.md');
168
+ return { ok: true, root, stages };
169
+ }
170
+
171
+ // --- next-cmd --------------------------------------------------------------------------------
172
+ // The handoff grammar of lint-contract rule 6: `▶ Next — /clear, then <cmd> (alternatives)`.
173
+ // Backticks are decoration; one trailing parenthetical is dropped. The last line in the file wins.
174
+ function nextCmdOf(txt) {
175
+ let out = '';
176
+ for (const line of String(txt || '').split('\n')) {
177
+ const m = /▶ Next\s*—(.*)$/.exec(line);
178
+ if (!m) continue;
179
+ const hand = m[1].replace(/`/g, '').trim();
180
+ const o = /^\/clear\s*,\s*then\s+(.*)$/.exec(hand);
181
+ if (o) out = o[1].replace(/\([^()]*\)\s*$/, '').trim();
182
+ }
183
+ return out;
184
+ }
185
+ // The slice of PROGRESS.md owned by phase NN's epilogue; the whole file when it has none.
186
+ function epilogueText(root, id) {
187
+ const txt = readText(J(root, 'PROGRESS.md')) || '';
188
+ const lines = txt.split('\n');
189
+ let start = -1;
190
+ for (let i = 0; i < lines.length; i++) {
191
+ const h = /^## Epilogue — phase (\S+)/.exec(lines[i]);
192
+ if (h && nn(h[1]) === id) start = i;
193
+ }
194
+ if (start < 0) return txt;
195
+ let end = lines.length;
196
+ for (let i = start + 1; i < lines.length; i++) if (/^## /.test(lines[i])) { end = i; break; }
197
+ return lines.slice(start, end).join('\n');
198
+ }
199
+
200
+ // --- roteiro ---------------------------------------------------------------------------------
201
+ const FLAG_ONE = { '--research': 'research', '--brainstorm': 'brainstorm', '--interactive': 'interactive',
202
+ '--auto-decision': 'auto_decision', '--dry-run': 'dry_run', '--resume': 'resume' };
203
+ const FLAG_VAL = { '--pause-at': 'pause_at', '--redo': 'redo', '--from': 'from', '--to': 'to',
204
+ '--only': 'only', '--verify': 'verify' };
205
+ // A stage reference in --pause-at / --redo: an id, a bare phase number, or `phase 7`.
206
+ function stageId(x) {
207
+ const s = String(x || '').trim();
208
+ if (/^\d{1,3}$/.test(s)) return 'phase-' + nn(s);
209
+ const m = /^(phase|verify)[\s-]*(\d{1,3})$/.exec(s);
210
+ return m ? m[1] + '-' + nn(m[2]) : s;
211
+ }
212
+ function parseFlags(str) {
213
+ const f = { pause_at: [], redo: [], from: null, to: null, only: null, verify: null };
214
+ for (const k of Object.values(FLAG_ONE)) f[k] = false;
215
+ const t = String(str || '').trim().split(/\s+/).filter(Boolean);
216
+ for (let i = 0; i < t.length; i++) {
217
+ if (FLAG_ONE[t[i]]) { f[FLAG_ONE[t[i]]] = true; continue; }
218
+ const k = FLAG_VAL[t[i]];
219
+ if (!k) continue;
220
+ const v = i + 1 < t.length && !/^--/.test(t[i + 1]) ? t[++i] : null;
221
+ if (k === 'pause_at' || k === 'redo') { if (v !== null) f[k].push(v); } else f[k] = v;
222
+ }
223
+ if (f.only !== null) { f.from = f.only; f.to = f.only; } // --only N implies from = to = N
224
+ return f;
225
+ }
226
+ const num = (x) => Number(String(x).replace(/[^\d.]/g, '')) || 0;
227
+ const talk = (interactive) => (interactive ? '' : ' --no-talk'); // --interactive drops --no-talk
228
+
229
+ function roteiro(root, flagStr, objective) {
230
+ const d = detect(root);
231
+ const f = parseFlags(flagStr);
232
+ const st = {};
233
+ for (const s of d.stages) st[s.id] = s;
234
+ const redo = new Set(f.redo.map(stageId));
235
+ const pause = new Set(f.pause_at.map(stageId));
236
+ const obj = String(objective || '').trim();
237
+ // Empty repository per D-03-06: no research, no OPENING.md, no PLAN.md.
238
+ const bare = st.research.status === 'todo' && st.brainstorm.status === 'todo' && !exists(J(root, 'PLAN.md'));
239
+ if (bare && !obj) return { ok: true, root, needs_objective: true, roteiro: [] };
240
+ const out = [];
241
+ const want = (id) => redo.has(id) || !st[id] || st[id].status !== 'done';
242
+ const push = (id, command) => out.push({ stage: id, command,
243
+ status: st[id] ? st[id].status : 'todo', pause_after: pause.has(id) });
244
+ if (f.research && want('research')) push('research', 'll-research "' + (obj || '<objective>') + '"');
245
+ if (f.brainstorm && want('brainstorm')) push('brainstorm', 'll-brainstorm project' + talk(f.interactive));
246
+ if (want('decide')) push('decide', 'll-decide project --no-talk');
247
+ const rows = rowsOf(root);
248
+ const pend = rows.filter((r) => want('phase-' + r.nn));
249
+ const inRange = (r) => (f.from === null || num(r.nn) >= num(f.from))
250
+ && (f.to === null || num(r.nn) <= num(f.to));
251
+ const kept = pend.filter(inRange);
252
+ const half = (r) => st['phase-' + r.nn].status === 'half';
253
+ for (const r of kept.filter(half).concat(kept.filter((r) => !half(r)))) { // a half phase resumes first
254
+ push('phase-' + r.nn, 'll-implement ' + r.nn + talk(f.interactive));
255
+ const asked = new RegExp('^ll-verify\\s+0*' + num(r.nn) + '$').test(nextCmdOf(epilogueText(root, r.nn)));
256
+ if ((f.verify === 'all' || asked) && want('verify-' + r.nn)) push('verify-' + r.nn, 'll-verify ' + r.nn);
257
+ }
258
+ // Close only when the roteiro covers every phase still open: --only, --from or --to cut it out.
259
+ if (f.only === null && kept.length === pend.length && want('close')) push('close', 'll-close --no-talk');
260
+ return { ok: true, root, needs_objective: false, roteiro: out };
261
+ }
262
+
263
+ // --- report ----------------------------------------------------------------------------------
264
+ const MARK = '[decided by absence — revisable]';
265
+ function report(root) {
266
+ const dir = J(root, 'decisions'), out = [];
267
+ for (const name of ls(dir)) {
268
+ if (!/\.md$/.test(name)) continue;
269
+ const lines = (readText(J(dir, name)) || '').split('\n');
270
+ const i = lines.findIndex((l) => l.indexOf(MARK) >= 0);
271
+ if (i < 0) continue;
272
+ const h = lines.find((l) => /^#\s+/.test(l)) || '';
273
+ out.push({ file: 'decisions/' + name, title: h.replace(/^#\s+/, '').trim(), line: i + 1 });
274
+ }
275
+ return { ok: true, root, decisions: out };
276
+ }
277
+
278
+ // --- auto-md ---------------------------------------------------------------------------------
279
+ // The docs/AUTO.md body of D-03-07: objective, flags, the roteiro table, then two empty sections.
280
+ function autoMd(root, objective, flagStr) {
281
+ const ev = {};
282
+ for (const s of detect(root).stages) ev[s.id] = s.evidence;
283
+ const r = roteiro(root, flagStr, objective);
284
+ const L = ['# AUTO — autonomous run', '',
285
+ '## Objective', '', String(objective || '').trim() || '<objective>', '',
286
+ '## Flags', '', String(flagStr || '').trim() || '(none)', '',
287
+ '## Roteiro', '', '| # | stage | command | status | evidence |', '|---|---|---|---|---|'];
288
+ r.roteiro.forEach((e, i) => L.push('| ' + (i + 1) + ' | ' + e.stage + ' | ' + e.command + ' | '
289
+ + e.status + ' | ' + (ev[e.stage] || '—') + ' |'));
290
+ if (!r.roteiro.length) {
291
+ L.push('| — | — | — | — | ' + (r.needs_objective ? 'no objective given' : 'nothing left to run') + ' |');
292
+ }
293
+ L.push('', '## Decisions taken alone', '', '(filled at the end of the run)', '',
294
+ '## Log', '', '(one dated line per stage transition)');
295
+ return { ok: true, root, markdown: L.join('\n') };
296
+ }
297
+
298
+ // --- cli -------------------------------------------------------------------------------------
299
+ function parseArgv(argv) {
300
+ const flags = {}, pos = [];
301
+ for (let i = 0; i < argv.length; i++) {
302
+ const a = argv[i];
303
+ const kv = /^--([^=]+)=([\s\S]*)$/.exec(a);
304
+ if (kv) { flags[kv[1]] = kv[2]; continue; }
305
+ if (a === '--cwd' || a === '--flags' || a === '--objective') { flags[a.slice(2)] = argv[++i]; continue; }
306
+ if (/^--/.test(a)) { flags[a.slice(2)] = true; continue; }
307
+ pos.push(a);
308
+ }
309
+ return { flags, pos };
310
+ }
311
+ function rootOf(a) {
312
+ const r = path.resolve(String(a.flags.cwd || process.cwd()));
313
+ if (!isDir(r)) throw new Error('not a directory: ' + r);
314
+ return r;
315
+ }
316
+ const C = {
317
+ detect: (a) => detect(rootOf(a)),
318
+ roteiro: (a) => roteiro(rootOf(a), a.flags.flags, a.flags.objective),
319
+ 'next-cmd': (a) => ({ ok: true, command: nextCmdOf(readText(path.resolve(a.pos[0] || ''))) }),
320
+ report: (a) => report(rootOf(a)),
321
+ 'auto-md': (a) => autoMd(rootOf(a), a.flags.objective, a.flags.flags),
322
+ };
323
+ const FMT = {
324
+ detect: (r) => r.stages.map((s) => s.id.padEnd(12) + ' ' + s.status.padEnd(5) + ' ' + s.evidence).join('\n'),
325
+ roteiro: (r) => (r.needs_objective ? 'needs_objective'
326
+ : r.roteiro.map((e, i) => (i + 1) + '. ' + e.stage.padEnd(12) + ' ' + e.command
327
+ + (e.pause_after ? ' [pause]' : '')).join('\n')),
328
+ 'next-cmd': (r) => r.command,
329
+ report: (r) => r.decisions.map((d) => d.file + ':' + d.line + ' ' + d.title).join('\n'),
330
+ 'auto-md': (r) => r.markdown,
331
+ };
332
+
333
+ function main() {
334
+ const argv = process.argv.slice(2), cmd = argv[0];
335
+ try {
336
+ if (!C[cmd]) throw new Error('usage: ll-auto.js <' + Object.keys(C).join('|') + '> [args] [--cwd <dir>] [--json]');
337
+ const a = parseArgv(argv.slice(1));
338
+ const res = C[cmd](a);
339
+ process.stdout.write((a.flags.json || !FMT[cmd] ? JSON.stringify(res) : FMT[cmd](res)) + '\n');
340
+ } catch (e) {
341
+ process.stdout.write(JSON.stringify({ ok: false, reason: (e && e.message) || String(e) }) + '\n');
342
+ }
343
+ process.exit(0);
344
+ }
345
+ main();
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: ll-brainstorm
3
- description: "Opens a phase, a project or a loose idea by deciding in front of the owner instead of asking — scouts the repo, then shows one map (A: what Claude decides, each item with its repo analog at file:line; B: at most 4 calls only the owner can make, batched once; C: deferred) and ends with phases/NN/DECISIONS.md or docs/decide/OPENING.md plus the next command. Use when the owner says \"vamos discutir\", \"tenho uma ideia\", \"não sei ainda\", \"me ajuda a pensar\", \"brainstorm\", \"antes de começar a fase X\", \"abrir projeto novo\", \"li esse documento / áudio / transcrição, o que fazemos com isso\", \"a tentativa anterior não deu certo\" — or in English \"let's discuss\", \"I have an idea\", \"help me think\", \"before we start phase X\", \"new project\", \"the previous attempt failed\". Not for an already-specified request or a plan ready for adversarial pressure (both go to `ll-decide`), nor for a market question (`ll-research --market`)."
3
+ description: Opens a phase, a project or a loose idea by deciding in front of the owner instead of asking, and writes the opening map to phases/NN/DECISIONS.md or docs/decide/OPENING.md with the next command.
4
4
  argument-hint: "[phase-number | project | chat] [--no-talk]"
5
+ disable-model-invocation: true
5
6
  ---
6
7
 
7
8
  # Brainstorm
@@ -123,7 +124,7 @@ Write the file, print the score in three lines and the next command. Nothing is
123
124
  Closed. <k> locked by me (<r> revisable), <j> answered by you (<c> against the recommendation),
124
125
  <m> deferred. Questions asked <j> / assumptions <a> / band-1 open 0. (printed even when <j> = 0)
125
126
  → phases/NN/DECISIONS.md
126
- ▶ Next — `/clear` then `ll-implement NN` (or "adjust X" if something is wrong)
127
+ ▶ Next — /clear, then ll-implement NN (or "adjust X" if something is wrong)
127
128
  ```
128
129
 
129
130
  File skeleton (8 sections, all present even when empty):
@@ -154,7 +155,7 @@ decision he did not make is a defect, not a rounding. Band-1 items and items tha
154
155
  phases also get `decisions/DEC-NNNN-<slug>.md`. The helper `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills/ll-implement/scripts/ll-tools.js` is used only if it exists — this skill does not ship it; when it is absent,
155
156
  the DEC id is the next number after the highest in `decisions/`; with it, `dec-reserve <n>`.
156
157
 
157
- ## Closing criterion
158
+ ## Completion criterion
158
159
 
159
160
  The conversation closes when four list comparisons hold — no model score:
160
161
  1. Zero Blocking items open (answered by the owner or explicitly deferred by him).
@@ -169,7 +170,7 @@ close A as it stands and handle the rest in execution, or take the premise that
169
170
  to `ll-decide project` (its premortem) as the next command." Budget: ≤4 turns on the phase route, ≤8 on project.
170
171
 
171
172
  Done when the file exists with its 8 sections and the Score line reads `0 band-1 items open`.
172
- ▶ Next — `/clear` then `ll-implement NN` (phase route) · `ll-decide project` (project route) ·
173
+ ▶ Next — /clear, then ll-implement NN (phase route; project route: ll-decide project)
173
174
  nothing to run (chat route).
174
175
 
175
176
  ## References
@@ -61,6 +61,9 @@ Silence of 10 minutes ratifies the recommended list (A), never a blocking item (
61
61
  ## Silence, delegation, directives
62
62
  - Silence for 10 minutes in the hot window (harness AFK timeout) ratifies option A of a band-2/3 item or
63
63
  list A of a map — never option B, never a band-1 item. Record `ratified by silence`; in a run, `[decided by absence — revisable]`.
64
+ - Under `--no-talk` the ten minutes are not waited for: the recommended option is taken at once and
65
+ recorded `[decided by absence — revisable]`. A band-1 item is never taken this way — it is written
66
+ as a decision in state `WAITING` and the work that depends on it stops there.
64
67
  - "Claude decide", "você decide", "pergunta pro time" are legitimate answers: record an assumption
65
68
  `ASM-n` (permanent; announced if changed later) or the delegation, and never ask that item again.
66
69
  - An order against the recommendation is a directive: one challenge with the cost named, then
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: ll-close
3
- description: Closes a delivery or a milestone — reconciles the backlog against executable conditions, writes docs/DELIVERY.md for the manager, stamps the epilogue with phase-stats, produces a retrospective, promotes the lessons that became permanent rules to project memory and CLAUDE.md, and asks for one block of ratification. Use when the request is "fecha", "fecha a fase", "pode arquivar", "encerra isso", "fecha o milestone", or at the end of a /goal; `--milestone` archives the phases, collapses the ROADMAP and updates the project's current state. It refuses to close without an approved VERIFICATION.md; for the verification itself use ll-verify.
4
- argument-hint: "[--milestone <name>]"
3
+ description: Closes a phase or a delivery, reconciling the backlog against executable conditions, writing docs/DELIVERY.md, stamping the epilogue, recording the retrospective and asking for one block of ratification.
4
+ argument-hint: "[--milestone <name>] [--no-talk]"
5
5
  disable-model-invocation: true
6
6
  allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/ll-tools.js *)
7
7
  ---
@@ -29,7 +29,7 @@ Reply to the owner in Portuguese; every file you write is in English.
29
29
 
30
30
  Read the verification for the target: the newest by mtime between `phases/NN/VERIFICATION.md` (phase) and `VERIFICATION.md` at the repo root (delivery); say which one opened the gate. Close only on `APPROVED`, or on `APPROVED_WITH_RESERVATIONS` whose reservations are accepted in a `decisions/DEC-*.md` naming them. Anything else — `REJECTED`, `product: FAIL`, `process: FAIL` with no DEC, no verification file at all — stops here:
31
31
 
32
- > Não fecho: `<motivo em uma linha>`. ▶ Next — `/clear`, then `ll-verify NN` (or record the reservations in a DEC and run `ll-close` again).
32
+ > Não fecho: `<motivo em uma linha>`. ▶ Next — /clear, then ll-verify NN (or record the reservations in a DEC and run ll-close again)
33
33
 
34
34
  A verification whose ledger has `STALE` lines is verification of code that moved: it does not open the gate.
35
35
 
@@ -41,7 +41,7 @@ A verification whose ledger has `STALE` lines is verification of code that moved
41
41
  4. **Write `docs/RETROSPECTIVE-<date>.md`** from `references/retrospective.md` — about 60 lines, driven by the numbers from step 3, one lesson per line with the evidence that produced it.
42
42
  5. **Promote what is permanent.** A lesson that changes how the next phase runs goes to project memory; a lesson that is a standing rule of this repository goes as one line into the project's `CLAUDE.md`, in the owner's words when he gave them. Two lines at most per delivery: a CLAUDE.md that grows every close stops being read.
43
43
  6. **Peer notice.** If a peer session (infra, ops, another repo) shipped, waits on something, or was asked for something in `docs/REQUESTS.md`, that goes in the summary as one line with the request id and its state — not as a new message.
44
- 7. **One block of ratification.** A single `AskUserQuestion`, at most 4 items, only after everything is written: the open pendings with their conditions, and the accepted risks the delivery carries. Options carry the cost of each path and the recommendation is marked. Silence keeps what is written; nothing here blocks the close.
44
+ 7. **One block of ratification.** A single `AskUserQuestion`, at most 4 items, only after everything is written: the open pendings with their conditions, and the accepted risks the delivery carries. Options carry the cost of each path and the recommendation is marked. Silence keeps what is written; nothing here blocks the close. With `--no-talk`, this block is not asked: the same items — open pendings with their conditions, accepted risks, recommendation — are written instead under the epilogue in PROGRESS.md and in DELIVERY.md §5, each line marked `[decided by absence — revisable]` with the date; the gate above is unchanged.
45
45
  8. **Report.** Verdict and seals, what changed in one line, numbers from `phase-stats`, open pendings by id, accepted risks, next command.
46
46
 
47
47
  ## Flow — `--milestone <name>`
@@ -53,7 +53,7 @@ The list always goes out; the audit ritual is optional and offered once, never i
53
53
  3. Collapse the closed phases in `ROADMAP.md` into one `<details>` block per phase, keeping the numbering intact — phase 7 stays phase 7 forever, and a later reference to it still resolves.
54
54
  4. Transport only what survives: an open pending with an executable condition goes to `BACKLOG.md`; a criterion never proven goes to the next milestone's ROADMAP as a criterion, not as a note. What survives neither test is dropped, and the report says which.
55
55
  5. Update `## Current state` in the project's `CLAUDE.md`: what exists now, what the next milestone is, where the history went. Replace the section; do not append to it.
56
- 6. One block of ratification, as in the delivery flow, plus the open pendings the milestone inherits.
56
+ 6. One block of ratification, as in the delivery flow, plus the open pendings the milestone inherits. `--no-talk` follows the same rule: the block is not asked, and the same items are written under the epilogue instead.
57
57
 
58
58
  ## Completion criterion
59
59
 
@@ -42,7 +42,9 @@ reason, never omitted.>
42
42
  <What was decided on the owner's behalf while he was not there, one per line: the assumption, the
43
43
  DEC that records it, what breaks if it is wrong, and how expensive it is to reverse. This section
44
44
  feeds the ratification block; if it is empty, say "none — every decision in this delivery is in
45
- decisions/".>
45
+ decisions/". Under `--no-talk`, this section also carries the open pendings, accepted risks and
46
+ recommendation that would otherwise be asked in the ratification block, each line ending
47
+ `[decided by absence — revisable]` and the date.>
46
48
 
47
49
  ## 6. Out of this round, and why
48
50
  <What was asked for or found and deliberately not done. One line each: what, why (cost, risk,