chore: rolling promotion dev -> main - #2533
Conversation
13 src-proven ~/.genie residue entries join the legacy-v4 manifest (lstat-only, symlinks never followed, full-content backup-first); genie doctor gains a v4-residue check with opt-in --fix (json-safe via logSink; uncertain keeps reported, never touched); update diagnostics age-filter scheduler signals at 48h so stale incidents stop resurfacing. Wish v4-home-residue-doctor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1FLEV2Qse3jbX5Wz1sjWE
feat(doctor): v4 home-residue detection + --fix, diagnostics age filter
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
📝 WalkthroughWalkthroughAdds v4-era genie home residue detection and backup-first cleanup to legacy-v4.ts, exposes it via a new doctor check and ChangesV4 Home Residue Detection, Cleanup, and Doctor Integration
Estimated code review effort: 4 (Complex) | ~60 minutes Scheduler Log Age Filtering
Estimated code review effort: 3 (Moderate) | ~20 minutes Version Bumps and Wish Doc
Sequence Diagram(s)sequenceDiagram
participant CLI as genie doctor --fix
participant Doctor as doctorCommand
participant Cleanup as cleanupV4
participant Legacy as legacy-v4.ts
participant FS as Filesystem
CLI->>Doctor: run with fix=true
Doctor->>Cleanup: cleanupV4(options)
Cleanup->>Legacy: detectV4HomeResidue(genieHome)
Legacy->>FS: scan manifest paths
FS-->>Legacy: residue relics
Cleanup->>FS: backup relics, then remove
Cleanup-->>Doctor: V4CleanupResult (homeResidue, actions)
Doctor->>Legacy: checkV4Residue(home, genieHome)
Legacy-->>Doctor: pass/warn check results
Doctor-->>CLI: aggregated JSON output
Estimated code review effort: 4 (Complex) | ~50 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: be8f30ab3e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch { | ||
| continue; // absent — the v5-normal case | ||
| } | ||
| if (stat.isSymbolicLink()) continue; // never follow a link out of the home |
There was a problem hiding this comment.
Skip residue under symlinked parents
When a manifest entry is nested below a symlinked parent, e.g. ~/.genie/logs -> /shared/logs, lstat('~/.genie/logs/scheduler.log') follows the logs symlink and sees the external file as ordinary residue. A later doctor --fix, install, or update cleanup then backs up and removes that external target, despite the stated “never follow a link out of the home” contract. Please reject entries if any path component between genieHome and the relic is a symlink, not just the final component.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
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 (1)
src/genie-commands/legacy-v4.ts (1)
502-524: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
console.logdefault violates the no-console.logguideline forsrc/**/*.ts.The default
emitsink at line 518 falls back toconsole.log. As per coding guidelines,**/*.tsundersrc/**should not useconsole.login source files. Even as a documented CLI-chatter default, this is a direct violation; preferprocess.stdout.write(matching the pattern already used for the json-mode stderr sink one call site away indoctor.ts).♻️ Proposed fix
- emit: options.logSink ?? ((line: string) => console.log(line)), + emit: options.logSink ?? ((line: string) => process.stdout.write(`${line}\n`)),As per coding guidelines,
src/**/*.ts: "Do not useconsole.login source files."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/genie-commands/legacy-v4.ts` around lines 502 - 524, The default emit sink in cleanupV4 currently falls back to console.log, which violates the no-console.log guideline for source files. Update the CleanupContext.emit fallback in cleanupV4 to write through process.stdout.write instead, keeping the same line output behavior without using console.log. Use the existing emit/logLines flow in cleanupV4 and preserve the CLI messaging semantics while removing the direct console.log dependency.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/genie-commands/doctor.test.ts`:
- Around line 298-301: The source-lock test in doctor.test.ts is too brittle
because it regex-matches the literal doctor.ts source instead of behavior.
Remove the raw source assertion in doctorCommand wires cleanup strictly behind
the fix flag (source lock), or replace it with a behavioral test that targets
doctorCommand/cleanupV4 by spying on cleanupV4 and asserting it is only called
when options.fix is true and not called otherwise.
In `@src/genie-commands/doctor.ts`:
- Around line 236-251: The v4 residue summary row is skipped when there is no
home residue or orphaned caches but rulesKeptUserModified is true, because the
current conditional in doctor.ts only emits the header for the all-clear case or
when residue/claude counts are nonzero. Update the logic around the v4 residue
CheckResult construction so the top-level row is always added, using the
existing residue, claudeCount, and rulesKeptUserModified signals to choose pass
vs warn, and keep the later per-item rules-file row unchanged.
In `@src/genie-commands/legacy-v4.ts`:
- Around line 216-289: `detectV4HomeResidue()` can still fail when
`sizeOfPathTree()` races with concurrent cleanup after the initial `lstatSync()`
check. Update the relic sizing path to use the same safe sizing approach as
`doctor.ts`’s `safeSizeOf()`, so any `lstatSync`/`readdirSync`/nested lookup
error is caught and that entry is skipped instead of aborting `genie doctor`.
Keep the change localized around `sizeOfPathTree` and its call site in
`detectV4HomeResidue`.
In `@src/genie-commands/update.ts`:
- Around line 1051-1064: The docstring is attached to the wrong symbol: move the
age-filtered/exported boundary-test comment from interface ParsedSignalLine so
it documents summarizeJsonlSignals instead. While touching
summarizeJsonlSignals, remove the repeated Date.parse(newestStaleTimestamp) work
by tracking the newest stale entry’s numeric tsMs directly and comparing against
that value when updating the stale summary.
---
Outside diff comments:
In `@src/genie-commands/legacy-v4.ts`:
- Around line 502-524: The default emit sink in cleanupV4 currently falls back
to console.log, which violates the no-console.log guideline for source files.
Update the CleanupContext.emit fallback in cleanupV4 to write through
process.stdout.write instead, keeping the same line output behavior without
using console.log. Use the existing emit/logLines flow in cleanupV4 and preserve
the CLI messaging semantics while removing the direct console.log dependency.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: dd1aee72-d66e-45fa-b3d2-a741d6d54b90
📒 Files selected for processing (13)
.claude-plugin/marketplace.json.genie/wishes/v4-home-residue-doctor/WISH.mdpackage.jsonplugins/genie/.claude-plugin/plugin.jsonplugins/genie/package.jsonsrc/genie-commands/__tests__/update.test.tssrc/genie-commands/doctor.test.tssrc/genie-commands/doctor.tssrc/genie-commands/install.test.tssrc/genie-commands/legacy-v4.test.tssrc/genie-commands/legacy-v4.tssrc/genie-commands/update.tssrc/genie.ts
| test('doctorCommand wires cleanup strictly behind the fix flag (source lock)', () => { | ||
| const source = readFileSync(join(import.meta.dir, 'doctor.ts'), 'utf-8'); | ||
| expect(source).toMatch(/if \(options\?\.fix\) \{\s*\n\s*cleanupV4\(/); | ||
| }); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Brittle "source lock" test greps raw source text.
Asserting on doctor.ts's literal source via regex is fragile — any unrelated formatting change (e.g., Biome reformatting the if block) breaks this test without a real regression. The existing behavioral tests (doctorCommand without --fix mutates nothing, doctor --fix --json E2E) already cover the "fix strictly gated" contract; consider dropping this test or replacing it with a behavioral assertion (e.g., spy on cleanupV4 and assert it's not called without --fix).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/genie-commands/doctor.test.ts` around lines 298 - 301, The source-lock
test in doctor.test.ts is too brittle because it regex-matches the literal
doctor.ts source instead of behavior. Remove the raw source assertion in
doctorCommand wires cleanup strictly behind the fix flag (source lock), or
replace it with a behavioral test that targets doctorCommand/cleanupV4 by spying
on cleanupV4 and asserting it is only called when options.fix is true and not
called otherwise.
| const results: CheckResult[] = []; | ||
| const claudeCount = orphanedCaches.length + (rulesReclaimable ? 1 : 0); | ||
| if (residue.length === 0 && claudeCount === 0 && !rulesKeptUserModified) { | ||
| results.push({ name: 'v4 residue', status: 'pass', detail: 'none found' }); | ||
| } else if (residue.length + claudeCount > 0) { | ||
| const totalBytes = | ||
| residue.reduce((sum, r) => sum + r.sizeBytes, 0) + | ||
| (rulesReclaimable ? safeSizeOf(claude.rulesFile.path) : 0) + | ||
| orphanedCaches.reduce((sum, d) => sum + safeSizeOf(d.path), 0); | ||
| results.push({ | ||
| name: 'v4 residue', | ||
| status: 'warn', | ||
| detail: `${residue.length + claudeCount} reclaimable item(s) (${residue.length} genie-home, ${claudeCount} claude), ${prettyBytes(totalBytes)}`, | ||
| suggestion: 'Run `genie doctor --fix` to back up and remove (backups: ~/.genie/state-backups/).', | ||
| }); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Missing "v4 residue" summary row when only a user-modified rules file exists.
When residue.length === 0, claudeCount === 0, but rulesKeptUserModified === true (a customized .claude/rules/genie-orchestration.md, no home residue, no orphaned caches), neither branch fires: the first if requires !rulesKeptUserModified, and the second else if requires residue.length + claudeCount > 0. The top-level "v4 residue" pass/warn header is silently dropped — only the per-item "v4 residue: ~/.claude rules file" row (pushed later at line 261-267) survives. This edge case isn't covered by doctor.test.ts (its "user-modified" test also seeds serve.pid, so residue.length is 1 there, masking the gap).
🐛 Proposed fix
- if (residue.length === 0 && claudeCount === 0 && !rulesKeptUserModified) {
- results.push({ name: 'v4 residue', status: 'pass', detail: 'none found' });
- } else if (residue.length + claudeCount > 0) {
+ if (residue.length === 0 && claudeCount === 0 && !rulesKeptUserModified) {
+ results.push({ name: 'v4 residue', status: 'pass', detail: 'none found' });
+ } else if (residue.length + claudeCount === 0 && rulesKeptUserModified) {
+ results.push({ name: 'v4 residue', status: 'warn', detail: 'kept user-modified rules file only' });
+ } else if (residue.length + claudeCount > 0) {
const totalBytes = ...📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const results: CheckResult[] = []; | |
| const claudeCount = orphanedCaches.length + (rulesReclaimable ? 1 : 0); | |
| if (residue.length === 0 && claudeCount === 0 && !rulesKeptUserModified) { | |
| results.push({ name: 'v4 residue', status: 'pass', detail: 'none found' }); | |
| } else if (residue.length + claudeCount > 0) { | |
| const totalBytes = | |
| residue.reduce((sum, r) => sum + r.sizeBytes, 0) + | |
| (rulesReclaimable ? safeSizeOf(claude.rulesFile.path) : 0) + | |
| orphanedCaches.reduce((sum, d) => sum + safeSizeOf(d.path), 0); | |
| results.push({ | |
| name: 'v4 residue', | |
| status: 'warn', | |
| detail: `${residue.length + claudeCount} reclaimable item(s) (${residue.length} genie-home, ${claudeCount} claude), ${prettyBytes(totalBytes)}`, | |
| suggestion: 'Run `genie doctor --fix` to back up and remove (backups: ~/.genie/state-backups/).', | |
| }); | |
| } | |
| const results: CheckResult[] = []; | |
| const claudeCount = orphanedCaches.length + (rulesReclaimable ? 1 : 0); | |
| if (residue.length === 0 && claudeCount === 0 && !rulesKeptUserModified) { | |
| results.push({ name: 'v4 residue', status: 'pass', detail: 'none found' }); | |
| } else if (residue.length + claudeCount === 0 && rulesKeptUserModified) { | |
| results.push({ name: 'v4 residue', status: 'warn', detail: 'kept user-modified rules file only' }); | |
| } else if (residue.length + claudeCount > 0) { | |
| const totalBytes = | |
| residue.reduce((sum, r) => sum + r.sizeBytes, 0) + | |
| (rulesReclaimable ? safeSizeOf(claude.rulesFile.path) : 0) + | |
| orphanedCaches.reduce((sum, d) => sum + safeSizeOf(d.path), 0); | |
| results.push({ | |
| name: 'v4 residue', | |
| status: 'warn', | |
| detail: `${residue.length + claudeCount} reclaimable item(s) (${residue.length} genie-home, ${claudeCount} claude), ${prettyBytes(totalBytes)}`, | |
| suggestion: 'Run `genie doctor --fix` to back up and remove (backups: ~/.genie/state-backups/).', | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/genie-commands/doctor.ts` around lines 236 - 251, The v4 residue summary
row is skipped when there is no home residue or orphaned caches but
rulesKeptUserModified is true, because the current conditional in doctor.ts only
emits the header for the all-clear case or when residue/claude counts are
nonzero. Update the logic around the v4 residue CheckResult construction so the
top-level row is always added, using the existing residue, claudeCount, and
rulesKeptUserModified signals to choose pass vs warn, and keep the later
per-item rules-file row unchanged.
| export interface V4HomeResidueRelic { | ||
| /** Absolute path of the found artifact. */ | ||
| path: string; | ||
| /** Genie-home-relative display path, e.g. `logs/scheduler.log`. */ | ||
| relPath: string; | ||
| kind: 'file' | 'dir'; | ||
| /** Bytes on disk (recursive for dirs; symlinks never followed). */ | ||
| sizeBytes: number; | ||
| /** The manifest entry's src-proof. */ | ||
| evidence: string; | ||
| } | ||
|
|
||
| /** Bytes on disk for a file or tree (recursive; symlinks contribute 0, never followed). */ | ||
| export function sizeOfPathTree(path: string): number { | ||
| const stat = lstatSync(path); | ||
| if (stat.isSymbolicLink()) return 0; | ||
| if (!stat.isDirectory()) return stat.size; | ||
| let total = 0; | ||
| for (const entry of readdirSync(path, { withFileTypes: true })) { | ||
| total += sizeOfPathTree(join(path, entry.name)); | ||
| } | ||
| return total; | ||
| } | ||
|
|
||
| /** Expand a single-`*` glob entry against the DIRECT children of its parent dir. */ | ||
| function expandGlobEntry(genieHome: string, entry: HomeResidueEntry): string[] { | ||
| const parent = join(genieHome, ...entry.relPath.slice(0, -1)); | ||
| const pattern = entry.relPath[entry.relPath.length - 1]; | ||
| const [prefix, suffix] = pattern.split('*'); | ||
| try { | ||
| if (!statSync(parent).isDirectory()) return []; | ||
| } catch { | ||
| return []; // parent absent or unreadable — nothing to expand | ||
| } | ||
| return readdirSync(parent) | ||
| .filter((name) => name.startsWith(prefix) && name.endsWith(suffix) && name.length >= prefix.length + suffix.length) | ||
| .map((name) => join(parent, name)); | ||
| } | ||
|
|
||
| /** | ||
| * Detect v4 daemon-era residue under the genie home dir. Read-only — never | ||
| * creates, writes, or logs anything (doctor without --fix relies on this). | ||
| */ | ||
| export function detectV4HomeResidue(genieHome: string = resolveGenieHome()): V4HomeResidueRelic[] { | ||
| const relics: V4HomeResidueRelic[] = []; | ||
| try { | ||
| if (!statSync(genieHome).isDirectory()) return relics; // absent or not a dir — nothing to scan | ||
| } catch { | ||
| return relics; | ||
| } | ||
| for (const entry of V4_LEGACY_MANIFEST.homeResidue) { | ||
| const paths = entry.kind === 'glob' ? expandGlobEntry(genieHome, entry) : [join(genieHome, ...entry.relPath)]; | ||
| for (const path of paths) { | ||
| let stat: ReturnType<typeof lstatSync>; | ||
| try { | ||
| stat = lstatSync(path); | ||
| } catch { | ||
| continue; // absent — the v5-normal case | ||
| } | ||
| if (stat.isSymbolicLink()) continue; // never follow a link out of the home | ||
| const isDir = stat.isDirectory(); | ||
| if (entry.kind === 'dir' && !isDir) continue; // shape mismatch → not ours, keep | ||
| if (entry.kind === 'file' && isDir) continue; | ||
| relics.push({ | ||
| path, | ||
| relPath: relative(genieHome, path), | ||
| kind: isDir ? 'dir' : 'file', | ||
| sizeBytes: sizeOfPathTree(path), | ||
| evidence: entry.evidence, | ||
| }); | ||
| } | ||
| } | ||
| return relics; | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== legacy-v4 slice ==\n'
sed -n '1,260p' src/genie-commands/legacy-v4.ts
printf '\n== doctor slice ==\n'
sed -n '1,260p' src/genie-commands/doctor.ts
printf '\n== references to sizeOfPathTree and safeSizeOf ==\n'
rg -n "sizeOfPathTree|safeSizeOf" src/genie-commands src --glob '*.ts'Repository: automagik-dev/genie
Length of output: 22028
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== legacy-v4 detectV4HomeResidue tail ==\n'
sed -n '260,300p' src/genie-commands/legacy-v4.ts
printf '\n== doctor checkV4Residue call site ==\n'
sed -n '320,430p' src/genie-commands/doctor.ts
printf '\n== fs throw probe ==\n'
node - <<'JS'
const { lstatSync, mkdtempSync, writeFileSync, rmSync } = require('node:fs');
const { tmpdir } = require('node:os');
const { join } = require('node:path');
const dir = mkdtempSync(join(tmpdir(), 'genie-size-probe-'));
const file = join(dir, 'vanish.txt');
writeFileSync(file, 'x');
rmSync(file);
try {
lstatSync(file);
console.log('unexpected: lstatSync did not throw');
} catch (err) {
console.log(err && err.code ? err.code : String(err));
}
JSRepository: automagik-dev/genie
Length of output: 6024
Guard sizeOfPathTree() here
detectV4HomeResidue() already treats vanished paths as normal during the lstatSync() check, but this call can still throw if the file disappears before sizing. Wrap it like doctor.ts’s safeSizeOf() so concurrent cleanup skips the relic instead of aborting genie doctor.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/genie-commands/legacy-v4.ts` around lines 216 - 289,
`detectV4HomeResidue()` can still fail when `sizeOfPathTree()` races with
concurrent cleanup after the initial `lstatSync()` check. Update the relic
sizing path to use the same safe sizing approach as `doctor.ts`’s
`safeSizeOf()`, so any `lstatSync`/`readdirSync`/nested lookup error is caught
and that entry is skipped instead of aborting `genie doctor`. Keep the change
localized around `sizeOfPathTree` and its call site in `detectV4HomeResidue`.
| /** | ||
| * Age-filtered (48h default): scheduler.log is a dead v4 artifact on upgraded | ||
| * machines, and without the filter a June disk-full incident resurfaced as | ||
| * "Recent scheduler signals" weeks later on a healthy machine. Entries with an | ||
| * unparseable/missing timestamp are kept — staleness must be proven, not | ||
| * assumed. Exported (with injectable clock) for boundary tests. | ||
| */ | ||
| interface ParsedSignalLine { | ||
| level: string; | ||
| event: string; | ||
| ts: string | null; | ||
| tsMs: number; | ||
| error?: string; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Docstring is attached to the wrong declaration; minor redundant parsing.
The comment block at Lines 1051-1057 (mentions "Exported (with injectable clock) for boundary tests") describes summarizeJsonlSignals, but it's placed directly above interface ParsedSignalLine instead. Also, Line 1092 re-parses Date.parse(newestStaleTimestamp) on every stale line instead of tracking the numeric tsMs of the current newest stale entry directly.
♻️ Suggested tidy-up
-/**
- * Age-filtered (48h default): scheduler.log is a dead v4 artifact on upgraded
- * machines, and without the filter a June disk-full incident resurfaced as
- * "Recent scheduler signals" weeks later on a healthy machine. Entries with an
- * unparseable/missing timestamp are kept — staleness must be proven, not
- * assumed. Exported (with injectable clock) for boundary tests.
- */
interface ParsedSignalLine {
level: string;
event: string;
ts: string | null;
tsMs: number;
error?: string;
}
@@
+/**
+ * Age-filtered (48h default): scheduler.log is a dead v4 artifact on upgraded
+ * machines, and without the filter a June disk-full incident resurfaced as
+ * "Recent scheduler signals" weeks later on a healthy machine. Entries with an
+ * unparseable/missing timestamp are kept — staleness must be proven, not
+ * assumed. Exported (with injectable clock) for boundary tests.
+ */
export function summarizeJsonlSignals(path: string, nowMs: number = Date.now()): JsonlSignalSummary {
const signals = new Map<string, RecentLogSignal>();
let newestStaleTimestamp: string | null = null;
+ let newestStaleMs = -Infinity;
for (const line of tailLines(path)) {
const parsed = parseSignalLine(line);
if (!parsed) continue;
if (parsed.ts && !Number.isNaN(parsed.tsMs) && nowMs - parsed.tsMs > SCHEDULER_SIGNAL_MAX_AGE_MS) {
- if (newestStaleTimestamp === null || Date.parse(newestStaleTimestamp) < parsed.tsMs) {
+ if (parsed.tsMs > newestStaleMs) {
+ newestStaleMs = parsed.tsMs;
newestStaleTimestamp = parsed.ts;
}
continue;
}Also applies to: 1085-1104
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync, execSync, spawn } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/genie-commands/update.ts` around lines 1051 - 1064, The docstring is
attached to the wrong symbol: move the age-filtered/exported boundary-test
comment from interface ParsedSignalLine so it documents summarizeJsonlSignals
instead. While touching summarizeJsonlSignals, remove the repeated
Date.parse(newestStaleTimestamp) work by tracking the newest stale entry’s
numeric tsMs directly and comparing against that value when updating the stale
summary.
Rolling Promotion PR
Auto-maintained rolling promotion PR from
devtomain.Process:
ready-to-mergeadded when all checks passSummary by CodeRabbit
New Features
genie doctor --fixoption to automatically clean up detected v4 residue with backups.Bug Fixes
Chores
5.260705.8.