@miller-tech/uap 1.175.13 → 1.178.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/dist/.tsbuildinfo +1 -1
- package/dist/benchmarks/harness-card.d.ts +86 -0
- package/dist/benchmarks/harness-card.d.ts.map +1 -0
- package/dist/benchmarks/harness-card.js +153 -0
- package/dist/benchmarks/harness-card.js.map +1 -0
- package/dist/benchmarks/paired/report.d.ts +15 -0
- package/dist/benchmarks/paired/report.d.ts.map +1 -1
- package/dist/benchmarks/paired/report.js +12 -0
- package/dist/benchmarks/paired/report.js.map +1 -1
- package/dist/benchmarks/paired/types.d.ts +1 -1
- package/dist/bin/cli.js +27 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/bench.d.ts.map +1 -1
- package/dist/cli/bench.js +64 -3
- package/dist/cli/bench.js.map +1 -1
- package/dist/cli/harness.d.ts +3 -2
- package/dist/cli/harness.d.ts.map +1 -1
- package/dist/cli/harness.js +81 -0
- package/dist/cli/harness.js.map +1 -1
- package/dist/cli/memory.d.ts +7 -1
- package/dist/cli/memory.d.ts.map +1 -1
- package/dist/cli/memory.js +165 -0
- package/dist/cli/memory.js.map +1 -1
- package/dist/delivery/agentic-executor.d.ts +76 -2
- package/dist/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +231 -29
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/edit-match.d.ts +134 -0
- package/dist/delivery/edit-match.d.ts.map +1 -0
- package/dist/delivery/edit-match.js +331 -0
- package/dist/delivery/edit-match.js.map +1 -0
- package/dist/memory/dynamic-retrieval.d.ts.map +1 -1
- package/dist/memory/dynamic-retrieval.js +57 -0
- package/dist/memory/dynamic-retrieval.js.map +1 -1
- package/dist/memory/reconstruct-ingest.d.ts +88 -0
- package/dist/memory/reconstruct-ingest.d.ts.map +1 -0
- package/dist/memory/reconstruct-ingest.js +165 -0
- package/dist/memory/reconstruct-ingest.js.map +1 -0
- package/dist/memory/reconstruct-store.d.ts +143 -0
- package/dist/memory/reconstruct-store.d.ts.map +1 -0
- package/dist/memory/reconstruct-store.js +312 -0
- package/dist/memory/reconstruct-store.js.map +1 -0
- package/dist/memory/reconstruct.d.ts +272 -0
- package/dist/memory/reconstruct.d.ts.map +1 -0
- package/dist/memory/reconstruct.js +552 -0
- package/dist/memory/reconstruct.js.map +1 -0
- package/dist/policies/schemas/policy.d.ts +2 -2
- package/dist/self-harness/evidence-proposer.d.ts +58 -0
- package/dist/self-harness/evidence-proposer.d.ts.map +1 -0
- package/dist/self-harness/evidence-proposer.js +132 -0
- package/dist/self-harness/evidence-proposer.js.map +1 -0
- package/dist/self-harness/manifest.d.ts +130 -0
- package/dist/self-harness/manifest.d.ts.map +1 -0
- package/dist/self-harness/manifest.js +247 -0
- package/dist/self-harness/manifest.js.map +1 -0
- package/dist/self-harness/mods.d.ts +57 -3
- package/dist/self-harness/mods.d.ts.map +1 -1
- package/dist/self-harness/mods.js +72 -0
- package/dist/self-harness/mods.js.map +1 -1
- package/dist/self-harness/orchestrator.d.ts +27 -0
- package/dist/self-harness/orchestrator.d.ts.map +1 -1
- package/dist/self-harness/orchestrator.js +47 -4
- package/dist/self-harness/orchestrator.js.map +1 -1
- package/dist/self-harness/pending.d.ts +17 -3
- package/dist/self-harness/pending.d.ts.map +1 -1
- package/dist/self-harness/pending.js +26 -4
- package/dist/self-harness/pending.js.map +1 -1
- package/dist/self-harness/profile.d.ts +17 -1
- package/dist/self-harness/profile.d.ts.map +1 -1
- package/dist/self-harness/profile.js +11 -4
- package/dist/self-harness/profile.js.map +1 -1
- package/dist/self-harness/run.d.ts +19 -0
- package/dist/self-harness/run.d.ts.map +1 -1
- package/dist/self-harness/run.js +43 -4
- package/dist/self-harness/run.js.map +1 -1
- package/dist/self-harness/transfer.d.ts.map +1 -1
- package/dist/self-harness/transfer.js +2 -0
- package/dist/self-harness/transfer.js.map +1 -1
- package/dist/self-harness/validate.d.ts +17 -6
- package/dist/self-harness/validate.d.ts.map +1 -1
- package/dist/self-harness/validate.js +73 -8
- package/dist/self-harness/validate.js.map +1 -1
- package/dist/telemetry/tool-calls.d.ts +82 -0
- package/dist/telemetry/tool-calls.d.ts.map +1 -0
- package/dist/telemetry/tool-calls.js +280 -0
- package/dist/telemetry/tool-calls.js.map +1 -0
- package/dist/telemetry/tool-failure.d.ts +71 -0
- package/dist/telemetry/tool-failure.d.ts.map +1 -0
- package/dist/telemetry/tool-failure.js +123 -0
- package/dist/telemetry/tool-failure.js.map +1 -0
- package/docs/plans/harness-engineering-uplift-2026-07-31.md +280 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-tool matching (harness plan area A, 2026-07-31).
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists: the harness-disclosure literature reports that changing
|
|
5
|
+
* ONLY the edit-tool format moved a fixed model from 6.7% to 68.3% on SWE-bench
|
|
6
|
+
* (Pi Research / Grok Code Fast, cited in arXiv 2605.23950). The same paper
|
|
7
|
+
* measures harness variance at 7.8x model variance. The edit tool is the single
|
|
8
|
+
* highest-leverage surface in the tool layer, and ours was strict-exact-match:
|
|
9
|
+
* one whitespace character off and the turn was dead, with an error message that
|
|
10
|
+
* told the model to "match exactly" — which is precisely the thing it had just
|
|
11
|
+
* failed to do.
|
|
12
|
+
*
|
|
13
|
+
* The strategy ladder implemented here:
|
|
14
|
+
*
|
|
15
|
+
* 1. EXACT — byte-identical substring. Unchanged, always tried first, so
|
|
16
|
+
* a model that gets it right sees identical behaviour.
|
|
17
|
+
* 2. TOLERANT — line-aligned match after per-line whitespace normalisation
|
|
18
|
+
* (leading/trailing trimmed, internal runs collapsed). Catches
|
|
19
|
+
* the dominant real miss: right code, wrong indentation.
|
|
20
|
+
* 3. NEAREST — no match at all, so instead of "not found" we return the
|
|
21
|
+
* closest region of the file with line numbers, turning a dead
|
|
22
|
+
* turn into a corrective one.
|
|
23
|
+
*
|
|
24
|
+
* Everything here is PURE (string in, decision out) so the ladder is unit
|
|
25
|
+
* testable without a model, a file system, or a delivery run.
|
|
26
|
+
*/
|
|
27
|
+
/** How a needle was located in the haystack. */
|
|
28
|
+
export type EditMatchKind =
|
|
29
|
+
/** Byte-identical substring found (possibly several — see `count`). */
|
|
30
|
+
'exact'
|
|
31
|
+
/** Found only after per-line whitespace normalisation. */
|
|
32
|
+
| 'tolerant'
|
|
33
|
+
/** Found more than once and the caller gave no disambiguator. */
|
|
34
|
+
| 'ambiguous'
|
|
35
|
+
/** Not found by any strategy. */
|
|
36
|
+
| 'miss';
|
|
37
|
+
export interface EditMatch {
|
|
38
|
+
kind: EditMatchKind;
|
|
39
|
+
/** Character offset of the matched span. -1 when `kind` is 'miss'/'ambiguous'. */
|
|
40
|
+
index: number;
|
|
41
|
+
/** Length of the matched span in characters. 0 when there is no span. */
|
|
42
|
+
length: number;
|
|
43
|
+
/** How many candidate matches the winning strategy found. */
|
|
44
|
+
count: number;
|
|
45
|
+
/**
|
|
46
|
+
* Operator/model-facing explanation. For 'tolerant' this names the whitespace
|
|
47
|
+
* drift so the model learns; for 'miss' it carries the nearest-match report.
|
|
48
|
+
*/
|
|
49
|
+
note?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Leading whitespace of the FILE line the tolerant span starts on, and of the
|
|
52
|
+
* needle's first line. `applyEditMatch` re-indents the replacement by their
|
|
53
|
+
* delta: without it a tolerant match splices the model's (wrong) indentation
|
|
54
|
+
* in at the target site, which is cosmetic in C-like languages and a syntax
|
|
55
|
+
* error in Python.
|
|
56
|
+
*/
|
|
57
|
+
targetIndent?: string;
|
|
58
|
+
needleIndent?: string;
|
|
59
|
+
/**
|
|
60
|
+
* True when the matched span excludes the trailing newline (the tolerant rung
|
|
61
|
+
* is line-aligned). `applyEditMatch` strips one trailing newline from the
|
|
62
|
+
* replacement so the exact and tolerant rungs cannot produce different line
|
|
63
|
+
* counts for the same (old, new) pair.
|
|
64
|
+
*/
|
|
65
|
+
lineAligned?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface EditMatchOptions {
|
|
68
|
+
/** 1-based occurrence selector, as the `edit_file` tool exposes it. */
|
|
69
|
+
occurrence?: number | null;
|
|
70
|
+
/**
|
|
71
|
+
* Allow the whitespace-tolerant rung. Defaults to true. This is a harness
|
|
72
|
+
* KNOB, deliberately: `ToolMod` in the self-harness search space flips it so
|
|
73
|
+
* the paired bench can measure whether tolerance helps or hurts, rather than
|
|
74
|
+
* us asserting that it helps.
|
|
75
|
+
*/
|
|
76
|
+
tolerant?: boolean;
|
|
77
|
+
/** Include the nearest-match report on a miss. Defaults to true. */
|
|
78
|
+
diagnostics?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Treat leading whitespace as significant (Python/YAML/Make/...). Callers pass
|
|
81
|
+
* `isIndentSensitive(path)`. Defaults to false.
|
|
82
|
+
*/
|
|
83
|
+
indentSensitive?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export declare function isIndentSensitive(path: string): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Build the nearest-match report shown when nothing matched.
|
|
88
|
+
*
|
|
89
|
+
* The model's failure is almost always "my memory of the file is stale", so the
|
|
90
|
+
* single most useful thing we can hand back is the CURRENT text of the region it
|
|
91
|
+
* was aiming at, with line numbers it can feed straight into `edit_range`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function nearestMatchReport(haystack: string, needle: string, maxLines?: number): string;
|
|
94
|
+
/**
|
|
95
|
+
* Locate `oldStr` in `current` using the exact -> tolerant -> miss ladder.
|
|
96
|
+
*
|
|
97
|
+
* Contract: a returned 'exact' or 'tolerant' match is safe to apply verbatim by
|
|
98
|
+
* slicing at [index, index+length). 'ambiguous' and 'miss' carry a `note` that
|
|
99
|
+
* is already model-facing prose.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveEditMatch(current: string, oldStr: string, options?: EditMatchOptions): EditMatch;
|
|
102
|
+
/**
|
|
103
|
+
* Apply a resolved match. Caller must have checked `kind` is exact/tolerant.
|
|
104
|
+
*
|
|
105
|
+
* Two corrections happen only on the line-aligned (tolerant) path, because only
|
|
106
|
+
* there does the matched span differ in shape from what the model sent:
|
|
107
|
+
*
|
|
108
|
+
* - RE-INDENT. The span starts at column 0 of the matched line while the needle
|
|
109
|
+
* may have been unindented, so splicing verbatim would move the code to
|
|
110
|
+
* column 0. We rebase the replacement onto the target's indentation.
|
|
111
|
+
* - TRAILING NEWLINE. The span excludes the line break that follows it, so a
|
|
112
|
+
* `new_string` ending in "\n" would add a blank line — making the same
|
|
113
|
+
* (old, new) pair produce different files depending on which rung fired.
|
|
114
|
+
*/
|
|
115
|
+
export declare function applyEditMatch(current: string, match: EditMatch, newStr: string): string;
|
|
116
|
+
export interface RangeEditResult {
|
|
117
|
+
ok: boolean;
|
|
118
|
+
/** Updated text when ok. */
|
|
119
|
+
text?: string;
|
|
120
|
+
/** Model-facing reason when not ok. */
|
|
121
|
+
error?: string;
|
|
122
|
+
/** Number of lines the range covered, for the success note. */
|
|
123
|
+
replacedLines?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Replace an inclusive 1-based LINE RANGE.
|
|
127
|
+
*
|
|
128
|
+
* Why this exists alongside `edit_file`: a model that cannot reproduce exact
|
|
129
|
+
* whitespace can still count lines, and `read_file` hands it numbered context.
|
|
130
|
+
* This gives the ladder a rung that does not depend on reproducing file bytes at
|
|
131
|
+
* all — the failure mode the tolerant rung only softens.
|
|
132
|
+
*/
|
|
133
|
+
export declare function applyRangeEdit(current: string, startLine: number, endLine: number, newText: string): RangeEditResult;
|
|
134
|
+
//# sourceMappingURL=edit-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-match.d.ts","sourceRoot":"","sources":["../../src/delivery/edit-match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,gDAAgD;AAChD,MAAM,MAAM,aAAa;AACvB,uEAAuE;AACrE,OAAO;AACT,0DAA0D;GACxD,UAAU;AACZ,iEAAiE;GAC/D,WAAW;AACb,iCAAiC;GAC/B,MAAM,CAAC;AAEX,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,aAAa,CAAC;IACpB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oEAAoE;IACpE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA0CD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AA4ED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,MAAM,CAkCzF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAoFX;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAqBxF;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,eAAe,CAuBjB"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-tool matching (harness plan area A, 2026-07-31).
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists: the harness-disclosure literature reports that changing
|
|
5
|
+
* ONLY the edit-tool format moved a fixed model from 6.7% to 68.3% on SWE-bench
|
|
6
|
+
* (Pi Research / Grok Code Fast, cited in arXiv 2605.23950). The same paper
|
|
7
|
+
* measures harness variance at 7.8x model variance. The edit tool is the single
|
|
8
|
+
* highest-leverage surface in the tool layer, and ours was strict-exact-match:
|
|
9
|
+
* one whitespace character off and the turn was dead, with an error message that
|
|
10
|
+
* told the model to "match exactly" — which is precisely the thing it had just
|
|
11
|
+
* failed to do.
|
|
12
|
+
*
|
|
13
|
+
* The strategy ladder implemented here:
|
|
14
|
+
*
|
|
15
|
+
* 1. EXACT — byte-identical substring. Unchanged, always tried first, so
|
|
16
|
+
* a model that gets it right sees identical behaviour.
|
|
17
|
+
* 2. TOLERANT — line-aligned match after per-line whitespace normalisation
|
|
18
|
+
* (leading/trailing trimmed, internal runs collapsed). Catches
|
|
19
|
+
* the dominant real miss: right code, wrong indentation.
|
|
20
|
+
* 3. NEAREST — no match at all, so instead of "not found" we return the
|
|
21
|
+
* closest region of the file with line numbers, turning a dead
|
|
22
|
+
* turn into a corrective one.
|
|
23
|
+
*
|
|
24
|
+
* Everything here is PURE (string in, decision out) so the ladder is unit
|
|
25
|
+
* testable without a model, a file system, or a delivery run.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Normalise ONE line for tolerant comparison: collapse internal whitespace runs
|
|
29
|
+
* and drop trailing whitespace. Leading whitespace is dropped only when the file
|
|
30
|
+
* is not indentation-sensitive.
|
|
31
|
+
*
|
|
32
|
+
* `preserveIndent` exists because INDENTATION IS SEMANTIC in Python, YAML,
|
|
33
|
+
* Makefiles and Nim. Without it,
|
|
34
|
+
*
|
|
35
|
+
* if x: if x:
|
|
36
|
+
* a() a()
|
|
37
|
+
* b() b()
|
|
38
|
+
*
|
|
39
|
+
* normalise identically, and a tolerant match binds an anchor in one block to
|
|
40
|
+
* the other — a silent wrong-block edit on a default-on path. For those files we
|
|
41
|
+
* keep the indent exact and only forgive internal/trailing spacing.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately NOT normalising anything else. Case, punctuation and quote style
|
|
44
|
+
* are all semantic in code — a match that ignored them would apply an edit the
|
|
45
|
+
* model did not ask for, which is far worse than a failed edit.
|
|
46
|
+
*/
|
|
47
|
+
function normalizeLine(line, preserveIndent = false) {
|
|
48
|
+
// Split indent from body FIRST. Collapsing the whole line would squash the
|
|
49
|
+
// leading run to a single space too, so 4-space and 8-space indents would
|
|
50
|
+
// normalise identically and `preserveIndent` would preserve nothing.
|
|
51
|
+
const indent = indentOf(line);
|
|
52
|
+
const body = line.slice(indent.length).replace(/\s+/g, ' ').trimEnd();
|
|
53
|
+
return preserveIndent ? indent + body : body;
|
|
54
|
+
}
|
|
55
|
+
/** Leading-whitespace prefix of a line. */
|
|
56
|
+
function indentOf(line) {
|
|
57
|
+
return /^[ \t]*/.exec(line)?.[0] ?? '';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Extensions whose semantics depend on leading whitespace. A tolerant match in
|
|
61
|
+
* one of these must not forgive indentation.
|
|
62
|
+
*/
|
|
63
|
+
const INDENT_SENSITIVE = /\.(py|pyi|yaml|yml|nim|coffee|haml|slim|sass|pug|jade)$|(^|\/)Makefile$/i;
|
|
64
|
+
export function isIndentSensitive(path) {
|
|
65
|
+
return INDENT_SENSITIVE.test(path);
|
|
66
|
+
}
|
|
67
|
+
/** Character offset at which each line starts, plus a terminating sentinel. */
|
|
68
|
+
function lineOffsets(text) {
|
|
69
|
+
const offsets = [0];
|
|
70
|
+
for (let i = 0; i < text.length; i++) {
|
|
71
|
+
if (text[i] === '\n')
|
|
72
|
+
offsets.push(i + 1);
|
|
73
|
+
}
|
|
74
|
+
return offsets;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Token-overlap similarity in [0,1] between two normalised lines. Used only to
|
|
78
|
+
* RANK candidates for the nearest-match report — never to decide an edit.
|
|
79
|
+
*/
|
|
80
|
+
function similarity(a, b) {
|
|
81
|
+
if (a === b)
|
|
82
|
+
return 1;
|
|
83
|
+
const at = a.split(' ').filter(Boolean);
|
|
84
|
+
const bt = b.split(' ').filter(Boolean);
|
|
85
|
+
if (at.length === 0 || bt.length === 0)
|
|
86
|
+
return 0;
|
|
87
|
+
const bCounts = new Map();
|
|
88
|
+
for (const t of bt)
|
|
89
|
+
bCounts.set(t, (bCounts.get(t) ?? 0) + 1);
|
|
90
|
+
let shared = 0;
|
|
91
|
+
for (const t of at) {
|
|
92
|
+
const n = bCounts.get(t) ?? 0;
|
|
93
|
+
if (n > 0) {
|
|
94
|
+
shared++;
|
|
95
|
+
bCounts.set(t, n - 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return (2 * shared) / (at.length + bt.length);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Find every line-window in `haystack` whose whitespace-normalised form equals
|
|
102
|
+
* the normalised `needle`. Returns character spans.
|
|
103
|
+
*
|
|
104
|
+
* Line-aligned by construction: a needle that starts or ends mid-line simply
|
|
105
|
+
* finds nothing here and falls through to the miss rung. That is the safe
|
|
106
|
+
* direction — a mid-line tolerant match would have to guess where the span ends.
|
|
107
|
+
*/
|
|
108
|
+
function tolerantSpans(haystack, needle, preserveIndent) {
|
|
109
|
+
const needleLines = needle.split('\n');
|
|
110
|
+
// A needle with a trailing newline yields an empty final element; dropping it
|
|
111
|
+
// lets "block plus newline" match the same lines as "block".
|
|
112
|
+
if (needleLines.length > 1 && needleLines[needleLines.length - 1] === '')
|
|
113
|
+
needleLines.pop();
|
|
114
|
+
const wanted = needleLines.map((l) => normalizeLine(l, preserveIndent));
|
|
115
|
+
if (wanted.length === 0 || wanted.every((l) => l === ''))
|
|
116
|
+
return [];
|
|
117
|
+
const hayLines = haystack.split('\n');
|
|
118
|
+
const offsets = lineOffsets(haystack);
|
|
119
|
+
const spans = [];
|
|
120
|
+
for (let start = 0; start + wanted.length <= hayLines.length; start++) {
|
|
121
|
+
let ok = true;
|
|
122
|
+
for (let k = 0; k < wanted.length; k++) {
|
|
123
|
+
if (normalizeLine(hayLines[start + k], preserveIndent) !== wanted[k]) {
|
|
124
|
+
ok = false;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!ok)
|
|
129
|
+
continue;
|
|
130
|
+
const index = offsets[start];
|
|
131
|
+
const lastLine = start + wanted.length - 1;
|
|
132
|
+
// End of the span = end of the last matched line, newline NOT included, so
|
|
133
|
+
// the replacement cannot swallow the line break that follows it.
|
|
134
|
+
const end = offsets[lastLine] + hayLines[lastLine].length;
|
|
135
|
+
spans.push({ index, length: end - index, indent: indentOf(hayLines[start]) });
|
|
136
|
+
}
|
|
137
|
+
return spans;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Build the nearest-match report shown when nothing matched.
|
|
141
|
+
*
|
|
142
|
+
* The model's failure is almost always "my memory of the file is stale", so the
|
|
143
|
+
* single most useful thing we can hand back is the CURRENT text of the region it
|
|
144
|
+
* was aiming at, with line numbers it can feed straight into `edit_range`.
|
|
145
|
+
*/
|
|
146
|
+
export function nearestMatchReport(haystack, needle, maxLines = 8) {
|
|
147
|
+
const needleLines = needle
|
|
148
|
+
.split('\n')
|
|
149
|
+
.map((l) => normalizeLine(l))
|
|
150
|
+
.filter((l) => l !== '');
|
|
151
|
+
if (needleLines.length === 0)
|
|
152
|
+
return '';
|
|
153
|
+
const hayLines = haystack.split('\n');
|
|
154
|
+
const anchor = needleLines[0];
|
|
155
|
+
let bestLine = -1;
|
|
156
|
+
let bestScore = 0;
|
|
157
|
+
for (let i = 0; i < hayLines.length; i++) {
|
|
158
|
+
const score = similarity(anchor, normalizeLine(hayLines[i]));
|
|
159
|
+
if (score > bestScore) {
|
|
160
|
+
bestScore = score;
|
|
161
|
+
bestLine = i;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Below this the "nearest" line is noise and quoting it would mislead.
|
|
165
|
+
if (bestLine < 0 || bestScore < 0.34)
|
|
166
|
+
return '';
|
|
167
|
+
const from = Math.max(0, bestLine - 2);
|
|
168
|
+
// Bounded by maxLines, NOT by the needle length: this text goes straight back
|
|
169
|
+
// into the model's context, and a 200-line old_string must not echo 200 lines.
|
|
170
|
+
const to = Math.min(hayLines.length - 1, bestLine + Math.max(1, maxLines - 2));
|
|
171
|
+
const excerpt = hayLines
|
|
172
|
+
.slice(from, to + 1)
|
|
173
|
+
.map((l, i) => `${String(from + i + 1).padStart(5)}| ${l}`)
|
|
174
|
+
.join('\n');
|
|
175
|
+
return (`Nearest region in the CURRENT file (lines ${from + 1}-${to + 1}, ` +
|
|
176
|
+
`best anchor line ${bestLine + 1}):\n${excerpt}\n` +
|
|
177
|
+
`Copy the exact text above, or use edit_range with start_line/end_line.`);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Locate `oldStr` in `current` using the exact -> tolerant -> miss ladder.
|
|
181
|
+
*
|
|
182
|
+
* Contract: a returned 'exact' or 'tolerant' match is safe to apply verbatim by
|
|
183
|
+
* slicing at [index, index+length). 'ambiguous' and 'miss' carry a `note` that
|
|
184
|
+
* is already model-facing prose.
|
|
185
|
+
*/
|
|
186
|
+
export function resolveEditMatch(current, oldStr, options = {}) {
|
|
187
|
+
const { occurrence = null, tolerant = true, diagnostics = true, indentSensitive = false, } = options;
|
|
188
|
+
// --- Rung 1: exact -------------------------------------------------------
|
|
189
|
+
const exactCount = oldStr.length === 0 ? 0 : current.split(oldStr).length - 1;
|
|
190
|
+
if (exactCount > 0) {
|
|
191
|
+
if (exactCount > 1 && occurrence == null) {
|
|
192
|
+
return {
|
|
193
|
+
kind: 'ambiguous',
|
|
194
|
+
index: -1,
|
|
195
|
+
length: 0,
|
|
196
|
+
count: exactCount,
|
|
197
|
+
note: `old_string matches ${exactCount} times — add surrounding context or pass occurrence (1-based).`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const which = occurrence ?? 1;
|
|
201
|
+
if (!Number.isInteger(which) || which < 1 || which > exactCount) {
|
|
202
|
+
return {
|
|
203
|
+
kind: 'ambiguous',
|
|
204
|
+
index: -1,
|
|
205
|
+
length: 0,
|
|
206
|
+
count: exactCount,
|
|
207
|
+
note: `occurrence ${String(occurrence)} out of range (1..${exactCount}).`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
let idx = -1;
|
|
211
|
+
for (let i = 0; i < which; i++)
|
|
212
|
+
idx = current.indexOf(oldStr, idx + 1);
|
|
213
|
+
return { kind: 'exact', index: idx, length: oldStr.length, count: exactCount };
|
|
214
|
+
}
|
|
215
|
+
// --- Rung 2: whitespace-tolerant ----------------------------------------
|
|
216
|
+
if (tolerant) {
|
|
217
|
+
const spans = tolerantSpans(current, oldStr, indentSensitive);
|
|
218
|
+
if (spans.length > 0) {
|
|
219
|
+
// UNIQUENESS IS REQUIRED here, and `occurrence` is deliberately NOT used to
|
|
220
|
+
// disambiguate. The model chose its occurrence index against what it
|
|
221
|
+
// believed the EXACT matches were; the normalised candidate list has
|
|
222
|
+
// different membership and ordering, so honouring it would silently land
|
|
223
|
+
// the edit at an unrelated site and still report success.
|
|
224
|
+
if (spans.length > 1) {
|
|
225
|
+
return {
|
|
226
|
+
kind: 'ambiguous',
|
|
227
|
+
index: -1,
|
|
228
|
+
length: 0,
|
|
229
|
+
count: spans.length,
|
|
230
|
+
note: `old_string did not match exactly, and matches ${spans.length} places once whitespace ` +
|
|
231
|
+
`is normalised — that is too ambiguous to guess at. Re-read the file and copy the exact ` +
|
|
232
|
+
`text, or use edit_range with explicit line numbers.`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const span = spans[0];
|
|
236
|
+
const needleFirst = oldStr.split('\n')[0] ?? '';
|
|
237
|
+
return {
|
|
238
|
+
kind: 'tolerant',
|
|
239
|
+
index: span.index,
|
|
240
|
+
length: span.length,
|
|
241
|
+
count: 1,
|
|
242
|
+
targetIndent: span.indent,
|
|
243
|
+
needleIndent: indentOf(needleFirst),
|
|
244
|
+
lineAligned: true,
|
|
245
|
+
note: 'old_string did not match byte-for-byte; matched after normalising whitespace. ' +
|
|
246
|
+
'The edit was applied — re-read the file before the next edit so your anchors are current.',
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// --- Rung 3: miss, with diagnostics -------------------------------------
|
|
251
|
+
const report = diagnostics ? nearestMatchReport(current, oldStr) : '';
|
|
252
|
+
return {
|
|
253
|
+
kind: 'miss',
|
|
254
|
+
index: -1,
|
|
255
|
+
length: 0,
|
|
256
|
+
count: 0,
|
|
257
|
+
note: report
|
|
258
|
+
? `old_string not found.\n${report}`
|
|
259
|
+
: 'old_string not found — re-read the file and match the CURRENT content.',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Apply a resolved match. Caller must have checked `kind` is exact/tolerant.
|
|
264
|
+
*
|
|
265
|
+
* Two corrections happen only on the line-aligned (tolerant) path, because only
|
|
266
|
+
* there does the matched span differ in shape from what the model sent:
|
|
267
|
+
*
|
|
268
|
+
* - RE-INDENT. The span starts at column 0 of the matched line while the needle
|
|
269
|
+
* may have been unindented, so splicing verbatim would move the code to
|
|
270
|
+
* column 0. We rebase the replacement onto the target's indentation.
|
|
271
|
+
* - TRAILING NEWLINE. The span excludes the line break that follows it, so a
|
|
272
|
+
* `new_string` ending in "\n" would add a blank line — making the same
|
|
273
|
+
* (old, new) pair produce different files depending on which rung fired.
|
|
274
|
+
*/
|
|
275
|
+
export function applyEditMatch(current, match, newStr) {
|
|
276
|
+
let replacement = newStr;
|
|
277
|
+
if (match.lineAligned) {
|
|
278
|
+
if (replacement.endsWith('\n'))
|
|
279
|
+
replacement = replacement.slice(0, -1);
|
|
280
|
+
const target = match.targetIndent ?? '';
|
|
281
|
+
const needle = match.needleIndent ?? '';
|
|
282
|
+
if (target !== needle) {
|
|
283
|
+
replacement = replacement
|
|
284
|
+
.split('\n')
|
|
285
|
+
.map((line, i) => {
|
|
286
|
+
// Blank lines stay blank — indenting them just adds trailing whitespace.
|
|
287
|
+
if (line.trim() === '')
|
|
288
|
+
return line;
|
|
289
|
+
const stripped = needle && line.startsWith(needle) ? line.slice(needle.length) : line;
|
|
290
|
+
// The first line is always rebased; later lines keep their relative
|
|
291
|
+
// indentation, which stripping the common needle prefix preserves.
|
|
292
|
+
return i === 0 ? target + stripped.trimStart() : target + stripped;
|
|
293
|
+
})
|
|
294
|
+
.join('\n');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return current.slice(0, match.index) + replacement + current.slice(match.index + match.length);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Replace an inclusive 1-based LINE RANGE.
|
|
301
|
+
*
|
|
302
|
+
* Why this exists alongside `edit_file`: a model that cannot reproduce exact
|
|
303
|
+
* whitespace can still count lines, and `read_file` hands it numbered context.
|
|
304
|
+
* This gives the ladder a rung that does not depend on reproducing file bytes at
|
|
305
|
+
* all — the failure mode the tolerant rung only softens.
|
|
306
|
+
*/
|
|
307
|
+
export function applyRangeEdit(current, startLine, endLine, newText) {
|
|
308
|
+
const lines = current.split('\n');
|
|
309
|
+
if (!Number.isInteger(startLine) || !Number.isInteger(endLine)) {
|
|
310
|
+
return { ok: false, error: 'start_line and end_line must be integers (1-based, inclusive).' };
|
|
311
|
+
}
|
|
312
|
+
if (startLine < 1 || endLine < startLine) {
|
|
313
|
+
return { ok: false, error: `invalid range ${startLine}..${endLine} (need 1 <= start_line <= end_line).` };
|
|
314
|
+
}
|
|
315
|
+
if (endLine > lines.length) {
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
error: `end_line ${endLine} is past the end of the file (${lines.length} lines) — re-read it.`,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
// A trailing newline on new_text would inject a blank line, because the join
|
|
322
|
+
// below re-adds the separator. Strip exactly one.
|
|
323
|
+
const replacement = newText.endsWith('\n') ? newText.slice(0, -1) : newText;
|
|
324
|
+
const updated = [
|
|
325
|
+
...lines.slice(0, startLine - 1),
|
|
326
|
+
...replacement.split('\n'),
|
|
327
|
+
...lines.slice(endLine),
|
|
328
|
+
].join('\n');
|
|
329
|
+
return { ok: true, text: updated, replacedLines: endLine - startLine + 1 };
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=edit-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-match.js","sourceRoot":"","sources":["../../src/delivery/edit-match.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-retrieval.d.ts","sourceRoot":"","sources":["../../src/memory/dynamic-retrieval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AA2EjC;;;;;GAKG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACtF,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACxF;AAQD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,MAAM,GAAE,oBAA+C,GACtD;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAE/E;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,gBAAgB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;IACrF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,eAAe,EAAE,MAAM,EACvB,WAAW,GAAE,MAAsB,EACnC,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;CACxB,GACL,OAAO,CAAC,oBAAoB,CAAC,CAqV/B;
|
|
1
|
+
{"version":3,"file":"dynamic-retrieval.d.ts","sourceRoot":"","sources":["../../src/memory/dynamic-retrieval.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AA2EjC;;;;;GAKG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACtF,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxF,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACxF;AAQD;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAC3B,MAAM,GAAE,oBAA+C,GACtD;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAE/E;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,kBAAkB,CAAC;IACnC,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAChE,gBAAgB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAAC;IACrF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,eAAe,EAAE,MAAM,EACvB,WAAW,GAAE,MAAsB,EACnC,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,YAAY,CAAC,EAAE,YAAY,CAAC;CACxB,GACL,OAAO,CAAC,oBAAoB,CAAC,CAqV/B;AAgyBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAmC7D"}
|
|
@@ -471,6 +471,19 @@ async function queryAllMemorySources(taskInstruction, classification, entities,
|
|
|
471
471
|
source: 'terminal-bench-knowledge',
|
|
472
472
|
});
|
|
473
473
|
}
|
|
474
|
+
// Source 7: ACTIVE RECONSTRUCTION (harness plan E, agent path).
|
|
475
|
+
//
|
|
476
|
+
// An ADDITIONAL source, never a replacement. Review of the CLI wiring caught
|
|
477
|
+
// the opposite shape — `--active` replaced two-tier retrieval with a graph
|
|
478
|
+
// over one tier, silently narrowing the corpus. Here every passive source
|
|
479
|
+
// above has already run; this contributes the associative hops they cannot
|
|
480
|
+
// make (reaching a memory the query never names, via a shared tag), and its
|
|
481
|
+
// results flow through the same dedup + budget as everything else.
|
|
482
|
+
//
|
|
483
|
+
// Opt-in and fail-open: off unless UAP_MEMORY_ACTIVE=1 with a non-empty
|
|
484
|
+
// graph, and any error leaves the other sources untouched.
|
|
485
|
+
const activeMemories = await queryActiveReconstruction(taskInstruction, projectRoot, depth);
|
|
486
|
+
memories.push(...activeMemories);
|
|
474
487
|
// Deduplicate and sort by relevance
|
|
475
488
|
const uniqueMemories = deduplicateMemories(memories);
|
|
476
489
|
uniqueMemories.sort((a, b) => b.relevance - a.relevance);
|
|
@@ -488,6 +501,50 @@ async function queryAllMemorySources(taskInstruction, classification, entities,
|
|
|
488
501
|
}
|
|
489
502
|
return budgeted;
|
|
490
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* Recall by traversing the cue-tag-content graph while reasoning (harness plan E).
|
|
506
|
+
*
|
|
507
|
+
* Refreshes only when the index is STALE. A full store scan on every model turn
|
|
508
|
+
* would put a latency regression on the hot path of every agent, paid whether or
|
|
509
|
+
* not the graph helped; never refreshing would serve an index that silently ages
|
|
510
|
+
* out. The TTL is the compromise.
|
|
511
|
+
*
|
|
512
|
+
* Relevance is deliberately capped below the top of the passive range. These are
|
|
513
|
+
* associative hops — genuinely useful, but reached by structure rather than by
|
|
514
|
+
* direct match — so they should fill remaining budget, not displace a memory the
|
|
515
|
+
* query named outright.
|
|
516
|
+
*/
|
|
517
|
+
async function queryActiveReconstruction(taskInstruction, projectRoot, depth) {
|
|
518
|
+
try {
|
|
519
|
+
const { shouldUseActiveRecall, recallActive } = await import('./reconstruct-store.js');
|
|
520
|
+
if (!shouldUseActiveRecall(projectRoot))
|
|
521
|
+
return [];
|
|
522
|
+
const result = await recallActive(projectRoot, taskInstruction, {
|
|
523
|
+
refresh: 'if-stale',
|
|
524
|
+
// Bounded by the same adaptive depth as every other source, so a complex
|
|
525
|
+
// query gets more hops and a simple one cannot balloon the context.
|
|
526
|
+
maxContext: Math.max(3, Math.min(12, depth.longTerm)),
|
|
527
|
+
maxSteps: 4,
|
|
528
|
+
});
|
|
529
|
+
return result.context.map((node, i) => ({
|
|
530
|
+
// Truncated like EVERY other source. The merged budget loop `break`s on
|
|
531
|
+
// the first item that overflows, so one multi-KB node landing mid-list
|
|
532
|
+
// would discard every lower-ranked memory beneath it — including real
|
|
533
|
+
// passive hits. No other source can produce an item large enough to do
|
|
534
|
+
// that, and this one must not either.
|
|
535
|
+
content: node.text.slice(0, 500),
|
|
536
|
+
type: node.layer === 'semantic' ? 'lesson' : 'context',
|
|
537
|
+
// Decay with admission order: earlier nodes were reached in earlier hops
|
|
538
|
+
// and sit closer to what the query actually asked about.
|
|
539
|
+
relevance: Math.max(0.35, 0.7 - i * 0.03),
|
|
540
|
+
source: 'active-reconstruction',
|
|
541
|
+
}));
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
// Fail open — a broken or missing graph must never degrade the passive path.
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
}
|
|
491
548
|
/**
|
|
492
549
|
* Query short-term SQLite memory using FTS5 full-text search with LIKE fallback
|
|
493
550
|
* OPTIMIZATION 12: Use FTS5 index (memories_fts) for better semantic matching
|