@remembrance-ai/opencode-plugin 0.1.77 → 0.2.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/README.md +172 -32
- package/opencode.json +2 -2
- package/package.json +1 -1
- package/scripts/feedback-disposition.mjs +376 -0
- package/scripts/hook-core.mjs +6145 -685
- package/scripts/pending-action-payload.mjs +1044 -0
- package/scripts/private-learning-hook-policy.mjs +2515 -0
- package/servers/remembrance-mcp.mjs +62249 -6956
- package/skills/remembrancer/SKILL.md +219 -105
- package/skills/remembrancer/references/identity-preferences-topology.md +41 -0
- package/skills/remembrancer/references/remembrance-payloads.md +94 -17
- package/skills/remembrancer/references/remembrance-setup.md +186 -74
- package/src/index.mjs +131 -40
|
@@ -0,0 +1,2515 @@
|
|
|
1
|
+
const MAX_CONTEXT_CHARS = 4000;
|
|
2
|
+
const BOUNDARY_SEARCH_CHARS = 96;
|
|
3
|
+
|
|
4
|
+
// `scripts/sync-hook-core.mjs` refreshes this from the qualified generated-root
|
|
5
|
+
// receipt. Hook code uses the identity only to wake retained drafts after a
|
|
6
|
+
// trusted inspector change; it never accepts an identity from hook input.
|
|
7
|
+
export const PRIVATE_LEARNING_INSPECTOR_DIGEST =
|
|
8
|
+
"sha256:79f414e69dc023c6682178594136f580d6f22314773445cc1eebe95217c2b7ae";
|
|
9
|
+
|
|
10
|
+
const USER_CORRECTION_CAPTURE_PREFIX = [
|
|
11
|
+
"Remembrance user-correction capture:",
|
|
12
|
+
"This prompt may contain a first-party correction. Decide from the full conversation whether one really occurred; resolve it now and capture only a generalized lesson reusable beyond this person and repository.",
|
|
13
|
+
];
|
|
14
|
+
const USER_CORRECTION_CAPTURE_SUFFIX =
|
|
15
|
+
"Never send raw wording, repository content, paths, URLs, identifiers, or secrets; keep person-, repository-, and machine-specific facts in host-local memory.";
|
|
16
|
+
const USER_CORRECTION_CAPTURE_PRIVATE_ACTION =
|
|
17
|
+
"Use capture_learning.lesson for a compact lesson or private_skill for a complete workflow, then follow its exact next_action or bounded agent_recovery instructions.";
|
|
18
|
+
const USER_CORRECTION_CAPTURE_FALLBACK_ACTION =
|
|
19
|
+
"Use the private lesson lane for a compact lesson or propose a private skill for a complete workflow.";
|
|
20
|
+
|
|
21
|
+
export function userCorrectionCaptureDirectiveText(privateCapture) {
|
|
22
|
+
return [
|
|
23
|
+
...USER_CORRECTION_CAPTURE_PREFIX,
|
|
24
|
+
privateCapture
|
|
25
|
+
? USER_CORRECTION_CAPTURE_PRIVATE_ACTION
|
|
26
|
+
: USER_CORRECTION_CAPTURE_FALLBACK_ACTION,
|
|
27
|
+
USER_CORRECTION_CAPTURE_SUFFIX,
|
|
28
|
+
].join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const USER_CORRECTION_CAPTURE_MAX_CHARS = Math.max(
|
|
32
|
+
userCorrectionCaptureDirectiveText(true).length,
|
|
33
|
+
userCorrectionCaptureDirectiveText(false).length,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const REQUIRED_CONTEXT_ORDER = [
|
|
37
|
+
"authority",
|
|
38
|
+
"consent",
|
|
39
|
+
"retention",
|
|
40
|
+
"credential",
|
|
41
|
+
"completion",
|
|
42
|
+
"query_identity",
|
|
43
|
+
"notice",
|
|
44
|
+
];
|
|
45
|
+
const OPTIONAL_CONTEXT_ORDER = ["preference", "candidate_additional", "other"];
|
|
46
|
+
|
|
47
|
+
// The required organization settings line is reserved whole, after the
|
|
48
|
+
// authority lines: it is either complete or names every exact key with a
|
|
49
|
+
// required fetch, so any cut would drop a key or that instruction. It becomes
|
|
50
|
+
// this keyless required fetch when it cannot fit beside the minimum core
|
|
51
|
+
// context, or when it would evict a query action the keyless fetch leaves
|
|
52
|
+
// room for.
|
|
53
|
+
const REQUIRED_SETTINGS_KIND = "required_settings";
|
|
54
|
+
const REQUIRED_SETTINGS_FALLBACK =
|
|
55
|
+
"Required organization settings did not fit this context. Required next step before acting: call get_effective_preferences and apply every returned required organization setting in full. If the call is unavailable, say the complete required settings could not be loaded and do not claim they were applied.";
|
|
56
|
+
const CONTEXT_OUTPUT_ORDER = [
|
|
57
|
+
...REQUIRED_CONTEXT_ORDER.flatMap((kind) =>
|
|
58
|
+
kind === "authority" ? [kind, REQUIRED_SETTINGS_KIND] : [kind],
|
|
59
|
+
),
|
|
60
|
+
"query_action",
|
|
61
|
+
"candidate_primary",
|
|
62
|
+
"correction",
|
|
63
|
+
...OPTIONAL_CONTEXT_ORDER,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const KNOWN_CONTEXT_KINDS = new Set([
|
|
67
|
+
...REQUIRED_CONTEXT_ORDER,
|
|
68
|
+
...OPTIONAL_CONTEXT_ORDER,
|
|
69
|
+
"query_action",
|
|
70
|
+
"candidate_primary",
|
|
71
|
+
"correction",
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const ATOMIC_REQUIRED_CONTEXT_KINDS = new Set([
|
|
75
|
+
"consent",
|
|
76
|
+
"retention",
|
|
77
|
+
"credential",
|
|
78
|
+
"completion",
|
|
79
|
+
"query_identity",
|
|
80
|
+
"correction",
|
|
81
|
+
"notice",
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const WHOLE_REQUIRED_CONTEXT_KINDS = new Set(["correction"]);
|
|
85
|
+
|
|
86
|
+
const CORE_CONTEXT_BUDGETS = Object.freeze({
|
|
87
|
+
authority: { minimum: 240, preferred: 760 },
|
|
88
|
+
consent: { minimum: 220, preferred: 460 },
|
|
89
|
+
retention: { minimum: 220, preferred: 460 },
|
|
90
|
+
// The longest shipped credential-source notice is below 400 characters.
|
|
91
|
+
// Keep that compact notice whole while leaving room for an exact query action.
|
|
92
|
+
credential: { minimum: 400, preferred: 760 },
|
|
93
|
+
completion: { minimum: 260, preferred: 560 },
|
|
94
|
+
query_identity: { minimum: 220, preferred: 440 },
|
|
95
|
+
notice: { minimum: 160, preferred: 360 },
|
|
96
|
+
correction: {
|
|
97
|
+
minimum: USER_CORRECTION_CAPTURE_MAX_CHARS,
|
|
98
|
+
preferred: USER_CORRECTION_CAPTURE_MAX_CHARS,
|
|
99
|
+
},
|
|
100
|
+
candidate_primary: { minimum: 180, preferred: 420 },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const CORE_PREFERRED_EXPANSION_ORDER = [
|
|
104
|
+
"completion",
|
|
105
|
+
"consent",
|
|
106
|
+
"retention",
|
|
107
|
+
"credential",
|
|
108
|
+
"query_identity",
|
|
109
|
+
"notice",
|
|
110
|
+
"authority",
|
|
111
|
+
"candidate_primary",
|
|
112
|
+
"correction",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const CORE_FULL_EXPANSION_ORDER = [
|
|
116
|
+
"authority",
|
|
117
|
+
"credential",
|
|
118
|
+
"completion",
|
|
119
|
+
"consent",
|
|
120
|
+
"retention",
|
|
121
|
+
"query_identity",
|
|
122
|
+
"notice",
|
|
123
|
+
"candidate_primary",
|
|
124
|
+
"correction",
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const OMISSION_NOTICE =
|
|
128
|
+
"[Some lower-priority Remembrance context was omitted to fit the safety budget; query receipts and retained-draft recovery remain available.]";
|
|
129
|
+
|
|
130
|
+
const ATOMIC_OPTIONAL_KINDS = new Set(["preference"]);
|
|
131
|
+
const CONTINUATION_CONTEXT_KINDS = new Set([
|
|
132
|
+
"candidate_primary",
|
|
133
|
+
"candidate_additional",
|
|
134
|
+
"query_action",
|
|
135
|
+
"correction",
|
|
136
|
+
"notice",
|
|
137
|
+
...ATOMIC_OPTIONAL_KINDS,
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
const USER_CORRECTION_CAPTURE_REGEX_WINDOW_CHARS = 512;
|
|
141
|
+
// The two open-vocabulary replacement matchers have a bounded 512-character
|
|
142
|
+
// payload plus a lexical shell well below this allowance. Segment overlap is
|
|
143
|
+
// derived from the same contract so a valid match cannot straddle a flush.
|
|
144
|
+
const USER_CORRECTION_CAPTURE_REGEX_SHELL_CHARS = 128;
|
|
145
|
+
|
|
146
|
+
const USER_CORRECTION_CAPTURE_PATTERNS = [
|
|
147
|
+
/\b(?:that|this|it)(?:\s+(?:(?:completed|proposed|current|chosen)\s+)?(?:approach|implementation|design|result))?(?:'s|\s+(?:is|was|looks?|feels?))\s+(?:wrong|incorrect|overkill|too much|overcomplicated|the wrong (?:way|approach|direction))\b/i,
|
|
148
|
+
/\b(?:please\s+)?(?:do (?:this|it) differently|use a different approach)\b/i,
|
|
149
|
+
/\b(?:you|we)\s+(?:missed|forgot|overlooked|ignored|did not address|didn't address|failed to address)\s+(?:the\s+)?(?:actual\s+)?(?:(?:requested|required|expected|agreed(?:-upon)?)\s+)?(?:requirement|constraint|edge case|case|issue|failure(?: mode)?|step|request|point|behavior|regression)\b/i,
|
|
150
|
+
/\b(?:this|that|it)(?:(?:'s|\s+(?:is|was))\s+not|\s+(?:isn't|wasn't))\s+what\s+i\s+(?:asked|requested)\b/i,
|
|
151
|
+
/\byou\s+(?:broke|regressed)\s+(?:this|that|it|the\s+(?:build|flow|feature|behavior))\b/i,
|
|
152
|
+
/\b(?:that|this|the)\s+(?:(?:actual|current|latest|new)\s+)?(?:change|implementation|patch)\s+(?:broke|regressed)\s+(?:this|that|it|the\s+(?:build|flow|feature|behavior))\b/i,
|
|
153
|
+
/\b(?:i already (?:asked (?:you\s+)?(?:to|not to|for)|said (?:that|not to)|told you)|as i (?:said|asked|explained)|you keep (?:(?:missing|forgetting) (?:the )?(?:requirement|constraint|case|issue|step|point)|(?:doing|adding|removing) (?:this|that|the wrong thing)))\b/i,
|
|
154
|
+
/\bi (?:do not|don't) (?:like|agree with|want) (?:this|that|the) (?:approach|direction|implementation|design|result)\b/i,
|
|
155
|
+
/\b(?:i|we)\s+(?:(?:no\s+longer|do\s+not|don't)\s+(?:agree\s+with|accept|endorse|support|want|prefer|believe\s+in)|now\s+disagree\s+with)\s+(?:this|that|the)\s+(?:approach|direction|implementation|design|result|plan)\b/i,
|
|
156
|
+
/\b(?:please\s+)?(?:revert|undo|discard|reject) (?:that|this|the (?:approach|implementation|change|design|result))\b/i,
|
|
157
|
+
/\bi\s+(?:said|asked|told\s+you)(?:\s+you)?(?:\s+to)?\s+(?:use|follow|run|keep|avoid|remove|add|change|switch\s+to)\b/i,
|
|
158
|
+
new RegExp(
|
|
159
|
+
String.raw`\bactually,?\s+(?:please\s+)?(?:use|follow|run|keep|avoid|remove|add|change|switch\s+to)\b[^.!?;]{0,${USER_CORRECTION_CAPTURE_REGEX_WINDOW_CHARS}}\binstead\b`,
|
|
160
|
+
"i",
|
|
161
|
+
),
|
|
162
|
+
new RegExp(
|
|
163
|
+
String.raw`\b(?:please\s+)?(?:use|follow|run|keep|avoid|remove|add|change|switch\s+to)\b[^.!?;]{0,${USER_CORRECTION_CAPTURE_REGEX_WINDOW_CHARS}}\binstead\b`,
|
|
164
|
+
"i",
|
|
165
|
+
),
|
|
166
|
+
];
|
|
167
|
+
const USER_CORRECTION_CAPTURE_MATCHERS = USER_CORRECTION_CAPTURE_PATTERNS.map(
|
|
168
|
+
(pattern) => new RegExp(pattern.source, `${pattern.flags}g`),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
export function composeBoundedHookContext(context, additions = []) {
|
|
172
|
+
return composeBoundedHookContextResult(context, additions).context;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function composeBoundedHookContextResult(context, additions = []) {
|
|
176
|
+
const sections = hookContextSections(context, additions);
|
|
177
|
+
if (sections.length === 0) {
|
|
178
|
+
return { context: "", deliveredAdditionIds: [] };
|
|
179
|
+
}
|
|
180
|
+
const ordered = orderedContextSections(sections);
|
|
181
|
+
const full = ordered.map((section) => section.text).join("\n");
|
|
182
|
+
if (full.length <= MAX_CONTEXT_CHARS) {
|
|
183
|
+
return {
|
|
184
|
+
context: full,
|
|
185
|
+
deliveredAdditionIds: deliveredAdditionIds(
|
|
186
|
+
ordered,
|
|
187
|
+
ordered.map((section) => ({ ...section, complete: true })),
|
|
188
|
+
),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const contentBudget = MAX_CONTEXT_CHARS - OMISSION_NOTICE.length - 1;
|
|
193
|
+
const queryActions = sections.filter(
|
|
194
|
+
(section) => section.kind === "query_action",
|
|
195
|
+
);
|
|
196
|
+
const coreGroups = [
|
|
197
|
+
...REQUIRED_CONTEXT_ORDER.map((kind) => mergeContextKind(sections, kind)),
|
|
198
|
+
mergeContextKind(sections, "candidate_primary"),
|
|
199
|
+
mergeContextKind(sections, "correction"),
|
|
200
|
+
].filter(Boolean);
|
|
201
|
+
const minimumCoreLength = coreGroups.reduce(
|
|
202
|
+
(total, section) =>
|
|
203
|
+
total +
|
|
204
|
+
Math.min(section.text.length, CORE_CONTEXT_BUDGETS[section.kind].minimum),
|
|
205
|
+
0,
|
|
206
|
+
);
|
|
207
|
+
// Exact sections: the required settings first, then query actions. The
|
|
208
|
+
// keyed settings line gives way to the shorter keyless fetch before any
|
|
209
|
+
// query action is evicted for it.
|
|
210
|
+
const reserveQueryActions = (required) => {
|
|
211
|
+
const exact = required ? [required] : [];
|
|
212
|
+
for (const section of queryActions) {
|
|
213
|
+
const candidate = [...exact, section];
|
|
214
|
+
const separators = Math.max(0, coreGroups.length + candidate.length - 1);
|
|
215
|
+
if (
|
|
216
|
+
minimumCoreLength + joinedSectionLength(candidate) + separators <=
|
|
217
|
+
contentBudget
|
|
218
|
+
) {
|
|
219
|
+
exact.push({ ...section, complete: true });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return exact;
|
|
223
|
+
};
|
|
224
|
+
const requiredSettings = mergeContextKind(sections, REQUIRED_SETTINGS_KIND);
|
|
225
|
+
const keyedSettingsFit =
|
|
226
|
+
requiredSettings &&
|
|
227
|
+
minimumCoreLength + requiredSettings.text.length + coreGroups.length <=
|
|
228
|
+
contentBudget;
|
|
229
|
+
const requiredOptions = requiredSettings
|
|
230
|
+
? [
|
|
231
|
+
...(keyedSettingsFit ? [{ ...requiredSettings, complete: true }] : []),
|
|
232
|
+
{
|
|
233
|
+
...requiredSettings,
|
|
234
|
+
text: REQUIRED_SETTINGS_FALLBACK,
|
|
235
|
+
complete: false,
|
|
236
|
+
},
|
|
237
|
+
]
|
|
238
|
+
: [null];
|
|
239
|
+
// The first option keeping the most query actions wins: the keyed line
|
|
240
|
+
// unless the keyless fetch keeps one more.
|
|
241
|
+
const reserved = requiredOptions
|
|
242
|
+
.map(reserveQueryActions)
|
|
243
|
+
.reduce((best, next) => (next.length > best.length ? next : best));
|
|
244
|
+
const exactActionLength = joinedSectionLength(reserved);
|
|
245
|
+
const separatorCount = Math.max(0, coreGroups.length + reserved.length - 1);
|
|
246
|
+
const flexibleBudget = Math.max(
|
|
247
|
+
0,
|
|
248
|
+
contentBudget - exactActionLength - separatorCount,
|
|
249
|
+
);
|
|
250
|
+
const coreBudgets = allocateCoreContextBudgets(coreGroups, flexibleBudget);
|
|
251
|
+
const selectedSections = renderAndExpandCoreContext(
|
|
252
|
+
coreGroups,
|
|
253
|
+
coreBudgets,
|
|
254
|
+
flexibleBudget,
|
|
255
|
+
);
|
|
256
|
+
selectedSections.push(...reserved);
|
|
257
|
+
|
|
258
|
+
const optional = OPTIONAL_CONTEXT_ORDER.flatMap((kind) =>
|
|
259
|
+
sections.filter((section) => section.kind === kind),
|
|
260
|
+
);
|
|
261
|
+
for (const section of optional) {
|
|
262
|
+
const remaining =
|
|
263
|
+
contentBudget -
|
|
264
|
+
joinedSectionLength(selectedSections) -
|
|
265
|
+
(selectedSections.length > 0 ? 1 : 0);
|
|
266
|
+
if (remaining <= 0) continue;
|
|
267
|
+
if (section.text.length <= remaining) {
|
|
268
|
+
selectedSections.push({ ...section, complete: true });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (ATOMIC_OPTIONAL_KINDS.has(section.kind)) continue;
|
|
272
|
+
if (remaining >= 80) {
|
|
273
|
+
selectedSections.push({
|
|
274
|
+
...section,
|
|
275
|
+
text: boundedContextSection(section.text, remaining),
|
|
276
|
+
complete: false,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const selected = orderedContextSections(selectedSections);
|
|
282
|
+
const bounded = [...selected.map((section) => section.text), OMISSION_NOTICE]
|
|
283
|
+
.filter(Boolean)
|
|
284
|
+
.join("\n");
|
|
285
|
+
/* c8 ignore next 5 -- allocator arithmetic is fail-closed; source-mutation tests exercise this invariant */
|
|
286
|
+
if (bounded.length > MAX_CONTEXT_CHARS) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
"Remembrance hook context allocator exceeded its hard budget.",
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
context: bounded,
|
|
293
|
+
deliveredAdditionIds: deliveredAdditionIds(sections, selected),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function orderedContextSections(sections) {
|
|
298
|
+
return CONTEXT_OUTPUT_ORDER.flatMap((kind) =>
|
|
299
|
+
sections.filter((section) => section.kind === kind),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function mergeContextKind(sections, kind) {
|
|
304
|
+
const matches = sections.filter((section) => section.kind === kind);
|
|
305
|
+
if (matches.length === 0) return null;
|
|
306
|
+
return {
|
|
307
|
+
kind,
|
|
308
|
+
text: matches.map((section) => section.text).join("\n"),
|
|
309
|
+
parts: matches.map((section) => section.text),
|
|
310
|
+
sourceIds: [...new Set(matches.flatMap((section) => section.sourceIds))],
|
|
311
|
+
sourceKey: `merged:${kind}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function allocateCoreContextBudgets(sections, totalBudget) {
|
|
316
|
+
if (sections.length === 0) return [];
|
|
317
|
+
const floors = sections.map((section) =>
|
|
318
|
+
Math.min(section.text.length, CORE_CONTEXT_BUDGETS[section.kind].minimum),
|
|
319
|
+
);
|
|
320
|
+
const essential = sections.map((section) =>
|
|
321
|
+
Math.min(section.text.length, minimumCoreContextBudget(section)),
|
|
322
|
+
);
|
|
323
|
+
const preferred = sections.map((section) =>
|
|
324
|
+
Math.min(section.text.length, CORE_CONTEXT_BUDGETS[section.kind].preferred),
|
|
325
|
+
);
|
|
326
|
+
const budgets = [...floors];
|
|
327
|
+
let remaining = Math.max(0, totalBudget - budgets.reduce((a, b) => a + b, 0));
|
|
328
|
+
remaining = growContextBudgets(budgets, essential, remaining);
|
|
329
|
+
remaining = growContextBudgets(budgets, preferred, remaining);
|
|
330
|
+
growContextBudgets(
|
|
331
|
+
budgets,
|
|
332
|
+
sections.map((section) => section.text.length),
|
|
333
|
+
remaining,
|
|
334
|
+
);
|
|
335
|
+
return budgets;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function minimumCoreContextBudget(section) {
|
|
339
|
+
const configured = CORE_CONTEXT_BUDGETS[section.kind].minimum;
|
|
340
|
+
if (WHOLE_REQUIRED_CONTEXT_KINDS.has(section.kind)) {
|
|
341
|
+
return configured;
|
|
342
|
+
}
|
|
343
|
+
if (!ATOMIC_REQUIRED_CONTEXT_KINDS.has(section.kind)) {
|
|
344
|
+
return configured;
|
|
345
|
+
}
|
|
346
|
+
const units = completeDirectiveUnits(section.text).filter(
|
|
347
|
+
(unit) => directivePriority(unit) <= 1,
|
|
348
|
+
);
|
|
349
|
+
if (units.length === 0) return configured;
|
|
350
|
+
const marker = `[Additional ${contextKindLabel(section.kind)} context omitted.]`;
|
|
351
|
+
const essentialLength = joinedLength(units) + marker.length + 1;
|
|
352
|
+
return Math.max(configured, Math.min(section.text.length, essentialLength));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function renderAndExpandCoreContext(sections, budgets, totalBudget) {
|
|
356
|
+
const selected = sections.map((section, index) =>
|
|
357
|
+
renderCoreContextSection(section, budgets[index]),
|
|
358
|
+
);
|
|
359
|
+
expandCoreContext(
|
|
360
|
+
sections,
|
|
361
|
+
selected,
|
|
362
|
+
totalBudget,
|
|
363
|
+
CORE_PREFERRED_EXPANSION_ORDER,
|
|
364
|
+
(section) => CORE_CONTEXT_BUDGETS[section.kind].preferred,
|
|
365
|
+
);
|
|
366
|
+
expandCoreContext(
|
|
367
|
+
sections,
|
|
368
|
+
selected,
|
|
369
|
+
totalBudget,
|
|
370
|
+
CORE_FULL_EXPANSION_ORDER,
|
|
371
|
+
(section) => section.text.length,
|
|
372
|
+
);
|
|
373
|
+
return selected.filter((section) => section.text);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function renderCoreContextSection(section, budget) {
|
|
377
|
+
const text = boundedRequiredContextSection(
|
|
378
|
+
section.text,
|
|
379
|
+
budget,
|
|
380
|
+
section.kind,
|
|
381
|
+
);
|
|
382
|
+
return {
|
|
383
|
+
...section,
|
|
384
|
+
text,
|
|
385
|
+
complete: text === section.text,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function expandCoreContext(
|
|
390
|
+
sections,
|
|
391
|
+
selected,
|
|
392
|
+
totalBudget,
|
|
393
|
+
kindOrder,
|
|
394
|
+
targetBudget,
|
|
395
|
+
) {
|
|
396
|
+
for (const kind of kindOrder) {
|
|
397
|
+
const index = sections.findIndex((section) => section.kind === kind);
|
|
398
|
+
if (index < 0) continue;
|
|
399
|
+
const used = joinedSectionLength(
|
|
400
|
+
selected.filter((section) => section.text),
|
|
401
|
+
);
|
|
402
|
+
const remaining = totalBudget - used;
|
|
403
|
+
if (remaining <= 0) return;
|
|
404
|
+
const current = selected[index];
|
|
405
|
+
const section = sections[index];
|
|
406
|
+
const maximumOutputLength = Math.min(
|
|
407
|
+
targetBudget(section),
|
|
408
|
+
current.text.length + remaining,
|
|
409
|
+
);
|
|
410
|
+
if (maximumOutputLength <= current.text.length) continue;
|
|
411
|
+
const expanded = renderCoreContextSection(section, maximumOutputLength);
|
|
412
|
+
if (expanded.text.length > current.text.length) selected[index] = expanded;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function growContextBudgets(budgets, targets, available) {
|
|
417
|
+
let remaining = available;
|
|
418
|
+
const active = new Set(
|
|
419
|
+
budgets
|
|
420
|
+
.map((value, index) => (value < targets[index] ? index : null))
|
|
421
|
+
.filter((index) => index !== null),
|
|
422
|
+
);
|
|
423
|
+
while (remaining > 0 && active.size > 0) {
|
|
424
|
+
const share = Math.max(1, Math.floor(remaining / active.size));
|
|
425
|
+
for (const index of [...active]) {
|
|
426
|
+
const increase = Math.min(share, targets[index] - budgets[index]);
|
|
427
|
+
budgets[index] += increase;
|
|
428
|
+
remaining -= increase;
|
|
429
|
+
if (budgets[index] >= targets[index]) active.delete(index);
|
|
430
|
+
if (remaining <= 0) break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return remaining;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function boundedRequiredContextSection(value, maxLength, kind) {
|
|
437
|
+
const normalized = String(value).trim();
|
|
438
|
+
if (normalized.length <= maxLength) return normalized;
|
|
439
|
+
if (WHOLE_REQUIRED_CONTEXT_KINDS.has(kind)) {
|
|
440
|
+
const marker = `[${contextKindLabel(kind)} context omitted as one complete directive.]`;
|
|
441
|
+
return marker;
|
|
442
|
+
}
|
|
443
|
+
if (ATOMIC_REQUIRED_CONTEXT_KINDS.has(kind)) {
|
|
444
|
+
return boundedAtomicRequiredContext(normalized, maxLength, kind);
|
|
445
|
+
}
|
|
446
|
+
const marker = `\n[Remaining ${contextKindLabel(kind)} context omitted; do not infer omitted guidance.]`;
|
|
447
|
+
const head = boundedContextHead(normalized, maxLength - marker.length);
|
|
448
|
+
return `${head}${marker}`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function boundedContextHead(value, maxLength) {
|
|
452
|
+
const boundaryFloor = Math.max(0, maxLength - BOUNDARY_SEARCH_CHARS);
|
|
453
|
+
const candidate = Math.max(
|
|
454
|
+
value.lastIndexOf("\n", maxLength),
|
|
455
|
+
value.lastIndexOf(" ", maxLength),
|
|
456
|
+
);
|
|
457
|
+
return value.slice(0, candidate >= boundaryFloor ? candidate : maxLength);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function boundedAtomicRequiredContext(value, maxLength, kind) {
|
|
461
|
+
const marker = `[Additional ${contextKindLabel(kind)} context omitted.]`;
|
|
462
|
+
const units = completeDirectiveUnits(value);
|
|
463
|
+
const selected = [];
|
|
464
|
+
const available = maxLength - marker.length - 1;
|
|
465
|
+
const prioritized = units
|
|
466
|
+
.map((text, index) => ({ text, index, priority: directivePriority(text) }))
|
|
467
|
+
.sort(
|
|
468
|
+
(left, right) =>
|
|
469
|
+
left.priority - right.priority || left.index - right.index,
|
|
470
|
+
);
|
|
471
|
+
let used = 0;
|
|
472
|
+
for (const unit of prioritized) {
|
|
473
|
+
const separator = selected.length > 0 ? 1 : 0;
|
|
474
|
+
if (used + separator + unit.text.length > available) continue;
|
|
475
|
+
selected.push(unit);
|
|
476
|
+
used += separator + unit.text.length;
|
|
477
|
+
}
|
|
478
|
+
selected.sort((left, right) => left.index - right.index);
|
|
479
|
+
return [...selected.map((unit) => unit.text), marker].join("\n");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function createCompleteDirectiveUnitsScanner(measureWork) {
|
|
483
|
+
return function scanCompleteDirectiveUnits(value) {
|
|
484
|
+
const work = measureWork
|
|
485
|
+
? {
|
|
486
|
+
boundarySpanCodeUnits: 0,
|
|
487
|
+
cursorAdvances: 0,
|
|
488
|
+
lineCount: 0,
|
|
489
|
+
punctuationChecks: 0,
|
|
490
|
+
}
|
|
491
|
+
: null;
|
|
492
|
+
const units = value
|
|
493
|
+
.split(/\n+/u)
|
|
494
|
+
.flatMap((line) => {
|
|
495
|
+
if (measureWork) work.lineCount += 1;
|
|
496
|
+
return splitDirectiveLine(line, work, measureWork);
|
|
497
|
+
})
|
|
498
|
+
.map((unit) => unit.trim())
|
|
499
|
+
.filter(Boolean);
|
|
500
|
+
if (!measureWork) return units;
|
|
501
|
+
return {
|
|
502
|
+
unitCount: units.length,
|
|
503
|
+
work: {
|
|
504
|
+
...work,
|
|
505
|
+
total:
|
|
506
|
+
work.boundarySpanCodeUnits +
|
|
507
|
+
work.cursorAdvances +
|
|
508
|
+
work.lineCount +
|
|
509
|
+
work.punctuationChecks,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const scanCompleteDirectiveUnits = createCompleteDirectiveUnitsScanner(false);
|
|
516
|
+
const scanCompleteDirectiveUnitsWithWork =
|
|
517
|
+
createCompleteDirectiveUnitsScanner(true);
|
|
518
|
+
|
|
519
|
+
export function completeDirectiveUnits(value) {
|
|
520
|
+
return scanCompleteDirectiveUnits(value);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function measureCompleteDirectiveUnitsWorkForTests(value) {
|
|
524
|
+
return scanCompleteDirectiveUnitsWithWork(value);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function splitDirectiveLine(line, work, measureWork) {
|
|
528
|
+
const units = [];
|
|
529
|
+
let start = 0;
|
|
530
|
+
let cursor = 0;
|
|
531
|
+
let tokenStart = 0;
|
|
532
|
+
while (cursor < line.length) {
|
|
533
|
+
if (/\s/u.test(line[cursor])) {
|
|
534
|
+
cursor += 1;
|
|
535
|
+
if (measureWork) work.cursorAdvances += 1;
|
|
536
|
+
tokenStart = cursor;
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (!isDirectivePunctuation(line[cursor])) {
|
|
540
|
+
cursor += 1;
|
|
541
|
+
if (measureWork) work.cursorAdvances += 1;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const punctuationStart = cursor;
|
|
545
|
+
while (cursor < line.length && isDirectivePunctuation(line[cursor])) {
|
|
546
|
+
cursor += 1;
|
|
547
|
+
if (measureWork) work.cursorAdvances += 1;
|
|
548
|
+
}
|
|
549
|
+
if (cursor < line.length && !/\s/u.test(line[cursor])) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (
|
|
553
|
+
!isDirectiveSentenceBoundary(
|
|
554
|
+
line,
|
|
555
|
+
tokenStart,
|
|
556
|
+
punctuationStart,
|
|
557
|
+
cursor,
|
|
558
|
+
work,
|
|
559
|
+
measureWork,
|
|
560
|
+
)
|
|
561
|
+
) {
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
units.push(line.slice(start, cursor));
|
|
565
|
+
while (cursor < line.length && /\s/u.test(line[cursor])) {
|
|
566
|
+
cursor += 1;
|
|
567
|
+
if (measureWork) work.cursorAdvances += 1;
|
|
568
|
+
}
|
|
569
|
+
start = cursor;
|
|
570
|
+
tokenStart = cursor;
|
|
571
|
+
}
|
|
572
|
+
if (start < line.length) units.push(line.slice(start));
|
|
573
|
+
return units;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function isDirectivePunctuation(value) {
|
|
577
|
+
return value === "." || value === "!" || value === "?";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function isDirectiveSentenceBoundary(
|
|
581
|
+
line,
|
|
582
|
+
tokenStart,
|
|
583
|
+
punctuationStart,
|
|
584
|
+
punctuationEnd,
|
|
585
|
+
work = null,
|
|
586
|
+
measureWork = false,
|
|
587
|
+
) {
|
|
588
|
+
if (measureWork) {
|
|
589
|
+
work.boundarySpanCodeUnits += punctuationEnd - tokenStart;
|
|
590
|
+
}
|
|
591
|
+
if (punctuationEnd >= line.length) return true;
|
|
592
|
+
for (let index = punctuationStart; index < punctuationEnd; index += 1) {
|
|
593
|
+
if (measureWork) work.punctuationChecks += 1;
|
|
594
|
+
if (line[index] === "!" || line[index] === "?") return true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const token = line.slice(tokenStart, punctuationEnd);
|
|
598
|
+
// Internal URL, path, version, and dotted-name periods do not reach this
|
|
599
|
+
// branch because they are not followed by whitespace. Only the terminal
|
|
600
|
+
// period of a real abbreviation needs to suppress a sentence split.
|
|
601
|
+
return !/^(?:e\.g\.|i\.e\.|etc\.|vs\.|[A-Z](?:\.[A-Z]){1,}\.)$/u.test(token);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function directivePriority(value) {
|
|
605
|
+
if (
|
|
606
|
+
/^(?:do not|never)\b|\b(?:host denial|authorization|policy|validation|safety|terminal holds?)\b.*\b(?:another transport|auto-delete|automatically)\b/iu.test(
|
|
607
|
+
value,
|
|
608
|
+
)
|
|
609
|
+
)
|
|
610
|
+
return 0;
|
|
611
|
+
if (
|
|
612
|
+
/\b(?:inspect_private_learning_outbox|submit_private_lesson_candidate|submit_private_skill_candidate|retry tool|query_id|result_id)\b/u.test(
|
|
613
|
+
value,
|
|
614
|
+
)
|
|
615
|
+
)
|
|
616
|
+
return 1;
|
|
617
|
+
return 2;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function contextKindLabel(kind) {
|
|
621
|
+
return String(kind).replaceAll("_", " ");
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function joinedLength(values) {
|
|
625
|
+
return values.reduce(
|
|
626
|
+
(length, value, index) => length + value.length + (index > 0 ? 1 : 0),
|
|
627
|
+
0,
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function joinedSectionLength(sections) {
|
|
632
|
+
return joinedLength(sections.map((section) => section.text));
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function deliveredAdditionIds(allSections, selectedSections) {
|
|
636
|
+
const expected = new Map();
|
|
637
|
+
const delivered = new Map();
|
|
638
|
+
for (const section of allSections) {
|
|
639
|
+
for (const id of section.sourceIds) {
|
|
640
|
+
expected.set(id, (expected.get(id) ?? 0) + 1);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
for (const section of selectedSections) {
|
|
644
|
+
if (section.complete !== true) continue;
|
|
645
|
+
for (const id of section.sourceIds) {
|
|
646
|
+
delivered.set(id, (delivered.get(id) ?? 0) + 1);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return [...expected.entries()]
|
|
650
|
+
.filter(([id, count]) => delivered.get(id) === count)
|
|
651
|
+
.map(([id]) => id);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function boundedContextSection(value, maxLength) {
|
|
655
|
+
const normalized = String(value ?? "")
|
|
656
|
+
.replace(/\r\n?/gu, "\n")
|
|
657
|
+
.trim();
|
|
658
|
+
if (normalized.length <= maxLength) return normalized;
|
|
659
|
+
if (maxLength < 4) return "";
|
|
660
|
+
const budget = maxLength - 3;
|
|
661
|
+
const boundaryFloor = Math.max(0, budget - BOUNDARY_SEARCH_CHARS);
|
|
662
|
+
const newline = normalized.lastIndexOf("\n", budget);
|
|
663
|
+
const space = normalized.lastIndexOf(" ", budget);
|
|
664
|
+
const candidate = Math.max(newline, space);
|
|
665
|
+
const boundary = candidate >= boundaryFloor ? candidate : budget;
|
|
666
|
+
return `${normalized.slice(0, boundary).trimEnd()}...`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export function promptContainsFirstPartyCorrection(
|
|
670
|
+
prompt,
|
|
671
|
+
{ hostNotification = false } = {},
|
|
672
|
+
) {
|
|
673
|
+
if (hostNotification) return false;
|
|
674
|
+
return correctionPromptContainsFirstPartyCorrection(String(prompt ?? ""));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function hookContextSections(context, additions) {
|
|
678
|
+
let candidateSeen = false;
|
|
679
|
+
const values = [
|
|
680
|
+
{ text: context, id: null, kind: null },
|
|
681
|
+
...additions.map((addition) =>
|
|
682
|
+
addition && typeof addition === "object"
|
|
683
|
+
? {
|
|
684
|
+
text: addition.text,
|
|
685
|
+
id: String(addition.id ?? "").trim() || null,
|
|
686
|
+
kind: String(addition.kind ?? "").trim() || null,
|
|
687
|
+
}
|
|
688
|
+
: { text: addition, id: null, kind: null },
|
|
689
|
+
),
|
|
690
|
+
].filter((value) => value.text);
|
|
691
|
+
const sections = [];
|
|
692
|
+
for (const value of values) {
|
|
693
|
+
if (value.kind) {
|
|
694
|
+
const text = String(value.text).replace(/\r\n?/gu, "\n").trim();
|
|
695
|
+
if (text) {
|
|
696
|
+
sections.push({
|
|
697
|
+
kind: KNOWN_CONTEXT_KINDS.has(value.kind) ? value.kind : "notice",
|
|
698
|
+
text,
|
|
699
|
+
sourceIds: value.id ? [value.id] : [],
|
|
700
|
+
sourceKey: `typed:${sections.length}:${value.id ?? ""}`,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
const lines = String(value.text)
|
|
706
|
+
.replace(/\r\n?/gu, "\n")
|
|
707
|
+
.split(/\n/u)
|
|
708
|
+
.map((line) => line.trim())
|
|
709
|
+
.filter(Boolean);
|
|
710
|
+
let current = null;
|
|
711
|
+
for (const line of lines) {
|
|
712
|
+
const explicitKind = explicitContextKindForLine(line, candidateSeen);
|
|
713
|
+
const genericKind = genericRequiredContextKindForLine(line);
|
|
714
|
+
if (explicitKind) {
|
|
715
|
+
if (
|
|
716
|
+
current &&
|
|
717
|
+
/^(?:Skills:|Resources:)$/u.test(current.text) &&
|
|
718
|
+
/^- \[(?:HIGH|POSSIBLE|EXPLORATORY) MATCH\]/u.test(line)
|
|
719
|
+
) {
|
|
720
|
+
current.text += `\n${line}`;
|
|
721
|
+
candidateSeen = true;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (current) sections.push(current);
|
|
725
|
+
current = { kind: explicitKind, text: line, sourceIds: [] };
|
|
726
|
+
if (/^- \[(?:HIGH|POSSIBLE|EXPLORATORY) MATCH\]/u.test(line)) {
|
|
727
|
+
candidateSeen = true;
|
|
728
|
+
}
|
|
729
|
+
} else if (
|
|
730
|
+
genericKind &&
|
|
731
|
+
current &&
|
|
732
|
+
!CONTINUATION_CONTEXT_KINDS.has(current.kind)
|
|
733
|
+
) {
|
|
734
|
+
sections.push(current);
|
|
735
|
+
current = { kind: genericKind, text: line, sourceIds: [] };
|
|
736
|
+
} else if (current?.kind === REQUIRED_SETTINGS_KIND) {
|
|
737
|
+
// The reserved settings line stays exactly one line; a following
|
|
738
|
+
// unclassified line keeps its former authority treatment.
|
|
739
|
+
sections.push(current);
|
|
740
|
+
current = { kind: "authority", text: line, sourceIds: [] };
|
|
741
|
+
} else if (current) {
|
|
742
|
+
current.text += `\n${line}`;
|
|
743
|
+
} else {
|
|
744
|
+
current = {
|
|
745
|
+
kind: genericKind ?? "other",
|
|
746
|
+
text: line,
|
|
747
|
+
sourceIds: [],
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (current) sections.push(current);
|
|
752
|
+
}
|
|
753
|
+
const seen = new Map();
|
|
754
|
+
return sections.filter((section, index) => {
|
|
755
|
+
section.sourceKey ??= `parsed:${index}`;
|
|
756
|
+
const identityKind = section.kind.startsWith("candidate_")
|
|
757
|
+
? "candidate"
|
|
758
|
+
: section.kind;
|
|
759
|
+
const identity = `${identityKind}\0${section.text}`;
|
|
760
|
+
const previous = seen.get(identity);
|
|
761
|
+
if (previous) {
|
|
762
|
+
previous.sourceIds.push(
|
|
763
|
+
...section.sourceIds.filter((id) => !previous.sourceIds.includes(id)),
|
|
764
|
+
);
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
seen.set(identity, section);
|
|
768
|
+
return true;
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function explicitContextKindForLine(line, candidateSeen) {
|
|
773
|
+
if (line === OMISSION_NOTICE) return "other";
|
|
774
|
+
if (/^Required organization settings\b/iu.test(line)) {
|
|
775
|
+
return REQUIRED_SETTINGS_KIND;
|
|
776
|
+
}
|
|
777
|
+
if (
|
|
778
|
+
/^(?:Organization policy|Remembrance (?:auto-query|query-unavailable|capture routing|task-continuation) context|Trigger:)/iu.test(
|
|
779
|
+
line,
|
|
780
|
+
)
|
|
781
|
+
)
|
|
782
|
+
return "authority";
|
|
783
|
+
if (/^(?:Query receipt:|HIGH MATCH\b(?!\s+—))/u.test(line)) {
|
|
784
|
+
return "query_identity";
|
|
785
|
+
}
|
|
786
|
+
if (/^HIGH MATCH\s+—/u.test(line)) return "query_action";
|
|
787
|
+
if (/^- \[HIGH MATCH\]/u.test(line)) {
|
|
788
|
+
return candidateSeen ? "candidate_additional" : "candidate_primary";
|
|
789
|
+
}
|
|
790
|
+
if (/^Remembrance user-correction capture:/u.test(line)) return "correction";
|
|
791
|
+
if (
|
|
792
|
+
/\b(?:preference capture|Discretionary preferences|Preference compatibility|Private preference evidence)\b/iu.test(
|
|
793
|
+
line,
|
|
794
|
+
)
|
|
795
|
+
)
|
|
796
|
+
return "preference";
|
|
797
|
+
if (
|
|
798
|
+
/^(?:Remembrance credential source:|Remembrance setup needs attention:)/u.test(
|
|
799
|
+
line,
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
return "credential";
|
|
803
|
+
if (
|
|
804
|
+
/^(?:After using Remembrance:|Before finishing|Remembrance private-learning recovery|Delegating this task\?)/u.test(
|
|
805
|
+
line,
|
|
806
|
+
)
|
|
807
|
+
)
|
|
808
|
+
return "completion";
|
|
809
|
+
if (/^- \[(?:POSSIBLE|EXPLORATORY) MATCH\]/u.test(line)) {
|
|
810
|
+
return candidateSeen ? "candidate_additional" : "candidate_primary";
|
|
811
|
+
}
|
|
812
|
+
if (/^(?:Skills:|Resources:)/u.test(line)) {
|
|
813
|
+
return candidateSeen ? "candidate_additional" : "candidate_primary";
|
|
814
|
+
}
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function genericRequiredContextKindForLine(line) {
|
|
819
|
+
if (
|
|
820
|
+
/\b(?:authorization|authorize|approval|approved destination|host denial|exact (?:private )?(?:submission )?action|never (?:switch|change) (?:destination|visibility|transport))\b/iu.test(
|
|
821
|
+
line,
|
|
822
|
+
)
|
|
823
|
+
)
|
|
824
|
+
return "consent";
|
|
825
|
+
if (
|
|
826
|
+
/\b(?:draft remains|retained (?:draft|locally)|outbox|never auto-delete|unresolved draft|retry_scheduled|awaiting_authorization)\b/iu.test(
|
|
827
|
+
line,
|
|
828
|
+
)
|
|
829
|
+
)
|
|
830
|
+
return "retention";
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
const CORRECTION_WINDOW_CHARS = 8192;
|
|
834
|
+
const CORRECTION_WINDOW_OVERLAP_CHARS = 1024;
|
|
835
|
+
const CORRECTION_INLINE_BUFFER_CHARS = 8192;
|
|
836
|
+
|
|
837
|
+
const CORRECTION_DIRECT_PATTERNS = Object.freeze([
|
|
838
|
+
...USER_CORRECTION_CAPTURE_PATTERNS,
|
|
839
|
+
/\b(?:this|that|the)\s+(?:(?:current|latest|new|actual)\s+)?(?:change|patch|implementation|design|result|approach)\s+(?:broke|regressed)\s+(?:the\s+)?(?:build|flow|feature|behavior)\b/iu,
|
|
840
|
+
/\b(?:please\s+)?(?:revert|undo|discard|reject)\s+(?:this|that|the)\s+(?:change|patch|implementation|design|result|approach)\b/iu,
|
|
841
|
+
/\b(?:please\s+)?use\b.{0,512}?\binstead\b/iu,
|
|
842
|
+
/\b(?:my|our)\s+(?:current|new|latest|actual)\s+correction\b/iu,
|
|
843
|
+
/\b(?:you|we)\s+(?:(?:keep|kept)\s+)?(?:misread|misunderstood|misinterpreted|ignored|ignoring)\s+(?:(?:my|our|the|this|that|actual|stated)\s+){0,2}(?:request|spec(?:ification)?|requirement|constraint|instructions?|intent)\b/iu,
|
|
844
|
+
/\b(?:this|that|the)\s+[\p{L}\p{M}-]{1,48}\s+(?:you|we)\s+(?:wrote|made|produced|changed|implemented)\s+(?:is|was|remains)\s+(?:still\s+)?(?:wrong|incorrect|broken)\b/iu,
|
|
845
|
+
/\b(?:this|that|the)\s+(?:[\p{L}\p{M}-]{1,48}\s+){1,5}(?:is|was|remains)\s+(?:still\s+)?(?:wrong|incorrect)\b/iu,
|
|
846
|
+
/\b(?:this|that|it)(?:'s|\s+(?:is|was))\s+(?:still\s+)?broken\b/iu,
|
|
847
|
+
/\byou\s+(?:deleted|removed|edited|changed|updated|replaced)\s+(?:the|a)\s+wrong\s+[\p{L}\p{M}-]{1,48}\b/iu,
|
|
848
|
+
/\byou(?:'re|\s+are)\s+(?:solving|addressing|fixing|implementing)\s+(?:the|a)\s+wrong\s+(?:problem|issue|requirement|request|feature)\b/iu,
|
|
849
|
+
/\b(?:this|that|the)\s+(?:[\p{L}\p{M}-]{1,48}\s+){1,5}(?:doesn't|does\s+not)\s+(?:match|reflect|follow|address)\s+what\s+i\s+(?:described|requested|asked|specified)\b/iu,
|
|
850
|
+
/\b(?:please\s+)?stop\s+(?:rewriting|repeating|ignoring|replacing|deleting)\s+(?:the|my|our|this|that|these|those)\s+[\p{L}\p{M}-]{1,48}\b/iu,
|
|
851
|
+
]);
|
|
852
|
+
|
|
853
|
+
const CORRECTION_FACTUAL_TOKEN = String.raw`(?!(?:not|never|but|however|yet|rather|instead)\b)(?=[\p{L}\p{M}\p{N}'_+./:=<>%-]{0,63}[\p{L}\p{N}])[\p{L}\p{N}_+./-][\p{L}\p{M}\p{N}'_+./:=<>%-]{0,63}(?<!:)`;
|
|
854
|
+
const CORRECTION_FACTUAL_VALUE = String.raw`${CORRECTION_FACTUAL_TOKEN}(?:\s+${CORRECTION_FACTUAL_TOKEN}){0,7}`;
|
|
855
|
+
const CORRECTION_FACTUAL_ASSERTION = String.raw`${CORRECTION_FACTUAL_TOKEN}(?:\s+${CORRECTION_FACTUAL_TOKEN}){0,15}`;
|
|
856
|
+
const CORRECTION_FACTUAL_LEFT = new RegExp(
|
|
857
|
+
String.raw`(?<![\p{L}\p{M}\p{N}_./:-])(?<assertion>${CORRECTION_FACTUAL_ASSERTION})$`,
|
|
858
|
+
"iu",
|
|
859
|
+
);
|
|
860
|
+
const CORRECTION_FACTUAL_NEGATIVE_LEFT = new RegExp(
|
|
861
|
+
String.raw`(?<![\p{L}\p{M}\p{N}_./:-])(?:${CORRECTION_FACTUAL_ASSERTION}\s+)?not\s+(?!(?:only|just|merely|necessarily|always)\s)${CORRECTION_FACTUAL_VALUE}$`,
|
|
862
|
+
"iu",
|
|
863
|
+
);
|
|
864
|
+
const CORRECTION_FACTUAL_RIGHT = new RegExp(
|
|
865
|
+
String.raw`^(?!(?:only|just|merely|necessarily|always|to|because|although|unless|whether|if|when)\s)${CORRECTION_FACTUAL_VALUE}`,
|
|
866
|
+
"iu",
|
|
867
|
+
);
|
|
868
|
+
const CORRECTION_FACTUAL_CONNECTORS =
|
|
869
|
+
/(?<positiveConnector>(?:\s*,\s*|\s+)(?:not|rather\s+than|instead\s+of)\s+)|(?<negativeConnector>\s*,\s*(?:but\s+)?|\s+(?:but\s+|[\u2014-]\s+))/dgiu;
|
|
870
|
+
// Auxiliary negation ("should not be") is not a replacement contrast. The
|
|
871
|
+
// assertion vocabulary stays open; this is grammar, not a list of task verbs.
|
|
872
|
+
const CORRECTION_FACTUAL_NEGATED_AUXILIARY =
|
|
873
|
+
/\b(?:am|is|are|was|were|be|been|being|do|does|did|have|has|had|can|could|may|might|must|shall|should|will|would|need|dare|ought|it's|that's|this's)$/iu;
|
|
874
|
+
const CORRECTION_FACTUAL_UNARY_SUBJECT =
|
|
875
|
+
/^(?:i|we)\s+(?:(?:[\p{L}]+ly|would|could|should|will|can|may|might|do|did)\s+){0,3}[\p{L}]+$/iu;
|
|
876
|
+
const CORRECTION_FACTUAL_ATOM = new RegExp(
|
|
877
|
+
String.raw`^${CORRECTION_FACTUAL_TOKEN}$`,
|
|
878
|
+
"iu",
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
const CORRECTION_FIRST_PARTY_LABEL =
|
|
882
|
+
/^(?:feedback\s+from\s+(?:me|us|myself)|the\s+issue|in\s+review|security|reliability|client(?:-side|\s+api)|customer-facing|reviewer\s+mode|implementation\s+detail|from\s+my\s+(?:code\s+|design\s+|security\s+)?review|my\s+(?:feedback|assessment|finding)|our\s+(?:feedback|assessment|finding|internal\s+review|security\s+finding)|(?:my|our)\s+(?:runbook|test\s+plan|implementation\s+notes?|requirements?|plan)\s+(?:says|requires|specifies|states)|per\s+the\s+(?:agreed\s+plan|plan\s+we\s+agreed\s+on)|according\s+to\s+our\s+agreed\s+plan|(?:per|according\s+to|from)\s+(?:my|our)\s+(?:(?:architecture|code|design|security|implementation|internal)\s+)?(?:review|postmortem|requirements?|notes?|findings?)|(?:per|according\s+to|from)\s+the\s+(?:plan|requirements?|review|postmortem|notes?)\s+(?:i|we)\s+(?:wrote|authored|created))\s*(?:[:\u2014-]\s*|,\s*)/iu;
|
|
883
|
+
|
|
884
|
+
const CORRECTION_FIRST_PARTY_POSSESSIVE_LABEL =
|
|
885
|
+
/^(?:(?:per|according\s+to|from)\s+)?(?:my|our)\s+(?:(?:actual|additional|agreed|current|deployment|earlier|final|implementation|latest|new|operational|own|previous|prior|recent|release|specific|testing|unresolved)\s+){0,3}(?:addendum|aside|caveat|checklist|clarification|comment|concern|constraint|context|correction|detail|discussion|exception|exchange|feedback|finding|follow-up|instruction|issue|message|note|nuance|observation|plan|point|postmortem|question|rationale|reminder|request|requirement|review|runbook|thought|update|warning|wrinkle)\s*(?:[:\u2014-]\s*|,\s*)/iu;
|
|
886
|
+
|
|
887
|
+
const CORRECTION_REFERENCE_FRAME =
|
|
888
|
+
/^(?:to\s+[\p{L}\p{M}-]{1,48}|[\p{L}\p{M}-]{1,45}ing)(?:\s+(?:on|up|to|about|upon|with)){0,3}\s+/iu;
|
|
889
|
+
|
|
890
|
+
const CORRECTION_WITHDRAWN_REFERENCE =
|
|
891
|
+
/^(?:retract(?:ing)?|withdraw(?:ing)?)\b/iu;
|
|
892
|
+
|
|
893
|
+
// A communication has an author; a technical relationship such as
|
|
894
|
+
// "grouping by tenant" or "regression from caching" does not imply one.
|
|
895
|
+
const CORRECTION_ATTRIBUTED_SOURCE_NOUN = String.raw`(?:alerts?|assessments?|comments?|communications?|complaints?|dms?|drafts?|emails?|escalations?|excerpts?|feedback|findings?|messages?|notes?|postmortems?|quotes?|reports?|requests?|reviews?|summar(?:y|ies)|threads?|transcripts?|forwarded|provided|sent|shared)`;
|
|
896
|
+
const CORRECTION_ATTRIBUTED_HEADING = new RegExp(
|
|
897
|
+
String.raw`^(?:[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}\s+){0,5}${CORRECTION_ATTRIBUTED_SOURCE_NOUN}\s+(?:from|by)\s+(?<owner>[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}(?:\s+[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}){0,5})(?:\s*:\s*|\s+[\u2014-]\s*)`,
|
|
898
|
+
"iu",
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
const CORRECTION_REVIEW_TOPIC_NOUN =
|
|
902
|
+
/^(?:block(?:er|age)s?|bugs?|defects?|errors?|failures?|faults?|gaps?|inconsistenc(?:y|ies)|mismatch(?:es)?|nit(?:pick)?s?|objections?|oversights?|pushback|regressions?)$/iu;
|
|
903
|
+
|
|
904
|
+
// Keep ambiguous names (Mark, May, Trace, Race) out of this bounded vocabulary.
|
|
905
|
+
// A role, explicit owner, or multiword personal name still takes precedence.
|
|
906
|
+
const CORRECTION_TECHNICAL_TOPIC_NOUN =
|
|
907
|
+
/^(?:deadlocks?|livelocks?|starvation|timeouts?|latenc(?:y|ies)|throughput|concurrency|contention|backpressure|(?:de)?serializations?|(?:en|de)codings?|lifecycles?)$/iu;
|
|
908
|
+
|
|
909
|
+
const CORRECTION_AUTHORIAL_LABEL =
|
|
910
|
+
/^(?:fyi|important|again|heads\s+up|one\s+(?:additional|final|last|more)\s+thing|(?:(?:a|an|another|one|the)\s+)?(?:(?:additional|current|different|final|further|important|last|latest|new|operational|practical|related|release|separate|small|specific|unresolved|compatibility|deployment|implementation|reliability|security|testing)\s+){0,2}(?:addendum|aside|caveat|clarification|concern|constraint|context|correction|detail|exception|follow-up|issue|note|nuance|observation|point|question|rationale|reminder|requirement|thought|update|warning|wrinkle))\s*[:\u2014-]\s*/iu;
|
|
911
|
+
|
|
912
|
+
const CORRECTION_FIRST_PARTY_PIVOT =
|
|
913
|
+
/\b(?:i|we)\s+(?:(?:explicitly|formally|personally)\s+)?(?:agree|disagree|concur|adopt|endorse|confirm|hereby\s+accept)\b|\b(?:my|our)\s+(?:(?:current|new|latest|actual)\s+)?correction\b|\bmy\s+assessment\b|\b(?:in\s+my\s+view|this\s+is\s+(?:also\s+)?my\s+view|i\s+own\s+this\s+correction)\b|\bspeaking\s+for\s+(?:myself|ourselves)(?:\s+now)?\b/iu;
|
|
914
|
+
|
|
915
|
+
// Source heads and reporting grammar determine attribution independently of
|
|
916
|
+
// authorial heading vocabulary. Modifiers are bounded but open-ended.
|
|
917
|
+
const CORRECTION_SOURCE_MODIFIERS = String.raw`(?!(?:my|our|i|we|you)\b)(?:[\p{L}\p{N}][\p{L}\p{M}\p{N}'.-]{0,47}\s+){0,5}`;
|
|
918
|
+
const CORRECTION_SOURCE_LABEL = new RegExp(
|
|
919
|
+
String.raw`^${CORRECTION_SOURCE_MODIFIERS}(?:alerts?|calls?|changelogs?|doc(?:ument)?s?|emails?|logs?|notes|readmes?|spec(?:ification)?s?|threads?|tickets?|transcripts?)\s*[:,\u2014-]\s*`,
|
|
920
|
+
"iu",
|
|
921
|
+
);
|
|
922
|
+
const CORRECTION_DOCUMENT_REPORT = new RegExp(
|
|
923
|
+
String.raw`^${CORRECTION_SOURCE_MODIFIERS}(?:assertions?|changelogs?|comments?|doc(?:ument)?s?|instructions?|issues?|logs?|notes?|plans?|readmes?|reports?|requirements?|reviews?|runbooks?|spec(?:ification)?s?|tests?|tickets?|transcripts?)\s+(?:claim(?:s|ed)?|contain(?:s|ed)?|describe(?:s|d)?|indicate(?:s|d)?|mention(?:s|ed)?|note(?:s|d)?|read(?:s)?|report(?:s|ed)?|require(?:s|d)?|say(?:s)?|said|show(?:s|ed)?|specif(?:y|ies|ied)|state(?:s|d)?)\b`,
|
|
924
|
+
"iu",
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
const CORRECTION_REPORTING_VERB = String.raw`(?:say|says|said|mention(?:s|ed)?|note(?:s|d)?|point(?:s|ed)?\s+out|flag(?:s|ged)?|report(?:s|ed)?|relay(?:s|ed)?|show(?:s|ed)?|write|writes|wrote|claim(?:s|ed)?|state(?:s|d)?|tell|tells|told|ask(?:s|ed)?|argue(?:s|d)?|warn(?:s|ed)?|think|thinks|thought|believe(?:s|d)?|den(?:y|ies|ied)\s+saying)`;
|
|
928
|
+
const CORRECTION_SPEECH_REPORT = new RegExp(
|
|
929
|
+
String.raw`^(?<subject>(?:[\p{L}\p{N}][\p{L}\p{M}\p{N}'.-]{0,47}\s+){1,8}?)(?<verb>${CORRECTION_REPORTING_VERB})\b`,
|
|
930
|
+
"iu",
|
|
931
|
+
);
|
|
932
|
+
const CORRECTION_SPEECH_NOUN_PHRASE_REPORT = new RegExp(
|
|
933
|
+
String.raw`^(?<subject>(?:[\p{L}\p{N}][\p{L}\p{M}\p{N}'.-]{0,47}\s+){2,8}?)(?<verb>${CORRECTION_REPORTING_VERB})\b`,
|
|
934
|
+
"iu",
|
|
935
|
+
);
|
|
936
|
+
const CORRECTION_EXTERNAL_ROLE = String.raw`(?:architect|attorney|auditor|boss|client|colleague|compliance|consultant|contractor|coworker|customer|director|engineer|friend|lead|legal|manager|mentor|qa|reviewer|sre|support|teammate|user|vendor)s?`;
|
|
937
|
+
const CORRECTION_EXTERNAL_HEADING = new RegExp(
|
|
938
|
+
String.raw`^(?:(?:their|his|her|its)\s+|(?:(?:(?:my|our)\s+)?${CORRECTION_SOURCE_MODIFIERS}${CORRECTION_EXTERNAL_ROLE}(?:'s)?\s+)|(?!(?:my|our)\b)(?:[\p{L}\p{N}][\p{L}\p{M}\p{N}.-]*\s+){0,3}[\p{L}\p{M}\p{N}.-]+'s\s+)[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}(?:\s+[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}){0,7}\s*[:\u2014-]\s*`,
|
|
939
|
+
"iu",
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
const CORRECTION_SOURCE_PREFIXES = Object.freeze([
|
|
943
|
+
CORRECTION_SOURCE_LABEL,
|
|
944
|
+
CORRECTION_DOCUMENT_REPORT,
|
|
945
|
+
CORRECTION_EXTERNAL_HEADING,
|
|
946
|
+
/^(?:my|our)\s+(?:(?:[\p{L}\p{M}-]+\s+){0,2}[\p{L}\p{M}-]+'s)\s+[\p{L}\p{M}-]+(?:\s+[\p{L}\p{M}-]+){0,5}\s*[:\u2014-]\s*/iu,
|
|
947
|
+
/^(?:it|this|that)\s+(?:is|was|has\s+been)\s+(?:said|mentioned|noted|reported|flagged|claimed|stated)\s+by\s+(?!(?:me|us)\b)/iu,
|
|
948
|
+
/^(?:forwarded|provided|sent|shared)\s+by\s+(?!(?:me|us|myself)\b)/iu,
|
|
949
|
+
/^(?:my|our)\s+(?!own\b)(?:[\p{L}\p{M}\p{N}-]+\s+){0,2}[\p{L}\p{M}\p{N}-]+(?:'s|\u2019s)\s+(?:comment|email|feedback|message|note|report|review)\s*[:,\u2014-]\s*/iu,
|
|
950
|
+
/^(?:my|our)\s+(?:boss|client|colleague|customer|friend|lead|manager|reviewer|teammate|user)(?:'s|\u2019s)?(?:\s+(?:comment|email|feedback|message|note|report|review))?\s*[:,\u2014-]\s*/iu,
|
|
951
|
+
/^(?:my|our)\s+(?:architect|attorney|consultant|contractor|director|mentor|vendor)\s*,\s*/iu,
|
|
952
|
+
/^(?:my|our)\s+(?:build|ci|console|linter|terminal|test)\s+(?:log|output|report|transcript)\s*[:,\u2014-]\s*/iu,
|
|
953
|
+
/^(?:auditor|client|customer|external|legal|reviewer|support|third-party|user|vendor)-(?:authored|provided|reported|submitted|written)\s+(?:[\p{L}\p{M}\p{N}'-]+\s+){0,3}[\p{L}\p{M}\p{N}'-]+\s*[:,\u2014-]\s*/iu,
|
|
954
|
+
/^(?:auditor|client|customer|reviewer|support|user)\s+(?:comment|email|feedback|message|note|report|request|review)\s*[:,\u2014-]\s*/iu,
|
|
955
|
+
/^(?:(?:compliance|external|legal|privacy|security)\s+)?audit(?:\s+(?:comment|finding|note|report|review))?\s*[:,\u2014-]\s*/iu,
|
|
956
|
+
/^(?:the\s+following\s+is\s+from|this\s+came\s+from|i\s+am\s+quoting|quoting|according\s+to|per|from)\s+(?!(?:me|us|myself|my|our|the\s+agreed\s+plan|the\s+plan\s+we\s+agreed\s+on)\b)/iu,
|
|
957
|
+
/^(?:per|according\s+to|from)\s+(?:my|our)\s+(?:architects?|auditors?|clients?|colleagues?|customers?|engineers?|leads?|managers?|reviewers?|teammates?|users?|qa|sre|support|legal|compliance)\b/iu,
|
|
958
|
+
/^(?:here\s+(?:is|are)|copied\s+from|summary\s+from)\s+(?!(?:me|us|myself)\b)/iu,
|
|
959
|
+
/^(?:their|his|her|its|(?:claude|reviewer|auditor|qa)(?:'s|\u2019s)?)\s+(?:runbook|test\s+plan|implementation\s+notes?|requirements?|plan)\s+(?:says|requires|specifies|states)\s*[:\u2014-]/iu,
|
|
960
|
+
/^(?:a|the)\s+(?:user|customer|reviewer|auditor|engineer)\s+(?:wrote|writes|said|says|reported|reports|asked|asks)\b/iu,
|
|
961
|
+
/^(?:the\s+)?(?:console\s+transcript|terminal|build\s+transcript|failing\s+assertion)\s+(?:reads|printed|contains|says|reports)\b/iu,
|
|
962
|
+
/^(?:she|he|they)\s+(?:say|says|said|think|thinks|thought|report|reports|reported|write|writes|wrote|claim|claims|claimed)\b/iu,
|
|
963
|
+
/^(?:feedback|message|email|note|comment|report|request|complaint)\s+from\s+(?!(?:me|us|myself)\b)/iu,
|
|
964
|
+
/^(?:review\s+feedback|external\s+review|customer\s+email|support\s+thread|the\s+support\s+thread|the\s+linked\s+issue|in\s+the\s+linked\s+issue|ticket(?:\s+[A-Z][A-Z0-9_-]*)?|claude\s+feedback)\s*(?:[:,\u2014-]\s*|$)/iu,
|
|
965
|
+
/^(?:claude|qa|the\s+(?:reviewer|reviewers|auditor|logs?)|my\s+teammates?|our\s+reviewer|my\s+teammate|compliance|support|legal)\b.{0,96}?\b(?:say|says|said|think|thinks|thought|report|reports|reported|write|writes|wrote|claim|claims|claimed|note|notes|noted|tell|tells|told|mention|mentions|mentioned|argue|argues|argued|ask|asks|asked|read|reads|contain|contains|print|prints|printed)\b/iu,
|
|
966
|
+
/^(?:i|we)\s+(?:heard|read|saw)\b.{0,96}?\b(?:claude|qa|reviewer|auditor|teammate|compliance|support|legal)\b/iu,
|
|
967
|
+
/^(?:i|we)\s+(?:think|believe)\s*,?\s*(?:according\s+to\s+)?(?:claude|qa|the\s+reviewer|my\s+teammate|our\s+reviewer)\b/iu,
|
|
968
|
+
]);
|
|
969
|
+
|
|
970
|
+
const CORRECTION_TRANSFORMATION_PREFIXES = Object.freeze([
|
|
971
|
+
/^(?:(?:[\p{L}\p{M}-]+\s+)?translation|summary|quoted\s+text|(?:(?:proposed|draft|suggested)\s+)?commit\s+message|(?:(?:proposed|draft|suggested)\s+)?(?:issue|pr|review)\s+comment|review\s+feedback|example)\s*(?:[:\u2014-]\s*|$)/iu,
|
|
972
|
+
/^(?:(?:please|could\s+you|would\s+you|can\s+you)\s+)?(?:translate|summari[sz]e|paraphrase|rewrite|quote|attribute|condense)\b/iu,
|
|
973
|
+
/^(?:translating|summari[sz]ing|paraphrasing|rewriting|quoting|attributing|condensing)\b/iu,
|
|
974
|
+
/^(?:i|we)\s+(?:(?:need|want|would\s+like)\s+you\s+to|(?:am|are)\s+asking\s+you\s+to)\s+(?:translate|summari[sz]e|paraphrase|rewrite|quote|attribute|condense)\b/iu,
|
|
975
|
+
/^(?:(?:please|could\s+you|would\s+you|can\s+you)\s+)?(?:write|draft|create|prepare|make|turn|render|formulate|paste)\b.{0,160}?\b(?:commit\s+message|pr\s+comment|review\s+comment|issue\s+comment|report|summary|(?:concise\s+)?note|the\s+review)\b/iu,
|
|
976
|
+
/^(?:give|provide)\s+(?:me|us)\b.{0,96}?\b(?:summary|translation|paraphrase|rewrite)\b/iu,
|
|
977
|
+
/^use\s+(?:this|that|it|the\s+following)\b.{0,160}?\b(?:write|draft|create|prepare|make|turn|render|as|into|for)\b.{0,160}?\b(?:commit\s+message|pr\s+comment|review\s+comment|issue\s+comment|report|summary)\b/iu,
|
|
978
|
+
]);
|
|
979
|
+
|
|
980
|
+
const CORRECTION_MACHINE_PREFIX =
|
|
981
|
+
/^\s*(?:traceback\s+\(most\s+recent\s+call\s+last\):|(?:stderr|stdout|stack\s+trace)\s*[:>-]|ci\s*\/\s*cd\s+(?:error|failure|warning)\s*[:>-]|(?:ci\s+output|the\s+linter)\s+(?:says|warns)\s*[:>-])/iu;
|
|
982
|
+
const CORRECTION_HOST_OPEN =
|
|
983
|
+
/<(?<tag>hook_prompt|subagent_notification|system_notification)\b/iu;
|
|
984
|
+
|
|
985
|
+
const CORRECTION_QUOTE_DEFINITIONS = Object.freeze([
|
|
986
|
+
{ open: '"""', closes: ['"""'], symmetric: true },
|
|
987
|
+
{ open: "'''", closes: ["'''"], symmetric: true },
|
|
988
|
+
{ open: '"', closes: ['"'], symmetric: true },
|
|
989
|
+
{ open: "'", closes: ["'"], symmetric: true, apostrophe: true },
|
|
990
|
+
{ open: "\uff02", closes: ["\uff02"], symmetric: true },
|
|
991
|
+
{ open: "\uff07", closes: ["\uff07"], symmetric: true, apostrophe: true },
|
|
992
|
+
{ open: "\u201c", closes: ["\u201d"] },
|
|
993
|
+
{ open: "\u201d", closes: ["\u201c"] },
|
|
994
|
+
{ open: "\u201e", closes: ["\u201c", "\u201d"] },
|
|
995
|
+
{ open: "\u201f", closes: ["\u201d"] },
|
|
996
|
+
{ open: "\u2018", closes: ["\u2019"] },
|
|
997
|
+
{ open: "\u2019", closes: ["\u2018"], apostrophe: true },
|
|
998
|
+
{ open: "\u201a", closes: ["\u2018", "\u2019"] },
|
|
999
|
+
{ open: "\u201b", closes: ["\u2019"] },
|
|
1000
|
+
{ open: "\u00ab", closes: ["\u00bb"] },
|
|
1001
|
+
{ open: "\u00bb", closes: ["\u00ab"] },
|
|
1002
|
+
{ open: "\u2039", closes: ["\u203a"] },
|
|
1003
|
+
{ open: "\u203a", closes: ["\u2039"] },
|
|
1004
|
+
{ open: "\u3008", closes: ["\u3009"] },
|
|
1005
|
+
{ open: "\u300a", closes: ["\u300b"] },
|
|
1006
|
+
{ open: "\u300c", closes: ["\u300d"] },
|
|
1007
|
+
{ open: "\u300e", closes: ["\u300f"] },
|
|
1008
|
+
{ open: "\u301d", closes: ["\u301e", "\u301f"] },
|
|
1009
|
+
{ open: "\ufe41", closes: ["\ufe42"] },
|
|
1010
|
+
{ open: "\ufe43", closes: ["\ufe44"] },
|
|
1011
|
+
{ open: "\uff1c", closes: ["\uff1e"] },
|
|
1012
|
+
]);
|
|
1013
|
+
|
|
1014
|
+
function correctionPromptContainsFirstPartyCorrection(
|
|
1015
|
+
value,
|
|
1016
|
+
measuredWork = null,
|
|
1017
|
+
) {
|
|
1018
|
+
if (!value) return false;
|
|
1019
|
+
const decision = {
|
|
1020
|
+
candidate: false,
|
|
1021
|
+
priorCandidate: false,
|
|
1022
|
+
lastCandidate: "",
|
|
1023
|
+
lastCandidateStart: -1,
|
|
1024
|
+
windowOffset: 0,
|
|
1025
|
+
globalOptOut: false,
|
|
1026
|
+
sourceSuppressed: false,
|
|
1027
|
+
sourcePayloadPending: false,
|
|
1028
|
+
localSuppression: false,
|
|
1029
|
+
historicalPending: false,
|
|
1030
|
+
historicalText: "",
|
|
1031
|
+
historicalStart: 0,
|
|
1032
|
+
factualPending: null,
|
|
1033
|
+
};
|
|
1034
|
+
const work = measuredWork ?? {
|
|
1035
|
+
inputCodeUnits: 0,
|
|
1036
|
+
structuralCodeUnits: 0,
|
|
1037
|
+
semanticCodeUnits: 0,
|
|
1038
|
+
};
|
|
1039
|
+
const collector = correctionStatementCollector(
|
|
1040
|
+
(statement, ended, offset) => {
|
|
1041
|
+
decision.windowOffset = offset;
|
|
1042
|
+
correctionEvaluateStatement(statement, decision, ended, work);
|
|
1043
|
+
},
|
|
1044
|
+
work,
|
|
1045
|
+
() => {
|
|
1046
|
+
decision.factualPending = null;
|
|
1047
|
+
},
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
correctionScanVisibleText(String(value), collector, work);
|
|
1051
|
+
collector.finish();
|
|
1052
|
+
if (decision.historicalPending) decision.candidate = true;
|
|
1053
|
+
return !decision.globalOptOut && decision.candidate;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
export function measureFirstPartyWrapperScanWorkForTests(value) {
|
|
1057
|
+
const work = {
|
|
1058
|
+
inputCodeUnits: 0,
|
|
1059
|
+
structuralCodeUnits: 0,
|
|
1060
|
+
semanticCodeUnits: 0,
|
|
1061
|
+
};
|
|
1062
|
+
const detected = correctionPromptContainsFirstPartyCorrection(
|
|
1063
|
+
String(value ?? ""),
|
|
1064
|
+
work,
|
|
1065
|
+
);
|
|
1066
|
+
return {
|
|
1067
|
+
detected,
|
|
1068
|
+
work: {
|
|
1069
|
+
...work,
|
|
1070
|
+
total:
|
|
1071
|
+
work.inputCodeUnits + work.structuralCodeUnits + work.semanticCodeUnits,
|
|
1072
|
+
},
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function correctionStatementCollector(visit, work, interrupt) {
|
|
1077
|
+
const buffer = new Uint16Array(CORRECTION_WINDOW_CHARS);
|
|
1078
|
+
let length = 0;
|
|
1079
|
+
let offset = 0;
|
|
1080
|
+
let pendingBoundary = false;
|
|
1081
|
+
|
|
1082
|
+
const flush = (ended) => {
|
|
1083
|
+
if (length === 0) return;
|
|
1084
|
+
const raw = correctionStringFromCodeUnits(buffer, length);
|
|
1085
|
+
const statement = raw.trim();
|
|
1086
|
+
if (statement)
|
|
1087
|
+
visit(statement, ended, offset + raw.length - raw.trimStart().length);
|
|
1088
|
+
if (ended) {
|
|
1089
|
+
offset += length;
|
|
1090
|
+
length = 0;
|
|
1091
|
+
pendingBoundary = false;
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const retained = Math.min(length, CORRECTION_WINDOW_OVERLAP_CHARS);
|
|
1095
|
+
offset += length - retained;
|
|
1096
|
+
buffer.copyWithin(0, length - retained, length);
|
|
1097
|
+
length = retained;
|
|
1098
|
+
pendingBoundary = false;
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
const appendCodeUnit = (codeUnit) => {
|
|
1102
|
+
if (length === CORRECTION_WINDOW_CHARS) flush(false);
|
|
1103
|
+
buffer[length] = codeUnit;
|
|
1104
|
+
length += 1;
|
|
1105
|
+
work.semanticCodeUnits += 1;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const append = (text) => {
|
|
1109
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1110
|
+
const codeUnit = text.charCodeAt(index);
|
|
1111
|
+
const character = text[index];
|
|
1112
|
+
if (pendingBoundary && /\s/u.test(character)) flush(true);
|
|
1113
|
+
appendCodeUnit(codeUnit);
|
|
1114
|
+
if (/[.!?]/u.test(character)) {
|
|
1115
|
+
pendingBoundary = true;
|
|
1116
|
+
} else if (!/\s/u.test(character)) {
|
|
1117
|
+
pendingBoundary = false;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
return {
|
|
1123
|
+
appendRaw(raw) {
|
|
1124
|
+
const normalized = raw
|
|
1125
|
+
.replace(/\uff1c/gu, "\u2039")
|
|
1126
|
+
.replace(/\uff1e/gu, "\u203a")
|
|
1127
|
+
.replace(/[\u2018\u2019]/gu, "'")
|
|
1128
|
+
.normalize("NFKC");
|
|
1129
|
+
append(normalized);
|
|
1130
|
+
},
|
|
1131
|
+
separator(lineBreak = false) {
|
|
1132
|
+
if (pendingBoundary) {
|
|
1133
|
+
flush(true);
|
|
1134
|
+
interrupt();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
interrupt();
|
|
1138
|
+
if (lineBreak && length > 0 && length <= 512) {
|
|
1139
|
+
work.semanticCodeUnits += length;
|
|
1140
|
+
// A standalone control must not absorb the next line's heading as
|
|
1141
|
+
// its object. Source wrappers still stay joined to their payload.
|
|
1142
|
+
const control = correctionRetractionControl(
|
|
1143
|
+
correctionStringFromCodeUnits(buffer, length).trim(),
|
|
1144
|
+
);
|
|
1145
|
+
if (control?.kind === "turn" || control?.kind === "reference") {
|
|
1146
|
+
flush(true);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (length > 0 && buffer[length - 1] !== 32) appendCodeUnit(32);
|
|
1151
|
+
},
|
|
1152
|
+
boundary() {
|
|
1153
|
+
flush(true);
|
|
1154
|
+
interrupt();
|
|
1155
|
+
},
|
|
1156
|
+
finish() {
|
|
1157
|
+
flush(true);
|
|
1158
|
+
},
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function correctionScanVisibleText(value, collector, work) {
|
|
1163
|
+
const state = {
|
|
1164
|
+
fence: null,
|
|
1165
|
+
lazyBlockquote: false,
|
|
1166
|
+
paragraphOpen: false,
|
|
1167
|
+
traceContinuation: false,
|
|
1168
|
+
hostTag: null,
|
|
1169
|
+
quote: null,
|
|
1170
|
+
inline: null,
|
|
1171
|
+
};
|
|
1172
|
+
|
|
1173
|
+
let lineStart = 0;
|
|
1174
|
+
while (lineStart <= value.length) {
|
|
1175
|
+
const newline = value.indexOf("\n", lineStart);
|
|
1176
|
+
const lineEnd = newline < 0 ? value.length : newline;
|
|
1177
|
+
const contentEnd =
|
|
1178
|
+
lineEnd > lineStart && value[lineEnd - 1] === "\r"
|
|
1179
|
+
? lineEnd - 1
|
|
1180
|
+
: lineEnd;
|
|
1181
|
+
const prefix = value
|
|
1182
|
+
.slice(lineStart, Math.min(contentEnd, lineStart + 256))
|
|
1183
|
+
.normalize("NFKC");
|
|
1184
|
+
work.inputCodeUnits += lineEnd - lineStart + Number(newline >= 0);
|
|
1185
|
+
work.structuralCodeUnits += contentEnd - lineStart;
|
|
1186
|
+
const blank = correctionRangeIsBlank(value, lineStart, contentEnd);
|
|
1187
|
+
|
|
1188
|
+
if (state.hostTag) {
|
|
1189
|
+
if (
|
|
1190
|
+
value
|
|
1191
|
+
.slice(lineStart, contentEnd)
|
|
1192
|
+
.toLocaleLowerCase("en-US")
|
|
1193
|
+
.includes("</" + state.hostTag + ">")
|
|
1194
|
+
) {
|
|
1195
|
+
state.hostTag = null;
|
|
1196
|
+
}
|
|
1197
|
+
collector.separator();
|
|
1198
|
+
} else {
|
|
1199
|
+
const hostOpen = prefix.match(CORRECTION_HOST_OPEN);
|
|
1200
|
+
if (hostOpen?.groups?.tag) {
|
|
1201
|
+
const tag = hostOpen.groups.tag.toLocaleLowerCase("en-US");
|
|
1202
|
+
if (!prefix.toLocaleLowerCase("en-US").includes("</" + tag + ">")) {
|
|
1203
|
+
state.hostTag = tag;
|
|
1204
|
+
}
|
|
1205
|
+
collector.separator();
|
|
1206
|
+
} else {
|
|
1207
|
+
correctionScanLine({
|
|
1208
|
+
value,
|
|
1209
|
+
lineStart,
|
|
1210
|
+
lineEnd: contentEnd,
|
|
1211
|
+
prefix,
|
|
1212
|
+
blank,
|
|
1213
|
+
state,
|
|
1214
|
+
collector,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (newline < 0) break;
|
|
1220
|
+
if (blank) {
|
|
1221
|
+
correctionFlushUnmatchedInline(state, collector);
|
|
1222
|
+
state.quote = null;
|
|
1223
|
+
collector.boundary();
|
|
1224
|
+
} else {
|
|
1225
|
+
collector.separator(true);
|
|
1226
|
+
}
|
|
1227
|
+
lineStart = newline + 1;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
correctionFlushUnmatchedInline(state, collector);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function correctionScanLine({
|
|
1234
|
+
value,
|
|
1235
|
+
lineStart,
|
|
1236
|
+
lineEnd,
|
|
1237
|
+
prefix,
|
|
1238
|
+
blank,
|
|
1239
|
+
state,
|
|
1240
|
+
collector,
|
|
1241
|
+
}) {
|
|
1242
|
+
if (state.fence) {
|
|
1243
|
+
const fence = correctionFenceLine(prefix, state.fence);
|
|
1244
|
+
if (fence?.kind === "close") state.fence = null;
|
|
1245
|
+
state.paragraphOpen = false;
|
|
1246
|
+
state.traceContinuation = false;
|
|
1247
|
+
collector.separator();
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (blank) {
|
|
1252
|
+
state.lazyBlockquote = false;
|
|
1253
|
+
state.paragraphOpen = false;
|
|
1254
|
+
state.traceContinuation = false;
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const fence = correctionFenceLine(prefix, null);
|
|
1259
|
+
if (fence) {
|
|
1260
|
+
state.fence = { marker: fence.marker, width: fence.width };
|
|
1261
|
+
state.lazyBlockquote = false;
|
|
1262
|
+
state.paragraphOpen = false;
|
|
1263
|
+
state.traceContinuation = false;
|
|
1264
|
+
collector.separator();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
if (correctionLineStartsBlockquote(prefix)) {
|
|
1269
|
+
state.lazyBlockquote = !correctionBlockquoteMarkerIsBlank(prefix);
|
|
1270
|
+
state.paragraphOpen = state.lazyBlockquote;
|
|
1271
|
+
state.traceContinuation = false;
|
|
1272
|
+
collector.separator();
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (state.lazyBlockquote && !correctionLineInterruptsBlockquote(prefix)) {
|
|
1277
|
+
collector.separator();
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
state.lazyBlockquote = false;
|
|
1281
|
+
|
|
1282
|
+
if (correctionLineIsIndented(prefix) && !state.paragraphOpen) {
|
|
1283
|
+
state.paragraphOpen = false;
|
|
1284
|
+
state.traceContinuation = false;
|
|
1285
|
+
collector.separator();
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
const machine =
|
|
1290
|
+
CORRECTION_MACHINE_PREFIX.test(prefix) || state.traceContinuation;
|
|
1291
|
+
state.traceContinuation = machine;
|
|
1292
|
+
if (machine) {
|
|
1293
|
+
collector.separator();
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
correctionScanInlineRange(value, lineStart, lineEnd, state, collector);
|
|
1298
|
+
state.paragraphOpen = true;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function correctionFenceLine(prefix, active) {
|
|
1302
|
+
const match = prefix.match(/^( {0,3})(\x60{3,}|~{3,})(.*)$/u);
|
|
1303
|
+
if (!match) return null;
|
|
1304
|
+
const marker = match[2][0];
|
|
1305
|
+
const width = match[2].length;
|
|
1306
|
+
if (active) {
|
|
1307
|
+
if (
|
|
1308
|
+
marker !== active.marker ||
|
|
1309
|
+
width < active.width ||
|
|
1310
|
+
!/^[\t \r]*$/u.test(match[3])
|
|
1311
|
+
) {
|
|
1312
|
+
return null;
|
|
1313
|
+
}
|
|
1314
|
+
return { kind: "close", marker, width };
|
|
1315
|
+
}
|
|
1316
|
+
if (marker === "\x60" && match[3].includes("\x60")) return null;
|
|
1317
|
+
return { kind: "open", marker, width };
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function correctionLineStartsBlockquote(prefix) {
|
|
1321
|
+
return /^ {0,3}>/u.test(prefix);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function correctionBlockquoteMarkerIsBlank(prefix) {
|
|
1325
|
+
return /^ {0,3}>[\t \r]*$/u.test(prefix);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function correctionLineInterruptsBlockquote(prefix) {
|
|
1329
|
+
if (/^ {0,3}#{1,6}(?:[\t ]|$)/u.test(prefix)) return true;
|
|
1330
|
+
if (/^ {0,3}(?:\*[\t ]*){3,}$/u.test(prefix)) return true;
|
|
1331
|
+
if (/^ {0,3}(?:-[\t ]*){3,}$/u.test(prefix)) return true;
|
|
1332
|
+
if (/^ {0,3}(?:_[\t ]*){3,}$/u.test(prefix)) return true;
|
|
1333
|
+
if (/^ {0,3}[-+*][\t ]+/u.test(prefix)) return true;
|
|
1334
|
+
return /^ {0,3}1[.)][\t ]+/u.test(prefix);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function correctionLineIsIndented(prefix) {
|
|
1338
|
+
if (prefix.startsWith("\t")) return true;
|
|
1339
|
+
let columns = 0;
|
|
1340
|
+
for (const character of prefix) {
|
|
1341
|
+
if (character === " ") {
|
|
1342
|
+
columns += 1;
|
|
1343
|
+
} else if (character === "\t") {
|
|
1344
|
+
columns += 4 - (columns % 4);
|
|
1345
|
+
} else {
|
|
1346
|
+
break;
|
|
1347
|
+
}
|
|
1348
|
+
if (columns >= 4) return true;
|
|
1349
|
+
}
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function correctionRangeIsBlank(value, start, end) {
|
|
1354
|
+
for (let index = start; index < end; index += 1) {
|
|
1355
|
+
if (!/[\t \r]/u.test(value[index])) return false;
|
|
1356
|
+
}
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function correctionScanInlineRange(value, start, end, state, collector) {
|
|
1361
|
+
let cursor = start;
|
|
1362
|
+
while (cursor < end) {
|
|
1363
|
+
if (state.inline) {
|
|
1364
|
+
if (value[cursor] === "\x60") {
|
|
1365
|
+
const runEnd = correctionDelimiterRunEnd(value, cursor, end, "\x60");
|
|
1366
|
+
if (runEnd - cursor === state.inline.width) {
|
|
1367
|
+
state.inline = null;
|
|
1368
|
+
collector.separator();
|
|
1369
|
+
cursor = runEnd;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
correctionBufferInlinePoint(value, cursor, state);
|
|
1374
|
+
cursor += String.fromCodePoint(value.codePointAt(cursor)).length;
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (state.quote) {
|
|
1379
|
+
if (state.quote.escaped) {
|
|
1380
|
+
const close = state.quote.closes.find(
|
|
1381
|
+
(candidate) =>
|
|
1382
|
+
value[cursor] === "\\" && value.startsWith(candidate, cursor + 1),
|
|
1383
|
+
);
|
|
1384
|
+
if (close) {
|
|
1385
|
+
correctionCloseQuote(state, collector);
|
|
1386
|
+
cursor += close.length + 1;
|
|
1387
|
+
} else {
|
|
1388
|
+
correctionQuotePoint(value[cursor], state.quote);
|
|
1389
|
+
cursor += String.fromCodePoint(value.codePointAt(cursor)).length;
|
|
1390
|
+
}
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
const close = state.quote.closes.find((candidate) =>
|
|
1394
|
+
value.startsWith(candidate, cursor),
|
|
1395
|
+
);
|
|
1396
|
+
if (close) {
|
|
1397
|
+
if (
|
|
1398
|
+
state.quote.apostrophe &&
|
|
1399
|
+
correctionQuoteIsApostrophe(value, cursor, close.length, end)
|
|
1400
|
+
) {
|
|
1401
|
+
cursor += close.length;
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
if (
|
|
1405
|
+
state.quote.symmetric &&
|
|
1406
|
+
state.quote.open.length === 1 &&
|
|
1407
|
+
correctionQuoteCanOpen(value, cursor, close.length, end)
|
|
1408
|
+
) {
|
|
1409
|
+
state.quote.nestedDepth += 1;
|
|
1410
|
+
cursor += close.length;
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (state.quote.nestedDepth > 0) {
|
|
1414
|
+
state.quote.nestedDepth -= 1;
|
|
1415
|
+
} else {
|
|
1416
|
+
correctionCloseQuote(state, collector);
|
|
1417
|
+
}
|
|
1418
|
+
cursor += close.length;
|
|
1419
|
+
} else {
|
|
1420
|
+
correctionQuotePoint(value[cursor], state.quote);
|
|
1421
|
+
cursor += String.fromCodePoint(value.codePointAt(cursor)).length;
|
|
1422
|
+
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const escapedQuote = correctionEscapedQuoteAt(value, cursor, end);
|
|
1427
|
+
if (escapedQuote) {
|
|
1428
|
+
state.quote = { ...escapedQuote, nestedDepth: 0, escaped: true };
|
|
1429
|
+
collector.separator();
|
|
1430
|
+
cursor += escapedQuote.open.length + 1;
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (value[cursor] === "\x60") {
|
|
1435
|
+
const runEnd = correctionDelimiterRunEnd(value, cursor, end, "\x60");
|
|
1436
|
+
state.inline = correctionInlineState(runEnd - cursor);
|
|
1437
|
+
correctionBufferInlineText(value.slice(cursor, runEnd), state);
|
|
1438
|
+
cursor = runEnd;
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
const quote = correctionQuoteAt(value, cursor, end);
|
|
1443
|
+
if (quote) {
|
|
1444
|
+
state.quote = { ...quote, nestedDepth: 0 };
|
|
1445
|
+
collector.separator();
|
|
1446
|
+
cursor += quote.open.length;
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const point = String.fromCodePoint(value.codePointAt(cursor));
|
|
1451
|
+
collector.appendRaw(point);
|
|
1452
|
+
cursor += point.length;
|
|
1453
|
+
}
|
|
1454
|
+
if (state.inline && !state.inline.overflow) {
|
|
1455
|
+
correctionBufferInlineText(" ", state);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
function correctionQuotePoint(character, quote) {
|
|
1460
|
+
if (!/\s/u.test(character)) quote.endedSentence = /[.!?]/u.test(character);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function correctionCloseQuote(state, collector) {
|
|
1464
|
+
if (state.quote.endedSentence) collector.boundary();
|
|
1465
|
+
else collector.separator();
|
|
1466
|
+
state.quote = null;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function correctionEscapedQuoteAt(value, index, end) {
|
|
1470
|
+
if (value[index] !== "\\" || correctionEscapedAt(value, index)) return null;
|
|
1471
|
+
const definition = CORRECTION_QUOTE_DEFINITIONS.find(
|
|
1472
|
+
(candidate) =>
|
|
1473
|
+
candidate.symmetric &&
|
|
1474
|
+
candidate.open.length === 1 &&
|
|
1475
|
+
value.startsWith(candidate.open, index + 1),
|
|
1476
|
+
);
|
|
1477
|
+
if (!definition) return null;
|
|
1478
|
+
const delimiter = "\\" + definition.open;
|
|
1479
|
+
const closingIndex = value.indexOf(delimiter, index + delimiter.length);
|
|
1480
|
+
return closingIndex >= 0 && closingIndex < end ? definition : null;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function correctionDelimiterRunEnd(value, start, end, delimiter) {
|
|
1484
|
+
let cursor = start;
|
|
1485
|
+
while (cursor < end && value[cursor] === delimiter) cursor += 1;
|
|
1486
|
+
return cursor;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function correctionInlineState(width) {
|
|
1490
|
+
return {
|
|
1491
|
+
width,
|
|
1492
|
+
buffer: new Uint16Array(CORRECTION_INLINE_BUFFER_CHARS),
|
|
1493
|
+
length: 0,
|
|
1494
|
+
overflow: false,
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function correctionBufferInlinePoint(value, index, state) {
|
|
1499
|
+
const point = String.fromCodePoint(value.codePointAt(index));
|
|
1500
|
+
correctionBufferInlineText(point, state);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function correctionBufferInlineText(text, state) {
|
|
1504
|
+
if (state.inline.overflow) return;
|
|
1505
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1506
|
+
if (state.inline.length === CORRECTION_INLINE_BUFFER_CHARS) {
|
|
1507
|
+
state.inline.overflow = true;
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
state.inline.buffer[state.inline.length] = text.charCodeAt(index);
|
|
1511
|
+
state.inline.length += 1;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
function correctionFlushUnmatchedInline(state, collector) {
|
|
1516
|
+
if (!state.inline) return;
|
|
1517
|
+
if (!state.inline.overflow) {
|
|
1518
|
+
collector.appendRaw(
|
|
1519
|
+
correctionStringFromCodeUnits(state.inline.buffer, state.inline.length),
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
state.inline = null;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
function correctionQuoteAt(value, index, end) {
|
|
1526
|
+
if (correctionEscapedAt(value, index)) return null;
|
|
1527
|
+
for (const definition of CORRECTION_QUOTE_DEFINITIONS) {
|
|
1528
|
+
if (!value.startsWith(definition.open, index)) continue;
|
|
1529
|
+
if (
|
|
1530
|
+
definition.apostrophe &&
|
|
1531
|
+
correctionQuoteIsApostrophe(value, index, definition.open.length, end)
|
|
1532
|
+
) {
|
|
1533
|
+
return null;
|
|
1534
|
+
}
|
|
1535
|
+
if (
|
|
1536
|
+
definition.symmetric &&
|
|
1537
|
+
definition.open.length === 1 &&
|
|
1538
|
+
!correctionQuoteCanOpen(value, index, definition.open.length, end)
|
|
1539
|
+
) {
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
return definition;
|
|
1543
|
+
}
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function correctionEscapedAt(value, index) {
|
|
1548
|
+
let escapes = 0;
|
|
1549
|
+
for (
|
|
1550
|
+
let cursor = index - 1;
|
|
1551
|
+
cursor >= 0 && value[cursor] === "\\";
|
|
1552
|
+
cursor -= 1
|
|
1553
|
+
) {
|
|
1554
|
+
escapes += 1;
|
|
1555
|
+
}
|
|
1556
|
+
return escapes % 2 === 1;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function correctionQuoteIsApostrophe(value, index, width, end) {
|
|
1560
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
1561
|
+
const next = index + width < end ? value[index + width] : "";
|
|
1562
|
+
return /[\p{L}\p{N}_]/u.test(previous) && /[\p{L}\p{N}_]/u.test(next);
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function correctionQuoteCanOpen(value, index, width, end) {
|
|
1566
|
+
const previous = index > 0 ? value[index - 1] : "";
|
|
1567
|
+
const next = index + width < end ? value[index + width] : "";
|
|
1568
|
+
const openingContext =
|
|
1569
|
+
!previous || /\s/u.test(previous) || "([{<:;,\u2014-".includes(previous);
|
|
1570
|
+
return openingContext && Boolean(next) && !/\s/u.test(next);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
function correctionStringFromCodeUnits(buffer, length) {
|
|
1574
|
+
let output = "";
|
|
1575
|
+
for (let start = 0; start < length; start += 1024) {
|
|
1576
|
+
output += String.fromCharCode(
|
|
1577
|
+
...buffer.subarray(start, Math.min(length, start + 1024)),
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
return output;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function correctionEvaluateStatement(statement, decision, ended, work) {
|
|
1584
|
+
const { text, offsets } = correctionNormalizedStatement(statement);
|
|
1585
|
+
const question = /\?\s*$/u.test(text);
|
|
1586
|
+
const factualPending = decision.factualPending;
|
|
1587
|
+
decision.factualPending = null;
|
|
1588
|
+
work.semanticCodeUnits += statement.length;
|
|
1589
|
+
const pendingBefore = decision.historicalPending;
|
|
1590
|
+
let newHistoricalClaim = false;
|
|
1591
|
+
let localSuppression = decision.localSuppression;
|
|
1592
|
+
let sourcePayloadPending = decision.sourcePayloadPending;
|
|
1593
|
+
let statementHasContent = false;
|
|
1594
|
+
const clauses = correctionStatementClauses(text);
|
|
1595
|
+
|
|
1596
|
+
for (let index = 0; index < clauses.length; index += 1) {
|
|
1597
|
+
const clause = clauses[index];
|
|
1598
|
+
const provenance = correctionClauseProvenance(clause.text, work);
|
|
1599
|
+
const next = clauses[index + 1];
|
|
1600
|
+
const postfix =
|
|
1601
|
+
correctionPostfixAttribution(clause.text) ||
|
|
1602
|
+
(next?.boundary === "comma" &&
|
|
1603
|
+
correctionPostfixAttribution(next.text, true));
|
|
1604
|
+
if (provenance.source || postfix) {
|
|
1605
|
+
statementHasContent = true;
|
|
1606
|
+
decision.sourceSuppressed = provenance.source || "reported";
|
|
1607
|
+
sourcePayloadPending = Boolean(provenance.sourcePayloadPending);
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (sourcePayloadPending) {
|
|
1611
|
+
statementHasContent = true;
|
|
1612
|
+
// A reported interjection can end at a comma before its payload begins.
|
|
1613
|
+
sourcePayloadPending = !provenance.payload;
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
const payload = provenance.payload;
|
|
1618
|
+
const atStatementStart = !statementHasContent;
|
|
1619
|
+
statementHasContent ||= Boolean(
|
|
1620
|
+
payload || provenance.adoption || provenance.suppressed,
|
|
1621
|
+
);
|
|
1622
|
+
// Embedded controls belong to their source. A standalone, unquoted
|
|
1623
|
+
// memory refusal still wins over an inherited reporting scope.
|
|
1624
|
+
const retraction = provenance.retraction;
|
|
1625
|
+
const durableOptOut = correctionHasDurableOptOut(payload);
|
|
1626
|
+
if (
|
|
1627
|
+
decision.sourceSuppressed &&
|
|
1628
|
+
!atStatementStart &&
|
|
1629
|
+
!provenance.adoption &&
|
|
1630
|
+
(retraction || durableOptOut)
|
|
1631
|
+
)
|
|
1632
|
+
continue;
|
|
1633
|
+
if (retraction?.kind === "turn" || durableOptOut) {
|
|
1634
|
+
decision.globalOptOut = true;
|
|
1635
|
+
decision.historicalPending = false;
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (retraction) {
|
|
1639
|
+
if (retraction.kind === "claim") {
|
|
1640
|
+
if (decision.historicalPending) {
|
|
1641
|
+
decision.historicalPending = false;
|
|
1642
|
+
} else if (
|
|
1643
|
+
decision.lastCandidate &&
|
|
1644
|
+
(!retraction.target ||
|
|
1645
|
+
decision.lastCandidate
|
|
1646
|
+
.toLocaleLowerCase("en-US")
|
|
1647
|
+
.split(/[^\p{L}\p{N}-]+/u)
|
|
1648
|
+
.includes(retraction.target))
|
|
1649
|
+
) {
|
|
1650
|
+
decision.candidate = decision.priorCandidate;
|
|
1651
|
+
decision.lastCandidate = "";
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
const independent =
|
|
1658
|
+
provenance.adoption ||
|
|
1659
|
+
((Boolean(payload) || atStatementStart) &&
|
|
1660
|
+
/^(?:separately|to\s+be\s+clear|speaking\s+for\s+(?:myself|ourselves))\b/iu.test(
|
|
1661
|
+
clause.text,
|
|
1662
|
+
)) ||
|
|
1663
|
+
(clause.boundary === "adversative" &&
|
|
1664
|
+
decision.sourceSuppressed !== "transformation" &&
|
|
1665
|
+
Boolean(correctionFirstDirectMatch(payload)));
|
|
1666
|
+
if (independent) {
|
|
1667
|
+
decision.sourceSuppressed = false;
|
|
1668
|
+
localSuppression = false;
|
|
1669
|
+
}
|
|
1670
|
+
if (decision.sourceSuppressed) continue;
|
|
1671
|
+
if (provenance.suppressed) localSuppression = true;
|
|
1672
|
+
if (localSuppression) continue;
|
|
1673
|
+
|
|
1674
|
+
if (
|
|
1675
|
+
decision.historicalPending &&
|
|
1676
|
+
correctionIsHistoricalResolution(payload)
|
|
1677
|
+
) {
|
|
1678
|
+
decision.historicalPending = false;
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1681
|
+
const direct = correctionFirstDirectMatch(payload);
|
|
1682
|
+
const historicalDirect =
|
|
1683
|
+
provenance.historical &&
|
|
1684
|
+
/^i\s+(?:said|asked|told\s+you)\b/iu.test(clause.text) &&
|
|
1685
|
+
correctionFirstDirectMatch(clause.text);
|
|
1686
|
+
const explicitCorrection = provenance.explicitCorrection;
|
|
1687
|
+
const negatedValue = correctionNegatedFactualValue(payload);
|
|
1688
|
+
const atomicReplacement =
|
|
1689
|
+
ended && !question && index === 0 && factualPending
|
|
1690
|
+
? correctionAtomicFactualValue(payload)
|
|
1691
|
+
: null;
|
|
1692
|
+
const adjacentContrast =
|
|
1693
|
+
atomicReplacement && atomicReplacement !== factualPending.value;
|
|
1694
|
+
const eligible =
|
|
1695
|
+
direct ||
|
|
1696
|
+
historicalDirect ||
|
|
1697
|
+
adjacentContrast ||
|
|
1698
|
+
(ended && !question && correctionFactualContrastAtStart(payload)) ||
|
|
1699
|
+
(ended &&
|
|
1700
|
+
!negatedValue &&
|
|
1701
|
+
explicitCorrection &&
|
|
1702
|
+
correctionExplicitCorrectionPayload(payload));
|
|
1703
|
+
if (!eligible) {
|
|
1704
|
+
// Carry only a complete, first-party technical atom to the immediately
|
|
1705
|
+
// adjacent sentence ("Not v1. v2."). Invisible content breaks this link.
|
|
1706
|
+
if (
|
|
1707
|
+
ended &&
|
|
1708
|
+
!question &&
|
|
1709
|
+
!provenance.historical &&
|
|
1710
|
+
index === clauses.length - 1
|
|
1711
|
+
) {
|
|
1712
|
+
if (negatedValue) {
|
|
1713
|
+
decision.factualPending = {
|
|
1714
|
+
value: negatedValue,
|
|
1715
|
+
text: payload,
|
|
1716
|
+
start:
|
|
1717
|
+
decision.windowOffset +
|
|
1718
|
+
offsets[clause.offset + provenance.offset],
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
continue;
|
|
1723
|
+
}
|
|
1724
|
+
const candidateStart =
|
|
1725
|
+
decision.windowOffset +
|
|
1726
|
+
offsets[clause.offset + provenance.offset + (direct?.index ?? 0)];
|
|
1727
|
+
if (provenance.historical) {
|
|
1728
|
+
if (correctionIsHistoricalResolution(payload)) continue;
|
|
1729
|
+
decision.historicalPending = true;
|
|
1730
|
+
decision.historicalText = payload;
|
|
1731
|
+
decision.historicalStart = candidateStart;
|
|
1732
|
+
newHistoricalClaim = true;
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
if (decision.historicalPending) {
|
|
1736
|
+
correctionRecordCandidate(
|
|
1737
|
+
decision.historicalText,
|
|
1738
|
+
decision,
|
|
1739
|
+
decision.historicalStart,
|
|
1740
|
+
);
|
|
1741
|
+
decision.historicalPending = false;
|
|
1742
|
+
}
|
|
1743
|
+
correctionRecordCandidate(
|
|
1744
|
+
adjacentContrast ? `${factualPending.text} ${payload}` : payload,
|
|
1745
|
+
decision,
|
|
1746
|
+
adjacentContrast ? factualPending.start : candidateStart,
|
|
1747
|
+
);
|
|
1748
|
+
}
|
|
1749
|
+
if (pendingBefore && decision.historicalPending && !newHistoricalClaim) {
|
|
1750
|
+
correctionRecordCandidate(
|
|
1751
|
+
decision.historicalText,
|
|
1752
|
+
decision,
|
|
1753
|
+
decision.historicalStart,
|
|
1754
|
+
);
|
|
1755
|
+
decision.historicalPending = false;
|
|
1756
|
+
}
|
|
1757
|
+
decision.localSuppression = ended ? false : localSuppression;
|
|
1758
|
+
decision.sourcePayloadPending = ended ? false : sourcePayloadPending;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function correctionNormalizedStatement(statement) {
|
|
1762
|
+
const units = [];
|
|
1763
|
+
const offsets = [];
|
|
1764
|
+
let whitespace = false;
|
|
1765
|
+
for (let index = 0; index < statement.length; index += 1) {
|
|
1766
|
+
const space = /\s/u.test(statement[index]);
|
|
1767
|
+
if (!space || !whitespace) {
|
|
1768
|
+
units.push(space ? " " : statement[index]);
|
|
1769
|
+
offsets.push(index);
|
|
1770
|
+
}
|
|
1771
|
+
whitespace = space;
|
|
1772
|
+
}
|
|
1773
|
+
return { text: units.join(""), offsets };
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function correctionRecordCandidate(text, decision, start) {
|
|
1777
|
+
// Overlapping windows can report the same clause twice. Retractions must
|
|
1778
|
+
// not leave a duplicate copy eligible, or resurrect an already withdrawn one.
|
|
1779
|
+
if (start <= decision.lastCandidateStart) return;
|
|
1780
|
+
decision.priorCandidate = decision.candidate;
|
|
1781
|
+
decision.candidate = true;
|
|
1782
|
+
decision.lastCandidate = text;
|
|
1783
|
+
decision.lastCandidateStart = start;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function correctionStatementClauses(text) {
|
|
1787
|
+
const clauses = [];
|
|
1788
|
+
// A replacement's comma or "but" is part of one factual assertion, not an
|
|
1789
|
+
// independent authorial pivot out of a quoted/reported scope.
|
|
1790
|
+
const contrasts = correctionFactualMatches(text)
|
|
1791
|
+
.map((match) => correctionFactualConnector(match, text))
|
|
1792
|
+
.filter(Boolean);
|
|
1793
|
+
let contrastIndex = 0;
|
|
1794
|
+
// A pivot's optional comma belongs to the pivot, not a new clause boundary.
|
|
1795
|
+
const separators =
|
|
1796
|
+
/[,;]\s*|\b(?:but|however|yet)\b\s*,?\s*|\band\s+(?=(?:i|we)\s+(?:(?:explicitly|formally|personally)\s+)?(?:agree|disagree|concur|adopt|endorse|confirm)\b)|\s*:\s+|\s+[\u2014-]\s+/giu;
|
|
1797
|
+
let start = 0;
|
|
1798
|
+
let boundary = "statement";
|
|
1799
|
+
for (const match of text.matchAll(separators)) {
|
|
1800
|
+
while (
|
|
1801
|
+
contrasts[contrastIndex] &&
|
|
1802
|
+
match.index >= contrasts[contrastIndex][1]
|
|
1803
|
+
)
|
|
1804
|
+
contrastIndex += 1;
|
|
1805
|
+
const contrast = contrasts[contrastIndex];
|
|
1806
|
+
if (contrast && match.index >= contrast[0]) continue;
|
|
1807
|
+
const retractionBoundary = /^\s*[:\u2014-]/u.test(match[0]);
|
|
1808
|
+
if (
|
|
1809
|
+
retractionBoundary &&
|
|
1810
|
+
!correctionRetractionAfterLeadIn(
|
|
1811
|
+
text.slice(
|
|
1812
|
+
match.index + match[0].length,
|
|
1813
|
+
match.index + match[0].length + 512,
|
|
1814
|
+
),
|
|
1815
|
+
)
|
|
1816
|
+
)
|
|
1817
|
+
continue;
|
|
1818
|
+
const retainedSeparator = retractionBoundary
|
|
1819
|
+
? match[0].length
|
|
1820
|
+
: Number(match[0].startsWith(","));
|
|
1821
|
+
const raw = text.slice(start, match.index + retainedSeparator);
|
|
1822
|
+
const clause = raw.trim();
|
|
1823
|
+
if (clause)
|
|
1824
|
+
clauses.push({
|
|
1825
|
+
text: clause,
|
|
1826
|
+
boundary,
|
|
1827
|
+
offset: start + raw.length - raw.trimStart().length,
|
|
1828
|
+
});
|
|
1829
|
+
boundary = retractionBoundary
|
|
1830
|
+
? "retraction"
|
|
1831
|
+
: match[0].startsWith(",")
|
|
1832
|
+
? "comma"
|
|
1833
|
+
: match[0].startsWith(";")
|
|
1834
|
+
? "semicolon"
|
|
1835
|
+
: /^and\b/iu.test(match[0])
|
|
1836
|
+
? "conjunction"
|
|
1837
|
+
: "adversative";
|
|
1838
|
+
start = match.index + match[0].length;
|
|
1839
|
+
}
|
|
1840
|
+
const raw = text.slice(start);
|
|
1841
|
+
const tail = raw.trim();
|
|
1842
|
+
if (tail)
|
|
1843
|
+
clauses.push({
|
|
1844
|
+
text: tail,
|
|
1845
|
+
boundary,
|
|
1846
|
+
offset: start + raw.length - raw.trimStart().length,
|
|
1847
|
+
});
|
|
1848
|
+
return clauses;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function correctionFactualContrastAtStart(text) {
|
|
1852
|
+
return correctionFactualMatches(text).some(
|
|
1853
|
+
(match) =>
|
|
1854
|
+
match.index === 0 && Boolean(correctionFactualConnector(match, text)),
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
function correctionFactualMatches(text) {
|
|
1859
|
+
const matches = [];
|
|
1860
|
+
if (!/\b(?:not|rather\s+than|instead\s+of)\s/iu.test(text)) return matches;
|
|
1861
|
+
// Inspect a fixed-size neighborhood of each connector independently. A
|
|
1862
|
+
// neutral lead-in or rejected negation cannot swallow a later "not X but Y".
|
|
1863
|
+
for (const connector of text.matchAll(CORRECTION_FACTUAL_CONNECTORS)) {
|
|
1864
|
+
const positive = Boolean(connector.groups.positiveConnector);
|
|
1865
|
+
const leftStart = Math.max(0, connector.index - 2048);
|
|
1866
|
+
const left = text
|
|
1867
|
+
.slice(leftStart, connector.index)
|
|
1868
|
+
.match(
|
|
1869
|
+
positive ? CORRECTION_FACTUAL_LEFT : CORRECTION_FACTUAL_NEGATIVE_LEFT,
|
|
1870
|
+
);
|
|
1871
|
+
const end = connector.index + connector[0].length;
|
|
1872
|
+
const right = text.slice(end, end + 512).match(CORRECTION_FACTUAL_RIGHT);
|
|
1873
|
+
if (!left || !right) continue;
|
|
1874
|
+
const index = leftStart + left.index;
|
|
1875
|
+
matches.push({
|
|
1876
|
+
0: text.slice(index, end + right[0].length),
|
|
1877
|
+
index,
|
|
1878
|
+
groups: {
|
|
1879
|
+
...connector.groups,
|
|
1880
|
+
assertion: left.groups?.assertion,
|
|
1881
|
+
replacement: right[0],
|
|
1882
|
+
},
|
|
1883
|
+
indices: connector.indices,
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
return matches;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function correctionFactualConnector(match, text) {
|
|
1890
|
+
const connector =
|
|
1891
|
+
match.indices.groups.positiveConnector ??
|
|
1892
|
+
match.indices.groups.negativeConnector;
|
|
1893
|
+
if (
|
|
1894
|
+
match.groups.assertion &&
|
|
1895
|
+
(!correctionWithoutNeutralLeadIn(`${match.groups.assertion},`).text ||
|
|
1896
|
+
(/^\s*,?\s*not\b/iu.test(match.groups.positiveConnector) &&
|
|
1897
|
+
(CORRECTION_FACTUAL_NEGATED_AUXILIARY.test(match.groups.assertion) ||
|
|
1898
|
+
(!match.groups.positiveConnector.includes(",") &&
|
|
1899
|
+
CORRECTION_FACTUAL_UNARY_SUBJECT.test(match.groups.assertion)))))
|
|
1900
|
+
)
|
|
1901
|
+
return null;
|
|
1902
|
+
const replacement = correctionWithoutNeutralLeadIn(
|
|
1903
|
+
text.slice(connector[1], connector[1] + 512),
|
|
1904
|
+
).text;
|
|
1905
|
+
const valueEnd = connector[1] + match.groups.replacement.length;
|
|
1906
|
+
// A following clause delimiter ends a value ("XML, which ..."), not a
|
|
1907
|
+
// speaker label or an instruction owned by the preceding value.
|
|
1908
|
+
// Colons retain their prefix chain so nested report/transform wrappers use
|
|
1909
|
+
// the same provenance rules as a whole statement, without factual recursion.
|
|
1910
|
+
const context = /^\s*(?:[,;]|(?:but|however|yet)\b)/iu.test(
|
|
1911
|
+
text.slice(valueEnd, valueEnd + 16),
|
|
1912
|
+
)
|
|
1913
|
+
? correctionWithoutNeutralLeadIn(match.groups.replacement).text
|
|
1914
|
+
: replacement;
|
|
1915
|
+
const provenance = correctionClauseProvenance(
|
|
1916
|
+
context,
|
|
1917
|
+
{ semanticCodeUnits: 0 },
|
|
1918
|
+
false,
|
|
1919
|
+
);
|
|
1920
|
+
return provenance.source ||
|
|
1921
|
+
provenance.suppressed ||
|
|
1922
|
+
provenance.retraction ||
|
|
1923
|
+
provenance.adoption ||
|
|
1924
|
+
provenance.explicitCorrection ||
|
|
1925
|
+
provenance.historical ||
|
|
1926
|
+
!CORRECTION_FACTUAL_RIGHT.test(provenance.payload) ||
|
|
1927
|
+
correctionHasDurableOptOut(provenance.payload) ||
|
|
1928
|
+
/^(?:thanks|thank\s+you|okay|ok|sure|fine)(?:[.!]|$)/iu.test(
|
|
1929
|
+
provenance.payload,
|
|
1930
|
+
)
|
|
1931
|
+
? null
|
|
1932
|
+
: connector;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
function correctionAtomicFactualValue(text) {
|
|
1936
|
+
const value = text.replace(/[.!]$/u, "");
|
|
1937
|
+
return CORRECTION_FACTUAL_ATOM.test(value) &&
|
|
1938
|
+
/[\p{L}\p{N}]/u.test(value) &&
|
|
1939
|
+
/[\p{N}_/=-]|\.[\p{L}\p{N}]/u.test(value)
|
|
1940
|
+
? value
|
|
1941
|
+
: null;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function correctionNegatedFactualValue(text) {
|
|
1945
|
+
const match = text.match(/^not\s+(.{1,65})$/iu);
|
|
1946
|
+
return match ? correctionAtomicFactualValue(match[1]) : null;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
function correctionClauseProvenance(text, work, allowFactualFallback = true) {
|
|
1950
|
+
let cursor = 0;
|
|
1951
|
+
let adoption = false;
|
|
1952
|
+
let historical = false;
|
|
1953
|
+
let explicitCorrection = false;
|
|
1954
|
+
let surface = true;
|
|
1955
|
+
while (cursor < text.length) {
|
|
1956
|
+
// Prefix inspection is bounded even for thousands of nested headings.
|
|
1957
|
+
const head = text.slice(cursor, cursor + 512);
|
|
1958
|
+
work.semanticCodeUnits += head.length;
|
|
1959
|
+
const lead = correctionWithoutNeutralLeadIn(head);
|
|
1960
|
+
if (lead.offset) {
|
|
1961
|
+
cursor += lead.offset;
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
const retraction = correctionRetractionControl(head);
|
|
1965
|
+
if (retraction) {
|
|
1966
|
+
if (
|
|
1967
|
+
retraction.kind === "reference" &&
|
|
1968
|
+
retraction.end &&
|
|
1969
|
+
!correctionHasDurableOptOut(head)
|
|
1970
|
+
) {
|
|
1971
|
+
cursor += retraction.end;
|
|
1972
|
+
surface = false;
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
return { payload: text.slice(cursor), retraction, adoption };
|
|
1976
|
+
}
|
|
1977
|
+
const firstPartyLabel = correctionFirstPartyLabelAtStart(head);
|
|
1978
|
+
const wrapper = correctionWrapperAtStart(
|
|
1979
|
+
head,
|
|
1980
|
+
Boolean(firstPartyLabel),
|
|
1981
|
+
allowFactualFallback,
|
|
1982
|
+
);
|
|
1983
|
+
if (wrapper) {
|
|
1984
|
+
const remainder = text.slice(cursor + wrapper.end).trim();
|
|
1985
|
+
work.semanticCodeUnits += remainder.length;
|
|
1986
|
+
return {
|
|
1987
|
+
source: wrapper.kind,
|
|
1988
|
+
payload: "",
|
|
1989
|
+
sourcePayloadPending:
|
|
1990
|
+
Boolean(remainder) && !correctionWithoutNeutralLeadIn(remainder).text,
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
if (
|
|
1994
|
+
/^(?:if|assuming|suppose|supposing|provided|providing)\b|^(?:it\s+is\s+not\s+true\s+that|(?:i|we)\s+(?:never|did\s+not|didn't)\s+(?:say|said|claim|claimed))\b/iu.test(
|
|
1995
|
+
head,
|
|
1996
|
+
)
|
|
1997
|
+
) {
|
|
1998
|
+
return { suppressed: true, payload: "" };
|
|
1999
|
+
}
|
|
2000
|
+
const pivot = correctionFirstPartyPivot(head);
|
|
2001
|
+
if (pivot?.index === 0) {
|
|
2002
|
+
adoption ||= surface;
|
|
2003
|
+
explicitCorrection ||= pivot.kind === "correction_label";
|
|
2004
|
+
cursor += pivot.end;
|
|
2005
|
+
const delimiter = text
|
|
2006
|
+
.slice(cursor, cursor + 8)
|
|
2007
|
+
.match(/^\s*[:\u2014-]?\s*/u);
|
|
2008
|
+
cursor += delimiter[0].length;
|
|
2009
|
+
surface = false;
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
if (correctionHasDurableOptOut(head)) {
|
|
2013
|
+
return { payload: text.slice(cursor), adoption };
|
|
2014
|
+
}
|
|
2015
|
+
const historicalPrefix = correctionHistoricalPrefix(head);
|
|
2016
|
+
if (historicalPrefix) {
|
|
2017
|
+
historical = true;
|
|
2018
|
+
cursor += historicalPrefix[0].length;
|
|
2019
|
+
surface = false;
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
const heading =
|
|
2023
|
+
firstPartyLabel ||
|
|
2024
|
+
head.match(
|
|
2025
|
+
/^[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}(?:\s+[\p{L}\p{N}][\p{L}\p{M}\p{N}'-]{0,47}){0,7}(?:\s*:\s*|\s+[\u2014-]\s*)/u,
|
|
2026
|
+
);
|
|
2027
|
+
if (
|
|
2028
|
+
heading &&
|
|
2029
|
+
!correctionFirstDirectMatch(heading[0]) &&
|
|
2030
|
+
!(
|
|
2031
|
+
allowFactualFallback &&
|
|
2032
|
+
/^not\s+/iu.test(head) &&
|
|
2033
|
+
(correctionFactualContrastAtStart(head) ||
|
|
2034
|
+
correctionNegatedFactualValue(head))
|
|
2035
|
+
)
|
|
2036
|
+
) {
|
|
2037
|
+
explicitCorrection ||= /^correction\s*[:\u2014-]/iu.test(heading[0]);
|
|
2038
|
+
cursor += heading[0].length;
|
|
2039
|
+
surface = false;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
break;
|
|
2043
|
+
}
|
|
2044
|
+
return {
|
|
2045
|
+
payload: text.slice(cursor),
|
|
2046
|
+
offset: cursor,
|
|
2047
|
+
adoption,
|
|
2048
|
+
historical,
|
|
2049
|
+
explicitCorrection,
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
function correctionPostfixAttribution(text, atStart = false) {
|
|
2054
|
+
const match = atStart
|
|
2055
|
+
? text.match(/^(?:according\s+to|per|as)\s+/iu)
|
|
2056
|
+
: text.match(/\s+(?:according\s+to|per)\s+/iu);
|
|
2057
|
+
if (!match) return false;
|
|
2058
|
+
const suffix = text.slice(match.index).trim();
|
|
2059
|
+
return Boolean(
|
|
2060
|
+
correctionWrapperAtStart(
|
|
2061
|
+
suffix,
|
|
2062
|
+
Boolean(correctionFirstPartyLabelAtStart(suffix)),
|
|
2063
|
+
),
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function correctionFirstDirectMatch(text) {
|
|
2068
|
+
let first = null;
|
|
2069
|
+
for (const pattern of CORRECTION_DIRECT_PATTERNS) {
|
|
2070
|
+
const match = text.match(pattern);
|
|
2071
|
+
if (!match || match.index === undefined) continue;
|
|
2072
|
+
if (!first || match.index < first.index) {
|
|
2073
|
+
first = { index: match.index, end: match.index + match[0].length };
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return first;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
function correctionFirstPartyPivot(text, minimumIndex = 0) {
|
|
2080
|
+
const sliced = text.slice(minimumIndex);
|
|
2081
|
+
const match = sliced.match(CORRECTION_FIRST_PARTY_PIVOT);
|
|
2082
|
+
if (!match || match.index === undefined) return null;
|
|
2083
|
+
const index = minimumIndex + match.index;
|
|
2084
|
+
const value = match[0];
|
|
2085
|
+
return {
|
|
2086
|
+
index,
|
|
2087
|
+
end: index + value.length,
|
|
2088
|
+
kind: /\bcorrection\b/iu.test(value) ? "correction_label" : "adoption",
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function correctionWrapperAtStart(
|
|
2093
|
+
text,
|
|
2094
|
+
firstPartyLabel,
|
|
2095
|
+
allowFactualFallback = true,
|
|
2096
|
+
) {
|
|
2097
|
+
if (firstPartyLabel) return null;
|
|
2098
|
+
const lead = correctionWithoutNeutralLeadIn(text);
|
|
2099
|
+
if (correctionFirstPartyPivot(lead.text)?.index === 0) return null;
|
|
2100
|
+
const reference = correctionReferenceHeadingAtStart(lead.text);
|
|
2101
|
+
if (reference) {
|
|
2102
|
+
return {
|
|
2103
|
+
index: lead.offset,
|
|
2104
|
+
end: lead.offset + reference.label[0].length,
|
|
2105
|
+
kind: reference.kind,
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
for (const pattern of CORRECTION_SOURCE_PREFIXES) {
|
|
2109
|
+
const match = lead.text.match(pattern);
|
|
2110
|
+
if (!match || match.index !== 0) continue;
|
|
2111
|
+
return {
|
|
2112
|
+
index: lead.offset,
|
|
2113
|
+
end: lead.offset + match[0].length,
|
|
2114
|
+
kind: "reported",
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
const report = correctionSpeechReport(lead.text);
|
|
2118
|
+
const direct = report && correctionFirstDirectMatch(lead.text);
|
|
2119
|
+
if (
|
|
2120
|
+
report &&
|
|
2121
|
+
!correctionReportingSubjectIsFirstParty(report.groups.subject.trim()) &&
|
|
2122
|
+
!(
|
|
2123
|
+
direct &&
|
|
2124
|
+
direct.index < report.groups.subject.length &&
|
|
2125
|
+
/\b(?:i|we|you)\b/iu.test(report.groups.subject)
|
|
2126
|
+
)
|
|
2127
|
+
) {
|
|
2128
|
+
return {
|
|
2129
|
+
index: lead.offset,
|
|
2130
|
+
end: lead.offset + report[0].length,
|
|
2131
|
+
kind: "reported",
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
for (const pattern of CORRECTION_TRANSFORMATION_PREFIXES) {
|
|
2135
|
+
const match = lead.text.match(pattern);
|
|
2136
|
+
if (match) {
|
|
2137
|
+
return {
|
|
2138
|
+
index: lead.offset,
|
|
2139
|
+
end: lead.offset + match[0].length,
|
|
2140
|
+
kind: "transformation",
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
const attribution = lead.text.match(CORRECTION_ATTRIBUTED_HEADING);
|
|
2145
|
+
if (attribution) {
|
|
2146
|
+
return {
|
|
2147
|
+
index: lead.offset,
|
|
2148
|
+
end: lead.offset + attribution[0].length,
|
|
2149
|
+
kind: "reported",
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
const speaker = correctionNamedThirdPartyLabelAtStart(lead.text);
|
|
2153
|
+
if (
|
|
2154
|
+
speaker &&
|
|
2155
|
+
allowFactualFallback &&
|
|
2156
|
+
(correctionNegatedFactualValue(lead.text) ||
|
|
2157
|
+
correctionFactualMatches(lead.text).some(
|
|
2158
|
+
(match) =>
|
|
2159
|
+
match.index === 0 &&
|
|
2160
|
+
(match[0].length === lead.text.length ||
|
|
2161
|
+
/^\s*[,;]/u.test(lead.text.slice(match[0].length))) &&
|
|
2162
|
+
Boolean(correctionFactualConnector(match, lead.text)),
|
|
2163
|
+
))
|
|
2164
|
+
)
|
|
2165
|
+
return null;
|
|
2166
|
+
if (speaker) {
|
|
2167
|
+
return {
|
|
2168
|
+
index: lead.offset,
|
|
2169
|
+
end: lead.offset + speaker[0].length,
|
|
2170
|
+
kind: "reported",
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
return null;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
function correctionSpeechReport(text) {
|
|
2177
|
+
const report = text.match(CORRECTION_SPEECH_REPORT);
|
|
2178
|
+
if (!report) return null;
|
|
2179
|
+
const subject = report.groups.subject.trim();
|
|
2180
|
+
const verb = report.groups.verb.split(" ")[0];
|
|
2181
|
+
const past = /(?:ed)$|^(?:said|wrote|told|thought|read)$/iu.test(verb);
|
|
2182
|
+
const singularPresent = /s$/iu.test(verb);
|
|
2183
|
+
// "That flag defaults ..." names a flag, not a speaker named "That".
|
|
2184
|
+
// Retain a later real report: "That flag says ..." is still attributed.
|
|
2185
|
+
const requiresNoun =
|
|
2186
|
+
/^(?:the|a|an)$/iu.test(subject) ||
|
|
2187
|
+
(!past &&
|
|
2188
|
+
((/^(?:this|that)$/iu.test(subject) && !singularPresent) ||
|
|
2189
|
+
(/^(?:these|those)$/iu.test(subject) && singularPresent)));
|
|
2190
|
+
return requiresNoun
|
|
2191
|
+
? text.match(CORRECTION_SPEECH_NOUN_PHRASE_REPORT)
|
|
2192
|
+
: report;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function correctionReportingSubjectIsFirstParty(subject) {
|
|
2196
|
+
// A bare possessive before a verb-like topic ("my state-machine mismatch")
|
|
2197
|
+
// is ownership, not a third-party speaker. Keep speech punctuation intact.
|
|
2198
|
+
return /^(?:my|our)$|^(?:(?:as|like)\s+)?(?:i|we)(?:\s+(?:already|earlier|previously|never|did\s+not|didn't))*$|^(?:my|our)\s+(?:(?:own|agreed|current|latest|implementation|test|internal|security)\s+){0,3}(?:runbook|plan|notes?|requirements?|review|assessment|finding|message|feedback)$|^the\s+(?:plan|requirements?|review|notes?)\s+(?:i|we)\s+(?:wrote|authored|created)$/iu.test(
|
|
2199
|
+
subject,
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
function correctionFirstPartyLabelAtStart(text) {
|
|
2204
|
+
const pivot = correctionFirstPartyPivot(text);
|
|
2205
|
+
if (pivot?.kind === "correction_label" && pivot.index === 0) return null;
|
|
2206
|
+
const explicit =
|
|
2207
|
+
text.match(CORRECTION_FIRST_PARTY_LABEL) ??
|
|
2208
|
+
text.match(CORRECTION_FIRST_PARTY_POSSESSIVE_LABEL);
|
|
2209
|
+
if (explicit) return explicit;
|
|
2210
|
+
for (const pattern of [
|
|
2211
|
+
...CORRECTION_SOURCE_PREFIXES,
|
|
2212
|
+
...CORRECTION_TRANSFORMATION_PREFIXES,
|
|
2213
|
+
]) {
|
|
2214
|
+
const match = text.match(pattern);
|
|
2215
|
+
if (match?.index === 0) return null;
|
|
2216
|
+
}
|
|
2217
|
+
const reference = correctionReferenceHeadingAtStart(text);
|
|
2218
|
+
if (reference?.kind === "first_party") return reference.label;
|
|
2219
|
+
const attribution = text.match(CORRECTION_ATTRIBUTED_HEADING);
|
|
2220
|
+
if (
|
|
2221
|
+
attribution &&
|
|
2222
|
+
/^(?:me|us|myself|ourselves)$/iu.test(attribution.groups.owner)
|
|
2223
|
+
)
|
|
2224
|
+
return attribution;
|
|
2225
|
+
const authorial = text.match(CORRECTION_AUTHORIAL_LABEL);
|
|
2226
|
+
if (authorial) return authorial;
|
|
2227
|
+
if (correctionNamedThirdPartyLabelAtStart(text)) return null;
|
|
2228
|
+
return null;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
function correctionReferenceHeadingAtStart(text) {
|
|
2232
|
+
const frame = text.match(CORRECTION_REFERENCE_FRAME);
|
|
2233
|
+
if (!frame) return null;
|
|
2234
|
+
const remainder = text.slice(frame[0].length);
|
|
2235
|
+
const owned = remainder.match(CORRECTION_FIRST_PARTY_POSSESSIVE_LABEL);
|
|
2236
|
+
const label = owned ?? remainder.match(CORRECTION_EXTERNAL_HEADING);
|
|
2237
|
+
if (!label) return null;
|
|
2238
|
+
const intent = text.replace(/^to\s+/iu, "");
|
|
2239
|
+
// In a reference heading, "note" names the owned message; it is not a
|
|
2240
|
+
// reporting verb. Transforming or quoting that message still stays inert.
|
|
2241
|
+
// A withdrawn reference is also inert, but a later explicit adoption may
|
|
2242
|
+
// establish a new correction; withdrawing it is not a global capture opt-out.
|
|
2243
|
+
return {
|
|
2244
|
+
label: [frame[0] + label[0]],
|
|
2245
|
+
kind: CORRECTION_WITHDRAWN_REFERENCE.test(intent)
|
|
2246
|
+
? "withdrawn"
|
|
2247
|
+
: CORRECTION_TRANSFORMATION_PREFIXES.some((pattern) =>
|
|
2248
|
+
pattern.test(intent),
|
|
2249
|
+
)
|
|
2250
|
+
? "transformation"
|
|
2251
|
+
: owned
|
|
2252
|
+
? "first_party"
|
|
2253
|
+
: "reported",
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function correctionNamedThirdPartyLabelAtStart(text) {
|
|
2258
|
+
const label = text.match(
|
|
2259
|
+
/^(?<label>[\p{Lu}][\p{Ll}\p{M}'-]{1,31}(?:\s+[\p{Lu}][\p{Ll}\p{M}'-]{1,31}){0,2}|[A-Z]{2,12}(?:\s+(?:auditor|lead|manager|reviewer))?|(?:Design|Engineering|Legal|Product|Release|Security|Support|SRE)\s+(?:auditor|lead|manager|reviewer))(?:\s*[:,]\s*|\s+[\u2014-]\s*)/u,
|
|
2260
|
+
);
|
|
2261
|
+
if (!label) return null;
|
|
2262
|
+
const topic = label.groups.label.match(
|
|
2263
|
+
/^(?:(?:my|our)\s+)?(?<noun>[\p{L}\p{M}-]+)$/iu,
|
|
2264
|
+
);
|
|
2265
|
+
// A review-topic noun is not a person merely because its heading is
|
|
2266
|
+
// capitalized. Explicit roles/owners are resolved before this fallback;
|
|
2267
|
+
// multiword names and unknown labels keep their conservative treatment.
|
|
2268
|
+
return topic &&
|
|
2269
|
+
(CORRECTION_REVIEW_TOPIC_NOUN.test(topic.groups.noun) ||
|
|
2270
|
+
CORRECTION_TECHNICAL_TOPIC_NOUN.test(topic.groups.noun))
|
|
2271
|
+
? null
|
|
2272
|
+
: label;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
function correctionWithoutNeutralLeadIn(text) {
|
|
2276
|
+
const match = text.match(
|
|
2277
|
+
/^(?:(?:#{1,6}|[-+*]|\d+[.)])\s+)?(?:(?:actually|also|no(?=\s*[:,])|sorry|right|look|hmm|wait|separately|however|yet|but|once\s+more|to\s+be\s+clear|for\s+(?:clarity|context|practical\s+purposes)|quick\s+note)\b\s*[:,]?\s*)*/iu,
|
|
2278
|
+
);
|
|
2279
|
+
return {
|
|
2280
|
+
offset: match[0].length,
|
|
2281
|
+
text: text.slice(match[0].length),
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function correctionRetractionAfterLeadIn(text) {
|
|
2286
|
+
return correctionRetractionControl(correctionWithoutNeutralLeadIn(text).text);
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
function correctionHistoricalPrefix(text) {
|
|
2290
|
+
return text.match(
|
|
2291
|
+
/^(?:(?:earlier|previously|at\s+the\s+time)\b\s*|my\s+earlier\s+view\s+(?:was\s+)?|i\s+(?:(?:previously|earlier)\s+)?(?:thought|believed|said|claimed)\b\s*)/iu,
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
function correctionIsHistoricalResolution(text) {
|
|
2296
|
+
return /\b(?:after\s+(?:reviewing|checking|reconsidering)|on\s+review|the\s+result\s+shows|i\s+(?:retract|withdraw)|i\s+(?:take|took)\s+(?:that|it)\s+back|that\s+(?:(?:request|view|statement)\s+(?:no\s+longer\s+applies|was\s+mistaken)|was\s+mistaken)|i\s+(?:was|am)\s+mistaken|it\s+(?:is|was)\s+(?:right|correct)|you\s+did\s+not)\b/iu.test(
|
|
2297
|
+
text,
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function correctionExplicitCorrectionPayload(payload) {
|
|
2302
|
+
if (
|
|
2303
|
+
!payload ||
|
|
2304
|
+
/^(?:if|assuming|suppose|supposing|provided|providing)\b/iu.test(payload)
|
|
2305
|
+
) {
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
return [...payload.matchAll(/[\p{L}\p{N}_-]+/gu)].length >= 3;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
function correctionRetractionControl(text) {
|
|
2312
|
+
const cancel = text.match(
|
|
2313
|
+
/^(?:please\s+)?(?:never[\s-]*mind|nevermind|disregard|ignore|scratch)\b\s*(?<tail>.*)$/iu,
|
|
2314
|
+
);
|
|
2315
|
+
if (cancel) {
|
|
2316
|
+
const target = cancel.groups.tail.trim();
|
|
2317
|
+
const current = target.match(
|
|
2318
|
+
/^(?:this|that|it|(?:my|our)\s+last\s+(?:note|message|point|comment|correction|feedback))(?=\s|[:,;.!?]|$)\s*/iu,
|
|
2319
|
+
);
|
|
2320
|
+
const tail = target.slice(current?.[0].length ?? 0);
|
|
2321
|
+
if (
|
|
2322
|
+
!tail ||
|
|
2323
|
+
/^[:,\u2014;.!?-]/u.test(tail) ||
|
|
2324
|
+
/^(?:please|let(?:'s|\s+us)\s+move\s+on)\b/iu.test(tail)
|
|
2325
|
+
) {
|
|
2326
|
+
return { kind: "turn" };
|
|
2327
|
+
}
|
|
2328
|
+
const boundary = target.match(/\s*:\s*|\s+[\u2014-]\s*/u);
|
|
2329
|
+
if (boundary) {
|
|
2330
|
+
return {
|
|
2331
|
+
kind: "reference",
|
|
2332
|
+
end:
|
|
2333
|
+
cancel[0].length -
|
|
2334
|
+
cancel.groups.tail.length +
|
|
2335
|
+
boundary.index +
|
|
2336
|
+
boundary[0].length,
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
return { kind: "reference" };
|
|
2340
|
+
}
|
|
2341
|
+
if (
|
|
2342
|
+
/^(?:i|we)\s+(?:(?:retract|withdraw)\s+(?:that|this|(?:my|our|the)\s+(?:last|current))\s+(?:statement|claim|correction|note)|(?:take|took)\s+(?:that|it)\s+back)\b/iu.test(
|
|
2343
|
+
text,
|
|
2344
|
+
)
|
|
2345
|
+
) {
|
|
2346
|
+
return { kind: "claim" };
|
|
2347
|
+
}
|
|
2348
|
+
const named = text.match(
|
|
2349
|
+
/^(?:i|we)\s+(?:retract|withdraw)\s+(?:only\s+)?the\s+(?<target>[\p{L}\p{N}-]{1,48})\s+claim\b/iu,
|
|
2350
|
+
);
|
|
2351
|
+
if (named)
|
|
2352
|
+
return {
|
|
2353
|
+
kind: "claim",
|
|
2354
|
+
target: named.groups.target.toLocaleLowerCase("en-US"),
|
|
2355
|
+
};
|
|
2356
|
+
if (
|
|
2357
|
+
/^(?:please\s+)?(?:i|we)\s+(?:retract|withdraw)\s+(?:the|my|our)\s+(?:old|earlier|previous|prior)\b/iu.test(
|
|
2358
|
+
text,
|
|
2359
|
+
)
|
|
2360
|
+
) {
|
|
2361
|
+
return { kind: "reference" };
|
|
2362
|
+
}
|
|
2363
|
+
return null;
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
function correctionHasDurableOptOut(text) {
|
|
2367
|
+
if (
|
|
2368
|
+
/(?:^|[.!?]\s+)(?:please\s+)?forget\s+(?:all\s+of\s+)?(?:this|that|it|everything|the\s+(?:conversation|prompt|feedback|correction|lesson))\b/iu.test(
|
|
2369
|
+
text,
|
|
2370
|
+
)
|
|
2371
|
+
) {
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
if (
|
|
2375
|
+
/(?:^|[.!?]\s+)(?:never[\s-]*mind|nevermind)\s+(?:the\s+)?(?:prior|previous|earlier)\s+(?:test|case|example|scenario)\s+(?:where|in\s+which)\b/iu.test(
|
|
2376
|
+
text,
|
|
2377
|
+
)
|
|
2378
|
+
) {
|
|
2379
|
+
return true;
|
|
2380
|
+
}
|
|
2381
|
+
if (
|
|
2382
|
+
/\b(?:never[\s-]*mind|nevermind)\s+(?:about\s+)?(?:capture|remembrance|this\s+lesson|the\s+lesson|memory|learning)\b/iu.test(
|
|
2383
|
+
text,
|
|
2384
|
+
)
|
|
2385
|
+
) {
|
|
2386
|
+
return true;
|
|
2387
|
+
}
|
|
2388
|
+
if (
|
|
2389
|
+
/\b(?:not\s+(?:a\s+)?correction|(?:this\s+is\s+)?(?:a\s+)?one[-\s]off|one\s+time\s+only|(?:just\s+an?|hypothetical|illustrative|contrived)\s+(?:example|scenario)(?:\s+only)?|this\s+is\s+(?:just\s+)?an?\s+example|this\s+is\s+(?:only\s+)?for\s+this\s+task|for\s+this\s+task\s+only)\b/iu.test(
|
|
2390
|
+
text,
|
|
2391
|
+
)
|
|
2392
|
+
) {
|
|
2393
|
+
return true;
|
|
2394
|
+
}
|
|
2395
|
+
if (/\bthis\s+is\s+not\s+for\s+(?:remembrance|memory)\b/iu.test(text)) {
|
|
2396
|
+
return true;
|
|
2397
|
+
}
|
|
2398
|
+
if (
|
|
2399
|
+
/\b(?:i|we)\s+(?:do\s+not|don't|cannot|can't|will\s+not|won't)\s+consent\s+to\b.{0,128}\b(?:being\s+)?(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored|turn(?:ed)?\s+into)\b/iu.test(
|
|
2400
|
+
text,
|
|
2401
|
+
)
|
|
2402
|
+
) {
|
|
2403
|
+
return true;
|
|
2404
|
+
}
|
|
2405
|
+
if (
|
|
2406
|
+
/\b(?:i|we)\s+(?:do\s+not|don't)\s+(?:want|wish)\b.{0,96}\b(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored|turn(?:ed)?\s+into)\b.{0,64}\b(?:lesson|memory|guidance|remembrance|skill|learning\s+record)\b/iu.test(
|
|
2407
|
+
text,
|
|
2408
|
+
)
|
|
2409
|
+
) {
|
|
2410
|
+
return true;
|
|
2411
|
+
}
|
|
2412
|
+
if (
|
|
2413
|
+
/\b(?:i|we)\s+(?:do\s+not|don't)\s+(?:want|wish)\s+(?:this|that|it|anything|everything)\s+(?:to\s+be\s+)?(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored)\b/iu.test(
|
|
2414
|
+
text,
|
|
2415
|
+
)
|
|
2416
|
+
) {
|
|
2417
|
+
return true;
|
|
2418
|
+
}
|
|
2419
|
+
if (
|
|
2420
|
+
/\b(?:nothing|none)\s+(?:here|in\s+this\s+(?:conversation|prompt))?\s*(?:should|must|is\s+to)\s+(?:not\s+)?be\s+(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored)\b/iu.test(
|
|
2421
|
+
text,
|
|
2422
|
+
) ||
|
|
2423
|
+
/\beverything\s+(?:here|in\s+this\s+(?:conversation|prompt))?\s*(?:should|must|is\s+to)\s+not\s+be\s+(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored)\b/iu.test(
|
|
2424
|
+
text,
|
|
2425
|
+
)
|
|
2426
|
+
) {
|
|
2427
|
+
return true;
|
|
2428
|
+
}
|
|
2429
|
+
if (
|
|
2430
|
+
/\b(?:do\s+not|don't|never|no\s+need\s+to|please\s+do\s+not|please\s+don't)\s+(?:save|remember|record|learn(?:\s+from)?|capture|retain|preserve|derive|store|add)\b.{0,96}\b(?:this|that|it|anything|everything|conversation|prompt|feedback|correction|lesson|memory|guidance|remembrance|skill|learning\s+record)\b/iu.test(
|
|
2431
|
+
text,
|
|
2432
|
+
)
|
|
2433
|
+
) {
|
|
2434
|
+
return true;
|
|
2435
|
+
}
|
|
2436
|
+
if (
|
|
2437
|
+
/\b(?:do\s+not|don't|never|please\s+do\s+not|please\s+don't)\s+(?:turn|make|convert)\b.{0,96}\b(?:this|that|it|conversation|prompt|feedback|correction)\b.{0,96}\b(?:lesson|skill|memory|guidance|remembrance|learning\s+record)\b/iu.test(
|
|
2438
|
+
text,
|
|
2439
|
+
)
|
|
2440
|
+
) {
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
if (
|
|
2444
|
+
/\b(?:do\s+not|don't|never|please\s+do\s+not|please\s+don't)\s+(?:reuse|include)\b.{0,128}\b(?:conversation|prompt|feedback|correction|lesson|memory|guidance|remembrance|learning\s+record|future|later)\b/iu.test(
|
|
2445
|
+
text,
|
|
2446
|
+
)
|
|
2447
|
+
) {
|
|
2448
|
+
return true;
|
|
2449
|
+
}
|
|
2450
|
+
if (
|
|
2451
|
+
/\b(?:please\s+)?(?:exclude|omit)\b.{0,96}\b(?:this|that|it|conversation|prompt|feedback|correction|lesson)\b.{0,96}\b(?:future\s+guidance|memory|remembrance|learning\s+record|skill)\b/iu.test(
|
|
2452
|
+
text,
|
|
2453
|
+
)
|
|
2454
|
+
) {
|
|
2455
|
+
return true;
|
|
2456
|
+
}
|
|
2457
|
+
if (
|
|
2458
|
+
/\b(?:do\s+not|don't|never)\s+use\b.{0,96}\b(?:this|that|it|conversation|prompt|feedback|correction)\b.{0,96}\b(?:future|later|guidance|memory|lesson|skill|remembrance)\b/iu.test(
|
|
2459
|
+
text,
|
|
2460
|
+
)
|
|
2461
|
+
) {
|
|
2462
|
+
return true;
|
|
2463
|
+
}
|
|
2464
|
+
if (
|
|
2465
|
+
/\b(?:i|we)\s+(?:would\s+prefer|would\s+rather|prefer|would\s+like|want)\b.{0,96}\bnot\s+(?:to\s+)?(?:be\s+)?(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored|turn(?:ed)?\s+into)\b/iu.test(
|
|
2466
|
+
text,
|
|
2467
|
+
)
|
|
2468
|
+
) {
|
|
2469
|
+
return true;
|
|
2470
|
+
}
|
|
2471
|
+
if (
|
|
2472
|
+
/\b(?:i|we)\s+(?:would\s+prefer|would\s+rather|prefer|would\s+like|want)\s+(?:you\s+)?not\s+(?:to\s+)?(?:save|remember|record|learn|capture|retain|preserve|reuse|include|store)\s+(?:this|that|it|anything|everything)\b/iu.test(
|
|
2473
|
+
text,
|
|
2474
|
+
)
|
|
2475
|
+
) {
|
|
2476
|
+
return true;
|
|
2477
|
+
}
|
|
2478
|
+
if (
|
|
2479
|
+
/\b(?:i|we)(?:'d|\s+would)?\s+(?:prefer|rather)\b.{0,96}\b(?:not\b.{0,48}\b(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored|turn(?:ed)?\b.{0,32}\binto)|stay\s+out\s+of\s+(?:memory|guidance|remembrance))\b/iu.test(
|
|
2480
|
+
text,
|
|
2481
|
+
)
|
|
2482
|
+
) {
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
if (
|
|
2486
|
+
/\b(?:please\s+)?(?:refrain|abstain)\s+from\s+(?:saving|remembering|recording|learning|capturing|retaining|preserving|reusing|including|storing)\b/iu.test(
|
|
2487
|
+
text,
|
|
2488
|
+
)
|
|
2489
|
+
) {
|
|
2490
|
+
return true;
|
|
2491
|
+
}
|
|
2492
|
+
if (
|
|
2493
|
+
/\b(?:please\s+)?(?:keep|leave)\b.{0,64}\b(?:this|that|it|conversation|prompt|feedback|correction|lesson)\b.{0,64}\b(?:out\s+of|off|outside)\b.{0,64}\b(?:memory|guidance|remembrance|learning\s+record|skill)\b/iu.test(
|
|
2494
|
+
text,
|
|
2495
|
+
) ||
|
|
2496
|
+
/\b(?:please\s+)?keep\b.{0,64}\bfrom\s+being\s+(?:saved|remembered|recorded|learned|captured|retained|preserved|reused|included|stored)\b/iu.test(
|
|
2497
|
+
text,
|
|
2498
|
+
) ||
|
|
2499
|
+
/\b(?:please\s+)?(?:keep|leave)\b.{0,64}\b(?:out\s+of|off|outside)\b.{0,64}\b(?:memory|guidance|remembrance|learning\s+record|skill)\b/iu.test(
|
|
2500
|
+
text,
|
|
2501
|
+
)
|
|
2502
|
+
) {
|
|
2503
|
+
return true;
|
|
2504
|
+
}
|
|
2505
|
+
if (
|
|
2506
|
+
/\b(?:this|that|it|anything|everything)\s+(?:should|must)\s+stay\s+out\s+of\s+(?:shared\s+)?(?:memory|guidance|remembrance|learning\s+record|skill)\b/iu.test(
|
|
2507
|
+
text,
|
|
2508
|
+
)
|
|
2509
|
+
) {
|
|
2510
|
+
return true;
|
|
2511
|
+
}
|
|
2512
|
+
return /\b(?:this|that|it)\s+(?:should|must)\s+(?:not|never)\s+(?:become|turn\s+into|be\s+used\s+as)\s+(?:a\s+|the\s+)?(?:lesson|skill|memory|guidance|feedback)\b/iu.test(
|
|
2513
|
+
text,
|
|
2514
|
+
);
|
|
2515
|
+
}
|