recallnest 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/README.md +222 -254
- package/README_CN.md +206 -243
- package/package.json +3 -2
- package/src/capture-engine.ts +26 -4
- package/src/context-composer-stable.ts +5 -1
- package/src/embedder.ts +24 -0
- package/src/env-config.ts +18 -0
- package/src/ingest.ts +225 -17
- package/src/kg-extractor.ts +17 -0
- package/src/memory-output.ts +35 -0
- package/src/pivot-apply.ts +52 -1
- package/src/pivot-evidence.ts +128 -0
- package/src/ppr-traversal.ts +25 -1
- package/src/runtime-config.ts +9 -0
- package/src/session-engine.ts +11 -4
- package/src/session-output.ts +9 -1
- package/src/session-store.ts +2 -3
- package/src/skill-promotion.ts +67 -1
- package/src/__tests__/access-tracker-novelty.test.ts +0 -324
- package/src/__tests__/activity-counter.test.ts +0 -125
- package/src/__tests__/adaptive-fidelity.test.ts +0 -102
- package/src/__tests__/admission-control.test.ts +0 -206
- package/src/__tests__/age-label.test.ts +0 -33
- package/src/__tests__/anchor-generator.test.ts +0 -67
- package/src/__tests__/api-server-security.test.ts +0 -102
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/babel-integration.test.ts +0 -144
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/belief-history.test.ts +0 -220
- package/src/__tests__/canary-eval.test.ts +0 -114
- package/src/__tests__/capture-engine.test.ts +0 -1328
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -116
- package/src/__tests__/cc8-cc1-essential-context.test.ts +0 -298
- package/src/__tests__/circuit-breaker.test.ts +0 -99
- package/src/__tests__/cluster-consolidation.test.ts +0 -1236
- package/src/__tests__/confidence-metadata.test.ts +0 -125
- package/src/__tests__/confidence-tracker.test.ts +0 -261
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -418
- package/src/__tests__/connector-v1.test.ts +0 -181
- package/src/__tests__/consolidation-engine.test.ts +0 -520
- package/src/__tests__/constructive-retrieval.test.ts +0 -509
- package/src/__tests__/context-collapse-renderer.test.ts +0 -214
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-reconstructor.test.ts +0 -65
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/contracts.test.ts +0 -262
- package/src/__tests__/conversation-importer.test.ts +0 -692
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/data-checkup.test.ts +0 -304
- package/src/__tests__/decay-exemption.test.ts +0 -129
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/dedup-guard.test.ts +0 -39
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/distill-lock.test.ts +0 -305
- package/src/__tests__/distill-session-argshape.test.ts +0 -79
- package/src/__tests__/doctor.test.ts +0 -319
- package/src/__tests__/dream-pipeline.test.ts +0 -820
- package/src/__tests__/dream-scope-isolation.test.ts +0 -386
- package/src/__tests__/dream-write-batching.test.ts +0 -211
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/emotion-decay.test.ts +0 -46
- package/src/__tests__/emotion-detector.test.ts +0 -56
- package/src/__tests__/emotion-phase1-integration.test.ts +0 -153
- package/src/__tests__/emotion-retrieval.test.ts +0 -56
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/env-config-invariant.test.ts +0 -80
- package/src/__tests__/env-config.test.ts +0 -211
- package/src/__tests__/error-signature-boost.test.ts +0 -87
- package/src/__tests__/error-signature.test.ts +0 -95
- package/src/__tests__/error-taxonomy.test.ts +0 -129
- package/src/__tests__/ethics-layer.test.ts +0 -476
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/feature-flags-integration.test.ts +0 -54
- package/src/__tests__/frequency-tracker.test.ts +0 -113
- package/src/__tests__/freshness.test.ts +0 -186
- package/src/__tests__/graph-export.test.ts +0 -489
- package/src/__tests__/hotness-blend.test.ts +0 -283
- package/src/__tests__/ingest-chunking.test.ts +0 -44
- package/src/__tests__/ingest-scope-prefix.test.ts +0 -69
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/interference-detector.test.ts +0 -203
- package/src/__tests__/kg-extractor.test.ts +0 -192
- package/src/__tests__/kg-store.test.ts +0 -300
- package/src/__tests__/llm-client-pivot.test.ts +0 -628
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/maintenance-locks.test.ts +0 -197
- package/src/__tests__/mcp-efficiency.test.ts +0 -288
- package/src/__tests__/mcp-related-scopes.test.ts +0 -151
- package/src/__tests__/memory-boundaries.test.ts +0 -282
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +0 -362
- package/src/__tests__/memory-evolution.test.ts +0 -271
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-lint.test.ts +0 -421
- package/src/__tests__/memory-output.test.ts +0 -224
- package/src/__tests__/memory-promotion.test.ts +0 -315
- package/src/__tests__/memory-utility.test.ts +0 -256
- package/src/__tests__/migrate-dedup-ids.test.ts +0 -65
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/narrative-architecture.test.ts +0 -456
- package/src/__tests__/noise-filter.test.ts +0 -205
- package/src/__tests__/noise-prototype-bank.test.ts +0 -280
- package/src/__tests__/obsidian-connector.test.ts +0 -363
- package/src/__tests__/omitted-reporting.test.ts +0 -98
- package/src/__tests__/openai-contract.test.ts +0 -513
- package/src/__tests__/pending-review.test.ts +0 -77
- package/src/__tests__/pii-redact.test.ts +0 -333
- package/src/__tests__/pivot-apply.test.ts +0 -373
- package/src/__tests__/pivot-distill-supervisor.test.ts +0 -457
- package/src/__tests__/pivot-distill.test.ts +0 -1178
- package/src/__tests__/post-retrieval-filter.test.ts +0 -178
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/prediction-engine.test.ts +0 -349
- package/src/__tests__/predictive-prospective-memory.test.ts +0 -416
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -282
- package/src/__tests__/preference-slots.test.ts +0 -205
- package/src/__tests__/promotion-distinct-sources.test.ts +0 -162
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +0 -679
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander-alias.test.ts +0 -146
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-gate.test.ts +0 -266
- package/src/__tests__/recall-governor.test.ts +0 -227
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/resolve-db-path.test.ts +0 -130
- package/src/__tests__/resolve-tier.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4898
- package/src/__tests__/retrieval-eval.test.ts +0 -163
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retrieve-audit-revision.test.ts +0 -255
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +0 -123
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/salience-scoring.test.ts +0 -106
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/scope-suggester.test.ts +0 -80
- package/src/__tests__/session-checkpoint.test.ts +0 -291
- package/src/__tests__/session-distiller.test.ts +0 -640
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/skill-engine.test.ts +0 -552
- package/src/__tests__/skill-promotion.test.ts +0 -654
- package/src/__tests__/skill-verifier.test.ts +0 -122
- package/src/__tests__/source-heartbeat.test.ts +0 -254
- package/src/__tests__/stable-context-length-contract.test.ts +0 -62
- package/src/__tests__/store-patch-metadata.test.ts +0 -235
- package/src/__tests__/store-read-consistency.test.ts +0 -122
- package/src/__tests__/store-schema-migration.test.ts +0 -87
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/store-upsert.test.ts +0 -201
- package/src/__tests__/supersede-chain.test.ts +0 -154
- package/src/__tests__/synthesis-contract.test.ts +0 -252
- package/src/__tests__/synthesis-promotion.test.ts +0 -369
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/temporal-validity.test.ts +0 -160
- package/src/__tests__/term-registry-terminal-roster.test.ts +0 -127
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-error-surfacing.test.ts +0 -98
- package/src/__tests__/tool-output-compressor.test.ts +0 -160
- package/src/__tests__/topic-tag.test.ts +0 -113
- package/src/__tests__/trusted-memory.test.ts +0 -867
- package/src/__tests__/ui-server-security.test.ts +0 -93
- package/src/__tests__/usage-tracker.test.ts +0 -287
- package/src/__tests__/utility-weight.test.ts +0 -57
- package/src/__tests__/verbatim-self-recall.test.ts +0 -279
- package/src/__tests__/version-manager.test.ts +0 -248
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +0 -352
- package/src/__tests__/workflow-observation.test.ts +0 -433
- package/src/__tests__/write-verifier.test.ts +0 -122
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
export const PIVOT_EVIDENCE_CONTRACT_VERSION = 1 as const;
|
|
6
|
+
|
|
7
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
8
|
+
|
|
9
|
+
export const EvidenceCoordinateSchema = z.object({
|
|
10
|
+
sessionId: z.string().min(1).max(200),
|
|
11
|
+
ordinal: z.number().int().nonnegative(),
|
|
12
|
+
role: z.enum(["user", "assistant"]),
|
|
13
|
+
contentDigest: z.string().regex(SHA256_HEX),
|
|
14
|
+
}).strict();
|
|
15
|
+
export type EvidenceCoordinate = z.infer<typeof EvidenceCoordinateSchema>;
|
|
16
|
+
|
|
17
|
+
export const EvidenceWindowTurnSchema = z.object({
|
|
18
|
+
ordinal: z.number().int().nonnegative(),
|
|
19
|
+
role: z.enum(["user", "assistant"]),
|
|
20
|
+
contentDigest: z.string().regex(SHA256_HEX),
|
|
21
|
+
evidenceIndexes: z.array(z.number().int().nonnegative()).min(1).max(16),
|
|
22
|
+
}).strict();
|
|
23
|
+
export type EvidenceWindowTurn = z.infer<typeof EvidenceWindowTurnSchema>;
|
|
24
|
+
|
|
25
|
+
export const EvidenceWindowSchema = z.object({
|
|
26
|
+
sessionId: z.string().min(1).max(200),
|
|
27
|
+
startOrdinal: z.number().int().nonnegative(),
|
|
28
|
+
endOrdinal: z.number().int().nonnegative(),
|
|
29
|
+
turns: z.array(EvidenceWindowTurnSchema).min(1).max(16),
|
|
30
|
+
}).strict();
|
|
31
|
+
export type EvidenceWindow = z.infer<typeof EvidenceWindowSchema>;
|
|
32
|
+
|
|
33
|
+
export const PivotEvidenceProvenanceSchema = z.object({
|
|
34
|
+
evidenceContractVersion: z.literal(PIVOT_EVIDENCE_CONTRACT_VERSION),
|
|
35
|
+
sourceFingerprint: z.string().regex(SHA256_HEX),
|
|
36
|
+
anchorCoordinate: EvidenceCoordinateSchema,
|
|
37
|
+
evidenceWindows: z.array(EvidenceWindowSchema).max(16),
|
|
38
|
+
}).strict();
|
|
39
|
+
export type PivotEvidenceProvenance = z.infer<typeof PivotEvidenceProvenanceSchema>;
|
|
40
|
+
|
|
41
|
+
export function turnContentDigest(input: {
|
|
42
|
+
sessionId: string;
|
|
43
|
+
ordinal: number;
|
|
44
|
+
role: "user" | "assistant";
|
|
45
|
+
text: string;
|
|
46
|
+
}): string {
|
|
47
|
+
const canonical = JSON.stringify({
|
|
48
|
+
schemaVersion: PIVOT_EVIDENCE_CONTRACT_VERSION,
|
|
49
|
+
sessionId: input.sessionId,
|
|
50
|
+
ordinal: input.ordinal,
|
|
51
|
+
role: input.role,
|
|
52
|
+
text: input.text.replace(/\u0000/g, "").trim(),
|
|
53
|
+
});
|
|
54
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function evidenceWindowIssues(
|
|
58
|
+
provenance: PivotEvidenceProvenance,
|
|
59
|
+
evidenceCount: number,
|
|
60
|
+
expectedSessionId: string,
|
|
61
|
+
): string[] {
|
|
62
|
+
const issues: string[] = [];
|
|
63
|
+
if (provenance.anchorCoordinate.sessionId !== expectedSessionId) {
|
|
64
|
+
issues.push("anchor coordinate sessionId does not match candidate sessionId");
|
|
65
|
+
}
|
|
66
|
+
if (provenance.anchorCoordinate.role !== "user") {
|
|
67
|
+
issues.push("anchor coordinate must point to a user turn");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const seenEvidence = new Set<number>();
|
|
71
|
+
let previousEnd = -2;
|
|
72
|
+
for (const [windowIndex, window] of provenance.evidenceWindows.entries()) {
|
|
73
|
+
if (window.sessionId !== expectedSessionId) {
|
|
74
|
+
issues.push(`evidence window ${windowIndex} sessionId does not match candidate sessionId`);
|
|
75
|
+
}
|
|
76
|
+
const first = window.turns[0];
|
|
77
|
+
const last = window.turns[window.turns.length - 1];
|
|
78
|
+
if (window.startOrdinal !== first.ordinal || window.endOrdinal !== last.ordinal) {
|
|
79
|
+
issues.push(`evidence window ${windowIndex} bounds do not match its turns`);
|
|
80
|
+
}
|
|
81
|
+
if (window.startOrdinal <= previousEnd + 1) {
|
|
82
|
+
issues.push(`evidence window ${windowIndex} overlaps or should be merged with the previous window`);
|
|
83
|
+
}
|
|
84
|
+
previousEnd = window.endOrdinal;
|
|
85
|
+
|
|
86
|
+
for (let turnIndex = 0; turnIndex < window.turns.length; turnIndex++) {
|
|
87
|
+
const turn = window.turns[turnIndex];
|
|
88
|
+
if (turn.ordinal !== window.startOrdinal + turnIndex) {
|
|
89
|
+
issues.push(`evidence window ${windowIndex} contains a non-contiguous ordinal range`);
|
|
90
|
+
}
|
|
91
|
+
const localIndexes = new Set<number>();
|
|
92
|
+
for (const evidenceIndex of turn.evidenceIndexes) {
|
|
93
|
+
if (localIndexes.has(evidenceIndex)) {
|
|
94
|
+
issues.push(`evidence index ${evidenceIndex} is repeated inside one turn`);
|
|
95
|
+
}
|
|
96
|
+
localIndexes.add(evidenceIndex);
|
|
97
|
+
if (evidenceIndex < 0 || evidenceIndex >= evidenceCount) {
|
|
98
|
+
issues.push(`evidence index ${evidenceIndex} is outside the candidate evidence array`);
|
|
99
|
+
} else if (seenEvidence.has(evidenceIndex)) {
|
|
100
|
+
issues.push(`evidence index ${evidenceIndex} is mapped more than once`);
|
|
101
|
+
} else {
|
|
102
|
+
seenEvidence.add(evidenceIndex);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (let index = 0; index < evidenceCount; index++) {
|
|
108
|
+
if (!seenEvidence.has(index)) issues.push(`evidence index ${index} has no source coordinate`);
|
|
109
|
+
}
|
|
110
|
+
return issues;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Add provenance to content-addressed payloads only when the v1 evidence
|
|
115
|
+
* contract is present. Historical 2026-08-03 candidates remain byte-for-byte
|
|
116
|
+
* compatible; a partial or malformed new contract fails closed.
|
|
117
|
+
*/
|
|
118
|
+
export function evidenceIdentityPayload(value: unknown): Record<string, unknown> {
|
|
119
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
120
|
+
const record = value as Record<string, unknown>;
|
|
121
|
+
if (record.evidenceContractVersion === undefined) return {};
|
|
122
|
+
return PivotEvidenceProvenanceSchema.parse({
|
|
123
|
+
evidenceContractVersion: record.evidenceContractVersion,
|
|
124
|
+
sourceFingerprint: record.sourceFingerprint,
|
|
125
|
+
anchorCoordinate: record.anchorCoordinate,
|
|
126
|
+
evidenceWindows: record.evidenceWindows,
|
|
127
|
+
});
|
|
128
|
+
}
|
package/src/ppr-traversal.ts
CHANGED
|
@@ -10,7 +10,11 @@ import type { KGTriple, NeighborhoodResult } from "./kg-store.js";
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
|
|
12
12
|
export interface PPRConfig {
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Probability of CONTINUING to diffuse along an edge at each step.
|
|
15
|
+
* The teleport-back-to-seed probability is the complement: (1 - dampingFactor).
|
|
16
|
+
* (The previous version of this comment had the two reversed.)
|
|
17
|
+
*/
|
|
14
18
|
dampingFactor: number;
|
|
15
19
|
/** Max power-iteration rounds (default: 10) */
|
|
16
20
|
maxIterations: number;
|
|
@@ -31,6 +35,26 @@ export interface PPRResult {
|
|
|
31
35
|
path: string[];
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/**
|
|
39
|
+
* NOTE on dampingFactor = 0.85 (measured 2026-08-28, deliberately NOT changed):
|
|
40
|
+
*
|
|
41
|
+
* 0.85 is Brin & Page's default, tuned for billion-node web link graphs.
|
|
42
|
+
* HippoRAG 2 (arXiv 2502.14802, Table 13) uses 0.5 for knowledge graphs.
|
|
43
|
+
*
|
|
44
|
+
* Sensitivity measured on our own production KG (60,536 nodes / 66,332 edges,
|
|
45
|
+
* 10 real seeds, Top-10 Jaccard against alpha=0.85):
|
|
46
|
+
* alpha=0.30 -> 0.617 | 0.50 -> 0.648 | 0.70 -> 0.760 | 0.95 -> 0.598
|
|
47
|
+
* alpha=0.30 vs alpha=0.95 -> 0.371 (~63% of results differ)
|
|
48
|
+
* So the parameter IS sensitive. It is nevertheless left at 0.85, because
|
|
49
|
+
* tuning it does not fix the actual failure mode: at every alpha tested,
|
|
50
|
+
* generic hub nodes ("user" / "assistant" / "Alice") take ~30% of the top-20
|
|
51
|
+
* and land at ranks 2-4. Changing alpha only reshuffles the hubs among
|
|
52
|
+
* themselves.
|
|
53
|
+
*
|
|
54
|
+
* Fix the graph first (entity normalization + hub suppression), then tune.
|
|
55
|
+
* Revisit only after the top-20 hub share drops materially; any change must
|
|
56
|
+
* go through a shadow run first.
|
|
57
|
+
*/
|
|
34
58
|
export const DEFAULT_PPR_CONFIG: PPRConfig = {
|
|
35
59
|
dampingFactor: 0.85,
|
|
36
60
|
maxIterations: 10,
|
package/src/runtime-config.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface LocalMemoryConfig {
|
|
|
27
27
|
dimensions?: number;
|
|
28
28
|
taskQuery?: string;
|
|
29
29
|
taskPassage?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Per-request timeout (ms) for the embedding client. Omit to keep the SDK default
|
|
32
|
+
* (600s in openai v7); `RECALLNEST_EMBEDDING_TIMEOUT_MS` overrides this.
|
|
33
|
+
*/
|
|
34
|
+
timeoutMs?: number;
|
|
30
35
|
};
|
|
31
36
|
llm?: {
|
|
32
37
|
apiKey: string;
|
|
@@ -156,6 +161,10 @@ export function createComponents(config: LocalMemoryConfig, profileName?: string
|
|
|
156
161
|
dimensions: config.embedding.dimensions,
|
|
157
162
|
taskQuery: config.embedding.taskQuery,
|
|
158
163
|
taskPassage: config.embedding.taskPassage,
|
|
164
|
+
// Env wins over config.json so a single deployment can bound its embed calls
|
|
165
|
+
// without editing the shared file. Both unset → no `timeout` reaches the SDK,
|
|
166
|
+
// which is the behavior every release through v3.0.0 had.
|
|
167
|
+
timeoutMs: envConfig.embeddingTimeoutMs() ?? config.embedding.timeoutMs,
|
|
159
168
|
};
|
|
160
169
|
|
|
161
170
|
const embedder = createEmbedder(embeddingConfig);
|
package/src/session-engine.ts
CHANGED
|
@@ -8,6 +8,14 @@ import {
|
|
|
8
8
|
} from "./session-schema.js";
|
|
9
9
|
import { buildIdempotentRecordId } from "./idempotency.js";
|
|
10
10
|
|
|
11
|
+
/** 当 summary 里每一句都被 repo-state 规则滤掉时的兜底文案(单一来源,勿再字面重复)。 */
|
|
12
|
+
export const FALLBACK_SUMMARY = "Checkpoint captured current task state without repo-state details.";
|
|
13
|
+
|
|
14
|
+
/** summary 是否已退化成兜底文案——即「本来写了东西,但整段被清洗空了」。 */
|
|
15
|
+
export function isFallbackSummary(summary: string | null | undefined): boolean {
|
|
16
|
+
return (summary ?? "").trim() === FALLBACK_SUMMARY;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
const REPO_STATE_PATTERN = /\bgit status\b|未提交|staged|uncommitted|已修改文件|modified files?|untracked|新增文件|dirty repo|dirty worktree/iu;
|
|
12
20
|
const SENTENCE_SPLIT_PATTERN = /(?:\r?\n)+|(?<=[。!?!?;;])\s+/u;
|
|
13
21
|
const CHECKPOINT_SANITIZABLE_FIELDS = ["summary", "task", "decisions", "openLoops", "nextActions"] as const;
|
|
@@ -51,7 +59,7 @@ function sanitizeCheckpointSummary(summary: string): SanitizedStringResult {
|
|
|
51
59
|
.map(normalizeCheckpointText)
|
|
52
60
|
.filter(Boolean);
|
|
53
61
|
const kept = parts.filter((part) => !hasRepoStateText(part));
|
|
54
|
-
const value = kept[0] ? kept.join(" ") :
|
|
62
|
+
const value = kept[0] ? kept.join(" ") : FALLBACK_SUMMARY;
|
|
55
63
|
return {
|
|
56
64
|
value,
|
|
57
65
|
changed: kept.length !== parts.length,
|
|
@@ -146,13 +154,12 @@ export function buildSessionCheckpointResult(rawInput: unknown): SessionCheckpoi
|
|
|
146
154
|
resolvedScope: resolveCheckpointScope(sanitizedInput),
|
|
147
155
|
});
|
|
148
156
|
|
|
149
|
-
const
|
|
150
|
-
const isFallbackSummary = record.summary === FALLBACK_SUMMARY;
|
|
157
|
+
const summaryIsFallback = isFallbackSummary(record.summary);
|
|
151
158
|
const hasStructuredContent =
|
|
152
159
|
record.decisions.length > 0 ||
|
|
153
160
|
record.openLoops.length > 0 ||
|
|
154
161
|
record.nextActions.length > 0;
|
|
155
|
-
const quality: CheckpointQuality =
|
|
162
|
+
const quality: CheckpointQuality = summaryIsFallback && !hasStructuredContent ? "minimal" : "rich";
|
|
156
163
|
|
|
157
164
|
return {
|
|
158
165
|
record,
|
package/src/session-output.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ResumeContextResponse, SessionCheckpointRecord } from "./session-schema.js";
|
|
2
2
|
import { cleanText } from "./context-composer-text.js";
|
|
3
3
|
import { formatIsoAgeLabel } from "./age-label.js";
|
|
4
|
+
import { isFallbackSummary } from "./session-engine.js";
|
|
4
5
|
|
|
5
6
|
function listBlock(label: string, items: string[]): string[] {
|
|
6
7
|
if (items.length === 0) return [];
|
|
@@ -12,7 +13,14 @@ function listBlock(label: string, items: string[]): string[] {
|
|
|
12
13
|
|
|
13
14
|
export function formatCheckpointRecallSummary(record: SessionCheckpointRecord): string {
|
|
14
15
|
const parts: string[] = [];
|
|
15
|
-
const
|
|
16
|
+
const rawSummary = record.summary.trim();
|
|
17
|
+
// summary 整段被 repo-state 清洗规则滤空时会退化成兜底文案,headline 就只剩一句无信息的
|
|
18
|
+
// 套话。此时改用第一条实质内容顶上,并保留标记,让读者能分辨「本来就没写」和「被洗掉了」。
|
|
19
|
+
const substitute = isFallbackSummary(rawSummary)
|
|
20
|
+
? [...record.decisions, ...record.nextActions, ...record.openLoops]
|
|
21
|
+
.find((item) => item.trim().length > 0) ?? ""
|
|
22
|
+
: "";
|
|
23
|
+
const baseSummary = substitute ? `[summary sanitized] ${substitute}` : rawSummary;
|
|
16
24
|
if (baseSummary) {
|
|
17
25
|
parts.push(baseSummary);
|
|
18
26
|
}
|
package/src/session-store.ts
CHANGED
|
@@ -4,12 +4,11 @@ import { basename, join, resolve } from "node:path";
|
|
|
4
4
|
import { metaDir } from "./compat.js";
|
|
5
5
|
import type { SessionCheckpointRecord } from "./session-schema.js";
|
|
6
6
|
import { SessionCheckpointRecordSchema } from "./session-schema.js";
|
|
7
|
-
import { normalizeCheckpointScope, type CheckpointQuality } from "./session-engine.js";
|
|
7
|
+
import { isFallbackSummary, normalizeCheckpointScope, type CheckpointQuality } from "./session-engine.js";
|
|
8
8
|
|
|
9
|
-
const FALLBACK_SUMMARY = "Checkpoint captured current task state without repo-state details.";
|
|
10
9
|
|
|
11
10
|
export function classifyCheckpointQuality(record: SessionCheckpointRecord): CheckpointQuality {
|
|
12
|
-
const isFallback = record.summary
|
|
11
|
+
const isFallback = isFallbackSummary(record.summary);
|
|
13
12
|
const hasContent = record.decisions.length > 0 || record.openLoops.length > 0 || record.nextActions.length > 0;
|
|
14
13
|
return isFallback && !hasContent ? "minimal" : "rich";
|
|
15
14
|
}
|
package/src/skill-promotion.ts
CHANGED
|
@@ -34,6 +34,37 @@ export interface PromotionCandidate {
|
|
|
34
34
|
distinctSources?: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 被跨来源判据拒掉的簇 —— 「同一个坑踩了 N 次」。
|
|
39
|
+
*
|
|
40
|
+
* 这不是待晋升的规律,是反模式证据:`judgeDistinctSources` 判 `rejected` 时原本只
|
|
41
|
+
* `rejectedSingleSource++` 然后 continue,信号变成一个数字后消失(同文件下方注释
|
|
42
|
+
* 早就写着"走 failure-burst 反模式那条路",而那条路一直不存在)。
|
|
43
|
+
*
|
|
44
|
+
* 字段借自 MemTensor/memmy-agent 的 `Memory/src/service/evolution/negative-experience-pipeline.ts`
|
|
45
|
+
* (其 `NegativeExperienceSource` 枚举里字面就有 `tool_failure_burst`)。
|
|
46
|
+
* **有意只借它列的证据字段**:memmy 那份 schema 还有 `preference`(该怎么做)与
|
|
47
|
+
* `verification`(怎么确认没再犯),但那两格要的是判断不是统计 —— 扫描器编出来的
|
|
48
|
+
* 「该怎么做」比没有更糟。它们列在 `needsJudgement` 里等人或 LLM 填。
|
|
49
|
+
*/
|
|
50
|
+
export interface AntiPatternCandidate {
|
|
51
|
+
/** 目前只有一条来路;留成联合类型是为了将来接别的信号(如工具连续失败)时不改调用方。 */
|
|
52
|
+
source: "single_source_burst";
|
|
53
|
+
/** 撞上它的场景 —— 取簇种子的首行,和 candidates 的 suggestedName 同款提取。 */
|
|
54
|
+
trigger: string;
|
|
55
|
+
/** 反模式描述 —— 簇内多条的共性摘要。 */
|
|
56
|
+
antiPattern: string;
|
|
57
|
+
/** 同一个坑重复了几次。 */
|
|
58
|
+
occurrences: number;
|
|
59
|
+
/** 实际覆盖几个不同来源(判 rejected 说明它 < minDistinctSources)。 */
|
|
60
|
+
distinctSources: number;
|
|
61
|
+
/** 0-1。同一来源里重复越多越强,但不因此就变成"规律"。 */
|
|
62
|
+
evidenceStrength: number;
|
|
63
|
+
sourceEntries: Array<{ id: string; text: string }>;
|
|
64
|
+
/** 扫描器有意不填的字段,等判断补上。写死成数组是为了让调用方能机械检查。 */
|
|
65
|
+
needsJudgement: ReadonlyArray<"preference" | "verification">;
|
|
66
|
+
}
|
|
67
|
+
|
|
37
68
|
export interface PromotionScanResult {
|
|
38
69
|
candidates: PromotionCandidate[];
|
|
39
70
|
scannedCases: number;
|
|
@@ -46,6 +77,12 @@ export interface PromotionScanResult {
|
|
|
46
77
|
rejectedSingleSource: number;
|
|
47
78
|
/** No-silent-caps 披露:拿不到来源指针、跨来源判据弃权放行的簇数。 */
|
|
48
79
|
abstainedUnknownSource: number;
|
|
80
|
+
/**
|
|
81
|
+
* P4 反模式出口:被判 rejected 的簇的**内容**,不只是计数。
|
|
82
|
+
* 注意与 abstainedUnknownSource 的区别 —— 弃权的簇不进这里:
|
|
83
|
+
* 不知道来源就不能指控它是「同一个坑踩三次」。
|
|
84
|
+
*/
|
|
85
|
+
antiPatterns?: AntiPatternCandidate[];
|
|
49
86
|
}
|
|
50
87
|
|
|
51
88
|
export interface PromotionConfig {
|
|
@@ -462,6 +499,7 @@ export async function scanForPromotions(
|
|
|
462
499
|
vectorlessSkipped: 0,
|
|
463
500
|
rejectedSingleSource: 0,
|
|
464
501
|
abstainedUnknownSource: 0,
|
|
502
|
+
antiPatterns: [],
|
|
465
503
|
};
|
|
466
504
|
|
|
467
505
|
// 2. Cluster cases bucket-by-bucket (topicTag, untagged fallback)。
|
|
@@ -497,6 +535,19 @@ export async function scanForPromotions(
|
|
|
497
535
|
const sourceVerdict = judgeDistinctSources(cluster.members, cfg.minDistinctSources);
|
|
498
536
|
if (sourceVerdict.status === "rejected") {
|
|
499
537
|
result.rejectedSingleSource++;
|
|
538
|
+
// P4:不晋升,但也不让它无声消失 —— 同一个坑重复本身就是要记的东西。
|
|
539
|
+
// 仍然 continue,晋升判据分毫未动。
|
|
540
|
+
result.antiPatterns.push({
|
|
541
|
+
source: "single_source_burst",
|
|
542
|
+
trigger: extractName(cluster.seed.text),
|
|
543
|
+
antiPattern: summarizeCluster(cluster.members),
|
|
544
|
+
occurrences: cluster.members.length,
|
|
545
|
+
distinctSources: sourceVerdict.distinctSources,
|
|
546
|
+
// Bayesian smoothing,与 candidates 的 confidence 同款,饱和于 1。
|
|
547
|
+
evidenceStrength: cluster.members.length / (cluster.members.length + 2),
|
|
548
|
+
sourceEntries: cluster.members.map(m => ({ id: m.id, text: m.text })),
|
|
549
|
+
needsJudgement: ["preference", "verification"],
|
|
550
|
+
});
|
|
500
551
|
continue;
|
|
501
552
|
}
|
|
502
553
|
if (sourceVerdict.status === "abstained") result.abstainedUnknownSource++;
|
|
@@ -640,10 +691,25 @@ export function formatPromotionResult(result: PromotionScanResult): string {
|
|
|
640
691
|
if (result.rejectedSingleSource > 0) {
|
|
641
692
|
lines.push(
|
|
642
693
|
`⚠️ ${result.rejectedSingleSource} cluster(s) had enough entries but too few distinct sources`
|
|
643
|
-
+ ` —
|
|
694
|
+
+ ` — 同一次经历的重复不算规律(不晋升,改走下面的反模式清单)。`,
|
|
644
695
|
);
|
|
645
696
|
}
|
|
646
697
|
|
|
698
|
+
// P4:被拒的簇在这里落地。只显示计数等于让信号消失在一个数字里 ——
|
|
699
|
+
// 消费者看到的是这段文本,不是 result 对象。
|
|
700
|
+
// `?.` 不是防御性编程的装饰:PromotionScanResult 是导出接口,旧调用方构造的对象
|
|
701
|
+
// 没有这个字段,运行时会是 undefined(TS 编译期拦不住外部构造)。
|
|
702
|
+
if (result.antiPatterns?.length) {
|
|
703
|
+
lines.push(`\n🔁 ${result.antiPatterns.length} anti-pattern(s) — 同一个坑重复出现,不是规律但要记:\n`);
|
|
704
|
+
for (const [i, ap] of result.antiPatterns.entries()) {
|
|
705
|
+
lines.push(`### A${i + 1}. ${ap.trigger}`);
|
|
706
|
+
lines.push(`重复 ${ap.occurrences} 次,来自 ${ap.distinctSources} 个来源(证据强度 ${(ap.evidenceStrength * 100).toFixed(0)}%)`);
|
|
707
|
+
lines.push(`共性: ${ap.antiPattern}`);
|
|
708
|
+
lines.push(`待补判断: ${ap.needsJudgement.join(" / ")} — 扫描器只出证据,「该怎么做」和「怎么确认没再犯」要人或 LLM 填。`);
|
|
709
|
+
lines.push("");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
647
713
|
if (result.candidates.length === 0) {
|
|
648
714
|
lines.push("No promotion candidates found.");
|
|
649
715
|
return lines.join("\n");
|