neurain 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (47) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +19 -3
  3. package/docs/connect-runtime.en.md +1 -1
  4. package/docs/connect-runtime.kr.md +1 -1
  5. package/docs/development-status.en.md +19 -14
  6. package/docs/development-status.kr.md +19 -14
  7. package/docs/release-checklist.en.md +34 -1
  8. package/package.json +5 -2
  9. package/src/cli.mjs +95 -74
  10. package/src/core/adopt.mjs +19 -2
  11. package/src/core/area_index.mjs +308 -0
  12. package/src/core/backup.mjs +171 -0
  13. package/src/core/capture_durable.mjs +120 -52
  14. package/src/core/capture_enrich.mjs +180 -0
  15. package/src/core/compile_desk.mjs +30 -37
  16. package/src/core/digest.mjs +5 -3
  17. package/src/core/file_loose.mjs +204 -0
  18. package/src/core/freeze.mjs +38 -0
  19. package/src/core/health.mjs +92 -0
  20. package/src/core/hubs.mjs +92 -0
  21. package/src/core/label.mjs +87 -0
  22. package/src/core/label_intel.mjs +385 -0
  23. package/src/core/lint.mjs +50 -0
  24. package/src/core/memory.mjs +39 -0
  25. package/src/core/memory_intel.mjs +203 -0
  26. package/src/core/memory_lock.mjs +64 -0
  27. package/src/core/memory_write.mjs +357 -0
  28. package/src/core/memory_write_cli.mjs +51 -0
  29. package/src/core/onboard.mjs +3 -3
  30. package/src/core/organize.mjs +297 -0
  31. package/src/core/orphans.mjs +139 -0
  32. package/src/core/recall.mjs +49 -21
  33. package/src/core/recall_corpus.mjs +26 -0
  34. package/src/core/recall_lexical.mjs +26 -3
  35. package/src/core/reindex.mjs +48 -0
  36. package/src/core/render.mjs +197 -0
  37. package/src/core/resolve_target.mjs +92 -0
  38. package/src/core/retention.mjs +67 -0
  39. package/src/core/semantic.mjs +43 -7
  40. package/src/core/session_lint.mjs +119 -0
  41. package/src/core/session_pulse.mjs +73 -0
  42. package/src/core/session_write.mjs +231 -0
  43. package/src/core/status.mjs +33 -30
  44. package/src/core/structure_audit.mjs +107 -0
  45. package/src/core/sync.mjs +140 -0
  46. package/src/core/tidy.mjs +167 -0
  47. package/src/mcp/server.mjs +13 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,67 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - No unreleased changes recorded.
6
+
7
+ ## 0.1.0-alpha.10
8
+
9
+ - Output consistency hardening (from an adversarial cross-review of alpha.9): the rendered command block is now ASCII-clean by construction and byte-identical between the CLI and the MCP. `render.mjs` sanitizes every cell, note, and title (glyphs like the heavy check/x, stop sign, bullet, and any emoji map to ASCII or are stripped; status stays a word), so a glyph in a payload field or in user input renders clean instead of leaking or crashing. The MCP `renderedResult` now appends the same trailing newline the CLI adds (CLI == MCP byte-identical) and falls back to JSON only when a rendered block is truly absent. `digest` always carries a non-empty rendered block (an unknown session no longer yields empty MCP output). `status` sanitizes user-supplied ids in its error paths. Added tests: `sanitizeAscii` mapping/stripping, a glyph-in-field renders clean, and MCP output equals the CLI byte-for-byte for `neurain_session_status` and `neurain_compile`. npm test 169/169, readiness 100.
10
+
11
+
12
+ ## 0.1.0-alpha.9
13
+
14
+ - Output consistency (single render source): added `src/core/render.mjs` as the one place that renders user-facing command output, so every host shows the same block. `status`, `compile`, `sync`, and `capture` now build their output via `renderX(payload)`, expose it as `payload.rendered`, and return it as `{ text }` (the existing `digest.mjs` pattern). The MCP server's user-facing view tools (`neurain_session_status`, `neurain_compile`, `neurain_digest_preview`) now return that rendered markdown block instead of `JSON.stringify(payload)`, so an MCP host and the CLI emit byte-identical bytes; diagnostic/eval MCP tools still return JSON. Output is clean GitHub-flavored markdown tables, ASCII only, with status as words (ok/warn/missing/blocked) and no status glyphs or dashes. The `--json` payload shape is unchanged (machine consumers unaffected). Added `test/render.test.mjs` (per-renderer ascii/table/determinism, CLI-text == payload.rendered, MCP returns markdown not JSON). npm test 167/167, readiness 100.
15
+
16
+
17
+ ## 0.1.0-alpha.8
18
+
19
+ - Privacy (exact recall freshness): the exact-token branch reads from the SQLite FTS index, which is a cache rebuilt only on demand, so a markdown file that was public when indexed but has since turned `sensitivity: private`, been deleted, or gained a secret could linger in `recall search` and `hybrid-search` exact results until the next rebuild. `searchRecall` now re-gates the returned markdown paths against the CURRENT files (same private/secret/exists gate the markdown branches already apply) and drops any that are no longer safe. Only the top-K returned paths are re-read, so the exact branch stays fast (~4ms warm); a fresh index drops nothing, so results are unchanged (golden 9/9 identical). Event and receipt rows keep their own collection-time gating and pass through. Added `test/perf_recall_equivalence.test.mjs` coverage that a public-then-private and a deleted file do not surface from a stale index.
20
+
21
+
22
+ ## 0.1.0-alpha.7
23
+
24
+ - Hardening (recall perf, from an adversarial review): lock the "byte-identical results" claim and tighten the fast-path contracts, with no change to ranking/scores (golden-identical).
25
+
26
+ - Added `test/perf_recall_equivalence.test.mjs`: an oracle test that `countOccurrences` equals `split(term).length - 1` for every non-empty term (overlap/unicode/surrogate cases included), a proof that `scorePreparedSemantic` (prepare-once + per-doc trigram precompute) equals a naive per-doc reference scorer, a determinism check, and a guard that the shared corpus selector keeps private + secret-bearing files out of every branch.
27
+ - Extracted the lexical BM25 term-frequency count into an exported `countOccurrences(haystack, needle)` with an empty-needle guard (returns 0) so the index-loop form can never spin even if the term filter changes.
28
+ - `buildLexicalContext` now throws if a caller passes shared `markdownFiles` together with an `area` (the only safe share is whole-vault; an area context selects a strict subset, so this prevents a future caller from silently widening the corpus).
29
+ - `prepareSemanticQuery` now returns a frozen prepared query, and the provider fast-path contract (prepared query is immutable, no cross-call mutable state) is documented on the default provider.
30
+
31
+ ## 0.1.0-alpha.6
32
+
33
+ - Performance (hybrid recall): `hybrid-search` now walks the markdown corpus ONCE and shares it across its semantic and routed-lexical branches instead of each branch re-walking and re-reading the whole vault. The walk is shared only when no `--area` is set (the two branches then select the same whole-vault corpus); with an area they still walk independently. Results stay byte-identical (golden-verified) because the shared file list is exactly what each branch would have walked. Measured: `recall hybrid-search` ~970ms -> ~763ms (warm median); combined with alpha.5 that is ~1234ms -> ~763ms (-38%). npm test 153/153.
34
+
35
+
36
+ ## 0.1.0-alpha.5
37
+
38
+ - Performance (recall processing): cut recall/search processing time without changing results. The semantic scorer now prepares the query once and precomputes per-doc trigrams (instead of re-tokenizing the query and rebuilding `charTrigrams` per document), and the lexical BM25 counts term frequency with an index loop instead of `String.split`. Measured: `recall hybrid-search` ~1234ms -> ~970ms, `semantic-search` ~1031ms -> ~750ms (warm median), with byte-identical ranking/scores/matched_terms (golden-verified) and npm test 153/153.
39
+
40
+
41
+ ## 0.1.0-alpha.4
42
+
43
+ - Performance: lazy dynamic-import CLI dispatch. Each command now imports only its own `core/*.mjs` module on demand instead of loading all ~54 command modules on every invocation. Engine subprocess latency drops ~60-75ms across the board (`tidy` 150->90ms, `structure-audit` 120->60ms, `--help`/`--version` 50ms), with no change to the command surface or behavior (npm test 153/153; reviewed).
44
+
45
+
46
+ ## 0.1.0-alpha.3
47
+
48
+ - Destination resolver (`resolve_target.mjs`): `capture` and `compile` now derive one canonical target path from area, write intent, and sensitivity instead of leaving placement to the caller. Private, multi-area, and decision-required items still gate on the `<N>건 저장 진행` confirmation and are never auto-filed.
49
+
50
+ - Janitor (`neurain tidy`): read-only detection of provably-junk residue only (empty Obsidian `Untitled*.canvas/.base`, zero-byte daily notes, stray empty top-level directories); quarantine is recoverable (30-day trash). Registered areas, archives, and structural directories are never touched.
51
+ - Auto-filer (`neurain file`): finds loose durable markdown outside any canonical home and, gated by confirmation, relocates it recoverably and secret-gated; also surfaces read-only `raw/_inbox` routing proposals. Registered area roots and root-contract files are excluded.
52
+ - Structure audit (`neurain structure-audit`): advisory flags for area-adapter gaps, the `output/` vs `outputs/` split, and `sources_map` drift; composed into `lint`. Never writes and never fails a build.
53
+ - New-user organize (`neurain organize`): turns an unknown folder into a searchable area. Reuses the adopt scan, decides a per-file disposition, and on a gated apply scaffolds the area in copy mode (originals are never moved or deleted) and reindexes; hash-keyed receipt with `--rollback`. Secrets and already-structured KBs are protected.
54
+ - Organize and adopt preserve non-ASCII (for example Korean) folder names as area slugs.
55
+
56
+ ## 0.1.0-alpha.2
57
+
58
+ - Documentation currency gate: README and development-status version strings are asserted against package.json in readiness; volatile metric snapshots de-hardcoded (verified green in CI, not pinned in docs).
59
+
60
+
61
+ ## 0.1.0-alpha.1
62
+
63
+ - Capture: file/asset capture + R2/R4/R6 enrichment (overlap candidates, numeric conflicts, sha256 duplicate detection) ported to the engine.
64
+ - Memory-write: completed the generic ledger-primitive set (appendLine/enqueue/setQueueStatus/appendBlock/writeFileGuarded) so a consumer can forward every fact/task primitive to the engine.
65
+ - Release automation: tag-triggered npm publish via Trusted Publishing (OIDC), a push-CI gate (fast readiness + `npm publish --dry-run`), and a prerelease-never-`latest` dist-tag guard.
5
66
  - Expanded the recall markdown corpus to the general area knowledge class (`10_areas/<area>/**`, hubs, area registry), with config-extensible include/exclude (`recall.include`/`recall.exclude`).
6
67
  - Added label-based privacy gating (`labels.mjs`): per-file frontmatter `sensitivity`, area baseline from `_area.md`, and boundary-aware path markers exclude private content at index time. Fixes a substring marker that could exclude a whole legitimate folder.
7
68
  - Added a routed lexical recall branch (`recall_lexical.mjs`): BM25 + structural/layer/entity/domain/fact-ledger boosts, unioned ahead of exact-token and semantic in hybrid search. Auto-enabled when a search index registry has areas or `--area` is set; off by default on a bare vault.
package/README.md CHANGED
@@ -126,7 +126,7 @@ The canonical product development snapshot is maintained in:
126
126
  - `docs/development-status.en.md`
127
127
  - `docs/development-status.kr.md`
128
128
 
129
- As of 2026-06-09 KST, the package is a publish-ready alpha for private beta or alpha user tests. It is not public SaaS GA. The current verified gates are `npm test` 37/37, `node scripts/readiness.mjs --json` score 100, and `npm run readiness -- --json` score 100.
129
+ The package is a publish-ready alpha for private beta or alpha user tests, not public SaaS GA. All gates (`npm test`, the readiness leakage/secret/pack/tarball-install checks, and `npm audit`) run green in CI on every release — see `.github/workflows/release.yml`. The live score is `node scripts/readiness.mjs --json`.
130
130
 
131
131
  Watch reports are also read-only. They do not start a daemon and do not write durable knowledge. They observe recent text files, event journal entries, recap hints, and lesson candidates so a future review worker can decide what deserves attention.
132
132
 
@@ -159,7 +159,7 @@ For multi-area vaults, preview commands do not scan every area by default. Use `
159
159
  See also:
160
160
 
161
161
  - `docs/knowledge-os.en.md`
162
- - `docs/self-improve-90-roadmap.en.md`
162
+ - `docs/self-improvement-90-roadmap.en.md`
163
163
  - `docs/connect-runtime.en.md`
164
164
 
165
165
  ## Existing Folder Adoption
@@ -176,6 +176,22 @@ Neurain scans first and recommends one of three modes:
176
176
 
177
177
  Writes require an explicit confirmation phrase shown by the scan.
178
178
 
179
+ ## Keeping the Vault Tidy
180
+
181
+ Neurain keeps a working vault organized through read-only detection plus gated, recoverable writes. Nothing here moves or deletes a file without an explicit confirmation phrase, and registered areas, archives, and root-contract files are never touched.
182
+
183
+ ```bash
184
+ npx neurain tidy ~/NeurainDemo # report provably-junk residue (read-only)
185
+ npx neurain structure-audit ~/NeurainDemo # advisory structure flags (read-only)
186
+ npx neurain file ~/NeurainDemo # propose homes for loose docs (read-only)
187
+ npx neurain organize ~/SomeUnknownFolder --dry-run # turn an unknown folder into a searchable area
188
+ ```
189
+
190
+ - `tidy` detects only provably-junk residue (empty Obsidian `Untitled*` canvas/base files, zero-byte daily notes, stray empty top-level directories) and quarantines recoverably.
191
+ - `structure-audit` is advisory only: it flags area-adapter gaps, the `output/` vs `outputs/` split, and `sources_map` drift, and never fails a build.
192
+ - `file` relocates loose durable markdown into its canonical home behind a confirmation gate, and surfaces `raw/_inbox` routing proposals.
193
+ - `organize` turns an unknown folder into a searchable area in copy mode (originals are never moved or deleted), with a hash-keyed receipt and `--rollback`. Secrets and already-structured knowledge bases are protected.
194
+
179
195
  ## MCP
180
196
 
181
197
  The alpha MCP server is stdio-only:
@@ -188,7 +204,7 @@ It exposes read/capture/scan/preview tools only. It does not silently compile, p
188
204
 
189
205
  ## Status
190
206
 
191
- This is `0.1.0-alpha.0`. It is not a public SaaS GA release. The alpha exists to prove installability, local-first onboarding, Codex, Claude, Gemini, and Runtime connectivity, plus safety receipts.
207
+ This is `0.1.0-alpha.10`. It is not a public SaaS GA release. The alpha exists to prove installability, local-first onboarding, Codex, Claude, Gemini, and Runtime connectivity, plus safety receipts.
192
208
 
193
209
  Alpha publish command:
194
210
 
@@ -45,7 +45,7 @@ Safety boundary:
45
45
  - Durable Neurain writes still require explicit CLI confirmation.
46
46
  - The alpha snippet does not support paths containing newline or control bytes.
47
47
  - Scheduler access is read-only and cannot install background jobs or start daemons.
48
- - Scheduler eval access is read-only and checks trigger precision, trigger recall, no-recursion, private-boundary handling, case-file size limits, and target-root non-write snapshots.
48
+ - Scheduler eval access is read-only and checks trigger precision, trigger recall, no-recursion, private-boundary handling, case-file size limits, temp cleanup, and target-root non-write snapshots.
49
49
  - Lifecycle access is read-only through MCP. The runtime can inspect session lineage, while deeper lifecycle event emission uses the separate host-proxy contract below.
50
50
 
51
51
  ## Lifecycle Boundary
@@ -45,7 +45,7 @@ Safety boundary:
45
45
  - Durable Neurain write는 여전히 explicit CLI confirmation이 필요합니다.
46
46
  - Alpha snippet은 newline 또는 control byte가 포함된 path를 지원하지 않습니다.
47
47
  - Scheduler access는 read-only이며 background job을 설치하거나 daemon을 시작할 수 없습니다.
48
- - Scheduler eval access도 read-only이며 trigger precision, trigger recall, no-recursion, private-boundary handling, case-file size limit, target-root non-write snapshot을 확인합니다.
48
+ - Scheduler eval access도 read-only이며 trigger precision, trigger recall, no-recursion, private-boundary handling, case-file size limit, temp cleanup, target-root non-write snapshot을 확인합니다.
49
49
  - Lifecycle access는 MCP에서 read-only입니다. Runtime은 session lineage를 읽을 수 있고, 더 깊은 lifecycle event emission은 아래 host-proxy contract를 사용합니다.
50
50
 
51
51
  ## Lifecycle Boundary
@@ -1,9 +1,9 @@
1
1
  # Development Status
2
2
 
3
3
  Version: v0.1
4
- Last updated: 2026-06-09 KST
5
- Package: `neurain@0.1.0-alpha.0`
6
- Latest documented commit: `3496262 Add E24 onboarding and Gemini MCP connector`
4
+ Last updated: 2026-06-21 KST
5
+ Package: `neurain@0.1.0-alpha.10`
6
+ Latest documented commit: `fc907c2 fix(render): ascii-clean by construction + CLI==MCP byte-identical`
7
7
 
8
8
  This document is the canonical product development snapshot for the public package. It tracks what is shipped, what has evidence, and what must not be claimed yet.
9
9
 
@@ -11,16 +11,9 @@ This document is the canonical product development snapshot for the public packa
11
11
 
12
12
  Neurain is a publish-ready alpha CLI and MCP package. It is ready for private beta or alpha user tests, not public SaaS GA.
13
13
 
14
- Current verified score:
14
+ Current verification: every gate runs green in CI on each release — `npm test`, the full `npm run readiness` suite (leakage/secret scan, `npm audit`, `npm pack --dry-run`, and a temporary tarball-install smoke), and `node scripts/readiness.mjs --json` for the live score. See `.github/workflows/release.yml`.
15
15
 
16
- | Gate | Result |
17
- |---|---:|
18
- | `npm test` | 37/37 pass |
19
- | `node scripts/readiness.mjs --json` | `ok:true`, score `100`, 35 checks |
20
- | `npm run readiness -- --json` | `ok:true`, score `100` |
21
- | `npm audit` | 0 vulnerabilities |
22
- | `npm pack --dry-run` | pass |
23
- | Temporary tarball install smoke | pass |
16
+ Volatile metrics (exact test counts, readiness scores, check counts) are intentionally NOT pinned in this document — they are verified green in CI, not hand-copied, so this snapshot cannot silently drift from the suite.
24
17
 
25
18
  ## Shipped Development
26
19
 
@@ -28,6 +21,7 @@ Current verified score:
28
21
 
29
22
  - Starter vault initialization with `neurain init`.
30
23
  - Existing folder adoption scan, apply, receipt, and rollback.
24
+ - Unified `reindex` (register areas + per-area index refresh + recall rebuild in one pass; curated indexes are never auto-overwritten); `adopt --apply` auto-reindexes so an adopted folder is registered and searchable in one command.
31
25
  - Local doctor, search, capabilities, recap, and wrap preview.
32
26
  - Event journal add/list/verify with explicit confirmation.
33
27
  - Lesson list, candidates, eval, promotion, and rollback.
@@ -97,7 +91,7 @@ Status: shipped as publish-ready alpha.
97
91
  - README, quickstart, safety, privacy, support, pricing, troubleshooting, and release checklist exist.
98
92
  - CI workflow, issue templates, PR template, license, and security doc exist.
99
93
  - Full readiness verifies npm audit, pack dry-run, and temporary tarball install smoke.
100
- - Honest scope: npm publish or package name reservation still requires an explicit release action.
94
+ - Status: npm publish is done. `neurain@0.1.0-alpha.3` is published under the `alpha` tag and the package name is secured.
101
95
 
102
96
  ### E26: Thin MCP Connector
103
97
 
@@ -110,6 +104,18 @@ Status: shipped.
110
104
  - MCP server exposes bounded alpha tools for status, search, scan, recall, eval, live-case scaffold, lesson preview, scheduler preview, lifecycle report/eval, and wrap preview.
111
105
  - MCP does not expose silent durable wiki writes, lifecycle emit, daemon run/stop, curator write, recall rebuild write, or lesson promotion.
112
106
 
107
+ ### E27: Auto-Organization System
108
+
109
+ Status: shipped.
110
+
111
+ - CLI: `neurain tidy`, `neurain file`, `neurain structure-audit`, `neurain organize`.
112
+ - Destination resolver: `capture` and `compile` derive one canonical target path from area, write intent, and sensitivity. Private, multi-area, and decision-required items still gate on the `<N>건 저장 진행` confirmation and are never auto-filed.
113
+ - Janitor (`tidy`) detects provably-junk residue only (empty Obsidian canvas/base files, zero-byte daily notes, stray empty top-level directories). Read-only; quarantine is recoverable. Registered areas, archives, and structural directories are never touched.
114
+ - Auto-filer (`file`) relocates loose durable markdown into its canonical home behind a confirmation gate, recoverably and secret-gated, and surfaces read-only `raw/_inbox` routing proposals.
115
+ - Structure audit (`structure-audit`) is advisory: it flags area-adapter gaps, the `output/` vs `outputs/` split, and `sources_map` drift, and composes into `lint` without ever writing or failing a build.
116
+ - New-user organize (`organize`) turns an unknown folder into a searchable area in copy mode (originals are never moved or deleted), with a hash-keyed receipt and `--rollback`. Secrets and already-structured KBs are protected. Non-ASCII (for example Korean) folder names are preserved as area slugs.
117
+ - Honest scope: these are vault-hygiene helpers. Detection is read-only and every relocation is gated and recoverable; Neurain does not autonomously reorganize a user's folder.
118
+
113
119
  ## Current Connector Truth Table
114
120
 
115
121
  | Host | MCP connection | Lifecycle hook preview | Notes |
@@ -125,7 +131,6 @@ Status: shipped.
125
131
  - Non-developer GUI.
126
132
  - Hosted control plane.
127
133
  - Public Trust Center and status page.
128
- - npm publish or package name reservation.
129
134
  - Public GitHub publication if the repo is intentionally kept private during beta.
130
135
 
131
136
  ## Documentation Maintenance Rule
@@ -1,9 +1,9 @@
1
1
  # 개발 진행 상태
2
2
 
3
3
  Version: v0.1
4
- Last updated: 2026-06-09 KST
5
- Package: `neurain@0.1.0-alpha.0`
6
- Latest documented commit: `3496262 Add E24 onboarding and Gemini MCP connector`
4
+ Last updated: 2026-06-21 KST
5
+ Package: `neurain@0.1.0-alpha.10`
6
+ Latest documented commit: `fc907c2 fix(render): ascii-clean by construction + CLI==MCP byte-identical`
7
7
 
8
8
  이 문서는 public package 기준의 canonical 개발 상태 스냅샷입니다. 무엇이 shipped인지, 어떤 증거가 있는지, 아직 주장하면 안 되는 것이 무엇인지 함께 기록합니다.
9
9
 
@@ -11,16 +11,9 @@ Latest documented commit: `3496262 Add E24 onboarding and Gemini MCP connector`
11
11
 
12
12
  Neurain은 publish-ready alpha CLI 및 MCP package입니다. private beta 또는 alpha user test에는 들어갈 수 있지만 public SaaS GA는 아닙니다.
13
13
 
14
- 현재 검증된 상태:
14
+ 현재 검증: 매 릴리스마다 모든 게이트가 CI에서 green으로 통과합니다 — `npm test`, 전체 `npm run readiness`(leakage/secret 스캔, `npm audit`, `npm pack --dry-run`, 임시 tarball-install smoke), 라이브 점수는 `node scripts/readiness.mjs --json`. `.github/workflows/release.yml` 참조.
15
15
 
16
- | Gate | Result |
17
- |---|---:|
18
- | `npm test` | 37/37 pass |
19
- | `node scripts/readiness.mjs --json` | `ok:true`, score `100`, 35 checks |
20
- | `npm run readiness -- --json` | `ok:true`, score `100` |
21
- | `npm audit` | 0 vulnerabilities |
22
- | `npm pack --dry-run` | pass |
23
- | Temporary tarball install smoke | pass |
16
+ 휘발성 지표(정확한 테스트 수·readiness 점수·check 수)는 의도적으로 이 문서에 박지 않습니다 — CI에서 green으로 검증되며 손으로 복사하지 않으므로, 이 스냅샷이 suite와 조용히 어긋나지 않습니다.
24
17
 
25
18
  ## shipped 개발 범위
26
19
 
@@ -28,6 +21,7 @@ Neurain은 publish-ready alpha CLI 및 MCP package입니다. private beta 또는
28
21
 
29
22
  - `neurain init` starter vault initialization.
30
23
  - 기존 폴더 adoption scan, apply, receipt, rollback.
24
+ - 통합 `reindex`(영역 등록 + per-area 색인 새로고침 + recall rebuild를 한 번에; 큐레이티드 색인은 자동 덮어쓰기 안 함); `adopt --apply`가 자동 reindex하여 채택한 폴더가 한 명령으로 등록·검색 가능.
31
25
  - local doctor, search, capabilities, recap, wrap preview.
32
26
  - 명시적 confirmation이 필요한 event journal add/list/verify.
33
27
  - lesson list, candidates, eval, promotion, rollback.
@@ -97,7 +91,7 @@ Status: publish-ready alpha로 shipped.
97
91
  - README, quickstart, safety, privacy, support, pricing, troubleshooting, release checklist가 존재합니다.
98
92
  - CI workflow, issue template, PR template, license, security doc이 존재합니다.
99
93
  - full readiness가 npm audit, pack dry-run, temporary tarball install smoke를 검증합니다.
100
- - 정직한 범위: npm publish 또는 package name reservation은 별도 release action이 필요합니다.
94
+ - 상태: npm publish 완료. `neurain@0.1.0-alpha.3`가 `alpha` 태그로 발행됐고 패키지 이름도 선점됨.
101
95
 
102
96
  ### E26: Thin MCP Connector
103
97
 
@@ -110,6 +104,18 @@ Status: shipped.
110
104
  - MCP server는 status, search, scan, recall, eval, live-case scaffold, lesson preview, scheduler preview, lifecycle report/eval, wrap preview용 bounded alpha tool을 노출합니다.
111
105
  - MCP는 silent durable wiki write, lifecycle emit, daemon run/stop, curator write, recall rebuild write, lesson promotion을 노출하지 않습니다.
112
106
 
107
+ ### E27: 자동 정리 시스템
108
+
109
+ Status: shipped.
110
+
111
+ - CLI: `neurain tidy`, `neurain file`, `neurain structure-audit`, `neurain organize`.
112
+ - 목적지 리졸버: `capture`·`compile`이 영역·쓰기의도·민감도로부터 단일 canonical 목적지 경로를 도출합니다. private·다중영역·결정필요 항목은 여전히 `<N>건 저장 진행` 확인을 거치며 절대 자동 파일링되지 않습니다.
113
+ - Janitor(`tidy`)는 증명 가능한 잔여물만 탐지합니다(빈 Obsidian canvas/base 파일, 0바이트 데일리노트, 빈 최상위 디렉터리). 읽기전용이고 격리는 복구 가능하며, 등록된 영역·아카이브·구조 디렉터리는 건드리지 않습니다.
114
+ - Auto-filer(`file`)는 떠도는 durable 마크다운을 확인 게이트 뒤에서 복구 가능·시크릿 게이트로 canonical 위치로 옮기고, `raw/_inbox` 라우팅 제안을 읽기전용으로 보여줍니다.
115
+ - 구조 점검(`structure-audit`)은 권고용입니다: 영역 어댑터 결손, `output/` 대 `outputs/` 분리, `sources_map` 드리프트를 표시하고 `lint`에 합성되며, 쓰기나 빌드 실패를 일으키지 않습니다.
116
+ - 신규 사용자 organize(`organize`)는 미지의 폴더를 검색 가능한 영역으로 COPY 모드로 전환합니다(원본은 절대 이동·삭제 안 함). 해시 키 receipt + `--rollback`. 시크릿과 이미 구조화된 KB는 보호됩니다. 비ASCII(예: 한글) 폴더명은 영역 슬러그로 보존됩니다.
117
+ - 정직한 범위: 이들은 vault 위생 도구입니다. 탐지는 읽기전용이고 모든 재배치는 게이트·복구 가능하며, Neurain이 사용자 폴더를 자율적으로 재편하지 않습니다.
118
+
113
119
  ## 현재 connector truth table
114
120
 
115
121
  | Host | MCP connection | Lifecycle hook preview | Notes |
@@ -125,7 +131,6 @@ Status: shipped.
125
131
  - non-developer GUI.
126
132
  - hosted control plane.
127
133
  - public Trust Center 및 status page.
128
- - npm publish 또는 package name reservation.
129
134
  - beta 동안 repo를 private으로 유지한다면 public GitHub publication.
130
135
 
131
136
  ## 문서 유지관리 규칙
@@ -2,7 +2,40 @@
2
2
 
3
3
  Use this checklist before publishing an alpha release.
4
4
 
5
- ## Local Checks
5
+ ## Automated Release (preferred)
6
+
7
+ Releases are automated. You only decide WHEN to ship:
8
+
9
+ ```bash
10
+ npm version prerelease --preid=alpha # 0.1.0-alpha.0 -> 0.1.0-alpha.1 (commits + tags)
11
+ git push origin main --follow-tags # push the commit AND the new tag
12
+ ```
13
+
14
+ The tag push triggers `.github/workflows/release.yml`, which runs the full
15
+ readiness gate and then `npm publish --tag alpha` via npm Trusted Publishing
16
+ (OIDC) — no stored token, no interactive 2FA. Every push (untagged) is gated by
17
+ `.github/workflows/test.yml` (tests + fast readiness + `npm publish --dry-run`).
18
+
19
+ One-time setup: on npmjs.com, the package -> Settings -> Trusted Publisher ->
20
+ GitHub Actions, with this repo's owner/name and workflow `release.yml`.
21
+
22
+ ## Versioning policy
23
+
24
+ - Pre-1.0 alpha: increment with `npm version prerelease --preid=alpha`
25
+ (`-alpha.0 -> -alpha.1 -> ...`). Publish ALWAYS with `--tag alpha`; never move
26
+ the `latest` dist-tag to a prerelease. Only a deliberate stable (>= the first
27
+ non-prerelease) should ever hold `latest`.
28
+
29
+ ## Rollback / yank
30
+
31
+ - A published version cannot be overwritten and cannot be unpublished after 72h.
32
+ To pull a bad alpha: `npm deprecate neurain@<bad> "broken, use <good>"` and ship
33
+ a fixed `-alpha.<next>`.
34
+ - To roll the consuming vault back to a prior engine: point `NEURAIN_ENGINE_BIN`
35
+ at the previous tag's checkout (or pin the previous npm version), or set
36
+ `NEURAIN_LEGACY=1` to fall back to the vault's preserved implementations.
37
+
38
+ ## Local Checks (manual fallback)
6
39
 
7
40
  ```bash
8
41
  npm ci
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neurain",
3
- "version": "0.1.0-alpha.0",
3
+ "version": "0.1.0-alpha.10",
4
4
  "description": "Local-first Neurain Knowledge OS CLI and MCP connector.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -27,7 +27,10 @@
27
27
  "scripts": {
28
28
  "test": "node --test",
29
29
  "pack:dry-run": "npm pack --dry-run",
30
- "readiness": "node scripts/readiness.mjs --full"
30
+ "readiness": "node scripts/readiness.mjs --full",
31
+ "docs:check": "node scripts/sync-docs.mjs --check",
32
+ "docs:fix": "node scripts/sync-docs.mjs --write",
33
+ "version": "node scripts/sync-docs.mjs --write && git add -u README.md CHANGELOG.md docs/development-status.en.md docs/development-status.kr.md"
31
34
  },
32
35
  "dependencies": {
33
36
  "@modelcontextprotocol/sdk": "^1.17.5"
package/src/cli.mjs CHANGED
@@ -1,41 +1,62 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import path from 'node:path';
3
- import { adoptCommand, rollbackAdoption, scanAdoption } from './core/adopt.mjs';
4
- import { answerCommand } from './core/answer_eval.mjs';
5
- import { connectCommand } from './core/connect.mjs';
6
- import { capabilitiesCommand } from './core/capabilities.mjs';
7
- import { curatorCommand } from './core/curator.mjs';
8
- import { daemonCommand } from './core/daemon.mjs';
9
- import { doctorCommand } from './core/doctor.mjs';
10
- import { initCommand } from './core/init.mjs';
11
- import { journalCommand } from './core/journal.mjs';
12
- import { lessonsCommand } from './core/lessons.mjs';
13
- import { lifecycleCommand } from './core/lifecycle.mjs';
14
- import { liveCasesCommand } from './core/live_cases.mjs';
15
- import { onboardCommand } from './core/onboard.mjs';
16
- import { recapCommand } from './core/recap.mjs';
17
- import { recallCommand } from './core/recall.mjs';
18
- import { reviewCommand } from './core/review_worker.mjs';
19
- import { statusCommand } from './core/status.mjs';
20
- import { digestCommand } from './core/digest.mjs';
21
- import { queueCommand } from './core/queue.mjs';
22
- import { reviewQueueCommand } from './core/review_queue.mjs';
23
- import { routeCommand } from './core/route.mjs';
24
- import { planWritebackCommand } from './core/plan_writeback.mjs';
25
- import { flushCommand, sessionFlushCommand } from './core/flush.mjs';
26
- import { compileCommand } from './core/compile_desk.mjs';
27
- import { planReceiptCommand } from './core/plan_receipt.mjs';
28
- import { sourceDigestCommand } from './core/source_digest_gen.mjs';
29
- import { stageCommand } from './core/stage.mjs';
30
- import { queueArchiveCommand } from './core/queue_archive.mjs';
31
- import { captureCommand } from './core/capture_durable.mjs';
32
- import { completeCommand } from './core/complete.mjs';
33
- import { linkCheckCommand } from './core/link_check.mjs';
34
- import { schedulerCommand } from './core/scheduler.mjs';
35
- import { searchCommand } from './core/search.mjs';
36
- import { watchCommand } from './core/watch.mjs';
37
- import { wrapCommand } from './core/wrap.mjs';
38
- import { startMcpServer } from './mcp/server.mjs';
3
+
4
+ const COMMAND_HANDLERS = {
5
+ init: async (args) => (await import('./core/init.mjs')).initCommand(args),
6
+ onboard: async (args) => (await import('./core/onboard.mjs')).onboardCommand(args),
7
+ answer: async (args) => (await import('./core/answer_eval.mjs')).answerCommand(args),
8
+ doctor: async (args) => (await import('./core/doctor.mjs')).doctorCommand(args),
9
+ search: async (args) => (await import('./core/search.mjs')).searchCommand(args),
10
+ connect: async (args) => (await import('./core/connect.mjs')).connectCommand(args),
11
+ journal: async (args) => (await import('./core/journal.mjs')).journalCommand(args),
12
+ lifecycle: async (args) => (await import('./core/lifecycle.mjs')).lifecycleCommand(args),
13
+ 'live-cases': async (args) => (await import('./core/live_cases.mjs')).liveCasesCommand(args),
14
+ lessons: async (args) => (await import('./core/lessons.mjs')).lessonsCommand(args),
15
+ curator: async (args) => (await import('./core/curator.mjs')).curatorCommand(args),
16
+ daemon: async (args) => (await import('./core/daemon.mjs')).daemonCommand(args),
17
+ recall: async (args) => (await import('./core/recall.mjs')).recallCommand(args),
18
+ reindex: async (args) => (await import('./core/reindex.mjs')).reindexCommand(args),
19
+ status: async (args) => (await import('./core/status.mjs')).statusCommand(args),
20
+ digest: async (args) => (await import('./core/digest.mjs')).digestCommand(args),
21
+ queue: async (args) => (await import('./core/queue.mjs')).queueCommand(args),
22
+ 'review-queue': async (args) => (await import('./core/review_queue.mjs')).reviewQueueCommand(args),
23
+ route: async (args) => (await import('./core/route.mjs')).routeCommand(args),
24
+ 'plan-writeback': async (args) => (await import('./core/plan_writeback.mjs')).planWritebackCommand(args),
25
+ flush: async (args) => (await import('./core/flush.mjs')).flushCommand(args),
26
+ 'session-flush': async (args) => (await import('./core/flush.mjs')).sessionFlushCommand(args),
27
+ compile: async (args) => (await import('./core/compile_desk.mjs')).compileCommand(args),
28
+ 'plan-receipt': async (args) => (await import('./core/plan_receipt.mjs')).planReceiptCommand(args),
29
+ 'source-digest': async (args) => (await import('./core/source_digest_gen.mjs')).sourceDigestCommand(args),
30
+ stage: async (args) => (await import('./core/stage.mjs')).stageCommand(args),
31
+ 'queue-archive': async (args) => (await import('./core/queue_archive.mjs')).queueArchiveCommand(args),
32
+ capture: async (args) => (await import('./core/capture_durable.mjs')).captureCommand(args),
33
+ complete: async (args) => (await import('./core/complete.mjs')).completeCommand(args),
34
+ 'link-check': async (args) => (await import('./core/link_check.mjs')).linkCheckCommand(args),
35
+ 'session-lint': async (args) => (await import('./core/session_lint.mjs')).sessionLintCommand(args),
36
+ label: async (args) => (await import('./core/label.mjs')).labelCommand(args),
37
+ orphans: async (args) => (await import('./core/orphans.mjs')).orphansCommand(args),
38
+ hubs: async (args) => (await import('./core/hubs.mjs')).hubsCommand(args),
39
+ 'area-index': async (args) => (await import('./core/area_index.mjs')).areaIndexCommand(args),
40
+ lint: async (args) => (await import('./core/lint.mjs')).lintCommand(args),
41
+ tidy: async (args) => (await import('./core/tidy.mjs')).tidyCommand(args),
42
+ 'structure-audit': async (args) => (await import('./core/structure_audit.mjs')).structureAuditCommand(args),
43
+ file: async (args) => (await import('./core/file_loose.mjs')).fileCommand(args),
44
+ organize: async (args) => (await import('./core/organize.mjs')).organizeCommand(args),
45
+ 'session-pulse': async (args) => (await import('./core/session_pulse.mjs')).sessionPulseCommand(args),
46
+ sync: async (args) => (await import('./core/sync.mjs')).syncCommand(args),
47
+ health: async (args) => (await import('./core/health.mjs')).healthCommand(args),
48
+ memory: async (args) => (await import('./core/memory.mjs')).memoryCommand(args),
49
+ retention: async (args) => (await import('./core/retention.mjs')).retentionCommand(args),
50
+ freeze: async (args) => (await import('./core/freeze.mjs')).freezeCommand(args),
51
+ 'memory-write': async (args) => (await import('./core/memory_write_cli.mjs')).memoryWriteCommand(args),
52
+ backup: async (args) => (await import('./core/backup.mjs')).backupCommand(args),
53
+ capabilities: async (args) => (await import('./core/capabilities.mjs')).capabilitiesCommand(args),
54
+ recap: async (args) => (await import('./core/recap.mjs')).recapCommand(args),
55
+ watch: async (args) => (await import('./core/watch.mjs')).watchCommand(args),
56
+ review: async (args) => (await import('./core/review_worker.mjs')).reviewCommand(args),
57
+ scheduler: async (args) => (await import('./core/scheduler.mjs')).schedulerCommand(args),
58
+ wrap: async (args) => (await import('./core/wrap.mjs')).wrapCommand(args),
59
+ };
39
60
 
40
61
  export async function runCli(argv) {
41
62
  const [command, ...rest] = argv;
@@ -50,46 +71,17 @@ export async function runCli(argv) {
50
71
  }
51
72
 
52
73
  const args = parseArgs(rest);
53
- if (command === 'init') return render(await initCommand(args));
54
- if (command === 'onboard') return render(await onboardCommand(args));
55
74
  if (command === 'adopt') {
75
+ const { adoptCommand, rollbackAdoption } = await import('./core/adopt.mjs');
56
76
  if (args.rollback) return render(await rollbackAdoption(args));
57
77
  return render(await adoptCommand(args));
58
78
  }
59
- if (command === 'answer') return render(await answerCommand(args));
60
- if (command === 'doctor') return render(await doctorCommand(args));
61
- if (command === 'search') return render(await searchCommand(args));
62
- if (command === 'connect') return render(await connectCommand(args));
63
- if (command === 'journal') return render(await journalCommand(args));
64
- if (command === 'lifecycle') return render(await lifecycleCommand(args));
65
- if (command === 'live-cases') return render(await liveCasesCommand(args));
66
- if (command === 'lessons') return render(await lessonsCommand(args));
67
- if (command === 'curator') return render(await curatorCommand(args));
68
- if (command === 'daemon') return render(await daemonCommand(args));
69
- if (command === 'recall') return render(await recallCommand(args));
70
- if (command === 'status') return render(await statusCommand(args));
71
- if (command === 'digest') return render(await digestCommand(args));
72
- if (command === 'queue') return render(await queueCommand(args));
73
- if (command === 'review-queue') return render(await reviewQueueCommand(args));
74
- if (command === 'route') return render(await routeCommand(args));
75
- if (command === 'plan-writeback') return render(await planWritebackCommand(args));
76
- if (command === 'flush') return render(await flushCommand(args));
77
- if (command === 'session-flush') return render(await sessionFlushCommand(args));
78
- if (command === 'compile') return render(await compileCommand(args));
79
- if (command === 'plan-receipt') return render(await planReceiptCommand(args));
80
- if (command === 'source-digest') return render(await sourceDigestCommand(args));
81
- if (command === 'stage') return render(await stageCommand(args));
82
- if (command === 'queue-archive') return render(await queueArchiveCommand(args));
83
- if (command === 'capture') return render(await captureCommand(args));
84
- if (command === 'complete') return render(await completeCommand(args));
85
- if (command === 'link-check') return render(await linkCheckCommand(args));
86
- if (command === 'capabilities') return render(await capabilitiesCommand(args));
87
- if (command === 'recap') return render(await recapCommand(args));
88
- if (command === 'watch') return render(await watchCommand(args));
89
- if (command === 'review') return render(await reviewCommand(args));
90
- if (command === 'scheduler') return render(await schedulerCommand(args));
91
- if (command === 'wrap') return render(await wrapCommand(args));
92
- if (command === 'mcp') return startMcpServer(args);
79
+ const handler = COMMAND_HANDLERS[command];
80
+ if (handler) return render(await handler(args));
81
+ if (command === 'mcp') {
82
+ const { startMcpServer } = await import('./mcp/server.mjs');
83
+ return startMcpServer(args);
84
+ }
93
85
  if (command === 'selftest') return runSelftest();
94
86
 
95
87
  throw new Error(`Unknown command: ${command}\n\n${helpText()}`);
@@ -152,8 +144,11 @@ function helpText() {
152
144
  Usage:
153
145
  neurain init <folder> [--area general] [--lang ko|en] [--dry-run]
154
146
  neurain onboard <folder> [--lang ko|en] [--host codex|claude|gemini|runtime] [--json]
155
- neurain adopt <folder> [--dry-run] [--apply --confirm "<N>건 저장 진행"]
147
+ neurain adopt <folder> [--dry-run] [--apply --confirm "<N>건 저장 진행"] [--no-reindex]
156
148
  neurain adopt --rollback <receipt> [--root <folder>]
149
+ neurain organize <folder> [--apply --confirm "<N>건 저장 진행"] [--allow-secret] [--no-reindex] [--json]
150
+ neurain organize --rollback <receipt> --root <folder> [--json]
151
+ neurain reindex <folder> [--dry-run] [--json]
157
152
  neurain doctor <folder> [--json]
158
153
  neurain search <folder> <query> [--top 10] [--json]
159
154
  neurain journal list <folder> [--type type] [--top 20] [--json]
@@ -203,6 +198,23 @@ Usage:
203
198
  neurain capture <folder> [--text "..." | <text> | --file rel] [--session-id id] [--type memo] [--title t] [--area name] [--sensitivity s] [--intent i] [--allow-secret] [--dry-run] [--json]
204
199
  neurain complete <folder> --source-id id --compiled-to "a.md,b.md" [--status compiled] [--completed-at ts] [--dry-run] [--json]
205
200
  neurain link-check <folder> [--scope .] [--top 20] [--include-raw] [--include-archive] [--include-output] [--json]
201
+ neurain session-lint <folder> [--stale-days 7] [--now ms] [--json]
202
+ neurain label <folder> [--check | --apply] [--area name | --all] [--glob substr] [--limit N] [--json]
203
+ neurain orphans <folder> [--area name] [--fix] [--json]
204
+ neurain hubs <folder> [--check | --apply] [--area name] [--json]
205
+ neurain area-index <folder> [--build area | --refresh area | --register-curated area [--sensitivity private] | --detect [--fix] [--restamp-curated]] [--force] [--dry-run] [--json]
206
+ neurain lint <folder> [--json]
207
+ neurain tidy <folder> [--json]
208
+ neurain structure-audit <folder> [--json]
209
+ neurain file <folder> [--apply --confirm "<N>건 저장 진행"] [--allow-secret] [--json]
210
+ neurain session-pulse <folder> --session-id id --summary "..." [--focus t] [--next t] [--max-notes 8] [--no-area-brief] [--queue ...] [--now ts] [--dry-run] [--allow-secret] [--json]
211
+ neurain sync <folder> --session-id id [--summary "..."] [--level light|standard|full] [--no-pulse] [--no-area-brief] [--queue ...] [--now ts] [--dry-run] [--json]
212
+ neurain health <folder> [--fix] [--now ts] [--json]
213
+ neurain memory <folder> [--facts q | --tasks q | --conflicts | --verify | --stats] [--area name] [--top 10] [--json]
214
+ neurain retention <folder> [--area name] [--stale-days 120] [--write] [--now ms] [--json]
215
+ neurain freeze <folder> <acquire|release|status> [--owner name] [--json]
216
+ neurain memory-write <folder> --area name --file ledger.md --op add-fact|add-task|set-status|append-event [--fields json] [--changes json] [--event json] [--fact-id id] [--id id] [--apply] [--json]
217
+ neurain backup <folder> --target <dir> [--label name] | --verify <name> | --restore <name> [--to <dir>] | --drill <name> [--target <dir>] | --list [--json]
206
218
  neurain recap <folder> [--area name] [--json]
207
219
  neurain watch <folder> [--area name] [--since-minutes 1440] [--top 10] [--poll-once] [--json]
208
220
  neurain review <folder> [--area name] [--since-minutes 1440] [--top 10] [--json]
@@ -249,7 +261,16 @@ Alpha principles:
249
261
  - Source-digest and plan-receipt are W-B writers that touch only their own non-canonical artifacts: source-digest writes the rebuildable source-digest manifest (dry by default, --write to update) and plan-receipt writes an optional receipt under output/receipts/. Every durable write goes through an atomic temp+fsync+rename plus a cross-process lock, and the manifest's path sensitivity is decided by the label resolver, never hardcoded area names.
250
262
  - Stage is the secret gate: content lands in .neurain-staging (excluded from every walk), is scanned fail-closed for high-confidence secret shapes, mnemonics, context-aware hex keys, and high-entropy tokens, and is promoted to its canonical target by an atomic rename ONLY if clean; a hit holds the content in staging, leaves the target untouched, and exits 2. Queue-archive moves terminal queue rows into an append-only archive under one lock (archive appended before the hot-queue rewrite, so a crash never loses a row).
251
263
  - Capture and complete are the durable pipeline writers. They write raw markdown (capture, routed through the stage secret gate), the _inbox envelope, the writeback queue (capture appends, complete rewrites - both under a lock), and a wiki/log line, but they NEVER write session-state, handoff, or area-brief files: those stay W-D-owned, returned as an unapplied session_state_delta whose pending_count is advisory (the future vault shuttle recomputes at apply time). Capture is text-only in this increment; file capture and overlap/numeric enrichment are the documented flip gate.
252
- - Link-check (W-C) is a read-only graph validator: it resolves every markdown link and wikilink against the vault and reports unresolved targets by file and area, writing nothing. Area buckets use the configured structural dirs, not hardcoded area names.
264
+ - Link-check and session-lint (W-C) are read-only validators that write nothing. Link-check resolves every markdown link and wikilink against the vault and reports unresolved targets by file and area. Session-lint validates the session layer: required paths, session-state vs handoff consistency, pending-count drift vs the queue, handoff frontmatter, and queue rows referencing known sessions. Both use the configured structural dirs, not hardcoded area names.
265
+ - Label (W-C) is the label-system CLI: --check (default, read-only) proposes the managed label block (type/areas/entities/domains/sensitivity/tags) per knowledge file and reports private leaks; --apply writes it additively and idempotently, atomic per file, refusing symlinks. The label brain (label_intel) hard-codes nothing vault-specific — vocabulary comes from each area's entity dictionary and tuning from an optional label-config.json. A private/published file never gets entity/domain tags.
266
+ - Backup (W-E) is the local snapshot + EXACT restore + restore-drill: the manifest records files (sha256+size) and dirs, symlinks/special files are refused, and restore reconstructs the target byte-exactly (and proves it) via stage-verify-rename-aside-reverify, so a no-git vault still has a verifiable rollback. The macOS/iCloud encrypted external backup stays vault-side.
267
+ - Memory-write (W-E) is the registry-driven fact/task ledger writer: schemas, ID templates, mandatory/dash columns and paths come from memory-write-registry.json, so every area is handled by the same code. It appends rows or edits a fact's lifecycle columns while keeping every other row byte-identical, under an O_EXCL lock with a sha256 compare-and-swap (concurrent modification aborts) + fsync. Default dry-run; a live write needs the area's write_enabled:true, a realpath inside area_root, a fail-closed secret scan, and the freeze lock not held.
268
+ - Memory and retention (W-E, read-only) query the per-area fact/task ledgers via a config-driven reader that maps heterogeneous table schemas by column name. Memory does status-aware fact/task lookup, conflicts, and verify; retention flags active facts for human review (contradicted / unverified / stale / low-confidence / missing-metadata) and never deletes (optional --write emits a report). Neither touches the ledgers.
269
+ - Health (W-D) composes doctor + lint into one verdict and reconciles session-state drift: pending_count's source of truth is the writeback queue and last_pulse's is the handoff frontmatter, so a session can drift. Read-only without --fix; with --fix it repairs drift via the locked session-state path and runs the W-C maintenance fixers (orphans, label, area-index) before a final lint.
270
+ - Session-pulse (W-D) is the session working-memory writer: it pulses one durable note into the handoff, optionally the area brief and a queue row, then advances session-state. Hardened per cross-review: markdown is written first and the authoritative session-state.json last (a crash leaves the markdown ahead but state recoverable), every write is atomic, the area brief is rendered + labelled in memory and written once, and the note is secret-scanned fail-closed. session-state writes are this wave's (W-D) responsibility; W-B's capture/complete only returned a delta for it to apply.
271
+ - Lint (W-C) is a read-only STRUCTURAL aggregator: it composes the ported validators (link-check, session-lint, advisory orphans) into one health verdict. It deliberately does NOT re-implement the vault's documentation-governance checks (master version, clipper templates, instruction lengths, progressive disclosure) — those stay vault-side.
272
+ - Area-index (W-C) generates a per-area search index from the area's own signals (wiki/entities pages, frontmatter entity_candidates, area-tagged raw envelopes, salience-gated content terms), registers it, detects staleness via a file signature, and refreshes drifted generated indexes. Curated indexes are never auto-overwritten; --detect is read-only, writes happen only with --build/--refresh/--register-curated/--detect --fix.
273
+ - Orphans and hubs (W-C) are the graph connection layer. Orphans reports knowledge files with no inbound and no outbound wikilink, classified (structural / has-entities / no-entities); --fix adds one additive per-area graph-backlog page (source files untouched). Hubs generates per-entity hub pages linking member notes (dry-run by default, --apply writes and prunes only its own hub_generated pages); private areas and private files are excluded, member files are never modified.
253
274
  - Live-cases scaffold creates a redacted E23 reviewed-case pack scaffold with hash-only source refs. It does not claim human evidence and stores no raw source text or absolute paths.
254
275
  - Onboard is a read-only first-run guide for non-developers. It explains the next command without creating files or calling a model.
255
276
  - Answer eval checks faithfulness, citation accuracy, conflict surfacing, abstention, private boundaries, and stale-source handling without model calls or writes.