arcane-cli 1.5.1 → 1.7.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.
Files changed (25) hide show
  1. package/README.md +37 -0
  2. package/dist/assets/.arcane/governance/agent-approved-paths.md +1 -1
  3. package/dist/assets/.arcane/governance/decision-documentation-standard.md +37 -1
  4. package/dist/assets/.arcane/governance/development-methodology.md +20 -0
  5. package/dist/assets/.arcane/governance/git-conventions.md +25 -3
  6. package/dist/assets/.arcane/governance/naming-conventions.md +1 -1
  7. package/dist/assets/.arcane/governance/spell-authoring-standards.md +40 -0
  8. package/dist/assets/.arcane/governance/universal-agent-rules.md +1 -1
  9. package/dist/assets/.arcane/spells/_fragments/push-policy-check.md +7 -0
  10. package/dist/assets/.arcane/spells/spell-address-review.md +15 -3
  11. package/dist/assets/.arcane/spells/spell-arcane-version.md +26 -0
  12. package/dist/assets/.arcane/spells/spell-check-drift.md +1 -0
  13. package/dist/assets/.arcane/spells/spell-close-session.md +45 -1
  14. package/dist/assets/.arcane/spells/spell-commit-work.md +23 -5
  15. package/dist/assets/.arcane/spells/spell-create-pull-request.md +19 -4
  16. package/dist/assets/.arcane/spells/spell-feedback.md +9 -0
  17. package/dist/assets/.arcane/spells/spell-manifest.md +25 -3
  18. package/dist/assets/.arcane/spells/spell-open-session.md +5 -0
  19. package/dist/assets/.arcane/spells/spell-save-idea.md +7 -1
  20. package/dist/assets/.arcane/spells/spell-ship.md +13 -1
  21. package/dist/assets/.arcane/spells/spell-sync-pull-request.md +12 -0
  22. package/dist/assets/.arcane/spells/spell-todo.md +84 -2
  23. package/dist/index.js +1170 -382
  24. package/dist/index.js.map +1 -1
  25. package/package.json +1 -1
@@ -30,6 +30,18 @@ Parse `status: new` entries from the hub root `IDEAS.md` and every `{BUSINESS_RO
30
30
 
31
31
  Tolerate operator YAML frontmatter above a book's header — entries always live below the `---` divider. Malformed entries (missing timestamp, unparseable status comment) are listed under a `Skipped` heading with `file:line`, never silently dropped.
32
32
 
33
+ **Status-comment grammar.** A status comment is `<!-- status: <state>[; <key>: <value>]... -->`. The first field is the status itself, unchanged from before: `new`, `promoted → <dest> YYYY-MM-DD`, or `dropped YYYY-MM-DD` with an optional `(reason)`. Zero or more `; <key>: <value>` fields may follow it. `spell-todo --sweep --validate` writes three of them: `value` and `fit` (each `y`, `n` or `?`) and `dup` (`none`, or a short reference). Both forms parse:
34
+
35
+ ```
36
+ <!-- status: new -->
37
+ <!-- status: new; value: y; fit: y; dup: none -->
38
+ ```
39
+
40
+ - **Splitting:** split the comment body on `; ` only where the next segment starts with a lowercase `key: `. A `; ` anywhere else — inside a drop reason, say — stays part of the field it is in.
41
+ - **Deciding `new`:** only the first field does. An entry is collected when that field is exactly `status: new`, with or without fields after it.
42
+ - **Unknown fields:** an unknown key, or a known key with an unexpected value, is ignored and shown as-is. It never moves the entry to `Skipped`.
43
+ - **Unparseable:** a comment is unparseable, and the entry goes to `Skipped`, when it does not open with `status:`, is not closed with `-->`, or its first field is not exactly `new`, `promoted …` or `dropped …` (a typo such as `status: nwe` or `status: New` is reported, never silently left out of both lists).
44
+
33
45
  ## Step 2 — List
34
46
 
35
47
  Print one numbered list, grouped by book, oldest entry first within each group. Numbering is session-scoped — it does not persist across runs.
@@ -45,6 +57,8 @@ ventures/ordovica/TODO.md
45
57
  3. [ ] Confirm cart snapshot order (open item, no status comment — todo-book entries are listed as-is)
46
58
  ```
47
59
 
60
+ An entry whose status comment carries validation fields (Step 1's grammar) shows them after its text — for example `(value: y, fit: n, dup: none)` — so that the operator can leave out `fit: n` entries, or ones marked as duplicates, when selecting in Step 3. An entry without them is listed exactly as before.
61
+
48
62
  ## Step 3 — Select
49
63
 
50
64
  Ask: `Promote which? (numbers/ranges, e.g. 1,3,5-8, or "all")`. Accept a single answer covering the whole batch. `all` selects every listed entry, including ones in books outside `--venture` if no filter was given.
@@ -84,7 +98,9 @@ This confirmation covers ordinary destinations only — it never authorizes a di
84
98
 
85
99
  ## Step 5 — Disclosure Gate
86
100
 
87
- Any destination whose repo has `visibility: "public"` — (a) or (d) landing in a public repo, or (c) filing a tracker item in a public tracker (a GitHub issue on a public repo is exactly as much a disclosure as its `IDEAS.md`) — is a disclosure, never covered by `all`, `go`, or any other batch confirmation. Per entry, print the **exact text** that will become public and ask for the literal word `disclose`. Anything else skips that one entry (it stays `status: new`) without aborting the rest of the batch.
101
+ The gate is **keyed on the destination repository's visibility, never on the destination letter**. An entry is a disclosure when the repository its text would land in — that repo's tree, its tracker, or anything else it publishes — has `visibility: "public"` in the registry, whatever route puts it there. There is deliberately no list of covered destinations: an enumerated list is how a route slips through. A PRD scaffold (b) writes the idea text into the public repo's tree, so it is a disclosure; so is a tracker item filed on a public repo (a GitHub issue on a public repo is exactly as much a disclosure as its `IDEAS.md`), and so is any destination added to Step 4 later. If the destination repository's visibility is missing from the registry, treat it as public.
102
+
103
+ A disclosure is never covered by `all`, `go`, or any other batch confirmation. Per entry, print the **exact text** that will become public and ask for the literal word `disclose`. Anything else skips that one entry (it stays `status: new`) without aborting the rest of the batch. **Enforcement: structured spell gate (ARC-023) — a public-destination entry is written only after the operator's own literal `disclose` for that entry; a timeout, cancellation, or host-generated fallback is not consent and skips the entry.**
88
104
 
89
105
  On a repo's first-ever disclosure (its `IDEAS.md` has no existing entries with a `promoted → public:` marker), offer to add this boundary statement to that file's header in the same staged change:
90
106
 
@@ -110,13 +126,19 @@ hub:TODO.md
110
126
  book:<other-slug>/IDEAS.md
111
127
  ```
112
128
 
129
+ Any marker whose destination repository is public ends in ` (disclosed)` — whichever route landed it, because Step 5 gated it as a disclosure.
130
+
131
+ **Tracker-id suffix.** When route (c) files a tracker item, append the new work item's id to the hub entry's text as a suffix — before its status comment — in the format [governance/development-methodology.md](../../.arcane/governance/development-methodology.md) defines once under "Tracker-ID Suffix on Captured Items" for that tracker's provider (`[AB#<id>]` Azure DevOps, `[#<id>]` GitHub, the issue key for Jira). Use only the id the create command returned. The promoted marker still names the repo (`github:<slug>#<id>`), which is what disambiguates a GitHub suffix inside a hub book. An entry that already carries a suffix keeps it unchanged wherever it lands. No tracker item filed means no suffix: entries routed without a tracker (and every internal-mode flow) are written exactly as before.
132
+
113
133
  Dropped entries get `<!-- status: dropped YYYY-MM-DD -->`, optionally with a trailing `(reason)`.
114
134
 
135
+ Validation fields already in an entry's comment (Step 1's grammar) are kept after the new first field when it is marked, e.g. `<!-- status: dropped 2026-09-27; value: n; fit: y; dup: none -->`. An entry without them gets exactly the marker above.
136
+
115
137
  Before any downstream write, grep the target file for the entry's normalized first ~40 characters — if already present, treat this entry as already-landed, mark the hub entry promoted, and skip the write. This is what makes an interrupted or re-run batch safe.
116
138
 
117
139
  ## Step 7 — Leak Scan
118
140
 
119
- Before writing outbound text to any consumer or public destination, scan it for:
141
+ Before writing outbound text to any consumer or public destination, scan it. Outbound text is the idea text **and the entry's tag** (`[#tag]`): the tag travels with the entry into whatever the destination copies it into (an `IDEAS.md` line, a tracker label or title), so a tag is outbound text like any other. **Enforcement: structured spell gate (ARC-023) — a hit in either field blocks that entry's write until the operator rewrites it or keeps it in the book.** Scan both for:
120
142
 
121
143
  - Every other venture's slug and aliases from the registry (never the destination's own)
122
144
  - Hub path fragments (`{BUSINESS_ROOT}` paths, this repo's own root-relative paths)
@@ -141,7 +163,7 @@ Run spell-commit-work in each repo. N entries remain new.
141
163
  ## Rules
142
164
 
143
165
  - Never run outside a hub repo. Never infer hub status.
144
- - Never let `all` or the plan-confirm authorize a disclosure — that confirmation is always separate, always per-entry, always the literal word `disclose`.
166
+ - A disclosure is any entry whose destination repository is public, whatever the destination (Step 5). Never let `all` or the plan-confirm authorize a disclosure — that confirmation is always separate, always per-entry, always the literal word `disclose`.
145
167
  - Never write a sibling venture's slug, a hub path, or a machine name into a consumer or public artifact — the leak scan is mandatory, not advisory.
146
168
  - Never commit. Stage and report; hand off to `spell-commit-work`.
147
169
  - Never guess a venture from partial input — resolve through the registry's aliases or ask.
@@ -99,6 +99,11 @@ Before anything else, check:
99
99
  - Run `npm view arcane-cli version` to get the latest published version.
100
100
  - **(a) Managed files behind the installed CLI** (installed CLI version is newer than the version recorded in `.arcane.json`): surface a warning: `⚠️ Managed files out of date: files at <installed> → CLI at <cli>. Run spell update to resync files.`
101
101
  - **(b) Installed CLI behind the latest published version** (latest published is newer than the installed CLI): surface a warning: `⚠️ Arcane CLI update available: CLI at <cli> → latest <latest>. Upgrade the CLI, then run spell update.`
102
+ - **Setup questions from an AI harness (no interactive terminal, PRD D-02 / #282):** if you run `spell update` yourself — a repository write, so apply the Mutation Guard first — note that a harness shell is not an interactive terminal. The CLI then skips every unanswered manifest question and prints ``! Skipped N manifest question(s) (<fields>) — no interactive terminal. Run `spell update` from a terminal, or answer by flags:``, followed by the exact line to re-run, such as `spell update --tracking-mode <internal|external> --push-policy <open|guarded|blocked>`. `spell init --profile <profile>` prints the same kind of `spell update` line when it leaves questions unanswered. Do not send the operator off to find a terminal. Instead:
103
+ 1. Ask the operator each skipped question through the harness's own question UI (a plain chat question if the harness has none): one question per flag the CLI printed, offering exactly the values that flag lists. The flags are `--role`, `--tracking-mode`, `--external-provider`, `--content-sensitivity`, `--push-policy` and `--subject-root`. `--tracking-mode external` also needs `--external-provider <ado|github|jira|other>`, so ask that too when the answer is `external`.
104
+ 2. Re-run `spell update` with the flags the CLI printed, filled with the operator's answers, for example `spell update --tracking-mode internal --push-policy guarded`.
105
+ 3. Never pass a flag the operator did not answer. No guessed, default or "recommended" value stands in for an answer. An unanswered question stays unset: the next interactive run asks it, and the next harness run reports it skipped again.
106
+ 4. Never loosen `push_policy` by flag. `--push-policy` only answers an unset question or tightens a recorded policy, and the CLI refuses a loosening. Moving a repository from `blocked` or `guarded` toward `open` is only ever `spell unblock-push`, which the operator runs from an interactive terminal (ARC-034 decision 6, ARC-049 decision 6). Tell them so; do not try to run it for them.
102
107
  - **If either axis shows drift, emit the canonical version-drift diagram beside the warning(s)** — per the generated state diagrams convention (rule 8, ARC-036). One commit per *distinct* version value among the three readings, in the order repo-files → installed-CLI → npm-latest; collapse consecutive identical values into a single shared commit rather than repeating it. Branch off immediately after the commit for each axis that drifts — `repo-files` after the repo-files-version commit if axis A drifts, `installed-cli` after the installed-CLI-version commit if axis B drifts. Tag the final commit `"latest"`. The dangling branch(es) *are* the drifting axis/axes — no legend needed:
103
108
  ```mermaid
104
109
  gitGraph
@@ -58,7 +58,10 @@ When targeting a venture book that doesn't exist yet as a file, create it from t
58
58
  - Strip a leading invocation phrase if the user dictated one (e.g., "save this idea —", "note that", "remind me to") — log the idea, not the command to log it.
59
59
  - Lightly clean it up: fix obvious typos and capitalization, but **do not** rewrite, expand, or editorialize. The user's phrasing is the record.
60
60
  - If the idea is clearly multiple distinct ideas, split them into separate entries (one appended line each).
61
- - Optionally infer a single short tag (1–2 words, kebab-case) from the content — e.g., `ui`, `infra`, `marketing`, `dx`. If nothing fits cleanly, omit the tag.
61
+ - Optionally infer a single short tag (1–2 words, kebab-case) from the content. Where the tag comes from depends on `.arcane.json`'s optional `idea_tags` field, `{ "mode": "extend" | "replace", "tags": [...] }`:
62
+ - **`idea_tags` declared:** pick the tag from the declared vocabulary and never invent a tag outside it. `mode: "extend"` — the vocabulary is the declared `tags` plus today's free-form default tags: prefer a declared tag when one fits, otherwise infer one as below. `mode: "replace"` — the declared `tags` are the whole vocabulary; no free-form tag is inferred. If nothing in the vocabulary fits cleanly, omit the tag.
63
+ - **No `idea_tags`:** infer a free-form tag from the content — e.g., `ui`, `infra`, `marketing`, `dx`. If nothing fits cleanly, omit the tag.
64
+ - A tag must never carry a venture, client, or person name — tag the idea's subject (`ui`, `billing`), never whose it is. A tag travels with the entry when `spell-manifest` promotes it downstream, so a name in a tag leaks exactly as a name in the text would. **Enforcement: explicitly advisory prose (ARC-023) — the agent's judgment at capture time; in a hub, `spell-manifest`'s leak scan re-checks the tag before it leaves, but only for sibling venture names, hub paths, machine names and the org-token denylist.**
62
65
  - **If the idea contains a secret** (token, password, API key, connection string), redact it inline (e.g., `[redacted]`) before writing and warn the user in the confirmation. Never write the raw value to `IDEAS.md`.
63
66
 
64
67
  ## Step 2 — Append to IDEAS.md
@@ -73,7 +76,9 @@ Entry format:
73
76
 
74
77
  - Get the real local date and time from the system (e.g., run `date`) rather than guessing — a wrong timestamp silently corrupts the append-only log.
75
78
  - The `status: new` HTML comment lets later triage mark entries as `promoted` / `dropped` without deleting history.
79
+ - This spell always writes the plain `<!-- status: new -->`. Later, `spell-todo --sweep --validate` may add `; value: …; fit: …; dup: …` fields to the comment after an operator's batch confirmation. The grammar that reads both forms is in `spell-manifest` Step 1.
76
80
  - Include `[#tag]` only if a tag was inferred; otherwise omit it.
81
+ - **Tracker-id suffix (`tracking_mode: external` only):** if `.arcane.json`'s tracking mode is `external` and the idea names a work item that already exists, end the idea text with that id's suffix in the provider's format, before the status comment — defined once in [governance/development-methodology.md](../../.arcane/governance/development-methodology.md) under "Tracker-ID Suffix on Captured Items" (e.g. `[#123]` GitHub, `[AB#123]` Azure DevOps, the issue key for Jira). Never guess an id and never file a work item from here — this spell stays instant. In `tracking_mode: internal`, the entry format above is unchanged.
77
82
 
78
83
  If `IDEAS.md` does not exist yet, create it with this header first, then append:
79
84
 
@@ -114,4 +119,5 @@ Do not ask follow-up questions. Do not commit — leave that to `spell-commit-wo
114
119
  - Never rewrite the user's idea beyond light cleanup. Preserve their words.
115
120
  - No approval gate — write immediately.
116
121
  - Never include secrets, tokens, or credentials in an entry.
122
+ - Never put a venture, client, or person name in a tag; when `.arcane.json` declares `idea_tags`, draw the tag from that vocabulary (Step 1).
117
123
  - Do not commit the change.
@@ -38,7 +38,19 @@ Workflow:
38
38
  - Merge the target branch into the current branch (e.g. `git merge origin/<target>`).
39
39
  - **On merge conflicts:** STOP and ask the user to resolve them, or run `spell-sync-pull-request` — it handles exactly this case, with a recoverable ref and an explicit mechanical-vs-ambiguous conflict boundary. Do not attempt to auto-resolve here.
40
40
  - After a clean merge (or once the user has resolved conflicts), re-run the full test suite and confirm it passes.
41
- - Push the synced branch (e.g. `git push`).
41
+ - Before pushing, run the push-policy check. It also governs step 9's remote branch deletion.
42
+
43
+ <!-- fragment:push-policy-check:start -->
44
+ **Push-policy check (ARC-049) — before any push.** Read `push_policy` from the repository's `.arcane.json` at the repository root (a self-hosted source repository with no root manifest keeps it in `src/assets/.arcane.json` when that file declares `selfHosted: true`). An absent file or an absent field means `open`. The check covers every push this spell makes: a branch push, a `--force-with-lease` push, and a remote branch deletion (`git push <remote> --delete <branch>`).
45
+ - **`open` or absent:** proceed with the push.
46
+ - **`guarded`:** before the push, state the policy ("This repository's push policy is `guarded`: every push needs your explicit confirmation."), name the remote, the branch and the kind of push, and ask the operator for explicit confirmation. Push only on an explicit operator confirmation of that push. A timeout, a cancellation, a delegated or host-generated response, or assent given to something else is not confirmation. **In an autonomous run with no operator to ask, do not push:** report the push as pending operator confirmation, naming the branch and the commits it would carry. This fails closed.
47
+ - **`blocked`:** do not attempt the push. Say why: the repository's push policy is recorded as `blocked`, so pushes are refused on purpose. Name `spell unblock-push` as the only way to lift it, run by the operator from an interactive terminal. Never run it yourself, and never work around the block (`--no-verify`, another remote or URL, editing git config or `.arcane.json`).
48
+ - **Any other value, or a `.arcane.json` that cannot be parsed:** do not push. Report what was found and suggest `spell doctor`.
49
+ - When a push does not happen, skip every step that depends on it (opening or updating a PR, remote branch cleanup) and report the commits as committed locally and not pushed.
50
+ - Enforcement: structured spell gate (ARC-023) — for `guarded`, this check is the only thing that holds a push for confirmation. A `blocked` push is also refused by the pre-push hook and the sentinel push URL that `spell init` or `spell block-push` install, and `spell doctor` reports a `blocked` policy whose controls are missing.
51
+ <!-- fragment:push-policy-check:end -->
52
+
53
+ - Push the synced branch (e.g. `git push`) only when the check allows it. If it stops the push, the ship is blocked until the operator confirms (`guarded`) or lifts the policy (`blocked`); record that in the ship report.
42
54
 
43
55
  3. **Run pre-deploy checklist**:
44
56
  - [ ] All CI pipeline checks pass (build, test, lint, security scan).
@@ -111,6 +111,18 @@ recoverable ref from Step 1 is still available.
111
111
 
112
112
  ## Step 5 — Push and verify
113
113
 
114
+ Run the push-policy check first. If it stops the push, stop here: the rebased branch stays local, the recoverable ref from Step 1 still points at the pre-sync state, and Step 6's report never runs — report the stop instead.
115
+
116
+ <!-- fragment:push-policy-check:start -->
117
+ **Push-policy check (ARC-049) — before any push.** Read `push_policy` from the repository's `.arcane.json` at the repository root (a self-hosted source repository with no root manifest keeps it in `src/assets/.arcane.json` when that file declares `selfHosted: true`). An absent file or an absent field means `open`. The check covers every push this spell makes: a branch push, a `--force-with-lease` push, and a remote branch deletion (`git push <remote> --delete <branch>`).
118
+ - **`open` or absent:** proceed with the push.
119
+ - **`guarded`:** before the push, state the policy ("This repository's push policy is `guarded`: every push needs your explicit confirmation."), name the remote, the branch and the kind of push, and ask the operator for explicit confirmation. Push only on an explicit operator confirmation of that push. A timeout, a cancellation, a delegated or host-generated response, or assent given to something else is not confirmation. **In an autonomous run with no operator to ask, do not push:** report the push as pending operator confirmation, naming the branch and the commits it would carry. This fails closed.
120
+ - **`blocked`:** do not attempt the push. Say why: the repository's push policy is recorded as `blocked`, so pushes are refused on purpose. Name `spell unblock-push` as the only way to lift it, run by the operator from an interactive terminal. Never run it yourself, and never work around the block (`--no-verify`, another remote or URL, editing git config or `.arcane.json`).
121
+ - **Any other value, or a `.arcane.json` that cannot be parsed:** do not push. Report what was found and suggest `spell doctor`.
122
+ - When a push does not happen, skip every step that depends on it (opening or updating a PR, remote branch cleanup) and report the commits as committed locally and not pushed.
123
+ - Enforcement: structured spell gate (ARC-023) — for `guarded`, this check is the only thing that holds a push for confirmation. A `blocked` push is also refused by the pre-push hook and the sentinel push URL that `spell init` or `spell block-push` install, and `spell doctor` reports a `blocked` policy whose controls are missing.
124
+ <!-- fragment:push-policy-check:end -->
125
+
114
126
  ```bash
115
127
  git push --force-with-lease
116
128
  ```
@@ -12,18 +12,22 @@ agent: agent
12
12
  - It reads the current TODO landscape and repo structure to avoid duplicates and pick the right home.
13
13
  - Use this any time you have an idea mid-session and don't want to lose it or park it in the wrong place.
14
14
  - If the idea is ADR-worthy or journal-worthy, it flags that and routes accordingly.
15
+ - `--prune` removes checked items from a TODO book — report first, outcome recorded or migrated before anything is deleted, one fingerprinted batch approval, and unchecked items are never touched.
15
16
  - **In a hub repo:** "add a todo for &lt;venture&gt;" targets that venture's own `TODO.md` book instead of the hub root. `--sweep` skips elaboration entirely and reports new/open counts across every book (hub root + every venture).
16
17
 
17
18
  ---
18
19
 
19
- Resolve `{BUSINESS_ROOT}` from `.arcane.json`'s `business_root` field (default `ventures/` if unset) before either mode below.
20
+ Resolve `{BUSINESS_ROOT}` from `.arcane.json`'s `business_root` field (default `ventures/` if unset) before any mode below.
20
21
 
21
22
  ## Sweep Mode (`--sweep`)
22
23
 
23
24
  If the argument is (or starts with) `--sweep`, skip every other step in this spell entirely and run this instead:
24
25
 
25
- - **In a hub repo:** report `status: new` counts and oldest-entry age for the hub root `TODO.md` plus every `{BUSINESS_ROOT}/<slug>/TODO.md` that exists. Group by book, point at `spell-manifest` for entries ready to promote. Report-only — no edits.
26
+ - **In a hub repo:** report `status: new` counts and oldest-entry age for the hub root `TODO.md` plus every `{BUSINESS_ROOT}/<slug>/TODO.md` that exists. Group by book, point at `spell-manifest` for entries ready to promote. Report-only — no edits (the one exception is the opt-in `--validate` below).
26
27
  - **In a consumer repo:** report the same, for the repo-root `TODO.md` only (no venture books exist there).
28
+ - **Idea books too:** report the same `status: new` counts for the idea books that sit beside those TODO books — the hub root `IDEAS.md` and each venture's own `IDEAS.md` in a hub, the repo-root `IDEAS.md` in a consumer repo. Still report-only.
29
+
30
+ **Sweep scope is hub-local (PRD D-08).** The sweep reads only books and journals inside this repository: in a hub, the hub's TODO books, the hub's IDEAS books, and the hub's own `journal/`. **It never reads a consumer repo's clone — not even a registered clone on this machine.** In a consumer repo it reads only that repo's own root books and `journal/`. Reaching into other repositories would widen what this spell touches, a privacy and scope change nobody asked for.
27
31
 
28
32
  Output format:
29
33
 
@@ -32,8 +36,84 @@ Todo sweep — N books, M open items
32
36
 
33
37
  hub TODO.md 12 open, oldest 2026-07-14
34
38
  ventures/ordovica/TODO.md 3 open, oldest 2026-08-10
39
+ hub IDEAS.md 5 new, oldest 2026-08-02
40
+ ```
41
+
42
+ ### Unrouted lessons (part of every `--sweep`)
43
+
44
+ The sweep also lists lessons from this repository's own journal that nothing has picked up yet. This pass is report-only, like the rest of the sweep: no writes, no moves, no filing, and no network calls.
45
+
46
+ - **Where:** the hub's own `journal/` only — or, in a consumer repo, that repo's own `journal/` — within the hub-local scope above. Never a consumer clone's journal.
47
+ - **Window:** journal entries dated on or after `--since YYYY-MM-DD`. Without `--since`, the last 30 days. The sweep is report-only, so it keeps no marker of when it last ran: to cover "since the last sweep", pass that sweep's date as `--since`. The output header states the window used.
48
+ - **What counts as a lesson:** each heading directly under a `### Lessons Learned` section — one level deeper, e.g. `#### <lesson>` — because `spell-close-session` writes one heading per lesson. The pass reads headings only. **Bold sentences and bullet items are not read as lessons.** A `### Lessons Learned` section with no per-lesson headings (the older bullet style) is reported as one line — `<path>: Lessons Learned has no per-lesson headings — not harvested` — so it is never silently skipped.
49
+ - **Routed or unrouted:** a lesson is routed when something references it:
50
+ - a `FEEDBACK.md` item;
51
+ - an idea-book entry or a TODO item within the sweep's scope — a reference is the lesson's heading text, or a link to its journal file with the lesson's heading anchor;
52
+ - an issue link in the lesson's own body (`#123`, `AB#123`, a Jira key, or an issue URL).
53
+
54
+ Everything else is unrouted. A lesson that `spell-close-session`'s Lessons Learned routing step (#274) already sent to `FEEDBACK.md` or an idea book is referenced there, so it drops out here.
55
+
56
+ ```
57
+ Lessons since 2026-08-28 (hub journal) — 2 unrouted
58
+ journal/2026-09-11-cache-rollout.md
59
+ - The stated risk was work that did not exist
60
+ - Building the state first beat reading the plan
61
+ journal/2026-09-02-release-prep.md: Lessons Learned has no per-lesson headings — not harvested
62
+ Promote with spell-feedback (framework), spell-save-idea (product idea), or spell-todo (task).
35
63
  ```
36
64
 
65
+ ### Idea validation (`--sweep --validate`, opt-in)
66
+
67
+ Without `--validate`, the sweep never edits anything. With it, the sweep also asks two or three quick yes/no questions per idea entry, so that `spell-manifest` can later see which ideas are worth promoting. Capture itself (`spell-save-idea`) stays unchanged.
68
+
69
+ 1. **Candidates:** idea-book entries, within the hub-local scope above, whose status comment is `status: new` and does not yet carry a `value:` field. TODO-book items have no status comment and are not validated.
70
+ 2. **Ask, per entry** — show the entry's text and book, then ask:
71
+ - `value` — Is this valuable to the people who use what this repo builds? (`y` / `n` / `?`)
72
+ - `fit` — Does it belong here, rather than in another venture, another repo, or a consumer's own product? (`y` / `n` / `?`)
73
+ - `dup` — Does it duplicate an existing idea entry, TODO item or tracker issue? (`none`, or a short reference such as `#123` or `IDEAS.md 2026-08-02 14:10`; never containing `;` or `--`)
74
+
75
+ `skip` leaves the entry unanswered; `stop` ends the questions and goes on to the confirmation with what has been answered so far. **Nothing is written while the questions are being asked** — the answers are held until step 3.
76
+ 3. **One batch confirmation.** Print every change as `file:line`, the old comment, and the new one. Then ask once: `Write N status comments? (write / cancel)`. Only the operator's own literal `write` writes. A timeout, a cancellation, a host-generated fallback or ordinary conversational assent is not confirmation, and nothing is written. If a book changed since it was read, re-read it and confirm again. **Enforcement: structured spell gate (ARC-023) — answers are written only after the operator's single batch `write`; the check is agent-administered, not tool-verified.**
77
+ 4. **Write only the status comment.** The entry text, timestamp and tag stay untouched, and the books stay append-only. The answers are added as fields after the status. The grammar is `spell-manifest`'s Step 1 "Status-comment grammar", which reads both forms:
78
+
79
+ ```
80
+ <!-- status: new --> before
81
+ <!-- status: new; value: y; fit: y; dup: none --> after
82
+ ```
83
+
84
+ Update `last_updated` in the frontmatter of every touched book. Do not commit; hand off to `spell-commit-work`.
85
+
86
+ ## Prune Mode (`--prune`)
87
+
88
+ If the argument is (or starts with) `--prune`, skip every other step in this spell entirely and run this instead. It removes resolved items from a TODO book only after proving each one's outcome is recorded somewhere durable. (Not to be confused with `spell update --prune`, which removes orphaned managed files.)
89
+
90
+ **Target book:** this repo's root `TODO.md`. In a hub, `--prune --venture <slug>` targets that venture's own `TODO.md` instead, resolved through the registry's aliases exactly as Step 0 below does. One book per run.
91
+
92
+ 1. **Collect checked items only.** A candidate is a checked item — `- [x]` or `- [X]` — together with its indented continuation lines, and only when every item it is nested under is checked too: a checked sub-item under an unchecked parent belongs to that open item and is never a candidate. **An unchecked item (`- [ ]`) is never a candidate: it is never deleted, moved or edited by this mode.** A checked item with any unchecked sub-item is kept whole and reported as `has open sub-items — kept`.
93
+ 2. **Check each outcome is recorded durably.** For each candidate, look for the record of what happened, in this order:
94
+ - the item's own reference — a file link, a PR or issue link, an ADR id, a tracker-id suffix — followed and confirmed to exist and to describe the outcome;
95
+ - otherwise, a search of `journal/`, `DECISIONS.md`, `CHANGELOG.md`, and the repo's audit log and playbooks where they exist, for the item's key terms.
96
+
97
+ Classify each candidate as `recorded → <where>` (path, plus heading or line) or `unrecorded`. A merged PR or closed issue counts as durable only when the item links it; an unlinked guess ("probably PR #40") is `unrecorded`.
98
+ 3. **Report first — no edits yet.** Print every candidate with its classification. For each `unrecorded` one, print the outcome migration you propose: a short outcome note (what was done, when, where it can be seen) appended to today's journal entry, `journal/YYYY-MM-DD-<topic-slug>.md`, under a `### Pruned TODO outcomes` heading. If you cannot tell what the outcome was, say so and leave that item out of the batch — never invent an outcome.
99
+
100
+ ```
101
+ Todo prune — TODO.md, 5 checked items
102
+
103
+ 1. [x] Add export button recorded → CHANGELOG.md (1.4.0)
104
+ 2. [x] Decide on ADR numbering recorded → DECISIONS.md (numbering entry)
105
+ 3. [x] Document the retrofit path unrecorded → migrate to journal/2026-09-27-todo-prune.md
106
+ 4. [x] Wire the push hook has open sub-items — kept
107
+ 5. [x] Try the new cache outcome unknown — kept (tell me what happened, or leave it)
108
+
109
+ Delete 3 items (1 after migration). Fingerprint: 3f9c1a…
110
+ ```
111
+ 4. **One batch approval, fingerprinted.** This is the same gate `spell-commit-work` step 8 uses for a commit. Compute an approval fingerprint from the exact deletion list (book path, line, full item text for every item to delete) plus the exact migration text. Present the list, the migration text and the fingerprint through a structured approval control, and wait for an authenticated operator response tied to that fingerprint. One approval covers the whole batch; there are no per-item prompts. A timeout, a cancellation, a host-generated fallback, a delegated response, or ordinary conversational assent is not approval: halt with nothing written. Recompute the fingerprint immediately before writing. If the book or the migration text changed, the approval is void; ask again. **Enforcement: structured spell gate (ARC-023) — nothing is written until the operator approves the fingerprinted batch; the fingerprint comparison is agent-administered, not tool-verified.**
112
+ 5. **Migrate, then delete.** Write every migration first and confirm each one landed. Then delete exactly the approved lines, and nothing else. An item whose migration failed stays in the book. Update `last_updated` in the frontmatter of every touched file.
113
+ 6. **Record the deletion; do not commit.** `governance/records-conventions.md` ("Retention and deletion") requires an approved deletion to be recorded. Propose a commit message whose body lists each deleted item and where its outcome is recorded, and hand off to `spell-commit-work`. No separate completed-items ledger is written: `records-conventions.md` defines no home for one, and the commit body plus the migrated journal notes are the record.
114
+
115
+ **Speed rule:** a book with no checked items reports `Nothing to prune — 0 checked items` and stops.
116
+
37
117
  ## Step 0 — Venture Targeting (Hub Only)
38
118
 
39
119
  If the input names a venture ("add a todo for ordo", `--venture <slug>`), resolve it through `{BUSINESS_ROOT}/registry.json`'s aliases first (exact slug → alias → closest match offered — never guessed). Unknown slug:
@@ -104,6 +184,8 @@ Expand the idea into one or more concrete TODO items. Each item must:
104
184
  - Include a file reference if a canonical home doc already exists (e.g., `— see [[DECISIONS]]`)
105
185
  - Be specific enough to act on without further clarification
106
186
 
187
+ **Tracker-id suffix (`tracking_mode: external` only):** when `.arcane.json`'s tracking mode is `external` and an item corresponds to a work item that already exists (the operator gave its id, or the idea names it), end the item with that id's suffix in the provider's format — defined once in [governance/development-methodology.md](../../.arcane/governance/development-methodology.md) under "Tracker-ID Suffix on Captured Items" (e.g. `[#123]` GitHub, `[AB#123]` Azure DevOps, the issue key for Jira). Never guess an id or file a work item just to get one. In `tracking_mode: internal`, items are written without a suffix, exactly as before.
188
+
107
189
  If the idea naturally decomposes into multiple sub-items (e.g., "build dashboard" → design, implement, deploy, document), list them individually — do not bundle vague compound items.
108
190
 
109
191
  If the idea is an `adr-candidate`, also draft a one-sentence ADR title suggestion (e.g., `ADR-NNN: Use GitHub Actions for CI instead of Azure Pipelines`).