arcane-cli 1.15.1 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,7 +54,7 @@ npm install -g arcane-cli # then: spell init
54
54
  <br>
55
55
 
56
56
  <div align="center">
57
- <img src="./assets/demo/spell-init.gif" alt="spell init — installs the full spell library and governance" width="800">
57
+ <img src="./assets/demo/spell-init.svg" alt="Animated terminal recording: spell init --profile full installs the spell library, governance docs, agent policies and templates into a fresh repository." width="800">
58
58
  </div>
59
59
 
60
60
  </details>
@@ -65,7 +65,7 @@ npm install -g arcane-cli # then: spell init
65
65
  <br>
66
66
 
67
67
  <div align="center">
68
- <img src="./assets/demo/spell-agents-arcanos.gif" alt="spell agents init — Arcanos naming strategy" width="800">
68
+ <img src="./assets/demo/spell-agents-arcanos.svg" alt="Animated terminal recording: spell agents init picks the full roster and the Arcanos naming strategy, then spell agents list shows the named team." width="800">
69
69
  </div>
70
70
 
71
71
  </details>
@@ -76,7 +76,7 @@ npm install -g arcane-cli # then: spell init
76
76
  <br>
77
77
 
78
78
  <div align="center">
79
- <img src="./assets/demo/spell-agents-custom.gif" alt="spell agents init — custom public-domain mythology naming" width="800">
79
+ <img src="./assets/demo/spell-agents-custom.svg" alt="Animated terminal recording: spell agents init with the base profile and custom naming, typing a public-domain mythology name (Athena, Apollo, Thoth, Maat) for each role, then spell agents list." width="800">
80
80
  </div>
81
81
 
82
82
  </details>
@@ -20,6 +20,8 @@ they may not be deleted. Applies to documentation and records repositories.
20
20
  document by an old link is never stranded.
21
21
  - Deletion is a separate, explicit decision from supersession — and some records
22
22
  may not be deleted at all.
23
+ - Completed TODO items move to an append-only done book (`DONE.md`) rather than
24
+ being deleted.
23
25
  - These are framework conventions, not legal advice. Where a retention
24
26
  obligation applies, that obligation governs.
25
27
 
@@ -86,6 +88,25 @@ Rules:
86
88
  One-directional linking only helps readers who arrive at the old document.
87
89
  The reverse direction is what preserves the record's own history.
88
90
 
91
+ ## Completed TODO items
92
+
93
+ A completed TODO item has one home once it leaves its TODO book: the **done
94
+ book**, `DONE.md` beside the TODO book by default. `spell-todo --prune` moves an
95
+ item there word for word, with its continuation lines, under the heading it had
96
+ in the TODO book, once its outcome is shown to be recorded somewhere durable.
97
+
98
+ - **The done book is append-only. Enforcement: structured spell gate (ARC-023) — `spell-todo --prune` only adds lines to it, inside its fingerprinted batch approval; no script checks a done book's history for an edited or removed line.**
99
+ It keeps the list of what was finished, which git history can recover line by
100
+ line but cannot be read as a list.
101
+ - **It is not the outcome record.** The outcome still lives where `--prune`
102
+ found it or migrated it (a journal entry, `DECISIONS.md`, `CHANGELOG.md`, a
103
+ linked pull request). The done book keeps the item; the outcome record keeps
104
+ the evidence.
105
+ - **`.arcane.json`'s `todo_done_book` sets its path, relative to the TODO
106
+ book's directory, or opts out with `false`.** Under `false`, `--prune`
107
+ deletes checked items instead, and that deletion is recorded as the section
108
+ below requires.
109
+
89
110
  ## Retention and deletion
90
111
 
91
112
  Supersession and deletion are different decisions and must not be conflated.
@@ -13,7 +13,7 @@ agent: agent
13
13
  - It reads the current TODO landscape and repo structure to avoid duplicates and pick the right home.
14
14
  - Use this any time you have an idea mid-session and don't want to lose it or park it in the wrong place.
15
15
  - If the idea is ADR-worthy or journal-worthy, it flags that and routes accordingly.
16
- - `--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.
16
+ - `--prune` removes checked items from a TODO book and moves them word for word to an append-only done book (`DONE.md` by default; `todo_done_book: false` in `.arcane.json` deletes instead) — report first, outcome recorded or migrated before anything is removed, one fingerprinted batch approval, and unchecked items are never touched.
17
17
  - **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).
18
18
 
19
19
  ---
@@ -86,20 +86,28 @@ Without `--validate`, the sweep never edits anything. With it, the sweep also as
86
86
 
87
87
  ## Prune Mode (`--prune`)
88
88
 
89
- 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
+ If the argument is (or starts with) `--prune`, skip every other step in this spell entirely and run this instead. It moves resolved items out of a TODO book into a done 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.)
90
90
 
91
91
  **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.
92
92
 
93
- 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
+ **Done book:** read `.arcane.json`'s optional `todo_done_book` field.
94
+
95
+ - **Absent:** the done book is `DONE.md`, in the same directory as the target book.
96
+ - **A path, for example `"DONE.md"` or `"docs/DONE.md"`:** the done book is that path, relative to the target book's directory.
97
+ - **`false`:** there is no done book. Approved items are deleted, not moved, as `--prune` did before the done book existed.
98
+
99
+ The done book is the home `governance/records-conventions.md` ("Completed TODO items") names for completed TODO items. **It is append-only: this mode adds lines to it and never edits, reorders or removes a line already in it.**
100
+
101
+ 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`. A checked sub-item whose checked parent is itself a candidate is one of that parent's continuation lines: it moves with the parent and is never a separate candidate, so nothing reaches the done book twice.
94
102
  2. **Check each outcome is recorded durably.** For each candidate, look for the record of what happened, in this order:
95
103
  - 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;
96
104
  - 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.
97
105
 
98
- 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`.
99
- 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.
106
+ 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`. The done book does not count as the outcome record: it keeps the item, not the evidence of what the item produced.
107
+ 3. **Report first — no edits yet.** Print every candidate with its classification, and the done book it will move to. 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.
100
108
 
101
109
  ```
102
- Todo prune — TODO.md, 5 checked items
110
+ Todo prune — TODO.md, 5 checked items → DONE.md
103
111
 
104
112
  1. [x] Add export button recorded → CHANGELOG.md (1.4.0)
105
113
  2. [x] Decide on ADR numbering recorded → DECISIONS.md (numbering entry)
@@ -107,11 +115,19 @@ If the argument is (or starts with) `--prune`, skip every other step in this spe
107
115
  4. [x] Wire the push hook has open sub-items — kept
108
116
  5. [x] Try the new cache outcome unknown — kept (tell me what happened, or leave it)
109
117
 
110
- Delete 3 items (1 after migration). Fingerprint: 3f9c1a…
118
+ Move 3 items to DONE.md (1 after migration). Fingerprint: 3f9c1a…
111
119
  ```
112
- 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.**
113
- 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.
114
- 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.
120
+
121
+ With `todo_done_book: false`, the header names no done book and the last line reads `Delete 3 items (1 after migration).`
122
+ 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, plus the done-book path and its current content (or its absence) when items move. 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, the move to the done book included; 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, the migration text or the done book 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.**
123
+ 5. **Migrate, move, then delete.** Write every migration first and confirm each one landed. Then, unless `todo_done_book` is `false`, copy each approved item into the done book:
124
+ - **Word for word.** The item line and its continuation lines exactly as they stand in the TODO book, checkbox and indentation included; nothing reworded, dated or summarised.
125
+ - **Under the same heading.** The item's heading path is the nearest heading above it in the TODO book plus every heading that encloses that one (each next heading above it at a higher level), excluding the book's level-1 title. Match the path in the done book heading by heading, by exact text and level, top level first; where a level has two matches, use the first. The item goes directly under the nearest heading, after the items already there and before that heading's first subheading or the next heading, so items keep their order and never read as part of a subsection. A heading missing from the path is added word for word at the end of its parent's section in the done book (before the next heading of the parent's level or higher), or at the end of the file when the parent is the title; the item goes under it. An item with no heading but the title above it goes directly under the done book's `# Done` title, before its first heading.
126
+ - **Never through a link.** If the done book, or a directory on its path, is a symbolic link, stop and report it; write nothing.
127
+ - **Creating the file when missing.** A new done book starts with a `# Done` title and the line `<!-- Append-only: spell-todo --prune adds items here and never edits or removes them. -->`, and has no frontmatter, so no later run has a reason to edit a line already in it.
128
+
129
+ Confirm every copied item is in the done book word for word. Then delete exactly the approved lines from the TODO book, and nothing else. An item whose migration or copy failed stays in the TODO book. Update `last_updated` in the frontmatter of every touched file that has one, except the done book: its existing lines are never edited, frontmatter included, whoever created it.
130
+ 6. **Record the move; 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 item removed from the TODO book, where its outcome is recorded, and the done book it moved to (or that it was deleted, under `todo_done_book: false`), and hand off to `spell-commit-work`. The done book, the commit body and the migrated journal notes are the record.
115
131
 
116
132
  **Speed rule:** a book with no checked items reports `Nothing to prune — 0 checked items` and stops.
117
133
 
package/dist/index.js CHANGED
@@ -607,6 +607,16 @@ function validateSecretsScanExcludePrefixes(value, filePath) {
607
607
  function isValidIdeaTags(value) {
608
608
  return isRecord(value) && (value["mode"] === "extend" || value["mode"] === "replace") && Array.isArray(value["tags"]) && value["tags"].every((tag) => typeof tag === "string" && tag.trim() !== "");
609
609
  }
610
+ var TODO_DONE_BOOK_RESERVED = /* @__PURE__ */ new Set(["todo.md", "ideas.md", "decisions.md", "changelog.md"]);
611
+ function isValidTodoDoneBook(value) {
612
+ if (value === false) return true;
613
+ if (typeof value !== "string" || !isValidSubjectRoot(value)) return false;
614
+ if (value !== value.trim() || value.includes(":")) return false;
615
+ const normalized = path2.posix.normalize(value.replace(/\\/g, "/"));
616
+ const segments = normalized.split("/");
617
+ const basename5 = segments[segments.length - 1] ?? "";
618
+ return /^[^/]+\.md$/i.test(basename5) && !/^\.md$/i.test(basename5) && !segments.slice(0, -1).some((segment) => segment.startsWith(".") && segment !== ".") && !TODO_DONE_BOOK_RESERVED.has(basename5.toLowerCase());
619
+ }
610
620
  function manifestPath(targetDir) {
611
621
  return path2.join(targetDir, MANIFEST_FILE);
612
622
  }
@@ -651,6 +661,9 @@ function validateManifestFields(manifest, filePath) {
651
661
  if (manifest.idea_tags !== void 0 && !isValidIdeaTags(manifest.idea_tags)) {
652
662
  throw new ManifestInvalidFieldError(filePath, "idea_tags", manifest.idea_tags);
653
663
  }
664
+ if (manifest.todo_done_book !== void 0 && !isValidTodoDoneBook(manifest.todo_done_book)) {
665
+ throw new ManifestInvalidFieldError(filePath, "todo_done_book", manifest.todo_done_book);
666
+ }
654
667
  }
655
668
  async function readManifest(targetDir) {
656
669
  const filePath = manifestPath(targetDir);