neurain 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/docs/connect-runtime.en.md +1 -1
- package/docs/connect-runtime.kr.md +1 -1
- package/docs/development-status.en.md +6 -12
- package/docs/development-status.kr.md +6 -12
- package/package.json +5 -2
- package/src/cli.mjs +4 -1
- package/src/core/adopt.mjs +13 -0
- package/src/core/area_index.mjs +32 -5
- package/src/core/reindex.mjs +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- No unreleased changes recorded.
|
|
6
|
+
|
|
7
|
+
## 0.1.0-alpha.2
|
|
8
|
+
|
|
9
|
+
- 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).
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## 0.1.0-alpha.1
|
|
13
|
+
|
|
14
|
+
- Capture: file/asset capture + R2/R4/R6 enrichment (overlap candidates, numeric conflicts, sha256 duplicate detection) ported to the engine.
|
|
15
|
+
- 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.
|
|
16
|
+
- 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
17
|
- 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
18
|
- 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
19
|
- 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
|
-
|
|
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-
|
|
162
|
+
- `docs/self-improvement-90-roadmap.en.md`
|
|
163
163
|
- `docs/connect-runtime.en.md`
|
|
164
164
|
|
|
165
165
|
## Existing Folder Adoption
|
|
@@ -188,7 +188,7 @@ It exposes read/capture/scan/preview tools only. It does not silently compile, p
|
|
|
188
188
|
|
|
189
189
|
## Status
|
|
190
190
|
|
|
191
|
-
This is `0.1.0-alpha.
|
|
191
|
+
This is `0.1.0-alpha.2`. 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
192
|
|
|
193
193
|
Alpha publish command:
|
|
194
194
|
|
|
@@ -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-
|
|
5
|
-
Package: `neurain@0.1.0-alpha.
|
|
6
|
-
Latest documented commit: `
|
|
4
|
+
Last updated: 2026-06-14 KST
|
|
5
|
+
Package: `neurain@0.1.0-alpha.2`
|
|
6
|
+
Latest documented commit: `66016ba docs(dev-status): note unified reindex + adopt auto-reindex in the command surface`
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# 개발 진행 상태
|
|
2
2
|
|
|
3
3
|
Version: v0.1
|
|
4
|
-
Last updated: 2026-06-
|
|
5
|
-
Package: `neurain@0.1.0-alpha.
|
|
6
|
-
Latest documented commit: `
|
|
4
|
+
Last updated: 2026-06-14 KST
|
|
5
|
+
Package: `neurain@0.1.0-alpha.2`
|
|
6
|
+
Latest documented commit: `66016ba docs(dev-status): note unified reindex + adopt auto-reindex in the command surface`
|
|
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
|
-
|
|
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neurain",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
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
|
@@ -36,6 +36,7 @@ import { labelCommand } from './core/label.mjs';
|
|
|
36
36
|
import { orphansCommand } from './core/orphans.mjs';
|
|
37
37
|
import { hubsCommand } from './core/hubs.mjs';
|
|
38
38
|
import { areaIndexCommand } from './core/area_index.mjs';
|
|
39
|
+
import { reindexCommand } from './core/reindex.mjs';
|
|
39
40
|
import { lintCommand } from './core/lint.mjs';
|
|
40
41
|
import { sessionPulseCommand } from './core/session_pulse.mjs';
|
|
41
42
|
import { syncCommand } from './core/sync.mjs';
|
|
@@ -81,6 +82,7 @@ export async function runCli(argv) {
|
|
|
81
82
|
if (command === 'curator') return render(await curatorCommand(args));
|
|
82
83
|
if (command === 'daemon') return render(await daemonCommand(args));
|
|
83
84
|
if (command === 'recall') return render(await recallCommand(args));
|
|
85
|
+
if (command === 'reindex') return render(await reindexCommand(args));
|
|
84
86
|
if (command === 'status') return render(await statusCommand(args));
|
|
85
87
|
if (command === 'digest') return render(await digestCommand(args));
|
|
86
88
|
if (command === 'queue') return render(await queueCommand(args));
|
|
@@ -180,8 +182,9 @@ function helpText() {
|
|
|
180
182
|
Usage:
|
|
181
183
|
neurain init <folder> [--area general] [--lang ko|en] [--dry-run]
|
|
182
184
|
neurain onboard <folder> [--lang ko|en] [--host codex|claude|gemini|runtime] [--json]
|
|
183
|
-
neurain adopt <folder> [--dry-run] [--apply --confirm "<N>건 저장 진행"]
|
|
185
|
+
neurain adopt <folder> [--dry-run] [--apply --confirm "<N>건 저장 진행"] [--no-reindex]
|
|
184
186
|
neurain adopt --rollback <receipt> [--root <folder>]
|
|
187
|
+
neurain reindex <folder> [--dry-run] [--json]
|
|
185
188
|
neurain doctor <folder> [--json]
|
|
186
189
|
neurain search <folder> <query> [--top 10] [--json]
|
|
187
190
|
neurain journal list <folder> [--type type] [--top 20] [--json]
|
package/src/core/adopt.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { absPath, compactStamp, ensureDir, exists, generatedPath, isTextFile, relPath, safeResolve, sha256, slug, timestamp, writeFileNoOverwrite } from './fs.mjs';
|
|
5
5
|
import { inferSensitivityFromPath, secretLike } from './safety.mjs';
|
|
6
|
+
import { reindexCommand } from './reindex.mjs';
|
|
6
7
|
|
|
7
8
|
const maxFileBytes = 20 * 1024 * 1024;
|
|
8
9
|
const maxFiles = 5000;
|
|
@@ -17,6 +18,17 @@ export async function adoptCommand(args) {
|
|
|
17
18
|
throw new Error(`Apply requires --confirm "${expected}".`);
|
|
18
19
|
}
|
|
19
20
|
scan.apply = applyAdoption(scan, { root: target });
|
|
21
|
+
// Once the adapters land, register the area and rebuild search in one pass so the
|
|
22
|
+
// adopted folder is immediately searchable. Best-effort: a reindex hiccup must
|
|
23
|
+
// never fail the adoption itself, and --no-reindex opts out.
|
|
24
|
+
if (scan.apply?.ok && !args['no-reindex']) {
|
|
25
|
+
try {
|
|
26
|
+
const r = await reindexCommand({ _: [target], json: true });
|
|
27
|
+
scan.reindex = r.payload || null;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
scan.reindex = { ok: false, error: String((error && error.message) || error) };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
}
|
|
21
33
|
if (args.json) return { json: true, payload: scan };
|
|
22
34
|
return {
|
|
@@ -32,6 +44,7 @@ export async function adoptCommand(args) {
|
|
|
32
44
|
`- Risks: ${scan.summary.risk_count}`,
|
|
33
45
|
`- Confirmation required: ${scan.confirmation_required}`,
|
|
34
46
|
scan.apply ? `- Apply receipt: ${scan.apply.receipt_path}` : '',
|
|
47
|
+
scan.reindex ? `- Reindex: ${scan.reindex.ok ? 'registered + searchable' : 'skipped (' + (scan.reindex.error || 'n/a') + ')'}` : '',
|
|
35
48
|
].filter(Boolean).join('\n'),
|
|
36
49
|
};
|
|
37
50
|
}
|
package/src/core/area_index.mjs
CHANGED
|
@@ -130,9 +130,15 @@ function highConfidenceEntities(ctx, area, relFiles) {
|
|
|
130
130
|
byCanonical.set(low, cur);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// The recall resolver (recall_intel.resolveAreaPath) reads `source_docs` and always
|
|
134
|
+
// resolves them against `${area_root}/`, so emit AREA-relative paths under that key.
|
|
135
|
+
// Evidence outside the area (raw inbox, wiki/entities hubs) can never resolve under
|
|
136
|
+
// area_root and is hard-excluded from the recall corpus anyway, so drop it here.
|
|
137
|
+
const areaPrefix = `${ctx.areasDir}/${area}/`;
|
|
138
|
+
const toAreaRel = (rel) => (rel.startsWith(areaPrefix) ? rel.slice(areaPrefix.length) : null);
|
|
133
139
|
return [...byCanonical.values()]
|
|
134
140
|
.filter((e) => e.confidence === 'high' || e.count >= 2)
|
|
135
|
-
.map((e) => ({ id: `entity:${area}:${slug(e.canonical)}`, type: e.type, canonical: e.canonical, aliases: [...e.aliases], confidence: e.confidence,
|
|
141
|
+
.map((e) => ({ id: `entity:${area}:${slug(e.canonical)}`, type: e.type, canonical: e.canonical, aliases: [...e.aliases], confidence: e.confidence, source_docs: [...e.evidence].map(toAreaRel).filter(Boolean).slice(0, 5) }));
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
function keywordsFromFolder(name) {
|
|
@@ -143,7 +149,7 @@ function domainsFromFolders(ctx, area) {
|
|
|
143
149
|
if (!fs.existsSync(dir)) return [];
|
|
144
150
|
return fs.readdirSync(dir, { withFileTypes: true })
|
|
145
151
|
.filter((d) => d.isDirectory() && !/^(search-index|_trash|_archive|node_modules)/.test(d.name) && !d.name.startsWith('.'))
|
|
146
|
-
.map((d) => ({ id: `domain:${area}:${slug(d.name)}`, label: d.name, keywords: keywordsFromFolder(d.name), boost_paths: [
|
|
152
|
+
.map((d) => ({ id: `domain:${area}:${slug(d.name)}`, label: d.name, keywords: keywordsFromFolder(d.name), boost_paths: [d.name] }))
|
|
147
153
|
.filter((d) => d.keywords.length);
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -154,11 +160,23 @@ function saveRegistry(ctx, reg) {
|
|
|
154
160
|
atomicWriteJson(path.join(ctx.root, ctx.registryPath), reg);
|
|
155
161
|
}
|
|
156
162
|
|
|
163
|
+
function hasCuratedSource(ctx, area) {
|
|
164
|
+
const dir = `${ctx.areasDir}/${area}/search-index`;
|
|
165
|
+
const ents = readJsonSafe(path.join(ctx.root, `${dir}/entities.json`), null);
|
|
166
|
+
const doms = readJsonSafe(path.join(ctx.root, `${dir}/domain-routing.json`), null);
|
|
167
|
+
return Array.isArray(ents) && ents.length > 0 && Array.isArray(doms);
|
|
168
|
+
}
|
|
169
|
+
|
|
157
170
|
function buildArea(ctx, area, { force = false } = {}) {
|
|
158
171
|
const reg = loadRegistry(ctx);
|
|
159
172
|
const existing = reg.areas?.[area];
|
|
160
173
|
if (existing?.curated) return { ok: false, area, error: 'curated index, refusing to overwrite (use the curated source)' };
|
|
161
174
|
if (existing && !force) return { ok: false, area, error: 'already registered; pass --force to rebuild a generated index' };
|
|
175
|
+
// An unregistered area may still carry hand-written index files on disk; never
|
|
176
|
+
// clobber them on an unforced build (register them as curated instead).
|
|
177
|
+
if (!existing && !force && hasCuratedSource(ctx, area)) {
|
|
178
|
+
return { ok: false, area, error: 'existing index files present; use --register-curated to keep them, or --force to overwrite' };
|
|
179
|
+
}
|
|
162
180
|
|
|
163
181
|
const relFiles = areaFiles(ctx, area);
|
|
164
182
|
const entities = highConfidenceEntities(ctx, area, relFiles);
|
|
@@ -175,7 +193,9 @@ function buildArea(ctx, area, { force = false } = {}) {
|
|
|
175
193
|
});
|
|
176
194
|
reg.areas = reg.areas || {};
|
|
177
195
|
const previous = reg.areas[area] || {};
|
|
178
|
-
|
|
196
|
+
// generated and curated are mutually exclusive registry states; assert curated:false
|
|
197
|
+
// so a (re)generated index can never keep a stale curated flag from a prior pass.
|
|
198
|
+
reg.areas[area] = { ...previous, area_root: `${ctx.areasDir}/${area}`, index_dir: 'search-index', entities: 'entities.json', domain_routing: 'domain-routing.json', generated: true, curated: false, signature: sig, path_map: previous.path_map || [] };
|
|
179
199
|
saveRegistry(ctx, reg);
|
|
180
200
|
}
|
|
181
201
|
return { ok: true, area, entities: entities.length, domains: domains.length, scanned: relFiles.length, signature: sig.slice(0, 12), dry_run: ctx.dryRun };
|
|
@@ -199,7 +219,9 @@ function registerCurated(ctx, area, { sensitivity } = {}) {
|
|
|
199
219
|
const reg = loadRegistry(ctx);
|
|
200
220
|
reg.areas = reg.areas || {};
|
|
201
221
|
const previous = reg.areas[area] || {};
|
|
202
|
-
|
|
222
|
+
// Clear any stale generated flag: register-curated must leave a clean curated-only
|
|
223
|
+
// state (the previous spread would otherwise preserve generated:true and contradict).
|
|
224
|
+
reg.areas[area] = { ...previous, area_root: `${ctx.areasDir}/${area}`, index_dir: 'search-index', entities: 'entities.json', domain_routing: 'domain-routing.json', curated: true, generated: false, ...(sensitivity ? { sensitivity } : {}), signature: sig, path_map: previous.path_map || [] };
|
|
203
225
|
saveRegistry(ctx, reg);
|
|
204
226
|
}
|
|
205
227
|
return { ok: true, area, mode: 'register-curated', entities: entities.length, domains: domains.length, scanned: relFiles.length, signature: sig.slice(0, 12), sensitivity: sensitivity || null, dry_run: ctx.dryRun };
|
|
@@ -237,7 +259,12 @@ function autoTidy(ctx, restampCurated) {
|
|
|
237
259
|
const { missing, stale_curated, stale_generated } = detect(ctx);
|
|
238
260
|
const reg = loadRegistry(ctx);
|
|
239
261
|
const built = []; const refreshed = []; const restamped_curated = [];
|
|
240
|
-
for (const area of missing)
|
|
262
|
+
for (const area of missing) {
|
|
263
|
+
// Preserve hand-written index files (register as curated) rather than overwrite
|
|
264
|
+
// them with a generated starter; only truly empty/absent indexes are built.
|
|
265
|
+
if (hasCuratedSource(ctx, area)) { if (registerCurated(ctx, area, {}).ok) restamped_curated.push(area); }
|
|
266
|
+
else if (buildArea(ctx, area, {}).ok) built.push(area);
|
|
267
|
+
}
|
|
241
268
|
for (const area of stale_generated) if (refresh(ctx, area).ok) refreshed.push(area);
|
|
242
269
|
if (restampCurated) {
|
|
243
270
|
for (const area of stale_curated) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { absPath } from './fs.mjs';
|
|
2
|
+
import { areaIndexCommand } from './area_index.mjs';
|
|
3
|
+
import { rebuildRecall } from './recall.mjs';
|
|
4
|
+
|
|
5
|
+
// Unified "make the whole vault's search current" orchestration. One command runs the
|
|
6
|
+
// two layers a fresh or just-changed vault needs:
|
|
7
|
+
// 1. per-area indexes: register any unregistered area, refresh stale GENERATED
|
|
8
|
+
// indexes, and restamp curated signatures. Hand-curated entity/domain files are
|
|
9
|
+
// never overwritten -- areas that drifted and need human re-curation are reported,
|
|
10
|
+
// not auto-changed.
|
|
11
|
+
// 2. the FTS5 recall index: rebuilt so all current markdown is searchable.
|
|
12
|
+
// Thin wrapper over existing primitives: idempotent and non-destructive.
|
|
13
|
+
export async function reindexCommand(args) {
|
|
14
|
+
const root = absPath(args._[0] || args.root || process.cwd());
|
|
15
|
+
const dryRun = Boolean(args['dry-run']);
|
|
16
|
+
|
|
17
|
+
const ai = await areaIndexCommand({ _: [root], detect: true, fix: !dryRun, 'restamp-curated': !dryRun, json: true });
|
|
18
|
+
const aix = ai.payload || {};
|
|
19
|
+
|
|
20
|
+
const recall = await rebuildRecall(root, { dryRun });
|
|
21
|
+
|
|
22
|
+
const payload = {
|
|
23
|
+
ok: recall.ok !== false,
|
|
24
|
+
command: 'reindex',
|
|
25
|
+
dry_run: dryRun,
|
|
26
|
+
area_index: {
|
|
27
|
+
built: aix.built || [],
|
|
28
|
+
refreshed: aix.refreshed || [],
|
|
29
|
+
restamped_curated: aix.restamped_curated || [],
|
|
30
|
+
needs_recuration: aix.needs_recuration || [],
|
|
31
|
+
},
|
|
32
|
+
recall: { indexed_count: recall.indexed_count, durable_write: recall.durable_write },
|
|
33
|
+
};
|
|
34
|
+
if (args.json) return { json: true, payload };
|
|
35
|
+
return {
|
|
36
|
+
text: [
|
|
37
|
+
'# Neurain reindex',
|
|
38
|
+
'',
|
|
39
|
+
`- Root: ${root}`,
|
|
40
|
+
`- Dry run: ${dryRun ? 'yes' : 'no'}`,
|
|
41
|
+
`- Areas registered/built: ${payload.area_index.built.length}`,
|
|
42
|
+
`- Generated areas refreshed: ${payload.area_index.refreshed.length}`,
|
|
43
|
+
`- Curated signatures restamped: ${payload.area_index.restamped_curated.length}`,
|
|
44
|
+
`- Needs hand re-curation: ${payload.area_index.needs_recuration.join(', ') || 'none'}`,
|
|
45
|
+
`- Recall docs indexed: ${payload.recall.indexed_count ?? '?'}`,
|
|
46
|
+
].join('\n'),
|
|
47
|
+
};
|
|
48
|
+
}
|