runlist 0.99.2 → 0.100.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/GETTING_STARTED.md +249 -0
- package/README.md +414 -46
- package/bin/runlist.mjs +402 -51
- package/package.json +3 -1
- package/plugins/runlist-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/runlist-codex/bin/consume-prompt.mjs +45 -0
- package/plugins/runlist-codex/bin/runlist-hook +29 -11
- package/plugins/runlist-codex/bin/session-start.mjs +16 -0
- package/plugins/runlist-codex/bin/subagent-start.mjs +16 -0
- package/plugins/runlist-codex/hooks/hooks.json +11 -0
- package/plugins/runlist-codex/skills/runlist/SKILL.md +27 -11
- package/plugins/runlist-codex/skills/runlist-setup/SKILL.md +109 -0
- package/runlist.config.example.mjs +6 -0
- package/src/agent-context.mjs +8 -2
- package/src/agent-hosts.mjs +305 -0
- package/src/app-create.mjs +11 -1
- package/src/atomic-mutation.mjs +140 -35
- package/src/baton.mjs +127 -33
- package/src/codex-integration.mjs +57 -7
- package/src/commands.mjs +92 -38
- package/src/config-command.mjs +115 -0
- package/src/config-setting.mjs +163 -0
- package/src/config.mjs +57 -12
- package/src/doctor-hosts.mjs +192 -0
- package/src/doctor.mjs +16 -6
- package/src/export.mjs +11 -0
- package/src/git.mjs +58 -2
- package/src/guard.mjs +41 -13
- package/src/handoff-record.mjs +290 -30
- package/src/handoffs-help.mjs +143 -0
- package/src/handoffs-mode.mjs +486 -0
- package/src/handoffs-scope.mjs +98 -0
- package/src/help.mjs +72 -0
- package/src/host-integration.mjs +7 -1
- package/src/hud.mjs +174 -29
- package/src/index.mjs +11 -4
- package/src/init.mjs +255 -160
- package/src/install.mjs +35 -18
- package/src/installation.mjs +110 -27
- package/src/journal.mjs +2 -2
- package/src/lifecycle.mjs +22 -11
- package/src/managed-path.mjs +68 -4
- package/src/migrate.mjs +7 -0
- package/src/naming.mjs +26 -0
- package/src/new.mjs +202 -91
- package/src/pickup-card.mjs +1 -1
- package/src/plan-changes.mjs +49 -0
- package/src/prompt-handoff.mjs +16 -0
- package/src/prompts.mjs +101 -27
- package/src/rename.mjs +13 -0
- package/src/setup-carry.mjs +291 -0
- package/src/setup-waiting.mjs +20 -0
- package/src/setup-worker.mjs +257 -0
- package/src/setup.mjs +404 -0
- package/src/statuses.mjs +5 -2
- package/src/update.mjs +239 -79
- package/src/use.mjs +4 -2
- package/src/validate.mjs +7 -0
package/bin/runlist.mjs
CHANGED
|
@@ -8,11 +8,15 @@ import { readPlanStage } from '../src/stages.mjs';
|
|
|
8
8
|
import { scopeFilingCoverage } from '../src/filing.mjs';
|
|
9
9
|
import { die, warn, levenshtein, isArchivedPath, toRepoPath } from '../src/util.mjs';
|
|
10
10
|
import { recordCliInvocation, recordGlobalError, sanitizeTelemetryArgv } from '../src/journal.mjs';
|
|
11
|
+
import { outsideHandoffs, outsideHandoffsLine } from '../src/handoffs-scope.mjs';
|
|
11
12
|
import { findRepeatFailureHint } from '../src/hints.mjs';
|
|
13
|
+
import { focusedCommandHelp, unsupportedCommandHelp } from '../src/help.mjs';
|
|
12
14
|
import {
|
|
13
15
|
KNOWN_COMMANDS,
|
|
14
16
|
canonicalCommand,
|
|
17
|
+
commandDefinition,
|
|
15
18
|
commandOwnsOption,
|
|
19
|
+
commandPlanning,
|
|
16
20
|
commandPolicy,
|
|
17
21
|
commandUsage,
|
|
18
22
|
validateCommandArgs,
|
|
@@ -30,6 +34,8 @@ const QUERY_VALUE_FLAGS = new Set([
|
|
|
30
34
|
|
|
31
35
|
function requireCommandPolicy(command, policy) {
|
|
32
36
|
if (policy) return policy;
|
|
37
|
+
const correction = unsupportedCommandHelp(command);
|
|
38
|
+
if (correction) die(`Unknown command: ${command}\n\n${correction}`);
|
|
33
39
|
const matches = KNOWN_COMMANDS
|
|
34
40
|
.map(cmd => ({ cmd, dist: levenshtein(command, cmd) }))
|
|
35
41
|
.sort((a, b) => a.dist - b.dist);
|
|
@@ -61,6 +67,9 @@ function applyPathScopeToIndex(index, config, inputs) {
|
|
|
61
67
|
|
|
62
68
|
const selected = new Set();
|
|
63
69
|
for (const input of inputs) {
|
|
70
|
+
// A handoffs-only project checks its prompts and nothing else; any other
|
|
71
|
+
// path hears that planning is off (src/handoffs-scope.mjs).
|
|
72
|
+
if (outsideHandoffs(input, config)) die(outsideHandoffsLine(input));
|
|
64
73
|
const resolved = resolveExistingPath(input, config);
|
|
65
74
|
if (!resolved) die(`Could not resolve check path: ${input}`);
|
|
66
75
|
|
|
@@ -108,11 +117,39 @@ function applyPathScopeToIndex(index, config, inputs) {
|
|
|
108
117
|
}
|
|
109
118
|
}
|
|
110
119
|
|
|
120
|
+
// Help where planning is off (a handoffs-only project, src/handoffs-mode.mjs):
|
|
121
|
+
// the handoff verbs and the command that turns planning on, nothing else.
|
|
122
|
+
const HANDOFFS_HELP = `runlist v${pkg.version} — session handoffs (planning is off in this project)
|
|
123
|
+
|
|
124
|
+
Commands:
|
|
125
|
+
baton <slug> @<draft-file> Save a resume prompt for the next session (resume-<slug>)
|
|
126
|
+
prompts List saved prompts
|
|
127
|
+
prompts show <name...> Read saved prompts without consuming them
|
|
128
|
+
use [<name>] Resume a saved prompt (no name: the oldest pending)
|
|
129
|
+
new prompt <name> @<file> Save a prompt under a name of its own
|
|
130
|
+
hud What a new session is told at startup
|
|
131
|
+
setup Turn planning on (plans, docs and an index); prompts stay put
|
|
132
|
+
|
|
133
|
+
More help:
|
|
134
|
+
runlist help statuses Prompt statuses
|
|
135
|
+
runlist <cmd> --help Per-command details
|
|
136
|
+
|
|
137
|
+
Global flags: --config <path> --dry-run/-n --verbose --version`;
|
|
138
|
+
|
|
139
|
+
const HANDOFFS_STATUS_HELP = `runlist help statuses — prompt statuses (planning is off in this project)
|
|
140
|
+
|
|
141
|
+
pending Saved for a later session. \`runlist use <name>\` resumes it.
|
|
142
|
+
archived Consumed or set aside; kept under docs/prompts/archived/.
|
|
143
|
+
|
|
144
|
+
Plan and doc statuses come with planning; \`runlist setup\` turns it on.`;
|
|
145
|
+
|
|
111
146
|
const HELP = {
|
|
112
147
|
_main: `runlist v${pkg.version} — frontmatter markdown document manager
|
|
113
148
|
|
|
114
149
|
Common commands:
|
|
150
|
+
setup Guided project setup; preview changes before applying
|
|
115
151
|
plans Live plans (excludes archived)
|
|
152
|
+
show <file> Plan/doc outline and open boxes; --section reads one section
|
|
116
153
|
prompts Prompt queue/admin (list, next, archive, new)
|
|
117
154
|
briefing Full briefing with plan counts + next steps
|
|
118
155
|
agent-context Compact bounded JSON context for agents
|
|
@@ -140,7 +177,8 @@ sessions keep making, and to log every one for audit:
|
|
|
140
177
|
{"matcher":"Bash|Read|Edit|Write","hooks":[{"type":"command","command":"runlist guard"}]}
|
|
141
178
|
|
|
142
179
|
Rules:
|
|
143
|
-
commit-prompt
|
|
180
|
+
commit-prompt policy git add/commit including a pending prompt:
|
|
181
|
+
untracked warns, tracked allows, local denies
|
|
144
182
|
cat-prompt warn cat/less/head of a docs/prompts/*.md (use \`runlist use\`)
|
|
145
183
|
read-prompt warn Read tool on a saved prompt (use \`runlist use\`)
|
|
146
184
|
edit-status deny CHANGING a \`status:\` line — via Edit/Write or in-place
|
|
@@ -202,15 +240,32 @@ host gets that a different way:
|
|
|
202
240
|
\`runlist.js\` without that stamp is treated as hand-authored and is
|
|
203
241
|
never overwritten or removed without --force.
|
|
204
242
|
|
|
243
|
+
Before installing, warns if no persistent CLI is on PATH. Temporary npx,
|
|
244
|
+
pnpm dlx, bunx, Yarn wrapper bins and npm-injected project bins do not
|
|
245
|
+
count. Run \`npm install -g runlist\`, then restart/reload the host.
|
|
246
|
+
--json reports persistentCliMissing only when true.
|
|
247
|
+
|
|
205
248
|
Writing outside the repo is why this is an explicit verb: \`runlist doctor\`
|
|
206
249
|
reports a missing integration but never installs one.`,
|
|
207
250
|
|
|
208
|
-
update: `runlist update — update the
|
|
251
|
+
update: `runlist update — update the CLI and already installed host integrations
|
|
209
252
|
|
|
210
253
|
runlist update npm i -g runlist + claude plugin update runlist@runlist
|
|
211
254
|
runlist update --check report CLI vs plugin versions, do nothing (network-free)
|
|
255
|
+
runlist update --check --json offline executable/profile/source report; no host programs run
|
|
212
256
|
runlist update --cli-only only the npm CLI
|
|
213
|
-
runlist update --plugin-only only
|
|
257
|
+
runlist update --plugin-only only installed host integrations
|
|
258
|
+
runlist update --host codex only this installed host integration (no npm CLI/GUI update)
|
|
259
|
+
runlist update --host claude --claude-config /absolute/profile --claude-bin /absolute/native/claude
|
|
260
|
+
|
|
261
|
+
--host accepts claude, codex or opencode and conflicts with --cli-only.
|
|
262
|
+
Repeat --claude-config to select separate profiles; shared stores update once.
|
|
263
|
+
--claude-bin preserves the chosen executable path. Unknown wrapper/profile
|
|
264
|
+
mapping requires an explicit native binary; no profile is guessed.
|
|
265
|
+
Missing integrations stay missing during update; use install or an explicitly
|
|
266
|
+
selected doctor repair. Unmarked files remain user-owned. Catalog/source parity
|
|
267
|
+
and installed records are checked separately from live activation, which still
|
|
268
|
+
requires reload/trust. Unpublished checkout differences are labelled separately.
|
|
214
269
|
|
|
215
270
|
The plugin and CLI ship in lockstep; a release bumps both. Updating the plugin
|
|
216
271
|
requires a session restart (or /reload-plugins) to apply. The plugin step needs
|
|
@@ -253,7 +308,7 @@ Populated by the \`runlist guard\` PreToolUse hook — see \`runlist help guard\
|
|
|
253
308
|
'help:all': `runlist v${pkg.version} — full command list
|
|
254
309
|
|
|
255
310
|
View & Query:
|
|
256
|
-
hud [--json]
|
|
311
|
+
hud [--json] Session-start handoff orientation + pending-prompt triage
|
|
257
312
|
list [--verbose] [--json] List docs grouped by status (default command)
|
|
258
313
|
briefing [--json] Full briefing with plan status counts + next steps
|
|
259
314
|
context [--summarize] [--json] Full briefing (LLM-oriented; use --json --compact for bounded JSON)
|
|
@@ -327,7 +382,9 @@ Create & Export:
|
|
|
327
382
|
export [--format md|html|json] Export docs as markdown, HTML, or JSON
|
|
328
383
|
|
|
329
384
|
Setup:
|
|
385
|
+
setup [--yes] [--json] Guided setup, meaningful first plan and selected document adoption
|
|
330
386
|
init Create starter config + docs directory
|
|
387
|
+
config show|get|set Inspect project layout/policy; explicitly edit prompt tracking
|
|
331
388
|
install [claude|codex|opencode] Install the agent-host integration (no arg = status)
|
|
332
389
|
update [--check|--cli-only|--plugin-only] Update the CLI + Claude Code plugin (--check reports skew, no network)
|
|
333
390
|
statuses [list|add|set|remove|migrate] Manage per-project status taxonomy
|
|
@@ -713,23 +770,26 @@ Options:
|
|
|
713
770
|
|
|
714
771
|
hud: `runlist hud — actionable triage for session start
|
|
715
772
|
|
|
716
|
-
Prints
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
dumps the full plan-status pipeline and per-plan next_step bodies (kilobytes
|
|
773
|
+
Prints pending prompts, this session's owned plan, and actionable notices.
|
|
774
|
+
Before the first saved handoff, a configured repo with no pending prompts or
|
|
775
|
+
owned plan gets one orientation line. Otherwise silent when nothing needs
|
|
776
|
+
attention. --json returns structured state and the check-error count.
|
|
777
|
+
Distinct from \`runlist briefing\`, which dumps the full plan-status pipeline and per-plan next_step bodies (kilobytes
|
|
722
778
|
on large repos). Use hud for ergonomic session boot; use briefing for
|
|
723
779
|
explicit "give me the full picture."
|
|
724
780
|
|
|
725
|
-
The pending-prompts line
|
|
726
|
-
\`runlist
|
|
727
|
-
|
|
781
|
+
The pending-prompts line says to resume only when asked, via
|
|
782
|
+
\`runlist use <name>\`. --prompt-submit reads a hook JSON payload from stdin:
|
|
783
|
+
save requests get baton instructions; pickup requests get a distinct hint only
|
|
784
|
+
when prompts are pending. Text hooks read prompt frontmatter and ownership
|
|
785
|
+
records without building the index.
|
|
728
786
|
|
|
729
787
|
Recommended SessionStart hook (in ~/.claude/settings.json):
|
|
730
788
|
"SessionStart": [{ "hooks": [{ "type": "command", "command": "runlist hud", "timeout": 5 }] }]
|
|
731
789
|
|
|
732
790
|
Options:
|
|
791
|
+
--prompt-submit Request-specific save/pickup hint (hook JSON on stdin)
|
|
792
|
+
--subagent Compact workflow primer for child agents
|
|
733
793
|
--json Output as JSON ({ owned, prompts, errors, previousSelf,
|
|
734
794
|
fleet, recentRejections, misuseRecap, drift })`,
|
|
735
795
|
|
|
@@ -859,6 +919,17 @@ sync hub status rows, sync dates from git, regenerate the index, then show
|
|
|
859
919
|
remaining issues.
|
|
860
920
|
|
|
861
921
|
Modes:
|
|
922
|
+
--hosts Unified Claude/Codex/OpenCode inventory: executables,
|
|
923
|
+
profiles/shared stores, installed/expected versions,
|
|
924
|
+
source parity and unknown live-session loading.
|
|
925
|
+
Read-only, offline, no project JS import; supports --json.
|
|
926
|
+
--hosts --repair <h> Preview repairs for named hosts only; repeat --repair
|
|
927
|
+
for more hosts. Add --apply to execute those targets.
|
|
928
|
+
--dry-run always wins. --apply alone is refused here.
|
|
929
|
+
Use --claude-config /absolute/profile (repeatable) and
|
|
930
|
+
--claude-bin /absolute/native/claude for explicit targeting.
|
|
931
|
+
Repair may install a selected missing integration;
|
|
932
|
+
foreign/unreadable content and ambiguous wrappers are refused.
|
|
862
933
|
(default) Auto-fix pass — previews by default since 0.37.0
|
|
863
934
|
(F4). Use --apply (alias --yes) to actually write;
|
|
864
935
|
explicit --dry-run still wins over --apply if both
|
|
@@ -1058,6 +1129,7 @@ What the draft does:
|
|
|
1058
1129
|
- Draft opening with a \`---\` frontmatter block → those keys replace the
|
|
1059
1130
|
scaffold's (status, surfaces, modules, current_state, next_step, …), so no
|
|
1060
1131
|
frontmatter edit is needed afterwards. \`type:\` is fixed by <type>.
|
|
1132
|
+
A status invalid for that type is replaced with its normal starting status.
|
|
1061
1133
|
To change status later, use \`runlist set <status> <file>\`.
|
|
1062
1134
|
|
|
1063
1135
|
The repo's own types, starting statuses and folders are listed at the end of
|
|
@@ -1242,13 +1314,83 @@ Examples:
|
|
|
1242
1314
|
|
|
1243
1315
|
Use --dry-run (-n) to preview changes without writing anything.`,
|
|
1244
1316
|
|
|
1245
|
-
|
|
1317
|
+
setup: `runlist setup — guided project setup, with a concrete preview
|
|
1246
1318
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1319
|
+
Run in a terminal for choices and confirmation. Without an interactive terminal,
|
|
1320
|
+
setup previews only; add --yes to apply selected actions. --dry-run always
|
|
1321
|
+
previews without writing. The CLI works without the optional desktop app.
|
|
1249
1322
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1323
|
+
--docs-root <path> Fresh project docs folder (default docs); existing roots preserved
|
|
1324
|
+
--prompt-tracking <p> untracked (recommended), tracked, or local; existing choice preserved
|
|
1325
|
+
--gitignore Explicitly add missing ignore rules; default leaves ignore files alone
|
|
1326
|
+
--host <host> none (default), claude, codex, or opencode; explicit host install
|
|
1327
|
+
--first-plan <slug> Create a plan from the following user-supplied state and next action
|
|
1328
|
+
--current-state <text> What is true now (required with --first-plan)
|
|
1329
|
+
--next-step <text> Concrete next action (required with --first-plan)
|
|
1330
|
+
--adopt <path> Preview/adopt selected untagged Markdown; repeat for more paths
|
|
1331
|
+
--no-carry-prompts Leave pre-setup prompts and plans in place (default carries them over)
|
|
1332
|
+
--json Structured preview or completed/outstanding report
|
|
1333
|
+
--yes Apply choices without terminal questions
|
|
1334
|
+
|
|
1335
|
+
Examples:
|
|
1336
|
+
runlist setup
|
|
1337
|
+
runlist setup --docs-root notes --dry-run
|
|
1338
|
+
runlist setup --first-plan example --current-state "Requirements agreed." --next-step "Build a prototype." --dry-run
|
|
1339
|
+
runlist setup --first-plan example --current-state "Requirements agreed." --next-step "Build a prototype." --yes
|
|
1340
|
+
runlist setup --adopt docs/notes.md --dry-run
|
|
1341
|
+
|
|
1342
|
+
Existing JavaScript hooks, configured folders and prompt locations are preserved.
|
|
1343
|
+
In a handoffs-only project (the config the first baton writes), setup turns
|
|
1344
|
+
planning on: it rewrites that config's mode, widens its root from docs/prompts
|
|
1345
|
+
to docs, adds plans and an index, and leaves the prompts where they are. The
|
|
1346
|
+
preview counts the existing Markdown under docs/ that planning starts checking.
|
|
1347
|
+
--docs-root cannot choose another folder there.
|
|
1348
|
+
Fresh setup carries runlist prompts and plans (including archives, and prompts an
|
|
1349
|
+
older runlist left in a root prompts/) into the new folders, fixing references
|
|
1350
|
+
and local handoff/ownership state. Foreign files stay put.
|
|
1351
|
+
Setup must finish transaction recovery before writing the first config; unresolved
|
|
1352
|
+
transactions stay Outstanding. Inspect them with runlist doctor --transactions.
|
|
1353
|
+
Untracked does not mean ignored: git add -A may include prompts and runtime state.
|
|
1354
|
+
Setup lists a missing persistent CLI on PATH under Outstanding.
|
|
1355
|
+
JSON includes checks.host.persistentCliMissing only when true.
|
|
1356
|
+
Run \`npm install -g runlist\` before reloading.
|
|
1357
|
+
Setup reports pre-existing document issues and unfinished host integration;
|
|
1358
|
+
installed integration still needs a new/reloaded host session.`,
|
|
1359
|
+
|
|
1360
|
+
init: `runlist init [--prompt-tracking <untracked|tracked|local>] [--gitignore]
|
|
1361
|
+
|
|
1362
|
+
Creates starter config and missing scaffold files; respects configured roots
|
|
1363
|
+
and index paths. Existing files are preserved. Existing Markdown can be
|
|
1364
|
+
inspected to pre-populate a new config; adoption hints identify untagged docs.
|
|
1365
|
+
|
|
1366
|
+
New projects default to untracked prompts (recommended to reduce commit churn).
|
|
1367
|
+
Untracked does not mean ignored: git add -A can still include them. Existing
|
|
1368
|
+
configs keep their policy; use runlist config set promptTracking <value> to change it.
|
|
1369
|
+
No ignore files are edited by default; runtime state in .runlist/ and legacy
|
|
1370
|
+
.dotmd/ may also be staged by git add -A. --gitignore explicitly appends missing
|
|
1371
|
+
state rules and prompt rules (unless tracked). Preview with --dry-run.
|
|
1372
|
+
Host integration is separate: runlist install <claude|codex|opencode>.`,
|
|
1373
|
+
|
|
1374
|
+
config: `runlist config — inspect or change supported onboarding settings
|
|
1375
|
+
|
|
1376
|
+
runlist config show [--json]
|
|
1377
|
+
runlist config get promptTracking [--json]
|
|
1378
|
+
runlist config get root [--json]
|
|
1379
|
+
runlist config get promptDirectory [--json]
|
|
1380
|
+
runlist config set promptTracking <untracked|tracked|local> [--dry-run] [--json]
|
|
1381
|
+
|
|
1382
|
+
Shows effective values and their sources: promptTracking, mode (plans, or
|
|
1383
|
+
handoffs when planning is off), root, docsRoots, index.path, archiveDir and
|
|
1384
|
+
promptDirectory. Resolved roots, index and prompt
|
|
1385
|
+
directory use absolute paths; root keeps its configured string or array.
|
|
1386
|
+
Only promptTracking is CLI-editable; other settings give manual config guidance.
|
|
1387
|
+
Inspection imports the trusted project's JavaScript config but invokes no hooks.
|
|
1388
|
+
untracked recommends leaving pending prompts out of commits and warns in guards;
|
|
1389
|
+
tracked allows prompt Git operations; local denies them (legacy default for
|
|
1390
|
+
existing configs without an explicit choice). A new project defaults to untracked.
|
|
1391
|
+
Untracked does not mean ignored; git add -A can include pending prompts.
|
|
1392
|
+
Writes preserve hooks and refuse symlinks, computed or ambiguous exports. Run setup first
|
|
1393
|
+
if no config exists. Changing policy never edits ignore files or Git state.`,
|
|
1252
1394
|
|
|
1253
1395
|
plans: `runlist plans — list live plans (excludes archived by default)
|
|
1254
1396
|
|
|
@@ -1359,10 +1501,23 @@ Usage (write the resume to a file first, then pick the form that matches):
|
|
|
1359
1501
|
runlist baton @/tmp/draft.md # a plan is in-session: save resume-<plan-slug>, release the plan
|
|
1360
1502
|
runlist baton <plan-file> @/tmp/draft.md # hand off a named plan
|
|
1361
1503
|
runlist baton <slug> @/tmp/draft.md # no plan: save resume-<slug>, change nothing else
|
|
1362
|
-
runlist baton --done [<plan-file>]
|
|
1504
|
+
runlist baton --done [<plan-file>] --next-step "..." # after a plan handoff: write next_step, confirm it
|
|
1363
1505
|
The resume can also come from stdin (\`-\`, or a pipe) or --message "..." for one-liners.
|
|
1364
1506
|
Baton prints the prompt name it saved, as its last line too, and, in plan mode, the git commit to run.
|
|
1365
1507
|
|
|
1508
|
+
No setup needed: in a Git repository with no runlist config, the first saved
|
|
1509
|
+
prompt also creates a handoffs-only runlist.config.mjs at the worktree root,
|
|
1510
|
+
with prompts in docs/prompts/ (planning stays off until \`runlist setup\`).
|
|
1511
|
+
Prompts an older runlist saved in a root prompts/ move to docs/prompts/ first,
|
|
1512
|
+
in one line of output (--dry-run lists the moves); a name collision there
|
|
1513
|
+
refuses the save and writes no config. Other files in prompts/ stay put.
|
|
1514
|
+
Only type: prompt files in docs/prompts/ are runlist's: plan views and reads
|
|
1515
|
+
filtered to another --type say planning is off; plan actions and files outside
|
|
1516
|
+
that folder refuse with the same line. Outside a Git working tree
|
|
1517
|
+
baton refuses. Plans and prompts saved before any config elsewhere in the
|
|
1518
|
+
repository stay where they are; the planning-off line counts the ones
|
|
1519
|
+
\`runlist setup\` brings in.
|
|
1520
|
+
|
|
1366
1521
|
The resume (10–20 lines): the next concrete decision plus any gotchas, and the
|
|
1367
1522
|
paths of the plans/docs it concerns — not a recap of the plan body.
|
|
1368
1523
|
|
|
@@ -1376,6 +1531,8 @@ Options:
|
|
|
1376
1531
|
--template Print a resume draft (or --subagent draft), using saved Markdown settings
|
|
1377
1532
|
--replace Replace exactly one pending handoff; archive its prior text
|
|
1378
1533
|
--done Report a plan handoff done once next_step changed after the prompt was saved
|
|
1534
|
+
--next-step "..." With --done: write the plan's next_step (one line; must differ from the saved one)
|
|
1535
|
+
--current-state "..." With --done: also write current_state (never counts as done on its own)
|
|
1379
1536
|
--json Structured repository/session/generated file result
|
|
1380
1537
|
--dry-run, -n Preview without writing
|
|
1381
1538
|
|
|
@@ -1395,23 +1552,31 @@ Examples:
|
|
|
1395
1552
|
runlist baton docs/plans/auth.md @/tmp/draft.md
|
|
1396
1553
|
runlist baton docs/plans/auth.md @/tmp/new-draft.md --replace
|
|
1397
1554
|
runlist baton --status paused --note "blocked on review" @/tmp/d.md
|
|
1555
|
+
runlist baton --done --next-step "wire the retry path" --current-state "parser merged"
|
|
1398
1556
|
cat /tmp/draft.md | runlist baton
|
|
1399
1557
|
|
|
1400
1558
|
Plan mode (a plan is in-session, or you pass one) publishes in one atomic
|
|
1401
1559
|
cooperating transaction:
|
|
1402
1560
|
1. A resume prompt named resume-<plan-slug>, stamped with a plan: link so
|
|
1403
|
-
consuming it re-claims the plan (see \`runlist use\`). The
|
|
1404
|
-
|
|
1561
|
+
consuming it re-claims the plan (see \`runlist use\`). The next session's
|
|
1562
|
+
hud surfaces it; never paste resume text
|
|
1405
1563
|
into chat.
|
|
1406
1564
|
2. Releases the plan: one status flip, in-session → active by default.
|
|
1407
1565
|
3. Defers the shared generated index and prints exact repository-only commit
|
|
1408
|
-
guidance
|
|
1409
|
-
|
|
1566
|
+
guidance for the plan. Prompts stay out of this generated pathspec; use
|
|
1567
|
+
\`runlist config get promptTracking\` for the project's prompt Git policy.
|
|
1568
|
+
Ownership records are local runtime state.
|
|
1410
1569
|
4. When the plan was in-session and has a next_step field, the handoff is not
|
|
1411
|
-
done yet:
|
|
1412
|
-
\`runlist baton --done
|
|
1413
|
-
|
|
1414
|
-
|
|
1570
|
+
done yet: update the plan body first, then run
|
|
1571
|
+
\`runlist baton --done --next-step "<next move>"\` (plus
|
|
1572
|
+
\`--current-state "..."\` if that changed). It writes the frontmatter,
|
|
1573
|
+
creating a missing key, and refuses a next_step that still reads as it did
|
|
1574
|
+
when the prompt was saved. The prompt is saved first, so a stop between the
|
|
1575
|
+
two steps loses nothing, and \`--done <plan-file>\` works from any session.
|
|
1576
|
+
\`--dry-run\` with --next-step previews the write and removes nothing.
|
|
1577
|
+
The fields go only to the plan at the path the handoff was saved for. If
|
|
1578
|
+
it moved, update it where it is now and run \`--done <new-path>\`, or clear
|
|
1579
|
+
the handoff with \`--done <old-path>\`.
|
|
1415
1580
|
Which plan? Pass it explicitly, or baton resolves exactly one plan owned by
|
|
1416
1581
|
this authoritative session. Journal entries and global in-session counts never
|
|
1417
1582
|
grant ownership. A live pickup-hook delivery lease blocks release and force
|
|
@@ -1602,6 +1767,7 @@ heading matches case-insensitively: exact first, then as a prefix, then as a
|
|
|
1602
1767
|
substring; other matches are named. A section read stops at 500 lines or
|
|
1603
1768
|
60 KB and prints the command that continues it. Headings and boxes inside code
|
|
1604
1769
|
fences are ignored. Several files at once get a shorter outline each.
|
|
1770
|
+
There is no --lines option; continue with --section "<heading>" --from <line>.
|
|
1605
1771
|
|
|
1606
1772
|
A phase heading shows its own boxes as [done/total], → marks the active phase
|
|
1607
1773
|
(the one \`use\` would point at), and a marker its boxes contradict is named
|
|
@@ -1849,6 +2015,7 @@ function splitGlobalArgs(args) {
|
|
|
1849
2015
|
let explicitConfig = null;
|
|
1850
2016
|
let rootArg = null;
|
|
1851
2017
|
let typeArg = null;
|
|
2018
|
+
const typeArgs = [];
|
|
1852
2019
|
let dryRun = false;
|
|
1853
2020
|
let verbose = false;
|
|
1854
2021
|
|
|
@@ -1863,7 +2030,7 @@ function splitGlobalArgs(args) {
|
|
|
1863
2030
|
if (local) rest.push(arg, next);
|
|
1864
2031
|
else if (arg === '--config') explicitConfig = next;
|
|
1865
2032
|
else if (arg === '--root') rootArg = next;
|
|
1866
|
-
else typeArg = next;
|
|
2033
|
+
else { typeArg = next; typeArgs.push(next); }
|
|
1867
2034
|
i += 1;
|
|
1868
2035
|
continue;
|
|
1869
2036
|
}
|
|
@@ -1876,7 +2043,49 @@ function splitGlobalArgs(args) {
|
|
|
1876
2043
|
rest.push(arg);
|
|
1877
2044
|
}
|
|
1878
2045
|
|
|
1879
|
-
return { command, rest, explicitConfig, rootArg, typeArg, dryRun, verbose };
|
|
2046
|
+
return { command, rest, explicitConfig, rootArg, typeArg, typeArgs, dryRun, verbose };
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// Every value given for `option`, as `--type x` or `--type=x`.
|
|
2050
|
+
function optionValues(args, option) {
|
|
2051
|
+
const values = [];
|
|
2052
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
2053
|
+
if (args[i] === option && args[i + 1] !== undefined) values.push(args[++i]);
|
|
2054
|
+
else if (args[i].startsWith(`${option}=`)) values.push(args[i].slice(option.length + 1));
|
|
2055
|
+
}
|
|
2056
|
+
return values;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// Where planning is off, the briefing views end with the planning-off line.
|
|
2060
|
+
async function writePlanningOffLine(config) {
|
|
2061
|
+
const { planningOffLine } = await import('../src/handoffs-scope.mjs');
|
|
2062
|
+
const { documentsWaitingForSetup } = await import('../src/setup-waiting.mjs');
|
|
2063
|
+
process.stdout.write(`${planningOffLine(await documentsWaitingForSetup(config))}\n`);
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// A type a command takes as a positional argument rather than --type, which
|
|
2067
|
+
// the planning gate reads like one: `statuses migrate <type>` and the new
|
|
2068
|
+
// value of `migrate type <old> <new>`.
|
|
2069
|
+
function positionalTypeSelector(command, args) {
|
|
2070
|
+
const positional = args.filter(arg => !arg.startsWith('-'));
|
|
2071
|
+
if (command === 'statuses' && positional[0] === 'migrate' && positional[1]) return { type: positional[1], spelling: `statuses migrate ${positional[1]}` };
|
|
2072
|
+
if (command === 'migrate' && positional[0] === 'type' && positional[2]) return { type: positional[2], spelling: `migrate type ${positional[1]} ${positional[2]}` };
|
|
2073
|
+
return null;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
// The verbs that can change a document, for the carry that runs before them
|
|
2077
|
+
// in a repository with no config (src/handoffs-mode.mjs carryBeforeChange).
|
|
2078
|
+
const DOCUMENT_CHANGES = new Set(['use', 'next', 'archive', 'set', 'status', 'bulk', 'bulk-tag', 'touch', 'rename', 'migrate', 'fix-refs', 'sync-status', 'fix-membership', 'record', 'decision']);
|
|
2079
|
+
function changesDocuments(command, args) {
|
|
2080
|
+
if (DOCUMENT_CHANGES.has(command)) return true;
|
|
2081
|
+
if (command === 'prompts') return ['use', 'resume', 'next', 'archive', 'link', 'hold', 'shelve', 'unhold', 'unshelve'].includes(args[0]);
|
|
2082
|
+
if (command === 'runlist' || command === 'roadmap') return args.some(arg => ['next', 'add', 'remove', 'reorder'].includes(arg));
|
|
2083
|
+
if (command === 'yardstick') return ['mark', 'close', 'fold', 'clear'].includes(args[0]);
|
|
2084
|
+
if (command === 'lint' || command === 'check') return args.includes('--fix');
|
|
2085
|
+
// Not the transaction, claim and session tools: they are how a stuck move or
|
|
2086
|
+
// claim is resolved, so nothing may run ahead of them.
|
|
2087
|
+
if (command === 'doctor') return (args.includes('--apply') || args.includes('--yes')) && !args.some(arg => ['--transactions', '--claims', '--session'].includes(arg));
|
|
2088
|
+
return false;
|
|
1880
2089
|
}
|
|
1881
2090
|
|
|
1882
2091
|
async function main() {
|
|
@@ -1891,7 +2100,7 @@ async function main() {
|
|
|
1891
2100
|
// Before-command options are global. After the command, a schema-declared
|
|
1892
2101
|
// local option wins; otherwise the historical anywhere-global form remains.
|
|
1893
2102
|
const parsed = splitGlobalArgs(args);
|
|
1894
|
-
let { command, explicitConfig, rootArg, typeArg, dryRun, verbose } = parsed;
|
|
2103
|
+
let { command, explicitConfig, rootArg, typeArg, typeArgs, dryRun, verbose } = parsed;
|
|
1895
2104
|
let restArgs = parsed.rest;
|
|
1896
2105
|
const captureHelpConfig = async topic => {
|
|
1897
2106
|
_resolvedCommand = 'help';
|
|
@@ -1947,12 +2156,48 @@ async function main() {
|
|
|
1947
2156
|
}
|
|
1948
2157
|
}
|
|
1949
2158
|
|
|
2159
|
+
// One help renderer for `runlist help <cmd>` and `runlist <cmd> --help`, so
|
|
2160
|
+
// both spellings say the same thing, including where planning is off.
|
|
2161
|
+
const renderCommandHelp = async (name, helpArgs) => {
|
|
2162
|
+
if (_resolvedConfig?.mode === 'handoffs') {
|
|
2163
|
+
if (commandPlanning(name, helpArgs)) {
|
|
2164
|
+
const { planningOffLine } = await import('../src/handoffs-scope.mjs');
|
|
2165
|
+
const { documentsWaitingForSetup } = await import('../src/setup-waiting.mjs');
|
|
2166
|
+
return `${planningOffLine(await documentsWaitingForSetup(_resolvedConfig))}\n`;
|
|
2167
|
+
}
|
|
2168
|
+
const { handoffsCommandHelp } = await import('../src/handoffs-help.mjs');
|
|
2169
|
+
const text = handoffsCommandHelp(name);
|
|
2170
|
+
if (text) return `${text}\n`;
|
|
2171
|
+
}
|
|
2172
|
+
const focused = focusedCommandHelp(name, helpArgs);
|
|
2173
|
+
let text = focused?.fallback ? `No focused help for this request; showing complete \`runlist ${name} --help\`.\n\n` : '';
|
|
2174
|
+
text += `${focused?.text ?? HELP[name] ?? commandUsage(name)}\n`;
|
|
2175
|
+
if (name === 'new' && !focused?.text) {
|
|
2176
|
+
// Best effort: outside a runlist repo, or with a broken config, the
|
|
2177
|
+
// static help above is the whole answer.
|
|
2178
|
+
try {
|
|
2179
|
+
const repoConfig = _resolvedConfig;
|
|
2180
|
+
if (repoConfig?.configFound !== false) {
|
|
2181
|
+
const { newHelpForRepo } = await import('../src/new.mjs');
|
|
2182
|
+
text += `\n${newHelpForRepo(repoConfig)}\n`;
|
|
2183
|
+
}
|
|
2184
|
+
} catch { /* static help already printed */ }
|
|
2185
|
+
}
|
|
2186
|
+
return text;
|
|
2187
|
+
};
|
|
2188
|
+
|
|
1950
2189
|
if (command === 'help' || command === '--help' || command === '-h') {
|
|
1951
2190
|
const topic = restArgs[0];
|
|
1952
|
-
|
|
2191
|
+
const topicCommand = topic && commandDefinition(topic) && !HELP[`help:${topic}`] ? commandDefinition(topic).name : null;
|
|
2192
|
+
await captureHelpConfig(topic ? (HELP[topic] || HELP[`help:${topic}`] || topicCommand ? topic : '(unknown)') : '_main');
|
|
2193
|
+
if (_resolvedConfig?.mode === 'handoffs' && (!topic || topic === 'all' || topic === 'statuses')) {
|
|
2194
|
+
process.stdout.write(`${topic === 'statuses' ? HANDOFFS_STATUS_HELP : HANDOFFS_HELP}\n`);
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
1953
2197
|
if (topic) {
|
|
1954
2198
|
const key = `help:${topic}`;
|
|
1955
2199
|
if (HELP[key]) { process.stdout.write(`${HELP[key]}\n`); return; }
|
|
2200
|
+
if (topicCommand) { process.stdout.write(await renderCommandHelp(topicCommand, [])); return; }
|
|
1956
2201
|
if (HELP[topic]) { process.stdout.write(`${HELP[topic]}\n`); return; }
|
|
1957
2202
|
process.stderr.write(`Unknown help topic: ${topic}\n\nAvailable topics: all, statuses\nPer-command help: runlist <cmd> --help\n`);
|
|
1958
2203
|
process.exitCode = 1;
|
|
@@ -1972,30 +2217,23 @@ async function main() {
|
|
|
1972
2217
|
);
|
|
1973
2218
|
const doctorExplicitApply = args.includes('--apply') || args.includes('--yes');
|
|
1974
2219
|
const effectiveDryRun = dryRun || (command === 'doctor' && !doctorSubMode && !doctorExplicitApply);
|
|
1975
|
-
const passiveMachineContext = command === 'agent-context'
|
|
2220
|
+
const passiveMachineContext = command === 'agent-context' || command === 'config' || command === 'setup'
|
|
1976
2221
|
|| (command === 'context' && args.includes('--json') && args.includes('--compact'));
|
|
1977
2222
|
// Git/frontmatter drift is validation work, not index construction. Keep the
|
|
1978
2223
|
// bounded history scan on commands that report or repair that drift; ordinary
|
|
1979
2224
|
// reads still run schema/reference validation without walking 10k commits.
|
|
1980
2225
|
const gitStaleness = command === 'check' || command === 'doctor';
|
|
1981
2226
|
_suppressObservability = effectiveDryRun || command === 'hud' || passiveMachineContext;
|
|
2227
|
+
const hostDiagnostics = command === 'doctor' && restArgs.includes('--hosts');
|
|
2228
|
+
const updateInspection = command === 'update' && restArgs.includes('--check');
|
|
2229
|
+
if (hostDiagnostics || updateInspection) _suppressObservability = true;
|
|
1982
2230
|
|
|
1983
2231
|
// Per-command help
|
|
1984
2232
|
if (args.includes('--help') || args.includes('-h')) {
|
|
1985
2233
|
requireCommandPolicy(command, dispatchPolicy);
|
|
1986
|
-
await captureHelpConfig(command);
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
// Best effort: outside a runlist repo, or with a broken config, the
|
|
1990
|
-
// static help above is the whole answer.
|
|
1991
|
-
try {
|
|
1992
|
-
const repoConfig = _resolvedConfig;
|
|
1993
|
-
if (repoConfig?.configFound !== false) {
|
|
1994
|
-
const { newHelpForRepo } = await import('../src/new.mjs');
|
|
1995
|
-
process.stdout.write(`\n${newHelpForRepo(repoConfig)}\n`);
|
|
1996
|
-
}
|
|
1997
|
-
} catch { /* static help already printed */ }
|
|
1998
|
-
}
|
|
2234
|
+
if (!hostDiagnostics && !updateInspection) await captureHelpConfig(command);
|
|
2235
|
+
const helpArgs = restArgs.filter(arg => arg !== '--help' && arg !== '-h');
|
|
2236
|
+
process.stdout.write(await renderCommandHelp(command, _resolvedConfig?.mode === 'handoffs' ? helpArgs : restArgs));
|
|
1999
2237
|
return;
|
|
2000
2238
|
}
|
|
2001
2239
|
|
|
@@ -2007,6 +2245,37 @@ async function main() {
|
|
|
2007
2245
|
return;
|
|
2008
2246
|
}
|
|
2009
2247
|
|
|
2248
|
+
// Host diagnostics must work before project setup and with a broken project
|
|
2249
|
+
// config. This branch deliberately imports no project JavaScript and runs no
|
|
2250
|
+
// state migration or document sweep. Only selected repairs can write.
|
|
2251
|
+
if (command === 'doctor' && restArgs.includes('--hosts')) {
|
|
2252
|
+
requireCommandPolicy(command, dispatchPolicy);
|
|
2253
|
+
try { restArgs = validateCommandArgs(command, restArgs); } catch (err) { die(err.message); }
|
|
2254
|
+
const { runDoctorHosts } = await import('../src/doctor-hosts.mjs');
|
|
2255
|
+
await runDoctorHosts(restArgs, { repoRoot: process.cwd() }, { dryRun });
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
if (command === 'update') {
|
|
2260
|
+
requireCommandPolicy(command, dispatchPolicy);
|
|
2261
|
+
try { restArgs = validateCommandArgs(command, restArgs); } catch (err) { die(err.message); }
|
|
2262
|
+
const { runUpdate } = await import('../src/update.mjs');
|
|
2263
|
+
await runUpdate(restArgs, { repoRoot: process.cwd() }, { dryRun });
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
if (command === 'install') {
|
|
2268
|
+
requireCommandPolicy(command, dispatchPolicy);
|
|
2269
|
+
try { restArgs = validateCommandArgs(command, restArgs); } catch (err) { die(err.message); }
|
|
2270
|
+
const { runInstall } = await import('../src/install.mjs');
|
|
2271
|
+
await runInstall(restArgs, { repoRoot: process.cwd() }, { dryRun });
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (command === 'doctor' && restArgs.some(arg => ['--repair', '--claude-config', '--claude-bin'].includes(arg))) {
|
|
2276
|
+
die('Host repair and Claude selectors require runlist doctor --hosts.');
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2010
2279
|
if (command === 'baton' && restArgs.includes('--template')) {
|
|
2011
2280
|
_suppressObservability = true;
|
|
2012
2281
|
requireCommandPolicy(command, dispatchPolicy);
|
|
@@ -2049,7 +2318,7 @@ async function main() {
|
|
|
2049
2318
|
// nags. `install`/`doctor`/`update` are skipped: they report this themselves,
|
|
2050
2319
|
// and telling `runlist install opencode` to run `runlist install opencode` is
|
|
2051
2320
|
// noise.
|
|
2052
|
-
if (!['install', 'doctor', 'update', 'hud', 'guard'].includes(command)) {
|
|
2321
|
+
if (!['install', 'doctor', 'update', 'hud', 'guard', 'setup', 'config'].includes(command)) {
|
|
2053
2322
|
const { degradedIdentityNotice } = await import('../src/host-integration.mjs');
|
|
2054
2323
|
const notice = degradedIdentityNotice(config?.repoRoot, { version: pkg.version });
|
|
2055
2324
|
if (notice) process.stderr.write(`${notice}\n`);
|
|
@@ -2060,7 +2329,9 @@ async function main() {
|
|
|
2060
2329
|
value: { dryRun, passive: command === 'hud' || passiveMachineContext, suppressSideEffects, gitStaleness },
|
|
2061
2330
|
enumerable: false,
|
|
2062
2331
|
});
|
|
2063
|
-
|
|
2332
|
+
// Init owns scaffolding and validates policy before writing. Do not migrate
|
|
2333
|
+
// legacy state ahead of that validation (including a refused init request).
|
|
2334
|
+
if (!suppressSideEffects && command !== 'init') {
|
|
2064
2335
|
const { migrateStateDirectory } = await import('../src/state-migration.mjs');
|
|
2065
2336
|
const { LEGACY_STATE_DIR, STATE_DIR } = await import('../src/naming.mjs');
|
|
2066
2337
|
const migration = migrateStateDirectory(config.repoRoot);
|
|
@@ -2075,17 +2346,89 @@ async function main() {
|
|
|
2075
2346
|
// present in the centralized command policy registry.
|
|
2076
2347
|
if (!config.presets[command]) requireCommandPolicy(command, dispatchPolicy);
|
|
2077
2348
|
|
|
2349
|
+
// Handoffs-only projects (src/handoffs-mode.mjs) have no plans or docs. A
|
|
2350
|
+
// command the registry marks as needing planning, or any command whose
|
|
2351
|
+
// arguments ask for a type other than prompts, says planning is off and
|
|
2352
|
+
// names the command that turns it on. The arguments are read as the command
|
|
2353
|
+
// will run them: a preset's own arguments included, every --type counted
|
|
2354
|
+
// (repeated, comma-separated, or --type=x), and the positional type a
|
|
2355
|
+
// command takes (`statuses migrate <type>`, `migrate type <old> <new>`).
|
|
2356
|
+
// This runs before argument validation so `runlist runlist` with no hub
|
|
2357
|
+
// gets the same answer.
|
|
2358
|
+
if (config.mode === 'handoffs') {
|
|
2359
|
+
const { requirePlanning } = await import('../src/handoffs-scope.mjs');
|
|
2360
|
+
const preset = config.presets[command];
|
|
2361
|
+
const effective = [...(preset ?? []), ...restArgs];
|
|
2362
|
+
const json = effective.includes('--json');
|
|
2363
|
+
const types = [...typeArgs, ...optionValues(effective, '--type')].flatMap(value => value.split(',')).map(type => type.trim()).filter(Boolean);
|
|
2364
|
+
const others = types.filter(type => type !== 'prompt');
|
|
2365
|
+
const positionalType = preset ? null : positionalTypeSelector(command, restArgs);
|
|
2366
|
+
const planning = commandPlanning(command, restArgs);
|
|
2367
|
+
const typed = !planning && others.length > 0;
|
|
2368
|
+
const selected = !planning && !typed && positionalType && positionalType.type !== 'prompt';
|
|
2369
|
+
const requirement = planning?.requirement ?? (typed ? (preset || commandPolicy(command)?.mutation === 'none' ? 'view' : 'action') : selected ? 'action' : null);
|
|
2370
|
+
if (requirement) {
|
|
2371
|
+
for (const w of config.configWarnings ?? []) warn(w);
|
|
2372
|
+
const what = selected ? `\`runlist ${positionalType.spelling}\``
|
|
2373
|
+
: `\`runlist ${[command, planning?.subcommand].filter(Boolean).join(' ')}${typed ? ` --type ${others.join(',')}` : ''}\``;
|
|
2374
|
+
const { documentsWaitingForSetup } = await import('../src/setup-waiting.mjs');
|
|
2375
|
+
if (requirePlanning(config, what, requirement, { json, waiting: await documentsWaitingForSetup(config) })) return;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2078
2379
|
try {
|
|
2079
2380
|
restArgs = validateCommandArgs(command, restArgs, { preset: Boolean(config.presets[command]) });
|
|
2080
2381
|
} catch (err) {
|
|
2081
2382
|
die(err.message);
|
|
2082
2383
|
}
|
|
2083
2384
|
|
|
2385
|
+
// Before any config, prompts an older runlist saved in the root prompts/
|
|
2386
|
+
// move to docs/prompts/ ahead of a command that changes documents
|
|
2387
|
+
// (src/handoffs-mode.mjs), so none is consumed, archived or rewritten where
|
|
2388
|
+
// runlist no longer writes. A file argument naming a moved prompt follows it.
|
|
2389
|
+
if (!config.configFound && changesDocuments(command, restArgs)) {
|
|
2390
|
+
const { carryBeforeChange } = await import('../src/handoffs-mode.mjs');
|
|
2391
|
+
const carried = await carryBeforeChange(config, { dryRun: effectiveDryRun });
|
|
2392
|
+
if (carried?.preview) {
|
|
2393
|
+
process.stderr.write(`[dry-run] \`runlist ${command}\` then runs on the moved prompts; nothing else is previewed until they have moved.\n`);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
if (carried) {
|
|
2397
|
+
restArgs = restArgs.map(arg => {
|
|
2398
|
+
const move = carried.moves.find(item => [process.cwd(), config.repoRoot].some(base => path.resolve(base, arg) === item.source));
|
|
2399
|
+
return move ? move.target : arg;
|
|
2400
|
+
});
|
|
2401
|
+
// Run from a subfolder, the docs root is that subfolder, and the moved
|
|
2402
|
+
// prompts are in docs/prompts/ at the project's top: the command reads
|
|
2403
|
+
// them there.
|
|
2404
|
+
if (path.resolve(config.docsRoot) !== path.resolve(config.repoRoot)) {
|
|
2405
|
+
const execution = config._execution;
|
|
2406
|
+
config = await resolveConfig(config.repoRoot);
|
|
2407
|
+
Object.defineProperty(config, '_execution', { value: execution, enumerable: false });
|
|
2408
|
+
_resolvedConfig = config;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2084
2413
|
// Init — runInit re-resolves the config from disk internally (after any
|
|
2085
2414
|
// starter-config write), so we don't need to pre-pass it.
|
|
2086
2415
|
if (command === 'init') {
|
|
2087
2416
|
const { runInit } = await import('../src/init.mjs');
|
|
2088
|
-
await runInit(process.cwd(), config, { dryRun });
|
|
2417
|
+
await runInit(process.cwd(), config, { dryRun, argv: restArgs });
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
if (command === 'config') {
|
|
2422
|
+
if (rootArg || typeArg) die('Config inspection does not use --root/--type query filters. Inspect root with runlist config get root; edit project layout manually.');
|
|
2423
|
+
const { runConfig } = await import('../src/config-command.mjs');
|
|
2424
|
+
await runConfig(restArgs, config, { dryRun });
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
if (command === 'setup') {
|
|
2429
|
+
if (rootArg || typeArg) die('Setup does not use --root/--type query filters. Choose a fresh docs folder with --docs-root; existing configured roots are preserved.');
|
|
2430
|
+
const { runSetup } = await import('../src/setup.mjs');
|
|
2431
|
+
await runSetup(restArgs, config, { dryRun });
|
|
2089
2432
|
return;
|
|
2090
2433
|
}
|
|
2091
2434
|
|
|
@@ -2098,8 +2441,11 @@ async function main() {
|
|
|
2098
2441
|
// runlist isn't used, so don't nag them about a missing config (they no-op
|
|
2099
2442
|
// cleanly on their own). The warning is still useful for interactive commands.
|
|
2100
2443
|
const HOOK_COMMANDS = new Set(['hud', 'guard']);
|
|
2101
|
-
|
|
2102
|
-
|
|
2444
|
+
// A prompt saved here creates a handoffs-only config itself (src/handoffs-mode.mjs).
|
|
2445
|
+
const savesPrompt = command === 'baton' || (command === 'new' && restArgs.find(a => !a.startsWith('-')) === 'prompt')
|
|
2446
|
+
|| (command === 'prompts' && restArgs[0] === 'new');
|
|
2447
|
+
if (!config.configFound && command !== 'init' && !HOOK_COMMANDS.has(command) && !savesPrompt) {
|
|
2448
|
+
warn('No runlist config found — using defaults. Run `runlist setup` to create one and carry over saved prompts and plans.');
|
|
2103
2449
|
}
|
|
2104
2450
|
|
|
2105
2451
|
if (config.configWarnings && config.configWarnings.length > 0) {
|
|
@@ -2226,7 +2572,7 @@ async function main() {
|
|
|
2226
2572
|
// src/baton.mjs for why this is a single verb and not a skill choreography.
|
|
2227
2573
|
if (command === 'baton') {
|
|
2228
2574
|
const { runBaton } = await import('../src/baton.mjs');
|
|
2229
|
-
await runBaton(restArgs, config, { dryRun });
|
|
2575
|
+
await runBaton(restArgs, config, { dryRun, verbose });
|
|
2230
2576
|
return;
|
|
2231
2577
|
}
|
|
2232
2578
|
// `runlist next` is a top-level alias for `runlist use` with no arg — consume
|
|
@@ -2289,9 +2635,11 @@ async function main() {
|
|
|
2289
2635
|
if (command === 'misuse') { const { runMisuse } = await import('../src/misuse-read.mjs'); runMisuse(restArgs, config); return; }
|
|
2290
2636
|
if (command === 'journal') { const { runJournal } = await import('../src/journal-read.mjs'); runJournal(restArgs, config); return; }
|
|
2291
2637
|
if (command === 'pickup' || command === 'unpickup' || command === 'release' || command === 'finish') {
|
|
2638
|
+
if (config.mode === 'handoffs') die(`\`runlist ${command}\` was removed. Handoffs: \`runlist baton <slug> @<file>\` saves one, \`runlist use <name>\` resumes one, \`runlist prompts\` lists them.`);
|
|
2292
2639
|
die(`\`runlist ${command}\` was removed — use the ownership-aware lifecycle verbs:\n runlist use <file> # atomically claim + mark in-session + print the plan\n runlist set <status> <file> # transition and release ownership when leaving in-session\n runlist archive <file> # close out atomically`);
|
|
2293
2640
|
}
|
|
2294
2641
|
if (command === 'runlist') { const { runRunlist } = await import('../src/runlist.mjs'); await runRunlist(restArgs, config, { dryRun }); return; }
|
|
2642
|
+
if (command === 'handoff' && config.mode === 'handoffs') die('`runlist handoff` was removed. Handoffs: `runlist baton <slug> @<file>` saves one, `runlist use <name>` resumes one, `runlist prompts` lists them.');
|
|
2295
2643
|
if (command === 'handoff') { die('`runlist handoff` was removed in 0.31.0. Use `runlist prompts new <name>` to create a saved prompt instead. The .dotmd/handoffs/ sidecar mechanism no longer exists; see CHANGELOG.'); }
|
|
2296
2644
|
if (command === 'status') { const { runStatus } = await import('../src/lifecycle.mjs'); await runStatus(restArgs, config, { dryRun }); return; }
|
|
2297
2645
|
if (command === 'set') { const { runSet } = await import('../src/lifecycle.mjs'); await runSet(restArgs, config, { dryRun }); return; }
|
|
@@ -2529,6 +2877,7 @@ async function main() {
|
|
|
2529
2877
|
|
|
2530
2878
|
if (command === 'index') {
|
|
2531
2879
|
if (!config.indexPath) {
|
|
2880
|
+
if (config.mode === 'handoffs') die('There is no generated index: planning is off in this project. `runlist setup` turns planning on and adds one (your prompts stay where they are).');
|
|
2532
2881
|
die('Index generation is not configured. Add an `index` section to your runlist.config.mjs.');
|
|
2533
2882
|
}
|
|
2534
2883
|
const print = args.includes('--print');
|
|
@@ -2625,6 +2974,7 @@ async function main() {
|
|
|
2625
2974
|
}, null, 2) + '\n');
|
|
2626
2975
|
} else {
|
|
2627
2976
|
process.stdout.write(renderBriefing(index, config));
|
|
2977
|
+
if (config.mode === 'handoffs') await writePlanningOffLine(config);
|
|
2628
2978
|
}
|
|
2629
2979
|
return;
|
|
2630
2980
|
}
|
|
@@ -2696,6 +3046,7 @@ async function main() {
|
|
|
2696
3046
|
return;
|
|
2697
3047
|
}
|
|
2698
3048
|
process.stdout.write(renderContext(index, config, { summarize, model }));
|
|
3049
|
+
if (config.mode === 'handoffs') await writePlanningOffLine(config);
|
|
2699
3050
|
return;
|
|
2700
3051
|
}
|
|
2701
3052
|
|