recallnest 2.3.0 → 2.5.4
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 +150 -0
- package/README.md +17 -31
- package/README_CN.md +12 -22
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +22 -1
- package/src/__tests__/access-tracker-novelty.test.ts +238 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1238 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +33 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +782 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +374 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +333 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +216 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +84 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +321 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +76 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +174 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +250 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +314 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +230 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +601 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +184 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +38 -32
- package/src/activity-counter.ts +46 -18
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +27 -7
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/capture-engine.ts +54 -21
- package/src/cli.ts +169 -37
- package/src/confidence-tracker.ts +36 -18
- package/src/consolidation-engine.ts +109 -11
- package/src/context-composer.ts +8 -1
- package/src/data-checkup.ts +95 -6
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +9 -2
- package/src/dream-pipeline.ts +118 -34
- package/src/env-config.ts +77 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +251 -6
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +31 -10
- package/src/kg-extractor.ts +6 -2
- package/src/kg-store.ts +236 -44
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +85 -1799
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +941 -0
- package/src/mcp-tools-core.ts +633 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +3 -1
- package/src/memory-evolution.ts +73 -1
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +114 -3
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +43 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/pii-detector.ts +157 -5
- package/src/query-expander.ts +90 -9
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retriever.ts +259 -139
- package/src/runtime-config.ts +8 -1
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +1 -0
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +247 -47
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/store.ts +267 -38
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +2 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -60
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/ingest-2026-04-13.log +0 -87
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
package/src/pii-detector.ts
CHANGED
|
@@ -2,12 +2,21 @@
|
|
|
2
2
|
* F-3: Lightweight PII detection.
|
|
3
3
|
* Scans text for common sensitive patterns (passwords, tokens, IDs).
|
|
4
4
|
* Returns warnings, does not block writes.
|
|
5
|
+
*
|
|
6
|
+
* F-3b (2026-07-10): Deterministic secret redaction on the same rule set.
|
|
7
|
+
* `redactSecrets` scrubs redactable matches BEFORE text reaches any external
|
|
8
|
+
* surface (LLM prompts, embedding API, stderr logs, audit trail). Detection
|
|
9
|
+
* and redaction share one rule table so the two can never drift apart.
|
|
10
|
+
* Deliberately NOT redacted: phone / email (legitimate memory content, warn
|
|
11
|
+
* only) and bare long-hex (would false-positive on git commit hashes, which
|
|
12
|
+
* are high-value memory content; prefixed forms like `secret=<hex>` are
|
|
13
|
+
* caught by the assignment rules).
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
16
|
export type PIISeverity = "high" | "medium" | "low";
|
|
8
17
|
|
|
9
18
|
export interface PIIDetection {
|
|
10
|
-
type: string; // "api_key" | "password" | "id_number" | "email" | "phone" | "credit_card"
|
|
19
|
+
type: string; // "api_key" | "password" | "id_number" | "email" | "phone" | "credit_card" | ...
|
|
11
20
|
severity: PIISeverity;
|
|
12
21
|
match: string; // the matched text (partially masked)
|
|
13
22
|
position: number; // char offset
|
|
@@ -19,42 +28,147 @@ export interface PIIScanResult {
|
|
|
19
28
|
summary: string; // human-readable summary
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
export interface RedactResult {
|
|
32
|
+
text: string; // scrubbed text
|
|
33
|
+
redacted: number; // number of replacements made
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
interface PIIRule {
|
|
23
37
|
type: string;
|
|
24
38
|
severity: PIISeverity;
|
|
25
39
|
pattern: RegExp;
|
|
40
|
+
/** Scrub matches via redactSecrets (detection-only rules leave text intact) */
|
|
41
|
+
redact: boolean;
|
|
42
|
+
/** For key=value style rules: keep the key name, scrub only the value */
|
|
43
|
+
keepPrefix?: RegExp;
|
|
44
|
+
/** Checksum gate for broad numeric patterns: redact ONLY when this returns
|
|
45
|
+
* true (scan still warns regardless). Keeps snowflake IDs / order numbers
|
|
46
|
+
* from being destructively rewritten. */
|
|
47
|
+
validate?: (match: string) => boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** GB 11643 checksum (ISO 7064 MOD 11-2) for 18-digit Chinese national IDs. */
|
|
51
|
+
function isValidChineseId(id: string): boolean {
|
|
52
|
+
if (!/^\d{17}[\dXx]$/.test(id)) return false;
|
|
53
|
+
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
|
54
|
+
const checkChars = "10X98765432";
|
|
55
|
+
let sum = 0;
|
|
56
|
+
for (let i = 0; i < 17; i++) sum += Number(id[i]) * weights[i];
|
|
57
|
+
return checkChars[sum % 11] === id[17].toUpperCase();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Luhn checksum for card-number candidates (separators stripped first). */
|
|
61
|
+
function isValidLuhn(candidate: string): boolean {
|
|
62
|
+
const digits = candidate.replace(/[\s-]/g, "");
|
|
63
|
+
if (!/^\d{16}$/.test(digits)) return false;
|
|
64
|
+
let sum = 0;
|
|
65
|
+
for (let i = 0; i < 16; i++) {
|
|
66
|
+
let d = Number(digits[15 - i]);
|
|
67
|
+
if (i % 2 === 1) {
|
|
68
|
+
d *= 2;
|
|
69
|
+
if (d > 9) d -= 9;
|
|
70
|
+
}
|
|
71
|
+
sum += d;
|
|
72
|
+
}
|
|
73
|
+
return sum % 10 === 0;
|
|
26
74
|
}
|
|
27
75
|
|
|
28
76
|
const PII_RULES: PIIRule[] = [
|
|
77
|
+
// --- Vendor-prefixed token literals (high confidence, full scrub) ---
|
|
78
|
+
{
|
|
79
|
+
type: "anthropic_key",
|
|
80
|
+
severity: "high",
|
|
81
|
+
pattern: /sk-ant-[A-Za-z0-9_\-]{10,}/g,
|
|
82
|
+
redact: true,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "github_token",
|
|
86
|
+
severity: "high",
|
|
87
|
+
pattern: /gh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
88
|
+
redact: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: "slack_token",
|
|
92
|
+
severity: "high",
|
|
93
|
+
pattern: /xox[baprs]-[A-Za-z0-9\-]{10,}/g,
|
|
94
|
+
redact: true,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: "aws_access_key",
|
|
98
|
+
severity: "high",
|
|
99
|
+
pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
|
100
|
+
redact: true,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
type: "google_api_key",
|
|
104
|
+
severity: "high",
|
|
105
|
+
pattern: /\bAIza[A-Za-z0-9_\-]{35}\b/g,
|
|
106
|
+
redact: true,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "jwt",
|
|
110
|
+
severity: "high",
|
|
111
|
+
pattern: /\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\b/g,
|
|
112
|
+
redact: true,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
type: "bearer_token",
|
|
116
|
+
severity: "high",
|
|
117
|
+
pattern: /\bBearer\s+[A-Za-z0-9._~+\/\-]{16,}=*/g,
|
|
118
|
+
redact: true,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "pem_private_key",
|
|
122
|
+
severity: "high",
|
|
123
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
124
|
+
redact: true,
|
|
125
|
+
},
|
|
126
|
+
// --- Generic assignment / key styles (keep key name, scrub value) ---
|
|
127
|
+
// Quoted keys and whitespace around the separator are allowed so JSON/YAML/
|
|
128
|
+
// config forms ({"api_key": "..."}, password = ...) are caught too.
|
|
29
129
|
{
|
|
30
130
|
type: "api_key",
|
|
31
131
|
severity: "high",
|
|
32
|
-
pattern: /(?:sk-[A-Za-z0-9_\-]{20,}|(?:api[_-]?key|token|secret)[
|
|
132
|
+
pattern: /(?:sk-[A-Za-z0-9_\-]{20,}|["']?(?:api[_-]?key|token|secret)["']?\s*[=:]\s*["']?[A-Za-z0-9_\-]{20,})/gi,
|
|
133
|
+
redact: true,
|
|
134
|
+
keepPrefix: /^["']?(?:api[_-]?key|token|secret)["']?\s*[=:]\s*["']?/i,
|
|
33
135
|
},
|
|
34
136
|
{
|
|
35
137
|
type: "password",
|
|
36
138
|
severity: "high",
|
|
37
|
-
pattern: /(?:password|passwd|pwd)[
|
|
139
|
+
pattern: /["']?(?:password|passwd|pwd)["']?\s*[=:]\s*["']?[^\s"',;]{8,}/gi,
|
|
140
|
+
redact: true,
|
|
141
|
+
keepPrefix: /^["']?(?:password|passwd|pwd)["']?\s*[=:]\s*["']?/i,
|
|
38
142
|
},
|
|
143
|
+
// --- High-risk personal identifiers ---
|
|
144
|
+
// Broad numeric patterns scan-warn on shape but only REDACT when the
|
|
145
|
+
// checksum verifies — otherwise snowflake IDs / order numbers get rewritten.
|
|
39
146
|
{
|
|
40
147
|
type: "id_number",
|
|
41
148
|
severity: "high",
|
|
42
|
-
pattern:
|
|
149
|
+
pattern: /(?<!\d)\d{17}[\dXx](?!\d)/g,
|
|
150
|
+
redact: true,
|
|
151
|
+
validate: isValidChineseId,
|
|
43
152
|
},
|
|
44
153
|
{
|
|
45
154
|
type: "credit_card",
|
|
46
155
|
severity: "high",
|
|
47
|
-
pattern:
|
|
156
|
+
pattern: /(?<!\d)\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}(?![\d])/g,
|
|
157
|
+
redact: true,
|
|
158
|
+
validate: isValidLuhn,
|
|
48
159
|
},
|
|
160
|
+
// --- Detection-only: legitimate memory content, warn but keep ---
|
|
49
161
|
{
|
|
50
162
|
type: "phone",
|
|
51
163
|
severity: "medium",
|
|
52
164
|
pattern: /1[3-9]\d{9}/g,
|
|
165
|
+
redact: false,
|
|
53
166
|
},
|
|
54
167
|
{
|
|
55
168
|
type: "email",
|
|
56
169
|
severity: "low",
|
|
57
170
|
pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
171
|
+
redact: false,
|
|
58
172
|
},
|
|
59
173
|
];
|
|
60
174
|
|
|
@@ -96,3 +210,41 @@ export function scanForPII(text: string): PIIScanResult {
|
|
|
96
210
|
summary,
|
|
97
211
|
};
|
|
98
212
|
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Deterministically scrub redactable secrets. Pure regex, no LLM call.
|
|
216
|
+
* Replacement is `[REDACTED:<type>]`; assignment-style rules keep the key
|
|
217
|
+
* name (`password=[REDACTED:password]`) so the memory stays readable.
|
|
218
|
+
*/
|
|
219
|
+
export function redactSecrets(text: string): RedactResult {
|
|
220
|
+
let out = text;
|
|
221
|
+
let redacted = 0;
|
|
222
|
+
|
|
223
|
+
for (const rule of PII_RULES) {
|
|
224
|
+
if (!rule.redact) continue;
|
|
225
|
+
rule.pattern.lastIndex = 0;
|
|
226
|
+
out = out.replace(rule.pattern, (match) => {
|
|
227
|
+
if (rule.validate && !rule.validate(match)) {
|
|
228
|
+
return match; // checksum failed → leave intact (detection-only)
|
|
229
|
+
}
|
|
230
|
+
redacted++;
|
|
231
|
+
if (rule.keepPrefix) {
|
|
232
|
+
const prefixMatch = match.match(rule.keepPrefix);
|
|
233
|
+
if (prefixMatch) {
|
|
234
|
+
return `${prefixMatch[0]}[REDACTED:${rule.type}]`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return `[REDACTED:${rule.type}]`;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { text: out, redacted };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Redact + truncate for log lines. Truncation happens AFTER redaction so a
|
|
246
|
+
* secret can never straddle the cut and leak its prefix.
|
|
247
|
+
*/
|
|
248
|
+
export function redactForLog(text: string, max = 60): string {
|
|
249
|
+
return redactSecrets(text).text.slice(0, max);
|
|
250
|
+
}
|
package/src/query-expander.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* v1.3: P0.3 — User-level alias-map (data/alias-map.json) for short query expansion.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { existsSync, readFileSync } from "fs";
|
|
14
|
-
import { join } from "path";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
|
+
import { dirname, join } from "path";
|
|
15
15
|
import { resolveQueryEntities } from "./entity-resolver.js";
|
|
16
16
|
|
|
17
17
|
interface SynonymEntry {
|
|
@@ -89,9 +89,14 @@ export function resetAliasMapCache(): void {
|
|
|
89
89
|
aliasMapCache = null;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** Resolve the alias-map file path currently in effect. */
|
|
93
|
+
export function aliasMapFilePath(): string {
|
|
94
|
+
return aliasMapPath ?? join(process.cwd(), "data", "alias-map.json");
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
function loadAliasMap(): AliasEntry[] {
|
|
93
98
|
if (aliasMapCache !== null) return aliasMapCache;
|
|
94
|
-
const filePath =
|
|
99
|
+
const filePath = aliasMapFilePath();
|
|
95
100
|
try {
|
|
96
101
|
if (existsSync(filePath)) {
|
|
97
102
|
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
@@ -112,6 +117,75 @@ function loadAliasMap(): AliasEntry[] {
|
|
|
112
117
|
return aliasMapCache;
|
|
113
118
|
}
|
|
114
119
|
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// User alias management (P1-B manage_alias tool 后端)
|
|
122
|
+
//
|
|
123
|
+
// 这些别名只作用于 BM25 通道(expandQuery 在 hybridRetrieval 内部被调用),
|
|
124
|
+
// 刻意不进 vector embedding——随手加的用户别名若改变语义向量,排查
|
|
125
|
+
// retrieval drift 会非常困难。全通道的 builtin 规则在 src/aliases.ts。
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/** 写入校验:trigger 最短 2 字符(防高频泛词),expansions 最多 8 个。 */
|
|
129
|
+
export const USER_ALIAS_TRIGGER_MIN = 2;
|
|
130
|
+
export const USER_ALIAS_EXPANSIONS_MAX = 8;
|
|
131
|
+
|
|
132
|
+
export type AliasUpsertResult =
|
|
133
|
+
| { ok: true; action: "added" | "updated"; entry: AliasEntry }
|
|
134
|
+
| { ok: false; reason: string };
|
|
135
|
+
|
|
136
|
+
/** 新增/更新用户别名(写 alias-map.json;校验后缓存即时失效)。 */
|
|
137
|
+
export function upsertUserAlias(trigger: string, expansions: string[]): AliasUpsertResult {
|
|
138
|
+
const t = trigger.trim();
|
|
139
|
+
const exp = [...new Set(expansions.map(e => e.trim()).filter(e => e.length > 0))];
|
|
140
|
+
|
|
141
|
+
if (t.length < USER_ALIAS_TRIGGER_MIN) {
|
|
142
|
+
return { ok: false, reason: `trigger 太短(<${USER_ALIAS_TRIGGER_MIN} 字符)——高频泛词会污染所有查询` };
|
|
143
|
+
}
|
|
144
|
+
if (exp.length === 0) {
|
|
145
|
+
return { ok: false, reason: "expansions 为空" };
|
|
146
|
+
}
|
|
147
|
+
if (exp.length > USER_ALIAS_EXPANSIONS_MAX) {
|
|
148
|
+
return { ok: false, reason: `expansions 超过 ${USER_ALIAS_EXPANSIONS_MAX} 个——过长扩展会冲淡 BM25 原查询词权重` };
|
|
149
|
+
}
|
|
150
|
+
if (exp.every(e => e === t)) {
|
|
151
|
+
return { ok: false, reason: "expansions 与 trigger 相同,无扩展意义" };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rules = loadAliasMap();
|
|
155
|
+
const idx = rules.findIndex(r => r.trigger === t);
|
|
156
|
+
const entry: AliasEntry = { trigger: t, expansions: exp };
|
|
157
|
+
const next = idx >= 0 ? rules.map((r, i) => (i === idx ? entry : r)) : [...rules, entry];
|
|
158
|
+
|
|
159
|
+
const path = aliasMapFilePath();
|
|
160
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
161
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
162
|
+
aliasMapCache = null;
|
|
163
|
+
return { ok: true, action: idx >= 0 ? "updated" : "added", entry };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** 删除用户别名;返回是否真的删了。 */
|
|
167
|
+
export function removeUserAlias(trigger: string): boolean {
|
|
168
|
+
const rules = loadAliasMap();
|
|
169
|
+
const next = rules.filter(r => r.trigger !== trigger.trim());
|
|
170
|
+
if (next.length === rules.length) return false;
|
|
171
|
+
const path = aliasMapFilePath();
|
|
172
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
173
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
174
|
+
aliasMapCache = null;
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** 列出用户别名规则。 */
|
|
179
|
+
export function listUserAliases(): AliasEntry[] {
|
|
180
|
+
return [...loadAliasMap()];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 检视:query 命中了哪些用户别名(BM25-only 通道)。 */
|
|
184
|
+
export function explainUserAliases(query: string): AliasEntry[] {
|
|
185
|
+
const lower = query.toLowerCase();
|
|
186
|
+
return loadAliasMap().filter(e => lower.includes(e.trigger.toLowerCase()));
|
|
187
|
+
}
|
|
188
|
+
|
|
115
189
|
/**
|
|
116
190
|
* Expand a query by appending synonym terms from the dictionary.
|
|
117
191
|
* Returns the original query with additional terms appended.
|
|
@@ -123,7 +197,8 @@ export function expandQuery(query: string): string {
|
|
|
123
197
|
// Entity resolution pre-pass: normalize aliases before synonym expansion
|
|
124
198
|
const resolved = resolveQueryEntities(query);
|
|
125
199
|
const lower = resolved.toLowerCase();
|
|
126
|
-
const
|
|
200
|
+
const synonymAdditions = new Set<string>();
|
|
201
|
+
const aliasAdditions = new Set<string>();
|
|
127
202
|
|
|
128
203
|
// Static synonym map (built-in)
|
|
129
204
|
for (const entry of SYNONYM_MAP) {
|
|
@@ -138,7 +213,7 @@ export function expandQuery(query: string): string {
|
|
|
138
213
|
if (cnMatch || enMatch) {
|
|
139
214
|
for (const exp of entry.expansions) {
|
|
140
215
|
if (!lower.includes(exp.toLowerCase())) {
|
|
141
|
-
|
|
216
|
+
synonymAdditions.add(exp);
|
|
142
217
|
}
|
|
143
218
|
}
|
|
144
219
|
}
|
|
@@ -151,15 +226,21 @@ export function expandQuery(query: string): string {
|
|
|
151
226
|
if (lower.includes(triggerLower)) {
|
|
152
227
|
for (const exp of alias.expansions) {
|
|
153
228
|
if (!lower.includes(exp.toLowerCase())) {
|
|
154
|
-
|
|
229
|
+
aliasAdditions.add(exp);
|
|
155
230
|
}
|
|
156
231
|
}
|
|
157
232
|
}
|
|
158
233
|
}
|
|
159
234
|
|
|
160
|
-
if (
|
|
235
|
+
if (aliasAdditions.size === 0 && synonymAdditions.size === 0) return resolved;
|
|
161
236
|
|
|
162
|
-
// Cap expansion terms to prevent query bloat
|
|
163
|
-
|
|
237
|
+
// Cap expansion terms to prevent query bloat. User aliases rank first inside
|
|
238
|
+
// the cap — 用户为这个 trigger 显式配置的 canonical 名,不应被泛化同义词
|
|
239
|
+
// 挤出 MAX_EXPANSION_TERMS(P1-B:同义词"挂"曾把 alias 的实体名挤掉)。
|
|
240
|
+
const merged = [
|
|
241
|
+
...aliasAdditions,
|
|
242
|
+
...[...synonymAdditions].filter(t => !aliasAdditions.has(t)),
|
|
243
|
+
];
|
|
244
|
+
const limited = merged.slice(0, MAX_EXPANSION_TERMS);
|
|
164
245
|
return `${resolved} ${limited.join(" ")}`;
|
|
165
246
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { LLMClient } from "./llm-client.js";
|
|
14
14
|
import { logWarn } from "./stderr-log.js";
|
|
15
|
+
import * as envConfig from "./env-config.js";
|
|
15
16
|
|
|
16
17
|
// ============================================================================
|
|
17
18
|
// Configuration
|
|
@@ -38,7 +39,7 @@ export const DEFAULT_SYNTHESIZER_CONFIG: SynthesizerConfig = {
|
|
|
38
39
|
* Check if synthesis is enabled via environment variable.
|
|
39
40
|
*/
|
|
40
41
|
export function isSynthesisEnabled(): boolean {
|
|
41
|
-
return
|
|
42
|
+
return envConfig.synthesize();
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
package/src/retention-policy.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import * as envConfig from "./env-config.js";
|
|
10
11
|
|
|
11
12
|
export interface RetentionPolicy {
|
|
12
13
|
/** Auto-archive memories older than this many days (0 = disabled) */
|
|
@@ -29,7 +30,7 @@ function scopeHash(scope: string): string {
|
|
|
29
30
|
|
|
30
31
|
function retentionDir(configDir?: string): string {
|
|
31
32
|
return join(
|
|
32
|
-
configDir ?? (
|
|
33
|
+
configDir ?? envConfig.dataDir(),
|
|
33
34
|
"retention",
|
|
34
35
|
);
|
|
35
36
|
}
|