instar 1.3.31 → 1.3.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/commands/init.d.ts.map +1 -1
  2. package/dist/commands/init.js +8 -37
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/server.d.ts.map +1 -1
  5. package/dist/commands/server.js +47 -1
  6. package/dist/commands/server.js.map +1 -1
  7. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  8. package/dist/core/PostUpdateMigrator.js +18 -0
  9. package/dist/core/PostUpdateMigrator.js.map +1 -1
  10. package/dist/core/instarSettingsHooks.d.ts +95 -0
  11. package/dist/core/instarSettingsHooks.d.ts.map +1 -0
  12. package/dist/core/instarSettingsHooks.js +136 -0
  13. package/dist/core/instarSettingsHooks.js.map +1 -0
  14. package/dist/core/types.d.ts +16 -0
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/core/types.js.map +1 -1
  17. package/dist/feedback-factory/dryrun/dryRunCompare.d.ts +82 -0
  18. package/dist/feedback-factory/dryrun/dryRunCompare.d.ts.map +1 -0
  19. package/dist/feedback-factory/dryrun/dryRunCompare.js +88 -0
  20. package/dist/feedback-factory/dryrun/dryRunCompare.js.map +1 -0
  21. package/dist/feedback-factory/processor/parity.d.ts +111 -0
  22. package/dist/feedback-factory/processor/parity.d.ts.map +1 -0
  23. package/dist/feedback-factory/processor/parity.js +118 -0
  24. package/dist/feedback-factory/processor/parity.js.map +1 -0
  25. package/dist/feedback-factory/store/ReadOnlyShadowStore.d.ts +59 -0
  26. package/dist/feedback-factory/store/ReadOnlyShadowStore.d.ts.map +1 -0
  27. package/dist/feedback-factory/store/ReadOnlyShadowStore.js +88 -0
  28. package/dist/feedback-factory/store/ReadOnlyShadowStore.js.map +1 -0
  29. package/dist/monitoring/StopNotifier.d.ts +88 -0
  30. package/dist/monitoring/StopNotifier.d.ts.map +1 -0
  31. package/dist/monitoring/StopNotifier.js +105 -0
  32. package/dist/monitoring/StopNotifier.js.map +1 -0
  33. package/dist/server/AgentServer.d.ts +2 -0
  34. package/dist/server/AgentServer.d.ts.map +1 -1
  35. package/dist/server/AgentServer.js +1 -0
  36. package/dist/server/AgentServer.js.map +1 -1
  37. package/dist/server/routes.d.ts +4 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +13 -0
  40. package/dist/server/routes.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +63 -63
  43. package/upgrades/1.3.31.md +9 -10
  44. package/upgrades/1.3.32.md +28 -0
  45. package/upgrades/1.3.33.md +36 -0
  46. package/upgrades/side-effects/existing-agent-pretooluse-hook-parity.md +39 -0
  47. package/upgrades/side-effects/feedback-factory-dryrun.md +93 -0
  48. package/upgrades/side-effects/notify-on-stop-layer-b.md +34 -0
@@ -0,0 +1,36 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ **Notify-on-stop, Layer B.** When the Unjustified Stop Gate judges a stop unjustified-but-unblockable (`continue` in shadow mode — the gate wants to keep going but shadow can't block, so the session silently stalls) or ambiguous (`escalate`), and the stopping session is **unattended** (an autonomous run), the user now gets one coalesced plain-English heads-up ("a background run stopped mid-task — want me to pick it back up?"). Previously the gate saw these and could do nothing the user could observe.
8
+
9
+ Tightly bounded to stay near-silent: only those two genuinely-stuck decision classes, only unattended sessions, at most once per session per 30 minutes, coalesced onto the single system (lifeline) topic. Routine turn-ends, blocked-and-continued stops, and transient fail-opens stay silent. Default ON (Justin's explicit "tell me why it stopped"); disable with `monitoring.notifyOnStop.enabled=false`. Pairs with Layer A (autonomous-run terminal-exit notices).
10
+
11
+ **Feedback Factory Migration — dry-run/compare machinery.** Next increment of the migration (spec `docs/specs/feedback-factory-migration.md`, approved). Ships the safety harness that lets the ported Instar processor run safely against Portal's live database during the cutover — without ever being able to change it. Three new internal modules under `src/feedback-factory/`:
12
+
13
+ - **`store/ReadOnlyShadowStore.ts`** — a read-only wrapper around the feedback data layer. Reads pass through; every write throws. The structural guarantee that the dry-run can never mutate the curated bug history (Portal stays the sole writer through cutover).
14
+ - **`processor/parity.ts`** — the comparator for the three order-independent invariants the migration spec pins: per-cluster fingerprint, terminal status, and recurrence count. Recomputing fingerprints over the live clusters and diffing them is the highest-signal check — it catches any Python↔TypeScript divergence on real production titles.
15
+ - **`dryrun/dryRunCompare.ts`** — the runner: reads the clusters, compares the invariants, writes a JSONL audit trail, and returns a verdict. `divergent === true` is the signal that blocks cutover.
16
+
17
+ Still internal building blocks — not yet wired into any route or job, so no behavioral change. The live database adapter behind the read-only seam lands once Dawn hands off the read credentials.
18
+
19
+ ## What to Tell Your User
20
+
21
+ - If one of my background runs stalls mid-task when it shouldn't have, you now get a single heads-up — even when the watchdog can't restart it itself.
22
+ - On the feedback system: this is the safety harness for moving it in-house. It lets me run my new version *alongside* Dawn's, reading the same live data, and prove they make identical decisions — while making it physically impossible for my version to touch the real records. The comparison is built to ignore harmless ordering differences and only flag *real* disagreements.
23
+
24
+ ## Summary of New Capabilities
25
+
26
+ | Capability | How to Use |
27
+ |-----------|-----------|
28
+ | Unjustified mid-task stalls on unattended sessions surface one coalesced Telegram | Automatic (default on); `monitoring.notifyOnStop.enabled=false` to disable |
29
+ | Read-only shadow store (write-guard) | Internal `src/feedback-factory/store/ReadOnlyShadowStore.ts` — wraps any `FeedbackStore`; writes throw |
30
+ | Parity invariant comparator | Internal `src/feedback-factory/processor/parity.ts` — `compareInvariants()` |
31
+ | Dry-run/compare runner | Internal `src/feedback-factory/dryrun/dryRunCompare.ts` — `runDryRunCompare(source, opts)` → verdict + JSONL |
32
+
33
+ ## Evidence
34
+
35
+ - **Notify-on-stop Layer B:** `src/monitoring/StopNotifier.ts` (decision matrix + attended-gate + dedup); wired via `src/server/routes.ts`, `src/commands/server.ts`, `src/server/AgentServer.ts`. Config: `monitoring.notifyOnStop` in `src/core/types.ts`. Tests: `tests/unit/StopNotifier.test.ts` (21) + `tests/unit/stop-notifier-wiring.test.ts` (6). Spec: `docs/specs/NOTIFY-ON-STOP-SPEC.md`. Side-effects: `upgrades/side-effects/notify-on-stop-layer-b.md`.
36
+ - **Feedback-factory dry-run:** 21 new unit tests across the three modules, all green; full feedback-factory unit dir 128/128 green; `tsc --noEmit` clean. Both-sides-of-boundary: every read delegates AND every write throws (with the offending method name); the comparator asserts no-divergence on matching data plus a real divergence for each invariant; the runner covers the clean path, a divergent corpus, the JSONL audit trail, return-only mode, and the never-mutates-the-source guarantee. Faithful to the reference: per-cluster fingerprint is derived exactly as the Python's `cmd_backfill_fingerprints` (`computeFingerprint(cluster.type, cluster.title)`). Side-effects: `upgrades/side-effects/feedback-factory-dryrun.md`.
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — Existing-Agent PreToolUse Hook Parity
2
+
3
+ **Spec:** docs/specs/EXISTING-AGENT-PRETOOLUSE-HOOK-PARITY-SPEC.md (approved: true, internal-adversarial review 2026-05-27)
4
+
5
+ Closes the dark-guardrail migration gap: `init.ts` wired a full instar Bash PreToolUse hook set for NEW agents, but `PostUpdateMigrator.migrateSettings` only ensured slopcheck-guard + the MCP gate for EXISTING agents — so `deferral-detector.js` (the false-blocker pre-filter; this is Task 3 of the silent-stalls postmortem), `grounding-before-messaging.sh`, `external-communication-guard.js`, and `post-action-reflection.js` shipped to disk on every existing agent but were never switched on in settings. Same failure class as the 2026-05-27 silent-stall incident (looks installed, does nothing).
6
+
7
+ ## What changed
8
+ - `src/core/instarSettingsHooks.ts` — NEW. Single source of truth for the canonical instar `Bash` and `mcp__.*` PreToolUse hook ENTRIES (`INSTAR_BASH_PRETOOLUSE_HOOKS`, `INSTAR_MCP_PRETOOLUSE_HOOKS`), a `instarHookFilename()` helper, and the idempotent `ensureInstarBashPreToolUseHooks()`.
9
+ - `src/commands/init.ts` — refactored the inline `instarBashHooks` / `instarMcpHooks` literals to consume the shared constants (fresh copies via `.map(h => ({...h}))`). Behavior-preserving: byte-identical settings output for new agents (verified by the existing `hook-installation.test.ts`, all green).
10
+ - `src/core/PostUpdateMigrator.ts` — `migrateSettings` now calls `ensureInstarBashPreToolUseHooks(preToolUse)` before the slopcheck block; appends only missing canonical hooks, idempotently, reporting each as a `dark-guardrail wiring` upgrade.
11
+ - Tests: `tests/unit/instar-settings-hooks.test.ts` (16) + `tests/unit/PostUpdateMigrator-pretooluse-parity.test.ts` (3).
12
+
13
+ ## Over-block / under-block
14
+ - UNDER: a custom hook with the same script filename would be treated as "present" and not re-added. Acceptable — that's the intent (don't duplicate). Custom hooks under `.claude/hooks/custom/` have different filenames, so they're never matched/displaced.
15
+ - OVER: none. The function only APPENDS missing canonical hooks; it never reorders or removes existing entries, and it touches only the `Bash` matcher. Slopcheck-guard and all non-Bash matchers are untouched.
16
+
17
+ ## Signal vs authority
18
+ N/A for blocking — this wires guardrail hooks into settings. Within the false-blocker design, `deferral-detector` is the SIGNAL (non-blocking checklist injection); `MessagingToneGate` B17 is the AUTHORITY (already live). This change lights up the signal layer on existing agents; it adds no new blocking authority.
19
+
20
+ ## Idempotency / migration parity
21
+ - IS a migration-parity fix. New-agent path (init.ts) and existing-agent path (migrateSettings) now consume the SAME constant — drift is impossible by construction; an anti-drift unit test locks the canonical filename set.
22
+ - `ensureInstarBashPreToolUseHooks` is idempotent (filename-substring presence check, robust to `${CLAUDE_PROJECT_DIR}` vs absolute path); re-runs are no-ops (verified).
23
+ - No config or CLAUDE.md change. No new hook scripts (all four already ship + are ESM-safe).
24
+
25
+ ## Interactions
26
+ - Runs before the existing slopcheck ensure-block; since it creates the `Bash` matcher when absent, slopcheck's "no Bash matcher" branch becomes a no-op while its "add slopcheck" branch still fires. Both end states verified together in the parity test.
27
+ - Codex hooks (`installCodexHooks.ts`, `.*` matcher) are a separate path, intentionally not governed by this module.
28
+
29
+ ## Rollback
30
+ Revert the migrator call + the init refactor + delete the shared module (3 files) and the 2 test files. Worst case: existing agents return to the dark-guardrail state.
31
+
32
+ ## Tests
33
+ - `tests/unit/instar-settings-hooks.test.ts` (16) — anti-drift contract, filename extraction, add/idempotent/preserve/no-matcher/no-dup/fresh-copy, init-imports-the-constant.
34
+ - `tests/unit/PostUpdateMigrator-pretooluse-parity.test.ts` (3) — real `migrateSettings` against a stale agent home: wires the four, idempotent, reports each.
35
+ - Regression-safety: `hook-installation.test.ts`, `installCodexHooks.test.ts`, `codexHookContractCanary.test.ts`, `autonomous-skill-deployment.test.ts`, `capabilities-discoverability.test.ts` all green post-refactor.
36
+
37
+ ## NOT in this commit (tracked)
38
+ - Notify-on-stop (Task 2 — separate PR/spec).
39
+ - Self-propagation harness (Task 4).
@@ -0,0 +1,93 @@
1
+ # Side-effects review — feedback-factory dry-run/compare + ReadOnlyShadowStore
2
+
3
+ Spec: `docs/specs/feedback-factory-migration.md` (converged v2, approved 2026-05-26).
4
+ Increment: the Phase-1/3 dry-run/compare machinery — `ReadOnlyShadowStore` (write-guard),
5
+ `parity.ts` (invariant comparator), `dryRunCompare.ts` (runner + `ParitySource` seam).
6
+ Purely additive: three new `src/feedback-factory/` files + three new unit test files; zero
7
+ edits to existing code.
8
+
9
+ ## What this ships
10
+
11
+ The mechanism that lets the ported Instar processor run **read-only against Portal's live
12
+ canonical DB** and have its decisions compared to Portal's, with Portal remaining the sole
13
+ writer through cutover (the one-shared-DB precondition that prevents split-brain). Three
14
+ order-independent invariants per spec §2.3:
15
+
16
+ 1. **Fingerprint (per cluster)** — `computeFingerprint(cluster.type, cluster.title)` recomputed
17
+ over Portal's live clusters and diffed against the stored value. Pure, no replay, fully
18
+ order-independent. The highest-signal gate (catches byte-level Python↔JS divergence on real
19
+ production titles). Faithful to the reference's `cmd_backfill_fingerprints` (:256).
20
+ 2. **Terminal status** — keyed by fingerprint, not raw slug clusterId.
21
+ 3. **Recurrence/cycling count** — keyed by fingerprint.
22
+
23
+ `result.divergent === true` is the structural signal that blocks Phase 4 cutover.
24
+
25
+ ## Over-block / under-block
26
+
27
+ - **ReadOnlyShadowStore**: deliberately over-strict on writes — every one of the 6 mutating
28
+ methods throws. This is the correct direction: the failure we are guarding against
29
+ (corrupting Portal's curated bug history) is catastrophic and irreversible, while a false
30
+ block is a loud, immediate, recoverable error at the seam. No under-block path exists: there
31
+ is no method that silently no-ops a write.
32
+ - **Fingerprint comparator**: skips clusters with no stored fingerprint (pre-backfill rows) —
33
+ prevents the false positive of flagging "divergence" against an empty string. It does NOT
34
+ skip any cluster that *has* a fingerprint, so it cannot under-block a real divergence.
35
+ - **Outcome comparator (status/recurrence)**: treats a fingerprint present on only one side as
36
+ a divergence (`missing-instar` / `missing-portal`) rather than ignoring it — chosen to
37
+ surface grouping disagreements rather than hide them. This is the conservative (over-surface)
38
+ direction, appropriate for a cutover gate.
39
+
40
+ ## Level-of-abstraction fit
41
+
42
+ - The comparator is **pure** (no I/O) and lives beside the other pure processor logic in
43
+ `processor/`. The runner owns the I/O (read source + JSONL append) and lives in a new
44
+ `dryrun/` directory — matching the existing separation (pure decision logic vs. composition).
45
+ - `ParitySource` is the single read-only seam. The Prisma-over-Postgres adapter is the only
46
+ production implementation and is intentionally NOT built here (credentials-gated — see
47
+ Deferrals). The interface is the contract Dawn confirmed her adapter slots into.
48
+ - `ReadOnlyShadowStore` wraps the existing `FeedbackStore` interface rather than inventing a
49
+ new one — it is a faithful decorator, so any present or future `FeedbackStore` consumer can
50
+ be made read-only without changes.
51
+
52
+ ## Signal-vs-authority compliance
53
+
54
+ This increment is **signal-only**, not authority. It produces a verdict (`divergent`) and a
55
+ JSONL audit trail; it takes no action — no cutover, no migration, no messaging, no mutation.
56
+ The authority to advance Phase 4 remains with the operator + Dawn's line-by-line review. This
57
+ matches the Instar separation: a brittle/automated comparator detects and emits; a
58
+ higher-context human/gate decides. The runner cannot itself flip any switch.
59
+
60
+ ## Interactions
61
+
62
+ - **Portal's canonical DB**: read-only by construction (ShadowStore guard + read-only
63
+ `ParitySource`). The runner only ever reads + appends to a *local* JSONL log. No write path
64
+ to Portal exists in this code.
65
+ - **The ported processor (`process.ts` → `processUnprocessed`)**: the runner does NOT call it
66
+ (it would write). The comparator uses only the pure functions. The ShadowStore guard is the
67
+ backstop if a future caller wires `processUnprocessed` against a shadow store by mistake —
68
+ it throws at the first write instead of corrupting data.
69
+ - **Existing feedback-factory tests**: unaffected (additive change; full unit dir green —
70
+ 128 tests including the 21 new).
71
+ - **No config, hook, route, template, or migration surface is touched** — so no Migration
72
+ Parity / Agent Awareness obligation is triggered by *this* increment (those land with the
73
+ receiver/dispatch wiring + observability increments, which DO touch routes/templates).
74
+
75
+ ## Rollback cost
76
+
77
+ Trivial and isolated. Three new `src/` files + three new test files, no edits elsewhere,
78
+ nothing wired into server startup or routes yet. Reverting the commit removes the files with
79
+ zero downstream impact — nothing imports them in production paths. No data migration, no
80
+ state, no deployed surface.
81
+
82
+ ## Deferrals
83
+
84
+ - The **Prisma-over-Postgres `ParitySource` adapter** is not built in this increment.
85
+ Reason: it requires Dawn's read-only Postgres credentials + `prisma db pull` against the live
86
+ schema (a genuine external dependency, not avoidable work). The `ParitySource` interface +
87
+ `InMemoryParitySource` make the runner fully buildable and testable now; the live adapter
88
+ slots in behind the interface the moment credentials land. <!-- tracked: topic-12476 -->
89
+ - **Auto-derivation of Instar's status/recurrence outcomes (invariants 2 & 3) from a
90
+ point-in-time read** is not built. Reason: a faithful replay needs window-START cluster state,
91
+ which a current-state read cannot supply; the outcomes must come from a snapshot / full-history
92
+ export. The comparator + runner accept those outcome lists as inputs today, so the path is
93
+ complete pending the data source. <!-- tracked: topic-12476 -->
@@ -0,0 +1,34 @@
1
+ # Side-Effects Review — Notify-on-Stop, Layer B (gate-fed unjustified-stall notice)
2
+
3
+ **Spec:** docs/specs/NOTIFY-ON-STOP-SPEC.md (approved: true) — Layer B of two. Layer A (autonomous-run-ended notice) shipped in a prior PR.
4
+
5
+ When the UnjustifiedStopGate (shadow/enforce) classifies a stop as unjustified-but-unblockable (`continue` in `shadow` — the gate wants to continue but shadow can't block, so the session silently stalls) or ambiguous (`escalate`), an UNATTENDED session can go quiet mid-task and the user never hears about it. Layer B turns those specific classifications into ONE coalesced heads-up.
6
+
7
+ ## What changed
8
+ - `src/monitoring/StopNotifier.ts` (new) — thin DECISION layer: `isNotifyWorthyStop` (shadow+continue OR escalate; everything else silent) + attended-gate (unattended/autonomous only, default) + per-session dedup (30-min cooldown) + master enable (default ON). Delivery is delegated to an injected `escalate` sink.
9
+ - `src/commands/server.ts` — constructs a dedicated `SentinelNotifier` (telegramEscalation **on** — distinct from the housekeeping sentinel notifier, which stays default-off) reusing the lifeline-topic `sendConsolidated` transport + a JSONL log; wraps it in a `StopNotifier`. Null when telegram isn't wired or config disables it.
10
+ - `src/server/routes.ts` — the `/internal/stop-gate/evaluate` route computes `autonomousActive` (via `getHotPathState`) and, after the authority decision, calls `ctx.stopNotifier?.maybeNotify({ sessionId, mode, decision, autonomousActive })`. Fire-and-forget; never affects the gate decision or the HTTP response.
11
+ - `src/server/AgentServer.ts` — accepts + forwards `stopNotifier` to the routes ctx.
12
+ - `src/core/types.ts` — `monitoring.notifyOnStop?: { enabled?, unattendedOnly?, cooldownMs? }`.
13
+ - Tests: `StopNotifier.test.ts` (21 — both sides of every decision boundary, attended-gate, dedup window/expiry/per-session, master gate, throwing-sink safety) + `stop-notifier-wiring.test.ts` (6 — route→notifier→sink→server→AgentServer chain, guards the PR#334 dead-code failure mode).
14
+
15
+ ## Signal vs authority
16
+ The Stop-hook router is a dumb thin client; the decision to ALARM the user lives server-side in StopNotifier, where the gate decision + attended-state + dedup ledger all exist. StopNotifier adds NO blocking authority — it's a delivery decision only. It never changes the gate's `decision`.
17
+
18
+ ## Near-silent compliance
19
+ Default-on is the deliberate exception Justin explicitly asked for ("tell me why it stopped"), and it is tightly bounded: only two genuinely-stuck decision classes, only unattended sessions, at most once per session per 30 min, coalesced into one message on the single lifeline topic. Routine turn-ends (`allow`), blocked-and-continued (`continue` in enforce), continue-ceiling (`force_allow`), and fail-open are all silent. A single config flag (`monitoring.notifyOnStop.enabled=false`) kills it with no redeploy.
20
+
21
+ ## Over/under-notify
22
+ - OVER: a shadow-mode `continue` could occasionally be a gate misjudgment (the CONTINUE_CEILING exists because the authority can be wrong). Mitigated by attended-gate + 30-min dedup; the alternative (a silent autonomous stall) is exactly what Justin wants eliminated.
23
+ - UNDER: when the gate is `off` (no evaluate call) there's no Layer B notice — but Layer A still covers autonomous terminal exits, and the gate runs in `shadow` on real agents.
24
+
25
+ ## Migration parity
26
+ Default-on works WITHOUT any config key present (`enabled !== false` ⇒ true when undefined), so existing agents get it on update to this version with no migrateConfig step. The wiring is server-side; no agent-installed-file change.
27
+
28
+ ## Rollback
29
+ `monitoring.notifyOnStop.enabled=false` (instant, no redeploy) or revert the 6 files + 2 tests.
30
+
31
+ ## Tests / verification
32
+ - Tier 1 unit: StopNotifier.test.ts (decision matrix both sides + gates).
33
+ - Wiring-integrity: stop-notifier-wiring.test.ts (no dead code).
34
+ - Tier 3 live: test-as-self before merge — drive a synthetic shadow+continue on an autonomous session through the real route and confirm the lifeline-topic heads-up fires once (and a routine `allow` stays silent).