bareloop 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/root.js CHANGED
@@ -105,13 +105,14 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
105
105
  * failures" — [] === [] would false-read as "reds unchanged" (Finding 2).
106
106
  * @param {string} gap @returns {string[]|null} normalized kept lines, or null when untrustworthy
107
107
  */
108
- const keptSet = (gap) => {
109
- if (!keepRe || gap.includes(GAP_KEEP_TRIM_MARKER)) return null;
110
- const lines = [...new Set(gap.split('\n').filter((l) => keepRe.test(l)).map(normalizeRedLine))].sort();
108
+ const keptSet = (gap, pattern) => {
109
+ const re = pattern ? new RegExp(pattern, 'm') : keepRe;
110
+ if (!re || gap.includes(GAP_KEEP_TRIM_MARKER)) return null;
111
+ const lines = [...new Set(gap.split('\n').filter((l) => re.test(l)).map(normalizeRedLine))].sort();
111
112
  return lines.length ? lines : null;
112
113
  };
113
114
 
114
- /** @type {{writes: string[], reds: string[]|null, tee: Map<string, TeeEntry>}[]} finalized attempts */
115
+ /** @type {{writes: string[], reds: string[]|null, stage: string|undefined, tee: Map<string, TeeEntry>}[]} finalized attempts */
115
116
  const attempts = [];
116
117
  /** @type {Set<string>} cumulative audit write-set at the last observe */
117
118
  let prevCumulative = new Set();
@@ -179,10 +180,16 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
179
180
  * Called at the START of attempt `iteration`, with the PREVIOUS attempt's
180
181
  * gap. Finalizes that attempt from the books, runs the detector, and
181
182
  * returns the injection for the attempt about to run — or null (inert).
182
- * @param {{iteration: number, gap?: string, writes: string[]}} o
183
+ *
184
+ * `redStage`/`redKeep` are the STAGED-CLOSE seam (PRD v1.28): a close is an
185
+ * ordered list of named stages, the first red renders the verdict, so the
186
+ * pattern that designates failures belongs to whichever stage rendered it
187
+ * THIS attempt. Callers with a single red-set source (the per-step exit
188
+ * evaluator) pass neither and keep the constructor's run-wide pattern.
189
+ * @param {{iteration: number, gap?: string, writes: string[], redStage?: string, redKeep?: string}} o
183
190
  * @returns {null | {stage: 'summary'|'verbatim', note: string, event: object}}
184
191
  */
185
- observe({ iteration, gap, writes }) {
192
+ observe({ iteration, gap, writes, redStage, redKeep }) {
186
193
  const cumulative = new Set(writes);
187
194
  if (!gap) { // first attempt: nothing judged yet, nothing to compare
188
195
  prevCumulative = cumulative;
@@ -197,7 +204,7 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
197
204
  for (const p of tee.keys()) if (cumulative.has(p) && !delta.includes(p)) delta.push(p);
198
205
  // `gap` is guaranteed truthy here (the `if (!gap) return` guard above handled
199
206
  // the empty case); reds === null comes only from keptSet's own UNKNOWN logic.
200
- attempts.push({ writes: delta, reds: keptSet(gap), tee });
207
+ attempts.push({ writes: delta, reds: keptSet(gap, redKeep), stage: redStage, tee });
201
208
  // The detector only ever compares the last two finalized attempts, so older
202
209
  // ones (and their teed content) are dead state — keep just the two (Finding 7).
203
210
  if (attempts.length > 2) attempts.shift();
@@ -210,12 +217,22 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
210
217
  if (attempts.length < 2) return null;
211
218
  const [prev, last] = attempts.slice(-2);
212
219
  const overlap = last.writes.filter((p) => prev.writes.includes(p));
213
- // Both UNKNOWN → no trustworthy red-set either side: write-overlap alone
214
- // decides (writes-only mode, named below). One known and one UNKNOWN → the
215
- // red-set is not provably the same, so it counts as MOVEMENT, never a fire
216
- // (a real red-set followed by a crash/empty gap is new information, not
220
+ // A CHANGED failing stage is movement by definition — a different wall of
221
+ // the same inspection is a different failure, so the two red-sets are not
222
+ // comparable and repetition is not provable. Same bucket as one-known-one-
223
+ // UNKNOWN below, and for the same reason. This guard is what makes a single
224
+ // union pattern wrong: the stages of a real close are near-clones of one
225
+ // grader (job #4: 7 of 12 kept-line templates byte-identical), so a union
226
+ // kept-set can be IDENTICAL across a stage change and read as repetition —
227
+ // a false fire, which "inert when not stuck" forbids.
228
+ //
229
+ // Otherwise: both UNKNOWN → no trustworthy red-set either side, write-overlap
230
+ // alone decides (writes-only mode, named below). One known and one UNKNOWN →
231
+ // the red-set is not provably the same, so it counts as MOVEMENT, never a
232
+ // fire (a real red-set followed by a crash/empty gap is new information, not
217
233
  // repetition). Both known → the honest set comparison.
218
- const redsSame = last.reds === null && prev.reds === null ? null
234
+ const redsSame = last.stage !== prev.stage ? false
235
+ : last.reds === null && prev.reds === null ? null
219
236
  : last.reds === null || prev.reds === null ? false
220
237
  : JSON.stringify(last.reds) === JSON.stringify(prev.reds);
221
238
  // Finding 3: when writes carry no information (text mode — the one target is
@@ -237,6 +254,9 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
237
254
  iteration, stage, mode, streak,
238
255
  paths: shownPaths, ...(morePaths > 0 ? { pathsElided: morePaths } : {}),
239
256
  redSetSize: last.reds ? last.reds.length : null,
257
+ // which stage of the close designated those failures — absent for the
258
+ // single-source callers, so the record never claims a stage it lacks
259
+ ...(last.stage !== undefined ? { redStage: last.stage } : {}),
240
260
  };
241
261
 
242
262
  // Finding 7: what the worker REACHED FOR fired the detector; what it