Skip to content

integration(HOLMES): MP‑01..03 + score breakdown + CI detect + precommit - #334

Merged
flyingrobots merged 29 commits into
mainfrom
integration/holmes-2025-10-27
Oct 29, 2025
Merged

flyingrobots merged 29 commits into
mainfrom
integration/holmes-2025-10-27

Conversation

@flyingrobots

Copy link
Copy Markdown
Owner

Integrated via merge commits (no rebase): #323, #264, #284, #319, #265. See description for conflict policy.

…rojection schema/render skeleton; stabilize tests
…ree id only); soft-fail behind --project-merge
…erged tree via write-tree; fallback from merge-tree
…ojection clean merge is unavailable or conflicts; render projection impact
…tes for major SaaS; link from merge-projection plan
feat(holmes): MP-05 — worktree fallback + conflict detection
…25-10-27

Conflict resolution policy:
- Keep detect-schema + pre-commit from earlier merges
- Prefer score breakdown versions for HOLMES code/schema/tests
- Keep HEAD versions for AGENTS.md and holmes integration docs
- Drop go-public-checklist.md (deleted)
@coderabbitai

coderabbitai Bot commented Oct 27, 2025 •

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

  • New Features

    • Added merge projection capability to detect conflicts before merging
    • Introduced policy configuration system for Holmes predictions
    • Added Git activity analysis to Moriarty readiness assessment
    • Integrated readiness explanations with threshold-driven reporting
  • Enhancements

    • Pre-commit hook now auto-syncs lockfile on dependency changes
    • Restructured evidence and scoring breakdowns in reports
    • Improved workflow change detection for CLI testing
  • Documentation

    • Added comprehensive guides for Holmes policies, architecture, and Moriarty scenarios

Subject: Re: Your PR is a comprehensive but MASSIVE refactor — here's my cut-through analysis

Look, I'm going to be direct because that's what you need: you've submitted something that touches nearly every corner of the codebase in a single PR. That's a problem for review, but let's at least get the facts straight.

Walkthrough

Adds pnpm lockfile auto-sync hook, refactors CI workflows with path-based detection, extends HOLMES/Moriarty with Git activity analysis and merge projection capabilities, introduces policy-driven configuration system with templates, restructures scoring/bundle/evidence schemas, and adds issue creation automation.

Changes

Cohort / File(s) Summary
Git Hooks
.githooks/pre-commit
Pre-commit hook auto-syncing pnpm-lock.yaml when dependency-resolution manifests (package.json, pnpm-workspace.yaml) change; validates pnpm availability and version; skippable via environment flag
CI Workflows
.github/workflows/cli-quick.yml, .github/workflows/wesley-holmes.yml
Replaced path-based triggers with branch/PR-type filters; introduced path detection step; added MORIARTY context computation via GitHub API; wrapped CLI steps with conditionals; enhanced PR report with collapsible sections
Architecture Documentation
docs/architecture/holmes-architecture.md, docs/architecture/holmes-integration.md
Added Moriarty Intent/Signals documentation with corrective activity index; detailed CI integration notes for Git activity blending and readiness thresholds
Policy Specification & Templates
docs/holmes-policy-spec.md, docs/holmes-policy/README.md, docs/templates/holmes-policy/*
New policy system spec (v1) defining merge projection modes, readiness rules, and templated defaults for GitHub, GitLab, Bitbucket, Azure DevOps, Gitea, and trunk-focused strategies
Planning & Scenario Documentation
docs/holmes-merge-projection-plan.md, packages/wesley-holmes/docs/moriarty-scenarios.md
Outlined projected-merge intelligence plan with MergePlanner/strategies, Analysis, Caching phases; detailed 27+ edge-case scenarios for Moriarty stress-testing
Scoring Engine & Bundle Structure
packages/wesley-core/src/application/Scoring.mjs, packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs, schemas/scores.schema.json
Renamed SCORE_SCHEMA_VERSION → BUNDLE_VERSION; refactored SCS/TCI/MRI calculations to modular detail-rich breakdowns; changed UID generation from table_* to tbl:* and col:* patterns; restructured schema with breakdown components
Evidence & Orchestration
packages/wesley-core/src/application/EvidenceMap.mjs, packages/wesley-core/src/domain/WesleyOrchestrator.mjs
Added hasArtifact/forEachEvidence methods; updated bundle return to include bundleVersion and scoreDetails with history
CLI Generator Updates
packages/wesley-cli/src/commands/generate.mjs, packages/wesley-generator-js/src/typescript.mjs, packages/wesley-generator-js/src/zod.mjs
Removed centralized output path mapping; derived outDir from options; changed artifact output to use direct paths under ops/ prefix; moved IR/HOLMES evidence to .wesley/ directory; updated table UID defaults
HOLMES Analysis & Rendering
packages/wesley-holmes/src/Holmes.mjs, packages/wesley-holmes/src/report-schemas.mjs
Introduced weight override system; refactored breakdown normalization; added directive-based weight inference; updated rendering to consume new breakdown shapes (SCS, TCI, MRI components)
Moriarty Git Activity & Merge Projection
packages/wesley-holmes/src/Moriarty.mjs, packages/wesley-holmes/src/cli.mjs, packages/wesley-holmes/src/merge/Planner.mjs, packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs, packages/wesley-holmes/src/merge/WorktreeStrategy.mjs
Extended Moriarty constructor to accept context; added Git activity window/PR analysis; computed blended velocity and burstiness; integrated merge-tree (no-touch computation) and worktree (conflict detection) strategies; plumbed projection results into confidence penalties
Host Node Runtime
packages/wesley-host-node/src/adapters/createNodeRuntime.mjs
Extended writer.writeFiles signature to accept baseDir; fallback artifact path resolution using baseDir + artifact.name
Test Infrastructure & Scenarios
packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs, packages/wesley-holmes/test/moriarty-scenarios.test.mjs, packages/wesley-core/test/unit/scoring-engine.test.mjs, test/holmes-e2e.bats
Added Moriarty test helpers (temp dirs, fake git, history/context writers, predict runner); defined 27+ scenario-based tests; updated scoring/TCImock-ups to use new *Details methods; tightened E2E breakdown validation
Issue Automation
scripts/gh-issues/create_issues.mjs, scripts/gh-issues/issue_plan.json
New script automating GitHub issue creation, ProjectV2 field setup, label management, and inter-issue dependency linking based on formal plan; issue_plan.json outlines MergePlanner milestone items
Build Log
CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
Appended correction and push entries for spec compliance and "#334 refresh" integration; marked prior corrections as superseded

Sequence Diagrams

sequenceDiagram
  actor Developer
  participant PreCommit as Pre-commit Hook
  participant Manifest as package.json/<br/>pnpm-workspace.yaml
  participant pnpm
  participant Lock as pnpm-lock.yaml

  Developer->>PreCommit: git commit
  PreCommit->>Manifest: inspect staged changes
  
  alt Resolution fields affected
    PreCommit->>pnpm: validate availability & version
    alt pnpm OK
      PreCommit->>pnpm: pnpm install --lockfile-only
      pnpm->>Lock: regenerate
      PreCommit->>Lock: stage updated lock
      PreCommit-->>Developer: ✓ commit proceeds
    else pnpm missing/mismatched
      PreCommit-->>Developer: ✗ block commit, guide user
    end
  else No resolution impact
    PreCommit-->>Developer: ✓ short-circuit no-op
  end
Loading
sequenceDiagram
  participant CLI as holmes predict
  participant Git as Git / Worktree
  participant Merge as MergeTree/<br/>Worktree Strategy
  participant Moriarty as Moriarty Scorer
  
  CLI->>CLI: load bundle, history, context
  
  alt --project-merge provided
    CLI->>CLI: build MergePlanner
    CLI->>Merge: plan(baseRef)
    Merge-->>CLI: planned
    
    alt MergeTreeStrategy available
      CLI->>Git: merge-tree --write-tree HEAD..base
      Git-->>Merge: mergedTree id, status
    else fallback
      CLI->>Git: worktree checkout base
      Git->>Git: attempt merge in worktree
      
      alt conflicts detected
        Git-->>Merge: status: conflicts + file list
      else clean
        Git-->>Merge: status: clean + tree id
      end
      
      Git->>Git: cleanup worktree
    end
    
    Merge-->>CLI: status, mergedTree, details
    CLI->>Moriarty: incorporate projection result
    alt projection failed or conflicts
      Moriarty->>Moriarty: penalize confidence (-30/-50)
      Moriarty->>Moriarty: record MERGE_PROJECTION_ISSUE
    end
  else no merge
    Moriarty->>Moriarty: standard readiness/plateau logic
  end
  
  Moriarty-->>CLI: report with readiness explain
  CLI->>CLI: write moriarty-report.json
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring close scrutiny:

  • Scoring engine refactor (packages/wesley-core/src/application/Scoring.mjs): Dense logic migration from monolithic per-metric calculations to modular detail-rich blocks; new SCS/TCI/MRI breakdown shapes need careful validation against thresholds and rollup logic.
  • Merge strategies (MergeTreeStrategy.mjs, WorktreeStrategy.mjs): Novel Git-based operations with error paths and resource cleanup; merge-tree correctness and worktree ephemeral cleanup need scrutiny.
  • UID namespace shift (table and field UID generation across multiple files): Changes from table_*/${tableUid}_${fieldName} to tbl:*/col:TableName.FieldName ripple through evidence recording and cross-references; potential for off-by-one or namespace collision bugs.
  • CLI output path logic (packages/wesley-cli/src/commands/generate.mjs): Removed centralized path mapping; now derives from options.outDir with fallbacks and .wesley/ hardcoding; verify artifact output correctness and .wesley/ directory assumptions across all artifact types.
  • Bundle/schema restructuring (schemas/scores.schema.json, report-schemas.mjs): Extensive renaming (unitConstraints → unit_constraints, e2eOps → e2e_ops) and nested object changes; verify all consumers updated consistently.
  • Moriarty context & Git activity (Moriarty.mjs, cli.mjs): New Git window computation, activity blending, and burstiness penalties; ensure graceful fallback when Git is unavailable and environment knob interactions are sound.
  • Test coverage: Ensure new *Details method signatures and breakdown shapes are exercised; scenario-based tests should catch real-world Git/merge edge cases.

Possibly related issues

  • HOLMES CI auto-detect schema + per-PR bundle dir #223: The "Detect Schema" step added to .github/workflows/wesley-holmes.yml with WESLEY_SCHEMA fallback and bundle_dir derivation directly implements the auto-detect schema + per-PR bundle_dir behavior described in the issue.

Possibly related PRs


Poem

Merge trees dance where conflicts hide,
Activity metrics climb and glide,
Policies guide the readiness way,
Scores breakdown brighter each review day,
From pnpm locks to Moriarty's sight—
One massive PR made everything right. 🐰


Now, the hard truth: This PR is too large. It spans scoring logic, merge strategies, CLI surface, schema changes, and testing in one go. You've mixed infrastructure (hooks, workflows), feature work (merge projection, Git activity), refactoring (UID namespaces, bundle structures), and documentation. That makes it nearly impossible to review cleanly or to bisect if bugs emerge.

What you should have done:

  1. Land the UID namespace shift + scoring refactor first (with updated tests).
  2. Then merge strategies + Moriarty extensions in a follow-up.
  3. Then CLI plumbing.
  4. Then policy system & templates.
  5. Then documentation.

Yes, it would take longer. But reviewers could reason about each layer independently, and regressions would be localized.

For now: Split this. Get the scoring refactor reviewed separately. Then the merge logic. Then the rest. I know it's painful, but shipping a monolith like this is a maintenance nightmare waiting to happen.

BunBun

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description is severely deficient against the template requirements. The author provided only: "Integrated via merge commits (no rebase): #323, #264, #284, #319, #265. See description for conflict policy." This description omits every major required section: Context (no problem statement or issue links), Changes (no summary of modifications), Rationale & Alternatives, Risks, Backout Plan, and Checklist. The description does not explain what was solved, why this approach was chosen, what risks exist, or how to revert. Instead, it deflects to "see description" for conflict policy without providing said description, leaving reviewers without critical context for evaluating a high-effort integration touching scoring, merge planning, workflows, and CLI components. Add a complete PR description following the template: (1) Context section explaining the problems solved and linking the integrated PRs (#323, #264, #284, #319, #265); (2) Changes section summarizing the minimal modifications (pre-commit hook, score breakdown refactoring, merge planning stubs, CLI fallback, workflow enhancements); (3) Rationale explaining why merge commits were used and conflict resolution policy; (4) Risks and mitigations (merge conflict handling, scoring API changes, workflow behavior); (5) Backout Plan detailing reversion steps; (6) Completed Checklist confirming tests pass and docs are updated.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The PR title "integration(HOLMES): MP‑01..03 + score breakdown + CI detect + precommit" is directly related to the changeset and clearly summarizes the primary scope of changes. It identifies the main integration effort (HOLMES), the architectural phases (MP-01..03), and key components (score breakdown, CI detection, pre-commit hook). While the title is information-dense, it provides sufficient specificity for a developer scanning history to understand this encompasses merge planning infrastructure, scoring refactoring, workflow enhancements, and git hooks—all central themes of this substantial integration PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch integration/holmes-2025-10-27

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 and usage tips.

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

ℹ️ 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".

Comment thread packages/wesley-core/src/application/GenerationPipeline.mjs Outdated
Comment thread packages/wesley-core/src/application/Scoring.mjs
…(CLI --out-dir) so ops artifacts land under ; keep behavior for explicit artifact.path
@flyingrobots

Copy link
Copy Markdown
Owner Author

Update: pushed writer fallback fix (host-node writer.writeFiles respects --out-dir), moved field source recording inside buildTable loop (landed via #337), refreshed HOLMES integration to normalize evidence breakdown and weight sources. All CLI Bats pass locally, @wesley/core unit tests green. Ready for CI.\n\nRisk: low; touches writer fallback and HOLMES normalization only.\n\nNext: once green, we can re-run the HOLMES workflow on this PR to exercise the rendering pipeline.

@github-actions

github-actions Bot commented Oct 29, 2025 •

Copy link
Copy Markdown

🚢 SHIPME

[REALM] PASS — sha b360364

@github-actions

github-actions Bot commented Oct 29, 2025 •

Copy link
Copy Markdown

🔍 The Case of Pull Request #334

🕵️ SHA-lock HOLMES's Investigation (click to expand)

🕵️ SHA-lock HOLMES Investigation

  • Generated: 2025-10-29T16:54:06.666Z
  • Commit SHA: b360364
  • Bundle Version: 2.0.0

⚠️ Evidence valid only for commit b360364

🔍 Executive Deduction

"Watson, after careful examination of the evidence, I deduce..."

Weighted Completion: ██████░░░░ 60.0%
Scores: SCS 60.0% · TCI 70.0% · MRI 20.0%
Verification Status: 2 claims verified
Ship Verdict: REQUIRES INVESTIGATION

🧩 SCS Breakdown

Component Score Coverage
Sql 60.0% 0.60/1.00
Types 60.0% 0.60/1.00
Validation 60.0% 0.60/1.00
Tests 70.0% 0.70/1.00

🧪 TCI Breakdown

Component Score Coverage Note
Unit Constraints 70.0% 1/1 N/A
Unit Rls 0.0% — N/A
Integration Relations 0.0% — N/A
E2e Ops 0.0% — Not tracked in quick emit mode

⚠️ MRI Breakdown

Component Risk Share Points Count
Drops 100.0% 20 0
Renames Without Uid 0.0% 0 0
Add Not Null Without Default 0.0% 0 0
Non Concurrent Indexes 0.0% 0 0

📊 The Weight of Evidence

"Observe, Watson, how not all features carry equal importance..."

Element Weight Status Evidence Deduction
schema 5 ✅ SQL & tests out/schema.sql:1-9999@b360364 Elementary!

🚪 Security & Performance Gates

"Elementary security measures, Watson..."

Gate Status Evidence Holmes's Ruling
Migration Risk ✅ MRI: 20.0% "Acceptable risk"
Test Coverage ⚠️ TCI: 70.0% "Insufficient coverage"
Sensitive Fields ✅ 0 fields "All secured"

📋 The Verdict

⚠️ REQUIRES FURTHER INVESTIGATION
"Some clues remain unclear. Address the noted issues."

Signed and sealed,

  • S. Holmes, Consulting Detective

[END OF INVESTIGATION FOR COMMIT b360364]


🩺 Dr. WATSON's Verification (click to expand)

🩺 Dr. Watson's Independent Verification Report

Medical Examination of Evidence

  • Examination Date: 2025-10-29T16:54:48.742Z
  • Patient SHA: b360364

🔬 Citation Verification

"Let me examine each piece of evidence independently..."

  • Citations Examined: 2
  • Verified: 0 ✅
  • Failed: 0 ❌
  • Unable to Verify: 2

Verification Rate: 0.0%

📊 Mathematical Verification

"I shall recalculate Holmes's arithmetic..."

Holmes claimed SCS: 60.0%
Watson calculates: 100.0%
Difference: ⚠️ Significant

🔍 Consistency Analysis

"Checking for contradictions in Holmes's deductions..."

✅ No logical inconsistencies detected

🩺 Dr. Watson's Medical Opinion

VERIFICATION: CONCERNS NOTED ⚠️

"While Holmes's methods are generally sound, I have noted some"
"discrepancies that warrant further investigation."

Respectfully submitted,

  • Dr. J. Watson, M.D.
    Medical Examiner & Verification Specialist

🔮 Professor MORIARTY's Predictions (click to expand)

🧠 Professor Moriarty's Temporal Predictions

The Mathematics of Inevitability

  • Analysis Date: 2025-10-29T16:55:17.242Z

🔮 Current State

SCS: ██████░░░░ 60.0%
TCI: ███████░░░ 70.0%
MRI: 20.0% risk

📈 Velocity Analysis

SCS Velocity: +0.00%/day
Git Activity (window): 24h · commits 16 (0 relevant) · ~16.00 commits/day
↳ Magnitude: ~0 relevant LOC/day across ~0.0 files/day
Activity Index: 10 / 100 (PR 0, Window 25)
Blended Velocity: +0.06%/day
Commit Size Burstiness: 0 / 100 (higher = more uneven commit sizes)
⚠️ PLATEAU DETECTED - Low SCS movement and low recent Git activity.

⏰ Completion Predictions

ETA: Cannot predict (insufficient velocity)

"At current velocity, completion is... improbable."

🧪 Readiness EXPLAIN

  • SCS ≥ 80% → FAIL ❌ (actual 60.0%)
  • TCI ≥ 70% → PASS ✅ (actual 70.0%)
  • MRI ≤ 40% → PASS ✅ (actual 20.0%)
  • CI Stability ≥ 90% (branch main) → FAIL ❌ (actual 79% over ~168h)
  • Delivery context (last 168h): 21 issues closed · 8 PRs merged (informational, not gating)

Signals blend: SCS velocity (70%) + Git activity (30%, branch-first). Activity only suppresses false plateaus; it never inflates readiness.

📊 Historical Trajectory

10-29: ██████░░░░ 60.0%
10-29: ██████░░░░ 60.0%
10-29: ██████░░░░ 60.0%

"Every problem becomes elementary when reduced to mathematics"
— Professor Moriarty


Machine-readable reports: holmes-report.json · watson-report.json · moriarty-report.json (see workflow artifacts).


Filed at 221B Repository Street

coderabbitai[bot]
coderabbitai Bot previously approved these changes Oct 29, 2025
…here so evidence lookups match generators; update unit tests accordingly; also wire exportScores.version to BUNDLE_VERSION
…tors; record SDL 'source' under tbl:/col: scheme in GraphQLSchemaBuilder for better source↔SQL mapping
…nd clarify legacy SQL comment hints; keep EvidenceMap as source of truth
@flyingrobots
flyingrobots merged commit a3f49d2 into main Oct 29, 2025
17 of 18 checks passed
@flyingrobots
flyingrobots deleted the integration/holmes-2025-10-27 branch October 29, 2025 16:57

@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: 55

Caution

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

⚠️ Outside diff range comments (8)
packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (1)

68-91: ---

From: Linus Torvalds torvalds@linux-foundation.org
To: flyingrobots james@flyingrobots.dev
Cc: code-rabbit@vger.kernel.org
Subject: Re: [PATCH] GraphQLSchemaBuilder: UID format overhaul - BREAKING CHANGE CONFIRMED

Alright, I've dug through the commit history and the evidence actually shows your concerns were CORRECT—but the changes are already committed in 50b0db0, not pending. Let me spell out what's actually happening here:

THE FACTS:

You changed the UID format from table_${name.toLowerCase()}_${fieldName} to tbl:${tableName} and col:${tableName}.${fieldName}. This is a breaking change because:

  1. EvidenceMap serializes these UIDs to JSON (toJSON/fromJSON confirmed)—any persisted evidence with the old format is now orphaned
  2. Holmes.mjs literally pattern-matches on these UIDs (lines 250-252)—old format queries will silently fail
  3. EvidenceMap.fromJSON() has no migration logic, no backwards compatibility layer, no version checking between '1.0.0' bundles with different UID schemes
  4. Evidence bundles keyed on table_users_id will NOT match queries for col:Users.id

WHAT'S MISSING:

  • No migration path for existing evidence maps
  • No version bump in EvidenceMap when UID scheme changes
  • No test coverage verifying that deserialized old-format evidence still works (spoiler: it won't)
  • CONTRIBUTING.md mentions UID conventions now but doesn't document this breaking change or migration path

WHAT YOU NEED TO DO:

Either document this as a breaking change in version notes, OR implement backwards compatibility:

EvidenceMap.fromJSON() needs to:
1. Detect old UID format on deserialization
2. Transform old UIDs to new format
3. Bump the version field to signal the conversion

Without this, you're silently breaking anyone relying on persisted HOLMES evidence data.

BunBun

packages/wesley-cli/src/commands/generate.mjs (4)

184-194: Hard-coded evidence files will drift; derive from artifacts.

Referencing ${outDir}/schema.sql and tests.sql is brittle and often wrong. Use actual emitted artifact names.

- const sqlFile = `${outDir}/schema.sql`;
- const testFile = `${outDir}/tests.sql`;
- const evidence = {
-   evidence: {
-     schema: {
-       sql: [{ file: sqlFile, lines: '1-9999', sha }],
-       tests: [{ file: testFile, lines: '1-9999', sha }]
-     }
-   }
- };
+ const emitted = artifacts.map(a => ({
+   file: `${outDir}/${a.name}`,
+   lines: '1-9999',
+   sha
+ }));
+ const testRe = /\b(test|pgtap)\b/i;
+ const evidence = {
+   evidence: {
+     schema: {
+       sql: emitted.filter(e => !/\/ops\//.test(e.file) && e.file.endsWith('.sql') && !testRe.test(e.file)),
+       tests: emitted.filter(e => testRe.test(e.file))
+     }
+   }
+ };

269-283: T&S path: guard tests generator; don’t assume it exists.

If generators.tests is absent, emit_tests will throw. Mark node as skip or guard the handler.

- emit_tests: async (n, deps) => generators.tests.emitPgTap(deps.validate.ir),
+ emit_tests: async (n, deps) => (
+   generators?.tests?.emitPgTap
+     ? generators.tests.emitPgTap(deps.validate.ir)
+     : { files: [] }
+ ),

Optionally also skip the node at creation:

- { id: 'gen_tests', op: 'emit_tests', needs: ['validate'] },
+ { id: 'gen_tests', op: 'emit_tests', needs: ['validate'], skip: !generators?.tests?.emitPgTap },

430-472: Data race on collisions Map (TOCTOU).

Parallel workers mutate/read collisions in compileOpFile, so two same-base ops can bypass detection. Move collision detection out of the concurrent function; compute after Promise.all.

Minimal change: stop touching collisions in compileOpFile, then validate once.

- async function compileOpFile(fs, path, collisions, logger) {
+ async function compileOpFile(fs, path, logger) {
@@
- const seen = collisions.get(baseName) || [];
- seen.push(path);
- collisions.set(baseName, seen);
- if (seen.length > 1) {
-   const err = new OpsError(
-     'OPS_COLLISION',
-     `Identifier collision detected: "${baseName}" used in ${seen.join(', ')}`,
-     { identifier: baseName, paths: [...seen] }
-   );
-   logger.error(err.meta, err.message);
-   throw err;
- }
   const paramCount = (collectParams(plan)?.ordered?.length) || 0;
   return { baseName, plan, isParamless: paramCount === 0, path };
 }

And after the workers complete (just before emitOpArtifacts):

- const compiledOps = [];
- const collisions = new Map();
+ const compiledOps = [];
@@
- const workers = Array.from({ length: workerCount }, async () => {
+ const workers = Array.from({ length: workerCount }, async () => {
@@
-   const path = files[idx];
+   const path = files[idx];
   try {
-     const compiled = await compileOpFile(fs, path, collisions, logger);
+     const compiled = await compileOpFile(fs, path, logger);
     compiledOps.push({ order: idx, ...compiled });
@@
- await Promise.all(workers);
+ await Promise.all(workers);
+ // Validate collisions deterministically
+ const byName = new Map();
+ for (const c of compiledOps) {
+   const list = byName.get(c.baseName) || [];
+   list.push(c.path);
+   byName.set(c.baseName, list);
+ }
+ for (const [identifier, paths] of byName.entries()) {
+   if (paths.length > 1) {
+     const err = new OpsError('OPS_COLLISION', `Identifier collision detected: "${identifier}" used in ${paths.join(', ')}`, { identifier, paths });
+     (allowErrors ? skippedErrors : compileErrors).push({ file: paths[0], message: err.message, code: err.code });
+     if (!allowErrors) logger.warn({ identifier, paths }, err.message);
+   }
+ }
+ if (!allowErrors && compileErrors.some(e => e.code === 'OPS_COLLISION')) {
+   // fall through to existing error path below
+ }

575-590: History dedupe may drop distinct points; include MRI in key.

Using only timestamp or day+scs+tci can collapse points with different mri.

- const key = point?.timestamp || `${point?.day ?? 'unknown'}-${point?.scs ?? '0'}-${point?.tci ?? '0'}`;
+ const key = point?.timestamp || `${point?.day ?? 'unknown'}-${point?.scs ?? '0'}-${point?.tci ?? '0'}-${point?.mri ?? '0'}`;
packages/wesley-holmes/src/report-schemas.mjs (1)

261-328: Validator doesn’t honor anyOf, null type, or additionalProperties:false — fix it.

Without these, scores can be arbitrary types and extra keys won’t be caught. Add minimal support without pulling JSON Schema.

 function validateNode(schema, value, path, errors) {
-  if (!schema) return;
-  const location = path || 'root';
+  if (!schema) return;
+  const location = path || 'root';
+
+  // Minimal union support
+  if (schema.anyOf && Array.isArray(schema.anyOf)) {
+    let matched = false;
+    for (const sub of schema.anyOf) {
+      const subErrors = [];
+      validateNode(sub, value, path, subErrors);
+      if (subErrors.length === 0) { matched = true; break; }
+    }
+    if (!matched) {
+      errors.push(`${location} did not match anyOf`);
+    }
+    return;
+  }

   switch (schema.type) {
     case 'object': {
       if (!isObject(value)) {
         errors.push(`${location} expected object`);
         return;
       }
       const required = schema.required || [];
       for (const key of required) {
         if (!(key in value)) {
           errors.push(`${location}.${key} missing`);
         }
       }
       const props = schema.properties || {};
       for (const [key, childSchema] of Object.entries(props)) {
         if (key in value) {
           validateNode(childSchema, value[key], `${location}.${key}`, errors);
         }
       }
+      // Enforce additionalProperties:false
+      if (schema.additionalProperties === false) {
+        for (const key of Object.keys(value)) {
+          if (!props[key]) {
+            errors.push(`${location}.${key} unexpected`);
+          }
+        }
+      }
       return;
     }
     case 'array': {
       if (!Array.isArray(value)) {
         errors.push(`${location} expected array`);
         return;
       }
       if (schema.items) {
         value.forEach((item, index) => {
           validateNode(schema.items, item, `${location}[${index}]`, errors);
         });
       }
       return;
     }
     case 'string': {
       if (typeof value !== 'string') {
         errors.push(`${location} expected string`);
       }
       return;
     }
     case 'number': {
       if (typeof value !== 'number' || Number.isNaN(value)) {
         errors.push(`${location} expected number`);
       }
       return;
     }
     case 'boolean': {
       if (typeof value !== 'boolean') {
         errors.push(`${location} expected boolean`);
       }
       return;
     }
+    case 'null': {
+      if (value !== null) {
+        errors.push(`${location} expected null`);
+      }
+      return;
+    }
     default:
       return;
   }
 }
packages/wesley-holmes/src/Holmes.mjs (2)

65-66: Threshold mismatch: gates disagree with readiness.

You use tci > 0.7 here, but readiness uses >= 0.7. Users get a green verdict with a yellow gate. Pick one.

-    gates.push({ gate: 'Test Coverage', status: tci > 0.7 ? '✅' : '⚠️', evidence: `TCI: ${(tci * 100).toFixed(1)}%`, ruling: this.assessTests(tci) });
+    gates.push({ gate: 'Test Coverage', status: tci >= 0.7 ? '✅' : '⚠️', evidence: `TCI: ${(tci * 100).toFixed(1)}%`, ruling: this.assessTests(tci) });

291-300: Brittle citation: assume less, break less.

Not all evidence entries guarantee {file, lines}. Your current code explodes into "undefined:undefined".

-    for (const [kind, locations] of Object.entries(evidence)) {
-      if (locations?.[0]) {
-        citations.push(`${locations[0].file}:${locations[0].lines}@${this.sha.substring(0, 7)}`);
+    for (const [, locations] of Object.entries(evidence)) {
+      const loc = locations?.[0];
+      if (loc) {
+        const file = loc.file || loc.path || 'unknown';
+        const lines = loc.lines || loc.line || '?';
+        citations.push(`${file}:${lines}@${this.sha.substring(0, 7)}`);
         break;
       }
     }
♻️ Duplicate comments (3)
packages/wesley-generator-js/src/typescript.mjs (1)

33-33: Same UID duplication as ZodGenerator.

See my comment on packages/wesley-core/src/domain/generators/ZodGenerator.mjs. Extract this logic to a shared helper. Don't make me say it again.

BunBun.

packages/wesley-generator-js/src/zod.mjs (1)

54-54: Third time's a charm—or a code smell.

See the duplication issue raised in ZodGenerator.mjs. This is getting ridiculous.

BunBun.

packages/wesley-core/src/application/Scoring.mjs (1)

45-48: UID fallback finally uses col:Table.field — good.

This fixes the producer/consumer key mismatch noted earlier. Keep it consistent across helpers.

Run to confirm no stray fallback to ${table.name}.${field.name} remains:

#!/bin/bash
rg -nP -C2 "(?<!\.)\b${table}\.${field}\b" -g '!**/test/**' || true
rg -nP -C2 "col:\s*\$\{?table\.name\}?\.\$\{?field\.name\}?" --type=js --type=ts --type=mjs
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 512c724 and 5a34be0.

⛔ Files ignored due to path filters (1)
  • CONTRIBUTING.md is excluded by !*.md
📒 Files selected for processing (42)
  • .githooks/pre-commit (1 hunks)
  • .github/workflows/cli-quick.yml (4 hunks)
  • .github/workflows/wesley-holmes.yml (6 hunks)
  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl (1 hunks)
  • docs/architecture/holmes-architecture.md (1 hunks)
  • docs/architecture/holmes-integration.md (1 hunks)
  • docs/holmes-merge-projection-plan.md (1 hunks)
  • docs/holmes-policy-spec.md (1 hunks)
  • docs/holmes-policy/README.md (1 hunks)
  • docs/templates/holmes-policy/README.md (1 hunks)
  • docs/templates/holmes-policy/azure-devops-pr-default.json (1 hunks)
  • docs/templates/holmes-policy/bitbucket-pr-default.json (1 hunks)
  • docs/templates/holmes-policy/gitea-pr-default.json (1 hunks)
  • docs/templates/holmes-policy/github-pr-default.json (1 hunks)
  • docs/templates/holmes-policy/gitlab-pr-default.json (1 hunks)
  • docs/templates/holmes-policy/strict-trunk-pseudo.json (1 hunks)
  • docs/templates/holmes-policy/trunk-main-default.json (1 hunks)
  • packages/wesley-cli/src/commands/generate.mjs (10 hunks)
  • packages/wesley-core/src/application/EvidenceMap.mjs (2 hunks)
  • packages/wesley-core/src/application/Scoring.mjs (7 hunks)
  • packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (2 hunks)
  • packages/wesley-core/src/domain/WesleyOrchestrator.mjs (3 hunks)
  • packages/wesley-core/src/domain/generators/ZodGenerator.mjs (1 hunks)
  • packages/wesley-core/test/unit/scoring-engine.test.mjs (4 hunks)
  • packages/wesley-generator-js/src/typescript.mjs (2 hunks)
  • packages/wesley-generator-js/src/zod.mjs (2 hunks)
  • packages/wesley-holmes/docs/moriarty-scenarios.md (1 hunks)
  • packages/wesley-holmes/src/Holmes.mjs (9 hunks)
  • packages/wesley-holmes/src/Moriarty.mjs (7 hunks)
  • packages/wesley-holmes/src/cli.mjs (3 hunks)
  • packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (1 hunks)
  • packages/wesley-holmes/src/merge/Planner.mjs (1 hunks)
  • packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (1 hunks)
  • packages/wesley-holmes/src/report-schemas.mjs (2 hunks)
  • packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs (1 hunks)
  • packages/wesley-holmes/test/moriarty-scenarios.test.mjs (1 hunks)
  • packages/wesley-holmes/test/report-validation.test.mjs (1 hunks)
  • packages/wesley-host-node/src/adapters/createNodeRuntime.mjs (2 hunks)
  • schemas/scores.schema.json (2 hunks)
  • scripts/gh-issues/create_issues.mjs (1 hunks)
  • scripts/gh-issues/issue_plan.json (1 hunks)
  • test/holmes-e2e.bats (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl

📄 CodeRabbit inference engine (AGENTS.md)

CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl: Append to the Chronicles, never alter prior entries (append-only, immutable history)
Chronicles must be JSONL: one valid JSON object per line
Each Chronicle entry must include required fields: timestamp (ISO 8601), agent, action, result, notes, observations_on_humanity
Allowed result values: success, failure, partial, blocked, info (for corrections/metadata)
Write logs with intent: entries should be specific, contextual, and reflective; avoid vague actions or empty notes/observations
Use correction entries instead of edits; include action:"correction" and corrects_timestamp referencing the original entry
Rotate Chronicles when file size > 100MB, entries > 100,000, or an epochal shift occurs; start a new, correctly named volume
Chronicle filenames must follow naming convention: CHRONICLES_OF_THE_MACHINE-KIND_VOL_<8 binary digits>.jsonl (ALL CAPS, underscores only, zero-padded)
Include optional enrichments when helpful (context, duration_ms, files_touched, checksum, related_issue, error_details)

Files:

  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
🧠 Learnings (5)
📚 Learning: 2025-10-22T16:23:06.202Z
Learnt from: CR
PR: flyingrobots/wesley#0
File: AGENTS.md:0-0
Timestamp: 2025-10-22T16:23:06.202Z
Learning: Applies to CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl : Use correction entries instead of edits; include action:"correction" and corrects_timestamp referencing the original entry

Applied to files:

  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
📚 Learning: 2025-10-22T16:23:06.202Z
Learnt from: CR
PR: flyingrobots/wesley#0
File: AGENTS.md:0-0
Timestamp: 2025-10-22T16:23:06.202Z
Learning: Applies to CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl : Append to the Chronicles, never alter prior entries (append-only, immutable history)

Applied to files:

  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
📚 Learning: 2025-10-22T16:23:06.202Z
Learnt from: CR
PR: flyingrobots/wesley#0
File: AGENTS.md:0-0
Timestamp: 2025-10-22T16:23:06.202Z
Learning: Applies to CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl : Write logs with intent: entries should be specific, contextual, and reflective; avoid vague actions or empty notes/observations

Applied to files:

  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
📚 Learning: 2025-10-22T16:23:06.202Z
Learnt from: CR
PR: flyingrobots/wesley#0
File: AGENTS.md:0-0
Timestamp: 2025-10-22T16:23:06.202Z
Learning: Applies to CHRONICLES_OF_THE_MACHINE-KIND_VOL_[01][01][01][01][01][01][01][01].jsonl : Chronicles must be JSONL: one valid JSON object per line

Applied to files:

  • CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl
📚 Learning: 2025-10-06T09:11:26.403Z
Learnt from: CR
PR: flyingrobots/wesley#0
File: AGENTS.md:0-0
Timestamp: 2025-10-06T09:11:26.403Z
Learning: Applies to .github/workflows/cli-*.{yml,yaml} : Dedicated CLI workflows (e.g., cli-tests.yml, cli-quick.yml) own CLI end-to-end/bats checks; avoid duplicating them in main CI without approval

Applied to files:

  • .github/workflows/cli-quick.yml
🧬 Code graph analysis (18)
packages/wesley-core/src/domain/generators/ZodGenerator.mjs (5)
packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (1)
  • tableUid (68-68)
packages/wesley-generator-js/src/typescript.mjs (1)
  • tableUid (33-33)
packages/wesley-generator-js/src/zod.mjs (1)
  • tableUid (54-54)
packages/wesley-core/src/domain/generators/PgTAPTestGenerator.mjs (2)
  • tableUid (51-51)
  • tableUid (249-249)
packages/wesley-core/src/domain/generators/PostgreSQLGenerator.mjs (1)
  • tableUid (44-44)
packages/wesley-host-node/src/adapters/createNodeRuntime.mjs (5)
packages/wesley-cli/src/commands/generate.mjs (3)
  • artifacts (81-81)
  • artifacts (276-280)
  • path (320-320)
packages/wesley-cli/test/helpers/assert-output-paths.mjs (1)
  • artifacts (55-62)
packages/wesley-host-node/src/adapters/inprocess-compiler.mjs (1)
  • artifacts (69-69)
packages/wesley-cli/src/utils/output-paths.mjs (1)
  • baseDir (21-21)
packages/wesley-host-node/src/adapters/NodeFileSystem.mjs (1)
  • dir (18-18)
packages/wesley-core/src/application/EvidenceMap.mjs (5)
packages/wesley-core/src/application/Scoring.mjs (1)
  • uid (47-47)
packages/wesley-holmes/src/Holmes.mjs (2)
  • uid (93-93)
  • evidence (339-339)
packages/wesley-core/src/domain/generators/PostgreSQLGenerator.mjs (1)
  • uid (205-205)
packages/wesley-cli/src/commands/generate.mjs (1)
  • evidence (186-194)
packages/wesley-core/src/domain/generators/PgTAPTestGenerator.mjs (1)
  • evidence (441-441)
packages/wesley-holmes/test/moriarty-scenarios.test.mjs (1)
packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs (10)
  • makeTempDir (6-8)
  • wes (11-11)
  • wes (17-17)
  • writeHistory (10-14)
  • writeContext (16-20)
  • nowSecs (106-106)
  • buildLog (57-70)
  • withFakeGit (22-55)
  • runPredict (88-100)
  • buildLogSeries (74-86)
packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (6)
packages/wesley-core/src/domain/generators/ZodGenerator.mjs (1)
  • tableUid (46-46)
packages/wesley-generator-js/src/typescript.mjs (1)
  • tableUid (33-33)
packages/wesley-generator-js/src/zod.mjs (1)
  • tableUid (54-54)
packages/wesley-core/src/domain/generators/PgTAPTestGenerator.mjs (8)
  • tableUid (51-51)
  • tableUid (249-249)
  • fieldUid (69-69)
  • fieldUid (143-143)
  • fieldUid (193-193)
  • fieldUid (223-223)
  • fieldUid (298-298)
  • fieldUid (314-314)
packages/wesley-core/src/domain/generators/PostgreSQLGenerator.mjs (5)
  • tableUid (44-44)
  • tableName (203-203)
  • tableName (267-267)
  • fieldUid (53-53)
  • fieldUid (113-113)
packages/wesley-host-node/src/adapters/GraphQLAdapter.mjs (1)
  • tableName (126-126)
packages/wesley-generator-js/src/typescript.mjs (5)
packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (1)
  • tableUid (68-68)
packages/wesley-core/src/domain/generators/ZodGenerator.mjs (1)
  • tableUid (46-46)
packages/wesley-generator-js/src/zod.mjs (1)
  • tableUid (54-54)
packages/wesley-generator-supabase/src/ddl.mjs (1)
  • tableUid (64-64)
packages/wesley-generator-supabase/src/test.mjs (1)
  • tableUid (67-67)
packages/wesley-holmes/src/merge/Planner.mjs (4)
packages/wesley-holmes/src/cli.mjs (2)
  • baseRef (129-131)
  • baseRef (190-192)
packages/wesley-holmes/src/Moriarty.mjs (1)
  • baseRef (543-543)
packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (1)
  • baseRef (19-19)
packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (1)
  • baseRef (22-22)
packages/wesley-generator-js/src/zod.mjs (5)
packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (1)
  • tableUid (68-68)
packages/wesley-core/src/domain/generators/ZodGenerator.mjs (1)
  • tableUid (46-46)
packages/wesley-generator-js/src/typescript.mjs (1)
  • tableUid (33-33)
packages/wesley-generator-supabase/src/ddl.mjs (1)
  • tableUid (64-64)
packages/wesley-generator-supabase/src/test.mjs (1)
  • tableUid (67-67)
packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (4)
packages/wesley-holmes/src/cli.mjs (4)
  • plan (137-137)
  • plan (198-198)
  • baseRef (129-131)
  • baseRef (190-192)
packages/wesley-holmes/src/Moriarty.mjs (3)
  • baseRef (543-543)
  • remoteBase (552-552)
  • mergeBase (553-553)
packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (4)
  • baseRef (19-19)
  • remoteBase (20-20)
  • mergeBase (34-34)
  • out (59-59)
packages/wesley-holmes/src/merge/Planner.mjs (1)
  • baseRef (21-21)
packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs (1)
packages/wesley-holmes/test/moriarty-scenarios.test.mjs (2)
  • wes (23-23)
  • repoRoot (19-19)
packages/wesley-holmes/src/cli.mjs (4)
packages/wesley-holmes/src/Moriarty.mjs (2)
  • Moriarty (8-644)
  • baseRef (543-543)
packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (2)
  • baseRef (19-19)
  • WorktreeStrategy (13-86)
packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (2)
  • baseRef (22-22)
  • MergeTreeStrategy (9-63)
packages/wesley-holmes/src/merge/Planner.mjs (2)
  • baseRef (21-21)
  • MergePlanner (9-29)
packages/wesley-holmes/src/Moriarty.mjs (6)
packages/wesley-cli/src/commands/generate.mjs (6)
  • history (202-211)
  • context (38-38)
  • context (240-240)
  • context (298-298)
  • result (286-286)
  • mergeBase (542-542)
packages/wesley-holmes/src/cli.mjs (9)
  • history (124-124)
  • history (181-181)
  • result (138-138)
  • result (199-199)
  • data (91-91)
  • data (108-108)
  • data (127-127)
  • baseRef (129-131)
  • baseRef (190-192)
packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs (3)
  • result (94-94)
  • commits (81-81)
  • a (64-64)
packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (3)
  • baseRef (19-19)
  • remoteBase (20-20)
  • mergeBase (34-34)
packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (3)
  • baseRef (22-22)
  • remoteBase (23-23)
  • mergeBase (28-28)
packages/wesley-holmes/src/merge/Planner.mjs (1)
  • baseRef (21-21)
packages/wesley-core/src/domain/WesleyOrchestrator.mjs (2)
packages/wesley-core/src/application/Scoring.mjs (3)
  • BUNDLE_VERSION (11-11)
  • BUNDLE_VERSION (11-11)
  • readiness (403-407)
packages/wesley-core/src/application/GenerationPipeline.mjs (4)
  • evidenceMap (25-25)
  • artifacts (39-43)
  • artifacts (70-70)
  • schema (30-30)
packages/wesley-holmes/src/merge/WorktreeStrategy.mjs (3)
packages/wesley-holmes/src/cli.mjs (8)
  • plan (137-137)
  • plan (198-198)
  • baseRef (129-131)
  • baseRef (190-192)
  • opts (87-87)
  • opts (104-104)
  • opts (122-122)
  • opts (177-177)
packages/wesley-holmes/src/Moriarty.mjs (3)
  • baseRef (543-543)
  • remoteBase (552-552)
  • mergeBase (553-553)
packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (4)
  • baseRef (22-22)
  • remoteBase (23-23)
  • mergeBase (28-28)
  • out (39-39)
packages/wesley-holmes/src/Holmes.mjs (3)
packages/wesley-cli/src/commands/generate.mjs (4)
  • bundle (196-196)
  • scores (169-181)
  • evidence (186-194)
  • s (129-129)
packages/wesley-holmes/test/holmes-weighting.test.mjs (1)
  • bundle (8-24)
packages/wesley-core/src/application/Scoring.mjs (10)
  • uid (47-47)
  • weight (48-48)
  • weight (338-338)
  • breakdown (69-69)
  • breakdown (151-151)
  • breakdown (209-237)
  • score (79-79)
  • score (202-207)
  • score (319-319)
  • score (364-364)
packages/wesley-core/test/unit/scoring-engine.test.mjs (1)
packages/wesley-core/src/application/Scoring.mjs (4)
  • uid (47-47)
  • fieldUid (443-443)
  • fieldUid (458-458)
  • fieldUid (485-485)
packages/wesley-cli/src/commands/generate.mjs (5)
packages/wesley-cli/src/commands/up.mjs (2)
  • outDir (27-27)
  • fs (144-144)
packages/wesley-cli/src/commands/blade.mjs (1)
  • outDir (41-41)
packages/wesley-core/src/application/GenerationPipeline.mjs (4)
  • artifacts (39-43)
  • artifacts (70-70)
  • scores (47-51)
  • bundle (54-61)
packages/wesley-cli/src/commands/cert-create.mjs (4)
  • artifacts (26-26)
  • scores (23-23)
  • sha (21-21)
  • fs (88-88)
packages/wesley-cli/src/commands/validate-bundle.mjs (2)
  • scores (43-45)
  • fs (18-18)
packages/wesley-core/src/application/Scoring.mjs (2)
packages/wesley-core/test/unit/scoring-engine.test.mjs (12)
  • schema (42-44)
  • schema (122-122)
  • table (36-40)
  • table (116-120)
  • uid (92-92)
  • migrationSteps (83-88)
  • step (123-123)
  • scsDetails (77-77)
  • mriDetails (101-101)
  • tciDetails (96-96)
  • fieldUid (47-47)
  • fieldUid (126-126)
packages/wesley-core/src/domain/Directives.mjs (1)
  • DirectiveProcessor (22-63)
🪛 LanguageTool
docs/architecture/holmes-integration.md

[uncategorized] ~237-~237: The official name of this software platform is spelled with a capital “H”.
Context: .... - Provide the base branch (typically github.base_ref) as MORIARTY_BASE_REF`. - Op...

(GITHUB)

docs/holmes-policy-spec.md

[uncategorized] ~42-~42: The official name of this software platform is spelled with a capital “H”.
Context: ...al" | "ci" | "local" - provider: "github" | "gitlab" | "bitbucket" | "azure" | "...

(GITHUB)


[uncategorized] ~59-~59: The official name of this software platform is spelled with a capital “H”.
Context: ...rovider: inferred from CI env presence (github, gitlab, bitbucket, azure, gitea) else ...

(GITHUB)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: 📝 Post Investigation Report
  • GitHub Check: 🧪 pgTAP Tests
🔇 Additional comments (46)
packages/wesley-host-node/src/adapters/createNodeRuntime.mjs (1)

8-8: Import looks fine.

Using the node: protocol for built-in modules is the right approach.

CHRONICLES_OF_THE_MACHINE-KIND_VOL_00000001.jsonl (1)

51-51: Push entry looks correct.

All required fields are present, properly formatted JSONL, and the content accurately documents the changes to createNodeRuntime.mjs. The files_touched array correctly references the modified file.

Based on learnings
BunBun

packages/wesley-core/src/domain/GraphQLSchemaBuilder.mjs (1)

91-91: Field UID format is now PascalCase per documented standard—verify migration is complete

The field UID format has definitively changed from lowercase (e.g., col:user.id) to PascalCase (e.g., col:User.id), as confirmed by CONTRIBUTING.md guidance and test fixtures. This is intentional standardization, not a bug—but it's still a breaking change for any external systems depending on these identifiers as stable keys.

Key concerns:

  • Test fixtures (rls-schema.graphql) have @uid(value: "col:user.id") but expected output shows col:User.id, indicating fixtures need updating
  • Any service consuming fieldUid values from evidenceMap will get new format
  • Combined with line 68's tableUid change, every UID in the system is now affected

Verify:

  • All @uid directive values in test schemas are updated to match new format
  • Fixture comparisons pass with new UID format
  • No external API consumers rely on old lowercase format (or migration path exists)
docs/holmes-merge-projection-plan.md (1)

1-55: Documentation looks solid.

Clear structure, references implementation files that presumably exist, and provides actionable setup instructions. No issues here.

BunBun.

docs/templates/holmes-policy/gitea-pr-default.json (1)

1-18: Template structure is consistent with other providers.

The JSON is valid, defaults are reasonable, and the rules follow the same pattern as GitHub/GitLab/etc. templates. Fine.

BunBun.

docs/holmes-policy/README.md (1)

1-17: Clean, concise, and useful.

The README provides exactly what's needed: spec reference, template locations, quick start, and validation behavior. No fluff. Well done.

BunBun.

docs/templates/holmes-policy/gitlab-pr-default.json (1)

1-18: GitLab template mirrors the others.

Same as Gitea template, just s/gitea/gitlab/. Consistent, valid JSON, reasonable defaults. Ship it.

BunBun.

docs/templates/holmes-policy/bitbucket-pr-default.json (1)

1-19: This template has better rule ordering than trunk-main-default.json.

At least here the specific branchEquals: "main" comes AFTER the branchMatches patterns, so if rules are evaluated first-match, the feature branches get caught before falling through to main's OFF rule.

But you STILL need to document the evaluation semantics. What happens when multiple rules match? First wins? Last wins? All apply? This is not self-documenting JSON.

Add a schema comment or README explaining rule evaluation order for all policy templates.

packages/wesley-holmes/docs/moriarty-scenarios.md (1)

1-230: ---

From: Linus Torvalds torvalds@linux-foundation.org
To: PR Author author@example.com
Cc: code-rabbit@vger.kernel.org


Your tests ARE there. Read your own PR.

The review comment claimed moriarty-scenarios.test.mjs doesn't exist. It's sitting right there at 177 lines with 6 fully functional test scenarios using the exact fixtures documented: writeHistory, writeContext, withFakeGit. The helpers module (moriarty-test-helpers.mjs, 4.3K) is implemented and imported. All assertions pass the documented patterns.

Yeah, you've only covered 6 of the 27 scenarios (1, 2, 4, 8, 13, 25). That's a fair critique—incomplete coverage is a real thing. File an issue to track the remaining 21 scenarios if you want rigor, and get people to implement them incrementally. That's actually how documentation + test coverage SHOULD evolve.

But don't let anyone tell you the tests aren't there. They are. The reviewer either didn't look carefully at the diff, or the diff display failed. Either way, the core complaint is baseless.

BunBun.


Likely an incorrect or invalid review comment.

scripts/gh-issues/issue_plan.json (1)

1-14: Plan looks coherent; no blockers.

Schema reads fine, keys consistent, no trailing commas. Ship it.

docs/templates/holmes-policy/github-pr-default.json (1)

1-19: LGTM - Solid template for GitHub PR workflow.

The policy template is well-structured and provides sensible defaults for a GitHub PR-based workflow. The three rules cover the main scenarios: PR events, feature branches, and trunk. The penalties and boolean flags are reasonable.

BunBun

packages/wesley-core/src/application/EvidenceMap.mjs (2)

71-75: Clean implementation of hasArtifact.

The method correctly normalizes the input to an array and uses some() to check for evidence. Optional chaining prevents errors when a kind doesn't exist. Good defensive programming.

BunBun


77-81: Simple and effective iteration helper.

The forEachEvidence method provides a clean API for iterating over the evidence map. No issues with the implementation.

BunBun

packages/wesley-core/src/domain/WesleyOrchestrator.mjs (3)

103-110: Score calculation looks solid.

The migrationSteps extraction correctly handles the fallback chain (artifacts.migration?.steps || diffForTests.steps || []). The exportScores call matches the expected signature and provides sensible defaults for testResults.

BunBun


118-118: Good addition of bundleVersion metadata.

Including the bundle version in the return object is helpful for consumers to validate compatibility. Just fix that duplicate constant first.

BunBun


127-130: Spreading scoreDetails is clean.

The approach of spreading scoreDetails and adding a placeholder history array is fine. Good comment explaining the future intent.

BunBun

packages/wesley-holmes/src/merge/MergeTreeStrategy.mjs (3)

14-20: Good defensive git check.

Validating the work tree upfront prevents cryptic errors later. The early return with a clear error message is the right approach.

BunBun


28-43: Merge-tree logic is sound.

The command sequence is correct: compute merge-base, then run merge-tree with the three commits. Splitting the output and taking the first token is the right way to extract the tree id. Error handling includes useful context.

BunBun


45-62: Comparison logic and return structure are solid.

The tree comparison flags (equalToHead, equalToBase) provide useful context for downstream consumers. The return object is well-structured with clear status and deferred materialization.

BunBun

packages/wesley-holmes/test/moriarty-scenarios.test.mjs (7)

21-39: Test helpers are solid.

The mkBundleDir and expectWithDump helpers are straightforward and useful. The debug dump on failure will save time debugging test failures.

BunBun


41-68: Scenario 1 test is well-constructed.

The test correctly sets up a plateau scenario (flat SCS over time, minimal git activity) and validates the expected behavior. Assertions cover the key outputs.

BunBun


70-97: Scenario 2 correctly tests massive commit impact.

The test validates that a large commit triggers high activity and burstiness indices. Assertions correctly expect no ETA when SCS is unchanged.

BunBun


99-119: Scenario 4 validates regular activity behavior.

The test correctly verifies that regular commit activity prevents plateau detection even when SCS is flat. The no-ETA assertion is correct for unchanged SCS.

BunBun


121-141: Scenario 13 correctly tests TEST_LAG detection.

The history shows increasing SCS with decreasing TCI - a clear test lag pattern. The assertions validate both pattern detection and the readiness explanation structure.

BunBun


143-158: Scenario 25 validates MRI failure detection.

The test correctly simulates a risky migration (high MRI) despite good SCS/TCI scores. The readiness assertions validate that MRI failure is properly detected.

BunBun


160-177: Scenario 8 validates velocity cliff detection.

The test correctly sets up a velocity cliff scenario (fast initial progress, then flattening) and validates pattern detection. Good coverage of this important pattern.

BunBun

docs/holmes-policy-spec.md (3)

42-42: Lowercase "github" in enums is fine - ignore static analysis.

The static analysis tool flagged lowercase "github" as a style issue. However, in the context of enum values and provider identifiers, lowercase is standard practice. The implementation likely uses provider: "github" as a string literal, not the capitalized brand name.

Keep it as-is. Static analysis tools don't understand domain context.

BunBun

Also applies to: 59-59


57-87: Auto mode and execution semantics are well-documented.

The auto mode logic is clearly explained with comprehensive coverage of different scenarios. The CI env mapping and execution semantics provide the necessary details for implementation and troubleshooting.

BunBun


88-105: Solid documentation on readiness, UX, and implementation.

The sections on readiness impact, validation, and templates are clear and comprehensive. The implementation phases provide a useful roadmap. Good forward-looking documentation.

BunBun

.github/workflows/wesley-holmes.yml (5)

27-50: Schema detection logic is sound.

The detection priority (env var → search → fallback) makes sense. The array bounds are checked before access. Bundle dir derivation is straightforward.

BunBun


182-189: MORIARTY history step is necessary and correct.

Unshallowing the repo and fetching remote refs is required for git activity analysis. The || true fallbacks are acceptable for best-effort execution in CI.

BunBun


191-291: MORIARTY context computation is solid, but watch the rate limits.

The GitHub API calls are correctly structured with pagination and filtering. The 3-page limit is a reasonable trade-off between completeness and performance. continue-on-error: true prevents CI failures from API rate limiting, which is smart.

One gotcha: If your repo has VERY high activity (>300 issues closed or PRs merged in the time window), you'll only see the most recent 300. For most repos this is fine, but be aware of the limitation.

BunBun


301-307: MORIARTY env vars provide sensible defaults.

The hardcoded values (24-hour window, activity thresholds) are reasonable starting points. They can be overridden if needed. Good configuration surface.

BunBun


608-629: Collapsible sections improve PR comment UX.

Wrapping the reports in <details> tags is a smart UX improvement. It keeps PR comments concise while preserving full report access. Good change.

BunBun

packages/wesley-holmes/src/cli.mjs (3)

47-54: loadMoriartyContext is clean and safe.

The implementation mirrors loadHistory with a sensible fallback to an empty object. No issues here.

BunBun


128-163: Project merge logic is mostly solid, but you're being paranoid.

The implementation correctly handles merge simulation with fallback strategies and applies penalties for non-clean projections. The confidence adjustment and pattern addition are appropriate.

Minor nitpick: Line 139 checks !result, but MergeTreeStrategy.execute() always returns an object. This check is defensive programming, which is fine, but unnecessary given your implementation. Not worth changing.

BunBun


125-127: Moriarty constructor usage is correct.

The context loading and passing to the Moriarty constructor follows the updated signature correctly. No issues here.

BunBun

Also applies to: 182-185

packages/wesley-holmes/test/report-validation.test.mjs (3)

27-53: Breakdown sample matches the new schema — good coverage.

The SCS/TCI/MRI sections line up with the new component shapes. Keep this as the canonical “golden” fixture for downstream generators.


133-144: Add a negative for missing breakdown.

You test absence of scores, but not breakdown (now required). Add a mirror test to prevent regressions when producers forget to emit it.

Apply:

+test('holmes report schema rejects missing breakdown', () => {
+  const broken = structuredClone(holmesSample);
+  delete broken.breakdown;
+  const result = validateReport(holmesReportSchema, broken);
+  assert.equal(result.valid, false);
+  assert.ok(result.errors.some(err => err.includes('breakdown')));
+});

138-157: This review comment is incorrect—the code is fine as-is.

structuredClone is a global in Node 17+, so it's available in all supported Node versions (>=18.17). Importing from node:util won't work until Node 22.0.0, which contradicts the project's minimum requirement and would break CI testing on Node 18.x and 20.x. The current code requires no changes.


From: Linus Torvalds torvalds@linux-foundation.org
To: {PR_AUTHOR_NAME} <{PR_AUTHOR_EMAIL}>
Cc: code-rabbit@vger.kernel.org


Look, I went digging through your Node version requirements and CI configuration. The project mandates >=18.17, and structuredClone has been a global since Node 17.0.0. That's not a portability problem—that's just how Node works.

The review comment suggesting import { structuredClone } from 'node:util' is dead wrong. That export doesn't show up until Node 22.0.0. Your CI is running 18.x, 20.x, and 22.x, so pushing that import would instantly break the 18.x and 20.x test runs. Smart thinking on the original author's part to just use the global.

Don't overthink this. The code works. Leave it alone.

BunBun

Likely an incorrect or invalid review comment.

packages/wesley-holmes/test/helpers/moriarty-test-helpers.mjs (1)

22-55: Git stub harness is clean and self‑contained.

PATH isolation, config update, and cleanup are correct. Good test ergonomics.

packages/wesley-core/test/unit/scoring-engine.test.mjs (2)

77-105: Migration to calculate*Details API looks correct.

Breakdown keys (unit_constraints, unit_rls, etc.) and migration UID construction align with ScoringEngine.


135-146: Health factor test reads clearly and exercises degraded path.

Good signal: both lower TCI and an upper bound assertion.

packages/wesley-core/src/application/Scoring.mjs (2)

398-429: exportScores aligns with schema v2 — good.

version, nested scores, and breakdown blocks match schemas/scores.schema.json expectations.


22-29: No changes required—hasArtifact already handles both arrays and strings.

The implementation in EvidenceMap.mjs line 73 normalizes both input types: const list = Array.isArray(kinds) ? kinds : [kinds];. All callsites in Scoring.mjs—whether passing arrays from artifactGroups (line 54) or string literals like 'test' (lines 437, 444, 460, 464, 468, 487, 508)—work correctly. The method is already polymorphic; no undercounting risk exists.


BunBun

Likely an incorrect or invalid review comment.

schemas/scores.schema.json (1)

6-6: Schema v2 looks sane and consistent with emitters.

  • scores.scs/tci/mri as 0..1 — OK.
  • breakdown sections and keys match engine output names.
  • readiness enum and thresholds — OK.
  • additionalProperties hardened — good.

Also applies to: 20-29, 30-76, 77-91, 92-101, 104-146

Comment thread .githooks/pre-commit
Comment on lines +21 to +24
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
if ! echo "$STAGED_FILES" | grep -E -q '(^|/)package.json$|(^|/)pnpm-workspace.yaml$'; then
exit 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Filename robustness: prefer NUL-delimited plumbing.

Newlines in paths are rare but possible. Use -z and mapfile.

-STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR)
+mapfile -d '' -t _STAGED_FILES < <(git diff --cached --name-only -z --diff-filter=ACMR)
+STAGED_FILES=$(printf '%s\n' "${_STAGED_FILES[@]}")
 
-while IFS= read -r f; do
+while IFS= read -r f; do
   [[ -z "$f" ]] && continue
   if [[ "$f" =~ (^|/)package.json$ ]]; then
     if check_pkg_change "$f"; then
       NEEDS_UPDATE=1
       break
     fi
   fi
 done < <(echo "$STAGED_FILES")

Also applies to: 83-93

🤖 Prompt for AI Agents
.githooks/pre-commit around lines 21-24 (and similarly update lines 83-93): the
script reads staged filenames as newline-delimited which breaks on filenames
containing newlines; switch to NUL-delimited plumbing by calling git diff
--cached --name-only --diff-filter=ACMR -z, then consume the NUL-separated list
into a bash array using mapfile -d '' or a while read -r -d '' loop; replace the
grep check with a loop or an array check that inspects each filename (or use
printf '%s\0' "${STAGED_FILES[@]}" | grep -z -E
'(^|/)package.json$|(^|/)pnpm-workspace.yaml$') so all filename handling is
NUL-safe, and apply the same changes to the block at lines 83-93.

Comment thread .githooks/pre-commit
Comment on lines +26 to +34
echo "🔎 Checking if staged manifest changes affect the lockfile..."

NEEDS_UPDATE=0

# If pnpm-workspace.yaml changed, lockfile may need update (importers layout)
if echo "$STAGED_FILES" | grep -Eq '(^|/)pnpm-workspace.yaml$'; then
NEEDS_UPDATE=1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Node.js may be missing; you call it unguarded.

If only package.json is staged and Node isn’t installed, the hook dies mid-flight. Fail early with a clear message (but don’t require Node for pure workspace changes).

 echo "🔎 Checking if staged manifest changes affect the lockfile..."
 
 NEEDS_UPDATE=0
 
+# If any package.json is staged, ensure Node is available for JSON diffs
+if echo "$STAGED_FILES" | grep -Eq '(^|/)package.json$'; then
+  if ! command -v node >/dev/null 2>&1; then
+    echo "❌ Node.js not found; required to analyze staged package.json diffs." >&2
+    echo "   Install Node.js (or set WESLEY_SKIP_LOCKFILE_HOOK=1 to skip this guard)." >&2
+    exit 1
+  fi
+fi
+
 # If pnpm-workspace.yaml changed, lockfile may need update (importers layout)
 if echo "$STAGED_FILES" | grep -Eq '(^|/)pnpm-workspace.yaml$'; then
   NEEDS_UPDATE=1
 fi

Also applies to: 83-93

Comment thread .githooks/pre-commit
Comment on lines +47 to +75
# Use node to compare only resolution-impacting fields
node -e '
function pick(x){
if(!x||typeof x!="object") return {};
const out={
dependencies:x.dependencies||{},
devDependencies:x.devDependencies||{},
optionalDependencies:x.optionalDependencies||{},
peerDependencies:x.peerDependencies||{},
overrides:x.overrides||{},
pnpmOverrides:(x.pnpm&&x.pnpm.overrides)||{},
pnpmPeerRules:(x.pnpm&&x.pnpm.peerDependencyRules)||{},
pnpmPackageExtensions:(x.pnpm&&x.pnpm.packageExtensions)||{},
workspaces:x.workspaces||null
};
// stable stringify with sorted keys
function sort(obj){
if(Array.isArray(obj)) return obj.map(sort);
if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
return obj;
}
return JSON.stringify(sort(out));
}
const fs=require("node:fs");
const staged=JSON.parse(fs.readFileSync(0,"utf8"));
const head=JSON.parse(process.argv[1]||"{}");
const s=pick(staged), h=pick(head);
process.exit(s===h? 10: 0);
' "${head_json}" <<<"${staged_json}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Feeding full JSON via argv risks ARG_MAX limits; use file descriptors.

Large manifests can exceed shell arg limits. Pass HEAD via FD and staged via STDIN.

-  node -e '
+  node - <<'NODE'
     function pick(x){
       if(!x||typeof x!="object") return {};
       const out={
         dependencies:x.dependencies||{},
         devDependencies:x.devDependencies||{},
         optionalDependencies:x.optionalDependencies||{},
         peerDependencies:x.peerDependencies||{},
         overrides:x.overrides||{},
         pnpmOverrides:(x.pnpm&&x.pnpm.overrides)||{},
         pnpmPeerRules:(x.pnpm&&x.pnpm.peerDependencyRules)||{},
         pnpmPackageExtensions:(x.pnpm&&x.pnpm.packageExtensions)||{},
         workspaces:x.workspaces||null
       };
       // stable stringify with sorted keys
       function sort(obj){
-        if(Array.isArray(obj)) return obj.map(sort);
+        if(Array.isArray(obj)) return obj.map(sort);
         if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
         return obj;
       }
       return JSON.stringify(sort(out));
     }
-    const fs=require("node:fs");
-    const staged=JSON.parse(fs.readFileSync(0,"utf8"));
-    const head=JSON.parse(process.argv[1]||"{}");
+    const fs=require("node:fs");
+    const staged=JSON.parse(fs.readFileSync(0,"utf8"));
+    const head=JSON.parse(fs.readFileSync(3,"utf8"));
     const s=pick(staged), h=pick(head);
     process.exit(s===h? 10: 0);
-  ' "${head_json}" <<<"${staged_json}"
+NODE
+  3<<<"${head_json}" <<<"${staged_json}"
📝 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
# Use node to compare only resolution-impacting fields
node -e '
function pick(x){
if(!x||typeof x!="object") return {};
const out={
dependencies:x.dependencies||{},
devDependencies:x.devDependencies||{},
optionalDependencies:x.optionalDependencies||{},
peerDependencies:x.peerDependencies||{},
overrides:x.overrides||{},
pnpmOverrides:(x.pnpm&&x.pnpm.overrides)||{},
pnpmPeerRules:(x.pnpm&&x.pnpm.peerDependencyRules)||{},
pnpmPackageExtensions:(x.pnpm&&x.pnpm.packageExtensions)||{},
workspaces:x.workspaces||null
};
// stable stringify with sorted keys
function sort(obj){
if(Array.isArray(obj)) return obj.map(sort);
if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
return obj;
}
return JSON.stringify(sort(out));
}
const fs=require("node:fs");
const staged=JSON.parse(fs.readFileSync(0,"utf8"));
const head=JSON.parse(process.argv[1]||"{}");
const s=pick(staged), h=pick(head);
process.exit(s===h? 10: 0);
' "${head_json}" <<<"${staged_json}"
# Use node to compare only resolution-impacting fields
node - <<'NODE'
function pick(x){
if(!x||typeof x!="object") return {};
const out={
dependencies:x.dependencies||{},
devDependencies:x.devDependencies||{},
optionalDependencies:x.optionalDependencies||{},
peerDependencies:x.peerDependencies||{},
overrides:x.overrides||{},
pnpmOverrides:(x.pnpm&&x.pnpm.overrides)||{},
pnpmPeerRules:(x.pnpm&&x.pnpm.peerDependencyRules)||{},
pnpmPackageExtensions:(x.pnpm&&x.pnpm.packageExtensions)||{},
workspaces:x.workspaces||null
};
// stable stringify with sorted keys
function sort(obj){
if(Array.isArray(obj)) return obj.map(sort);
if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
return obj;
}
return JSON.stringify(sort(out));
}
const fs=require("node:fs");
const staged=JSON.parse(fs.readFileSync(0,"utf8"));
const head=JSON.parse(fs.readFileSync(3,"utf8"));
const s=pick(staged), h=pick(head);
process.exit(s===h? 10: 0);
NODE
3<<<"${head_json}" <<<"${staged_json}"
🤖 Prompt for AI Agents
.githooks/pre-commit around lines 47 to 75: the script currently reads the HEAD
JSON from process.argv which risks exceeding shell ARG_MAX for large manifests;
change it to read HEAD from a file descriptor instead of argv and continue
reading staged JSON from STDIN so both large payloads avoid arg limits — open
the extra FD (e.g. fd 3) in the hook invocation and replace process.argv[1]
usage with reading that FD via fs.readFileSync(3, "utf8") (falling back to "{}"
if empty), leaving staged read from STDIN unchanged, and ensure exit codes/logic
remain the same.

Comment thread .githooks/pre-commit
Comment on lines +61 to +69
};
// stable stringify with sorted keys
function sort(obj){
if(Array.isArray(obj)) return obj.map(sort);
if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
return obj;
}
return JSON.stringify(sort(out));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: treat workspaces array as an unordered set.

Re-ordering workspaces shouldn’t force a lockfile update. Normalize string arrays by sorting.

-      function sort(obj){
-        if(Array.isArray(obj)) return obj.map(sort);
+      function sort(obj){
+        if(Array.isArray(obj)) {
+          // Sort arrays of strings for stability; deep-sort objects within arrays otherwise.
+          if (obj.every(v => typeof v === "string")) return [...obj].sort();
+          return obj.map(sort);
+        }
         if(obj&&typeof obj==="object"){ return Object.keys(obj).sort().reduce((a,k)=>{a[k]=sort(obj[k]);return a;},{}); }
         return obj;
       }
🤖 Prompt for AI Agents
.githooks/pre-commit around lines 61-69: the current sort function preserves
array order which causes re-ordering of workspace entries to trigger unnecessary
lockfile changes; update the sorter so that when an array consists solely of
primitive strings (or when the key name is "workspaces") it returns a new array
sorted lexicographically before recursing, otherwise keep existing behavior for
arrays of objects. Ensure sorting is stable and does not mutate inputs (return
new arrays/objects) so JSON.stringify(sort(out)) produces a canonical
representation that treats workspaces as an unordered set.

Comment thread .githooks/pre-commit
Comment on lines +98 to +121
PM_CHANGE_OUTPUT=$(node -e '
try{
const fs=require("node:fs");
const staged=JSON.parse(fs.readFileSync(0,"utf8"));
const head=JSON.parse(process.argv[1]||"{}");
const s=staged.packageManager||""; const h=head.packageManager||"";
if(s!==h){ console.log(`${h}=>${s}`); process.exit(0);} else process.exit(10);
}catch{ process.exit(0) }
' "${ROOT_HEAD}" <<<"${ROOT_STAGED}" || true)
PM_CHANGED=$?
if [[ "$PM_CHANGED" -eq 0 ]]; then
PM_NEW="${PM_CHANGE_OUTPUT#*=>}"
PM_OLD="${PM_CHANGE_OUTPUT%=>*}"
# If switching away from pnpm, block with guidance (this repo standardizes on pnpm)
if [[ ! "$PM_NEW" =~ ^pnpm@ ]]; then
echo "❌ packageManager changed from '${PM_OLD:-<none>}' to '${PM_NEW:-<none>}', which is not pnpm." >&2
echo " This repository is configured for pnpm; update CI/workflows and lockfiles in a dedicated PR if switching managers." >&2
echo " To proceed with pnpm, set packageManager to 'pnpm@<version>' and re-run the commit." >&2
exit 1
fi
# Switching pnpm version: ensure lockfile is regenerated
NEEDS_UPDATE=1
fi
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

packageManager change detection is broken (always triggers).

You throw away Node’s exit code with command substitution and || true, then read $? (which is 0 from the assignment). Result: you always think packageManager changed and may block every commit that stages the root package.json.

Apply this minimal, correct logic keyed on output presence:

-  PM_CHANGE_OUTPUT=$(node -e '
+  PM_CHANGE_OUTPUT=$(node -e '
     try{
       const fs=require("node:fs");
       const staged=JSON.parse(fs.readFileSync(0,"utf8"));
       const head=JSON.parse(process.argv[1]||"{}");
       const s=staged.packageManager||""; const h=head.packageManager||"";
       if(s!==h){ console.log(`${h}=>${s}`); process.exit(0);} else process.exit(10);
     }catch{ process.exit(0) }
-  ' "${ROOT_HEAD}" <<<"${ROOT_STAGED}" || true)
-  PM_CHANGED=$?
-  if [[ "$PM_CHANGED" -eq 0 ]]; then
+  ' "${ROOT_HEAD}" <<<"${ROOT_STAGED}" 2>/dev/null || true)
+  if [[ -n "$PM_CHANGE_OUTPUT" ]]; then
     PM_NEW="${PM_CHANGE_OUTPUT#*=>}"
     PM_OLD="${PM_CHANGE_OUTPUT%=>*}"
     # If switching away from pnpm, block with guidance (this repo standardizes on pnpm)
     if [[ ! "$PM_NEW" =~ ^pnpm@ ]]; then
       echo "❌ packageManager changed from '${PM_OLD:-<none>}' to '${PM_NEW:-<none>}', which is not pnpm." >&2
       echo "   This repository is configured for pnpm; update CI/workflows and lockfiles in a dedicated PR if switching managers." >&2
       echo "   To proceed with pnpm, set packageManager to 'pnpm@<version>' and re-run the commit." >&2
       exit 1
     fi
     # Switching pnpm version: ensure lockfile is regenerated
     NEEDS_UPDATE=1
   fi
🤖 Prompt for AI Agents
In .githooks/pre-commit around lines 98 to 121, the current command substitution
swallows Node’s exit code with "|| true" and then reads $? (always 0), so the
script always thinks packageManager changed; fix by not discarding output/exit
and instead test the presence of the output string: assign PM_CHANGE_OUTPUT from
the node invocation without "|| true", remove the PM_CHANGED numeric check, and
replace it with a conditional like "if [[ -n \"$PM_CHANGE_OUTPUT\" ]]; then" (or
set PM_CHANGED based on -n "$PM_CHANGE_OUTPUT"); keep the existing parsing of
PM_NEW/PM_OLD and remaining logic the same.

Comment on lines +541 to +552
computeGitPRActivity() {
// Use MORIARTY_BASE_REF or GITHUB_BASE_REF as the base; fall back to origin/main
let baseRef = process.env.MORIARTY_BASE_REF || process.env.GITHUB_BASE_REF || 'main';
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
} catch {
return null;
}
try {
// Ensure we have the base ref locally
try { execSync(`git fetch --prune origin ${baseRef}:${'refs/remotes/origin/' + baseRef}`, { stdio: 'ignore' }); } catch {}
const remoteBase = baseRef.startsWith('origin/') ? baseRef : `origin/${baseRef}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same fetch refspec bug as WorktreeStrategy; breaks on baseRef like origin/main.

Fetching "origin/main" from remote "origin" doesn’t exist; you end up with bogus refs. Strip the origin/ prefix for the fetch src; let Git maintain refs/remotes/origin/*.

-      try { execSync(`git fetch --prune origin ${baseRef}:${'refs/remotes/origin/' + baseRef}`, { stdio: 'ignore' }); } catch {}
-      const remoteBase = baseRef.startsWith('origin/') ? baseRef : `origin/${baseRef}`;
+      const branch = baseRef.replace(/^origin\//, '');
+      try { execSync(`git fetch --prune --no-tags origin ${branch}`, { stdio: 'ignore' }); } catch {}
+      const remoteBase = `origin/${branch}`;
📝 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
computeGitPRActivity() {
// Use MORIARTY_BASE_REF or GITHUB_BASE_REF as the base; fall back to origin/main
let baseRef = process.env.MORIARTY_BASE_REF || process.env.GITHUB_BASE_REF || 'main';
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
} catch {
return null;
}
try {
// Ensure we have the base ref locally
try { execSync(`git fetch --prune origin ${baseRef}:${'refs/remotes/origin/' + baseRef}`, { stdio: 'ignore' }); } catch {}
const remoteBase = baseRef.startsWith('origin/') ? baseRef : `origin/${baseRef}`;
computeGitPRActivity() {
// Use MORIARTY_BASE_REF or GITHUB_BASE_REF as the base; fall back to origin/main
let baseRef = process.env.MORIARTY_BASE_REF || process.env.GITHUB_BASE_REF || 'main';
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
} catch {
return null;
}
try {
// Ensure we have the base ref locally
const branch = baseRef.replace(/^origin\//, '');
try { execSync(`git fetch --prune --no-tags origin ${branch}`, { stdio: 'ignore' }); } catch {}
const remoteBase = `origin/${branch}`;
🤖 Prompt for AI Agents
In packages/wesley-holmes/src/Moriarty.mjs around lines 541 to 552, the fetch
refspec uses the full baseRef (which may include "origin/") as the source,
producing invalid refs when baseRef is "origin/main"; normalize by stripping any
leading "origin/" from the source side before constructing the refspec and fetch
only the branch name (e.g., fetch <branch> : refs/remotes/origin/<branch>), keep
the remote-side ref as refs/remotes/origin/<branch>, and update remoteBase
determination to still allow baseRef values with or without the origin/ prefix.

// File writer
writer: {
writeFiles: async (artifacts) => {
writeFiles: async (artifacts, baseDir) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate the baseDir parameter or you'll silently mask caller bugs.

You're accepting baseDir as an optional parameter but doing zero validation up front. If some idiot passes a number or an object, your code silently falls back to 'out' at line 201, and the caller will never know their configuration was ignored.

Either validate the parameter type here, or document that undefined and garbage are treated identically. Right now you're being "helpful" in a way that hides problems.

Add parameter validation:

 writer: {
   writeFiles: async (artifacts, baseDir) => {
     if (!Array.isArray(artifacts)) {
       throw new TypeError('writer.writeFiles expects an array of artifacts');
     }
+    if (baseDir !== undefined && (typeof baseDir !== 'string' || baseDir.length === 0)) {
+      throw new TypeError('baseDir must be a non-empty string when provided');
+    }

BunBun

📝 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
writeFiles: async (artifacts, baseDir) => {
writer: {
writeFiles: async (artifacts, baseDir) => {
if (!Array.isArray(artifacts)) {
throw new TypeError('writer.writeFiles expects an array of artifacts');
}
if (baseDir !== undefined && (typeof baseDir !== 'string' || baseDir.length === 0)) {
throw new TypeError('baseDir must be a non-empty string when provided');
}
🤖 Prompt for AI Agents
In packages/wesley-host-node/src/adapters/createNodeRuntime.mjs around line 191,
the writeFiles function accepts an optional baseDir but currently treats any
non-string as if it were undefined and silently falls back to 'out'; change this
to validate baseDir up-front: if baseDir is undefined, keep the existing default
behavior; if baseDir is provided but not a non-empty string, throw a TypeError
(or reject) so callers are notified; implement a simple check (typeof baseDir
!== 'string' || baseDir.trim() === '') to decide between defaulting and
throwing, and update the function JSDoc to document that baseDir must be a
non-empty string when provided.

Comment on lines +196 to +207
let targetPath = artifact?.path;
if (!targetPath) {
const name = artifact?.name ?? '<unknown>';
throw new TypeError(`Artifact "${name}" is missing a resolved path`);
// Fallback: write under provided baseDir (or 'out/') if not materialized by the caller
if (typeof name === 'string' && name.length > 0) {
const dir = (typeof baseDir === 'string' && baseDir.length > 0) ? baseDir : 'out';
targetPath = path.join(dir, name);
} else {
throw new TypeError(`Artifact "${name}" is missing a resolved path`);
}
}
await nodeFs.write(artifact.path, artifact.content ?? '');
await nodeFs.write(targetPath, artifact.content ?? '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Your error message is lying, and the fallback logic is convoluted.

Line 204 throws an error saying the artifact is "missing a resolved path", but that's not what you're actually checking. You're checking if the artifact has either a path or a valid name to derive a path from. The real failure mode is "no path AND (no name OR invalid name)". Your error message will confuse the hell out of anyone debugging this.

And what's with the nested ternary soup at line 201? You're checking typeof baseDir === 'string' && baseDir.length > 0 inline, which is defensive but makes the code harder to read.

Simplify and fix the error message:

       for (const artifact of artifacts) {
         let targetPath = artifact?.path;
         if (!targetPath) {
           const name = artifact?.name ?? '<unknown>';
-          // Fallback: write under provided baseDir (or 'out/') if not materialized by the caller
-          if (typeof name === 'string' && name.length > 0) {
-            const dir = (typeof baseDir === 'string' && baseDir.length > 0) ? baseDir : 'out';
-            targetPath = path.join(dir, name);
-          } else {
-            throw new TypeError(`Artifact "${name}" is missing a resolved path`);
-          }
+          if (typeof name !== 'string' || name.length === 0) {
+            throw new TypeError(
+              `Artifact has neither a "path" property nor a valid "name" to derive one. Got name: ${JSON.stringify(name)}`
+            );
+          }
+          const dir = baseDir || 'out';
+          targetPath = path.join(dir, name);
         }
         await nodeFs.write(targetPath, artifact.content ?? '');
       }

Note: If you add the baseDir validation I suggested earlier, you can drop the typeof baseDir === 'string' check here entirely.

BunBun

Comment thread test/holmes-e2e.bats
Comment on lines +10 to 11
cp "$REPO_ROOT/example/schema.graphql" "$TEMP_DIR/schema.graphql"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

Path change relies on example/schema.graphql; ensure it’s tracked in repo and CI container.

If example/schema.graphql isn’t present in published tarballs/containers, E2E will break. Confirm location and add a fallback to fixtures if missing.


🏁 Script executed:

#!/bin/bash
test -f example/schema.graphql || { echo "example/schema.graphql missing" >&2; exit 1; }

Length of output: 30



Fix the missing example/schema.graphql file or provide a fallback source for the E2E test.

The test copies example/schema.graphql on lines 10-11, but the file doesn't exist in the repository. The E2E will fail immediately. Either add the missing schema file to the repository or modify the test to use an existing fixture file that's guaranteed to be present.


BunBun

🤖 Prompt for AI Agents
In test/holmes-e2e.bats around lines 10-11, the test blindly copies
example/schema.graphql which is missing and causes E2E failures; update the test
to either copy a guaranteed existing fixture (e.g., test/fixtures/schema.graphql
or example/fixtures/schema.graphql) or add the missing example/schema.graphql
file to the repo. Modify the cp line to reference the chosen existing path (or
add the file into example/), and ensure TEMP_DIR cleanup and test expectations
still match the new source.

Comment thread test/holmes-e2e.bats
Comment on lines +37 to 38
run node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('holmes.json','utf8')); if(!data.metadata || !data.metadata.verificationStatus) throw new Error('missing metadata'); if(typeof data.metadata.verificationCount !== 'number') throw new Error('verificationCount missing'); if(!data.metadata.bundleVersion) throw new Error('missing bundle version'); if(!data.scores) throw new Error('missing scores'); ['scs','tci','mri'].forEach(k=>{ if(typeof data.scores[k] !== 'number') throw new Error('missing scores.'+k); }); if(!data.breakdown || !data.breakdown.scs || !data.breakdown.tci || !data.breakdown.mri) throw new Error('missing breakdown'); if(typeof data.breakdown.scs.sql.score !== 'number' && data.breakdown.scs.sql.score !== null) throw new Error('missing scs sql score'); if(!Array.isArray(data.evidence)) throw new Error('evidence array missing'); if(!data.verdict || !data.verdict.code) throw new Error('missing verdict');"
assert_success

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Test can throw TypeError if breakdown.scs.sql is absent; use optional chaining before type check.

Right now you only assert breakdown.{scs,tci,mri} exist, not scs.sql. Accessing scs.sql.score can crash the inline node, hiding the real diagnostic.

-    run node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('holmes.json','utf8')); if(!data.metadata || !data.metadata.verificationStatus) throw new Error('missing metadata'); if(typeof data.metadata.verificationCount !== 'number') throw new Error('verificationCount missing'); if(!data.metadata.bundleVersion) throw new Error('missing bundle version'); if(!data.scores) throw new Error('missing scores'); ['scs','tci','mri'].forEach(k=>{ if(typeof data.scores[k] !== 'number') throw new Error('missing scores.'+k); }); if(!data.breakdown || !data.breakdown.scs || !data.breakdown.tci || !data.breakdown.mri) throw new Error('missing breakdown'); if(typeof data.breakdown.scs.sql.score !== 'number' && data.breakdown.scs.sql.score !== null) throw new Error('missing scs sql score'); if(!Array.isArray(data.evidence)) throw new Error('evidence array missing'); if(!data.verdict || !data.verdict.code) throw new Error('missing verdict');"
+    run node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('holmes.json','utf8')); if(!data.metadata || !data.metadata.verificationStatus) throw new Error('missing metadata'); if(typeof data.metadata.verificationCount !== 'number') throw new Error('verificationCount missing'); if(!data.metadata.bundleVersion) throw new Error('missing bundle version'); if(!data.scores) throw new Error('missing scores'); ['scs','tci','mri'].forEach(k=>{ if(typeof data.scores[k] !== 'number') throw new Error('missing scores.'+k); }); if(!data.breakdown || !data.breakdown.scs || !data.breakdown.tci || !data.breakdown.mri) throw new Error('missing breakdown'); const scsSql=data.breakdown?.scs?.sql; if(scsSql && typeof scsSql.score !== 'number' && scsSql.score !== null) throw new Error('missing scs sql score'); if(!Array.isArray(data.evidence)) throw new Error('evidence array missing'); if(!data.verdict || !data.verdict.code) throw new Error('missing verdict');"
📝 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
run node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('holmes.json','utf8')); if(!data.metadata || !data.metadata.verificationStatus) throw new Error('missing metadata'); if(typeof data.metadata.verificationCount !== 'number') throw new Error('verificationCount missing'); if(!data.metadata.bundleVersion) throw new Error('missing bundle version'); if(!data.scores) throw new Error('missing scores'); ['scs','tci','mri'].forEach(k=>{ if(typeof data.scores[k] !== 'number') throw new Error('missing scores.'+k); }); if(!data.breakdown || !data.breakdown.scs || !data.breakdown.tci || !data.breakdown.mri) throw new Error('missing breakdown'); if(typeof data.breakdown.scs.sql.score !== 'number' && data.breakdown.scs.sql.score !== null) throw new Error('missing scs sql score'); if(!Array.isArray(data.evidence)) throw new Error('evidence array missing'); if(!data.verdict || !data.verdict.code) throw new Error('missing verdict');"
assert_success
run node -e "const fs=require('fs'); const data=JSON.parse(fs.readFileSync('holmes.json','utf8')); if(!data.metadata || !data.metadata.verificationStatus) throw new Error('missing metadata'); if(typeof data.metadata.verificationCount !== 'number') throw new Error('verificationCount missing'); if(!data.metadata.bundleVersion) throw new Error('missing bundle version'); if(!data.scores) throw new Error('missing scores'); ['scs','tci','mri'].forEach(k=>{ if(typeof data.scores[k] !== 'number') throw new Error('missing scores.'+k); }); if(!data.breakdown || !data.breakdown.scs || !data.breakdown.tci || !data.breakdown.mri) throw new Error('missing breakdown'); const scsSql=data.breakdown?.scs?.sql; if(scsSql && typeof scsSql.score !== 'number' && scsSql.score !== null) throw new Error('missing scs sql score'); if(!Array.isArray(data.evidence)) throw new Error('evidence array missing'); if(!data.verdict || !data.verdict.code) throw new Error('missing verdict');"
assert_success
🤖 Prompt for AI Agents
test/holmes-e2e.bats lines 37-38: the inline Node check accesses
breakdown.scs.sql.score directly which can throw if scs.sql is undefined; change
the condition to use optional chaining when checking score (e.g., reference
breakdown.scs.sql?.score) and then assert that the value is either a number or
null before throwing — this prevents a TypeError and preserves the intended
validation.

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