instar 1.3.8 → 1.3.10

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 (36) hide show
  1. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  2. package/dist/config/ConfigDefaults.js +12 -0
  3. package/dist/config/ConfigDefaults.js.map +1 -1
  4. package/dist/feedback-factory/processor/similarity.d.ts +24 -0
  5. package/dist/feedback-factory/processor/similarity.d.ts.map +1 -0
  6. package/dist/feedback-factory/processor/similarity.js +44 -0
  7. package/dist/feedback-factory/processor/similarity.js.map +1 -0
  8. package/dist/scheduler/MentorOnboardingRunner.d.ts +63 -0
  9. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -0
  10. package/dist/scheduler/MentorOnboardingRunner.js +60 -0
  11. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -0
  12. package/dist/scheduler/MentorOnboardingTick.d.ts +68 -0
  13. package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -0
  14. package/dist/scheduler/MentorOnboardingTick.js +104 -0
  15. package/dist/scheduler/MentorOnboardingTick.js.map +1 -0
  16. package/dist/server/AgentServer.d.ts +14 -0
  17. package/dist/server/AgentServer.d.ts.map +1 -1
  18. package/dist/server/AgentServer.js +79 -0
  19. package/dist/server/AgentServer.js.map +1 -1
  20. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  21. package/dist/server/CapabilityIndex.js +12 -0
  22. package/dist/server/CapabilityIndex.js.map +1 -1
  23. package/dist/server/routes.d.ts +3 -0
  24. package/dist/server/routes.d.ts.map +1 -1
  25. package/dist/server/routes.js +24 -0
  26. package/dist/server/routes.js.map +1 -1
  27. package/package.json +1 -1
  28. package/scripts/feedback-factory/_py_similarity_ref.py +39 -0
  29. package/scripts/feedback-factory/similarity-corpus.json +25 -0
  30. package/scripts/feedback-factory/similarity-parity.mjs +72 -0
  31. package/src/data/builtin-manifest.json +47 -47
  32. package/src/scaffold/templates/jobs/instar/mentor-onboarding.md +41 -0
  33. package/upgrades/1.3.10.md +47 -0
  34. package/upgrades/1.3.9.md +27 -0
  35. package/upgrades/side-effects/feedback-factory-similarity.md +32 -0
  36. package/upgrades/side-effects/mentor-onboarding-job.md +186 -0
@@ -0,0 +1,27 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- bump: patch -->
4
+
5
+ ## What Changed
6
+
7
+ Second increment of the **Feedback Factory Migration** (Dawn → Echo; spec `docs/specs/feedback-factory-migration.md`, approved). Ports the **fuzzy title-similarity primitives** — `_tokenize` + `_jaccard_similarity` — from the reference Python (`the-portal/.claude/scripts/feedback-processor.py`) to TypeScript at `src/feedback-factory/processor/similarity.ts`.
8
+
9
+ These sit on top of the exact-match fingerprinter shipped in the previous increment: the fingerprint groups identically-normalized reports, and Jaccard similarity is the fuzzy layer that decides whether two *differently-worded* titles overlap enough to be the same bug (the input to the clustering merge thresholds). Still an internal building block — **not wired into any route or job yet**, so no behavioral change.
10
+
11
+ ## What to Tell Your User
12
+
13
+ - More groundwork for moving the feedback system in-house — the piece that recognizes two differently-worded reports as the same underlying issue.
14
+ - Same discipline as before: I proved my rewrite produces the same similarity scores AND the same group/don't-group decisions as Dawn's original, across 20 title pairs including the tricky non-English-text ones.
15
+
16
+ ## Summary of New Capabilities
17
+
18
+ | Capability | How to Use |
19
+ |-----------|-----------|
20
+ | Title-similarity primitives (TS port) | Internal module `src/feedback-factory/processor/similarity.ts` — not yet wired |
21
+ | Similarity parity harness | `node scripts/feedback-factory/similarity-parity.mjs` (local; set `PORTAL_PROCESSOR`) |
22
+
23
+ ## Evidence
24
+
25
+ - **Parity vs the real reference Python:** ran `_jaccard_similarity` from the reference processor and the TS port over 20 title pairs (identical / disjoint / partial-overlap / near the 0.35 and 0.55 merge thresholds / empty / punctuation-only / non-ASCII). Result: **20/20 match** — both the raw similarity value and the merge-threshold decision (the math is bit-identical IEEE-754 division; the harness asserts the *decision* too, since that is what actually drives clustering).
26
+ - **ASCII-tokenizer fidelity:** the reference tokenizer is ASCII-only (`[a-z0-9]+` after lowercasing), so the port uses the identical ASCII class — non-English letters and non-ASCII digits are separators in both, verified by the corpus.
27
+ - **CI anchors:** unit tests assert exact ratios (0.6, 0.4) and the threshold ordering, so a tokenizer/division regression fails in CI even without the reference checkout.
@@ -0,0 +1,32 @@
1
+ # Side-Effects Review — feedback-factory similarity primitives (Phase 1, increment 2)
2
+
3
+ **Slug:** `feedback-factory-similarity`
4
+ **Date:** `2026-05-26`
5
+ **Author:** Echo
6
+ **Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
7
+ **Scope:** The fuzzy title-similarity primitives — `_tokenize` (:1389) and `_jaccard_similarity` (:1394) of the reference processor — which sit on top of the exact-match fingerprint (increment 1) and underpin scar (c)'s false-merge guard. The threshold-application/clustering driver and the other scars are subsequent increments.
8
+
9
+ ## Summary of the change
10
+
11
+ Ports `_tokenize` + `_jaccard_similarity` from `the-portal/.claude/scripts/feedback-processor.py` to `src/feedback-factory/processor/similarity.ts`. Pure functions, no I/O. **Not wired into any route/job yet** — no behavioral change. Adds the similarity parity harness (`scripts/feedback-factory/similarity-parity.mjs` + `_py_similarity_ref.py` + `similarity-corpus.json`) and Tier-1 unit tests.
12
+
13
+ ## Equivalence verification
14
+
15
+ - **20/20 title pairs match the reference Python** — both the raw similarity value (1e-12 tolerance for float-string serialization only; the math is bit-identical IEEE-754 division) AND the threshold decisions (`≥0.55` fixed-merge, `≥0.35` merge, else no-merge), which are what actually drive clustering.
16
+ - The tokenizer is ASCII-only in the reference (`re.findall(r'[a-z0-9]+', text.lower())`), so the JS port uses the identical ASCII class `/[a-z0-9]+/g` (no `u`, no `i`). Non-ASCII letters and Unicode digits are separators in both — verified by corpus entries (café→{caf}, arabic digits dropped, Straße→{stra,e}).
17
+
18
+ ## Seven-dimension review
19
+
20
+ 1. **Over/under-reach** — Pure deterministic functions, no I/O, no global state, not imported by any runtime path. Cannot affect existing behavior. Risk is equivalence-to-reference, addressed above.
21
+ 2. **Level-of-abstraction fit** — Processor-logic layer (`src/feedback-factory/processor/`), alongside the fingerprint port. Correct home.
22
+ 3. **Signal vs Authority** — N/A; pure computation. The threshold constants that turn a similarity score into a merge decision live in the (later) clustering driver, where the curated-lifecycle authority also lives.
23
+ 4. **Interactions** — None. New isolated module; nothing imports it yet. Parity scripts are LOCAL-only (external reference path via `PORTAL_PROCESSOR`), no CI dependency on the reference checkout.
24
+ 5. **Rollback cost** — Trivial: delete the module + tests + scripts. No data, no migration, no wiring.
25
+ 6. **Migration parity** — N/A. New internal library code; touches no agent-installed file.
26
+ 7. **Failure modes** — (a) Port diverges from reference → caught by the parity harness (20/20) + golden-shaped unit assertions in CI. (b) Float boundary divergence at exactly 0.35/0.55 → mitigated by identical IEEE-754 division + the parity harness asserting threshold DECISIONS, not just values. (c) Reference path absent → harness exits 2 with a clear message (local gate, not CI).
27
+
28
+ ## Tests
29
+
30
+ - Tier-1 unit (CI): `tests/unit/feedback-factory/similarity.test.ts` — `tokenize` (lowercase/split/dedup, ASCII-only behavior, empty/punctuation), `jaccardSimilarity` (identical=1.0, disjoint=0.0, empty-guard=0.0, exact ∩/∪ ratios, threshold ordering). 8 tests.
31
+ - Parity (local gate, evidence): `scripts/feedback-factory/similarity-parity.mjs` → **20/20** values + threshold decisions identical to the reference Python.
32
+ - No integration/E2E this increment: not yet wired to a route/job; those tiers attach when the clustering driver + receiver land. Reasoned decision, documented.
@@ -0,0 +1,186 @@
1
+ # Side-Effects Review — Mentor-onboarding job (§19.4)
2
+
3
+ **Spec:** `docs/specs/FRAMEWORK-ONBOARDING-MENTOR-SPEC.md` (converged 5 iters, approved by Justin)
4
+ **Change:** The live mentor loop, shipped DORMANT. Pure tick core (`runMentorTick`), thin runner
5
+ (`MentorOnboardingRunner`), routes (`GET /mentor/status`, `POST /mentor/tick`), AgentServer wiring,
6
+ `mentor.*` config defaults, CapabilityIndex entry, and a built-in job template (`enabled:false`).
7
+ **Files:** `src/scheduler/MentorOnboardingTick.ts`, `src/scheduler/MentorOnboardingRunner.ts`,
8
+ `src/server/routes.ts`, `src/server/AgentServer.ts`, `src/server/CapabilityIndex.ts`,
9
+ `src/config/ConfigDefaults.ts`, `src/scaffold/templates/jobs/instar/mentor-onboarding.md`,
10
+ `tests/unit/MentorOnboardingTick.test.ts`, `tests/unit/MentorOnboardingRunner.test.ts`,
11
+ `tests/integration/mentor-routes.test.ts`, `tests/e2e/mentor-onboarding-lifecycle.test.ts`,
12
+ `upgrades/NEXT.md`.
13
+
14
+ ## Principle check (Phase 1)
15
+
16
+ Does this involve a decision point that gates info flow / blocks actions / constrains behavior?
17
+ **Yes — and it's built signal-only.** The tick *decides* whether to act (canary→budget→safe-window),
18
+ spawns a constrained sub-agent, and writes findings. But none of it has authority over the user's
19
+ world: today it only writes to the read-only ledger (no mentee-delivery path is wired yet — see
20
+ Live-promotion blockers). It ships dormant (`mentor.enabled=false`, `mode:'off'`), so the production
21
+ default is `POST /mentor/tick → {ran:false, reason:'disabled'}` — no spawn, no spend, no contact.
22
+
23
+ ## The seven questions
24
+
25
+ 1. **Over-block.** The safe-window check is conservative: `isMenteeBusy()` treats ANY running
26
+ session as "busy," so the mentor errs toward not-interrupting (correct bias). The budget cap and
27
+ min-interval floor likewise skip rather than act. No legitimate *required* action is blocked —
28
+ the mentor is purely additive.
29
+
30
+ 2. **Under-block.** The Stage-B *deep* log-forensics (assembling the mentee's rollouts/diffs and
31
+ LLM-classifying them) is not yet wired — `runStageBForensics` returns `[]` today, so the loop's
32
+ real signal is the Stage-A leak detector (§4.3) + the funnel. This is a tracked follow-on
33
+ (<!-- tracked: topic-13435 -->), not a silent gap: the funnel logs every run, so a loop producing
34
+ no findings is visible, not hidden. `getSurface` returns an empty history until the conversation
35
+ source is wired (same tracked follow-on) — meaning until then Stage A has thin context, which the
36
+ off-by-default + dry-run-first rollout is designed to surface before live.
37
+
38
+ 3. **Level-of-abstraction fit.** The orchestration is a PURE function (`runMentorTick`) with all
39
+ side-effects injected — the structural guarantees (canary-first, fail-closed budget, safe-window)
40
+ are in code, not the job prompt. The runner is thin glue; the job template is a thin timer that
41
+ pokes `POST /mentor/tick`. Spawn enforcement is delegated to the right layer (SessionManager
42
+ `--allowedTools`). This is the correct decomposition.
43
+
44
+ 4. **Signal vs authority.** Compliant. The tick produces signal (ledger findings) only — it never
45
+ merges code, advances graduation, or gates another component, and no mentee-delivery path is wired
46
+ yet. Authority stays with the human (§6/§8). The empty Stage-A tool grant is CLI-enforced, not
47
+ self-policed.
48
+
49
+ 5. **Interactions.** The runner reuses `ledger.captureRun` (funnel logs every run), `MentorStageA`
50
+ (empty grant + leak detector), and SessionManager.spawnSession. Min-interval + per-day counters
51
+ live on AgentServer and advance only via the `onTickRan` hook (so skipped ticks don't consume the
52
+ budget). A busy-session check prevents the mentor from interrupting other work. No shadowing of
53
+ existing sentinels; the mentor never kills/restarts anything.
54
+
55
+ 6. **External surfaces.** Two new routes behind Bearer auth (e2e: 401 without). A built-in job that
56
+ ships `enabled:false` (e2e asserts this). `mentor.*` config flows through the canonical
57
+ ConfigDefaults registry → existing agents get it via `applyDefaults` on migration (no bespoke
58
+ migrator block; matches `sessionReaper`/`topicIntent`). CapabilityIndex entry surfaces the routes.
59
+ No mentee-delivery path exists yet (Live-promotion blockers), so even `live` mode only observes.
60
+
61
+ 7. **Rollback cost.** Low. Dormant by default; revert removes the routes/job/config. No data
62
+ migration (the ledger/funnel tables are §19.1/19.2). The job auto-uninstalls when the template is
63
+ removed (installBuiltinJobs prunes slugs no longer in the templates dir).
64
+
65
+ ## Phase 5 — second-pass (REQUIRED here; deferred from §19.3)
66
+
67
+ This change spawns sub-agents and contains gating logic, so a dedicated reviewer audited it
68
+ independently. Their verdict is appended below (concur, 0 blocking concerns).
69
+
70
+ ## Live-promotion blockers (must close before `mentor.mode` → `live`) <!-- tracked: topic-13435 -->
71
+
72
+ The second-pass surfaced three items that are unreachable while dormant but MUST be closed before the
73
+ graduated-rollout flips the mentor to `live`. They are explicitly NOT ship-blockers for this dormant
74
+ change; they are the gate for activation:
75
+
76
+ 1. **No mentee-delivery path is wired.** The tick surfaces `stageAMessage` but nothing forwards it to
77
+ the mentee. The persist-only delivery (§6) must be built + tested before `live` can actually
78
+ contact anyone. (Code comments + Q1/Q4/Q6 above corrected to say this.)
79
+ 2. **Spawn poll robustness + real budget accounting.** `spawnStageA` polls 90×2s then captures the
80
+ pane; on poll-exhaustion it must kill the session and capture a `stage-a-timeout` finding rather
81
+ than read a partial transcript and orphan the tmux session. And `dailySpendCapUsd` is presently a
82
+ run-count cap (`maxRoundsPerDay`) — wire real cost accounting or rename the knob before live.
83
+ 3. **Async tick.** The 180s poll runs on the `POST /mentor/tick` request thread; before live, return
84
+ `202` + poll completion via `/mentor/status` (avoid the gate-latency-vs-client-timeout failure).
85
+
86
+ All three are gated behind `mentor.enabled=false` today and covered by the off→dry-run→live promotion
87
+ discipline (each step needs Justin's sign-off), so the dormant ship is safe.
88
+
89
+ ## Testing
90
+
91
+ - Tier 1 (unit): 8 `runMentorTick` (gate order: canary→budget→safe-window→Stage A→leak→Stage B→
92
+ capture; fail-closed budget before contact; leak capture; spawn-failure self-report) + 6 runner
93
+ (dormant short-circuit, busy/min-interval unsafe, status).
94
+ - Tier 2 (integration): 4 — `/mentor/status` 503/200; `/mentor/tick` disabled-by-default + runs-when-enabled.
95
+ - Tier 3 (e2e "alive"): 5 — real AgentServer boot, status 200-not-503 + dormant, tick disabled on the
96
+ production path, /capabilities surfaces it, 401 without auth, job template ships `enabled:false`.
97
+ - Affected push-config suite vs canonical main: 3431 + 300 capability tests green, no regressions.
98
+
99
+ ## Reviewer second-pass
100
+
101
+ **Concur with the review — with three non-blocking concerns to track.**
102
+
103
+ Independent audit of `MentorOnboardingTick.ts`, `MentorOnboardingRunner.ts`, the `buildMentorRunner`
104
+ wiring in `AgentServer.ts`, the `/mentor/status` + `/mentor/tick` handlers in `routes.ts`, and
105
+ `MentorStageA.ts`/`ConfigDefaults.ts`. The core safety properties hold:
106
+
107
+ - **Signal vs authority (Q1): clean.** The tick only writes to the read-only ledger via `capture`
108
+ and returns a `stageAMessage` string. It never kills/gates/blocks any component. `isMenteeBusy`
109
+ only makes the mentor skip *itself*. No brittle blocking authority anywhere.
110
+ - **Fail-closed / canary-first (Q2): real.** Order in `runMentorTick` is canary → budget →
111
+ safe-window → Stage A → leak → Stage B → capture. The budget gate (line 108) precedes the only
112
+ spawn (line 116). Every skip returns early before `capture`, so there is no partial
113
+ Stage-A-without-capture. Canary failure self-reports and halts.
114
+ - **Dormancy (Q3): genuinely inert.** `DEFAULT_MENTOR_CONFIG` and `ConfigDefaults.mentor` both ship
115
+ `enabled:false`/`mode:'off'`. `tick()` short-circuits to `{ran:false,reason:'disabled'}` BEFORE
116
+ touching any service. With defaults, `POST /mentor/tick` cannot spawn or spend. Verified by the
117
+ `enabled:true + mode:'off'` unit test.
118
+ - **Budget/min-interval counters (Q5): no double-count, no bypass.** `onTickRan` fires only when
119
+ `result.ran` (runner line 95); all five skip reasons return `ran:false` → counters do not advance
120
+ → skipped ticks consume no budget.
121
+ - **Two-hats (Q6): structurally enforced.** `STAGE_A_ALLOWED_TOOLS = []` is passed as
122
+ `allowedTools: [...STAGE_A_ALLOWED_TOOLS]`; the leak detector runs on the transcript and a leak is
123
+ captured as a finding. Empty-grant is CLI-enforced, not prompt-policed.
124
+
125
+ **Concern 1 (non-blocking) — the artifact overstates live-mode delivery; there is in fact NO
126
+ delivery path wired at all.** The artifact (lines 20, 44, 58) and the code comments
127
+ (`MentorOnboardingTick.ts:155-156`, `155` "delivered only in `live` mode by the runner") describe a
128
+ "persist-only path" that messages the mentee in `live` mode. That path does not exist: `tick()` in
129
+ `MentorOnboardingRunner.ts` returns `stageAMessage` and the route handler in `routes.ts:4275-4276`
130
+ simply JSON-returns the result; nothing ever forwards the message to the mentee (no
131
+ `messageRouter`/`relay`/`inject` call in the runner, tick, or route — grep-confirmed). This is
132
+ actually SAFER than advertised (live mode cannot contact anyone today — it is functionally
133
+ dry-run-plus-counter), so it is not a blocking safety issue. But it makes the "Signal vs authority /
134
+ live mode would message the mentee" framing inaccurate and means the highest-risk surface (actual
135
+ outbound contact) is entirely unbuilt and *untested*. The "delivered via the persist-only path"
136
+ comments should be corrected to "delivery is a tracked follow-on; live mode currently runs the loop
137
+ without contacting the mentee," and the live-delivery work tracked alongside the §19.4 follow-on
138
+ (topic-13435). Fix: amend the artifact + the two `MentorOnboardingTick.ts` comments; track delivery
139
+ as not-yet-built.
140
+
141
+ **Concern 2 (non-blocking) — the spawn poll can leak/orphan a Stage-A session, and the budget gate
142
+ counts ticks, not dollars.** In `buildMentorRunner` (`AgentServer.ts:675-680`) the spawn polls
143
+ 90×2s = 180s for the session to disappear from `listRunningSessions()`. `maxDurationMinutes:5` does
144
+ NOT bound the spawn within the poll window: the timeout is enforced lazily by the monitor tick with
145
+ a 20%-or-60-min buffer (`SessionManager.ts:598-602`), so a wedged Stage-A session is only reaped at
146
+ ~6 min — after the poll has already given up at 3 min. When the poll falls through, the code
147
+ captures whatever is on the pane and returns it as the "transcript" of an *unfinished* session
148
+ (garbage-in to the leak detector), while the tmux session keeps running until the monitor reaps it.
149
+ This is bounded (it will be reaped) and dormant today, but at live cadence (10-min floor, 24/day) a
150
+ chronically-slow mentee could overlap a still-running prior Stage-A against the next tick's
151
+ `isMenteeBusy` check (which would then correctly skip). Separately, `dailySpendCapUsd` (0.5) is
152
+ declared in config and the artifact calls the gate a "daily spend cap," but `budgetOk` only checks
153
+ `mentorRunsToday < maxRoundsPerDay` — it never accounts dollars. The cap is a *run-count* cap, not a
154
+ spend cap. Fix before live promotion: (a) treat poll-exhaustion distinctly from
155
+ session-completed — if the session is still running at 90 iterations, kill it explicitly and capture
156
+ a `stage-a-timeout` finding rather than returning a partial pane as a clean transcript; (b) either
157
+ wire `dailySpendCapUsd` to a real cost accounting (TokenLedger) or rename it / drop it from the
158
+ config + artifact so it isn't mistaken for a spend ceiling.
159
+
160
+ **Concern 3 (non-blocking) — the 180s synchronous poll runs on the request thread of
161
+ `POST /mentor/tick`.** `tick()` awaits `spawnStageA`, which awaits the 90×2s loop, so a live tick
162
+ holds the HTTP request open up to ~3 min. The built-in job is the only caller today and can tolerate
163
+ it, but any client (or the job runner's own HTTP timeout) hitting `/mentor/tick` will block or 408
164
+ mid-tick. This is the gate-latency-vs-client-timeout failure mode the team has hit before. The tick
165
+ core is non-blocking; the issue is purely the synchronous-spawn wiring in `buildMentorRunner`. Not a
166
+ dormant-mode risk. Fix before live: make `/mentor/tick` kick the tick and return `202 accepted`
167
+ (fire-and-poll via `/mentor/status`), or bound the request with an explicit handler timeout that is
168
+ distinct from spawn completion.
169
+
170
+ None of the three blocks dormant ship: with `enabled:false` the tick never runs, so the spawn/poll,
171
+ the (absent) delivery, and the spend-cap mislabel are all unreachable in production today. They are
172
+ promotion-blockers for `live`, not ship-blockers for this dormant change. They must be resolved (and
173
+ the delivery path actually built + tested) before `mentor.mode` is flipped to `live` — recommend the
174
+ graduated-rollout track gate `dry-run → live` on closing all three.
175
+
176
+ BLOCKING CONCERNS: 0
177
+
178
+ ## Post-push CI fixes
179
+
180
+ Two CI checks caught issues, both fixed:
181
+ - `route-completeness.test.ts` requires every `catch (err)` to pair with an `err instanceof Error`
182
+ check; the `/mentor/tick` handler used `(err as Error).message`. Switched to
183
+ `err instanceof Error ? err.message : String(err)`.
184
+ - `Docs Coverage` enforces a per-type floor; the new `mentor-onboarding` job dropped the `job`
185
+ floor (85%) since it was undocumented. Documented it in `reference/default-jobs.md` +
186
+ `architecture/the-living-system.md` (job coverage → 86%).