Skip to content

chore: rolling promotion dev -> main - #2533

Merged
namastex888 merged 3 commits into
mainfrom
dev
Jul 5, 2026
Merged

namastex888 merged 3 commits into
mainfrom
dev

Conversation

@namastex888

@namastex888 namastex888 commented Jul 5, 2026 •

Copy link
Copy Markdown
Contributor

Rolling Promotion PR

Auto-maintained rolling promotion PR from dev to main.

Process:

  • This PR is automatically created and kept open
  • Human reviews and merges when ready
  • Label ready-to-merge added when all checks pass

IMPORTANT: Merge with "Create a merge commit" — NEVER squash.
Squash merging breaks history sync between dev and main,
causing the next rolling PR to show all commits again.

Human approval required for merge to production.

Summary by CodeRabbit

  • New Features

    • Added a genie doctor --fix option to automatically clean up detected v4 residue with backups.
    • Improved doctor output to report v4 residue, including reclaimable items and clearer status details.
    • Update diagnostics now ignore stale scheduler signals by default and show the most recent old timestamp when no recent signals exist.
  • Bug Fixes

    • Reduced noise from outdated scheduler errors in update reports.
  • Chores

    • Bumped the app and plugin version to 5.260705.8.

namastex888 and others added 3 commits July 5, 2026 03:38
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
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds v4-era genie home residue detection and backup-first cleanup to legacy-v4.ts, exposes it via a new doctor check and --fix flag, filters stale scheduler log signals by age (48h) in update diagnostics with a stale-timestamp fallback message, updates several package/plugin manifest versions, and adds a wish document.

Changes

V4 Home Residue Detection, Cleanup, and Doctor Integration

Layer / File(s) Summary
Manifest and detection contracts
src/genie-commands/legacy-v4.ts
Adds HomeResidueEntry/HomeResidueKind types, expands V4_LEGACY_MANIFEST.homeResidue, and implements detectV4HomeResidue, sizeOfPathTree, and detectUncertainKeeps.
Cleanup engine emit routing and residue removal
src/genie-commands/legacy-v4.ts
Adds logSink/ctx.emit output routing, extends V4CleanupOptions/V4CleanupResult/V4CleanupActionKind, and implements cleanupHomeResidue with backup-first copy then delete, wired into cleanupV4's no-op detection and result.
Doctor check and --fix flag
src/genie-commands/doctor.ts, src/genie.ts
Adds exported checkV4Residue aggregating reclaimable residue into pass/warn results, extends doctorCommand with a fix option that runs cleanupV4, and adds the --fix CLI flag.
Tests and fixtures
src/genie-commands/legacy-v4.test.ts, src/genie-commands/doctor.test.ts, src/genie-commands/install.test.ts, src/genie-commands/__tests__/update.test.ts
Adds tests for detection/cleanup, doctor check pass/warn/fix paths and CLI e2e behavior, and updates fixtures for the new homeResidue field.

Estimated code review effort: 4 (Complex) | ~60 minutes

Scheduler Log Age Filtering

Layer / File(s) Summary
Age-filtered signal summarizer
src/genie-commands/update.ts
Reworks summarizeJsonlSignals to return JsonlSignalSummary, excluding entries older than 48h and tracking newestStaleTimestamp; wires it through collectUpdateDiagnostics and printDiagnosticsSummary.
Tests
src/genie-commands/__tests__/update.test.ts
Adds age-filter test coverage for mixed, stale-only, boundary, and unparseable timestamp cases.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Version Bumps and Wish Doc

Layer / File(s) Summary
Package/plugin version bumps
package.json, plugins/genie/package.json, plugins/genie/.claude-plugin/plugin.json, .claude-plugin/marketplace.json
Bumps version strings from 5.260705.7 to 5.260705.8.
Wish specification
.genie/wishes/v4-home-residue-doctor/WISH.md
Adds a wish document describing the residue cleanup and diagnostics plan.

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
Loading

Estimated code review effort: 4 (Complex) | ~50 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects this rolling promotion PR from dev to main and is concise and specific enough.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@namastex888
namastex888 merged commit a471d14 into main Jul 5, 2026
24 of 25 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (1)
src/genie-commands/legacy-v4.ts (1)

502-524: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

console.log default violates the no-console.log guideline for src/**/*.ts.

The default emit sink at line 518 falls back to console.log. As per coding guidelines, **/*.ts under src/** should not use console.log in source files. Even as a documented CLI-chatter default, this is a direct violation; prefer process.stdout.write (matching the pattern already used for the json-mode stderr sink one call site away in doctor.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 use console.log in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2aea4c5 and be8f30a.

📒 Files selected for processing (13)
  • .claude-plugin/marketplace.json
  • .genie/wishes/v4-home-residue-doctor/WISH.md
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/package.json
  • src/genie-commands/__tests__/update.test.ts
  • src/genie-commands/doctor.test.ts
  • src/genie-commands/doctor.ts
  • src/genie-commands/install.test.ts
  • src/genie-commands/legacy-v4.test.ts
  • src/genie-commands/legacy-v4.ts
  • src/genie-commands/update.ts
  • src/genie.ts

Comment on lines +298 to +301
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\(/);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

Comment on lines +236 to +251
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/).',
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Suggested change
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.

Comment on lines +216 to +289
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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 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));
}
JS

Repository: 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`.

Comment on lines +1051 to +1064
/**
* 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;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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.

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