runlist 0.98.2 → 0.99.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 CHANGED
@@ -71,8 +71,8 @@ controls are experimental; live retrieval depends on a compatible external
71
71
  provider. Local chat and push are deferred.
72
72
 
73
73
  <!-- runlist:release:start -->
74
- CLI and desktop release version: **0.98.2**.
75
- [Mac installer and release notes](https://github.com/reowens/runlist/releases/tag/desktop-v0.98.2).
74
+ CLI and desktop release version: **0.99.0**.
75
+ [Mac installer and release notes](https://github.com/reowens/runlist/releases/tag/desktop-v0.99.0).
76
76
  Normal product releases publish both components; an installed Mac app is updated with rollback after it has quit.
77
77
  <!-- runlist:release:end -->
78
78
 
@@ -803,6 +803,12 @@ excludes session prompts from `paths`.
803
803
  Saved prompts are local session state. Consume them with `runlist use`; inspect
804
804
  without consuming via `runlist prompts show`. Consuming a baton prompt also claims
805
805
  its plan; `runlist use --no-claim` reads and archives it without starting the plan.
806
+ Only an agent session consumes: Claude Code, Codex, OpenCode, or a shell with
807
+ `RUNLIST_SESSION_ID` set. In a plain terminal, `runlist use` prints the body and a
808
+ closing line naming the `runlist prompts use <path>` the session runs, and leaves
809
+ the prompt pending and its plan unclaimed. So `claude "$(runlist use)"` starts a
810
+ session that consumes the prompt itself, and a session that never starts takes
811
+ nothing.
806
812
 
807
813
  New plans are created `planned`; `runlist use` starts one, and
808
814
  `runlist new plan <name> --status <status>` sets a different starting status.
package/bin/runlist.mjs CHANGED
@@ -119,7 +119,8 @@ Common commands:
119
119
  set <status> [file] Transition status (start work, finish, archive — all via target status)
120
120
  new <type> <name> Create plan/doc/prompt (pipe stdin or @path for body)
121
121
  use [<file-or-slug>] Open a doc by type: prompt → consume, plan → start, doc → read
122
- (no file: consume oldest pending prompt)
122
+ (no file: consume oldest pending prompt; outside an
123
+ agent session a prompt is printed, not consumed)
123
124
  baton [<plan>|<slug>] @<draft-file>
124
125
  Save a resume prompt (+ release the plan, if one is in-session)
125
126
  archive <file> Close out a plan (status → archived, move, update refs)
@@ -422,6 +423,8 @@ prompt statuses
422
423
  pending Ready for the next session to consume.
423
424
  \`runlist prompts use <file>\` prints body + archives atomically.
424
425
  \`runlist prompts next\` does the same for the oldest pending.
426
+ Outside an agent session both only print, and the
427
+ prompt stays pending.
425
428
 
426
429
  archived Consumed prompt; body preserved in the archive directory,
427
430
  which is the only directory a prompt ever moves into.
@@ -1284,6 +1287,11 @@ Prompts are documents with \`type: prompt\`, typically saved under
1284
1287
  docs/prompts/. They seed future Claude sessions; consuming a prompt
1285
1288
  prints its body to stdout and atomically archives it (one-shot).
1286
1289
 
1290
+ Only an agent session consumes (Claude Code, Codex, OpenCode, or a shell
1291
+ with RUNLIST_SESSION_ID set). In a plain terminal \`use\`, \`resume\` and
1292
+ \`next\` print the body and a closing line naming the command the session
1293
+ runs to consume it; the prompt stays pending and its plan unclaimed.
1294
+
1287
1295
  \`runlist prompt\` (singular) is an alias for \`runlist prompts\` — every
1288
1296
  subcommand below works under either spelling.
1289
1297
 
@@ -1330,10 +1338,11 @@ Examples:
1330
1338
  runlist prompts list --status claimed # already-consumed prompts
1331
1339
  runlist prompts --json # JSON output
1332
1340
 
1333
- claude "$(runlist prompts next)" # consume oldest pending + run claude
1334
- claude "$(runlist prompts use resume-foo)" # by slug
1335
- claude "$(runlist prompts use docs/prompts/foo.md)" # by path
1336
- claude "$(runlist prompts resume resume-foo)" # \`resume\` is an alias for \`use\`
1341
+ runlist prompts use resume-foo # in a session: consume by slug
1342
+ runlist prompts use docs/prompts/foo.md # by path
1343
+ runlist prompts resume resume-foo # \`resume\` is an alias for \`use\`
1344
+ claude "$(runlist prompts next)" # from a terminal: start claude on the oldest
1345
+ # pending; the session consumes it itself
1337
1346
  runlist prompt list # singular alias for \`runlist prompts list\`
1338
1347
 
1339
1348
  runlist prompts show resume-foo # peek without consuming (triage)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runlist",
3
- "version": "0.98.2",
3
+ "version": "0.99.0",
4
4
  "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, lifecycle, and AI summaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runlist-codex",
3
- "version": "0.98.2",
3
+ "version": "0.99.0",
4
4
  "description": "Runlist workflow skill, session orientation, and tool guard for Codex",
5
5
  "author": {
6
6
  "name": "Reo Owens"
@@ -20,7 +20,7 @@ Use this skill in repositories with `runlist.config.mjs` or `dotmd.config.mjs`.
20
20
  - **Subagent handoffs:** use `runlist baton --subagent <task-slug> @/tmp/draft.md` to save `subagent-<task-slug>`. Under a parent-owned plan, choose a distinct task slug that does not resolve to a plan; do not release the parent's claim. A child owning a dedicated plan can use `runlist baton --subagent <plan-file> @/tmp/draft.md`. Add `--parent resume-<session-name>` to link the child beneath its parent in `rl prompts`; the parent may be saved later. Parent-session batons keep the default `resume-*` naming. Matching prefixes are kept once; conflicting prefixes are refused. Existing prompts keep their names, and `--subagent --replace` refuses a parent-prefixed prompt.
21
21
  - **Draft outlines:** `runlist baton --template` prints the resume outline. The desktop Templates editor saves resume/subagent outlines in `runlist.templates.json`; repository baton instructions still apply.
22
22
  - **Child draft:** `runlist baton --subagent --template --parent resume-<name>` prints the saved child outline (built-in: Task, State, Next, Evidence and Boundaries). Fill the draft; its `parent_prompt` travels on save. A launcher can pass `RUNLIST_PARENT_PROMPT` into the child environment instead. Keep full records at durable repo-relative paths with commits and checked results; temporary scratch paths are supplementary. Completed work is a result for the parent to record, not another restart. Repair old links without replacing text: `runlist prompts link <child...> --parent resume-<name>`. Showing or consuming a parent surfaces its pending children without consuming them.
23
- - Consume a saved prompt with `runlist use <prompt-file>` (or bare `runlist use` for the oldest). This archives and claims before printing the body. For read-only triage, use `runlist prompts show <file>` or `runlist prompts show --all`; never open pending prompts with a file reader or `cat`.
23
+ - Consume a saved prompt with `runlist use <prompt-file>` (or bare `runlist use` for the oldest). This archives and claims before printing the body. Only an agent session consumes; a plain terminal gets the body plus the `runlist prompts use <path>` line, and the prompt stays pending. A prompt that ends with that line is still pending: run the command first. For read-only triage, use `runlist prompts show <file>` or `runlist prompts show --all`; never open pending prompts with a file reader or `cat`.
24
24
  - Pending prompts are session-local. Commit the tracked plan or source files named by baton, not `docs/prompts/*.md`.
25
25
  - Create a document with `runlist new <type> <name> @<draft-file>`. A draft starting with frontmatter can supply scaffold fields; `type` is fixed by the command.
26
26
  - Record a decision with `runlist new decision <plan> --question "…" @<record-file>`; configured registers may also require `--answers`.
@@ -1,11 +1,12 @@
1
1
  // Keep the service and worker on the same terminal-qualified matrix.
2
+ // macOS evidence: Homebrew Git 2.56.0 passed the guarded-commit suite on ARM64.
2
3
  // Linux evidence: Debian 13 ARM64 / Git 2.47.3, pinned Node 24.21.0.
3
4
  export function commitPlatformQualified(platform=process.platform,arch=process.arch){
4
5
  return arch==='arm64'&&(platform==='darwin'||platform==='linux');
5
6
  }
6
7
  export function commitVersionQualified(version,platform=process.platform,arch=process.arch){
7
8
  if(!commitPlatformQualified(platform,arch))return false;
8
- return platform==='darwin'?/^git version 2\.(54|55)\.0(?: |$)/.test(version):version==='git version 2.47.3';
9
+ return platform==='darwin'?/^git version 2\.(54|55|56)\.0(?: |$)/.test(version):version==='git version 2.47.3';
9
10
  }
10
11
  export function localCommitSupport(version,platform=process.platform,arch=process.arch){
11
12
  const available=commitVersionQualified(version,platform,arch);
package/src/prompts.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { extractFrontmatter, parseSimpleFrontmatter } from './frontmatter.mjs';
4
- import { asString, toRepoPath, die, resolveDocPath, resolveRefPath, isArchivedPath } from './util.mjs';
4
+ import { agentSessionSource, asString, toRepoPath, die, resolveDocPath, resolveRefPath, isArchivedPath } from './util.mjs';
5
5
  import { buildIndex, resolveDocArg } from './index.mjs';
6
6
  import { runQuery } from './query.mjs';
7
7
  import { completePlanClaim, regenIndex, renderLifecycleMutation, runArchive } from './lifecycle.mjs';
@@ -372,6 +372,13 @@ export async function consumePrompt(filePath, config, opts) {
372
372
  }
373
373
  const family = childHandoffs(repoPath, config);
374
374
 
375
+ // Only an agent session consumes. A plain terminal (`claude "$(runlist use)"`,
376
+ // or someone looking) gets the body and the command the session runs, so a
377
+ // session that never starts takes nothing and claims no plan.
378
+ if (!dryRun && !agentSessionSource(opts.env ?? process.env)) {
379
+ return printUnconsumedPrompt(body + family.notice, repoPath, status);
380
+ }
381
+
375
382
  const planRef = asString(parsed.plan);
376
383
  let linkedClaim = null;
377
384
  let claimSkipReason = null;
@@ -401,8 +408,8 @@ export async function consumePrompt(filePath, config, opts) {
401
408
 
402
409
  // Archive BEFORE emitting the body. If runArchive throws (git mv failure,
403
410
  // hook crash, anything), the body must not have already gone to stdout —
404
- // otherwise `claude "$(runlist prompts next)"` consumes the prompt without it
405
- // ever being archived, and the next session sees the same prompt as pending.
411
+ // otherwise the session reads a prompt that was never archived, and the next
412
+ // session sees the same prompt as pending.
406
413
  const archiveResult = runArchive([filePath], config, {
407
414
  noIndex,
408
415
  showFiles,
@@ -476,6 +483,24 @@ export async function consumePrompt(filePath, config, opts) {
476
483
  };
477
484
  }
478
485
 
486
+ export function consumeInstruction(repoPath) {
487
+ return `This prompt is still pending: runlist consumes a prompt only inside an agent session. The session that works on it runs \`runlist prompts use ${repoPath}\` first.`;
488
+ }
489
+
490
+ async function printUnconsumedPrompt(body, repoPath, status) {
491
+ const content = `${body}${body.endsWith('\n') ? '' : '\n'}\n${consumeInstruction(repoPath)}\n`;
492
+ await new Promise((resolve, reject) => process.stdout.write(content, err => err ? reject(err) : resolve()));
493
+ return {
494
+ operation: 'print',
495
+ status: { from: status ?? null, to: status ?? null, changed: false },
496
+ repositoryFiles: [],
497
+ sessionFiles: [],
498
+ generatedFiles: [],
499
+ deferredGeneratedFiles: [],
500
+ claim: null,
501
+ };
502
+ }
503
+
479
504
  function linkedPlanRepoPath(planRef, promptPath, config) {
480
505
  const abs = resolveRefPath(planRef, path.dirname(promptPath), config.repoRoot);
481
506
  return abs && existsSync(abs) ? toRepoPath(abs, config.repoRoot) : null;
package/src/util.mjs CHANGED
@@ -52,6 +52,17 @@ export function hostSessionSource(env = process.env) {
52
52
  return null;
53
53
  }
54
54
 
55
+ // The agent session runlist runs inside, or null in a plain terminal. A
56
+ // terminal id names a window, not an agent, so it does not count: consuming a
57
+ // prompt there would archive it with no session to read it.
58
+ export function agentSessionSource(env = process.env) {
59
+ const source = hostSessionSource(env);
60
+ return source && source.scope !== 'terminal' ? source : null;
61
+ }
62
+
63
+ // Every variable that can name a session, for a caller that must clear them all.
64
+ export const SESSION_ID_VARIABLES = SESSION_ID_SOURCES.map(source => source.variable);
65
+
55
66
  // The session id the environment names, or null when it names none.
56
67
  export function hostSessionId(env = process.env) {
57
68
  return hostSessionSource(env)?.id ?? null;