@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.
Files changed (95) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/benchmarks/harness-card.d.ts +86 -0
  3. package/dist/benchmarks/harness-card.d.ts.map +1 -0
  4. package/dist/benchmarks/harness-card.js +153 -0
  5. package/dist/benchmarks/harness-card.js.map +1 -0
  6. package/dist/benchmarks/paired/report.d.ts +15 -0
  7. package/dist/benchmarks/paired/report.d.ts.map +1 -1
  8. package/dist/benchmarks/paired/report.js +12 -0
  9. package/dist/benchmarks/paired/report.js.map +1 -1
  10. package/dist/benchmarks/paired/types.d.ts +1 -1
  11. package/dist/bin/cli.js +27 -0
  12. package/dist/bin/cli.js.map +1 -1
  13. package/dist/cli/bench.d.ts.map +1 -1
  14. package/dist/cli/bench.js +64 -3
  15. package/dist/cli/bench.js.map +1 -1
  16. package/dist/cli/harness.d.ts +3 -2
  17. package/dist/cli/harness.d.ts.map +1 -1
  18. package/dist/cli/harness.js +81 -0
  19. package/dist/cli/harness.js.map +1 -1
  20. package/dist/cli/memory.d.ts +7 -1
  21. package/dist/cli/memory.d.ts.map +1 -1
  22. package/dist/cli/memory.js +165 -0
  23. package/dist/cli/memory.js.map +1 -1
  24. package/dist/delivery/agentic-executor.d.ts +76 -2
  25. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  26. package/dist/delivery/agentic-executor.js +231 -29
  27. package/dist/delivery/agentic-executor.js.map +1 -1
  28. package/dist/delivery/edit-match.d.ts +134 -0
  29. package/dist/delivery/edit-match.d.ts.map +1 -0
  30. package/dist/delivery/edit-match.js +331 -0
  31. package/dist/delivery/edit-match.js.map +1 -0
  32. package/dist/memory/dynamic-retrieval.d.ts.map +1 -1
  33. package/dist/memory/dynamic-retrieval.js +57 -0
  34. package/dist/memory/dynamic-retrieval.js.map +1 -1
  35. package/dist/memory/reconstruct-ingest.d.ts +88 -0
  36. package/dist/memory/reconstruct-ingest.d.ts.map +1 -0
  37. package/dist/memory/reconstruct-ingest.js +165 -0
  38. package/dist/memory/reconstruct-ingest.js.map +1 -0
  39. package/dist/memory/reconstruct-store.d.ts +143 -0
  40. package/dist/memory/reconstruct-store.d.ts.map +1 -0
  41. package/dist/memory/reconstruct-store.js +312 -0
  42. package/dist/memory/reconstruct-store.js.map +1 -0
  43. package/dist/memory/reconstruct.d.ts +272 -0
  44. package/dist/memory/reconstruct.d.ts.map +1 -0
  45. package/dist/memory/reconstruct.js +552 -0
  46. package/dist/memory/reconstruct.js.map +1 -0
  47. package/dist/policies/schemas/policy.d.ts +2 -2
  48. package/dist/self-harness/evidence-proposer.d.ts +58 -0
  49. package/dist/self-harness/evidence-proposer.d.ts.map +1 -0
  50. package/dist/self-harness/evidence-proposer.js +132 -0
  51. package/dist/self-harness/evidence-proposer.js.map +1 -0
  52. package/dist/self-harness/manifest.d.ts +130 -0
  53. package/dist/self-harness/manifest.d.ts.map +1 -0
  54. package/dist/self-harness/manifest.js +247 -0
  55. package/dist/self-harness/manifest.js.map +1 -0
  56. package/dist/self-harness/mods.d.ts +57 -3
  57. package/dist/self-harness/mods.d.ts.map +1 -1
  58. package/dist/self-harness/mods.js +72 -0
  59. package/dist/self-harness/mods.js.map +1 -1
  60. package/dist/self-harness/orchestrator.d.ts +27 -0
  61. package/dist/self-harness/orchestrator.d.ts.map +1 -1
  62. package/dist/self-harness/orchestrator.js +47 -4
  63. package/dist/self-harness/orchestrator.js.map +1 -1
  64. package/dist/self-harness/pending.d.ts +17 -3
  65. package/dist/self-harness/pending.d.ts.map +1 -1
  66. package/dist/self-harness/pending.js +26 -4
  67. package/dist/self-harness/pending.js.map +1 -1
  68. package/dist/self-harness/profile.d.ts +17 -1
  69. package/dist/self-harness/profile.d.ts.map +1 -1
  70. package/dist/self-harness/profile.js +11 -4
  71. package/dist/self-harness/profile.js.map +1 -1
  72. package/dist/self-harness/run.d.ts +19 -0
  73. package/dist/self-harness/run.d.ts.map +1 -1
  74. package/dist/self-harness/run.js +43 -4
  75. package/dist/self-harness/run.js.map +1 -1
  76. package/dist/self-harness/transfer.d.ts.map +1 -1
  77. package/dist/self-harness/transfer.js +2 -0
  78. package/dist/self-harness/transfer.js.map +1 -1
  79. package/dist/self-harness/validate.d.ts +17 -6
  80. package/dist/self-harness/validate.d.ts.map +1 -1
  81. package/dist/self-harness/validate.js +73 -8
  82. package/dist/self-harness/validate.js.map +1 -1
  83. package/dist/telemetry/tool-calls.d.ts +82 -0
  84. package/dist/telemetry/tool-calls.d.ts.map +1 -0
  85. package/dist/telemetry/tool-calls.js +280 -0
  86. package/dist/telemetry/tool-calls.js.map +1 -0
  87. package/dist/telemetry/tool-failure.d.ts +71 -0
  88. package/dist/telemetry/tool-failure.d.ts.map +1 -0
  89. package/dist/telemetry/tool-failure.js +123 -0
  90. package/dist/telemetry/tool-failure.js.map +1 -0
  91. package/docs/plans/harness-engineering-uplift-2026-07-31.md +280 -0
  92. package/package.json +1 -1
  93. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  94. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  95. 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;AAiuBD;;;;;;;;;;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"}
1
+ {"version":3,"file":"dynamic-retrieval.d.ts","sourceRoot":"","sources":["../../src/memory/dynamic-retrieval.ts"],"names":[],"mappings":"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"}
@@ -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