runlist 0.0.0 → 0.94.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1112 -6
  3. package/assets/app/app.css +283 -0
  4. package/assets/app/app.mjs +381 -0
  5. package/assets/app/block-editor.mjs +182 -0
  6. package/assets/app/block-model.mjs +52 -0
  7. package/assets/app/document-create.mjs +86 -0
  8. package/assets/app/document-lifecycle.mjs +36 -0
  9. package/assets/app/document-stages.mjs +22 -0
  10. package/assets/app/document-views.mjs +33 -0
  11. package/assets/app/document-yardstick.mjs +258 -0
  12. package/assets/app/editor-navigation.mjs +51 -0
  13. package/assets/app/filing-navigation.mjs +138 -0
  14. package/assets/app/git-changes.mjs +80 -0
  15. package/assets/app/git-commit.mjs +46 -0
  16. package/assets/app/index.html +70 -0
  17. package/assets/app/library-navigation.mjs +93 -0
  18. package/assets/app/outline.mjs +18 -0
  19. package/assets/app/quick-navigation.mjs +52 -0
  20. package/assets/app/record-navigation.mjs +78 -0
  21. package/assets/app/recovery-center.mjs +53 -0
  22. package/assets/app/semantic-search.mjs +14 -0
  23. package/assets/app/settings.mjs +15 -0
  24. package/assets/app/shared.mjs +104 -0
  25. package/assets/app/stage-source.mjs +75 -0
  26. package/assets/app/template-editor.mjs +47 -0
  27. package/assets/app/theme.mjs +25 -0
  28. package/assets/app/transport.mjs +43 -0
  29. package/assets/contracts/source-editing-v1.md +232 -0
  30. package/assets/opencode/plugin.js +162 -0
  31. package/assets/schemas/runlist-record-v1.schema.json +912 -0
  32. package/bin/dotmd.mjs +5 -0
  33. package/bin/runlist.mjs +2714 -0
  34. package/package.json +53 -6
  35. package/plugins/runlist-codex/.codex-plugin/plugin.json +18 -0
  36. package/plugins/runlist-codex/bin/runlist-hook +86 -0
  37. package/plugins/runlist-codex/hooks/hooks.json +16 -0
  38. package/plugins/runlist-codex/skills/runlist/SKILL.md +25 -0
  39. package/runlist.config.example.mjs +370 -0
  40. package/scripts/postinstall.mjs +56 -0
  41. package/src/agent-context.mjs +201 -0
  42. package/src/ai.mjs +30 -0
  43. package/src/app-create.mjs +169 -0
  44. package/src/app-filing.mjs +139 -0
  45. package/src/app-git-commits.mjs +48 -0
  46. package/src/app-git.mjs +180 -0
  47. package/src/app-library.mjs +152 -0
  48. package/src/app-lifecycle-worker.mjs +12 -0
  49. package/src/app-lifecycle.mjs +79 -0
  50. package/src/app-records.mjs +134 -0
  51. package/src/app-search.mjs +80 -0
  52. package/src/app-semantic-search.mjs +105 -0
  53. package/src/app-service.mjs +208 -0
  54. package/src/app-yardstick.mjs +269 -0
  55. package/src/app.mjs +102 -0
  56. package/src/atomic-mutation.mjs +1736 -0
  57. package/src/baton.mjs +435 -0
  58. package/src/body-link.mjs +48 -0
  59. package/src/bulk-tag.mjs +174 -0
  60. package/src/check-collapse.mjs +75 -0
  61. package/src/claude-commands.mjs +75 -0
  62. package/src/cli-editor.mjs +8 -0
  63. package/src/code-refs.mjs +545 -0
  64. package/src/codex-integration.mjs +81 -0
  65. package/src/color.mjs +14 -0
  66. package/src/commands.mjs +374 -0
  67. package/src/completions.mjs +93 -0
  68. package/src/config-edit.mjs +606 -0
  69. package/src/config.mjs +705 -0
  70. package/src/decision-action.mjs +58 -0
  71. package/src/decision.mjs +220 -0
  72. package/src/decisions.mjs +787 -0
  73. package/src/deps.mjs +317 -0
  74. package/src/desktop-install.mjs +158 -0
  75. package/src/desktop-runtime.mjs +4 -0
  76. package/src/desktop.mjs +25 -0
  77. package/src/diff.mjs +101 -0
  78. package/src/doctor.mjs +591 -0
  79. package/src/durable-rename.mjs +55 -0
  80. package/src/errors-read.mjs +44 -0
  81. package/src/export.mjs +496 -0
  82. package/src/extractors.mjs +101 -0
  83. package/src/filing.mjs +255 -0
  84. package/src/fix-membership.mjs +134 -0
  85. package/src/fix-refs.mjs +174 -0
  86. package/src/flags.mjs +318 -0
  87. package/src/frontmatter-fix.mjs +195 -0
  88. package/src/frontmatter.mjs +252 -0
  89. package/src/git-commit-filters.mjs +32 -0
  90. package/src/git-commit-protocol.mjs +198 -0
  91. package/src/git-commit-signing.mjs +75 -0
  92. package/src/git-commit-store.mjs +26 -0
  93. package/src/git-commit-support.mjs +16 -0
  94. package/src/git-commit-worker.mjs +32 -0
  95. package/src/git.mjs +909 -0
  96. package/src/glossary-check.mjs +35 -0
  97. package/src/glossary.mjs +243 -0
  98. package/src/graph.mjs +298 -0
  99. package/src/guard.mjs +488 -0
  100. package/src/health.mjs +212 -0
  101. package/src/hints.mjs +129 -0
  102. package/src/host-integration.mjs +339 -0
  103. package/src/hub-membership.mjs +284 -0
  104. package/src/hub.mjs +296 -0
  105. package/src/hud.mjs +436 -0
  106. package/src/index-file.mjs +163 -0
  107. package/src/index.mjs +478 -0
  108. package/src/init.mjs +512 -0
  109. package/src/install.mjs +222 -0
  110. package/src/journal-read.mjs +116 -0
  111. package/src/journal.mjs +472 -0
  112. package/src/lifecycle.mjs +1444 -0
  113. package/src/lint.mjs +386 -0
  114. package/src/managed-path.mjs +192 -0
  115. package/src/markdown-code-spans.mjs +58 -0
  116. package/src/migrate-prompts.mjs +171 -0
  117. package/src/migrate-template.mjs +170 -0
  118. package/src/migrate.mjs +90 -0
  119. package/src/misuse-read.mjs +61 -0
  120. package/src/model-request.mjs +33 -0
  121. package/src/model.mjs +523 -0
  122. package/src/modules.mjs +355 -0
  123. package/src/naming.mjs +55 -0
  124. package/src/native-action.mjs +59 -0
  125. package/src/native-record.mjs +244 -0
  126. package/src/new.mjs +1321 -0
  127. package/src/output-identity.mjs +111 -0
  128. package/src/parent-plan.mjs +58 -0
  129. package/src/parse-cache.mjs +99 -0
  130. package/src/path-containment.mjs +15 -0
  131. package/src/pickup-card.mjs +293 -0
  132. package/src/pickup.mjs +671 -0
  133. package/src/prompt.mjs +25 -0
  134. package/src/prompts.mjs +585 -0
  135. package/src/public-inspection.mjs +19 -0
  136. package/src/query.mjs +977 -0
  137. package/src/record-action.mjs +11 -0
  138. package/src/record-create.mjs +82 -0
  139. package/src/reference-planner.mjs +388 -0
  140. package/src/reference-values.mjs +11 -0
  141. package/src/rename.mjs +107 -0
  142. package/src/render.mjs +678 -0
  143. package/src/roadmap.mjs +184 -0
  144. package/src/runlist.mjs +944 -0
  145. package/src/section.mjs +225 -0
  146. package/src/semantic-search-client.mjs +58 -0
  147. package/src/ship.mjs +166 -0
  148. package/src/show.mjs +101 -0
  149. package/src/skill-drift.mjs +98 -0
  150. package/src/source-editor.mjs +491 -0
  151. package/src/stages.mjs +73 -0
  152. package/src/state-migration.mjs +123 -0
  153. package/src/stats.mjs +200 -0
  154. package/src/status-metadata.mjs +87 -0
  155. package/src/statuses.mjs +721 -0
  156. package/src/summary.mjs +74 -0
  157. package/src/surfaces.mjs +28 -0
  158. package/src/sync-status.mjs +316 -0
  159. package/src/template-store.mjs +62 -0
  160. package/src/update.mjs +338 -0
  161. package/src/use.mjs +58 -0
  162. package/src/util.mjs +337 -0
  163. package/src/validate.mjs +795 -0
  164. package/src/watch.mjs +56 -0
  165. package/src/xref.mjs +406 -0
  166. package/src/yardstick-command.mjs +178 -0
  167. package/src/yardstick.mjs +112 -0
package/package.json CHANGED
@@ -1,10 +1,57 @@
1
1
  {
2
2
  "name": "runlist",
3
- "version": "0.0.0",
4
- "description": "Plan lifecycle, ranked work queues and session handoff for markdown corpora. Placeholder — the tool currently ships as dotmd-cli and is being renamed here.",
5
- "keywords": ["markdown", "plans", "frontmatter", "cli", "documentation"],
6
- "license": "MIT",
7
- "author": "Robert Owens",
3
+ "version": "0.94.2",
4
+ "description": "CLI for managing markdown documents with YAML frontmatter — index, query, validate, graph, export, lifecycle, and AI summaries.",
8
5
  "type": "module",
9
- "files": ["README.md"]
6
+ "license": "MIT",
7
+ "bin": {
8
+ "runlist": "bin/runlist.mjs",
9
+ "rl": "bin/runlist.mjs",
10
+ "dotmd": "bin/dotmd.mjs"
11
+ },
12
+ "exports": {
13
+ ".": "./src/index.mjs"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "src/",
18
+ "assets/",
19
+ "plugins/runlist-codex/",
20
+ "scripts/postinstall.mjs",
21
+ "runlist.config.example.mjs"
22
+ ],
23
+ "keywords": [
24
+ "markdown",
25
+ "frontmatter",
26
+ "cli",
27
+ "docs",
28
+ "plans",
29
+ "adr",
30
+ "rfc",
31
+ "document-management",
32
+ "yaml",
33
+ "graph",
34
+ "export",
35
+ "ai-summary",
36
+ "validation",
37
+ "lifecycle"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/reowens/runlist.git"
42
+ },
43
+ "homepage": "https://github.com/reowens/runlist#readme",
44
+ "scripts": {
45
+ "check:public": "node scripts/check-public.mjs",
46
+ "prepack": "node scripts/check-public.mjs",
47
+ "test": "node --import ./test/setup-env.mjs --test --test-concurrency=4 test/*.test.mjs",
48
+ "postinstall": "node scripts/postinstall.mjs",
49
+ "preversion": "node scripts/release-preflight.mjs && npm test",
50
+ "version": "node scripts/prepare-version-commit.mjs",
51
+ "postversion": "bash scripts/postversion.sh",
52
+ "release:resume": "node scripts/recover-local-release.mjs && bash scripts/postversion.sh"
53
+ },
54
+ "engines": {
55
+ "node": ">=20"
56
+ }
10
57
  }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "runlist-codex",
3
+ "version": "0.94.2",
4
+ "description": "Runlist workflow skill, session orientation, and tool guard for Codex",
5
+ "author": {
6
+ "name": "Reo Owens"
7
+ },
8
+ "skills": "./skills/",
9
+ "interface": {
10
+ "displayName": "Runlist Codex",
11
+ "shortDescription": "Plan, handoff, and document workflow for Codex.",
12
+ "longDescription": "Adds a concise runlist skill, session primer, and prompt/status guard hooks for managed Markdown repositories.",
13
+ "developerName": "Reo Owens",
14
+ "category": "Productivity",
15
+ "capabilities": [],
16
+ "defaultPrompt": "Show my runlist plans and pending handoffs."
17
+ }
18
+ }
@@ -0,0 +1,86 @@
1
+ #!/bin/sh
2
+ # runlist Codex plugin hook wrapper.
3
+ #
4
+ # The plugin's hooks drive the whole workflow through the PATH `runlist` binary
5
+ # (`runlist hud`, `runlist guard`), falling back to the legacy `runlist` alias. If a user enables the plugin but hasn't run
6
+ # `npm i -g runlist`, the binary is missing, the hook errors `command not
7
+ # found`, and the session is primed with *silent nothing* — no signal that the
8
+ # whole workflow is one install away. runlist can't warn about its own absence
9
+ # (it's the thing that's missing), so the check lives here, outside the binary.
10
+ #
11
+ # Usage: runlist-hook [--hint] <runlist-subcommand> [args...]
12
+ # --hint emit a one-line install hint to stdout when no CLI is on PATH.
13
+ # Only SessionStart passes this, so the hint surfaces once per hook
14
+ # invocation (stdout becomes session context), never on a tool call.
15
+ #
16
+ # When the CLI is missing we always exit 0 with no further output: hooks must
17
+ # stay non-blocking, and the PreToolUse guard in particular must never deny a
18
+ # tool just because the optional CLI isn't installed.
19
+ hint=0
20
+ if [ "$1" = "--hint" ]; then
21
+ hint=1
22
+ shift
23
+ fi
24
+
25
+ if command -v runlist >/dev/null 2>&1; then
26
+ CLI=runlist
27
+ elif command -v dotmd >/dev/null 2>&1; then
28
+ CLI=dotmd
29
+ else
30
+ if [ "$hint" = "1" ]; then
31
+ echo "runlist plugin: neither \`runlist\` nor \`dotmd\` is on PATH, so its hooks are no-ops. Enable the workflow with: npm i -g runlist"
32
+ fi
33
+ exit 0
34
+ fi
35
+
36
+ # PreToolUse fast path: the guard fires on matched Bash, apply_patch, and MCP
37
+ # read calls, and
38
+ # a full Node boot (~100ms: CLI startup + config discovery) is pure overhead for
39
+ # the vast majority of payloads no guard rule can possibly match. Every rule
40
+ # needs a `.md` path AND either a prompts/ path (commit/cat/read-prompt) or the
41
+ # word `status` (edit-status) somewhere in the payload — a cheap shell glob
42
+ # decides that without leaving sh. Anything else gets the no-opinion reply
43
+ # (`{}`) directly. False positives here just pay the old cost (Node runs and
44
+ # returns {}); false negatives are impossible because the globs are strictly
45
+ # weaker than the rules they gate.
46
+ if [ "$1" = "guard" ]; then
47
+ payload=$(cat)
48
+ # Broad Git forms do not name a .md file in the payload; Node inspects the
49
+ # actual eligible/staged path set before deciding whether a live prompt is
50
+ # included. Always route those forms through the guard.
51
+ case "$payload" in
52
+ *git*add*|*git*stage*|*git*commit*)
53
+ printf '%s' "$payload" | "$CLI" "$@"
54
+ exit $?
55
+ ;;
56
+ esac
57
+ case "$payload" in
58
+ *.md*)
59
+ case "$payload" in
60
+ *prompts/*|*prompts\\*|*status*)
61
+ printf '%s' "$payload" | "$CLI" "$@"
62
+ exit $?
63
+ ;;
64
+ esac
65
+ ;;
66
+ esac
67
+ printf '{}\n'
68
+ exit 0
69
+ fi
70
+
71
+ # UserPromptSubmit fast path is retained for compatibility with the Claude
72
+ # wrapper; Codex does not register that hook here. It would fire on every
73
+ # message that asks for a handoff gets an answer. The glob is strictly weaker
74
+ # than the Node-side match, so a skipped payload could never have matched.
75
+ if [ "$1" = "hud" ] && [ "$2" = "--prompt-submit" ]; then
76
+ payload=$(cat)
77
+ case "$payload" in
78
+ *[Bb][Aa][Tt][Oo][Nn]*|*[Hh][Aa][Nn][Dd]*[Oo][Ff][Ff]*|*[Rr][Ee][Ss][Uu][Mm][Ee]*|*[Pp][Ii][Cc][Kk]*[Uu][Pp]*)
79
+ printf '%s' "$payload" | "$CLI" "$@"
80
+ exit 0
81
+ ;;
82
+ esac
83
+ exit 0
84
+ fi
85
+
86
+ exec "$CLI" "$@"
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "startup|resume|clear|compact",
6
+ "hooks": [{ "type": "command", "command": "sh \"${PLUGIN_ROOT}/bin/runlist-hook\" --hint hud", "timeout": 5 }]
7
+ }
8
+ ],
9
+ "PreToolUse": [
10
+ {
11
+ "matcher": "Bash|apply_patch|mcp__.*__(read_file|read_text_file|read_media_file|read_multiple_files)",
12
+ "hooks": [{ "type": "command", "command": "sh \"${PLUGIN_ROOT}/bin/runlist-hook\" guard", "timeout": 5 }]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: runlist
3
+ description: Manage a repository's plans, docs, decisions, and saved prompts with the runlist CLI. Use when starting or closing plan work, checking the plan queue, or creating or consuming a handoff.
4
+ ---
5
+
6
+ # Runlist workflow
7
+
8
+ Use this skill in repositories with `runlist.config.mjs` or `dotmd.config.mjs`. The CLI is `runlist` (from the `runlist` package; `dotmd` remains an alias). The session hook prints a short live orientation. `CODEX_THREAD_ID` is the session identity used for plan claims; do not replace it with a process ID.
9
+
10
+ **Document model:** a hub organizes related plans and supporting docs. Older docs and filenames call hubs *runlists*; this is the same document, not another level below a hub. A hub can be a coordination map or hold ordered plans in its `runlist:` array. A roadmap is an optional hub collecting other hubs and rolling up progress, with direct plan children also supported. Hubs currently use `type: plan` with hub metadata. Stage describes when a plan should ship, while status describes progress; neither adds a document level. Existing `runlist` commands, fields and output labels remain unchanged. See the repository README's “Hubs, Ordered Plans, And Roadmaps” section for examples.
11
+
12
+ **Optional filing policy:** with `export const filing = true` (or `{ categoryDepth: 3 }`), `check` warns on missing/multiple/uncategorized homes, while `coverage` lists direct/inherited homes and hub:line evidence. A home is a status-bearing table row whose first cell links the plan; mentions and order lists are references. A child without its own row can inherit a home through explicit `parent_plan:` ancestors. Categories come from headings or the containing hub's filing category. Scoping retains full evidence; incomplete scans suppress absence findings. Neither command assigns homes or repairs rows. Enable the policy or retire an older custom check only with repository authorization. Available in CLI 0.93.0.
13
+
14
+ - Orient with `runlist plans`. Use `runlist agent-context` for structured state; `runlist briefing` is the comprehensive view and can be large.
15
+ - Start plan work with `runlist use <plan-file>`. It claims the plan, marks it `in-session`, and prints the card.
16
+ - Change status with `runlist set <status> <file> [--note "why"]`; this runs validation, lifecycle hooks, ref repairs, and index updates. Never hand-edit a `status:` field.
17
+ - Close a shipped plan with `runlist archive <file>`; use `partial`, `active`, `awaiting`, or `blocked` when those match the actual state. Valid statuses are repository and type specific: `runlist statuses list --type plan`.
18
+ - For a handoff, write a concrete resume draft, then run `runlist baton [<plan-or-slug>] @<draft-file>`. An owned plan is released in the same operation. If a handoff for that work is pending, inspect it with `runlist prompts show <file>`; keep it if current or pass `--replace` with the new draft. Replacement archives the old text and refreshes exactly one pending prompt.
19
+ - 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`.
20
+ - Pending prompts are session-local. Commit the tracked plan or source files named by baton, not `docs/prompts/*.md`.
21
+ - 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.
22
+ - Record a decision with `runlist new decision <plan> --question "…" @<record-file>`; configured registers may also require `--answers`.
23
+ - If a lifecycle mutation reports an abandoned transaction, inspect `runlist doctor --transactions` before retrying. Use `--apply` only for cases the doctor identifies as recoverable.
24
+
25
+ The `PreToolUse` hook warns on direct pending-prompt reads and denies hand-edited status transitions or Git operations that would include a live prompt. A repo can opt into blocking direct reads of existing pending prompts with `guard: { promptReads: 'deny' }`. Archived prompts and `runlist prompts show` remain readable. Codex requires explicit trust of a plugin's hooks before they run.
@@ -0,0 +1,370 @@
1
+ // runlist.config.mjs — document management configuration
2
+ // All exports are optional. Omitted values use built-in defaults.
3
+ // Place this file at the root of your project.
4
+
5
+ // ─── Static Config ───────────────────────────────────────────────────────────
6
+
7
+ // Directory containing your markdown docs (relative to this config file)
8
+ export const root = 'docs';
9
+
10
+ // Optional owner-written product goal. Read its Markdown body beside a plan's
11
+ // delivers: field with `runlist yardstick show <plan>` or on pickup cards.
12
+ // Explicit mark/close/fold commands record owner choices; no fit is inferred.
13
+ // Closing/folding uses the existing archive workflow. Leave unset to disable.
14
+ // export const yardstick = 'docs/product-yardstick.md';
15
+
16
+ // Optional plan-home policy. A home is a status-bearing hub table row whose
17
+ // first cell links the plan; prose/commentary links do not count. A child can
18
+ // inherit its parent's home through parent_plan:. Closed plans/hubs are excluded.
19
+ // Categories come from ### headings, or from a category where the containing
20
+ // hub is itself rowed. Use categoryDepth: 2..6 for another heading level.
21
+ // check reports warnings; coverage lists homes and locations. Neither chooses
22
+ // homes or rewrites documents. Leave unset/false to disable this policy.
23
+ // export const filing = true;
24
+ // export const filing = { categoryDepth: 3 };
25
+
26
+ // GUI native flag/decision creation can use explicit persisted identity/settings.
27
+ // Otherwise the first reviewed creation writes runlist.records.json with a
28
+ // generated (or uniquely existing) repo ID, a managed records root and shared:false.
29
+ // Replace the placeholder with a UUID v4. The root must be inside document roots.
30
+ // shared:true requires a working Git checkout and a non-ignored destination.
31
+ // If the JSON settings file also exists, these four values must agree with it.
32
+ // export const records = { schema: 1, repositoryId: 'repo:<UUID-v4>', root: 'docs/records', shared: false };
33
+
34
+ // Subdirectory for archived docs (used by `runlist archive` and `runlist status`)
35
+ export const archiveDir = 'archived';
36
+
37
+ // Directories to skip when scanning
38
+ export const excludeDirs = ['evidence'];
39
+
40
+ // Agent-host tool guard: direct reads of existing pending prompts warn by
41
+ // default. Set promptReads to 'deny' to block those reads before execution;
42
+ // `runlist prompts show` and archived prompt reads remain available.
43
+ // export const guard = { promptReads: 'deny' };
44
+
45
+ // Floor under the scan surface. `runlist check` fails when it scans fewer docs than
46
+ // this, so a broken root or an over-eager exclude can't read as a clean estate —
47
+ // zero errors and zero docs look identical otherwise. Off when unset. Set it well
48
+ // below your real count (round down hard); raise it as the corpus grows.
49
+ // Override for one run with `runlist check --min-docs <n>`; skipped for path-scoped
50
+ // checks, which are deliberate subsets.
51
+ // export const minDocs = 500;
52
+
53
+ // ─── Code references (`runlist refs`) ────────────────────────────────────────
54
+ //
55
+ // Archive and rename repair every reference inside the doc roots. These four
56
+ // keys extend that to source files that cite a document by its repo-relative
57
+ // path in a comment, a docstring or a string literal. With no `codeRoots` the
58
+ // scan never runs and nothing changes.
59
+ //
60
+ // `runlist refs <old> <new>` reports, `--fix` writes; `runlist refs repair`
61
+ // does the same for every archived document still cited at its previous path.
62
+ // Archive and rename print the count afterwards and take `--fix-refs`.
63
+ //
64
+ // Only the full repo-relative path matches, never a bare basename — a corpus
65
+ // cites slugs as words, and matching one is how a rename corrupts an
66
+ // unrelated line. Only the path segment is replaced, so a trailing `§` or `#`
67
+ // anchor, a backtick or a quote is kept as written.
68
+
69
+ // Repo-relative directories scanned for path-shaped citations. Empty = off.
70
+ // export const codeRoots = ['packages', 'scripts', 'services'];
71
+
72
+ // File kinds considered. An entry with no leading dot is an exact basename,
73
+ // which is how a file with no extension opts in. Unset = the built-in list.
74
+ // export const codeExtensions = ['.ts', '.tsx', '.mjs', '.swift', '.sql', '.py', '.sh', 'Dockerfile'];
75
+
76
+ // Never scanned. Vendored trees and generated output, where a rewrite is
77
+ // undone by the next codegen run and the citation belongs upstream.
78
+ // Unset = node_modules, dist/build, and the usual generated-output shapes.
79
+ // export const codeExcludes = ['**/node_modules/**', '**/*.generated.*', '**/__generated__/**'];
80
+
81
+ // Files whose content is data keyed on document paths — a guard baseline
82
+ // whose keys are plan paths, for instance. Reported, never written.
83
+ // export const codeRefsUntouched = ['scripts/guards/plan-baseline.json'];
84
+
85
+ // Document types — each type has its own status vocabulary and context layout.
86
+ // Defaults: plan, doc, prompt. Override to customize statuses per type, or add new types.
87
+ //
88
+ // Statuses can be defined as an array (names only) or as an object (rich form).
89
+ // The object form co-locates all behavioral properties with each status,
90
+ // eliminating the need for separate lifecycle, staleDays, context, and taxonomy sections.
91
+
92
+ // ─── Rich status definitions (recommended) ──────────────────────────────────
93
+ // Each status is an object with optional properties:
94
+ // context: 'expanded' | 'listed' | 'counted' (default: 'counted')
95
+ // staleDays: number | null — stale threshold (default: null = never stale)
96
+ // requiresModule: boolean — require `module` frontmatter (default: false)
97
+ // archive: boolean — auto-move to archiveDir on transition (default: false)
98
+ // terminal: boolean — closed state; excluded from active-work stats/coverage scope (default: false)
99
+ // skipStale: boolean — exempt from stale checks (default: false)
100
+ // skipWarnings: boolean — exempt from validation warnings (default: false)
101
+ // quiet: boolean — sugar for `skipStale: true, skipWarnings: true`. Use for visible-but-quiet
102
+ // statuses (e.g. `partial`) where you want no nagging but DO want them in scope.
103
+ // Setting `skipStale: false` or `skipWarnings: false` explicitly overrides the sugar.
104
+ //
105
+ // `terminal` and `quiet` are orthogonal. Mark a status `terminal` only when it represents closure
106
+ // (excluded from active-work scope). Use `quiet` for noise suppression without closure semantics.
107
+ //
108
+ // Contradiction check (since 0.36.2): runlist `warn()`s at config-load when a status combines
109
+ // `skipStale: true` with a `staleDays` value (the number is silently ignored) or
110
+ // `skipWarnings: true` with `requiresModule: true` (the module requirement can never fire).
111
+ // The same check applies via the `quiet: true` sugar. Drop one of the conflicting fields to silence.
112
+ //
113
+ // Each plan stop-status maps to a distinct unstuck-action — the test for whether
114
+ // the vocabulary earns its weight. blocked = monitor (external arrival on its own
115
+ // schedule), awaiting = ask (chase the human/decision), queued-after = check the
116
+ // predecessor, paused = re-evaluate, partial = spawn successor plans for the
117
+ // deferred tail.
118
+ //
119
+ // export const types = {
120
+ // plan: {
121
+ // statuses: {
122
+ // 'in-session': { context: 'expanded', staleDays: 1, requiresModule: true },
123
+ // 'active': { context: 'expanded', staleDays: 14, requiresModule: true },
124
+ // 'planned': { context: 'listed', staleDays: 30, requiresModule: true },
125
+ // 'blocked': { context: 'listed', staleDays: 30, requiresModule: true },
126
+ // 'partial': { context: 'expanded', requiresModule: true, quiet: true }, // shipped + deferred tail; visible, no nagging
127
+ // 'paused': { context: 'listed', staleDays: 3, requiresModule: true }, // stopped mid-work, near-term review — loud (short stale threshold)
128
+ // 'awaiting': { context: 'listed', staleDays: 14, requiresModule: true }, // human input/decision wait — NOT quiet (pings get forgotten)
129
+ // 'queued-after': { context: 'counted', requiresModule: true, quiet: true }, // sequenced behind another plan
130
+ // 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
131
+ // },
132
+ // },
133
+ // doc: {
134
+ // statuses: {
135
+ // 'draft': { context: 'listed', staleDays: 30 },
136
+ // 'active': { context: 'expanded', staleDays: 14 },
137
+ // 'review': { context: 'listed', staleDays: 14 },
138
+ // 'reference': { context: 'counted', skipStale: true },
139
+ // 'deprecated': { context: 'counted', terminal: true, skipStale: true },
140
+ // 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
141
+ // },
142
+ // },
143
+ // prompt: {
144
+ // // Saved prompts that seed future Claude sessions. `runlist hud` surfaces
145
+ // // pending prompts on session start; `runlist prompts next` claims the oldest.
146
+ // statuses: {
147
+ // 'pending': { context: 'expanded', staleDays: 30 },
148
+ // 'held': { context: 'counted', quiet: true }, // saved but not next: hidden from hud/briefing, skipped by no-arg `use`
149
+ // 'shelved': { context: 'counted', quiet: true }, // legacy alias for held
150
+ // 'claimed': { context: 'counted', quiet: true },
151
+ // 'archived': { context: 'counted', archive: true, terminal: true, quiet: true },
152
+ // },
153
+ // },
154
+ // };
155
+
156
+ // ─── Array form (also supported) ────────────────────────────────────────────
157
+ // When using array form, define behavior in separate statuses/lifecycle/taxonomy sections.
158
+ // export const types = {
159
+ // plan: {
160
+ // statuses: ['in-session', 'active', 'planned', 'blocked', 'partial', 'paused', 'awaiting', 'queued-after', 'archived'],
161
+ // context: {
162
+ // expanded: ['in-session', 'active', 'partial'],
163
+ // listed: ['planned', 'blocked', 'paused', 'awaiting'],
164
+ // counted: ['queued-after', 'archived'],
165
+ // },
166
+ // staleDays: { 'in-session': 1, active: 14, planned: 30, blocked: 30, awaiting: 14 },
167
+ // },
168
+ // };
169
+
170
+ // Status workflow — fallback for docs without a type field. Order determines display grouping.
171
+ // When using rich status definitions, statuses.order and staleDays are derived automatically.
172
+ export const statuses = {
173
+ order: ['active', 'ready', 'planned', 'scoping', 'blocked', 'reference', 'archived'],
174
+ // Additional statuses valid only in specific roots (merged with order)
175
+ // Useful when different doc areas track different things (e.g. plans vs module docs)
176
+ // rootStatuses: {
177
+ // 'docs/modules': ['implemented', 'partial', 'draft', 'deprecated'],
178
+ // 'docs/core': ['implemented', 'partial'],
179
+ // },
180
+ // Days after which a doc is considered stale (null = never stale)
181
+ staleDays: {
182
+ active: 14,
183
+ ready: 14,
184
+ planned: 30,
185
+ blocked: 30,
186
+ scoping: 30,
187
+ },
188
+ };
189
+
190
+ // Lifecycle behavior — which statuses trigger special handling.
191
+ //
192
+ // IMPORTANT: only enable this block if you're using ARRAY-form types (above) or
193
+ // no `types` definition. With rich-form types, the runtime derives these from
194
+ // per-status `terminal` / `archive` / `skipStale` / `skipWarnings` / `quiet`
195
+ // flags. An explicit `lifecycle` export sitting alongside rich-form types will
196
+ // SILENTLY OVERRIDE the per-status flags — `runlist statuses` will warn you
197
+ // before writing into a config in that state.
198
+ //
199
+ // export const lifecycle = {
200
+ // archiveStatuses: ['archived'], // auto-move to archiveDir on transition
201
+ // skipStaleFor: ['archived'], // skip staleness checks
202
+ // skipWarningsFor: ['archived'], // skip validation warnings (summary, etc.)
203
+ // terminalStatuses: ['archived', 'deprecated', 'reference'], // skip current_state/next_step warnings, exclude from stats scope
204
+ // archiveNestedTypes: ['prompt'], // types that archive under <typeDir>/<archiveDir>
205
+ // // (e.g. docs/prompts/archived/) instead of the
206
+ // // shared <root>/<archiveDir>; set [] to disable
207
+ // };
208
+
209
+ // Taxonomy validation — set fields to null to skip validation.
210
+ // moduleRequiredFor is derived from requiresModule when using rich status definitions.
211
+ export const taxonomy = {
212
+ // Ordered stages are repository-defined; status still tracks work progress.
213
+ // Existing string lists remain supported: milestones: ['Launch', 'Later'].
214
+ // Omit milestones (or use null) to allow any ships value. Missing ships is Unset.
215
+ milestones: [
216
+ { word: 'Launch', meaning: 'Work intended for the next release.' },
217
+ { word: 'Later', meaning: 'Work deliberately deferred beyond that release.' },
218
+ ],
219
+ surfaces: ['web', 'ios', 'android', 'mobile', 'full-stack', 'frontend', 'backend', 'api', 'docs', 'ops', 'platform', 'infra', 'design'],
220
+ moduleRequiredFor: ['active', 'ready', 'planned', 'blocked'],
221
+ };
222
+
223
+ // Index file generation — remove this section to disable
224
+ export const index = {
225
+ path: 'docs/docs.md',
226
+ startMarker: '<!-- GENERATED:runlist:start -->',
227
+ endMarker: '<!-- GENERATED:runlist:end -->',
228
+ snapshot: 'status', // default; use 'state' to include live current_state text
229
+ archivedLimit: 8,
230
+ };
231
+
232
+ // Context briefing layout (`runlist context`)
233
+ export const context = {
234
+ expanded: ['active'],
235
+ listed: ['ready', 'planned'],
236
+ counted: ['blocked', 'scoping', 'reference', 'archived'],
237
+ recentDays: 3,
238
+ recentStatuses: ['active', 'ready', 'planned'],
239
+ recentLimit: 10,
240
+ truncateNextStep: 80,
241
+ // Cap on slugs shown in the "Stale: …" tail before "…and N more (run `runlist stale`)"
242
+ // takes over (since 0.36.2). Raise on wide terminals; lower for tighter briefings.
243
+ staleTailLimit: 8,
244
+ };
245
+
246
+ // Display settings
247
+ export const display = {
248
+ lineWidth: 0, // 0 = auto-detect terminal width
249
+ truncateTitle: 30,
250
+ truncateNextStep: 80,
251
+ };
252
+
253
+ // Reference fields for bidirectional link checking
254
+ export const referenceFields = {
255
+ bidirectional: ['related_docs'], // warn if A→B but B↛A
256
+ unidirectional: ['supports'], // one-way, no symmetry check
257
+ };
258
+
259
+ // Query presets — expand to filter args when used as commands
260
+ // Built-in: plans, stale, actionable. Add your own here:
261
+ export const presets = {
262
+ // plans: ['--type', 'plan', '--sort', 'status', '--all'], // built-in
263
+ // stale: ['--status', '...', '--stale', '--sort', 'updated', '--all'], // built-in
264
+ // actionable: ['--status', 'active,ready', '--has-next-step', ...], // built-in
265
+ mine: ['--owner', 'robert', '--status', 'active', '--all'],
266
+ };
267
+
268
+ // ─── Templates ───────────────────────────────────────────────────────────────
269
+ // Define new types or override builtins. `runlist new <type> <name>` looks here first.
270
+ //
271
+ // Properties:
272
+ // description: string — shown in `runlist new --list-types`
273
+ // defaultStatus: string — initial status if `--status` not passed
274
+ // acceptsBody: boolean — allow body input (inline / --body / @file / piped stdin).
275
+ // REQUIRED if you want `cat draft.md | runlist new <type> <slug>` (or @path,
276
+ // --body, heredoc) to work. Your `body` fn must also interpolate the input,
277
+ // e.g. `${ctx?.bodyInput?.trim() ?? ''}`. See the body-acceptance guard below.
278
+ // requiresBody: boolean — error if no body input (implies acceptsBody; see `prompt` builtin)
279
+ // targetRoot: string — name (basename or suffix) of the root this type lives in.
280
+ // In flat-array `root` configs (e.g. ['docs/plans', 'docs/prompts']),
281
+ // the new doc lands in the matching root. Falls back to `config.docsRoot`
282
+ // if no root matches. The `--root` CLI flag still overrides.
283
+ // dir: string — subdirectory under `docsRoot` to use when `targetRoot` doesn't
284
+ // match anything. Builtin `plan` and `prompt` set both for portability:
285
+ // under `docsRoot='docs'`, `dir` puts files in `docs/plans/` and `docs/prompts/`;
286
+ // under flat-array roots, `targetRoot` routes directly to the type-specific root.
287
+ // frontmatter: (status, isoTime, ctx) => string
288
+ // body: (title, ctx) => string — slot user-supplied body via `ctx.bodyInput`
289
+ //
290
+ // Body-acceptance guard (the #1 custom-template gotcha):
291
+ // When you override a builtin and supply your OWN `body` fn that NEVER references
292
+ // `bodyInput`, runlist assumes the fn would silently discard piped input — so it strips
293
+ // the inherited `acceptsBody`/`requiresBody` and rejects body input with a fail-fast
294
+ // error. Two ways to keep piped/@path/heredoc bodies working in a custom template:
295
+ // 1. interpolate `${ctx?.bodyInput?.trim() ?? ''}` somewhere in your `body` fn, OR
296
+ // 2. set `acceptsBody: true` explicitly (do BOTH if you want the input to actually land).
297
+ // A `body: (t) =>` that ignores `ctx` is the classic trap — it scaffolds fine but
298
+ // `runlist new <type> <slug> < draft.md` errors until you wire in `bodyInput`.
299
+ //
300
+ // Custom type example — adds a `spike` type that lives in the `spikes` root (or
301
+ // under `docs/spikes/` in single-root layouts):
302
+ // export const templates = {
303
+ // spike: {
304
+ // description: 'Timeboxed investigation',
305
+ // defaultStatus: 'active',
306
+ // targetRoot: 'spikes', // for flat-array root configs
307
+ // dir: 'spikes', // for single-root configs
308
+ // frontmatter: (s, d) => `type: spike\nstatus: ${s}\ncreated: ${d}\ntimebox: 2d`,
309
+ // body: (t) => `\n# ${t}\n\n## Hypothesis\n\n\n\n## Findings\n\n`,
310
+ // },
311
+ //
312
+ // // Override a builtin (e.g. project-specific prompt frontmatter shape).
313
+ // // Overrides shallow-merge OVER the builtin: any property you omit is inherited
314
+ // // (`targetRoot`, `dir`, `defaultStatus`, `requiresBody`, …), and anything you declare
315
+ // // wins. EXCEPTION: if you supply your own `body` fn that doesn't reference `bodyInput`,
316
+ // // the inherited `acceptsBody`/`requiresBody` are dropped (see the guard above) — so
317
+ // // re-declare `acceptsBody: true` and wire in `ctx.bodyInput` if you want piped bodies.
318
+ // // prompt: {
319
+ // // description: 'Project resume prompt',
320
+ // // defaultStatus: 'pending',
321
+ // // requiresBody: true,
322
+ // // targetRoot: 'prompts',
323
+ // // dir: 'prompts',
324
+ // // frontmatter: (s, d, ctx) => `type: prompt\nstatus: ${s}\nproject_field: yes`,
325
+ // // body: (t, ctx) => `\n${ctx?.bodyInput ?? ''}\n`,
326
+ // // },
327
+ // };
328
+
329
+ // ─── Function Hooks ──────────────────────────────────────────────────────────
330
+ // Hooks are optional. Each receives a default implementation it can wrap or replace.
331
+
332
+ // Custom validation — called after built-in validation for each doc.
333
+ // Return { errors: [], warnings: [] } to add issues.
334
+ // export function validate(doc, ctx) {
335
+ // const warnings = [];
336
+ // if (doc.status === 'active' && !doc.owner) {
337
+ // warnings.push({ path: doc.path, level: 'warning', message: 'Active docs should have an owner.' });
338
+ // }
339
+ // return { errors: [], warnings };
340
+ // }
341
+
342
+ // Render hooks — override any renderer by wrapping the default.
343
+ // export function renderContext(index, defaultRenderer) { return defaultRenderer(index); }
344
+ // export function renderCompactList(index, defaultRenderer) { return defaultRenderer(index); }
345
+ // export function renderCheck(index, defaultRenderer) { return defaultRenderer(index); }
346
+ // export function renderStats(stats, defaultRenderer) { return defaultRenderer(stats); }
347
+ // export function renderGraph(graph, defaultRenderer) { return defaultRenderer(graph); }
348
+ // export function formatSnapshot(doc, defaultFormatter) { return defaultFormatter(doc); }
349
+
350
+ // Post-parse doc transformation — add computed fields.
351
+ // export function transformDoc(doc) {
352
+ // return doc;
353
+ // }
354
+
355
+ // Lifecycle callbacks — fire after the operation completes.
356
+ // export function onArchive(doc, { oldPath, newPath }) {}
357
+ // export function onStatusChange(doc, { oldStatus, newStatus, path }) {}
358
+ // export function onTouch(doc, { path, date }) {}
359
+ // export function onNew({ path, status, title, template }) {}
360
+ // export function onRename({ oldPath, newPath, referencesUpdated }) {}
361
+ // export function onLint({ path, fixes }) {}
362
+ // Pickup hooks are delivered at least once. A retry reuses operationId, so any
363
+ // external side effect must deduplicate by that stable ID. Delivery uses a
364
+ // persisted process-identity lease; live/unverifiable owners are not reclaimed
365
+ // by age, and an expired lease retries only when its owner is demonstrably dead.
366
+ // export function onPickup({ path, oldStatus, newStatus, operationId }) {}
367
+
368
+ // AI hooks — override summarization (replaces the local model server; see `runlist model`).
369
+ // export function summarizeDoc(body, meta) { return 'Custom summary'; }
370
+ // export function summarizeDiff(diffOutput, filePath) { return 'Custom diff summary'; }
@@ -0,0 +1,56 @@
1
+ // Runs after `npm install runlist`. Because the Claude Code plugin is a
2
+ // separate artifact from this CLI, upgrading the CLI alone leaves the plugin
3
+ // (hooks/skill/commands) on its old version. This script bridges that — but
4
+ // conservatively:
5
+ //
6
+ // - Only on GLOBAL installs (`npm i -g`). A project devDep / CI / Docker
7
+ // install must never touch a user's Claude Code state.
8
+ // - Default: just print a one-line nudge. A CLI install silently mutating the
9
+ // agent's plugin cache is surprising; opt in with RUNLIST_AUTO_PLUGIN_UPDATE=1
10
+ // to actually run the refresh.
11
+ // - NEVER fail the install: everything is swallowed and we always exit 0. A
12
+ // nonzero postinstall would break `npm i -g runlist`.
13
+ // - Skipped entirely under `npm install --ignore-scripts`.
14
+ import { spawnSync } from 'node:child_process';
15
+
16
+ try {
17
+ // Lifecycle env: npm sets this to "true" for global installs.
18
+ if (process.env.npm_config_global !== 'true') process.exit(0);
19
+
20
+ const hasClaude = (() => {
21
+ try {
22
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
23
+ return spawnSync(cmd, ['claude'], { encoding: 'utf8' }).status === 0;
24
+ } catch { return false; }
25
+ })();
26
+
27
+ // OpenCode's integration is a file runlist writes, and nothing else would ever
28
+ // mention it — an OpenCode user who installs only the CLI gets no primer and
29
+ // a process-scoped session identity, silently. One line, never an install.
30
+ const hasOpencode = (() => {
31
+ try {
32
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
33
+ return spawnSync(cmd, ['opencode'], { encoding: 'utf8' }).status === 0;
34
+ } catch { return false; }
35
+ })();
36
+
37
+ if ((process.env.RUNLIST_AUTO_PLUGIN_UPDATE ?? process.env.DOTMD_AUTO_PLUGIN_UPDATE) === '1' && hasClaude) {
38
+ const updated = spawnSync('claude', ['plugin', 'update', 'runlist@runlist'], { stdio: 'ignore', timeout: 60000 });
39
+ process.stdout.write(updated.status === 0
40
+ ? 'runlist: refreshed the Claude Code plugin — restart your session (or /reload-plugins) to apply.\n'
41
+ : 'runlist: install the renamed Claude Code plugin with `runlist install claude`.\n');
42
+ } else {
43
+ // The CLI just installed fresh, so only the plugin can be stale — point at
44
+ // the targeted refresh rather than the full `runlist update` (CLI + plugin).
45
+ const nudge = hasClaude
46
+ ? 'runlist CLI installed. Using the Claude Code plugin? Run `runlist update --plugin-only` to refresh it, then restart.'
47
+ : 'runlist CLI installed.';
48
+ process.stdout.write(`${nudge}\n`);
49
+ }
50
+ if (hasOpencode) {
51
+ process.stdout.write('runlist: OpenCode detected — run `runlist install opencode` for per-session plan ownership and a session-start briefing.\n');
52
+ }
53
+ } catch {
54
+ // Best effort only — never break the install.
55
+ }
56
+ process.exit(0);