Skip to content

refactor: SQLite-first cutover for all local read paths#66

Merged
WellDunDun merged 12 commits intomainfrom
custom/prefix/router-1774017937646
Mar 20, 2026
Merged

refactor: SQLite-first cutover for all local read paths#66
WellDunDun merged 12 commits intomainfrom
custom/prefix/router-1774017937646

Conversation

@WellDunDun
Copy link
Collaborator

Summary

  • Adds canonical envelope columns (normalizer_version, capture_mode, raw_source_ref) to all 4 SQLite tables so upload staging reads from SQLite instead of JSONL
  • Replaces dashboard JSONL file watchers with WAL-based invalidation (fs.watchFile on the WAL)
  • Rewrites orchestrate.ts signal consumption to use SQLite directly (no JSONL rewrite)
  • Labels all remaining JSONL reads as test/custom-path fallbacks only
  • Prunes exec plans from 16 to 4 active (5 completed, 7 deferred)
  • Updates ARCHITECTURE.md, Dashboard.md, Doctor.md, and design docs to reflect shipped state

Test plan

  • bun test tests/alpha-upload/staging.test.ts — 25 pass, 0 fail (validates SQLite staging path)
  • bun test tests/observability.test.ts — validates doctor reports "pass" for dashboard freshness
  • bun test full suite — 1661 pass, 12 pre-existing fail, 0 new failures
  • Manual: run selftune dashboard and verify WAL-based live updates work
  • Manual: run selftune doctor and confirm dashboard_freshness_mode shows "pass"

🤖 Generated with Claude Code

WellDunDun and others added 4 commits March 20, 2026 16:40
.agents/ and skills-lock.json are dev tooling installed via `npx skills add`.
They were being shipped to users via `npx skills add selftune-dev/selftune`,
causing Reins to appear as part of the selftune skill. Now gitignored so
only the intended selftune skill in skill/ is visible to consumers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the manual API key provisioning path (--alpha-key flag) from
the CLI. Alpha enrollment now exclusively uses the device-code flow:
browser opens automatically, user approves, credentials are provisioned
and stored without any manual copy-paste.

Also removes zod schemas from telemetry-contract — the cloud repo
(gwangju-v1) already owns its own validation schemas in @selftune/shared.
The OSS CLI only needs types and hand-written validators (zero deps).

- Remove --alpha-key flag from init CLI and InitOptions interface
- Remove direct-key code branch from runInit()
- Update agent-guidance to reference device-code flow
- Update flush.ts error messages
- Update Initialize.md, Doctor.md, SKILL.md, alpha-remote-data-contract.md
- Delete telemetry-contract/src/schemas.ts (zod dependency)
- Remove zod from telemetry-contract package.json
- Rewrite tests to mock device-code flow instead of using alphaKey
- Add SELFTUNE_NO_BROWSER env var to suppress browser open in tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add canonical envelope columns (normalizer_version, capture_mode,
  raw_source_ref) to all 4 SQLite tables with migrations for existing DBs
- Store envelope fields during direct-write inserts so SQLite records
  pass isCanonicalRecord() validation
- Rewrite queryCanonicalRecordsForStaging() to reconstruct full
  contract-compliant records with session-level envelope fallback
- Replace dashboard JSONL file watchers with WAL-based invalidation
- Mark orchestrate signal consumption as SQLite-only (no JSONL rewrite)
- Label remaining JSONL reads as test/custom-path fallbacks only
- Update ARCHITECTURE.md, Dashboard.md, Doctor.md to reflect shipped state
- Prune exec plans: 5 completed, 7 deferred, 4 remain active

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Warning

Rate limit exceeded

@WellDunDun has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 6 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28fbf452-be79-453d-80fb-9facbda6d3d5

📥 Commits

Reviewing files that changed from the base of the PR and between fd4c8cc and 67b2e74.

📒 Files selected for processing (4)
  • biome.json
  • cli/selftune/init.ts
  • scripts/link-claude-workspace.sh
  • tests/localdb/read-queries.test.ts
📝 Walkthrough

Walkthrough

Makes SQLite the primary runtime read-store (WAL-driven SSE), removes local Alpha API key CLI support in favor of device-code/browser enrollment, extends local DB schema for canonical metadata, deletes Reins skill docs/lock, relaxes telemetry schema validations, and updates CLI, tests, and docs to the SQLite-first flows.

Changes

Cohort / File(s) Summary
Reins skill docs & lock
​.agents/skills/reins/HarnessMethodology.md, ​.agents/skills/reins/SKILL.md, ​.agents/skills/reins/Workflows/*, skills-lock.json
Removed Reins harness documentation and its lock entry.
Alpha enrollment / CLI & tests
cli/selftune/init.ts, cli/selftune/agent-guidance.ts, cli/selftune/alpha-identity.ts, cli/selftune/alpha-upload/flush.ts, skill/SKILL.md, skill/Workflows/Initialize.md, tests/init/*, tests/init/*-e2e.test.ts, tests/alpha-upload/*
Removed --alpha-key flow; enrollment now device-code/browser based. CLI messages, docs, and tests updated to require/expect device-code approval and to validate persisted identity fields.
SQLite-first read patterns
cli/selftune/activation-rules.ts, cli/selftune/eval/hooks-to-evals.ts, cli/selftune/hooks/evolution-guard.ts, cli/selftune/repair/skill-usage.ts, cli/selftune/grading/*, cli/selftune/monitoring/watch.ts, cli/selftune/contribute/bundle.ts, cli/selftune/hooks/auto-activate.ts, cli/selftune/normalization.ts, cli/selftune/metrics/*
Default data reads now prefer SQLite when paths equal defaults; JSONL is explicit fallback for overrides/tests. Removed broad try/catch fallbacks in several places and added clarifying comments.
Local DB schema & IO
cli/selftune/localdb/schema.ts, cli/selftune/localdb/direct-write.ts, cli/selftune/localdb/queries.ts, tests/localdb/*
Added canonical metadata columns (schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref) with migrations, write bindings, and new queryCanonicalRecordsForStaging(db) emitting typed record envelopes.
Dashboard WAL migration
cli/selftune/dashboard-server.ts, cli/selftune/dashboard-contract.ts, cli/selftune/observability.ts, apps/local-dashboard/src/components/runtime-footer.tsx, skill/Workflows/Dashboard.md, docs/design-docs/live-dashboard-sse.md, docs/design-docs/sqlite-first-migration.md
Replaced JSONL watchers with SQLite WAL fs.watchFile() (500ms debounce). Health contract accepts watcher_mode: "wal". Docs and checks updated to reflect WAL-based SSE as shipped.
Signal consumption / orchestrate
cli/selftune/orchestrate.ts, tests/signal-orchestrate.test.ts
Removed JSONL signal-log I/O. markSignalsConsumed now updates per-signal rows via updateSignalConsumed(...); signature simplified and tests converted to DB seeding/queries.
Upload staging / alpha upload
cli/selftune/alpha-upload/stage-canonical.ts, cli/selftune/alpha-upload/flush.ts, tests/alpha-upload/*
Staging reads canonical records from SQLite by default (CANONICAL_LOG); JSONL only when path overridden. Added tests for SQLite staging and structural payload assertions.
Telemetry contract & schemas
packages/telemetry-contract/src/schemas.ts, packages/telemetry-contract/src/index.ts, packages/telemetry-contract/package.json
Relaxed zod validations (e.g., push_id, record keys), removed schemas re-export, and bumped zod dependency to ^4.3.6.
Docs, health checks & UX
AGENTS.md, ARCHITECTURE.md, skill/references/logs.md, skill/Workflows/Doctor.md, various docs/design-docs/*
Docs updated to show WAL-only live updates, JSONL as backup/test-only, and new CLI/device-code examples. Health checks now mark freshness as pass with WAL message.
Tests & tooling
tests/* (init, alpha-upload, observability, trust-floor, staging, signal-orchestrate, localdb, orchestrate, hooks, etc.)
Adjusted many tests for WAL watcher mode, device-code alpha flow, SQLite-first reads, schema additions, DB-centric signal handling, and staging payload structure.
Misc / packaging / ignore
.gitignore, package.json, apps/local-dashboard/package.json, cli/selftune/types.ts, cli/selftune/orchestrate.ts, scripts/link-claude-workspace.sh
Added .agents/ and skills-lock.json to .gitignore, bumped version 0.2.8→0.2.9, added workspace devDep for telemetry-contract, adjusted re-export paths, and added a workspace linking script.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (selftune)
    participant Auth as Alpha Auth Server
    participant Browser as Browser/User
    participant DB as Local DB (SQLite)
    participant SSE as Dashboard SSE Server

    CLI->>Auth: POST /device-code (request device + user codes)
    Auth-->>CLI: {device_code, user_code, verification_uri}
    CLI->>Browser: open verification_uri
    Browser->>Auth: user approves
    CLI->>Auth: poll /device-code/poll
    Auth-->>CLI: approved {api_key, cloud_user_id, org_id}
    CLI->>DB: persist api_key + cloud_user_id (write)
    CLI->>DB: optionally stage canonical records
    DB->>DB: WAL entries appended
    SSE->>DB: fs.watchFile(wal) notices change
    SSE->>Clients: broadcast SSE "update"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commits format with 'refactor' prefix and clearly describes the main change: SQLite-first cutover for all local read paths.
Description check ✅ Passed The description is directly related to the changeset, providing a detailed summary of the SQLite migration work, test plan, and documentation updates included in the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch custom/prefix/router-1774017937646

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cli/selftune/hooks/evolution-guard.ts (1)

48-73: 🧹 Nitpick | 🔵 Trivial

SQLite-first implementation is correct.

The removal of the try/catch fallback for the default path aligns with the SQLite-first cutover. One minor observation: queryEvolutionAudit(db, skillName) already filters by skill name (per the relevant code snippet showing the SQL query), so the subsequent .filter((e) => e.skill_name === skillName) on line 68 is redundant. Not blocking, but could simplify.

♻️ Optional: remove redundant filter
   if (auditLogPath === EVOLUTION_AUDIT_LOG) {
     const { getDb } = await import("../localdb/db.js");
     const { queryEvolutionAudit } = await import("../localdb/queries.js");
     const db = getDb();
-    entries = queryEvolutionAudit(db, skillName) as Array<{
+    const skillEntries = queryEvolutionAudit(db, skillName) as Array<{
       skill_name?: string;
       action: string;
     }>;
+    if (skillEntries.length === 0) return false;
+    const lastEntry = skillEntries[skillEntries.length - 1];
+    return lastEntry.action === "deployed";
   } else {
     // test/custom-path fallback
     entries = readJsonl<{ skill_name?: string; action: string }>(auditLogPath);
   }
-
-  // Filter entries for this skill by skill_name field
-  const skillEntries = entries.filter((e) => e.skill_name === skillName);
-  if (skillEntries.length === 0) return false;
-
-  const lastEntry = skillEntries[skillEntries.length - 1];
-  return lastEntry.action === "deployed";
+  // JSONL path still needs filtering
+  const skillEntries = entries.filter((e) => e.skill_name === skillName);
+  if (skillEntries.length === 0) return false;
+  const lastEntry = skillEntries[skillEntries.length - 1];
+  return lastEntry.action === "deployed";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/hooks/evolution-guard.ts` around lines 48 - 73, The skill_name
filter after querying the DB is redundant because queryEvolutionAudit(db,
skillName) already returns only matching rows; in checkActiveMonitoring replace
the post-query filtering (the entries.filter(...) that creates skillEntries) by
using the entries array directly: check entries.length === 0, set lastEntry =
entries[entries.length - 1], and return lastEntry.action === "deployed"; keep
the JSONL fallback path unchanged (readJsonl still needs filtering for custom
paths if those logs include other skills).
tests/signal-orchestrate.test.ts (1)

211-254: ⚠️ Potential issue | 🟡 Minor

Add DB cleanup or use isolated in-memory database.

This test writes to the singleton SQLite DB via writeImprovementSignalToDb() but the afterEach hook only removes the temp directory—not the database. The sessions s1, s2, s3 remain in the DB and will contaminate subsequent tests.

Either:

  • Call _setTestDb(openDb(":memory:")) in a beforeEach or at test start, then reset in afterEach
  • Add explicit DB cleanup (clear the improvement_signals table for these session IDs) in afterEach
  • Use expect().toHaveLength() with optional chaining on query results instead of non-null assertions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/signal-orchestrate.test.ts` around lines 211 - 254, This test mutates
the shared SQLite DB and must isolate state; before running markSignalsConsumed
call _setTestDb(openDb(":memory:")) (or in a beforeEach) to switch the singleton
to an in-memory DB, then seed signals with writeImprovementSignalToDb and run
the test; in afterEach reset/close the test DB (or call _setTestDb(null) /
reopen the default) to avoid leaking rows in the improvement_signals table
between tests. Use the _setTestDb and openDb helpers to create/reset the DB and
ensure cleanup in afterEach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ARCHITECTURE.md`:
- Around line 218-219: Update ARCHITECTURE.md to consistently reflect the
SQLite-first runtime and WAL-based invalidation (remove or rewrite any
JSONL-centric descriptions); search for and replace references to JSONL file
watchers/orchestrate behavior with SQLite/local DB terms and mention WAL-based
SSE invalidation; also, when you change any logging or schema references, update
the corresponding symbols/files: constants.ts, types.ts,
skill/references/logs.md, localdb/schema.ts, materialize.ts, direct-write.ts,
and queries.ts to keep code/docs in sync per the noted checklist.

In `@cli/selftune/activation-rules.ts`:
- Around line 118-125: The code currently uses the last array element of
auditEntries (from queryEvolutionAudit or readJsonl) as the "last" evolution
timestamp, but queryEvolutionAudit yields rows in descending timestamp order so
the last element is the oldest; replace any use of
auditEntries[auditEntries.length - 1] (and the similar usage later around the
stale-evolution rule) with logic that computes the newest timestamp explicitly
(e.g., take auditEntries[0] if you know the source is descending, or more
robustly compute the max timestamp via a reduce over auditEntries) so both
branches (when ctx.evolution_audit_log_path === EVOLUTION_AUDIT_LOG and when
using readJsonl) use the actual most recent timestamp.

In `@cli/selftune/agent-guidance.ts`:
- Around line 41-45: The guidance currently includes a concrete "--alpha-email
<email>" flag even when no real email is known; update buildGuidance calls that
use buildAlphaInitCommand (e.g., the "alpha_cloud_link_required" case and the
other two occurrences noted) so buildAlphaInitCommand only returns a
next_command containing the --alpha-email flag when options?.email is a
non-empty/valid string; otherwise return the runnable command without the
--alpha-email flag (so runInit can execute the suggested command safely).

In `@cli/selftune/alpha-upload/stage-canonical.ts`:
- Around line 153-156: queryCanonicalRecordsForStaging(db) currently causes
O(n²) behavior because code in cli/selftune/localdb/queries.ts does per-record
lookups with sessions.find(...) when enriching
prompts/invocations/execution_facts; fix by building a sessions map once
(sessionById) before the loops and use sessionById.get(sessionId) (or
sessionById[sessionId]) inside the prompt/invocation/execution_fact enrichment
loops instead of sessions.find, and update the enrichRecord/path that consumes
those lookups so queryCanonicalRecordsForStaging(db) returns the same
CanonicalRecord[] but with O(n) lookup performance.

In `@cli/selftune/dashboard-contract.ts`:
- Line 202: Update the runtime-footer type guard that checks record.watcher_mode
in the component (runtime-footer.tsx) to accept the new "wal" value: locate the
conditional that currently reads (record.watcher_mode === "jsonl" ||
record.watcher_mode === "none") and either add record.watcher_mode === "wal" to
that OR-list or remove the manual guard entirely so you rely on the updated
dashboard-contract type for validity; ensure any downstream logic that depended
on excluding other values still behaves correctly after allowing "wal".

In `@cli/selftune/dashboard-server.ts`:
- Around line 253-254: The unwatch currently removes all watchers because
unwatchFile(walPath) is called without the specific listener; change the stop
logic in startDashboardServer so it calls unwatchFile(walPath, onWALChange)
(i.e., pass the same onWALChange callback that was passed to watchFile) and only
flip walWatcherActive for this instance when you successfully unwatch; reference
the onWALChange callback, walWatcherActive flag, and startDashboardServer so the
watcher removal is instance-scoped and does not remove other dashboard server
watchers.

In `@cli/selftune/init.ts`:
- Around line 511-516: The fast-path in runInit() prevents the opts.alpha /
opts.noAlpha branches from running on already-initialized installs; modify
runInit() (or its call site) so the device-code branch controlled by opts.alpha
is evaluated before returning the existing-config fast path, or add an explicit
parameter/flag to runInit(e.g., forceAlphaCheck or allowAlphaOverride) that
disables the early return when opts.alpha or opts.noAlpha are present; ensure
the check for opts.alpha (and opts.noAlpha) in init.ts executes prior to any
early exit so the device-code flow (requestDeviceCode, grant handling) runs and
the subsequent cliMain() success event accurately reflects the performed action.
- Around line 546-553: The validatedAlphaIdentity construction is overwriting
stored alpha metadata with undefined when --alpha-email/--alpha-name are not
provided; update the assignment so email and display_name fallback to
existingAlphaBeforeOverwrite values instead of becoming undefined (e.g., email:
opts.alphaEmail ?? existingAlphaBeforeOverwrite?.email and display_name:
opts.alphaName ?? existingAlphaBeforeOverwrite?.display_name), keep user_id
logic using existingAlphaBeforeOverwrite?.user_id ?? generateUserId(), and
ensure consent_timestamp and enrolled handling remain unchanged so re-auth does
not clear prior metadata.

In `@cli/selftune/localdb/queries.ts`:
- Around line 591-730: In queryCanonicalRecordsForStaging, replace repeated
sessions.find(...) lookups with a prebuilt Map keyed by session_id: construct a
Map from the sessions array (e.g., const sessionById = new Map(sessions.map(s =>
[s.session_id, s])) ) right after sessions is populated, then use
sessionById.get(p.session_id) in the prompts loop and similarly use
sessionById.get(si.session_id) and sessionById.get(ef.session_id) in the
invocations and facts loops to avoid O(n) scans.

In `@cli/selftune/orchestrate.ts`:
- Around line 131-133: The loop over signals currently calls
updateSignalConsumed(signal.session_id, signal.query, signal.signal_type, runId)
and ignores its return value/errors, allowing failed writes to be silently
dropped; change this to await the updateSignalConsumed call, check its success
(or catch exceptions), and handle failures explicitly—e.g., log an error with
identifying values (session_id, query, signal_type, runId), optionally retry a
few times, and if retries fail either collect and surface the failed signal IDs
or throw to fail the run; ensure references to updateSignalConsumed, signals,
and runId are used in the error handling so failures are observable and not
silently ignored.

In `@skill/Workflows/Dashboard.md`:
- Around line 14-18: Update the "Live Updates (SSE)" section to remove
references to JSONL file watchers and explicitly state that live updates are
driven by SQLite WAL-based invalidation (Server-Sent Events) only; replace any
language about watching JSONL files with a description of WAL-triggered SSE
updates, keep the note that TanStack Query polling (60s) is the fallback, and
retain the `selftune export` mention solely as a debugging/offline JSONL export
tool rather than a mechanism for live updates.

In `@tests/alpha-upload/staging.test.ts`:
- Around line 711-718: The test currently uses non-null assertions (result! and
c.evolution_evidence!) which violates the tests guideline; instead, after the
initial existence checks for result and c.evolution_evidence add an explicit
guard (e.g., if (!result) return fail(...) or throw) so subsequent lines can
safely reference result.payload and c.evolution_evidence without using !; update
the assertions around payload (p) and any uses at lines ~735-737 to rely on that
guard and normal property access rather than optional chaining everywhere,
ensuring you reference the existing variables result, p, and
c.evolution_evidence when applying the guard.

In `@tests/signal-orchestrate.test.ts`:
- Around line 212-215: Replace the CommonJS requires for
writeImprovementSignalToDb, queryImprovementSignals, and getDb with top-level ES
module imports: add import statements for writeImprovementSignalToDb from
"../cli/selftune/localdb/direct-write.js", queryImprovementSignals from
"../cli/selftune/localdb/queries.js", and getDb from
"../cli/selftune/localdb/db.js" at the top of the test file and remove the
require(...) lines; keep using the imported symbols (writeImprovementSignalToDb,
queryImprovementSignals, getDb) unchanged in the test code.
- Around line 256-264: Two identical tests assert that markSignalsConsumed([],
runId) is a no-op; remove the duplicate to avoid redundant coverage. Delete one
of the tests (either the "handles empty pending signals" or "handles empty
pending signals (no-op)" block) in tests/signal-orchestrate.test.ts so only a
single test calls expect(() => markSignalsConsumed([], "run_123" or
"run_456")).not.toThrow(); keep the descriptive test name you prefer and ensure
markSignalsConsumed is still imported/used by the remaining test.

---

Outside diff comments:
In `@cli/selftune/hooks/evolution-guard.ts`:
- Around line 48-73: The skill_name filter after querying the DB is redundant
because queryEvolutionAudit(db, skillName) already returns only matching rows;
in checkActiveMonitoring replace the post-query filtering (the
entries.filter(...) that creates skillEntries) by using the entries array
directly: check entries.length === 0, set lastEntry = entries[entries.length -
1], and return lastEntry.action === "deployed"; keep the JSONL fallback path
unchanged (readJsonl still needs filtering for custom paths if those logs
include other skills).

In `@tests/signal-orchestrate.test.ts`:
- Around line 211-254: This test mutates the shared SQLite DB and must isolate
state; before running markSignalsConsumed call _setTestDb(openDb(":memory:"))
(or in a beforeEach) to switch the singleton to an in-memory DB, then seed
signals with writeImprovementSignalToDb and run the test; in afterEach
reset/close the test DB (or call _setTestDb(null) / reopen the default) to avoid
leaking rows in the improvement_signals table between tests. Use the _setTestDb
and openDb helpers to create/reset the DB and ensure cleanup in afterEach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d28db92-ed50-42e9-a273-16c5edbf5aac

📥 Commits

Reviewing files that changed from the base of the PR and between 93de673 and a13f12d.

📒 Files selected for processing (63)
  • .agents/skills/reins/HarnessMethodology.md
  • .agents/skills/reins/SKILL.md
  • .agents/skills/reins/Workflows/Audit.md
  • .agents/skills/reins/Workflows/Doctor.md
  • .agents/skills/reins/Workflows/Evolve.md
  • .agents/skills/reins/Workflows/Scaffold.md
  • .gitignore
  • AGENTS.md
  • ARCHITECTURE.md
  • cli/selftune/activation-rules.ts
  • cli/selftune/agent-guidance.ts
  • cli/selftune/alpha-identity.ts
  • cli/selftune/alpha-upload/flush.ts
  • cli/selftune/alpha-upload/stage-canonical.ts
  • cli/selftune/contribute/bundle.ts
  • cli/selftune/dashboard-contract.ts
  • cli/selftune/dashboard-server.ts
  • cli/selftune/eval/hooks-to-evals.ts
  • cli/selftune/grading/auto-grade.ts
  • cli/selftune/grading/grade-session.ts
  • cli/selftune/hooks/auto-activate.ts
  • cli/selftune/hooks/evolution-guard.ts
  • cli/selftune/init.ts
  • cli/selftune/localdb/direct-write.ts
  • cli/selftune/localdb/queries.ts
  • cli/selftune/localdb/schema.ts
  • cli/selftune/monitoring/watch.ts
  • cli/selftune/observability.ts
  • cli/selftune/orchestrate.ts
  • cli/selftune/repair/skill-usage.ts
  • cli/selftune/sync.ts
  • docs/design-docs/alpha-remote-data-contract.md
  • docs/design-docs/live-dashboard-sse.md
  • docs/design-docs/sqlite-first-migration.md
  • docs/exec-plans/completed/dashboard-data-integrity-recovery.md
  • docs/exec-plans/completed/dashboard-signal-integration.md
  • docs/exec-plans/completed/local-sqlite-materialization.md
  • docs/exec-plans/completed/output-quality-loop-prereqs.md
  • docs/exec-plans/completed/telemetry-normalization.md
  • docs/exec-plans/deferred/advanced-skill-patterns-adoption.md
  • docs/exec-plans/deferred/cloud-auth-unification-for-alpha.md
  • docs/exec-plans/deferred/grader-prompt-evals.md
  • docs/exec-plans/deferred/mcp-tool-descriptions.md
  • docs/exec-plans/deferred/multi-agent-sandbox.md
  • docs/exec-plans/deferred/phase-d-marginal-case-review-spike.md
  • docs/exec-plans/deferred/user-owned-session-data-and-org-visible-outcomes.md
  • package.json
  • packages/telemetry-contract/package.json
  • packages/telemetry-contract/src/index.ts
  • packages/telemetry-contract/src/schemas.ts
  • skill/SKILL.md
  • skill/Workflows/Dashboard.md
  • skill/Workflows/Doctor.md
  • skill/Workflows/Initialize.md
  • skill/references/logs.md
  • skills-lock.json
  • tests/alpha-upload/staging.test.ts
  • tests/alpha-upload/status.test.ts
  • tests/init/alpha-consent.test.ts
  • tests/init/alpha-onboarding-e2e.test.ts
  • tests/observability.test.ts
  • tests/signal-orchestrate.test.ts
  • tests/trust-floor/health.test.ts
💤 Files with no reviewable changes (10)
  • packages/telemetry-contract/src/index.ts
  • skills-lock.json
  • packages/telemetry-contract/package.json
  • .agents/skills/reins/Workflows/Doctor.md
  • .agents/skills/reins/Workflows/Scaffold.md
  • .agents/skills/reins/HarnessMethodology.md
  • .agents/skills/reins/Workflows/Evolve.md
  • .agents/skills/reins/SKILL.md
  • .agents/skills/reins/Workflows/Audit.md
  • packages/telemetry-contract/src/schemas.ts

Comment on lines +218 to +219
The dashboard uses WAL-based invalidation for SSE live updates — JSONL file
watchers have been removed from the dashboard server.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Architecture doc is still internally inconsistent on runtime data paths.

Line 218–Line 219 correctly states JSONL dashboard watchers were removed, but other sections still describe JSONL-centric orchestrate/runtime behavior. Please align those remaining sections so the document consistently reflects SQLite-first reads/writes.

Based on learnings: "When changing JSONL log schema or adding a new log file, update constants.ts, types.ts, skill/references/logs.md, and all database-related files (localdb/schema.ts, materialize.ts, direct-write.ts, queries.ts), plus ARCHITECTURE.md data architecture".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ARCHITECTURE.md` around lines 218 - 219, Update ARCHITECTURE.md to
consistently reflect the SQLite-first runtime and WAL-based invalidation (remove
or rewrite any JSONL-centric descriptions); search for and replace references to
JSONL file watchers/orchestrate behavior with SQLite/local DB terms and mention
WAL-based SSE invalidation; also, when you change any logging or schema
references, update the corresponding symbols/files: constants.ts, types.ts,
skill/references/logs.md, localdb/schema.ts, materialize.ts, direct-write.ts,
and queries.ts to keep code/docs in sync per the noted checklist.

Comment on lines +711 to +718
const p = result!.payload;
// Envelope fields
expect(p.schema_version).toBe("2.0");
expect(typeof p.client_version).toBe("string");
expect(p.push_id).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
);
expect(typeof p.normalizer_version).toBe("string");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid non-null assertions in this Bun test.

result! and c.evolution_evidence! are exactly the pattern the test guideline asks us not to use here. Add an explicit guard after the existence checks so the rest of the assertions narrow naturally.

Suggested fix
-    const p = result!.payload;
+    if (!result) throw new Error("expected staged payload");
+    const p = result.payload;
@@
-    const ev = c.evolution_evidence![0];
+    const ev = c.evolution_evidence?.[0];
+    expect(ev).toBeDefined();

As per coding guidelines, tests/**/*.ts should use optional chaining (result?.) and avoid non-null assertions (result!).

Also applies to: 735-737

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/alpha-upload/staging.test.ts` around lines 711 - 718, The test
currently uses non-null assertions (result! and c.evolution_evidence!) which
violates the tests guideline; instead, after the initial existence checks for
result and c.evolution_evidence add an explicit guard (e.g., if (!result) return
fail(...) or throw) so subsequent lines can safely reference result.payload and
c.evolution_evidence without using !; update the assertions around payload (p)
and any uses at lines ~735-737 to rely on that guard and normal property access
rather than optional chaining everywhere, ensuring you reference the existing
variables result, p, and c.evolution_evidence when applying the guard.

WellDunDun and others added 4 commits March 20, 2026 18:14
- Fix stale-evolution rule using oldest instead of newest audit entry
  (queryEvolutionAudit returns DESC order, so use [0] not [length-1])
- Fix agent guidance emitting placeholder email in next_command
- Add "wal" to runtime-footer type guard in dashboard SPA
- Pass specific listener to unwatchFile() to avoid removing all watchers
- Preserve existing alpha identity metadata on re-auth without flags
- Let alpha mutations bypass existing-config fast path in runInit()
- Replace O(n²) sessions.find() with Map lookup in staging query
- Log failed signal consumed writes in orchestrate
- Fix Dashboard.md SSE section still referencing JSONL watchers
- Remove non-null assertions in staging test
- Convert require() to ESM imports in signal-orchestrate test
- Remove duplicate empty-signals test case

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused readJsonl and queryImprovementSignals imports
- Sort imports alphabetically per biome organizeImports rule
- Format function signatures and long lines per biome formatter
- Use template literal for WAL path concatenation
- Update formatAlphaStatus tests for new guidance without placeholder email
- Remove trailing blank line from duplicate test removal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Writes canonical records directly into SQLite tables (simulating the
runtime hook path), then calls stageCanonicalRecords(db) with default
args to exercise the SQLite read path. Verifies all 4 record kinds
are staged and envelope fields survive the round-trip.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…taging test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/signal-orchestrate.test.ts (1)

214-240: ⚠️ Potential issue | 🟠 Major

Isolate this SQLite-backed test from shared DB state.

getDb() is a process-wide singleton, and writeImprovementSignalToDb() uses INSERT OR IGNORE. With fixed keys (s1/s2/s3 plus fixed timestamps), this setup can silently reuse existing rows unless improvement_signals is cleared elsewhere, which makes the assertions order-dependent. Seed unique keys per test or clear those rows in setup/teardown. As per coding guidelines, tests/**/*.ts: "Isolated tests with proper setup/teardown".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/signal-orchestrate.test.ts` around lines 214 - 240, The test is reusing
process-wide SQLite state so fixed keys/timestamps can collide; before seeding
call getDb() and either delete existing rows from the improvement_signals table
for the session_ids used or generate unique session_id/timestamp values per
test, then use writeImprovementSignalToDb(...) to insert and
markSignalsConsumed(...) to consume; specifically update the test to call
getDb().exec("DELETE FROM improvement_signals WHERE session_id IN (...)") or
produce unique session_ids (not "s1"/"s2"/"s3") so the assertions on
improvement_signals reflect only this test's rows.
♻️ Duplicate comments (3)
cli/selftune/activation-rules.ts (1)

135-136: ⚠️ Potential issue | 🟠 Major

Compute the newest audit timestamp explicitly here.

auditEntries[0] is only correct for the SQLite branch because queryEvolutionAudit() sorts DESC. The JSONL fallback keeps file order, so test/custom-path overrides can still read the wrong entry and falsely trigger stale-evolution. Reduce the entries to the max finite parsed timestamp before computing ageMs.

Suggested fix
-    const lastEntry = auditEntries[0]; // queryEvolutionAudit returns DESC order
-    const lastTimestamp = new Date(lastEntry.timestamp).getTime();
+    const lastTimestamp = auditEntries.reduce((max, entry) => {
+      const ts = Date.parse(entry.timestamp);
+      return Number.isFinite(ts) && ts > max ? ts : max;
+    }, Number.NEGATIVE_INFINITY);
+    if (!Number.isFinite(lastTimestamp)) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/activation-rules.ts` around lines 135 - 136, The code currently
assumes auditEntries[0] is the newest entry; instead iterate/reduce auditEntries
to parse each entry.timestamp into a finite number and take the maximum to
produce lastTimestamp (then compute ageMs). Update the logic around variables
auditEntries, lastEntry/lastTimestamp and where ageMs is computed (the consumer
of queryEvolutionAudit()) to use the reduced max timestamp so JSONL (file-order)
and SQLite both yield the correct newest audit time.
cli/selftune/agent-guidance.ts (1)

8-10: ⚠️ Potential issue | 🟠 Major

Keep next_command free of raw email values.

next_command is a machine-executable shell string, and this change now splices stored user input into it without quoting or escaping. A malformed email can turn the guidance into an invalid command or inject extra shell tokens. Since --alpha-email is optional, the safer fix is to omit it from next_command.

Suggested fix
 function buildAlphaInitCommand(options?: {
-  email?: string;
   force?: boolean;
 }): string {
   const parts = ["selftune", "init", "--alpha"];
-  if (options?.email?.trim()) {
-    parts.push("--alpha-email", options.email);
-  }
   if (options?.force) {
     parts.push("--force");
   }
   return parts.join(" ");
 }
-        buildAlphaInitCommand({ email: options?.email }),
+        buildAlphaInitCommand(),
...
-        buildAlphaInitCommand({ email: options?.email, force: true }),
+        buildAlphaInitCommand({ force: true }),
...
-        buildAlphaInitCommand({ email: options?.email, force: true }),
+        buildAlphaInitCommand({ force: true }),
Based on learnings, "selftune is agent-first: users interact through their coding agent, not the CLI directly. SKILL.md and workflow docs are the product surface; the CLI is the agent's API." Also, "Error messages should guide the agent, not the human (e.g., suggest the next CLI command, not 'check the docs')."

Also applies to: 42-43, 50-51, 58-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/agent-guidance.ts` around lines 8 - 10, The constructed shell
token list 'parts' (used to form next_command) must not include raw user email
values; remove the push of "--alpha-email" and options.email into parts and
ensure any use of options.email is omitted from next_command generation (leave
the email flag out entirely when building parts/next_command), while preserving
other flags; update code paths that push email (references to parts,
options?.email, and next_command) so next_command never contains the unescaped
user-provided email.
cli/selftune/init.ts (1)

472-475: ⚠️ Potential issue | 🟠 Major

Reject or apply standalone alpha metadata flags consistently.

--alpha-email and --alpha-name now bypass the existing-config fast path, but runInit() only uses them inside the opts.alpha branch. That makes selftune init --alpha-name Bob a silent no-op rewrite, and cliMain() still disagrees with runInit() on empty-string values. Either require --alpha when those flags are present or merge them into the restored AlphaIdentity, and keep the mutation predicate identical in both places.

Based on learnings, "selftune is agent-first: users interact through their coding agent, not the CLI directly. SKILL.md and workflow docs are the product surface; the CLI is the agent's API."

Also applies to: 650-655

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/init.ts` around lines 472 - 475, The fast-path predicate in
cliMain() uses hasAlphaMutation (opts.alpha || opts.noAlpha || opts.alphaEmail
!== undefined || opts.alphaName !== undefined) but runInit() only applies
alphaEmail/alphaName when opts.alpha is true, causing silent no-ops; fix by
making behavior consistent: either (A) require opts.alpha when opts.alphaEmail
or opts.alphaName are set (validate and error early in cliMain()/parse stage),
or (B) if restoring an AlphaIdentity in runInit(), merge
opts.alphaEmail/opts.alphaName into the restored AlphaIdentity even when
opts.alpha is false so the mutation predicate matches runInit(); update the
logic around configPath existence, hasAlphaMutation, and the restoration code in
runInit()/AlphaIdentity handling to use the same predicate and ensure
empty-string values are honored consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/selftune/activation-rules.ts`:
- Around line 29-35: Wrap all SQLite access points (the getDb() and query*()
usages that branch on ctx.query_log_path === QUERY_LOG) in try/catch blocks so
DB errors don't propagate; on failure return the same safe fallback as the JSONL
path (either null for single-session reads or an empty array for lists) instead
of throwing. Specifically modify the blocks around the existing query reads (the
sequence using getDb() and queryQueryLog), the similar reads referenced at lines
42-50, and the reads around lines 118-125 to catch any exceptions from getDb()
or the query* functions and return null/[] accordingly so activation rules fail
open. Ensure you keep existing variable shapes (e.g., queries: Array<{
session_id: string; query: string }>) and only change error handling.

In `@cli/selftune/localdb/queries.ts`:
- Around line 644-647: The nullish-coalescing chain used to compute
raw_source_ref is misformatted; update the expression in the construction that
assigns raw_source_ref so it conforms to Biome styling (place each
operand/operator on its own properly indented line or wrap the chain in
parentheses) — locate the assignment that calls safeParseJson(p.raw_source_ref
as string | null) ?? safeParseJson(sessionEnvelope?.raw_source_ref as string |
null) ?? undefined and reformat it (alongside the nearby source_session_kind
line) so Biome formatter accepts it, ensuring references to safeParseJson,
raw_source_ref, and sessionEnvelope remain unchanged.
- Around line 696-727: While rebuilding execution_fact records in the loop that
iterates over facts (from the SELECT in queries.ts), include and preserve the
original execution_fact id by adding an execution_fact_id field set to ef.id
when pushing into records; update the records.push call (the object created for
"execution_fact") to emit execution_fact_id: ef.id so downstream consumers
retain the canonical ID instead of generating a new one.

In `@cli/selftune/orchestrate.ts`:
- Around line 131-138: The loop's check relies on
updateSignalConsumed(session_id, query, signal_type, runId) but that function
currently discards the .run() result inside safeWrite (callback returns void),
so zero-row UPDATEs are treated as success; change updateSignalConsumed to
return the actual affected-row count (or a boolean derived from it) by making
the safeWrite callback return the .run() result and then propagate
result.changes (or result.changes > 0) back to the caller; update callers (the
for loop expecting ok) to handle the new return type if needed so a zero-change
update yields false and triggers the error log.

In `@tests/alpha-upload/staging.test.ts`:
- Around line 716-718: Reformat the Jest assertion
expect(p.push_id).toMatch(...) to match Biome's formatting rules: adjust
whitespace and line breaks so the call and its regex argument follow Biome style
(e.g., place the .toMatch call and its regex argument on a single line or
otherwise match your project's Biome output); update the assertion around
expect, .toMatch and the regex literal so the formatter no longer reports drift
in the test containing expect(p.push_id).toMatch.

In `@tests/signal-orchestrate.test.ts`:
- Around line 14-17: Remove the two unused imports readJsonl and
queryImprovementSignals from the import block so only the used symbols
(writeImprovementSignalToDb and getDb) are imported; locate the import statement
that currently lists readJsonl, writeImprovementSignalToDb,
queryImprovementSignals, and getDb and edit it to drop readJsonl and
queryImprovementSignals to satisfy the linter/biome errors.

---

Outside diff comments:
In `@tests/signal-orchestrate.test.ts`:
- Around line 214-240: The test is reusing process-wide SQLite state so fixed
keys/timestamps can collide; before seeding call getDb() and either delete
existing rows from the improvement_signals table for the session_ids used or
generate unique session_id/timestamp values per test, then use
writeImprovementSignalToDb(...) to insert and markSignalsConsumed(...) to
consume; specifically update the test to call getDb().exec("DELETE FROM
improvement_signals WHERE session_id IN (...)") or produce unique session_ids
(not "s1"/"s2"/"s3") so the assertions on improvement_signals reflect only this
test's rows.

---

Duplicate comments:
In `@cli/selftune/activation-rules.ts`:
- Around line 135-136: The code currently assumes auditEntries[0] is the newest
entry; instead iterate/reduce auditEntries to parse each entry.timestamp into a
finite number and take the maximum to produce lastTimestamp (then compute
ageMs). Update the logic around variables auditEntries, lastEntry/lastTimestamp
and where ageMs is computed (the consumer of queryEvolutionAudit()) to use the
reduced max timestamp so JSONL (file-order) and SQLite both yield the correct
newest audit time.

In `@cli/selftune/agent-guidance.ts`:
- Around line 8-10: The constructed shell token list 'parts' (used to form
next_command) must not include raw user email values; remove the push of
"--alpha-email" and options.email into parts and ensure any use of options.email
is omitted from next_command generation (leave the email flag out entirely when
building parts/next_command), while preserving other flags; update code paths
that push email (references to parts, options?.email, and next_command) so
next_command never contains the unescaped user-provided email.

In `@cli/selftune/init.ts`:
- Around line 472-475: The fast-path predicate in cliMain() uses
hasAlphaMutation (opts.alpha || opts.noAlpha || opts.alphaEmail !== undefined ||
opts.alphaName !== undefined) but runInit() only applies alphaEmail/alphaName
when opts.alpha is true, causing silent no-ops; fix by making behavior
consistent: either (A) require opts.alpha when opts.alphaEmail or opts.alphaName
are set (validate and error early in cliMain()/parse stage), or (B) if restoring
an AlphaIdentity in runInit(), merge opts.alphaEmail/opts.alphaName into the
restored AlphaIdentity even when opts.alpha is false so the mutation predicate
matches runInit(); update the logic around configPath existence,
hasAlphaMutation, and the restoration code in runInit()/AlphaIdentity handling
to use the same predicate and ensure empty-string values are honored
consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 895e507a-53e5-4a22-96e3-f9d08f18bf06

📥 Commits

Reviewing files that changed from the base of the PR and between a13f12d and a4f880e.

📒 Files selected for processing (10)
  • apps/local-dashboard/src/components/runtime-footer.tsx
  • cli/selftune/activation-rules.ts
  • cli/selftune/agent-guidance.ts
  • cli/selftune/dashboard-server.ts
  • cli/selftune/init.ts
  • cli/selftune/localdb/queries.ts
  • cli/selftune/orchestrate.ts
  • skill/Workflows/Dashboard.md
  • tests/alpha-upload/staging.test.ts
  • tests/signal-orchestrate.test.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
tests/signal-orchestrate.test.ts (1)

216-240: ⚠️ Potential issue | 🟡 Minor

Make this DB test isolated from global SQLite state.

This test writes fixed session_id values (s1,s2,s3) into a singleton DB, so prior rows can make outcomes order-dependent. Use per-test unique IDs (or explicit row cleanup) before assertions.

♻️ Proposed refactor
 test("marks matching signals as consumed", () => {
+  const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+  const s1 = `s1-${suffix}`;
+  const s2 = `s2-${suffix}`;
+  const s3 = `s3-${suffix}`;
+
   const signals = [
-    makeSignal({ timestamp: "2025-01-01T00:00:00Z", session_id: "s1", mentioned_skill: "A" }),
-    makeSignal({ timestamp: "2025-01-01T00:01:00Z", session_id: "s2", mentioned_skill: "B" }),
+    makeSignal({ timestamp: "2025-01-01T00:00:00Z", session_id: s1, mentioned_skill: "A" }),
+    makeSignal({ timestamp: "2025-01-01T00:01:00Z", session_id: s2, mentioned_skill: "B" }),
     makeSignal({
       timestamp: "2025-01-01T00:02:00Z",
-      session_id: "s3",
+      session_id: s3,
       consumed: true,
       consumed_at: "2025-01-01T00:05:00Z",
       consumed_by_run: "old_run",
     }),
   ];
@@
   const updated = db
     .query(
-      "SELECT * FROM improvement_signals WHERE session_id IN ('s1','s2','s3') ORDER BY timestamp ASC",
+      "SELECT * FROM improvement_signals WHERE session_id IN (?, ?, ?) ORDER BY timestamp ASC",
     )
-    .all();
+    .all(s1, s2, s3);

As per coding guidelines: tests/**/*.ts should use isolated tests with proper setup/teardown.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/signal-orchestrate.test.ts` around lines 216 - 240, The test mutates
the global singleton DB by inserting signals with fixed session_ids
("s1","s2","s3"), making results order-dependent; change the test to isolate DB
state by either generating unique session_ids per test (e.g., via a UUID or
timestamp when calling makeSignal) or by cleaning up rows before assertions
(delete from improvement_signals where session_id IN (...) ) so prior runs don't
affect results; locate the inserts via makeSignal and
writeImprovementSignalToDb, ensure markSignalsConsumed is called only on the
intended pendingSignals, and use getDb to run any required cleanup or to create
a fresh in-memory DB for the test before querying improvement_signals.
cli/selftune/observability.ts (1)

199-208: 🧹 Nitpick | 🔵 Trivial

Health check performs no actual verification.

checkDashboardIntegrityHealth() now returns a static "pass" without verifying the database or WAL file exists. Consider adding minimal validation:

Suggested enhancement
 export function checkDashboardIntegrityHealth(): HealthCheck[] {
+  const walPath = `${DB_PATH}-wal`;
   const check: HealthCheck = {
     name: "dashboard_freshness_mode",
     path: DB_PATH,
-    status: "pass",
-    message: "Dashboard reads SQLite and watches WAL for live updates",
+    status: existsSync(DB_PATH) ? "pass" : "warn",
+    message: existsSync(DB_PATH)
+      ? `Dashboard reads SQLite and watches WAL for live updates`
+      : "SQLite database not found; run selftune init",
   };
 
   return [check];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/observability.ts` around lines 199 - 208,
checkDashboardIntegrityHealth currently returns a static "pass" without
verifying the underlying files; update it to perform minimal validation by
checking that the SQLite DB file (DB_PATH) exists and, if WAL mode is expected,
that the corresponding WAL file (DB_PATH + "-wal") exists and is readable; set
the HealthCheck.status to "pass" only when these checks succeed, otherwise set
"fail" (or "warn") and populate HealthCheck.message with a concise error
description; use the existing function name checkDashboardIntegrityHealth and
the DB_PATH constant to locate and implement the checks.
tests/alpha-upload/status.test.ts (1)

241-246: ⚠️ Potential issue | 🟡 Minor

Make the not enrolled command assertion exact.

This still passes if the guidance regresses to selftune init --alpha --force, because the substring selftune init --alpha is present there too. Assert the full line, or at least add a negative check for --force, so this state stays distinct from the recovery cases.

✅ Proposed fix
     const output = formatAlphaStatus(null);
     expect(output).toContain("not enrolled");
-    expect(output).toContain("Next command");
-    expect(output).toContain("selftune init --alpha");
+    expect(output).toContain("Next command:       selftune init --alpha");
+    expect(output).not.toContain("selftune init --alpha --force");
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/alpha-upload/status.test.ts` around lines 241 - 246, The test "returns
'not enrolled' line when not enrolled" uses formatAlphaStatus(null) but only
checks for a substring so it would still pass if the guidance becomes "selftune
init --alpha --force"; update the assertions in that test to assert the full
expected line (e.g., exact match for the guidance line returned by
formatAlphaStatus) or add a negative assertion that the output does not contain
"--force": reference the test name and the formatAlphaStatus call to locate the
assertion and replace the loose expect(output).toContain("selftune init
--alpha") with an equality/line-match assertion or add
expect(output).not.toContain("--force") to keep the not-enrolled state distinct
from recovery cases.
♻️ Duplicate comments (2)
cli/selftune/orchestrate.ts (1)

129-133: ⚠️ Potential issue | 🟡 Minor

markSignalsConsumed cannot detect zero-row updates.

At Line 129, ok is expected to represent “signal marked consumed”, but updateSignalConsumed() currently returns safeWrite(...) success even when UPDATE ... affects 0 rows, so the error branch at Line 130-133 is effectively unreachable for no-op updates.

♻️ Proposed fix (in cli/selftune/localdb/direct-write.ts)
 export function updateSignalConsumed(
   sessionId: string,
   query: string,
   signalType: string,
   runId: string,
 ): boolean {
-  return safeWrite("signal-consumed", (db) => {
-    getStmt(
+  return safeWrite("signal-consumed", (db) => {
+    const result = getStmt(
       db,
       "signal-consumed",
       `
       UPDATE improvement_signals
       SET consumed = 1, consumed_at = ?, consumed_by_run = ?
       WHERE session_id = ? AND query = ? AND signal_type = ? AND consumed = 0
     `,
-    ).run(new Date().toISOString(), runId, sessionId, query, signalType);
+    ).run(new Date().toISOString(), runId, sessionId, query, signalType);
+    return result.changes > 0;
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/orchestrate.ts` around lines 129 - 133, updateSignalConsumed
currently treats safeWrite success as "consumed" even when the UPDATE affects 0
rows, so change updateSignalConsumed (in cli/selftune/localdb/direct-write.ts)
to check the underlying write result (e.g., the returned result.changes /
rowsAffected value from safeWrite or the DB run() response) and return false
when 0 rows were updated; ensure orchestrate.ts's call to updateSignalConsumed
(used when marking signals consumed) uses that boolean to trigger the error
branch appropriately.
cli/selftune/localdb/queries.ts (1)

696-730: ⚠️ Potential issue | 🟠 Major

Keep execution_fact_id when rebuilding canonical execution facts.

This query reads id, but the reconstructed record never emits execution_fact_id. On the SQLite-first staging path, stageCanonicalRecords() then has to synthesize a new ID instead of reusing the row's canonical identity, which breaks stable dedupe/cursor behavior for execution facts.

🧩 Proposed fix
-      `SELECT id, session_id, occurred_at, prompt_id, tool_calls_json, total_tool_calls,
+      `SELECT id AS execution_fact_id, session_id, occurred_at, prompt_id, tool_calls_json, total_tool_calls,
               assistant_turns, errors_encountered, input_tokens, output_tokens,
               duration_ms, completion_status,
               schema_version, platform, normalized_at, normalizer_version, capture_mode, raw_source_ref
        FROM execution_facts ORDER BY occurred_at`,
@@
       raw_source_ref:
         safeParseJson(ef.raw_source_ref as string | null) ??
         safeParseJson(sessionEnvelope?.raw_source_ref as string | null) ??
         undefined,
       source_session_kind: sessionEnvelope?.source_session_kind ?? undefined,
       session_id: ef.session_id,
+      execution_fact_id: ef.execution_fact_id,
       occurred_at: ef.occurred_at,
       prompt_id: ef.prompt_id ?? undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/localdb/queries.ts` around lines 696 - 730, The reconstructed
execution_fact record omits the original row id, so preserve the canonical
execution_fact_id by adding an execution_fact_id property to the object pushed
into records (use ef.id as the value); update the object created in the loop
(where records.push({ ... })) to include execution_fact_id: ef.id ?? undefined
so downstream stageCanonicalRecords() can reuse the original ID for stable
dedupe/cursors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/selftune/agent-guidance.ts`:
- Around line 4-7: The buildAlphaInitCommand function is currently appending
options.email raw into the generated command which can inject extra tokens;
instead trim the input and validate it is a single safe email token (e.g., no
whitespace or shell metacharacters and matches a basic email pattern) before
adding it to parts; if it fails validation or contains unsafe characters, do not
push the --alpha-email flag at all (omit it) so the composed command cannot be
altered by malicious/invalid input (reference: buildAlphaInitCommand and
options.email).

In `@cli/selftune/init.ts`:
- Around line 472-475: The fast-path currently treats
opts.alphaEmail/opts.alphaName as mutations via hasAlphaMutation but the flags
are only applied when opts.alpha is true, so add an early validation before the
fast-path (near where hasAlphaMutation is computed) that rejects or exits if
(opts.alphaEmail !== undefined || opts.alphaName !== undefined) && !opts.alpha
with a clear error message; update both places (the earlier fast-path check
around hasAlphaMutation and the later block around lines 652-656) to perform
this same validation so standalone --alpha-email / --alpha-name cannot silently
bypass cached config.
- Around line 547-559: The code assigns result.api_key and other fields directly
into validatedAlphaIdentity after pollDeviceCode; add the same format/validation
used by checkAlphaReadiness to verify the approval payload (at least validate
api_key format and required fields cloud_user_id/org_id) before persisting or
emitting readiness. In practice, call the existing validator (or inline the same
checks) on result and abort/throw (and do not set validatedAlphaIdentity or emit
alpha_upload_ready) if the payload is malformed so broken credentials are never
written; update the block around pollDeviceCode, validatedAlphaIdentity and any
subsequent readiness emission to gate on that validation.

In `@tests/alpha-upload/staging.test.ts`:
- Around line 876-878: The test uses a loose assertion on
stageCanonicalRecords(db); change the assertion to require an exact count so
accidental duplicates fail the test: replace
expect(staged).toBeGreaterThanOrEqual(4) with expect(staged).toBe(4) (reference:
stageCanonicalRecords and the staged variable in this fixture) and update the
inline comment to state that exactly four canonical source rows are expected.

---

Outside diff comments:
In `@cli/selftune/observability.ts`:
- Around line 199-208: checkDashboardIntegrityHealth currently returns a static
"pass" without verifying the underlying files; update it to perform minimal
validation by checking that the SQLite DB file (DB_PATH) exists and, if WAL mode
is expected, that the corresponding WAL file (DB_PATH + "-wal") exists and is
readable; set the HealthCheck.status to "pass" only when these checks succeed,
otherwise set "fail" (or "warn") and populate HealthCheck.message with a concise
error description; use the existing function name checkDashboardIntegrityHealth
and the DB_PATH constant to locate and implement the checks.

In `@tests/alpha-upload/status.test.ts`:
- Around line 241-246: The test "returns 'not enrolled' line when not enrolled"
uses formatAlphaStatus(null) but only checks for a substring so it would still
pass if the guidance becomes "selftune init --alpha --force"; update the
assertions in that test to assert the full expected line (e.g., exact match for
the guidance line returned by formatAlphaStatus) or add a negative assertion
that the output does not contain "--force": reference the test name and the
formatAlphaStatus call to locate the assertion and replace the loose
expect(output).toContain("selftune init --alpha") with an equality/line-match
assertion or add expect(output).not.toContain("--force") to keep the
not-enrolled state distinct from recovery cases.

In `@tests/signal-orchestrate.test.ts`:
- Around line 216-240: The test mutates the global singleton DB by inserting
signals with fixed session_ids ("s1","s2","s3"), making results order-dependent;
change the test to isolate DB state by either generating unique session_ids per
test (e.g., via a UUID or timestamp when calling makeSignal) or by cleaning up
rows before assertions (delete from improvement_signals where session_id IN
(...) ) so prior runs don't affect results; locate the inserts via makeSignal
and writeImprovementSignalToDb, ensure markSignalsConsumed is called only on the
intended pendingSignals, and use getDb to run any required cleanup or to create
a fresh in-memory DB for the test before querying improvement_signals.

---

Duplicate comments:
In `@cli/selftune/localdb/queries.ts`:
- Around line 696-730: The reconstructed execution_fact record omits the
original row id, so preserve the canonical execution_fact_id by adding an
execution_fact_id property to the object pushed into records (use ef.id as the
value); update the object created in the loop (where records.push({ ... })) to
include execution_fact_id: ef.id ?? undefined so downstream
stageCanonicalRecords() can reuse the original ID for stable dedupe/cursors.

In `@cli/selftune/orchestrate.ts`:
- Around line 129-133: updateSignalConsumed currently treats safeWrite success
as "consumed" even when the UPDATE affects 0 rows, so change
updateSignalConsumed (in cli/selftune/localdb/direct-write.ts) to check the
underlying write result (e.g., the returned result.changes / rowsAffected value
from safeWrite or the DB run() response) and return false when 0 rows were
updated; ensure orchestrate.ts's call to updateSignalConsumed (used when marking
signals consumed) uses that boolean to trigger the error branch appropriately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2c68a0ed-0536-4712-a164-f0e294ab9c54

📥 Commits

Reviewing files that changed from the base of the PR and between a4f880e and 2967d6c.

📒 Files selected for processing (9)
  • cli/selftune/agent-guidance.ts
  • cli/selftune/dashboard-server.ts
  • cli/selftune/init.ts
  • cli/selftune/localdb/queries.ts
  • cli/selftune/observability.ts
  • cli/selftune/orchestrate.ts
  • tests/alpha-upload/staging.test.ts
  • tests/alpha-upload/status.test.ts
  • tests/signal-orchestrate.test.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cli/selftune/localdb/direct-write.ts (2)

388-405: ⚠️ Potential issue | 🟠 Major

Consume the specific signal row, not every matching query tuple.

This UPDATE ignores timestamp, so one call will mark every unconsumed (session_id, query, signal_type) row as consumed. That can silently eat duplicate signals or a same-query signal that arrived after the run read its snapshot. Thread timestamp (or a stable row id) through markSignalsConsumed() and include it in the predicate.

Suggested fix
-export function updateSignalConsumed(
-  sessionId: string,
+export function updateSignalConsumed(
+  timestamp: string,
+  sessionId: string,
   query: string,
   signalType: string,
   runId: string,
 ): boolean {
   const result = safeWriteResult("signal-consumed", (db) =>
     getStmt(
       db,
       "signal-consumed",
       `
       UPDATE improvement_signals
       SET consumed = 1, consumed_at = ?, consumed_by_run = ?
-      WHERE session_id = ? AND query = ? AND signal_type = ? AND consumed = 0
+      WHERE timestamp = ? AND session_id = ? AND query = ? AND signal_type = ? AND consumed = 0
     `,
-    ).run(new Date().toISOString(), runId, sessionId, query, signalType),
+    ).run(new Date().toISOString(), runId, timestamp, sessionId, query, signalType),
   );
   return result?.changes > 0;
 }
// cli/selftune/orchestrate.ts
const ok = updateSignalConsumed(
  signal.timestamp,
  signal.session_id,
  signal.query,
  signal.signal_type,
  runId,
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/localdb/direct-write.ts` around lines 388 - 405, The UPDATE
currently marks all rows matching (session_id, query, signal_type) because
timestamp isn't part of the WHERE; change the API and callers to consume a
single signal row by adding a timestamp (or stable row id) parameter to
updateSignalConsumed and using it in the WHERE clause (e.g. WHERE session_id = ?
AND query = ? AND signal_type = ? AND timestamp = ? AND consumed = 0) so only
that exact row is updated; also thread the new timestamp argument through
markSignalsConsumed (orchestrate.ts caller) when calling updateSignalConsumed
and pass it into the prepared statement so duplicates or later-arriving
same-query signals are not accidentally consumed.

410-456: ⚠️ Potential issue | 🟠 Major

SQLite-first staging is still not at canonical-field parity.

queryCanonicalRecordsForStaging() now rebuilds uploads from SQLite, but these writers still persist only a subset of the canonical records. Fields this codebase already builds today—e.g. session fields like external_session_id / repo_root / commit_sha / permission_mode / approval_policy / sandbox_policy / provider / end_reason, prompt fields like prompt_hash / parent_prompt_id / source_message_id, skill-invocation fields like skill_version_hash / tool_call_id, and execution-fact fields like bash_commands_redacted / end_reason—never reach SQLite here, so they disappear from default-path uploads after this cutover.

Also applies to: 459-483, 486-539, 542-572

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/localdb/direct-write.ts` around lines 410 - 456, The
insertSession writer (function insertSession) is missing many canonical session
fields (e.g. external_session_id, repo_root, commit_sha, permission_mode,
approval_policy, sandbox_policy, provider, end_reason) so those values are never
persisted to SQLite; update the INSERT column list and VALUES binding in
insertSession to include these missing fields (and add them to the ON CONFLICT
DO UPDATE clause with appropriate COALESCE/excluded logic), ensuring any complex
objects are JSON.stringified like raw_source_ref; apply the same change pattern
to the other writers referenced around lines 459-483, 486-539 and 542-572
(prompt, skill-invocation, and execution-fact writers) to add missing canonical
fields such as prompt_hash, parent_prompt_id, source_message_id,
skill_version_hash, tool_call_id, bash_commands_redacted, end_reason so
canonical parity is preserved.
♻️ Duplicate comments (1)
cli/selftune/activation-rules.ts (1)

128-148: ⚠️ Potential issue | 🟡 Minor

Don’t assume descending order in the JSONL fallback.

queryEvolutionAudit() is DESC, but readJsonl() preserves file order. For custom/test evolution_audit_log_path values, auditEntries[0] is usually the oldest record, so this rule can mark evolution as stale even when a newer JSONL entry exists. Compute the newest timestamp explicitly instead of indexing the first element.

Suggested fix
-    const lastEntry = auditEntries[0]; // queryEvolutionAudit returns DESC order
-    const lastTimestamp = new Date(lastEntry.timestamp).getTime();
+    const lastTimestamp = Math.max(
+      ...auditEntries.map((entry) => Date.parse(entry.timestamp)).filter((ts) => Number.isFinite(ts)),
+    );
+    if (!Number.isFinite(lastTimestamp)) return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cli/selftune/activation-rules.ts` around lines 128 - 148, When falling back
to readJsonl for ctx.evolution_audit_log_path, do not assume auditEntries[0] is
the newest; instead compute the newest timestamp across auditEntries (e.g., map
each entry.timestamp to new Date(...).getTime() and take the max) and use that
as lastTimestamp; update the logic around lastEntry/lastTimestamp (which
currently assumes queryEvolutionAudit returns DESC) so both branches
(queryEvolutionAudit and readJsonl) produce a correct newest timestamp before
comparing staleness, and keep existing empty-array handling and calls to
checkFalseNegatives unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/selftune/localdb/queries.ts`:
- Around line 660-692: The rebuilt skill_invocation records omit the skill_path
persisted by insertSkillInvocation; modify the SQL query in the invocations
retrieval (the const invocations = db.query(...).all()) to select skill_path
from the skill_invocations table and include skill_path in the emitted record
object (records.push) so the reconstructed record sets skill_path (falling back
to sessionEnvelope if appropriate) — update the record key alongside other
fields like skill_invocation_id, session_id, raw_source_ref, etc., so staged
skill_invocation entries retain the canonical skill_path.

In `@packages/telemetry-contract/src/schemas.ts`:
- Around line 168-170: The schema change relaxes push_id to z.string().min(1)
but the test staging.test.ts still asserts a UUID format; pick one resolution:
either revert the schema by changing push_id back to z.string().uuid() to
satisfy the existing UUID regex in staging.test.ts, or update the test to
validate a non-empty string instead of a UUID (remove the UUID regex/assertion
and assert presence/non-empty for push_id); locate the push_id declaration in
the telemetry schema and the UUID assertion in staging.test.ts (the test that
checks push_id at the alpha-upload path) and apply the corresponding change so
schema and test agree.

---

Outside diff comments:
In `@cli/selftune/localdb/direct-write.ts`:
- Around line 388-405: The UPDATE currently marks all rows matching (session_id,
query, signal_type) because timestamp isn't part of the WHERE; change the API
and callers to consume a single signal row by adding a timestamp (or stable row
id) parameter to updateSignalConsumed and using it in the WHERE clause (e.g.
WHERE session_id = ? AND query = ? AND signal_type = ? AND timestamp = ? AND
consumed = 0) so only that exact row is updated; also thread the new timestamp
argument through markSignalsConsumed (orchestrate.ts caller) when calling
updateSignalConsumed and pass it into the prepared statement so duplicates or
later-arriving same-query signals are not accidentally consumed.
- Around line 410-456: The insertSession writer (function insertSession) is
missing many canonical session fields (e.g. external_session_id, repo_root,
commit_sha, permission_mode, approval_policy, sandbox_policy, provider,
end_reason) so those values are never persisted to SQLite; update the INSERT
column list and VALUES binding in insertSession to include these missing fields
(and add them to the ON CONFLICT DO UPDATE clause with appropriate
COALESCE/excluded logic), ensuring any complex objects are JSON.stringified like
raw_source_ref; apply the same change pattern to the other writers referenced
around lines 459-483, 486-539 and 542-572 (prompt, skill-invocation, and
execution-fact writers) to add missing canonical fields such as prompt_hash,
parent_prompt_id, source_message_id, skill_version_hash, tool_call_id,
bash_commands_redacted, end_reason so canonical parity is preserved.

---

Duplicate comments:
In `@cli/selftune/activation-rules.ts`:
- Around line 128-148: When falling back to readJsonl for
ctx.evolution_audit_log_path, do not assume auditEntries[0] is the newest;
instead compute the newest timestamp across auditEntries (e.g., map each
entry.timestamp to new Date(...).getTime() and take the max) and use that as
lastTimestamp; update the logic around lastEntry/lastTimestamp (which currently
assumes queryEvolutionAudit returns DESC) so both branches (queryEvolutionAudit
and readJsonl) produce a correct newest timestamp before comparing staleness,
and keep existing empty-array handling and calls to checkFalseNegatives
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a537b75a-b533-4b74-90f1-f2a11dd3c147

📥 Commits

Reviewing files that changed from the base of the PR and between 2967d6c and e16a23f.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • apps/local-dashboard/package.json
  • cli/selftune/activation-rules.ts
  • cli/selftune/localdb/direct-write.ts
  • cli/selftune/localdb/queries.ts
  • cli/selftune/normalization.ts
  • cli/selftune/observability.ts
  • cli/selftune/orchestrate.ts
  • cli/selftune/types.ts
  • packages/telemetry-contract/package.json
  • packages/telemetry-contract/src/schemas.ts
  • tests/autonomy-proof.test.ts
  • tests/hooks/auto-activate.test.ts
  • tests/localdb/read-queries.test.ts
  • tests/localdb/write.test.ts
  • tests/orchestrate.test.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cli/selftune/init.ts`:
- Around line 461-465: The function validateAlphaMetadataFlags has a single-line
parameter list causing Biome formatting failures; change its signature to a
multiline parameter list (each parameter on its own line) while preserving types
and the existing body so the formatter accepts it, i.e., update the
validateAlphaMetadataFlags(alpha: boolean | undefined, email?: string, name?:
string): void { ... } declaration to a multiline form.

In `@scripts/link-claude-workspace.sh`:
- Around line 27-33: The current block that checks current_target, skill_src,
and claude_skill_path uses rm -rf "$claude_skill_path" when backup_path already
exists, which can silently delete data; change the logic in that conditional
(referencing current_target, skill_src, claude_skill_path, backup_path) to never
rm -rf the existing claude_skill_path but instead create a new unique backup
(e.g., append a timestamp or incrementing suffix to backup_path) and move
claude_skill_path into that new backup location, or skip replacing if a safe
backup cannot be made, ensuring the original backup is preserved.

In `@tests/localdb/read-queries.test.ts`:
- Around line 863-867: The predicate passed into
queryCanonicalRecordsForStaging(db).find(...) is manually wrapped in a way that
conflicts with Biome's formatter; reformat the arrow function predicate for the
variable invocation so it adheres to Biome styling (let the formatter determine
line breaks) — locate the .find(...) call in tests/localdb/read-queries.test.ts
(variable invocation and the arrow predicate) and put the predicate expression
into the canonical Biome-friendly shape (either a single-line boolean expression
or each && clause on its own aligned line) so CI formatting no longer fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5096b898-28b4-4cc5-9dad-b3d896b9202b

📥 Commits

Reviewing files that changed from the base of the PR and between e16a23f and fd4c8cc.

📒 Files selected for processing (9)
  • cli/selftune/agent-guidance.ts
  • cli/selftune/init.ts
  • cli/selftune/localdb/queries.ts
  • package.json
  • scripts/link-claude-workspace.sh
  • tests/agent-guidance.test.ts
  • tests/alpha-upload/staging.test.ts
  • tests/init/alpha-consent.test.ts
  • tests/localdb/read-queries.test.ts

@WellDunDun WellDunDun merged commit fa4562d into main Mar 20, 2026
8 checks passed
@WellDunDun WellDunDun deleted the custom/prefix/router-1774017937646 branch March 20, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant