nlm-memory 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli/digest.js +39 -1
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/nlm.js +106 -6
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/scope-backfill.d.ts +34 -0
- package/dist/cli/scope-backfill.js +237 -0
- package/dist/cli/scope-backfill.js.map +1 -0
- package/dist/cli/scope-coverage.d.ts +44 -0
- package/dist/cli/scope-coverage.js +205 -0
- package/dist/cli/scope-coverage.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +14 -0
- package/dist/core/adapters/claude-code.js +30 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/dataset/build-dataset.js +1 -0
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +3 -3
- package/dist/core/digest/compose.js +16 -17
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +43 -0
- package/dist/core/digest/hook-injection.js +79 -0
- package/dist/core/digest/hook-injection.js.map +1 -0
- package/dist/core/exemplars/capture-from-session.d.ts +1 -0
- package/dist/core/exemplars/capture-from-session.js +1 -1
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
- package/dist/core/exemplars/extract-exemplar.js +7 -1
- package/dist/core/exemplars/extract-exemplar.js.map +1 -1
- package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
- package/dist/core/exemplars/ingest-exemplar.js +1 -0
- package/dist/core/exemplars/ingest-exemplar.js.map +1 -1
- package/dist/core/hook/claude-settings.d.ts +1 -1
- package/dist/core/hook/claude-settings.js +0 -1
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/ingest/backfill-derivables.d.ts +92 -0
- package/dist/core/ingest/backfill-derivables.js +175 -0
- package/dist/core/ingest/backfill-derivables.js.map +1 -0
- package/dist/core/ingest/ingest-session.js +20 -0
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.js +6 -0
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.js +5 -0
- package/dist/core/ingest/reprocess.js.map +1 -1
- package/dist/core/ingest/transcript-derivables.d.ts +27 -0
- package/dist/core/ingest/transcript-derivables.js +138 -0
- package/dist/core/ingest/transcript-derivables.js.map +1 -0
- package/dist/core/outcome/coverage.d.ts +33 -0
- package/dist/core/outcome/coverage.js +46 -0
- package/dist/core/outcome/coverage.js.map +1 -0
- package/dist/core/outcome/rollup.d.ts +33 -0
- package/dist/core/outcome/rollup.js +113 -0
- package/dist/core/outcome/rollup.js.map +1 -0
- package/dist/core/recall/hook-recall-log.d.ts +2 -2
- package/dist/core/recall/hook-recall-log.js +2 -2
- package/dist/core/recall-facts/fact-recall-service.js +1 -0
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.js +2 -0
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.js +34 -3
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/scope/alias-map.d.ts +10 -0
- package/dist/core/scope/alias-map.js +41 -0
- package/dist/core/scope/alias-map.js.map +1 -0
- package/dist/core/scope/derive-scope.d.ts +8 -0
- package/dist/core/scope/derive-scope.js +54 -0
- package/dist/core/scope/derive-scope.js.map +1 -0
- package/dist/core/scope/scope-clause.d.ts +16 -0
- package/dist/core/scope/scope-clause.js +21 -0
- package/dist/core/scope/scope-clause.js.map +1 -0
- package/dist/core/scope/stamp-flag.d.ts +1 -0
- package/dist/core/scope/stamp-flag.js +4 -0
- package/dist/core/scope/stamp-flag.js.map +1 -0
- package/dist/core/signals/code-signal.d.ts +1 -0
- package/dist/core/signals/code-signal.js +1 -0
- package/dist/core/signals/code-signal.js.map +1 -1
- package/dist/core/signals/ingest-signal.d.ts +1 -1
- package/dist/core/signals/ingest-signal.js +3 -1
- package/dist/core/signals/ingest-signal.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +18 -0
- package/dist/core/signals/stamp-scope.js +28 -0
- package/dist/core/signals/stamp-scope.js.map +1 -0
- package/dist/core/storage/pg-code-exemplar-store.js +6 -2
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-fact-ingest.d.ts +1 -1
- package/dist/core/storage/pg-fact-ingest.js +4 -4
- package/dist/core/storage/pg-fact-ingest.js.map +1 -1
- package/dist/core/storage/pg-session-store.d.ts +2 -2
- package/dist/core/storage/pg-session-store.js +31 -31
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.js +4 -3
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +1 -0
- package/dist/core/storage/pg-workstream-store.js +2 -1
- package/dist/core/storage/pg-workstream-store.js.map +1 -1
- package/dist/core/storage/sqlite-code-exemplar-store.js +4 -2
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +1 -1
- package/dist/core/storage/sqlite-fact-store.js +8 -5
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-outcome-store.d.ts +89 -0
- package/dist/core/storage/sqlite-outcome-store.js +158 -0
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +22 -2
- package/dist/core/storage/sqlite-session-store.js +33 -35
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.js +4 -2
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +1 -0
- package/dist/core/storage/sqlite-workstream-store.js +2 -1
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -1
- package/dist/core/storage/supersedence-log.js +2 -0
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/telemetry/probe-filter.d.ts +35 -0
- package/dist/core/telemetry/probe-filter.js +56 -0
- package/dist/core/telemetry/probe-filter.js.map +1 -0
- package/dist/core/update-check/check.d.ts +6 -0
- package/dist/core/update-check/check.js +35 -0
- package/dist/core/update-check/check.js.map +1 -1
- package/dist/core/workstream/bind.d.ts +2 -1
- package/dist/core/workstream/bind.js +28 -9
- package/dist/core/workstream/bind.js.map +1 -1
- package/dist/core/workstream/model.d.ts +1 -0
- package/dist/core/workstream/model.js.map +1 -1
- package/dist/hook/pre-compact-hook.js +2 -1
- package/dist/hook/pre-compact-hook.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +3 -2
- package/dist/hook/prompt-recall-hook.js +8 -4
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +2 -1
- package/dist/hook/recall-over-http.js +15 -2
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-start-hook.js +2 -1
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +2 -1
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +6 -0
- package/dist/http/app.js +26 -38
- package/dist/http/app.js.map +1 -1
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.js +101 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/outcome.d.ts +85 -0
- package/dist/ports/outcome.js +11 -0
- package/dist/ports/outcome.js.map +1 -0
- package/dist/ports/session-store.d.ts +0 -6
- package/dist/ports/workstream-store.d.ts +1 -0
- package/dist/shared/net.d.ts +3 -0
- package/dist/shared/net.js +4 -0
- package/dist/shared/net.js.map +1 -0
- package/dist/shared/types.d.ts +33 -0
- package/dist/shared/types.js +4 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/ui/assets/{index-CMpQkbQy.js → index-CSaBtgXD.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/migrations/029_project_scope.sql +21 -0
- package/migrations/032_session_derivables.sql +11 -0
- package/migrations/pg/030_project_scope.sql +23 -0
- package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
- package/migrations/pg/032_session_derivables.sql +17 -0
- package/nlm/index.js +14 -6
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/scripts/prompt-recall-hook.mjs +15 -6
- package/plugin/scripts/session-start-hook.mjs +11 -3
- package/plugin/scripts/stop-hook.mjs +4 -1
- package/dist/hook/subagent-start-hook.d.ts +0 -28
- package/dist/hook/subagent-start-hook.js +0 -81
- package/dist/hook/subagent-start-hook.js.map +0 -1
package/README.md
CHANGED
|
@@ -339,6 +339,8 @@ recall: prompt / query
|
|
|
339
339
|
| `NLM_PORT` | `3940` | Daemon bind port (loopback only) |
|
|
340
340
|
| `NLM_DB_PATH` | `~/.nlm/canonical.sqlite` | SQLite canonical store location |
|
|
341
341
|
| `NLM_HOOK_MODE` | `live` | `live` injects pointer block; `shadow` logs without injecting |
|
|
342
|
+
| `NLM_HOOK_RECALL_TIMEOUT_MS` | `4000` | Hook-side budget for the daemon recall call. Must stay below `NLM_HOOK_DEADLINE_MS` and above the daemon's embed deadline or the pointer block silently empties (#396). |
|
|
343
|
+
| `NLM_HOOK_DEADLINE_MS` | `6000` | Outer wall-clock deadline for the recall + gate stages of a hook fire. Fail-open on expiry. |
|
|
342
344
|
| `NLM_HOOK_PROMPT_RECALL` | `(unset = off)` | Per-prompt ambient recall. Off by default (pull-first); set `on` to inject a pointer block on every prompt. `off` disables explicitly. |
|
|
343
345
|
| `NLM_HOOK_LOG` | `~/.nlm/hook-log.jsonl` | Hook fire log; powers digest's liveness alert |
|
|
344
346
|
| `NLM_USEFUL_HIT_LOG` | `~/.nlm/useful-hit-log.jsonl` | Citation/useful-hit ledger |
|
package/dist/cli/digest.js
CHANGED
|
@@ -10,12 +10,40 @@
|
|
|
10
10
|
import { existsSync, readFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import Database from "better-sqlite3";
|
|
13
14
|
import { composeDigest, } from "../core/digest/compose.js";
|
|
14
15
|
import { computePrecision } from "../core/recall/precision.js";
|
|
15
16
|
import { readHookRecallLog } from "../core/recall/hook-recall-log.js";
|
|
16
17
|
import { readCitationLog } from "../core/recall/citation-log.js";
|
|
17
18
|
import { checkHookLiveness } from "../core/digest/hook-liveness.js";
|
|
19
|
+
import { checkHookInjection } from "../core/digest/hook-injection.js";
|
|
18
20
|
import { hookAuthHeaders } from "../hook/hook-auth.js";
|
|
21
|
+
import { computeOutcomeCoverage } from "../core/outcome/coverage.js";
|
|
22
|
+
import { loadOutcomeCoverageInput } from "../core/storage/sqlite-outcome-store.js";
|
|
23
|
+
/** Sessions ended in the last N days count toward the Tier-B coverage block. */
|
|
24
|
+
const OUTCOME_COVERAGE_WINDOW_DAYS = 30;
|
|
25
|
+
function defaultDbPath() {
|
|
26
|
+
return process.env["NLM_DB_PATH"] ?? join(homedir(), ".nlm", "canonical.sqlite");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort, read-only: opens its own SQLite connection directly rather
|
|
30
|
+
* than round-tripping the daemon (same pattern as the hook-log/citation-log
|
|
31
|
+
* reads below). A failure here must not break the rest of the digest.
|
|
32
|
+
*/
|
|
33
|
+
async function loadOutcomeCoverage() {
|
|
34
|
+
const dbPath = defaultDbPath();
|
|
35
|
+
if (!existsSync(dbPath))
|
|
36
|
+
return null;
|
|
37
|
+
const db = new Database(dbPath, { readonly: true });
|
|
38
|
+
try {
|
|
39
|
+
const sinceIso = new Date(Date.now() - OUTCOME_COVERAGE_WINDOW_DAYS * 24 * 60 * 60 * 1000).toISOString();
|
|
40
|
+
const input = await loadOutcomeCoverageInput(db, { sinceIso });
|
|
41
|
+
return await computeOutcomeCoverage(input);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
db.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
19
47
|
export async function runDigest(opts) {
|
|
20
48
|
const base = `http://localhost:${opts.port}`;
|
|
21
49
|
const timeoutMs = opts.timeoutMs ?? 8000;
|
|
@@ -49,12 +77,14 @@ export async function runDigest(opts) {
|
|
|
49
77
|
const hookLogPath = process.env["NLM_HOOK_LOG"] ?? join(homedir(), ".nlm", "hook-log.jsonl");
|
|
50
78
|
const hookLogExists = existsSync(hookLogPath);
|
|
51
79
|
const hookLog = hookLogExists ? readHookLog(hookLogPath) : [];
|
|
52
|
-
const
|
|
80
|
+
const livenessAlert = checkHookLiveness({
|
|
53
81
|
sessions,
|
|
54
82
|
hookLog,
|
|
55
83
|
hookLogPath,
|
|
56
84
|
hookLogExists,
|
|
57
85
|
});
|
|
86
|
+
const injectionResult = checkHookInjection(hookLog);
|
|
87
|
+
const hookAlert = [livenessAlert, injectionResult.message].filter(Boolean).join("\n") || null;
|
|
58
88
|
// True cited-precision over the 7-day window (the honest "was recall useful"
|
|
59
89
|
// metric). Best-effort: a log-read failure must not break the digest.
|
|
60
90
|
let precision = null;
|
|
@@ -72,12 +102,20 @@ export async function runDigest(opts) {
|
|
|
72
102
|
catch {
|
|
73
103
|
precision = null;
|
|
74
104
|
}
|
|
105
|
+
let outcomeCoverage = null;
|
|
106
|
+
try {
|
|
107
|
+
outcomeCoverage = await loadOutcomeCoverage();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
outcomeCoverage = null;
|
|
111
|
+
}
|
|
75
112
|
const text = composeDigest({
|
|
76
113
|
stats,
|
|
77
114
|
recent,
|
|
78
115
|
port: opts.port,
|
|
79
116
|
hookAlert,
|
|
80
117
|
precision,
|
|
118
|
+
outcomeCoverage,
|
|
81
119
|
});
|
|
82
120
|
if (opts.telegram) {
|
|
83
121
|
await postTelegram(text);
|
package/dist/cli/digest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/cli/digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,aAAa,GAId,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAsC,MAAM,+BAA+B,CAAC;AACtG,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/cli/digest.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/nlm.js
CHANGED
|
@@ -60,6 +60,9 @@ import { connectCursor, disconnectCursor } from "../install/cursor.js";
|
|
|
60
60
|
import { describeRemove, describeUpsert, installCursorRules, installOpencodeRules, installWindsurfRules, uninstallCursorRules, uninstallOpencodeRules, uninstallWindsurfRules, } from "../install/rules-install.js";
|
|
61
61
|
import { runSupersedeCommand } from "./supersede.js";
|
|
62
62
|
import { runInitCommand } from "./init.js";
|
|
63
|
+
import { runScopeBackfill, formatBackfillResult } from "./scope-backfill.js";
|
|
64
|
+
import { runScopeCoverage, formatCoverageResult } from "./scope-coverage.js";
|
|
65
|
+
import { loadAliasMap } from "../core/scope/alias-map.js";
|
|
63
66
|
import { getUpdateStatus } from "../core/update-check/check.js";
|
|
64
67
|
import { connectHermes, disconnectHermes, hermesConfigPath } from "../install/hermes.js";
|
|
65
68
|
import { connectHermesAgent, disconnectHermesAgent, hermesAgentPluginDir } from "../install/hermes-agent.js";
|
|
@@ -94,6 +97,7 @@ import { runChecksOnSqlite, runChecksOnPg, applyFixOnSqlite, applyFixOnPg } from
|
|
|
94
97
|
import { normalizeLabel } from "../core/workstream/model.js";
|
|
95
98
|
import { resolveWorkstreamId } from "../core/workstream/resolve.js";
|
|
96
99
|
import { suggestMerges } from "../core/entities/dedup-suggest.js";
|
|
100
|
+
import { DEFAULT_NLM_PORT } from "../shared/net.js";
|
|
97
101
|
const __filename = fileURLToPath(import.meta.url);
|
|
98
102
|
const __dirname = dirname(__filename);
|
|
99
103
|
const MIGRATIONS_DIR = resolve(__dirname, "../../migrations");
|
|
@@ -101,7 +105,7 @@ const CLASSIFIER_FIXTURES_DIR = resolve(__dirname, "../../fixtures/classifier-go
|
|
|
101
105
|
const PG_MIGRATIONS_DIR = join(fileURLToPath(new URL(".", import.meta.url)), "../../migrations/pg");
|
|
102
106
|
const UI_DIST = resolve(__dirname, "../../dist/ui");
|
|
103
107
|
const DEFAULT_DB_PATH = resolve(homedir(), ".nlm/canonical.sqlite");
|
|
104
|
-
const DEFAULT_PORT =
|
|
108
|
+
const DEFAULT_PORT = Number.parseInt(DEFAULT_NLM_PORT, 10);
|
|
105
109
|
function dbPath() {
|
|
106
110
|
return process.env["NLM_DB_PATH"] ?? DEFAULT_DB_PATH;
|
|
107
111
|
}
|
|
@@ -384,6 +388,9 @@ program
|
|
|
384
388
|
},
|
|
385
389
|
signalStore: signals,
|
|
386
390
|
installScope: scope,
|
|
391
|
+
// Signal scope inheritance (NLM_SCOPE_STAMP): a signal with only a
|
|
392
|
+
// session_id inherits that session's stamped scope at ingest.
|
|
393
|
+
sessionScopeReader: store,
|
|
387
394
|
// Code-exemplar lane. The store + code embedder are always wired; the
|
|
388
395
|
// NLM_CODE_EXEMPLARS_ENABLED flag gates capture (POST /api/signal) and
|
|
389
396
|
// the /api/exemplar + /api/recall-code routes at request time.
|
|
@@ -406,6 +413,8 @@ program
|
|
|
406
413
|
exemplarStore: storage.exemplars,
|
|
407
414
|
codeEmbedder: codeEmb,
|
|
408
415
|
installScope: scope,
|
|
416
|
+
signalStore: signals,
|
|
417
|
+
sessionScopeReader: store,
|
|
409
418
|
workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
|
|
410
419
|
workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
|
|
411
420
|
},
|
|
@@ -1205,11 +1214,39 @@ program
|
|
|
1205
1214
|
await stack.storage.close();
|
|
1206
1215
|
}
|
|
1207
1216
|
});
|
|
1217
|
+
program
|
|
1218
|
+
.command("backfill-derivables")
|
|
1219
|
+
.description("Retroactive: stamp agent_persona + parent_session_id (#352 phase 2) onto sessions ingested\n" +
|
|
1220
|
+
"before the scheduler started stamping them at classify time. Idempotent (WHERE agent_persona IS NULL).")
|
|
1221
|
+
.option("--dry-run", "report counts without writing")
|
|
1222
|
+
.option("--with-transcript-scan", "also scan each candidate's transcript for primary_model/total_tokens/skill (slower)")
|
|
1223
|
+
.action(async (opts) => {
|
|
1224
|
+
const { backfillDerivables } = await import("../core/ingest/backfill-derivables.js");
|
|
1225
|
+
const stack = await buildStack();
|
|
1226
|
+
try {
|
|
1227
|
+
if (!(stack.storage instanceof SqliteStorage)) {
|
|
1228
|
+
console.error("backfill-derivables: only supported with SQLite storage (NLM_PG_URL must not be set)");
|
|
1229
|
+
await stack.storage.close();
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
const report = await backfillDerivables(stack.storage.rawDb(), {
|
|
1233
|
+
dryRun: Boolean(opts.dryRun),
|
|
1234
|
+
withTranscriptScan: Boolean(opts.withTranscriptScan),
|
|
1235
|
+
});
|
|
1236
|
+
console.log(`backfill-derivables${opts.dryRun ? " (dry-run)" : ""}: ` +
|
|
1237
|
+
`updated=${report.updated} skipped(already-stamped)=${report.skippedAlreadyStamped} ` +
|
|
1238
|
+
`skipped(no-op)=${report.skippedNoop} subagent-candidates=${report.subagentCandidates} ` +
|
|
1239
|
+
`unknown-parent=${report.unknownParent} transcript-scanned=${report.transcriptScanned} total=${report.total}`);
|
|
1240
|
+
}
|
|
1241
|
+
finally {
|
|
1242
|
+
await stack.storage.close();
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1208
1245
|
program
|
|
1209
1246
|
.command("mcp")
|
|
1210
1247
|
.description("Run as an MCP stdio server (for ~/.mcp.json)")
|
|
1211
1248
|
.action(async () => {
|
|
1212
|
-
const { recall, store, facts, factRecall, storage, scope } = await buildStack();
|
|
1249
|
+
const { recall, store, facts, factRecall, storage, scope, signals } = await buildStack();
|
|
1213
1250
|
const server = createMcpServer({
|
|
1214
1251
|
recall,
|
|
1215
1252
|
store,
|
|
@@ -1218,8 +1255,13 @@ program
|
|
|
1218
1255
|
exemplarStore: storage.exemplars,
|
|
1219
1256
|
codeEmbedder: buildCodeEmbedder(),
|
|
1220
1257
|
installScope: scope,
|
|
1258
|
+
signalStore: signals,
|
|
1259
|
+
sessionScopeReader: store,
|
|
1221
1260
|
workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
|
|
1222
1261
|
workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
|
|
1262
|
+
// Tier-B outcome rollup (#352 phase 2) only has a SQLite adapter today;
|
|
1263
|
+
// PgStorage deployments skip get_session's `outcome` field until parity lands.
|
|
1264
|
+
...(storage instanceof SqliteStorage ? { outcomeDb: storage.rawDb() } : {}),
|
|
1223
1265
|
});
|
|
1224
1266
|
const transport = new StdioServerTransport();
|
|
1225
1267
|
await server.connect(transport);
|
|
@@ -1607,14 +1649,12 @@ const SESSION_START_HOOK_JS = resolve(__dirname, "../hook/session-start-hook.js"
|
|
|
1607
1649
|
const SESSION_END_HOOK_JS = resolve(__dirname, "../hook/session-end-hook.js");
|
|
1608
1650
|
const STOP_HOOK_JS = resolve(__dirname, "../hook/stop-hook.js");
|
|
1609
1651
|
const PRE_COMPACT_HOOK_JS = resolve(__dirname, "../hook/pre-compact-hook.js");
|
|
1610
|
-
const SUBAGENT_START_HOOK_JS = resolve(__dirname, "../hook/subagent-start-hook.js");
|
|
1611
1652
|
const ALL_HOOKS = [
|
|
1612
1653
|
{ event: "UserPromptSubmit", script: HOOK_JS, label: "recall" },
|
|
1613
1654
|
{ event: "SessionStart", script: SESSION_START_HOOK_JS, label: "session-start" },
|
|
1614
1655
|
{ event: "SessionEnd", script: SESSION_END_HOOK_JS, label: "session-end" },
|
|
1615
1656
|
{ event: "Stop", script: STOP_HOOK_JS, label: "stop" },
|
|
1616
1657
|
{ event: "PreCompact", script: PRE_COMPACT_HOOK_JS, label: "pre-compact" },
|
|
1617
|
-
{ event: "SubagentStart", script: SUBAGENT_START_HOOK_JS, label: "subagent-start" },
|
|
1618
1658
|
];
|
|
1619
1659
|
function claudeSettingsPath() {
|
|
1620
1660
|
return process.env["NLM_CLAUDE_SETTINGS"] ?? join(homedir(), ".claude", "settings.json");
|
|
@@ -1624,7 +1664,7 @@ const hook = program
|
|
|
1624
1664
|
.description("Manage the Claude Code NLM hooks");
|
|
1625
1665
|
hook
|
|
1626
1666
|
.command("install")
|
|
1627
|
-
.description("Add the NLM hooks (recall + session-end + stop) to ~/.claude/settings.json (live mode)")
|
|
1667
|
+
.description("Add the NLM hooks (recall + session-start + session-end + stop + pre-compact) to ~/.claude/settings.json (live mode)")
|
|
1628
1668
|
.action(() => {
|
|
1629
1669
|
const path = claudeSettingsPath();
|
|
1630
1670
|
const installed = [];
|
|
@@ -2130,7 +2170,7 @@ program
|
|
|
2130
2170
|
program
|
|
2131
2171
|
.command("digest")
|
|
2132
2172
|
.description("Compose a daily-activity digest from the running daemon (optionally post to Telegram)")
|
|
2133
|
-
.option("-p, --port <n>", "daemon port", (v) => Number.parseInt(v, 10), Number.parseInt(process.env["NLM_PORT"] ??
|
|
2173
|
+
.option("-p, --port <n>", "daemon port", (v) => Number.parseInt(v, 10), Number.parseInt(process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT, 10))
|
|
2134
2174
|
.option("--telegram", "post to Telegram instead of printing to stdout (requires TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID)")
|
|
2135
2175
|
.action(async (opts) => {
|
|
2136
2176
|
autoloadEnv();
|
|
@@ -2566,6 +2606,66 @@ entitiesCmd
|
|
|
2566
2606
|
if (failed > 0)
|
|
2567
2607
|
process.exit(1);
|
|
2568
2608
|
});
|
|
2609
|
+
const scopeCmd = program
|
|
2610
|
+
.command("scope")
|
|
2611
|
+
.description("Scope management commands");
|
|
2612
|
+
scopeCmd
|
|
2613
|
+
.command("backfill")
|
|
2614
|
+
.description("Derive and stamp scope on legacy sessions/facts/exemplars/signals/workstreams from stored transcript evidence")
|
|
2615
|
+
.option("--apply", "write changes (default: dry run, no writes)")
|
|
2616
|
+
.option("--db <path>", "SQLite DB file to backfill (default: canonical NLM DB)")
|
|
2617
|
+
.option("--json", "emit machine-readable JSON summary instead of human-readable output")
|
|
2618
|
+
.action(async (opts) => {
|
|
2619
|
+
const targetPath = opts.db ?? dbPath();
|
|
2620
|
+
const storage = SqliteStorage.create({ dbPath: targetPath, migrationsDir: MIGRATIONS_DIR });
|
|
2621
|
+
const aliasMap = loadAliasMap();
|
|
2622
|
+
try {
|
|
2623
|
+
const result = await runScopeBackfill({
|
|
2624
|
+
db: storage.rawDb(),
|
|
2625
|
+
apply: Boolean(opts.apply),
|
|
2626
|
+
aliasMap,
|
|
2627
|
+
});
|
|
2628
|
+
if (opts.json) {
|
|
2629
|
+
const out = {
|
|
2630
|
+
dryRun: result.dryRun,
|
|
2631
|
+
sessions: { total: result.sessions.total, byScope: result.sessions.byScope },
|
|
2632
|
+
facts: { total: result.facts.total, byScope: result.facts.byScope },
|
|
2633
|
+
exemplars: { total: result.exemplars.total, byScope: result.exemplars.byScope },
|
|
2634
|
+
signals: { total: result.signals.total, byScope: result.signals.byScope },
|
|
2635
|
+
workstreams: { total: result.workstreams.total, byScope: result.workstreams.byScope },
|
|
2636
|
+
skipped: result.skipped,
|
|
2637
|
+
};
|
|
2638
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
2639
|
+
}
|
|
2640
|
+
else {
|
|
2641
|
+
formatBackfillResult(result, (s) => process.stdout.write(s));
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
finally {
|
|
2645
|
+
await storage.close();
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
scopeCmd
|
|
2649
|
+
.command("coverage")
|
|
2650
|
+
.description("Report per-table and recall-weighted scope coverage fractions")
|
|
2651
|
+
.option("--db <path>", "SQLite DB file (default: canonical NLM DB)")
|
|
2652
|
+
.option("--query-log <path>", "query log path for recall-weighted coverage")
|
|
2653
|
+
.option("--window <n>", "last N log entries for recall-weighted coverage (default 200)", (v) => Number.parseInt(v, 10), 200)
|
|
2654
|
+
.option("--json", "emit machine-readable JSON instead of human-readable output")
|
|
2655
|
+
.action(async (opts) => {
|
|
2656
|
+
const targetPath = opts.db ?? dbPath();
|
|
2657
|
+
const result = await runScopeCoverage({
|
|
2658
|
+
dbPath: targetPath,
|
|
2659
|
+
...(opts.queryLog ? { queryLogPath: opts.queryLog } : {}),
|
|
2660
|
+
window: opts.window,
|
|
2661
|
+
});
|
|
2662
|
+
if (opts.json) {
|
|
2663
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
2664
|
+
}
|
|
2665
|
+
else {
|
|
2666
|
+
formatCoverageResult(result, (s) => process.stdout.write(s));
|
|
2667
|
+
}
|
|
2668
|
+
});
|
|
2569
2669
|
// Entrypoint guard: only parse argv when this file IS the executed script.
|
|
2570
2670
|
// realpathSync resolves the npm bin symlink so the global `nlm` shim still
|
|
2571
2671
|
// matches; without the guard, importing any helper from this module inside
|