instar 1.2.36 → 1.2.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.2.36",
3
+ "version": "1.2.37",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -330,6 +330,134 @@ if (staged.includes(spec) && !staged.includes(eli16Rel)) {
330
330
  );
331
331
  }
332
332
 
333
+ // ─── Step 7.5: orphan deferrals must be tracked ───────────────────────────
334
+ // Why: on 2026-05-20 PR #284 shipped four of five fixes for a version-skew
335
+ // failure class and explicitly deferred the fifth ("lifeline auto-restart on
336
+ // server upgrade — out of scope today"). Two days later that exact deferral
337
+ // produced the same outage. Per user feedback 2026-05-22: "WE NEED TO CHANGE
338
+ // THIS. Our development work should focus on COMPLETE features/fixes with NO
339
+ // deferrals." This check makes the rule structural: any spec that contains
340
+ // "deferred / out of scope today / follow-up / preemptive fix / NOT in this
341
+ // PR" language must explicitly track each instance (HTML comment marker
342
+ // `<!-- tracked: <id> -->` within 200 chars, or a frontmatter
343
+ // `deferrals-tracked` field). Override via INSTAR_DEV_ALLOW_ORPHAN_DEFERRALS=1
344
+ // (logged for visibility).
345
+ //
346
+ // Detector patterns intentionally conservative — false-positives are cheaper
347
+ // than false-negatives here. The author can either link a tracker or rephrase
348
+ // the sentence to not promise a deferral.
349
+ // Patterns: { regex, requireUnnegated } — when requireUnnegated is true,
350
+ // we skip the match if the immediately-preceding chars contain "no ", "non-",
351
+ // "non ", "non", or "un" (so "no deferrals" / "non-deferred" / "undeferred"
352
+ // don't false-alarm).
353
+ //
354
+ // Reviewer feedback 2026-05-22: the prior implementation honored a
355
+ // `deferrals-tracked:` frontmatter wave-through that short-circuited the
356
+ // entire body scan. That was a loophole — a future author could write
357
+ // `deferrals-tracked: see below` and ship orphan deferrals with no
358
+ // validation. Closed: every body hit must have its own inline tracker
359
+ // marker within 200 chars. The §"Deferrals tracked" section in the spec is
360
+ // still useful as a human-readable catalog, but no longer a bypass.
361
+ const DEFERRAL_PATTERNS = [
362
+ { regex: /\bdeferred?\b/gi, requireUnnegated: true },
363
+ { regex: /\bdeferrals?\b/gi, requireUnnegated: true },
364
+ { regex: /\bout of scope today\b/gi, requireUnnegated: false },
365
+ { regex: /\bout of scope for now\b/gi, requireUnnegated: false },
366
+ { regex: /\bnot in this pr\b/gi, requireUnnegated: false },
367
+ { regex: /\bpreemptive fix\b/gi, requireUnnegated: false },
368
+ // Match standalone "follow-up" / "follow-ups" / "followups" mentions.
369
+ // Reviewer broadened from the prior narrow "(?!\s+(?:are\s+)?tracked)"
370
+ // exclusion — that only handled "follow-ups tracked" / "follow-ups are
371
+ // tracked" and false-positived on natural variants. We now require the
372
+ // 200-char tracker-marker check to do the work uniformly.
373
+ { regex: /\bfollow[- ]?ups?\b/gi, requireUnnegated: false },
374
+ ];
375
+ const TRACKED_NEAR_HIT_CHARS = 200;
376
+ const TRACKED_MARKER = /<!--\s*tracked:\s*[A-Za-z0-9._/-]+\s*-->/;
377
+ const NEGATION_BEFORE = /(?:\b(?:no|non-?|un)[- ]?)$/i;
378
+
379
+ function findOrphanDeferrals(content) {
380
+ const orphans = [];
381
+ for (const { regex, requireUnnegated } of DEFERRAL_PATTERNS) {
382
+ regex.lastIndex = 0;
383
+ let m;
384
+ while ((m = regex.exec(content)) !== null) {
385
+ const start = m.index;
386
+ if (requireUnnegated) {
387
+ // Look at up to 8 chars immediately before the match. Treat
388
+ // matches preceded by "no ", "non-", "non ", "un" as legitimate
389
+ // negations (false-positive guard for "no deferrals", "non-
390
+ // deferred", "undeferred").
391
+ const before = content.slice(Math.max(0, start - 8), start);
392
+ if (NEGATION_BEFORE.test(before)) continue;
393
+ }
394
+ const end = Math.min(content.length, start + m[0].length + TRACKED_NEAR_HIT_CHARS);
395
+ const slice = content.slice(start, end);
396
+ if (TRACKED_MARKER.test(slice)) continue;
397
+ // Compute line number for the operator's diagnostic.
398
+ const beforeAll = content.slice(0, start);
399
+ const lineNo = (beforeAll.match(/\n/g) || []).length + 1;
400
+ orphans.push({ pattern: regex.source, match: m[0], lineNo });
401
+ if (orphans.length >= 16) return orphans; // cap noise (was 8; widened patterns may trip more)
402
+ }
403
+ }
404
+ return orphans;
405
+ }
406
+
407
+ const orphans = findOrphanDeferrals(specContent);
408
+ if (orphans.length > 0) {
409
+ const override = process.env.INSTAR_DEV_ALLOW_ORPHAN_DEFERRALS === '1';
410
+ if (override) {
411
+ // Log the override for visibility — every use is audited.
412
+ try {
413
+ const logPath = path.join(TRACES_DIR, 'orphan-deferral-overrides.jsonl');
414
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
415
+ fs.appendFileSync(
416
+ logPath,
417
+ JSON.stringify({
418
+ at: new Date().toISOString(),
419
+ spec,
420
+ orphans,
421
+ stagedFiles: inScopeFiles,
422
+ }) + '\n',
423
+ );
424
+ console.warn(
425
+ `[instar-dev-precommit] WARN: INSTAR_DEV_ALLOW_ORPHAN_DEFERRALS=1 — ` +
426
+ `${orphans.length} orphan deferral(s) in ${spec} bypassed and logged to ${path.relative(ROOT, logPath)}`,
427
+ );
428
+ } catch { /* logging is best-effort */ }
429
+ } else {
430
+ blockCommit(
431
+ inScopeFiles,
432
+ [
433
+ `Spec ${spec} contains ${orphans.length} orphan deferral mention(s) — each must be tracked.`,
434
+ '',
435
+ ...orphans.map(o => ` • line ${o.lineNo}: "${o.match}"`),
436
+ '',
437
+ 'Why this rule exists:',
438
+ ' On 2026-05-20 a PR shipped 4 of 5 fixes for a failure class and',
439
+ ' explicitly deferred the 5th. Two days later that exact deferral',
440
+ ' produced the same outage. Deferrals are how regressions happen.',
441
+ '',
442
+ 'How to resolve each one:',
443
+ ' (a) Move the work into this PR (preferred — eliminates the deferral).',
444
+ ' (b) Add a tracked marker within 200 chars of the mention:',
445
+ ' `<!-- tracked: <id> -->` where <id> is an issue, topic, or',
446
+ ' commitment-action ID that owns the follow-up.',
447
+ ' (c) Rephrase the sentence so it no longer promises a deferral',
448
+ ' (e.g. quote the historical phrase verbatim with surrounding',
449
+ ' context that makes the non-prescriptive intent obvious).',
450
+ '',
451
+ 'Emergency override (logged + audited):',
452
+ ' INSTAR_DEV_ALLOW_ORPHAN_DEFERRALS=1 git commit ...',
453
+ ' Use only when the deferral language is in a non-prescriptive context',
454
+ ' (e.g. quoting an old spec). Every use lands in',
455
+ ' .instar/instar-dev-traces/orphan-deferral-overrides.jsonl for review.',
456
+ ].join('\n'),
457
+ );
458
+ }
459
+ }
460
+
333
461
  // ─── Step 8: proposal-derived runbook gate (S-3) ─────────────────────────
334
462
  // Per SELF-HEALING-REMEDIATOR-V2-SPEC §A11/§A22/§A32, runbook source files
335
463
  // emitted by the SystemReviewer proposal pipeline must: