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.
- package/LICENSE +21 -0
- package/README.md +1112 -6
- package/assets/app/app.css +283 -0
- package/assets/app/app.mjs +381 -0
- package/assets/app/block-editor.mjs +182 -0
- package/assets/app/block-model.mjs +52 -0
- package/assets/app/document-create.mjs +86 -0
- package/assets/app/document-lifecycle.mjs +36 -0
- package/assets/app/document-stages.mjs +22 -0
- package/assets/app/document-views.mjs +33 -0
- package/assets/app/document-yardstick.mjs +258 -0
- package/assets/app/editor-navigation.mjs +51 -0
- package/assets/app/filing-navigation.mjs +138 -0
- package/assets/app/git-changes.mjs +80 -0
- package/assets/app/git-commit.mjs +46 -0
- package/assets/app/index.html +70 -0
- package/assets/app/library-navigation.mjs +93 -0
- package/assets/app/outline.mjs +18 -0
- package/assets/app/quick-navigation.mjs +52 -0
- package/assets/app/record-navigation.mjs +78 -0
- package/assets/app/recovery-center.mjs +53 -0
- package/assets/app/semantic-search.mjs +14 -0
- package/assets/app/settings.mjs +15 -0
- package/assets/app/shared.mjs +104 -0
- package/assets/app/stage-source.mjs +75 -0
- package/assets/app/template-editor.mjs +47 -0
- package/assets/app/theme.mjs +25 -0
- package/assets/app/transport.mjs +43 -0
- package/assets/contracts/source-editing-v1.md +232 -0
- package/assets/opencode/plugin.js +162 -0
- package/assets/schemas/runlist-record-v1.schema.json +912 -0
- package/bin/dotmd.mjs +5 -0
- package/bin/runlist.mjs +2714 -0
- package/package.json +53 -6
- package/plugins/runlist-codex/.codex-plugin/plugin.json +18 -0
- package/plugins/runlist-codex/bin/runlist-hook +86 -0
- package/plugins/runlist-codex/hooks/hooks.json +16 -0
- package/plugins/runlist-codex/skills/runlist/SKILL.md +25 -0
- package/runlist.config.example.mjs +370 -0
- package/scripts/postinstall.mjs +56 -0
- package/src/agent-context.mjs +201 -0
- package/src/ai.mjs +30 -0
- package/src/app-create.mjs +169 -0
- package/src/app-filing.mjs +139 -0
- package/src/app-git-commits.mjs +48 -0
- package/src/app-git.mjs +180 -0
- package/src/app-library.mjs +152 -0
- package/src/app-lifecycle-worker.mjs +12 -0
- package/src/app-lifecycle.mjs +79 -0
- package/src/app-records.mjs +134 -0
- package/src/app-search.mjs +80 -0
- package/src/app-semantic-search.mjs +105 -0
- package/src/app-service.mjs +208 -0
- package/src/app-yardstick.mjs +269 -0
- package/src/app.mjs +102 -0
- package/src/atomic-mutation.mjs +1736 -0
- package/src/baton.mjs +435 -0
- package/src/body-link.mjs +48 -0
- package/src/bulk-tag.mjs +174 -0
- package/src/check-collapse.mjs +75 -0
- package/src/claude-commands.mjs +75 -0
- package/src/cli-editor.mjs +8 -0
- package/src/code-refs.mjs +545 -0
- package/src/codex-integration.mjs +81 -0
- package/src/color.mjs +14 -0
- package/src/commands.mjs +374 -0
- package/src/completions.mjs +93 -0
- package/src/config-edit.mjs +606 -0
- package/src/config.mjs +705 -0
- package/src/decision-action.mjs +58 -0
- package/src/decision.mjs +220 -0
- package/src/decisions.mjs +787 -0
- package/src/deps.mjs +317 -0
- package/src/desktop-install.mjs +158 -0
- package/src/desktop-runtime.mjs +4 -0
- package/src/desktop.mjs +25 -0
- package/src/diff.mjs +101 -0
- package/src/doctor.mjs +591 -0
- package/src/durable-rename.mjs +55 -0
- package/src/errors-read.mjs +44 -0
- package/src/export.mjs +496 -0
- package/src/extractors.mjs +101 -0
- package/src/filing.mjs +255 -0
- package/src/fix-membership.mjs +134 -0
- package/src/fix-refs.mjs +174 -0
- package/src/flags.mjs +318 -0
- package/src/frontmatter-fix.mjs +195 -0
- package/src/frontmatter.mjs +252 -0
- package/src/git-commit-filters.mjs +32 -0
- package/src/git-commit-protocol.mjs +198 -0
- package/src/git-commit-signing.mjs +75 -0
- package/src/git-commit-store.mjs +26 -0
- package/src/git-commit-support.mjs +16 -0
- package/src/git-commit-worker.mjs +32 -0
- package/src/git.mjs +909 -0
- package/src/glossary-check.mjs +35 -0
- package/src/glossary.mjs +243 -0
- package/src/graph.mjs +298 -0
- package/src/guard.mjs +488 -0
- package/src/health.mjs +212 -0
- package/src/hints.mjs +129 -0
- package/src/host-integration.mjs +339 -0
- package/src/hub-membership.mjs +284 -0
- package/src/hub.mjs +296 -0
- package/src/hud.mjs +436 -0
- package/src/index-file.mjs +163 -0
- package/src/index.mjs +478 -0
- package/src/init.mjs +512 -0
- package/src/install.mjs +222 -0
- package/src/journal-read.mjs +116 -0
- package/src/journal.mjs +472 -0
- package/src/lifecycle.mjs +1444 -0
- package/src/lint.mjs +386 -0
- package/src/managed-path.mjs +192 -0
- package/src/markdown-code-spans.mjs +58 -0
- package/src/migrate-prompts.mjs +171 -0
- package/src/migrate-template.mjs +170 -0
- package/src/migrate.mjs +90 -0
- package/src/misuse-read.mjs +61 -0
- package/src/model-request.mjs +33 -0
- package/src/model.mjs +523 -0
- package/src/modules.mjs +355 -0
- package/src/naming.mjs +55 -0
- package/src/native-action.mjs +59 -0
- package/src/native-record.mjs +244 -0
- package/src/new.mjs +1321 -0
- package/src/output-identity.mjs +111 -0
- package/src/parent-plan.mjs +58 -0
- package/src/parse-cache.mjs +99 -0
- package/src/path-containment.mjs +15 -0
- package/src/pickup-card.mjs +293 -0
- package/src/pickup.mjs +671 -0
- package/src/prompt.mjs +25 -0
- package/src/prompts.mjs +585 -0
- package/src/public-inspection.mjs +19 -0
- package/src/query.mjs +977 -0
- package/src/record-action.mjs +11 -0
- package/src/record-create.mjs +82 -0
- package/src/reference-planner.mjs +388 -0
- package/src/reference-values.mjs +11 -0
- package/src/rename.mjs +107 -0
- package/src/render.mjs +678 -0
- package/src/roadmap.mjs +184 -0
- package/src/runlist.mjs +944 -0
- package/src/section.mjs +225 -0
- package/src/semantic-search-client.mjs +58 -0
- package/src/ship.mjs +166 -0
- package/src/show.mjs +101 -0
- package/src/skill-drift.mjs +98 -0
- package/src/source-editor.mjs +491 -0
- package/src/stages.mjs +73 -0
- package/src/state-migration.mjs +123 -0
- package/src/stats.mjs +200 -0
- package/src/status-metadata.mjs +87 -0
- package/src/statuses.mjs +721 -0
- package/src/summary.mjs +74 -0
- package/src/surfaces.mjs +28 -0
- package/src/sync-status.mjs +316 -0
- package/src/template-store.mjs +62 -0
- package/src/update.mjs +338 -0
- package/src/use.mjs +58 -0
- package/src/util.mjs +337 -0
- package/src/validate.mjs +795 -0
- package/src/watch.mjs +56 -0
- package/src/xref.mjs +406 -0
- package/src/yardstick-command.mjs +178 -0
- package/src/yardstick.mjs +112 -0
package/package.json
CHANGED
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runlist",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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);
|