Skip to content

fix(analysis): repair host discovery and evidence coverage - #213

Open
Dzuhaib wants to merge 3 commits into
QoderAI:mainfrom
Dzuhaib:fix/analysis-host-discovery-evidence-coverage
Open

Dzuhaib wants to merge 3 commits into
QoderAI:mainfrom
Dzuhaib:fix/analysis-host-discovery-evidence-coverage

Conversation

@Dzuhaib

@Dzuhaib Dzuhaib commented Oct 24, 2026

Copy link
Copy Markdown

Summary

  • Consolidate Qoder asset and session home resolution, document --qoder-home, and prefer an existing ~/.qoder-cn before ~/.qoder.
  • Fetch 30/90/180-day history windows independently, expose commit limits, truncation, and coverage, and downgrade the review matrix when any window is incomplete.
  • Recognize root and nested AGENTS.md and CLAUDE.md carriers, exclude scratch directories from suggested scopes, discover tracked npm and Just validation gates, and include root AGENTS.md in Claude Rules.
  • Add a real lead/session binding regression while preserving the conflict path.
  • Make Windows fixtures portable with directory junctions, isolated DSH home state, and clean child-process shutdown.

Spec and stories

docs/specs/2026-10-24-harness-analysis-defects.md (Stories #212, #195, #164).

Validation

  • npm test: 1765 passed, 30 skipped, 0 failed
  • npm run harness-studio-apps:test: 3 files passed

Maintainers are allowed to edit this PR branch.

Consolidate Qoder asset and session home resolution, document --qoder-home, and prefer an existing ~/.qoder-cn before ~/.qoder. Fetch 30/90/180-day history windows independently, expose commit limits, truncation, and coverage, and downgrade the review matrix when any window is incomplete.

Recognize root and nested AGENTS.md and CLAUDE.md carriers, exclude scratch directories from suggested scopes, discover tracked npm and Just validation gates, and include root AGENTS.md in Claude Rules. Add a real lead/session binding regression while preserving the conflict path, and make Windows fixtures portable with directory junctions, isolated DSH home state, and clean child-process shutdown.

Spec: docs/specs/2026-10-24-harness-analysis-defects.md (Stories QoderAI#212, QoderAI#195, QoderAI#164). Test: npm test (1765 passed, 30 skipped); npm run harness-studio-apps:test (3 files passed).

Co-authored-by: opencode (DeepSeek V4.1 Flash) <noreply@opencode.ai>

@sorrycc sorrycc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this. Moving the Qoder home logic into one module and fetching each history window separately both fix real problems. I checked out the branch, read the diff and ran npm test (results at the end). Some changes are needed before merge, mainly items 1–3.

Must fix

1. resolveQoderSessionHome now uses QODER_HOME even when it points at the shared client cache (scripts/host-support/qoder-home.mjs)

Before this PR, the session analyzer ignored QODER_HOME and used options.home ?? options["qoder-home"] ?? "~/.qoder". Now it uses QODER_HOME without checking it. In the same file, resolveQoderSharedClientCacheRoot treats a QODER_HOME whose basename is not .qoder/qoder as the SharedClientCache root. A user with QODER_HOME=~/Library/Application Support/Qoder/SharedClientCache will have session analysis read transcripts from the cache directory and quietly find zero sessions. That is the same kind of failure #212 is about. Use the same check as the asset resolver:

if (environmentHome && looksLikeQoderAssetHome(environmentHome)) return path.resolve(expandHome(environmentHome));

and add a test with a non-asset QODER_HOME.

2. A root AGENTS.md and a root CLAUDE.md are counted twice (project-profile.mjs agentInstructionFiles)

Many repos ship both at the root, often with CLAUDE.md being @AGENTS.md or a symlink. Both now become separate instruction files with the same scope. count/rootCount double, and because status compares files.length to suggestedMinimum, a repo can move from thin to adequate when nothing new covers it. The same happens for every nested directory that has both files. Suggestion: dedupe by scope for count/rootCount/nestedCount/status, and keep the per-file list and carriers breakdown for reporting.

3. The consolidation is incomplete; three ~/.qoder fallbacks are still hardcoded

The PR says it consolidates Qoder home resolution, but these still default to ~/.qoder, so a CN-only install still misses them:

  • scripts/coding-agent-practices/checkup/sources.mjs:34
  • scripts/harness-component-snapshot/snapshot.mjs:113
  • scripts/harness-analysis/validate-canvas.mjs:633 (it also ignores the asset-home basename check on QODER_HOME)

Please route them through resolveQoderAssetHome, or say in the PR why they are excluded. Also, profiles/qoder.mjs still has fallbackLabel: "~/.qoder", so redacted evidence will say ~/.qoder even when ~/.qoder-cn was read.

Should fix

4. Each window now reruns git log --numstat

historyWindows now runs one full git log -N --numstat per window, so 4 log walks instead of 1, each with a 30s timeout. On this repo, analyzeGitHistoryProfile went from 483 ms to 1251 ms (about 2.6×), and the cost grows with repo size. Two cheaper options:

  • Reuse the main scan when it already covers the window. If the oldest commit in commits is older than sinceSeconds, the window is complete, so filter in memory as before and only query git for windows the main scan didn't reach.
  • Or run one git log --since=<largest window> and split the results in memory.

5. truncated is set when a window has exactly maxCommits commits

windowParsed.length >= maxCommits marks a window with exactly N commits as truncated. Request -${maxCommits + 1} and check > maxCommits (then drop the extra commit) to make it exact.

6. With the default limit, active repos always get review

With DEFAULT_MAX_COMMITS = 500, this repo's 90- and 180-day windows both come back truncated (618 commits in 90 days), so history-window-coverage will never be ready for a moderately active project. That is defensible, but the matrix note should say how to fix it (raise --max-commits). Otherwise users see a permanent review item with no next step. Also, the confidence !== "low" condition is new and not part of the spec's "downgrade when any window is incomplete". Please mention it in the spec or drop it.

7. PROJECT_GATE_PATTERN accepts watch and dev variants

(?::.+)? matches test:watch, build:watch, test:ui, e2e:headed and similar. These are now presented to agents as validation gate commands, and an agent that runs npm run test:watch will hang. Please exclude :watch|:dev|:ui|:headed|:open suffixes, or only allow suffixes known to finish.

Nits / questions

  • Claude Rules now includes root AGENTS.md as agents-md-compat. If Claude Code only reads it through an @AGENTS.md import in CLAUDE.md, showing it as a Claude rule overstates what Claude loads. Can you link the host behavior this relies on? Also, the visited set compares unresolved paths, so CLAUDE.md -> AGENTS.md symlinks produce two items. Consider realpath.
  • defaultQoderHome() picks ~/.qoder-cn whenever it exists. A user who has both dirs (e.g. tried CN once, mainly uses international) now silently loses ~/.qoder. Consider preferring the home with session data, or warning when both exist.
  • qoder-home.mjs has its own copy of expandHome instead of importing session-analysis/paths.mjs. If that's to avoid an import cycle, a one-line comment would help.
  • SCRATCH_DIRECTORIES stops scratch dirs from being suggested as scopes, but their files still count toward totals.sourceFiles and therefore suggestedMinimum. Is that intended?

Tests

npm test on 9319fce (macOS, Node 26): 119 files passed, 1 skipped; 1788 tests passed, 7 skipped, 0 failed. The new tests cover the happy paths well. Tests are still missing for items 1, 2 and 7 (non-asset QODER_HOME in session analysis, root AGENTS.md + CLAUDE.md together, test:watch script).

Bundle size: the shipped code shrinks slightly (about 50 duplicated lines in providers/qoder.mjs replace one 65-line shared module). No new dependencies.

Requesting changes for 1–3. 4–7 can be follow-ups if you'd rather keep this PR focused.

Ignore a QODER_HOME that names the shared client cache, route the remaining
Qoder fallbacks through the shared asset resolver, and report a provider-default
fallback label. Count instruction scopes once when AGENTS.md and CLAUDE.md share
a directory, while keeping the per-file and carrier breakdown.

Reuse the bounded history scan for windows it already covers, make truncation
exact at the commit limit, exclude watcher/dev/UI gates, and point the review
matrix at --max-commits. Deduplicate Claude project rules by realpath and
prefer the Qoder home that has session data.

Spec: docs/specs/2026-10-24-harness-analysis-defects.md (Stories QoderAI#212, QoderAI#195, QoderAI#164).
Test: npm test (1767 passed, 30 skipped).

Co-authored-by: opencode (DeepSeek V4.1 Flash Free) <noreply@opencode.ai>
@Dzuhaib

Dzuhaib commented Oct 25, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed review. All items 1–7 and the nits are addressed in a57855f, now pushed.

Changes made

  • 1: resolveQoderSessionHome now applies looksLikeQoderAssetHome to QODER_HOME. A shared-client-cache value falls back to the product default instead of reading the cache. Added a session-analysis test that sets QODER_HOME to SharedClientCache and still finds the .qoder fixture.
  • 2: Instruction counts are deduped by scope for count/rootCount/nestedCount/status/suggestedAdditional. The per-file list and carrier breakdown still report every carrier. The dual-carrier test now expects 2 scopes and 4 files.
  • 3: Routed checkup sources, component snapshot, and Canvas validation through resolveQoderAssetHome. Checkup only reports environment evidence when QODER_HOME is an asset home. fallbackLabel is now <provider-default:qoderHome>. No listed fallback was excluded.
  • 4: historyWindows reuses the main scan when it is complete or its oldest commit is older than the window, and only runs git for windows the scan did not reach. Covered windows no longer spawn an extra git log.
  • 5: Main and window scans request maxCommits + 1 and mark truncation only when the parsed length is greater than the limit, then drop the extra commit. Added an exact-limit test (4 commits, limit 4 -> complete).
  • 6: The history-window-coverage note now tells users to raise --max-commits; AC-3 documents the low-confidence downgrade.
  • 7: Gate extraction rejects :watch, :dev, :ui, :headed, and :open suffixes. test:unit still passes. Tests cover test:watch, test:ui, and lint:dev.

Nits

  • Linked Claude's AGENTS.md behavior in the code/AC and deduped project rules by realpath.
  • defaultQoderHome prefers .qoder-cn only when .qoder is absent or has no session data; when both have data, .qoder wins. Added a both-homes test.
  • Added the comment explaining why expandHome stays local (public-surface rule plus the import cycle).
  • Scratch files no longer inflate suggestedMinimum; totals.sourceFiles, sourceFilesPerInstruction, and coverage still include them.

Intentionally not changed

  • 4: I used the main-scan reuse option and did not add a separate single git log --since=<largest window> pass.
  • 6: DEFAULT_MAX_COMMITS remains 500; the fix is the documented --max-commits path rather than a larger default.
  • Claude rules: I kept the root AGENTS.md entry as agents-md-compat; I did not make it conditional on the absence of CLAUDE.md.
  • Scratch density: only the suggested minimum changed. If you want scratch files removed from sourceFilesPerInstruction and coverage as well, I can do that separately.
  • No new dependencies and no package changes.

Validation

  • Added tests for the non-asset QODER_HOME, dual AGENTS.md/CLAUDE.md, and test:watch/test:ui/lint:dev cases.
  • Windows npm test: 119 files passed, 1 skipped; 1767 passed, 30 skipped, 0 failed. The higher skip count versus your macOS run is the platform-specific skip set.
  • Also manually checked a CN-only home with QODER_HOME pointing at SharedClientCache: session resolution and checkup both read .qoder-cn.

@Dzuhaib
Dzuhaib requested a review from sorrycc October 25, 2026 20:08

@sorrycc sorrycc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the quick turnaround. I checked a57855f against each item from my last review and re-ran the suite.

Previous items: all addressed

  • 1 resolveQoderSessionHome now applies looksLikeQoderAssetHome, and the new session-analysis test covers a SharedClientCache value. ✅
  • 2 Counts and status are deduped by scope, and the per-file list and carriers still show every file. ✅
  • 3 The three call sites I listed now go through resolveQoderAssetHome, and fallbackLabel no longer says ~/.qoder. ✅ (but see below)
  • 4 Windows reuse the main scan when it covers them. That's the option I'd have picked. ✅
  • 5 maxCommits + 1 with a > check, plus the exact-limit test. ✅
  • 6 The note points to --max-commits, and AC-3 documents the confidence downgrade. ✅
  • 7 isProjectGateScript rejects the watch/dev/ui/headed/open suffixes, with tests. ✅
  • Nits: realpath dedupe, both-homes preference, the expandHome comment and the scratch exclusion all look good.

Still needed (one item)

resolveProviderHome in checkup/contract.mjs:187 still hardcodes env.QODER_HOME ?? ~/.qoder. I missed this one last time, sorry. It is the same bug as item 3, and it is on the write path: checkup/apply.mjs:69 uses it to find where provider-home source patches go, and scan.mjs:913 falls back to it for fingerprints. I checked with a CN-only HOME (only ~/.qoder-cn exists):

resolveQoderAssetHome()           -> ~/.qoder-cn
resolveProviderHome("qoder", {})  -> ~/.qoder
# with QODER_HOME=.../Qoder/SharedClientCache
resolveQoderAssetHome()           -> ~/.qoder-cn
resolveProviderHome("qoder", {})  -> .../Qoder/SharedClientCache

So on a CN install, checkup apply resolves patch targets under a different home from the one the scan inventoried, and with a cache-valued QODER_HOME it points into the shared cache. The fix is one line, qoder: resolveQoderAssetHome({ env }), plus a test like the session-analysis one. That should be the last hardcoded Qoder fallback in scripts/. A grep for QODER_HOME and ".qoder" finds nothing else outside workspace-relative .qoder paths.

Nits (optional)

  • providers/claude.mjs: the new comment says Claude reads root AGENTS.md "when no CLAUDE.md claims the project", but the entry is added unconditionally. You said that was intentional, so please reword the comment so it doesn't contradict the code.
  • QODER_SESSION_DIRECTORIES includes projects and sessions, but the Qoder session analyzer only reads audit/ and logs/. An empty projects/ dir would make .qoder count as having session data. This is harmless in practice, but ["audit", "logs"] would match what the analyzer actually reads.

Tests

npm test on a57855f (macOS, Node 26): 119 files passed, 1 skipped; 1790 tests passed, 7 skipped, 0 failed.

Requesting changes only for the resolveProviderHome fallback. Everything else is ready.

Checkup provider-home resolution read QODER_HOME or ~/.qoder directly, so a CN-only install patched a different home from the one the scan inventoried, and a SharedClientCache QODER_HOME could target the cache. Use resolveQoderAssetHome, and cover the apply path. Reword the Claude AGENTS.md comment and narrow Qoder session detection to audit and logs.
@Dzuhaib

Dzuhaib commented Oct 26, 2026

Copy link
Copy Markdown
Author

Thanks for the careful review. I had added the fixes in commit 084c725 (fix(analysis): resolve Qoder provider home consistently).

Fixed the remaining fallback in checkup/contract.mjs:

qoder: resolveQoderAssetHome({ env })

Added a regression test for a CN-only HOME and QODER_HOME=.../Qoder/SharedClientCache, including the resolveSourceRef path used by apply.

Applied both nits as well:

  • Reworded the Claude AGENTS.md comment so it matches the unconditional entry.
  • Narrowed QODER_SESSION_DIRECTORIES to ["audit", "logs"]; the both-homes test now uses audit.

Verification:

  • npm test: 119 files passed, 1 skipped; 1768 passed, 30 skipped, 0 failed.
  • npm run harness:generated: passed.
  • git diff --check: clean.

Grepped QODER_HOME and .qoder; no other hardcoded Qoder fallback remains outside the workspace-relative paths. Please take another look.

@Dzuhaib
Dzuhaib requested a review from sorrycc October 26, 2026 09:42

@sorrycc sorrycc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I checked 084c725 against my last review and re-ran the suite. Approving.

Previous item: addressed

  • resolveProviderHome fallback. checkup/contract.mjs now uses resolveQoderAssetHome({ env }). The new test covers both a CN-only HOME and a SharedClientCache-valued QODER_HOME, and it goes through resolveSourceRef, so it exercises the path checkup apply uses. ✅
  • Nits. The Claude AGENTS.md comment now matches the unconditional entry. QODER_SESSION_DIRECTORIES is narrowed to ["audit", "logs"], and the both-homes test now uses audit. ✅

I grepped scripts/ again for QODER_HOME and .qoder. The only remaining QODER_HOME reads are in inventory.mjs and checkup/sources.mjs, and both apply looksLikeQoderAssetHome. Every other match is a workspace-relative .qoder path.

Follow-up (not blocking)

packages/harness-studio/src/server/artifacts/registry/artifact-viewers.ts:38 (defaultCanvasViewerRoot) still uses QODER_HOME without the asset-home check and falls back to ~/.qoder. So on a CN-only install, Studio looks for Canvas viewers under the wrong home. This PR doesn't touch that file and it sits in a separate TS package, so a separate issue or PR is fine.

Tests

On 084c725 (macOS, Node 26):

  • npm test: 119 files passed, 1 skipped; 1791 tests passed, 7 skipped, 0 failed.
  • npm run harness:generated: passed.
  • git diff --check: clean.

Thanks for working through all three rounds.

@Dzuhaib

Dzuhaib commented Oct 26, 2026

Copy link
Copy Markdown
Author

Thanks for the approval and for checking the follow-up. Agreed — the Studio defaultCanvasViewerRoot fallback is outside this PR's scope. I'll handle it separately in a follow-up issue/PR using the same asset-home check, unless you'd prefer an issue first.

This branch has not been deployed

No deployments
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.

2 participants