@open-tomato/rafa 0.45.0 → 0.46.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/README.md +255 -23
- package/dist/PROMPT.md +18 -1
- package/dist/adapters/logger/settings.d.ts +132 -0
- package/dist/bugs/closest.d.ts +19 -0
- package/dist/bugs/fenced.d.ts +8 -0
- package/dist/bugs/places.d.ts +20 -0
- package/dist/bugs/record.d.ts +145 -0
- package/dist/bugs/title.d.ts +83 -0
- package/dist/bundled/agents/doc-updater.md +17 -3
- package/dist/bundled/agents/loop-implementer.md +14 -2
- package/dist/bundled/operators/skills/rafa-stretch-pit-stop/SKILL.md +10 -3
- package/dist/bundled/operators/skills/rafa-stretch-sweep/SKILL.md +5 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +101 -18
- package/dist/cli.js +74726 -62960
- package/dist/config/entry.d.ts +119 -0
- package/dist/config/index.d.ts +36 -0
- package/dist/config/index.js +51 -0
- package/dist/config/shape.d.ts +232 -0
- package/dist/config-registry.d.ts +127 -0
- package/dist/config-schema-board-project.d.ts +157 -0
- package/dist/config-schema-bugs.d.ts +71 -0
- package/dist/config-schema-errors.d.ts +54 -0
- package/dist/config-schema-features.d.ts +64 -0
- package/dist/config-schema-hub.d.ts +150 -0
- package/dist/config-schema-issues.d.ts +95 -0
- package/dist/config-schema-logger.d.ts +91 -0
- package/dist/config-schema-loop-continue.d.ts +122 -0
- package/dist/config-schema-loop-retries.d.ts +94 -0
- package/dist/config-schema-release.d.ts +307 -0
- package/dist/config-schema-tests.d.ts +170 -0
- package/dist/config-schema-triage.d.ts +142 -0
- package/dist/config-schema-wrap-up.d.ts +96 -0
- package/dist/core/command/command.d.ts +228 -0
- package/dist/core/command/confirm.d.ts +67 -0
- package/dist/core/command/index.d.ts +112 -0
- package/dist/core/command/lazy.d.ts +54 -0
- package/dist/core/command/registry-types.d.ts +96 -0
- package/dist/core/command/running.d.ts +96 -0
- package/dist/core/command/spends.d.ts +39 -0
- package/dist/core/command/types.d.ts +92 -0
- package/dist/core/command/version.d.ts +4 -0
- package/dist/core/config/branch-template.d.ts +20 -0
- package/dist/core/config/config-items.d.ts +86 -0
- package/dist/core/config/config-load.d.ts +47 -0
- package/dist/core/config/config-origins.d.ts +57 -0
- package/dist/core/config/config-readers.d.ts +167 -0
- package/dist/core/config/config-schema.d.ts +207 -0
- package/dist/core/config/config-sections.d.ts +493 -0
- package/dist/core/config/config.d.ts +320 -0
- package/dist/core/config/core-section.d.ts +39 -0
- package/dist/core/config/dangerous.d.ts +58 -0
- package/dist/core/config/default-routing.d.ts +16 -0
- package/dist/core/config/index.d.ts +141 -0
- package/dist/core/config/scope.d.ts +42 -0
- package/dist/core/config/section.d.ts +206 -0
- package/dist/core/config/sections.d.ts +45 -0
- package/dist/core/config/standard-schema.d.ts +27 -0
- package/dist/core/git/commit.d.ts +232 -0
- package/dist/core/git/gh-runner.d.ts +24 -0
- package/dist/core/git/git.d.ts +22 -0
- package/dist/core/git/index.d.ts +85 -0
- package/dist/core/loop/claude.d.ts +388 -0
- package/dist/core/loop/hop-record.d.ts +52 -0
- package/dist/core/loop/index.d.ts +179 -0
- package/dist/core/loop/session-env.d.ts +20 -0
- package/dist/core/loop/session-record-parse.d.ts +63 -0
- package/dist/core/loop/sessions.d.ts +408 -0
- package/dist/core/loop/suite-failure.d.ts +25 -0
- package/dist/core/output/active.d.ts +100 -0
- package/dist/core/output/stream.d.ts +20 -0
- package/dist/core/output/text.d.ts +55 -0
- package/dist/core/plan/block-reader.d.ts +46 -0
- package/dist/core/plan/blocks.d.ts +139 -0
- package/dist/core/plan/declaration.d.ts +366 -0
- package/dist/core/plan/plan-stamp.d.ts +75 -0
- package/dist/core/runtime/identity.d.ts +21 -0
- package/dist/core/runtime/index.d.ts +46 -0
- package/dist/effort/store/bring-forward.d.ts +131 -0
- package/dist/effort/store/check-rows.d.ts +31 -0
- package/dist/effort/store/commit-stats.d.ts +42 -0
- package/dist/effort/store/copy.d.ts +35 -0
- package/dist/effort/store/development-build.d.ts +105 -0
- package/dist/effort/store/effort-levels.d.ts +13 -0
- package/dist/effort/store/findings.d.ts +214 -0
- package/dist/effort/store/fix-schema.d.ts +184 -0
- package/dist/effort/store/index.js +10 -7
- package/dist/effort/store/location.d.ts +44 -0
- package/dist/effort/store/merge-commit-gaps.d.ts +68 -0
- package/dist/effort/store/merge-conflicts.d.ts +91 -0
- package/dist/effort/store/merge-rules.d.ts +82 -0
- package/dist/effort/store/merge-store.d.ts +211 -0
- package/dist/effort/store/merge-union.d.ts +92 -0
- package/dist/effort/store/migrate.d.ts +146 -0
- package/dist/effort/store/migrations.d.ts +92 -0
- package/dist/effort/store/ndjson-files.d.ts +114 -0
- package/dist/effort/store/origins.d.ts +116 -0
- package/dist/effort/store/plan-stubs.d.ts +79 -0
- package/dist/effort/store/rebuild-aside.d.ts +172 -0
- package/dist/effort/store/schema-plan.d.ts +102 -0
- package/dist/effort/store/session-row-types.d.ts +64 -0
- package/dist/effort/store/settings.d.ts +13 -0
- package/dist/effort/store/sqlite.d.ts +340 -0
- package/dist/effort/store/store-generation.d.ts +17 -0
- package/dist/effort/store/store-identity.d.ts +220 -0
- package/dist/effort/store/store-meta.d.ts +305 -0
- package/dist/effort/store/store-replaced.d.ts +72 -0
- package/dist/effort/store/types.d.ts +223 -0
- package/dist/errors/codes.d.ts +56 -0
- package/dist/errors/rafa-codes.d.ts +36 -0
- package/dist/index-2a7nb3an.js +125 -0
- package/dist/{index-r28fzsvc.js → index-39kbcw68.js} +350 -165
- package/dist/{index-nv34ptht.js → index-77ajfffn.js} +14 -9
- package/dist/index-cy98y87p.js +2597 -0
- package/dist/index-gqz4jn6j.js +13447 -0
- package/dist/index-gvek4r8m.js +710 -0
- package/dist/index-j4k2vdz3.js +1125 -0
- package/dist/index-jh1r4kcn.js +1067 -0
- package/dist/{index-mgh2s9bj.js → index-ykggr043.js} +58 -455
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +14715 -21516
- package/dist/learning/index.js +5 -6
- package/dist/plan/index.js +12 -11
- package/dist/plan-prompt.md +3 -1
- package/dist/ports/index.d.ts +732 -0
- package/dist/ports/learning.d.ts +101 -0
- package/dist/ports/log-level.d.ts +13 -0
- package/dist/ports/spec-review.d.ts +66 -0
- package/dist/pr/checks.d.ts +133 -0
- package/dist/pr/types.d.ts +422 -0
- package/dist/ready-4f8edezt.js +2016 -0
- package/dist/release-templates/release-bot-fragment.yml +153 -0
- package/dist/release-templates/release-publish.yml +110 -0
- package/dist/release-templates/release-settle.yml +122 -0
- package/dist/release-templates/release-tag.yml +102 -0
- package/dist/report/parse.d.ts +415 -0
- package/dist/schema/project-id.d.ts +79 -0
- package/dist/schema/tiers.d.ts +107 -0
- package/package.json +41 -3
- package/dist/index-hf2k497d.js +0 -16
- package/dist/index-nsr78ct2.js +0 -8219
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `text` Output adapter: a command's output as lines a person reads.
|
|
3
|
+
*
|
|
4
|
+
* Copied from `createTextOutput` in open-tomato's
|
|
5
|
+
* `packages/shared/cli-core/src/output.ts` at commit
|
|
6
|
+
* `18f94c843fff1bce65ed2245f8712ed9b64a2d51` (2026-06-25), and retyped
|
|
7
|
+
* against rafa's `Output` port, which `packages/rafa/src/ports/index.ts` copies from
|
|
8
|
+
* the same file's `CliOutput`.
|
|
9
|
+
*
|
|
10
|
+
* ## What the copy changes
|
|
11
|
+
*
|
|
12
|
+
* `info` is written at every verbosity, with no prefix. The source writes
|
|
13
|
+
* it at verbosity 1 and above, as `info: <message>`. The loop prints its
|
|
14
|
+
* progress lines at every verbosity, so under the source's threshold
|
|
15
|
+
* each would vanish at verbosity 0, and under its prefix each one left
|
|
16
|
+
* would change. `info(message)` writes `message`
|
|
17
|
+
* and a newline, the bytes `console.log(message)` writes for a single
|
|
18
|
+
* string: `text.test.ts` holds the two equal in a child process, over
|
|
19
|
+
* messages holding `%s`, `%%`, `%d`, `%o`, `%c` and `%j`, none of which
|
|
20
|
+
* `console.log` formats when it is handed one string.
|
|
21
|
+
*
|
|
22
|
+
* The rendering is otherwise the source's. `debug` is written at
|
|
23
|
+
* verbosity 2 and above as `debug: <message>`, `warn` and `error` at
|
|
24
|
+
* every verbosity as `warn: ` and `error: `, and `result` as `result: `
|
|
25
|
+
* followed by a string payload as it is or any other payload as JSON.
|
|
26
|
+
* `emit` renders a `log` event through the function of its level, a
|
|
27
|
+
* named `event` as nothing, and each other kind on a line of its own. The stream's `write` is a
|
|
28
|
+
* property, as `stream.ts` says, and the output answered is frozen.
|
|
29
|
+
*
|
|
30
|
+
* ## One stream
|
|
31
|
+
*
|
|
32
|
+
* Every line goes to the one stream the adapter is made with, `warn` and
|
|
33
|
+
* `error` included, as in the source. A `console.warn` or `console.error`
|
|
34
|
+
* line goes to stderr and carries no prefix, so the same message written
|
|
35
|
+
* through `warn` or `error` differs from it in both. `emit` also writes a
|
|
36
|
+
* line for a `start` event and a `result` event, `start: <command>` and
|
|
37
|
+
* `result: ok`, whatever the verbosity.
|
|
38
|
+
*/
|
|
39
|
+
import type { OutputStream } from './stream.js';
|
|
40
|
+
import type { Output } from '../../ports/index.js';
|
|
41
|
+
/** What {@link createTextOutput} makes an adapter with. */
|
|
42
|
+
export interface CreateTextOutputOptions {
|
|
43
|
+
/** How much is written: `debug` lines from 2, every other line at any verbosity. */
|
|
44
|
+
verbosity: number;
|
|
45
|
+
/** Where every line is written. */
|
|
46
|
+
stream: OutputStream;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Makes a `text` Output writing each line to `stream`.
|
|
50
|
+
*
|
|
51
|
+
* The output answered is frozen, so no caller replaces one of its
|
|
52
|
+
* functions on an output `packages/rafa/src/core/output/active.ts` may be handing
|
|
53
|
+
* every module of the process.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createTextOutput({ verbosity, stream }: CreateTextOutputOptions): Output;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The barrel over the block reader: every name `blocks.ts` exports, and
|
|
3
|
+
* nothing else.
|
|
4
|
+
*
|
|
5
|
+
* A file outside `packages/rafa/src/core/` imports this file and never the one behind
|
|
6
|
+
* it (`scripts/survey/core-barrels.ts` measures that, and
|
|
7
|
+
* `src/tests/core-barrels.sweep.test.ts` holds it against the tree). A
|
|
8
|
+
* file inside `packages/rafa/src/core/` imports `blocks.ts` directly and never this
|
|
9
|
+
* file.
|
|
10
|
+
*
|
|
11
|
+
* ## Why one file has a barrel of its own
|
|
12
|
+
*
|
|
13
|
+
* `packages/rafa/src/core/plan/` has two barrels, both listed under `CORE_BARRELS` in
|
|
14
|
+
* `scripts/survey/core-moves.ts`. `index.ts` holds the plan parser and
|
|
15
|
+
* the plan-file helpers. This one holds `blocks.ts` alone, because
|
|
16
|
+
* `index.ts` cannot take it.
|
|
17
|
+
*
|
|
18
|
+
* A barrel that groups a file with a file outside `packages/rafa/src/core/` that
|
|
19
|
+
* imports the barrel back by value is a runtime cycle.
|
|
20
|
+
* `src/utils/tracker.ts` imports `readRafaBlocks` by value, and
|
|
21
|
+
* `parse.ts`, behind `index.ts`, imported that file by value when the
|
|
22
|
+
* split was made. So `blocks.ts` behind `index.ts` closed a cycle
|
|
23
|
+
* through the tracker. `blocks.ts` imports no file, so a barrel that
|
|
24
|
+
* holds it alone is imported back by nothing it reaches.
|
|
25
|
+
*
|
|
26
|
+
* `parse.ts` now reads the blocker comment from `blocker-comment.ts`,
|
|
27
|
+
* and no file of `packages/rafa/src/core/` imports the tracker, so that cycle is
|
|
28
|
+
* gone. The split stands as it was made.
|
|
29
|
+
*
|
|
30
|
+
* `index.ts` records the measurement: six cycles before the move and
|
|
31
|
+
* the same six after it, and a seventh in the control that puts all
|
|
32
|
+
* three files behind one barrel.
|
|
33
|
+
*
|
|
34
|
+
* ## What it exports
|
|
35
|
+
*
|
|
36
|
+
* Each name is spelled, types apart from values, as the other core
|
|
37
|
+
* barrels spell theirs. `src/plan/index.ts`, the `./plan` entry,
|
|
38
|
+
* re-exports all six, so this barrel is exactly as wide as the published
|
|
39
|
+
* surface it feeds.
|
|
40
|
+
*
|
|
41
|
+
* `block-reader.test.ts` holds the list to the file: every name
|
|
42
|
+
* `blocks.ts` exports is here once, as the same binding, no other name
|
|
43
|
+
* is here, and no name here is on the other plan barrel.
|
|
44
|
+
*/
|
|
45
|
+
export type { LineSpan, RafaBlock, RafaBlockKind } from './blocks.js';
|
|
46
|
+
export { isRafaBlockKind, RAFA_BLOCK_KINDS, readRafaBlocks } from './blocks.js';
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading fenced `rafa:*` blocks out of a markdown document.
|
|
3
|
+
*
|
|
4
|
+
* A plan stays markdown a person can read, and the parts the loop acts
|
|
5
|
+
* on are fenced code blocks whose info string opens with `rafa:`:
|
|
6
|
+
*
|
|
7
|
+
* ````markdown
|
|
8
|
+
* ```rafa:plan
|
|
9
|
+
* stub: my-feature
|
|
10
|
+
* ```
|
|
11
|
+
* ````
|
|
12
|
+
*
|
|
13
|
+
* The rest of that first word is the block's KIND, `plan` above. This
|
|
14
|
+
* module answers every such block in source order with its kind, its
|
|
15
|
+
* body and the lines it spans, and nothing more: which kinds to act on,
|
|
16
|
+
* and what a body means, belong to whoever reads the document.
|
|
17
|
+
* {@link RAFA_BLOCK_KINDS} names the four kinds this phase defines.
|
|
18
|
+
*
|
|
19
|
+
* ## Fences are read the way a renderer shows them
|
|
20
|
+
*
|
|
21
|
+
* The checklist grammar is line-anchored and fence-blind: `findNextTask`
|
|
22
|
+
* dispatches a `- [ ] ` at column 0 inside a code fence, skipping one
|
|
23
|
+
* only inside a closed block this module answers. This reader is
|
|
24
|
+
* deliberately the opposite. A document
|
|
25
|
+
* that ILLUSTRATES the format puts its example inside a longer fence of
|
|
26
|
+
* its own, as the one above does, and a reader taking every line that
|
|
27
|
+
* opens with a `rafa:` fence for a block would hand the illustration to
|
|
28
|
+
* every task. So every fence is tracked, whatever its info string, and
|
|
29
|
+
* a `rafa:` line inside one is body text.
|
|
30
|
+
*
|
|
31
|
+
* The fence rules are CommonMark's, from its fenced code block section:
|
|
32
|
+
*
|
|
33
|
+
* - An opening fence is three or more backticks or tildes after at
|
|
34
|
+
* most three spaces. Four spaces, or a tab anywhere before it, and
|
|
35
|
+
* the line opens no fence; nor does a fence that does not open its
|
|
36
|
+
* line, which is prose quoting the syntax.
|
|
37
|
+
* - A backtick fence's info string holds no backtick, so a line such
|
|
38
|
+
* as ` ```rafa:plan``` ` is an inline code span and opens nothing.
|
|
39
|
+
* - A closing fence is the SAME character, at least as many of it,
|
|
40
|
+
* after at most three spaces, with nothing after it but spaces and
|
|
41
|
+
* tabs. It carries no info string, so ` ```ts ` inside a block is
|
|
42
|
+
* body; a block that holds a fence of its own opens with a longer
|
|
43
|
+
* one, as the illustration above does.
|
|
44
|
+
* - A fence never closed runs to the end of the document. The block
|
|
45
|
+
* is still answered, with {@link RafaBlock.closed} false: a session
|
|
46
|
+
* cut off mid-report leaves exactly that, and only the caller can
|
|
47
|
+
* judge whether a truncated body is usable.
|
|
48
|
+
* - Each body line loses up to as many leading spaces as the opening
|
|
49
|
+
* fence carried.
|
|
50
|
+
*
|
|
51
|
+
* What is NOT parsed is container structure. Measured against micromark
|
|
52
|
+
* (the CommonMark parser `@eslint/markdown` lints this repo's prose
|
|
53
|
+
* with), that costs three shapes: a fence inside a block quote, and one
|
|
54
|
+
* inside a list item indented four columns or more, are fences to a
|
|
55
|
+
* renderer and not here; a fence inside a multi-line HTML comment is
|
|
56
|
+
* raw HTML to a renderer and a block here. Outside those shapes the two
|
|
57
|
+
* agree: with every info string in 499 markdown files (this repo's, its
|
|
58
|
+
* sibling's and a skill library, on 2026-09-11) rewritten to open with
|
|
59
|
+
* `rafa:`, they answered the same kind, body and span for all 2,177
|
|
60
|
+
* fences. A plan puts its blocks at the top level.
|
|
61
|
+
*
|
|
62
|
+
* ## Lines
|
|
63
|
+
*
|
|
64
|
+
* A line is what `split('\n')` yields, the split `utils/tracker.ts`
|
|
65
|
+
* reads a tracker with, less one trailing carriage return, so a CRLF
|
|
66
|
+
* document reads exactly like its LF twin. A final newline ends the
|
|
67
|
+
* last line rather than opening an empty one. A LONE carriage return
|
|
68
|
+
* breaks no line here, where CommonMark breaks one on it; that is the
|
|
69
|
+
* fourth shape the measurement above found.
|
|
70
|
+
*
|
|
71
|
+
* {@link LineSpan} numbers lines from ONE, fences included, the way an
|
|
72
|
+
* editor and `grep -n` number them, so `sed -n '<first>,<last>p'`
|
|
73
|
+
* prints a block whole. `TaskInfo.lineNum` counts from ZERO: the line
|
|
74
|
+
* at index `i` of that split is line `i + 1` here.
|
|
75
|
+
*
|
|
76
|
+
* ## Unknown kinds
|
|
77
|
+
*
|
|
78
|
+
* A `rafa:` fence whose kind is none of {@link RAFA_BLOCK_KINDS} is
|
|
79
|
+
* answered like any other, never refused and never dropped. That is the
|
|
80
|
+
* rule `core/plan/declaration.ts` applies to an unrecognised key, for the
|
|
81
|
+
* same reason: a plan written for a later phase names kinds this one has
|
|
82
|
+
* never heard of, and refusing them would make every new kind a
|
|
83
|
+
* breaking change for older installs.
|
|
84
|
+
*
|
|
85
|
+
* The kind is the first word of the info string less its `rafa:`
|
|
86
|
+
* prefix, taken as written: `rafa:` alone answers the empty kind, and
|
|
87
|
+
* `rafa:Plan` answers `Plan`, which is unknown. The prefix is matched
|
|
88
|
+
* case-sensitively, as the declaration keys are, so `RAFA:plan` is an
|
|
89
|
+
* ordinary code block and no rafa block at all.
|
|
90
|
+
*
|
|
91
|
+
* Nothing here throws. A document with no block — empty, prose only, or
|
|
92
|
+
* fenced only in other languages — answers an empty list.
|
|
93
|
+
*/
|
|
94
|
+
/**
|
|
95
|
+
* The kinds this phase defines. `plan`, `context` and `stage-context`
|
|
96
|
+
* are a plan's; `report` ends a task session's final message.
|
|
97
|
+
*
|
|
98
|
+
* Each document's reader acts on its own kinds and ignores the rest.
|
|
99
|
+
* {@link readRafaBlocks} answers every kind; see the module note.
|
|
100
|
+
*/
|
|
101
|
+
export declare const RAFA_BLOCK_KINDS: readonly ["plan", "context", "stage-context", "report"];
|
|
102
|
+
/** One of the four kinds this phase defines. */
|
|
103
|
+
export type RafaBlockKind = (typeof RAFA_BLOCK_KINDS)[number];
|
|
104
|
+
/** The lines one block covers: from one, inclusive, fences included. */
|
|
105
|
+
export interface LineSpan {
|
|
106
|
+
/** The opening fence's line. */
|
|
107
|
+
readonly first: number;
|
|
108
|
+
/**
|
|
109
|
+
* The closing fence's line, or the document's last line when the
|
|
110
|
+
* block was never closed.
|
|
111
|
+
*/
|
|
112
|
+
readonly last: number;
|
|
113
|
+
}
|
|
114
|
+
/** One fenced `rafa:*` block, as the document wrote it. */
|
|
115
|
+
export interface RafaBlock {
|
|
116
|
+
/** The info string's first word less `rafa:`, as written. */
|
|
117
|
+
readonly kind: string;
|
|
118
|
+
/**
|
|
119
|
+
* Every line between the fences, each less the opening fence's
|
|
120
|
+
* indentation, joined with `\n` and carrying no final newline. Empty
|
|
121
|
+
* for a block with no line between its fences.
|
|
122
|
+
*/
|
|
123
|
+
readonly body: string;
|
|
124
|
+
/** Where the block sits in the document. */
|
|
125
|
+
readonly span: LineSpan;
|
|
126
|
+
/** False when the document ended before a closing fence. */
|
|
127
|
+
readonly closed: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** True when `kind` is one of the four kinds this phase defines. */
|
|
130
|
+
export declare function isRafaBlockKind(kind: string): kind is RafaBlockKind;
|
|
131
|
+
/**
|
|
132
|
+
* Every `rafa:*` block in a markdown document, in source order.
|
|
133
|
+
*
|
|
134
|
+
* Takes any string and answers a new list, empty when the document
|
|
135
|
+
* holds no rafa block. An unknown kind is answered like a known one and
|
|
136
|
+
* an unclosed block is answered with `closed` false; see the module
|
|
137
|
+
* note for what counts as a fence and how lines are numbered.
|
|
138
|
+
*/
|
|
139
|
+
export declare function readRafaBlocks(markdown: string): readonly RafaBlock[];
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a task's routing declaration off the end of its own line.
|
|
3
|
+
*
|
|
4
|
+
* A plan's task lines may carry a trailing brace block naming what the
|
|
5
|
+
* loop should dispatch the task WITH:
|
|
6
|
+
*
|
|
7
|
+
* ```text
|
|
8
|
+
* - [ ] Update the skill's cap sentence {agent=doc-updater effort=low}
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* The block is the planner's half of a decision the loop otherwise has
|
|
12
|
+
* no way to make. A task carrying none is dispatched exactly as every
|
|
13
|
+
* task was before this module existed — one model, one effort, every
|
|
14
|
+
* tool — so a prose task and a schema migration cost the same, and
|
|
15
|
+
* the only place that knows they should not is the document that
|
|
16
|
+
* wrote them. `start/dispatch.ts` reads the block off each task on its
|
|
17
|
+
* way to the prompt, which is what makes a declared task the one that
|
|
18
|
+
* runs differently.
|
|
19
|
+
*
|
|
20
|
+
* Two parsers were never told about the block and both had to keep
|
|
21
|
+
* working unchanged, which is what shapes the grammar. `findNextTask`
|
|
22
|
+
* matches `^- \[ \] (.+)` and trims the capture, so the block arrives
|
|
23
|
+
* inside `taskInfo.task` and would otherwise reach the injected prompt
|
|
24
|
+
* verbatim; `updateTrackerLine` rewrites the checkbox prefix and
|
|
25
|
+
* nothing before a blocker comment, so the block survives a tick
|
|
26
|
+
* byte-identical. Neither needed a change for the block — the
|
|
27
|
+
* stripping happens here, at the point the text is used. Both halves
|
|
28
|
+
* are driven over real trackers in `tests/tracker-declarations.test.ts`,
|
|
29
|
+
* which is where that claim is measured rather than stated.
|
|
30
|
+
*
|
|
31
|
+
* A blocker comment is the one thing a line may trail after the block,
|
|
32
|
+
* and it comes off in `findNextTask` rather than here
|
|
33
|
+
* (`utils/tracker.ts`). It has to come off first: the block is anchored
|
|
34
|
+
* at end of text, so with the comment still on it would read as task
|
|
35
|
+
* text.
|
|
36
|
+
*
|
|
37
|
+
* The strip rule reaches every `rafa:*` block too, but not from here.
|
|
38
|
+
* A declaration sits ON a task line, so the text alone says where it
|
|
39
|
+
* starts; a block sits AROUND lines, and only a reader holding the
|
|
40
|
+
* document can tell that a line is its body. `findNextTask` is that
|
|
41
|
+
* reader, and every site quoting a task back quotes what it answered,
|
|
42
|
+
* so a task line inside a closed block is skipped there and reaches
|
|
43
|
+
* neither this module nor the prompt, the log or the commit.
|
|
44
|
+
*
|
|
45
|
+
* Four rules keep a brace that a task WROTE ABOUT from being read as a
|
|
46
|
+
* declaration, and each one is a shape that occurs in this repo's own
|
|
47
|
+
* plans:
|
|
48
|
+
*
|
|
49
|
+
* - The block is anchored at END OF TEXT. A task naming `{}` or a
|
|
50
|
+
* JSON literal mid-sentence has something after it.
|
|
51
|
+
* - It carries NO NESTED BRACES, so a task ending on a code span
|
|
52
|
+
* holding an object literal does not match.
|
|
53
|
+
* - It holds AT LEAST ONE RECOGNISED key, which is the rule the
|
|
54
|
+
* scoped grammar turns on. `{ ... }` prose, an empty block and a
|
|
55
|
+
* block of keys nobody here answers to are all task text.
|
|
56
|
+
* - It is not the WHOLE text. A line that is only a declaration
|
|
57
|
+
* leaves no task to dispatch, and stripping it would hand the loop
|
|
58
|
+
* an empty prompt — the one failure that reads as the loop having
|
|
59
|
+
* silently lost the task.
|
|
60
|
+
*
|
|
61
|
+
* When none of that holds the answer is today's behaviour exactly:
|
|
62
|
+
* `text` is the input string unchanged and `declaration` is null.
|
|
63
|
+
* Nothing is stripped and no flag is resolved.
|
|
64
|
+
*
|
|
65
|
+
* Priority inside a block is duplicated ON PURPOSE. With `agent`
|
|
66
|
+
* present the loop never passes `--model` or `--tools`, because an
|
|
67
|
+
* agent definition carries its own model and tool set and a flag beside
|
|
68
|
+
* it would be two authorities for one decision. `--effort` joins
|
|
69
|
+
* `--agent` unless the agent's definition declares an effort of its
|
|
70
|
+
* own, which the caller answers through an {@link AgentEffortLookup}
|
|
71
|
+
* (`utils/agent-definition.ts` reads it off the definition's
|
|
72
|
+
* frontmatter): effort is the cost lever a plan most needs to reach the
|
|
73
|
+
* session, and a definition silent on it leaves the plan's level the
|
|
74
|
+
* only one there is. `budget` is outranked by nothing: an agent
|
|
75
|
+
* definition supplies no budget, so `--max-budget-usd` joins whatever
|
|
76
|
+
* else the block resolved to, agent or none. Every key is still PARSED
|
|
77
|
+
* and still sits on the record whether or not it became a flag, and
|
|
78
|
+
* {@link ResolvedFlags.suppressed} names exactly the granular ones an
|
|
79
|
+
* agent took.
|
|
80
|
+
*
|
|
81
|
+
* `skills`, `tests` and `docs` are the three recognised keys that map to
|
|
82
|
+
* no flag under any block. There is no CLI flag for a skill, and what a
|
|
83
|
+
* plan names there is a hint for whoever reads the dispatch record rather
|
|
84
|
+
* than something the spawn can pass on. `tests` is read by the RUNNER,
|
|
85
|
+
* not the session: it names how much of the suite the loop runs after
|
|
86
|
+
* the task commits (`affected`, `module` or `full`, {@link TEST_SCOPES}),
|
|
87
|
+
* and {@link readTestScope} answers it with the `affected` default when
|
|
88
|
+
* the block is absent or its value unusable. `docs` marks the task's ROLE
|
|
89
|
+
* in its plan: its one value, `repair` ({@link DOCS_ROLES}), names the
|
|
90
|
+
* closing task that repairs the docs of what the plan changed, and
|
|
91
|
+
* {@link readDocsRole} answers it, with null for every other task. All
|
|
92
|
+
* three are recognised all the same, because that is what makes
|
|
93
|
+
* `{skills=bun-testing}`, `{tests=full}` or `{docs=repair}` a declaration
|
|
94
|
+
* on its own and what gets a misspelled value into
|
|
95
|
+
* {@link TaskDeclaration.issues} instead of through to the agent's prompt
|
|
96
|
+
* as task text. None is ever named in {@link ResolvedFlags.suppressed},
|
|
97
|
+
* which says what an AGENT outranked, and nothing outranks a key that
|
|
98
|
+
* was never a flag.
|
|
99
|
+
*
|
|
100
|
+
* Nothing here throws. A recognised key whose value this module cannot
|
|
101
|
+
* use lands in {@link TaskDeclaration.issues} and maps to no flag, so
|
|
102
|
+
* the task runs at the loop's defaults instead of stalling on a CLI
|
|
103
|
+
* that refuses `--effort medum`. An unrecognised KEY is not an issue at
|
|
104
|
+
* all: it is retained in {@link TaskDeclaration.extras} for telemetry,
|
|
105
|
+
* which is what lets a grammar grow without every older plan going red.
|
|
106
|
+
*
|
|
107
|
+
* `EFFORT_LEVELS` and `EffortLevel` are core's
|
|
108
|
+
* (`packages/rafa/src/effort/store/effort-levels.ts`), where the store's dispatch writer
|
|
109
|
+
* types a declared effort; this file re-exports them under the same names.
|
|
110
|
+
*/
|
|
111
|
+
import type { EffortLevel } from '../../effort/store/effort-levels.js';
|
|
112
|
+
export type { EffortLevel } from '../../effort/store/effort-levels.js';
|
|
113
|
+
export { EFFORT_LEVELS } from '../../effort/store/effort-levels.js';
|
|
114
|
+
/**
|
|
115
|
+
* Keys this module answers to, in the order flags are emitted. `budget`
|
|
116
|
+
* sits ahead of `tools` because `--tools` is variadic and has to end the
|
|
117
|
+
* argument list (`core/loop/claude.ts`). `skills`, `tests` and `docs` come
|
|
118
|
+
* last because they emit no flag at all: each is recognised so that its value
|
|
119
|
+
* is PARSED and lands on the record, and a value the parser cannot use
|
|
120
|
+
* lands in {@link TaskDeclaration.issues} rather than passing silently.
|
|
121
|
+
*/
|
|
122
|
+
export declare const DECLARATION_KEYS: readonly ["agent", "model", "effort", "budget", "tools", "skills", "tests", "docs"];
|
|
123
|
+
/** One of the keys the grammar recognises. */
|
|
124
|
+
export type DeclarationKey = (typeof DECLARATION_KEYS)[number];
|
|
125
|
+
/**
|
|
126
|
+
* The granular keys, each mapping to a flag of its own with no agent, and
|
|
127
|
+
* each one an agent can outrank. `budget` maps to a flag of its own too,
|
|
128
|
+
* and is not one of them: nothing outranks it.
|
|
129
|
+
*/
|
|
130
|
+
export declare const GRANULAR_KEYS: readonly ["model", "effort", "tools"];
|
|
131
|
+
/** A key that maps to a flag of its own when no agent is named. */
|
|
132
|
+
export type GranularKey = (typeof GRANULAR_KEYS)[number];
|
|
133
|
+
/**
|
|
134
|
+
* The granular keys an `agent` outranks whenever it is named, its
|
|
135
|
+
* definition supplying a model and a tool set. `effort` is outranked
|
|
136
|
+
* only by a definition declaring one ({@link resolveDeclarationFlags}).
|
|
137
|
+
*/
|
|
138
|
+
export declare const AGENT_OWNED_KEYS: readonly ["model", "tools"];
|
|
139
|
+
/**
|
|
140
|
+
* Model aliases the CLI documents, which is what a plan should spell.
|
|
141
|
+
*
|
|
142
|
+
* A FULL model name is accepted as well ({@link FULL_MODEL_NAME}) —
|
|
143
|
+
* `--model` takes either — but an alias is what survives a model
|
|
144
|
+
* generation changing underneath the plan.
|
|
145
|
+
*/
|
|
146
|
+
export declare const MODEL_ALIASES: readonly ["opus", "sonnet", "haiku", "fable"];
|
|
147
|
+
/**
|
|
148
|
+
* How much of the suite the runner runs after a task commits, as a
|
|
149
|
+
* `tests=` value spells it: `affected` runs the test files the task's
|
|
150
|
+
* diff reaches, `module` the tests of every module the diff touches,
|
|
151
|
+
* and `full` the whole suite. Ordered narrowest first.
|
|
152
|
+
*/
|
|
153
|
+
export declare const TEST_SCOPES: readonly ["affected", "module", "full"];
|
|
154
|
+
/** One of the three scopes a `tests=` value names. */
|
|
155
|
+
export type TestScope = (typeof TEST_SCOPES)[number];
|
|
156
|
+
/**
|
|
157
|
+
* The scope a task runs under when it declares none, or declares one
|
|
158
|
+
* {@link isTestScope} refuses: the narrowest, since a task that needs
|
|
159
|
+
* more is the one its plan marks.
|
|
160
|
+
*/
|
|
161
|
+
export declare const DEFAULT_TEST_SCOPE: TestScope;
|
|
162
|
+
/**
|
|
163
|
+
* The roles a `docs=` value names. There is one: `repair` marks the task
|
|
164
|
+
* that repairs the docs of what its plan changed. A task declaring none
|
|
165
|
+
* has no docs role, so the key has no default value.
|
|
166
|
+
*/
|
|
167
|
+
export declare const DOCS_ROLES: readonly ["repair"];
|
|
168
|
+
/** The one role a `docs=` value names. */
|
|
169
|
+
export type DocsRole = (typeof DOCS_ROLES)[number];
|
|
170
|
+
/**
|
|
171
|
+
* True when `value` is an agent name this module passes on: a bare
|
|
172
|
+
* file stem, so no name can reach a path outside `.claude/agents/`.
|
|
173
|
+
*/
|
|
174
|
+
export declare function isAgentName(value: string): boolean;
|
|
175
|
+
/** The flag a declared budget is passed as, in the CLI's own spelling. */
|
|
176
|
+
export declare const BUDGET_FLAG = "--max-budget-usd";
|
|
177
|
+
/**
|
|
178
|
+
* Reads a `budget=` value as US dollars, or answers null when it is not
|
|
179
|
+
* one this module passes on: a shape {@link BUDGET_VALUE} refuses, or
|
|
180
|
+
* zero, which no session could run under.
|
|
181
|
+
*/
|
|
182
|
+
export declare function parseBudgetUsd(value: string): number | null;
|
|
183
|
+
/** One `key=value` pair, exactly as it was written. */
|
|
184
|
+
export interface DeclarationEntry {
|
|
185
|
+
/** Key as written. Matched case-sensitively against the grammar. */
|
|
186
|
+
key: string;
|
|
187
|
+
/** Value as written, with no unescaping of any kind. */
|
|
188
|
+
value: string;
|
|
189
|
+
}
|
|
190
|
+
/** Why a token could not be used. */
|
|
191
|
+
export type DeclarationIssueReason =
|
|
192
|
+
/** A recognised key appearing twice. The first occurrence wins. */
|
|
193
|
+
'duplicate-key'
|
|
194
|
+
/** A recognised key whose value this module cannot pass on. */
|
|
195
|
+
| 'unusable-value'
|
|
196
|
+
/** A token inside the block carrying no `=` at all. */
|
|
197
|
+
| 'stray-token';
|
|
198
|
+
/** One thing the block said that did not become a flag. */
|
|
199
|
+
export interface DeclarationIssue {
|
|
200
|
+
reason: DeclarationIssueReason;
|
|
201
|
+
/** Key it concerns, or the empty string for a stray token. */
|
|
202
|
+
key: string;
|
|
203
|
+
/** The token as written, so a log line can quote it. */
|
|
204
|
+
text: string;
|
|
205
|
+
}
|
|
206
|
+
/** What one block declared. */
|
|
207
|
+
export interface TaskDeclaration {
|
|
208
|
+
/** The block as it appeared, braces included. */
|
|
209
|
+
raw: string;
|
|
210
|
+
/** Every `key=value` token, in source order, recognised or not. */
|
|
211
|
+
entries: readonly DeclarationEntry[];
|
|
212
|
+
/** Entries whose key is none of {@link DECLARATION_KEYS}. */
|
|
213
|
+
extras: readonly DeclarationEntry[];
|
|
214
|
+
/** What was dropped, and why. Empty on a clean block. */
|
|
215
|
+
issues: readonly DeclarationIssue[];
|
|
216
|
+
/** Agent to dispatch under, or null. */
|
|
217
|
+
agent: string | null;
|
|
218
|
+
/** Model alias or full name, or null. */
|
|
219
|
+
model: string | null;
|
|
220
|
+
/** Effort level, or null. */
|
|
221
|
+
effort: EffortLevel | null;
|
|
222
|
+
/** Budget in US dollars, above zero, or null. */
|
|
223
|
+
budget: number | null;
|
|
224
|
+
/** Tool names, deduped in first-seen order, or null. */
|
|
225
|
+
tools: readonly string[] | null;
|
|
226
|
+
/**
|
|
227
|
+
* Skill names, deduped in first-seen order, or null. Maps to no flag:
|
|
228
|
+
* the key is recognised so a plan's skill hint is parsed and kept,
|
|
229
|
+
* and a reader takes it off the record rather than off the CLI.
|
|
230
|
+
*/
|
|
231
|
+
skills: readonly string[] | null;
|
|
232
|
+
/**
|
|
233
|
+
* Test scope the runner runs after the task commits, or null when the
|
|
234
|
+
* block names none or names one outside {@link TEST_SCOPES}. Maps to
|
|
235
|
+
* no flag; {@link readTestScope} answers the default for null.
|
|
236
|
+
*/
|
|
237
|
+
tests: TestScope | null;
|
|
238
|
+
/**
|
|
239
|
+
* The task's docs role, or null when the block names none or names one
|
|
240
|
+
* outside {@link DOCS_ROLES}. Maps to no flag; {@link readDocsRole}
|
|
241
|
+
* answers it.
|
|
242
|
+
*/
|
|
243
|
+
docs: DocsRole | null;
|
|
244
|
+
}
|
|
245
|
+
/** A task text split from the declaration it carried. */
|
|
246
|
+
export interface ParsedTask {
|
|
247
|
+
/**
|
|
248
|
+
* The text the loop should dispatch. The input unchanged when no
|
|
249
|
+
* declaration was found, and the text before the block otherwise.
|
|
250
|
+
*/
|
|
251
|
+
text: string;
|
|
252
|
+
/** What the block declared, or null when there was no block. */
|
|
253
|
+
declaration: TaskDeclaration | null;
|
|
254
|
+
}
|
|
255
|
+
/** What a declaration resolves to on the command line. */
|
|
256
|
+
export interface ResolvedFlags {
|
|
257
|
+
/** CLI arguments, in {@link DECLARATION_KEYS} order. */
|
|
258
|
+
args: readonly string[];
|
|
259
|
+
/**
|
|
260
|
+
* Keys present on the record that deliberately map to no flag.
|
|
261
|
+
* Non-empty only when an `agent` outranked them.
|
|
262
|
+
*/
|
|
263
|
+
suppressed: readonly GranularKey[];
|
|
264
|
+
}
|
|
265
|
+
/** True when `value` is one of the five effort levels. */
|
|
266
|
+
export declare function isEffortLevel(value: string): value is EffortLevel;
|
|
267
|
+
/** True when `value` is one of the three {@link TEST_SCOPES}. */
|
|
268
|
+
export declare function isTestScope(value: string): value is TestScope;
|
|
269
|
+
/**
|
|
270
|
+
* The test scope a task's declaration yields: the one it declared, or
|
|
271
|
+
* {@link DEFAULT_TEST_SCOPE} when there is no block, the block carries
|
|
272
|
+
* no `tests=`, or its value was unusable (and so recorded in
|
|
273
|
+
* {@link TaskDeclaration.issues} rather than on the record).
|
|
274
|
+
*/
|
|
275
|
+
export declare function readTestScope(declaration: TaskDeclaration | null): TestScope;
|
|
276
|
+
/** True when `value` is one of the {@link DOCS_ROLES}. */
|
|
277
|
+
export declare function isDocsRole(value: string): value is DocsRole;
|
|
278
|
+
/**
|
|
279
|
+
* The docs role a task's declaration yields: the one it declared, or null
|
|
280
|
+
* when there is no block, the block carries no `docs=`, or its value was
|
|
281
|
+
* unusable (and so recorded in {@link TaskDeclaration.issues} rather than
|
|
282
|
+
* on the record).
|
|
283
|
+
*/
|
|
284
|
+
export declare function readDocsRole(declaration: TaskDeclaration | null): DocsRole | null;
|
|
285
|
+
/**
|
|
286
|
+
* True when `value` is a model this module will pass on: one of the
|
|
287
|
+
* documented aliases, or a full name spelled the way the CLI's own
|
|
288
|
+
* help spells one.
|
|
289
|
+
*/
|
|
290
|
+
export declare function isModelValue(value: string): boolean;
|
|
291
|
+
/**
|
|
292
|
+
* Splits a `tools=` value into names, or answers null when it is not
|
|
293
|
+
* usable.
|
|
294
|
+
*
|
|
295
|
+
* Membership is deliberately NOT checked against a list of tool names.
|
|
296
|
+
* The built-in set lives in the CLI and moves with it, so a list kept
|
|
297
|
+
* here would eventually refuse a tool that exists — a failure that
|
|
298
|
+
* reads as a broken declaration rather than as a stale constant. The
|
|
299
|
+
* SHAPE is checked instead, which catches the mistakes a plan actually
|
|
300
|
+
* makes: an empty value, a doubled comma, a stray quote.
|
|
301
|
+
*/
|
|
302
|
+
export declare function parseToolList(value: string): readonly string[] | null;
|
|
303
|
+
/**
|
|
304
|
+
* Splits a `skills=` value into names, or answers null when it is not
|
|
305
|
+
* usable.
|
|
306
|
+
*
|
|
307
|
+
* Membership is NOT checked here against the skills installed, for the
|
|
308
|
+
* reason {@link parseToolList} gives: the set moves with the machine,
|
|
309
|
+
* and this reads one declaration and no tier. `agents/roster.ts` checks
|
|
310
|
+
* it where the tiers are read, and `rafa plan validate` and `loop
|
|
311
|
+
* start`'s preflight refuse a name no loaded tier resolves. The SHAPE is
|
|
312
|
+
* checked here instead — a bare directory
|
|
313
|
+
* stem, so no name reaches a path outside `.claude/skills/` — which
|
|
314
|
+
* catches an empty value, a doubled comma and a stray quote.
|
|
315
|
+
*/
|
|
316
|
+
export declare function parseSkillList(value: string): readonly string[] | null;
|
|
317
|
+
/**
|
|
318
|
+
* Splits a task's text from the declaration block it may carry.
|
|
319
|
+
*
|
|
320
|
+
* Takes the TEXT of a task rather than a whole tracker line, which is
|
|
321
|
+
* what `findNextTask` hands its caller. The anchor is therefore end of
|
|
322
|
+
* text, and a trailing space is tolerated because the tracker's own
|
|
323
|
+
* spelling puts two before the block.
|
|
324
|
+
*/
|
|
325
|
+
export declare function parseTaskDeclaration(taskText: string): ParsedTask;
|
|
326
|
+
/** The task text alone, for a caller with no use for the record. */
|
|
327
|
+
export declare function stripTaskDeclaration(taskText: string): string;
|
|
328
|
+
/**
|
|
329
|
+
* Answers whether the named agent's definition declares an effort of
|
|
330
|
+
* its own. The loop's answer comes from the definition's frontmatter
|
|
331
|
+
* (`utils/agent-definition.ts`); a test hands in whatever its case is
|
|
332
|
+
* about.
|
|
333
|
+
*/
|
|
334
|
+
export type AgentEffortLookup = (agent: string) => boolean;
|
|
335
|
+
/**
|
|
336
|
+
* Maps a declaration onto the flags the loop spawns Claude with.
|
|
337
|
+
*
|
|
338
|
+
* An `agent` outranks `model` and `tools` whenever it is named: the
|
|
339
|
+
* agent definition already names a model and a tool set, and passing
|
|
340
|
+
* a flag beside it would leave two authorities for one decision with
|
|
341
|
+
* no way to tell which won. `effort` is the exception. It joins
|
|
342
|
+
* `--agent` as `--effort` unless `agentDeclaresEffort` answers that
|
|
343
|
+
* the named agent's definition declares an effort of its own, because
|
|
344
|
+
* a definition silent on effort leaves the plan's level the only one
|
|
345
|
+
* there is. The lookup is asked only about a block carrying both keys,
|
|
346
|
+
* so every other block resolves without reading a definition. A name
|
|
347
|
+
* no definition answers for is the lookup's `false` and still passes
|
|
348
|
+
* `--effort`, leaving the CLI to refuse the name before any model
|
|
349
|
+
* call.
|
|
350
|
+
*
|
|
351
|
+
* Granular keys an agent outranked are named in
|
|
352
|
+
* {@link ResolvedFlags.suppressed} rather than dropped, which is what
|
|
353
|
+
* lets the dispatch say what it left to the agent. `skills`, `tests`
|
|
354
|
+
* and `docs` are not among them: they map to no flag under any block,
|
|
355
|
+
* so no agent ever took them.
|
|
356
|
+
*
|
|
357
|
+
* `budget` passes {@link BUDGET_FLAG} whatever else the block holds, an
|
|
358
|
+
* agent included, since a definition supplies no budget, and is never
|
|
359
|
+
* named as suppressed. It goes after `--effort` and ahead of `--tools`,
|
|
360
|
+
* whose variadic value has to end the list.
|
|
361
|
+
*
|
|
362
|
+
* A key whose value did not parse is absent from the record and so
|
|
363
|
+
* emits nothing here — the task runs at the loop's defaults, which is
|
|
364
|
+
* the failure worth having.
|
|
365
|
+
*/
|
|
366
|
+
export declare function resolveDeclarationFlags(declaration: TaskDeclaration | null, agentDeclaresEffort: AgentEffortLookup): ResolvedFlags;
|