Skip to content

feat(extract): auto-discover module tsconfig and extract per-module - #267

Merged
NTCoding merged 8 commits into
mainfrom
issue-261-extraction-tool-should-discove
Feb 22, 2026
Merged

NTCoding merged 8 commits into
mainfrom
issue-261-extraction-tool-should-discove

Conversation

@NTCoding

@NTCoding NTCoding commented Feb 22, 2026 •

Copy link
Copy Markdown
Owner

Closes #261

References

Discovered during issue #241 verification against ecommerce-demo-app ground truth.

Summary

createConfiguredProject in packages/riviere-cli/src/features/extract/infra/ts-morph/create-configured-project.ts only checks for tsconfig.json in the extraction config directory root. Projects with per-module tsconfigs (e.g., shipping-domain/tsconfig.json) get no type resolution — generic types like Map<string, Shipment> destructure to any.

Evidence

Diagnostic proof: shipment in for (const [id, shipment] of map.entries()) resolves to Shipment with tsconfig, any without. This causes 1 false negative in ecommerce-demo-app: shipping:useCase:UpdateTrackingUseCase → shipping:domainOp:Shipment is not detected.

Root Causes

1. Config design: module.path was a glob pattern (shipping-domain/src/**/*.ts) but named path. To find a module's tsconfig, the module's root directory is needed separately from the file pattern.

2. Single Project architecture: One ts-morph Project = one set of compiler options (one tsconfig). Modules organized as separate TS projects (own tsconfig) can't share a single Project. A per-module Project is needed, each configured with its module's tsconfig.

Implementation

Config schema change

Split path into two fields:

  • path — module root directory relative to config file (e.g., "shipping-domain")
  • glob — source file pattern within the module directory (e.g., "src/**/*.ts")

Source file resolution uses posix.join(module.path, module.glob).

Per-module Project creation

  • run-extraction.ts refactored to a per-module loop: for each module, resolve source files, create a Project with the module's tsconfig, extract/enrich/detect connections
  • findModuleTsConfigDir() checks resolve(configDir, modulePath, 'tsconfig.json') first, falls back to resolve(configDir, 'tsconfig.json')
  • Per-module infrastructure extracted to infra/per-module-operations.ts

Files changed

  • Schema: extraction-config-schema.ts, extraction-config.schema.json — added glob field
  • Config resolution: resolve-config.ts — passes through glob
  • Source file resolution: config-loader.ts — uses posix.join(path, glob)
  • Module matching: extractor.ts, enrich-components.ts — uses posix.join(m.path, m.glob) in findMatchingModule
  • Per-module Projects: run-extraction.ts, per-module-operations.ts, create-configured-project.ts, load-extraction-project.ts
  • Default conventions: default-extraction.config.json — "path": ".", "glob": "**/*.ts"
  • Test fixtures/specs: ~15 files updated for path+glob format
  • Generated docs: schema.md regenerated

Acceptance Criteria

  • Extraction resolves types correctly when only per-module tsconfigs exist (no root tsconfig needed)
  • Root tsconfig still works if present
  • --no-ts-config flag still bypasses discovery
  • ecommerce-demo-app verify:connections detects UpdateTrackingUseCase → Shipment without a root tsconfig (requires npm publish + demo app config update)

Summary by CodeRabbit

  • New Features

    • Split module config into a module root and a separate glob pattern for clearer file selection.
    • Extraction now returns structured results (draft vs full) with per-module enrichment, connection detection, and timing/failure details.
    • Workflow runner can emit JSON outputs for long-running tasks and selects reviewer models dynamically by review type.
  • Bug Fixes

    • Improved error handling and ensured stdout/stderr are flushed before process exit.

= and others added 3 commits February 22, 2026 12:59
Add tsconfig auto-discovery per module path so extraction uses proper
type resolution. Split per-module extraction into focused domain files,
move console output from command to entrypoint via infra/mappers,
and inline safe-extraction-operations library calls.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ent stdout flush race

process.exit() kills the process before console.log flushes to piped
stdout on POSIX, causing empty output from complete-task. Using
process.exitCode lets the event loop drain naturally.

Also changes code-review and bug-scanner agents to use sonnet model
for faster reviews (architecture-review and task-check stay on opus).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
… pipe data loss

process.exit() kills the process before console.log flushes to piped
stdout on POSIX. Using process.stdout.write('', callback) ensures
all pending writes complete before exit. process.exitCode alone
doesn't work because active handles (HTTP connections, Claude SDK)
prevent natural event loop drain.

Also changes code-review and bug-scanner agents to use sonnet model
for faster reviews (architecture-review and task-check stay on opus).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Feb 22, 2026 •

Copy link
Copy Markdown

Deploying living-architecture with  Cloudflare Pages  Cloudflare Pages

Latest commit: d7f5597
Status: ✅  Deploy successful!
Preview URL: https://9a247a7c.living-architecture.pages.dev
Branch Preview URL: https://issue-261-extraction-tool-sh.living-architecture.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Feb 22, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request refactors the extraction and dev-workflow pipelines to support per-module TypeScript configurations and structured extraction results. It splits module config into path (module root) and glob (source pattern), creates per-module contexts/projects, adds per-module enrichment/connection detection, and introduces workflow I/O/error-output JSON handling.

Changes

Cohort / File(s) Summary
Schema & Config docs
apps/docs/reference/extraction-config/schema.md, packages/riviere-extract-config/extraction-config.schema.json, packages/riviere-extract-config/src/extraction-config-schema.ts
Added required glob field; updated path description to mean module root; schema and docs updated.
Config loading & resolution
packages/riviere-cli/src/platform/infra/extraction-config/config-loader.ts, packages/riviere-cli/src/platform/infra/extraction-config/expand-module-refs.spec.ts, packages/riviere-extract-ts/src/features/extraction/domain/config-resolution/resolve-config.spec.ts
Construct source globs using posix.join(module.path, module.glob) and updated tests/fixtures to path+glob format.
Module matching
packages/riviere-extract-ts/src/features/extraction/domain/component-extraction/extractor.ts, packages/riviere-extract-ts/src/features/extraction/domain/value-extraction/enrich-components.ts
Use posix.join(m.path, m.glob) when matching files to modules.
Per-module extraction domain
packages/riviere-cli/src/features/extract/domain/extract-draft-components.ts, .../enrich-per-module.ts, .../detect-connections-per-module.ts, .../extraction-result.ts
New domain modules: ModuleContext extractor, enrichPerModule (with OrphanedDraftComponentError), detectConnectionsPerModule, and discriminated ExtractionResult type.
Run extraction & presentation
packages/riviere-cli/src/features/extract/commands/run-extraction.ts, packages/riviere-cli/src/features/extract/entrypoint/extract.ts, packages/riviere-cli/src/features/extract/infra/mappers/present-extraction-result.ts
Refactor runExtraction to return ExtractionResult (draftOnly or full); wire per-module flows (extractDraftComponents → enrichPerModule → detectConnectionsPerModule); add presentExtractionResult for CLI output.
Module contexts & tsconfig discovery
packages/riviere-cli/src/features/extract/infra/external-clients/create-module-contexts.ts, .../find-module-tsconfig-dir.ts, .../load-extraction-project.ts, .../find-module-tsconfig-dir.spec.ts
Added createModuleContexts to build per-module projects/files; added findModuleTsConfigDir to detect per-module tsconfig; updated project loading path.
Removed helpers / test updates
packages/riviere-cli/src/features/extract/infra/safe-extraction-operations.ts, various extract tests & fixtures (packages/riviere-cli/src/features/extract/..., packages/riviere-extract-config/..., packages/riviere-extract-conventions/..., packages/riviere-extract-ts/src/test-fixtures.ts)
Deleted safe-extraction-operations (logic moved to domain); updated many tests/fixtures to new path+glob shape and test signatures.
Dev-workflow: run-workflow & error I/O
tools/dev-workflow/platform/infra/workflow-execution/run-workflow.ts, .../run-workflow.spec.ts, .../error-handler.ts, .../error-handler.spec.ts, .../cli/format-timings.ts, .../cli/format-timings.spec.ts
Added optional resolveOutputFilePath and errorOutputFilePath hooks; handleWorkflowError writes structured JSON and exits after flushing stderr; runWorkflow writes optional JSON output and always exits 0 on success path; extracted formatTimingsMarkdown.
Dev-workflow: complete-task changes
tools/dev-workflow/features/complete-task/commands/complete-task.ts, .../complete-task.spec.ts, .../domain/workflow-setup.ts, .../domain/steps/run-code-review.ts, .../run-code-review.spec.ts
Rewrote complete-task context wiring, added resolveOutputFilePath export, moved runWorkflow imports to infra, introduced REVIEWER_MODELS mapping for dynamic model selection; tests updated for output/error path and reviewer model behavior.
Dev-workflow: IO exit behavior
tools/dev-workflow/platform/infra/external-clients/workflow-io.ts, .../workflow-io.spec.ts
Ensure stdout is flushed before process.exit by writing an empty string and using its callback; tests updated to mock process.exit/stdout.
Misc / tooling
.claude/commands/complete-task.md, packages/riviere-cli/vitest.config.mts, packages/riviere-extract-ts/README.md, packages/riviere-extract-config/README.md, package.json
Updated complete-task doc to show output file capture; removed safe-extraction-operations from coverage exclude; README examples adjusted to path+glob; bumped various @nx devDeps.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Extract Entrypoint
    participant RunExt as runExtraction
    participant CreateCtx as createModuleContexts
    participant ExtractDrafts as extractDraftComponents
    participant EnrichMod as enrichPerModule
    participant DetectConn as detectConnectionsPerModule
    participant Present as presentExtractionResult

    CLI->>RunExt: runExtraction(options, config, configDir, sourceFilePaths)
    RunExt->>CreateCtx: createModuleContexts(config, configDir, sourceFilePaths)
    CreateCtx-->>RunExt: ModuleContext[] (module, files, project per module)
    
    RunExt->>ExtractDrafts: extractDraftComponents(moduleContexts, config, configDir)
    ExtractDrafts-->>RunExt: DraftComponent[]
    
    alt draftOnly / dry-run
        RunExt-->>CLI: ExtractionResult (draftOnly)
    else full extraction
        RunExt->>EnrichMod: enrichPerModule(moduleContexts, drafts, config, configDir)
        EnrichMod-->>RunExt: {components, failedFields}
        
        RunExt->>DetectConn: detectConnectionsPerModule(moduleContexts, components, repo, allowIncomplete)
        DetectConn-->>RunExt: {links, timings}
        
        RunExt-->>CLI: ExtractionResult (full)
    end
    
    CLI->>Present: presentExtractionResult(result, options)
    Present-->>CLI: formatted CLI/JSON output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Review effort 4/5, extraction, refactoring, per-module

Poem

🐰 Hop and nibble, code in tow,

Modules split where globs now grow,
Tsconfigs found in each domain,
JSON outputs save the strain,
Hooray — the rabbit hops again!

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive Some changes appear out of scope: refactoring run-workflow import paths in dev-workflow features, adding formatTimingsMarkdown to infra, and restructuring workflow error handling seem unrelated to the core per-module tsconfig extraction objective. Verify whether dev-workflow refactoring and error-handler changes support the extraction feature or represent separate improvements; clarify scope boundaries if needed.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(extract): auto-discover module tsconfig and extract per-module' accurately summarizes the main change: implementing per-module TypeScript project discovery and extraction as a feature addition.
Linked Issues check ✅ Passed The PR fully addresses the objectives from linked issue #261: implements per-module tsconfig discovery, splits module config into path/glob, creates per-module Projects, maintains root tsconfig fallback, and preserves --no-ts-config bypass.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-261-extraction-tool-should-discove

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.16.0)
tools/dev-workflow/platform/infra/cli/format-timings.spec.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m

packages/riviere-cli/src/features/extract/domain/enrich-per-module.spec.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m

tools/dev-workflow/platform/infra/workflow-execution/error-handler.ts

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m


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.

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

Caution

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

⚠️ Outside diff range comments (8)
packages/riviere-extract-ts/README.md (2)

9-9: ⚠️ Potential issue | 🟡 Minor

Stale "Skeleton implementation" status note.

This note predates the current PR's work. The implementation is now substantive (per-module extraction, connection detection, enrichment). The status banner should be removed or updated to reflect the current state.

✏️ Proposed fix
-**Current Status:** Skeleton implementation. Predicate logic and output generation coming in subsequent tasks (see PRD phase-10-typescript-extraction).
-
 ## Installation
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/riviere-extract-ts/README.md` at line 9, Update the README status
line that currently reads "Skeleton implementation. Predicate logic and output
generation coming in subsequent tasks (see PRD phase-10-typescript-extraction)"
to reflect the current substantive implementation: either remove the "Skeleton
implementation" sentence entirely or replace it with a short, accurate status
summary (e.g., "Per-module extraction, connection detection, and enrichment
implemented — see PRD phase-10-typescript-extraction for remaining tasks") so
the README no longer claims the code is a skeleton.

42-43: ⚠️ Potential issue | 🟠 Major

README example is non-functional: missing required parameters and imports.

The usage example on lines 42–43 calls extractComponents(sourceFiles, config) with only 2 arguments, but the actual function signature requires:

  • project: Project (first parameter)
  • sourceFilePaths: string[] (second parameter)
  • config: ResolvedExtractionConfig (third parameter—note the type differs from the example's ExtractionConfig)
  • globMatcher: GlobMatcher (fourth parameter)
  • configDir?: string (optional fifth parameter)

The example also lacks the required imports for Project and matchesGlob. Update the example to match the actual CLI pattern: extractComponents(project, sourceFiles, resolvedConfig, matchesGlob, configDir).

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

In `@packages/riviere-extract-ts/README.md` around lines 42 - 43, The README
example calls extractComponents with the wrong signature and is missing imports;
update the snippet to import Project and matchesGlob and call extractComponents
with the proper parameters: create or obtain a Project instance (type Project)
and pass it as the first arg, pass sourceFiles as the second, pass a
ResolvedExtractionConfig (not raw ExtractionConfig) as the third, pass
matchesGlob (type GlobMatcher) as the fourth, and include the optional configDir
string as the fifth if applicable; ensure the example shows importing Project
and matchesGlob and constructing/obtaining resolvedConfig before calling
extractComponents(project, sourceFiles, resolvedConfig, matchesGlob, configDir).
packages/riviere-extract-conventions/src/default-config.spec.ts (1)

77-91: ⚠️ Potential issue | 🟡 Minor

Add glob to requiredKeys for an explicit assertion.

The new 10th key (glob) is verified only by the toHaveLength(10) count — any other extra key would also satisfy the test. Listing glob in requiredKeys makes the assertion targeted and would catch regressions where glob is missing or renamed.

🛡️ Proposed fix
     const requiredKeys = [
       'name',
       'path',
+      'glob',
       'api',
       'useCase',
       'domainOp',
       'event',
       'eventHandler',
       'eventPublisher',
       'ui',
     ]
     const moduleKeys = Object.keys(module)
     expect(moduleKeys).toStrictEqual(expect.arrayContaining(requiredKeys))
     expect(moduleKeys).toHaveLength(10)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/riviere-extract-conventions/src/default-config.spec.ts` around lines
77 - 91, The test builds requiredKeys and asserts moduleKeys contains them and
has length 10; add the missing 'glob' string to the requiredKeys array (in the
test that defines requiredKeys/moduleKeys) so the explicit assertion checks for
'glob' rather than relying solely on the toHaveLength(10) check; update the
requiredKeys array (the variable named requiredKeys in default-config.spec.ts)
to include 'glob'.
packages/riviere-extract-config/extraction-config.schema.json (1)

44-63: ⚠️ Potential issue | 🟡 Minor

Stale module description — "path pattern" now refers to glob, not path.

Line 46 still reads "A module defines extraction rules for a path pattern". With this PR, path is a directory (not a pattern) and the pattern lives in glob. The description should be updated to reflect the new two-field semantics.

📝 Suggested fix
-      "description": "A module defines extraction rules for a path pattern",
+      "description": "A module defines extraction rules for a directory path and source file glob pattern",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/riviere-extract-config/extraction-config.schema.json` around lines
44 - 63, Update the schema description for the "module" object to reflect the
new semantics: change the sentence "A module defines extraction rules for a path
pattern" to indicate that a module defines extraction rules for a module
directory ("path") combined with a file pattern ("glob"); reference the "module"
object and its "path" (module root directory) and "glob" (glob pattern for
source files) properties so the description clearly states that path is a
directory and glob is the pattern used to match files.
packages/riviere-extract-config/src/validation-extends.spec.ts (1)

85-96: ⚠️ Potential issue | 🟡 Minor

Test "no path" case now also omits glob, obscuring the intent

After making glob required, this test exercises "neither path nor glob present" rather than isolating a missing-path scenario. The assertion still passes, but the test no longer proves the validator specifically rejects a missing path. Adding glob here would make this test precise.

🔧 Proposed fix to isolate the missing-path case
    it('returns error when module has extends but no path', () => {
      const result = validateExtractionConfig({
        modules: [
          {
            name: 'orders',
+           glob: '**',
            extends: '@living-architecture/riviere-extract-conventions',
          },
        ],
      })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/riviere-extract-config/src/validation-extends.spec.ts` around lines
85 - 96, The test "returns error when module has extends but no path" now also
omits glob, so it actually tests "neither path nor glob present"; update the
test case in validation-extends.spec.ts to include a valid glob on the module
(e.g., add a glob property) while leaving path absent so
validateExtractionConfig is exercised specifically for missing path; keep the
same assertions (expect result.valid to be false and the error path to include
'/modules/0') and reference the validateExtractionConfig call and the module
object with name 'orders' and extends
'@living-architecture/riviere-extract-conventions' so the intent is clear.
tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts (2)

247-295: 🧹 Nitpick | 🔵 Trivial

Identical mock setup/teardown duplicated across two tests — extract a shared helper.

The Object.defineProperty + console.log spy setup (and matching teardown) is copied verbatim into both "default IO" tests. Regardless of whether vi.spyOn is adopted (see comment above), this should be extracted into a helper or beforeEach/afterEach scoped to a nested describe block to eliminate duplication and reduce maintenance surface.

♻️ Suggested structure
describe('uses default IO (no io option)', () => {
  beforeEach(() => {
    vi.spyOn(process, 'exit').mockImplementation(vi.fn() as never)
    vi.spyOn(console, 'log').mockImplementation(vi.fn())
  })
  // afterEach handled automatically by vi.restoreAllMocks() in outer beforeEach

  it('exits with 0 on success', async () => { ... })
  it('writeFile is a noop when resolveTimingsFilePath provided', async () => { ... })
})

Also applies to: 298-346

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

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`
around lines 247 - 295, The test duplicates the mock/teardown setup for
process.exit, process.stdout.write and console.log across two "uses default IO"
tests; extract this into a shared setup/teardown helper (or a nested describe
with beforeEach/afterEach) so both specs reuse it. Move the
Object.defineProperty mocks (or replace with vi.spyOn(process,
'exit').mockImplementation(...) and vi.spyOn(process.stdout,
'write').mockImplementation(...)) and the console.log spy (mockLog) into the
shared beforeEach, and restore/restoreAllMocks in afterEach (or rely on outer
restoreAllMocks) so individual tests only declare steps, buildContext and call
runWorkflow; keep references to runWorkflow, mockLog, process.exit and
process.stdout.write to locate the code to change.

247-295: ⚠️ Potential issue | 🟠 Major

process.exit / process.stdout.write mocks are not cleaned up if assertions fail.

Both tests mock these globals via Object.defineProperty and restore them at the end of the test body. If vi.waitFor(...) times out or throws, the restore code never runs. Because these mocks are not created via vi.spyOn, vi.restoreAllMocks() in beforeEach won't clean them up either, leaving subsequent tests with a stubbed process.exit.

Per testing conventions, prefer vi.spyOn so that beforeEach's vi.restoreAllMocks() handles cleanup automatically. If Object.defineProperty is truly required for process.stdout.write, at minimum wrap the assertion in try/finally.

🔒 Proposed fix — switch to `vi.spyOn` and remove manual teardown
  it('uses default IO when io option not provided', async () => {
-   const originalExit = process.exit
-   const originalWrite = process.stdout.write
-   const mockExit = vi.fn()
-   const mockWrite = vi.fn(
-     (
-       _data: string | Uint8Array,
-       encodingOrCb?: BufferEncoding | ((error?: Error | null) => void),
-       cb?: (error?: Error | null) => void,
-     ): boolean => {
-       const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb
-       if (callback) callback()
-       return true
-     },
-   )
-   Object.defineProperty(process, 'exit', {
-     value: mockExit,
-     configurable: true,
-   })
-   Object.defineProperty(process.stdout, 'write', {
-     value: mockWrite,
-     configurable: true,
-   })
-   const mockLog = vi.spyOn(console, 'log').mockImplementation(vi.fn())
+   const mockExit = vi.spyOn(process, 'exit').mockImplementation(vi.fn() as never)
+   const mockLog = vi.spyOn(console, 'log').mockImplementation(vi.fn())
    // ... test body ...
    await vi.waitFor(() => {
      expect(mockLog).toHaveBeenCalledWith(expect.stringContaining('result'))
      expect(mockExit).toHaveBeenCalledWith(0)
    })
-   Object.defineProperty(process, 'exit', { value: originalExit, configurable: true })
-   Object.defineProperty(process.stdout, 'write', { value: originalWrite, configurable: true })
-   mockLog.mockRestore()
  })

Also applies to: 298-346

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

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`
around lines 247 - 295, The test stubs process.exit and process.stdout.write
with Object.defineProperty which are not restored if vi.waitFor throws; change
these to use vi.spyOn(process, 'exit') and vi.spyOn(process.stdout, 'write') (or
wrap the test assertions in a try/finally) so vi.restoreAllMocks() in beforeEach
cleans them up automatically; update both test cases that call runWorkflow and
use mockLog/vi.waitFor to create spies via vi.spyOn(...) for process.exit and
process.stdout.write and remove the manual Object.defineProperty teardown (or
add try/finally around the assertions if Object.defineProperty must remain).
Ensure references to process.exit, process.stdout.write, runWorkflow, mockLog,
and vi.waitFor are updated accordingly.
packages/riviere-extract-ts/src/features/extraction/domain/value-extraction/enrich-components.spec.ts (1)

86-87: ⚠️ Potential issue | 🟡 Minor

notUsedModule calls still pass old glob-embedded paths, leaving fixtures in an inconsistent state.

notUsedModule now always appends glob: '**', so calling it with '/src/orders/**' produces a module with path: '/src/orders/**' and glob: '**'. If enrich-components.ts resolves the glob by joining module.path and module.glob (as stated in the PR description), the effective pattern would be /src/orders/**/**.

The tests still pass because alwaysMatch/neverMatch bypass glob evaluation, but the fixtures are inconsistent with the new convention and will mislead readers.

🐛 Proposed fix
-        [notUsedModule('orders', '/src/orders/**')],  // line 86
+        [notUsedModule('orders', '/src/orders')],
 
-      const result = enrich([], [notUsedModule('orders', '/src/orders/**')])  // line 107
+      const result = enrich([], [notUsedModule('orders', '/src/orders')])
 
-      const config: ResolvedExtractionConfig = {modules: [notUsedModule('other', '/src/other/**')],}  // line 217
+      const config: ResolvedExtractionConfig = {modules: [notUsedModule('other', '/src/other')],}

Also applies to: 107-107, 217-217

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

In
`@packages/riviere-extract-ts/src/features/extraction/domain/value-extraction/enrich-components.spec.ts`
around lines 86 - 87, The fixtures call notUsedModule with a glob-embedded path
(e.g., '/src/orders/**') but notUsedModule now appends module.glob = '**'
itself, causing duplicate globs; update the test fixtures to pass base
directories without the trailing '/**' (e.g., '/src/orders') wherever
notUsedModule is used (references: notUsedModule in the spec and the glob
resolution behavior in enrich-components.ts) so module.path holds the base path
and module.glob remains '**', keeping fixture expectations consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/commands/complete-task.md:
- Around line 29-33: Clarify that the "10-minute timeout (600000ms)" applies to
Claude's tool call, not the shell command: update the paragraph that introduces
the bash one-liner (the line containing "Run with a **10-minute timeout**
(600000ms)") to add a short parenthetical or sentence stating that the timeout
is set via the Claude tool's timeout_ms setting and the provided bash snippet
intentionally omits a shell-level timeout wrapper; keep the example command
unchanged and add the brief note immediately above or below the fenced code
block so readers understand the distinction.

In `@packages/riviere-cli/src/features/extract/domain/enrich-per-module.ts`:
- Around line 23-27: The loop over moduleContexts currently calls
draftComponents.filter(...) for each ctx (inside the for (const ctx of
moduleContexts) loop) causing O(modules × drafts) cost; pre-group
draftComponents once into a Map keyed by domain (e.g., domainToDrafts) before
the loop, then replace the per-iteration filter and moduleDrafts assignment with
a direct lookup like domainToDrafts.get(ctx.module.name) || [] so the overall
cost becomes O(modules + drafts) and you can remove the repeated filter call.
- Line 37: The code appends result.failures.map((f) => f.field) into
failedFields in enrichPerModule, which allows duplicate field names; change
enrichPerModule to deduplicate failedFields before returning (replace the
push-or-return step that follows failedFields.push(...result.failures.map(...))
with a deduplication step, e.g., build a Set or use Array.from(new
Set(failedFields))) so the user-facing warning in present-extraction-result.ts
shows unique field names only.
- Around line 23-38: DraftComponent entries in draftComponents are silently
dropped when their domain doesn't match any ctx.module.name in moduleContexts;
update the load/enrich loop that filters draftComponents (the code around
moduleContexts, moduleDrafts and the call to enrichComponents) to detect and
report unmatched drafts instead of discarding them: compute the set of known
module names from moduleContexts, partition draftComponents into matched (by
d.domain) and unmatched groups, log or collect diagnostics for unmatched drafts
(including their domain and identifier) and ensure unmatched entries are
surfaced to the caller (e.g., add to failedFields or return a separate failures
array) so stale/renamed domains are visible to users rather than silently
dropped.

In
`@packages/riviere-cli/src/features/extract/infra/mappers/present-extraction-result.ts`:
- Line 43: presentDraftResult currently forwards the entire ExtractOptions to
outputResult while presentFullResult passes only { output: options.output };
make them consistent by changing presentDraftResult so it passes only the output
shape (e.g., call outputResult(formatSuccess(components), { output:
options.output })) rather than the full ExtractOptions object; reference
presentDraftResult, presentFullResult, outputResult, and ExtractOptions to
locate and update the code.

In
`@packages/riviere-cli/src/features/extract/infra/ts-morph/find-module-tsconfig-dir.spec.ts`:
- Around line 11-15: The temp directory prefix used in createTestDir is
outdated; change the string `per-module-ops-test` to a name matching this spec
such as `find-module-tsconfig-dir-test` so test artifacts are clearly correlated
to the find-module-tsconfig-dir tests (update the join(tmpdir(),
`per-module-ops-test-${Date.now()}`) call in the createTestDir function
accordingly).

In `@packages/riviere-extract-config/README.md`:
- Line 14: The example config object is invalid: change the api.find value to a
valid findTarget plural (e.g., "classes") and add the required detectionRule
`where` field under api so the detectionRule has both `find` and `where`; update
the sample config assigned to `config` (referencing symbols: config, modules,
api, findTarget/detectionRule) so that isValidExtractionConfig(config) returns
true and the config can be typed as ExtractionConfig.

In `@tools/dev-workflow/platform/infra/workflow-execution/error-handler.spec.ts`:
- Around line 32-45: The beforeEach setup is missing vi.clearAllMocks(), causing
module-level mocks (mockProcessExit, mockStderrWrite) to accumulate calls across
tests; update the beforeEach for the test file so it calls vi.clearAllMocks() at
the top (before resetting capturedOutput and redefining process.exit and
process.stderr.write) to reset mock call counts and histories for
mockProcessExit and mockStderrWrite and any other spies like console.error
between tests.

---

Outside diff comments:
In `@packages/riviere-extract-config/extraction-config.schema.json`:
- Around line 44-63: Update the schema description for the "module" object to
reflect the new semantics: change the sentence "A module defines extraction
rules for a path pattern" to indicate that a module defines extraction rules for
a module directory ("path") combined with a file pattern ("glob"); reference the
"module" object and its "path" (module root directory) and "glob" (glob pattern
for source files) properties so the description clearly states that path is a
directory and glob is the pattern used to match files.

In `@packages/riviere-extract-config/src/validation-extends.spec.ts`:
- Around line 85-96: The test "returns error when module has extends but no
path" now also omits glob, so it actually tests "neither path nor glob present";
update the test case in validation-extends.spec.ts to include a valid glob on
the module (e.g., add a glob property) while leaving path absent so
validateExtractionConfig is exercised specifically for missing path; keep the
same assertions (expect result.valid to be false and the error path to include
'/modules/0') and reference the validateExtractionConfig call and the module
object with name 'orders' and extends
'@living-architecture/riviere-extract-conventions' so the intent is clear.

In `@packages/riviere-extract-conventions/src/default-config.spec.ts`:
- Around line 77-91: The test builds requiredKeys and asserts moduleKeys
contains them and has length 10; add the missing 'glob' string to the
requiredKeys array (in the test that defines requiredKeys/moduleKeys) so the
explicit assertion checks for 'glob' rather than relying solely on the
toHaveLength(10) check; update the requiredKeys array (the variable named
requiredKeys in default-config.spec.ts) to include 'glob'.

In `@packages/riviere-extract-ts/README.md`:
- Line 9: Update the README status line that currently reads "Skeleton
implementation. Predicate logic and output generation coming in subsequent tasks
(see PRD phase-10-typescript-extraction)" to reflect the current substantive
implementation: either remove the "Skeleton implementation" sentence entirely or
replace it with a short, accurate status summary (e.g., "Per-module extraction,
connection detection, and enrichment implemented — see PRD
phase-10-typescript-extraction for remaining tasks") so the README no longer
claims the code is a skeleton.
- Around line 42-43: The README example calls extractComponents with the wrong
signature and is missing imports; update the snippet to import Project and
matchesGlob and call extractComponents with the proper parameters: create or
obtain a Project instance (type Project) and pass it as the first arg, pass
sourceFiles as the second, pass a ResolvedExtractionConfig (not raw
ExtractionConfig) as the third, pass matchesGlob (type GlobMatcher) as the
fourth, and include the optional configDir string as the fifth if applicable;
ensure the example shows importing Project and matchesGlob and
constructing/obtaining resolvedConfig before calling extractComponents(project,
sourceFiles, resolvedConfig, matchesGlob, configDir).

In
`@packages/riviere-extract-ts/src/features/extraction/domain/value-extraction/enrich-components.spec.ts`:
- Around line 86-87: The fixtures call notUsedModule with a glob-embedded path
(e.g., '/src/orders/**') but notUsedModule now appends module.glob = '**'
itself, causing duplicate globs; update the test fixtures to pass base
directories without the trailing '/**' (e.g., '/src/orders') wherever
notUsedModule is used (references: notUsedModule in the spec and the glob
resolution behavior in enrich-components.ts) so module.path holds the base path
and module.glob remains '**', keeping fixture expectations consistent.

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`:
- Around line 247-295: The test duplicates the mock/teardown setup for
process.exit, process.stdout.write and console.log across two "uses default IO"
tests; extract this into a shared setup/teardown helper (or a nested describe
with beforeEach/afterEach) so both specs reuse it. Move the
Object.defineProperty mocks (or replace with vi.spyOn(process,
'exit').mockImplementation(...) and vi.spyOn(process.stdout,
'write').mockImplementation(...)) and the console.log spy (mockLog) into the
shared beforeEach, and restore/restoreAllMocks in afterEach (or rely on outer
restoreAllMocks) so individual tests only declare steps, buildContext and call
runWorkflow; keep references to runWorkflow, mockLog, process.exit and
process.stdout.write to locate the code to change.
- Around line 247-295: The test stubs process.exit and process.stdout.write with
Object.defineProperty which are not restored if vi.waitFor throws; change these
to use vi.spyOn(process, 'exit') and vi.spyOn(process.stdout, 'write') (or wrap
the test assertions in a try/finally) so vi.restoreAllMocks() in beforeEach
cleans them up automatically; update both test cases that call runWorkflow and
use mockLog/vi.waitFor to create spies via vi.spyOn(...) for process.exit and
process.stdout.write and remove the manual Object.defineProperty teardown (or
add try/finally around the assertions if Object.defineProperty must remain).
Ensure references to process.exit, process.stdout.write, runWorkflow, mockLog,
and vi.waitFor are updated accordingly.

Comment thread .claude/commands/complete-task.md Outdated
Comment thread packages/riviere-cli/src/features/extract/domain/enrich-per-module.ts Outdated
Comment thread packages/riviere-extract-config/README.md Outdated
= and others added 4 commits February 22, 2026 14:10
- Pre-group drafts by module using Map (O(n) vs O(modules × drafts))
- Deduplicate failedFields via Set
- Fail-fast on orphaned draft components with OrphanedDraftComponentError
- Fix inconsistent outputResult options shape in present-extraction-result
- Fix invalid README example (find: 'class' → find: 'classes')
- Fix stale temp dir prefix in find-module-tsconfig-dir spec
- Add missing vi.clearAllMocks() in error-handler spec beforeEach
- Clarify Bash tool timeout in complete-task.md

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Stdout piping through nx is unreliable for long-running processes.
Write result JSON to reviews/{branch}/output.json via existing
io.writeFile mechanism. Error output written to static path
reviews/error-output.json. Stdout still receives output for
backwards compatibility.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Move ExtractionResult type from commands/ to domain/ (SoC-002)
- Move formatTimingsMarkdown to platform/infra/cli/ (SoC-001/SoC-014)
- Inject createDirectory dep in workflow-setup to remove I/O from domain (SoC-004)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Rename infra/ts-morph/ to infra/external-clients/ (SoC-012)
- Move buildCompleteTaskContext from domain/ to command file (SoC-001/SoC-004)

Co-Authored-By: Claude Opus 4.6 <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts (3)

228-243: ⚠️ Potential issue | 🟡 Minor

"Does not write" tests pass before the workflow finishes — false positive risk.

vi.waitFor(() => expect(...).toHaveLength(0)) resolves immediately on the first poll because the collection is empty before the workflow has had a chance to execute. If a regression were introduced that causes a delayed write, these tests would still pass.

Contrast with the "writes" tests (e.g., line 220), which correctly block until a positive signal appears.

The fix is to first confirm workflow completion via mockIO.exitCode, then assert the absence:

-    await vi.waitFor(() => {
-      expect(mockIO.writeFileCalls).toHaveLength(0)
-    })
+    await vi.waitFor(() => expect(mockIO.exitCode).toBeDefined())
+    expect(mockIO.writeFileCalls).toHaveLength(0)

Apply the same pattern to the filter-based assertion at line 280:

-    await vi.waitFor(() => {
-      expect(mockIO.writeFileCalls.filter((c) => c.path.endsWith('output.json'))).toHaveLength(0)
-    })
+    await vi.waitFor(() => expect(mockIO.exitCode).toBeDefined())
+    expect(mockIO.writeFileCalls.filter((c) => c.path.endsWith('output.json'))).toHaveLength(0)

Also applies to: 267-282

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

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`
around lines 228 - 243, The test currently uses vi.waitFor(() =>
expect(mockIO.writeFileCalls).toHaveLength(0)) which can pass before the
workflow runs; update the test to wait for workflow completion by awaiting the
mockIO.exitCode (e.g., await vi.waitFor(() =>
expect(mockIO.exitCode).toBeDefined() or a specific code) after calling
runWorkflow(…) and only then assert
expect(mockIO.writeFileCalls).toHaveLength(0); apply the same change to the
filter-based assertion that inspects mockIO.writeFileCalls (replace its
immediate waitFor check with first waiting for mockIO.exitCode and then
asserting the filtered result is empty).

19-31: ⚠️ Potential issue | 🟡 Minor

Add Error.captureStackTrace to both test error classes.

Both TestExitSignal and ContextBuildError are missing Error.captureStackTrace, which is required by the coding guidelines for this path.

🛠️ Proposed fix
 class TestExitSignal extends Error {
   constructor() {
     super('process.exit called')
     this.name = 'TestExitSignal'
+    Error.captureStackTrace(this, TestExitSignal)
   }
 }

 class ContextBuildError extends Error {
   constructor() {
     super('context error')
     this.name = 'ContextBuildError'
+    Error.captureStackTrace(this, ContextBuildError)
   }
 }

Based on learnings: "Custom error classes must use Error.captureStackTrace for cleaner stack traces." (applies to tools/dev-workflow/**/*.{ts,tsx})

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

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`
around lines 19 - 31, Both custom error classes TestExitSignal and
ContextBuildError need Error.captureStackTrace called in their constructors to
produce proper stack traces; update each constructor to call
Error.captureStackTrace(this, TestExitSignal) and Error.captureStackTrace(this,
ContextBuildError) respectively (after super(...)) and keep the name assignments
as-is so they comply with the project's coding guidelines for custom errors.

304-403: ⚠️ Potential issue | 🟠 Major

Resource leak: process.exit / process.stdout.write not cleaned up on assertion failure, and setup is duplicated.

Both tests restore process.exit and process.stdout.write after await vi.waitFor(...). If vi.waitFor times out or an assertion inside it throws, the Object.defineProperty restores on lines 344–351 and 394–401 are never reached. vi.restoreAllMocks() in beforeEach does not restore Object.defineProperty overrides (it only restores vi.spyOn spies), so a failing test leaves the global mocks active for subsequent tests.

The same 20-line Object.defineProperty boilerplate is duplicated verbatim across both tests, making future maintenance error-prone.

Recommended fix: Use vi.spyOn (which vi.restoreAllMocks() handles automatically) and move the spy setup into a nested describe with beforeEach/afterEach to eliminate the duplication:

♻️ Proposed refactor
+  describe('default IO behavior', () => {
+    let mockExit: ReturnType<typeof vi.fn>
+    let mockWrite: ReturnType<typeof vi.fn>
+    let mockLog: ReturnType<typeof vi.spyOn>
+
+    beforeEach(() => {
+      mockExit = vi.fn()
+      mockWrite = vi.fn(
+        (
+          _data: string | Uint8Array,
+          encodingOrCb?: BufferEncoding | ((error?: Error | null) => void),
+          cb?: (error?: Error | null) => void,
+        ): boolean => {
+          const callback = typeof encodingOrCb === 'function' ? encodingOrCb : cb
+          if (callback) callback()
+          return true
+        },
+      )
+      vi.spyOn(process, 'exit').mockImplementation(mockExit)
+      vi.spyOn(process.stdout, 'write').mockImplementation(mockWrite)
+      mockLog = vi.spyOn(console, 'log').mockImplementation(vi.fn())
+    })
+
+    // vi.restoreAllMocks() in the outer beforeEach handles cleanup automatically
+
     it('uses default IO when io option not provided', async () => {
-      const originalExit = process.exit
-      const originalWrite = process.stdout.write
-      const mockExit = vi.fn()
-      const mockWrite = vi.fn(...)
-      Object.defineProperty(process, 'exit', { value: mockExit, configurable: true })
-      Object.defineProperty(process.stdout, 'write', { value: mockWrite, configurable: true })
-      const mockLog = vi.spyOn(console, 'log').mockImplementation(vi.fn())
       const steps: Step<BaseContext>[] = [...]
       const buildContext = async () => ({ branch: 'test' })

       runWorkflow(steps, buildContext)

       await vi.waitFor(() => {
         expect(mockLog).toHaveBeenCalledWith(expect.stringContaining('result'))
         expect(mockExit).toHaveBeenCalledWith(0)
       })
-
-      Object.defineProperty(process, 'exit', { value: originalExit, configurable: true })
-      Object.defineProperty(process.stdout, 'write', { value: originalWrite, configurable: true })
-      mockLog.mockRestore()
     })

     it('uses default IO writeFile (noop) when resolveTimingsFilePath provided without io', async () => {
-      // same Object.defineProperty boilerplate
       const steps: Step<BaseContext>[] = [...]
       const buildContext = async () => ({ branch: 'test' })

       runWorkflow(steps, buildContext, undefined, { resolveTimingsFilePath: () => 'timings.md' })

       await vi.waitFor(() => {
         expect(mockExit).toHaveBeenCalledWith(0)
       })
-
-      // same restore boilerplate
     })
+  })

Based on learnings: "When mocking globals, use vi.spyOn instead of direct property reassignment to prevent mock leaks if tests throw before restore is reached." (applies via **/*.{test,spec}.{ts,tsx} → docs/conventions/testing.md)

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

In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`
around lines 304 - 403, Replace the direct Object.defineProperty overrides for
process.exit and process.stdout.write with vi.spyOn-based mocks (e.g.,
vi.spyOn(process, 'exit').mockImplementation(mockExit) and
vi.spyOn(process.stdout, 'write').mockImplementation(mockWrite)) and move that
setup/teardown into a nested describe with beforeEach to create the mocks and
afterEach to call vi.restoreAllMocks() (or explicitly mockRestore) so mocks are
always cleaned even if vi.waitFor/assertions throw; update both tests to use the
shared describe and remove the duplicated Object.defineProperty blocks
(referencing process.exit and process.stdout.write in the changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/riviere-cli/src/features/extract/domain/enrich-per-module.spec.ts`:
- Around line 72-102: The test enriches drafts per module but doesn't assert
that enrichPerModule routes the correct drafts to each module call; update the
"enriches drafts grouped by module" spec to verify mockEnrichComponents was
invoked with per-module arguments (use mockEnrichComponents.mock.calls or
toHaveBeenCalledWith) so the first call receives the orders context and only the
orders draft (createModuleContext('orders'), [createDraft('orders','CompA')],
stubConfig, '/config') and the second call receives the shipping context and
only the shipping draft (createModuleContext('shipping'),
[createDraft('shipping','CompB')], stubConfig, '/config'); keep the existing
aggregated assertions but add these per-call argument checks to prevent passing
if routing is wrong.

In `@tools/dev-workflow/platform/infra/cli/format-timings.spec.ts`:
- Around line 6-41: Add a unit test for the edge case where stepTimings is an
empty array by calling formatTimingsMarkdown([], 0) and asserting the output
still includes the table header row and separator (e.g., the column names and
the markdown divider) but contains no data rows, and that the total is rendered
as "**Total: 0ms**"; reference the formatTimingsMarkdown function in the
existing spec and add the new it(...) block alongside the other tests.
- Around line 7-25: The test "formats step timings as markdown table" only
checks row content and total, so update the spec for formatTimingsMarkdown to
assert the full Markdown table structure: verify the presence of the top header
"# Workflow Timing", the table column header "| Step | Duration |", and the
separator row like "|------|----------|" (or equivalent dashes) in the result
string; keep the existing row and total assertions intact so the test ensures
both content and proper table formatting.

In `@tools/dev-workflow/platform/infra/workflow-execution/error-handler.ts`:
- Around line 16-18: The current call to writeFileSync when outputFilePath is
set can throw (e.g., ENOENT) and hide the original error; wrap the
writeFileSync(outputFilePath, jsonOutput, 'utf-8') call in a try/catch inside
the error handler so filesystem errors are caught and do not mask the original
error, and inside the catch log both the write error and the original error (or
at minimum the original error) to ensure diagnostics are preserved; reference
the existing symbols writeFileSync, outputFilePath and jsonOutput when locating
and updating the code.

---

Outside diff comments:
In `@tools/dev-workflow/platform/infra/workflow-execution/run-workflow.spec.ts`:
- Around line 228-243: The test currently uses vi.waitFor(() =>
expect(mockIO.writeFileCalls).toHaveLength(0)) which can pass before the
workflow runs; update the test to wait for workflow completion by awaiting the
mockIO.exitCode (e.g., await vi.waitFor(() =>
expect(mockIO.exitCode).toBeDefined() or a specific code) after calling
runWorkflow(…) and only then assert
expect(mockIO.writeFileCalls).toHaveLength(0); apply the same change to the
filter-based assertion that inspects mockIO.writeFileCalls (replace its
immediate waitFor check with first waiting for mockIO.exitCode and then
asserting the filtered result is empty).
- Around line 19-31: Both custom error classes TestExitSignal and
ContextBuildError need Error.captureStackTrace called in their constructors to
produce proper stack traces; update each constructor to call
Error.captureStackTrace(this, TestExitSignal) and Error.captureStackTrace(this,
ContextBuildError) respectively (after super(...)) and keep the name assignments
as-is so they comply with the project's coding guidelines for custom errors.
- Around line 304-403: Replace the direct Object.defineProperty overrides for
process.exit and process.stdout.write with vi.spyOn-based mocks (e.g.,
vi.spyOn(process, 'exit').mockImplementation(mockExit) and
vi.spyOn(process.stdout, 'write').mockImplementation(mockWrite)) and move that
setup/teardown into a nested describe with beforeEach to create the mocks and
afterEach to call vi.restoreAllMocks() (or explicitly mockRestore) so mocks are
always cleaned even if vi.waitFor/assertions throw; update both tests to use the
shared describe and remove the duplicated Object.defineProperty blocks
(referencing process.exit and process.stdout.write in the changes).

---

Duplicate comments:
In `@packages/riviere-extract-config/README.md`:
- Line 14: The README example config only defines api, so
isValidExtractionConfig(config) (and the typed ExtractionConfig/ModuleConfig)
will fail if the schema requires all six component types; update the example
module object in the modules array to include useCase, domainOp, event,
eventHandler, and ui (or eventPublisher if present in the schema) with each set
to { notUsed: true } alongside the existing api entry so the config validates
and the "config is typed as ExtractionConfig" comment is correct.

In `@tools/dev-workflow/platform/infra/workflow-execution/error-handler.spec.ts`:
- Around line 35-49: The test setup in beforeEach should retain
vi.clearAllMocks() to reset mock call counts, continue defining process.exit and
process.stderr.write using mockProcessExit and mockStderrWrite via
Object.defineProperty, and keep the vi.spyOn(console, 'error') mock that pushes
messages into capturedOutput; ensure these exact symbols (beforeEach,
vi.clearAllMocks, mockProcessExit, mockStderrWrite, console.error) remain
present and are not removed or replaced so test isolation and captured logging
behavior are preserved.

Comment thread tools/dev-workflow/platform/infra/cli/format-timings.spec.ts
Comment thread tools/dev-workflow/platform/infra/cli/format-timings.spec.ts Outdated
- Add per-module argument routing assertions to enrich test
- Add structural markdown assertions to format-timings tests
- Add empty stepTimings edge case test
- Wrap writeFileSync in try/catch in error handler

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@sonarqubecloud

Copy link
Copy Markdown

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

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

Inline comments:
In `@packages/riviere-cli/src/features/extract/domain/enrich-per-module.spec.ts`:
- Line 100: The test "enriches drafts grouped by module" currently only asserts
expect(result.components).toHaveLength(2), which doesn't verify actual component
values; update the assertion to verify content matches the mocked components ({
name: 'CompA', domain: 'orders' } and { name: 'CompB', domain: 'shipping' }) by
asserting on result.components' values (e.g.,
expect(result.components).toEqual([...]) or check result.components.map(c =>
c.name) includes 'CompA' and 'CompB' / use expect.arrayContaining) so the test
fails if both entries are the same; refer to result.components, CompA and CompB
when changing the assertion.

In `@tools/dev-workflow/platform/infra/cli/format-timings.spec.ts`:
- Around line 7-21: Add an assertion to the existing test that verifies the
formatted row for the boundary value 1000ms renders as the expected human
string: call formatTimingsMarkdown (already used) with the step { name: 'step1',
durationMs: 1000 } and assert that the returned result contains the table row
text for step1 with the expected duration (e.g., verify the string contains a
row like '| step1 | 1s |' or otherwise the precise formatted value your
formatter produces) so the ms/s boundary is covered.

In `@tools/dev-workflow/platform/infra/workflow-execution/error-handler.ts`:
- Around line 24-27: The current two-step flush+exit uses
console.error(jsonOutput) followed by process.stderr.write('', () =>
process.exit(1)), which is unclear; change it to write the actual jsonOutput
directly to stderr and exit in the write callback (e.g. call
process.stderr.write(jsonOutput + '\n', () => process.exit(1))) so the intent
(flush stderr then exit) is explicit and you can remove the console.error
call—update references to jsonOutput and process.stderr.write accordingly.

Comment thread tools/dev-workflow/platform/infra/cli/format-timings.spec.ts
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.

Extraction tool should discover per-module tsconfig files, not just root tsconfig

1 participant