ll-skills 2.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +42 -20
- package/agents/ll-executor.md +2 -1
- package/agents/ll-verifier.md +1 -0
- package/assets/preamble.md +27 -39
- package/bin/install.js +4 -1
- package/hooks/ll-precompact.js +29 -1
- package/hooks/ll-skills-check-update.js +6 -6
- package/hooks/ll-state.js +30 -2
- package/package.json +3 -2
- package/scripts/evals/README.md +81 -0
- package/scripts/evals/cases/auto-dry-run/assert.sh +35 -0
- package/scripts/evals/cases/auto-dry-run/case.json +8 -0
- package/scripts/evals/cases/auto-dry-run/prompt.txt +1 -0
- package/scripts/evals/cases/auto-empty-repo/assert.sh +25 -0
- package/scripts/evals/cases/auto-empty-repo/case.json +8 -0
- package/scripts/evals/cases/auto-empty-repo/fixture/.gitkeep +0 -0
- package/scripts/evals/cases/auto-empty-repo/prompt.txt +1 -0
- package/scripts/evals/cases/decide-final-round/assert.sh +45 -0
- package/scripts/evals/cases/decide-final-round/case.json +8 -0
- package/scripts/evals/cases/decide-final-round/fixture/README.md +3 -0
- package/scripts/evals/cases/decide-final-round/prompt.txt +1 -0
- package/scripts/evals/cases/executor-block/assert.sh +33 -0
- package/scripts/evals/cases/executor-block/case.json +8 -0
- package/scripts/evals/cases/executor-block/prompt.txt +14 -0
- package/scripts/evals/cases/goal-autonomous/assert.sh +35 -0
- package/scripts/evals/cases/goal-autonomous/case.json +8 -0
- package/scripts/evals/cases/goal-autonomous/fixture/PLAN.md +42 -0
- package/scripts/evals/cases/goal-autonomous/fixture/PROGRESS.md +20 -0
- package/scripts/evals/cases/goal-autonomous/fixture/ROADMAP.md +29 -0
- package/scripts/evals/cases/goal-autonomous/fixture/package.json +8 -0
- package/scripts/evals/cases/goal-autonomous/fixture/src/money.js +6 -0
- package/scripts/evals/cases/goal-autonomous/fixture/test/reconcile.test.js +8 -0
- package/scripts/evals/cases/goal-autonomous/prompt.txt +1 -0
- package/scripts/evals/cases/implement-review-gate/assert.sh +37 -0
- package/scripts/evals/cases/implement-review-gate/case.json +8 -0
- package/scripts/evals/cases/implement-review-gate/prompt.txt +1 -0
- package/scripts/evals/cases/implement-stops-at-next/assert.sh +121 -0
- package/scripts/evals/cases/implement-stops-at-next/case.json +9 -0
- package/scripts/evals/cases/implement-stops-at-next/prompt.txt +1 -0
- package/scripts/evals/cases/preamble-no-ritual/assert.sh +17 -0
- package/scripts/evals/cases/preamble-no-ritual/case.json +8 -0
- package/scripts/evals/cases/preamble-no-ritual/fixture/README.md +3 -0
- package/scripts/evals/cases/preamble-no-ritual/fixture/src/a.ts +3 -0
- package/scripts/evals/cases/preamble-no-ritual/prompt.txt +1 -0
- package/scripts/evals/cases/router-no-skill/assert.sh +33 -0
- package/scripts/evals/cases/router-no-skill/case.json +8 -0
- package/scripts/evals/cases/router-no-skill/fixture/PLAN.md +21 -0
- package/scripts/evals/cases/router-no-skill/fixture/README.md +7 -0
- package/scripts/evals/cases/router-no-skill/fixture/decisions/README.md +3 -0
- package/scripts/evals/cases/router-no-skill/prompt.txt +1 -0
- package/scripts/evals/cases/router-small/assert.sh +21 -0
- package/scripts/evals/cases/router-small/case.json +8 -0
- package/scripts/evals/cases/router-small/fixture/README.md +17 -0
- package/scripts/evals/cases/router-small/prompt.txt +1 -0
- package/scripts/evals/cases/scout-no-plan/assert.sh +41 -0
- package/scripts/evals/cases/scout-no-plan/case.json +8 -0
- package/scripts/evals/cases/scout-no-plan/prompt.txt +8 -0
- package/scripts/evals/cases/verifier-weakened-test/assert.sh +19 -0
- package/scripts/evals/cases/verifier-weakened-test/case.json +8 -0
- package/scripts/evals/cases/verifier-weakened-test/prompt.txt +13 -0
- package/scripts/evals/cases/verifier-weakened-test/setup.sh +19 -0
- package/scripts/evals/fixtures/manual-contract/out.json +29 -0
- package/scripts/evals/fixtures/manual-contract/out.txt +5 -0
- package/scripts/evals/fixtures/manual-contract/with-skill.json +46 -0
- package/scripts/evals/fixtures/router-no-skill/fail.txt +5 -0
- package/scripts/evals/fixtures/router-no-skill/out.json +27 -0
- package/scripts/evals/fixtures/router-no-skill/pass.txt +4 -0
- package/scripts/evals/lib/assert.sh +107 -0
- package/scripts/evals/lib/extract.js +73 -0
- package/scripts/evals/run.sh +369 -0
- package/scripts/fixtures/auto-closed/PLAN.md +5 -0
- package/scripts/fixtures/auto-closed/PROGRESS.md +20 -0
- package/scripts/fixtures/auto-closed/ROADMAP.md +6 -0
- package/scripts/fixtures/auto-closed/docs/DELIVERY.md +3 -0
- package/scripts/fixtures/auto-decisions/decisions/DEC-0001-taken-alone.md +13 -0
- package/scripts/fixtures/auto-decisions/decisions/DEC-0002-owner.md +13 -0
- package/scripts/fixtures/auto-noroadmap/PLAN.md +20 -0
- package/scripts/fixtures/auto-noroadmap/PROGRESS.md +11 -0
- package/scripts/fixtures/auto-verify-next/PLAN.md +5 -0
- package/scripts/fixtures/auto-verify-next/PROGRESS.md +18 -0
- package/scripts/fixtures/auto-verify-next/ROADMAP.md +5 -0
- package/scripts/fixtures/auto-verify-next/phases/01/PLAN.md +6 -0
- package/scripts/fixtures/evals-auto/auto-dry-run/pass.txt +18 -0
- package/scripts/fixtures/evals-auto/auto-empty-repo/pass.txt +2 -0
- package/scripts/fixtures/evals-auto/goal-autonomous/pass.txt +29 -0
- package/scripts/fixtures/lint-bad/folded-description/SKILL.md +13 -0
- package/scripts/fixtures/lint-bad/jargon-in-questions/SKILL.md +36 -0
- package/scripts/fixtures/lint-bad/model-invocation-false/SKILL.md +10 -0
- package/scripts/fixtures/next-bad/skills/ll-bad/SKILL.md +30 -0
- package/scripts/fixtures/next-good/skills/ll-good/SKILL.md +26 -0
- package/scripts/fixtures/project/BACKLOG.md +6 -5
- package/scripts/fixtures/project/PROGRESS.md +4 -0
- package/scripts/lint-contract.cjs +495 -0
- package/scripts/lint-prompts.sh +443 -0
- package/scripts/ll-tools.js +497 -450
- package/scripts/smoke-test.sh +481 -4
- package/skills/ll-auto/SKILL.md +74 -0
- package/skills/ll-auto/references/run.md +75 -0
- package/skills/ll-auto/references/stages.md +66 -0
- package/skills/ll-auto/scripts/ll-auto.js +345 -0
- package/skills/ll-brainstorm/SKILL.md +14 -12
- package/skills/ll-brainstorm/references/decision-policy.md +22 -12
- package/skills/ll-close/SKILL.md +8 -7
- package/skills/ll-close/references/delivery.md +3 -1
- package/skills/ll-decide/SKILL.md +21 -19
- package/skills/ll-decide/references/decision-policy.md +22 -12
- package/skills/ll-decide/references/decision-room.md +5 -3
- package/skills/ll-decide/references/interview.md +29 -13
- package/skills/ll-decide/references/plan-skeleton.md +14 -14
- package/skills/ll-decide/references/premise-gate.md +34 -20
- package/skills/ll-goal/SKILL.md +22 -4
- package/skills/ll-goal/references/goal-template.md +57 -0
- package/skills/ll-implement/SKILL.md +25 -22
- package/skills/ll-implement/references/briefs.md +2 -1
- package/skills/ll-implement/references/decision-policy.md +22 -12
- package/skills/ll-implement/references/phase-conversation.md +20 -12
- package/skills/ll-implement/references/phase-plan.md +23 -0
- package/skills/ll-oncall/SKILL.md +3 -2
- package/skills/ll-refine/SKILL.md +3 -2
- package/skills/ll-research/SKILL.md +3 -2
- package/skills/ll-resume/SKILL.md +9 -4
- package/skills/ll-update/SKILL.md +6 -1
- package/skills/ll-verify/SKILL.md +2 -1
- package/skills/ll-verify/references/verifier-briefs.md +3 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ll-auto
|
|
3
|
+
description: Drives the whole cycle from the state on disk — research, brainstorm, decide, phases, verifications, close — following each stage's own skill in place with the flags it was given, and reports at the end every decision it took alone.
|
|
4
|
+
argument-hint: "[\"<objective>\"] [--research] [--brainstorm] [--interactive] [--auto-decision] [--pause-at <stage|N>] [--from N] [--to N] [--only N] [--verify all] [--redo <stage>] [--dry-run] [--resume]"
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/ll-auto.js *)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Auto
|
|
10
|
+
|
|
11
|
+
Current state: !`${CLAUDE_SKILL_DIR}/scripts/ll-auto.js detect`
|
|
12
|
+
|
|
13
|
+
One invocation, the cycle the repository still owes: the helper reads the state from disk, the flags cut it into a roteiro, and this session walks the roteiro stage by stage, following each stage's own skill as a workflow file. `docs/AUTO.md` carries the run: objective, flags, the roteiro table and one log line per transition, so a compaction or a stop never loses the position. The run starts from the middle when the middle is what exists.
|
|
14
|
+
|
|
15
|
+
Four boundaries hold for the whole run:
|
|
16
|
+
- **The Skill tool is never called.** A stage runs by reading `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills/<skill>/SKILL.md` with `Read` (falling back to `skills/<skill>/SKILL.md` in the package tree when this repository is the one running) and following its Flow in place with the stage's arguments. Every skill is locked, this one included; the lock is not worked around, it is honoured by reading the instructions instead of invoking them.
|
|
17
|
+
- **Only this skill follows another skill.** The exception exists because the owner typed the command, and it lasts only for this invocation. No stage started here starts a further `ll-auto`.
|
|
18
|
+
- **No spending ceiling, ever.** Never stop, throttle or shorten a stage to save tokens or money, never announce a budget, never ask whether to continue for cost. The run ends when the roteiro ends, a pause flag says so, or nothing can move without the owner.
|
|
19
|
+
- **No question of its own.** The run never opens `AskUserQuestion`. The only questions the owner sees are the ones a stage asks while running under `--interactive`. When something is missing, the run prints the command that would complete it and stops.
|
|
20
|
+
|
|
21
|
+
Reply to the owner in Portuguese; every file written is in English.
|
|
22
|
+
|
|
23
|
+
## Deliverables
|
|
24
|
+
|
|
25
|
+
| File | Role | Mutability |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `docs/AUTO.md` | the run: objective, flags, roteiro table, decisions taken alone, log | rewritten by this skill on every transition; the roteiro rows only through `auto-md` and the log append-only |
|
|
28
|
+
| `docs/research-*/SUMMARY.md`, `docs/decide/`, `PLAN.md`, `ROADMAP.md`, `PROGRESS.md`, `phases/NN/`, `decisions/`, `BACKLOG.md`, `VERIFICATION.md`, `docs/DELIVERY.md` | whatever each stage writes, by the rules of that stage's own skill | owned by the stage skill; this skill writes none of them directly |
|
|
29
|
+
|
|
30
|
+
## Flow
|
|
31
|
+
|
|
32
|
+
Helper: `${CLAUDE_SKILL_DIR}/scripts/ll-auto.js`, written `ll-auto.js` below. Read commands exit 0 and answer `{"ok":false,"reason":…}` on a broken input. Commands: `detect`, `roteiro`, `next-cmd`, `report`, `auto-md`.
|
|
33
|
+
|
|
34
|
+
### 0. State, flags and roteiro
|
|
35
|
+
|
|
36
|
+
`ll-auto.js detect --json` gives the stage table above; `ll-auto.js roteiro --flags "<the arguments>" --objective "<objective>" --json` turns it into the ordered list of stages to run, with a command and a status per entry. `references/stages.md` holds the detection rules, the command per stage and the flag table.
|
|
37
|
+
|
|
38
|
+
Three stops before any work:
|
|
39
|
+
- `needs_objective: true` (no research, no `docs/decide/OPENING.md`, no `PLAN.md`, and no objective in the arguments) — print exactly two lines and stop, with no question: `Nada encontrado neste repositório: sem pesquisa, OPENING.md nem PLAN.md.` and the command to complete, `/ll-auto "<objetivo>" [--research] [--brainstorm]`.
|
|
40
|
+
- `--dry-run` — print the stage table from `detect` (one line per stage, as the helper prints it) and the roteiro table, then stop, before writing `docs/AUTO.md`.
|
|
41
|
+
- an empty roteiro with an objective present — say the cycle has nothing left and hand over.
|
|
42
|
+
|
|
43
|
+
`--resume`, or a plain invocation in a repository that already has `docs/AUTO.md`, reads the flags from that file's `## Flags` section and continues from the first row that is not `done`.
|
|
44
|
+
|
|
45
|
+
### 1. Open the run
|
|
46
|
+
|
|
47
|
+
Write `docs/AUTO.md` with `ll-auto.js auto-md --objective "<objective>" --flags "<the arguments>"`, redirected to the file. Print the roteiro table to the owner in one screen: this is the plan of the run and the last moment before it starts.
|
|
48
|
+
|
|
49
|
+
### 2. One stage at a time
|
|
50
|
+
|
|
51
|
+
For every roteiro entry, in order:
|
|
52
|
+
1. Mark the row `running` in `docs/AUTO.md` and append the log line `<date> <stage> running`.
|
|
53
|
+
2. Read that stage's `SKILL.md` at the path of boundary 1 and follow its Flow in place, with the entry's command as its arguments. The stage owns its files, its questions and its own ▶ Next line; that line is read, not pasted to the owner, and never starts another skill from here.
|
|
54
|
+
3. Run `ll-auto.js detect --json` again. Mark the row `done` when the stage's own status turned `done`, `half` when it did not, and append the log line with the evidence the helper gives.
|
|
55
|
+
4. List the `decisions/*.md` in state `WAITING` and apply `references/run.md`: without `--auto-decision` skip the stages that depend on them and stop when nothing else can run; with `--auto-decision` resolve each to its recommended option and continue.
|
|
56
|
+
5. When the entry carries `pause_after: true` (`--pause-at`), stop here with `▶ Next — /clear, then ll-auto --resume`.
|
|
57
|
+
|
|
58
|
+
A stage that fails inside its own skill ends the run at that row, marked with its status and its last line in the log; the roteiro below it stays `todo`.
|
|
59
|
+
|
|
60
|
+
### 3. Close the run
|
|
61
|
+
|
|
62
|
+
`ll-auto.js report --json` lists every `decisions/*.md` carrying `[decided by absence — revisable]`. Print that list as the end-of-run block, one line per file with its title, and write the same block into the `## Decisions taken alone` section of `docs/AUTO.md`. Then the count line and the handover. The end block, verbatim, is in `references/run.md`.
|
|
63
|
+
|
|
64
|
+
## Completion criterion
|
|
65
|
+
|
|
66
|
+
Every roteiro row is `done`, `skipped` or `waiting` in `docs/AUTO.md`, each with a log line carrying the evidence `detect` gave after the stage; the decisions taken alone are listed both in the conversation and in the file; nothing was decided outside `--auto-decision`; no question was asked by this skill. What the run did not reach is named with the command that would close it.
|
|
67
|
+
The run's last count line, printed and written to `docs/AUTO.md`:
|
|
68
|
+
`stages done X/Y · decisions taken alone N · waiting for the owner K · flags: <the arguments>`
|
|
69
|
+
▶ Next — /clear, then ll-resume
|
|
70
|
+
|
|
71
|
+
## References
|
|
72
|
+
|
|
73
|
+
- `references/stages.md` — the detection table, the command per stage and the flag table; step 0.
|
|
74
|
+
- `references/run.md` — the `docs/AUTO.md` template, the WAITING handling and the end-of-run block; steps 1, 2 and 3.
|
|
@@ -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();
|