Skip to content

test(cli): enforce initial layer import boundaries#2986

Merged
cv merged 86 commits intomainfrom
test/layer-import-boundaries
May 6, 2026
Merged

test(cli): enforce initial layer import boundaries#2986
cv merged 86 commits intomainfrom
test/layer-import-boundaries

Conversation

@cv
Copy link
Copy Markdown
Contributor

@cv cv commented May 4, 2026

Summary

Add a narrow layer-boundary guard for the new CLI directory structure before moving action/state/adapter modules further.

Stack Navigation

Changes

  • Added scripts/check-layer-import-boundaries.ts to validate initial import-boundary rules.
  • Added a Vitest smoke test that runs the guard without source-shape assertions.
  • Enforces domain purity, no oclif imports from actions, no command/action imports from adapters, and one registered oclif command class per production command file.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files --stage pre-push passes
  • npx tsx scripts/check-layer-import-boundaries.ts
  • npx vitest run test/layer-import-boundaries.test.ts
  • npm run source-shape:check
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • Chores
    • Updated pre-commit hook to improve formatting behavior during commits.
    • Added automated validation to enforce code architecture layer boundaries and prevent unintended cross-layer imports.
  • Tests
    • Added an automated test to verify the new layer-boundary validation runs successfully and reports passing status.

cv added 30 commits May 2, 2026 13:36
@wscurran wscurran added the refactor This is a refactor of the code and/or architecture. label May 4, 2026
@cv cv requested a review from prekshivyas May 5, 2026 00:27
@cv cv added the v0.0.35 Release target label May 5, 2026
@prekshivyas prekshivyas self-assigned this May 5, 2026
@cjagwani cjagwani assigned cjagwani and unassigned cjagwani May 5, 2026
cv added a commit that referenced this pull request May 6, 2026
## Summary
Move OpenShell-facing adapter modules out of the flat `src/lib/`
directory into `src/lib/adapters/openshell/**` so process/runtime
boundaries are visible in paths.

## Stack Navigation
- Position: 55 of 60
- Previous PR: [#2984 — refactor(cli): group pure domain
helpers](#2984)
- Next PR: [#2986 — test(cli): enforce initial layer import
boundaries](#2986)

## Changes
- Moved OpenShell command helpers to
`src/lib/adapters/openshell/client.ts`.
- Moved OpenShell runtime wrapper helpers to
`src/lib/adapters/openshell/runtime.ts`.
- Moved OpenShell binary resolution and timeout constants to
`resolve.ts` and `timeouts.ts`.
- Moved associated tests and updated action, command, and
integration-test imports.
- Kept behavior unchanged; this PR is a structural move plus import
updates.

## Type of Change
- [x] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Verification
- [x] `npx prek run --all-files --stage pre-push` passes
- [x] `npm run build:cli`
- [x] `npm run typecheck:cli`
- [x] OpenShell adapter targeted tests pass
- [x] `npm run ts-migration:guard -- --base
origin/refactor/layer-domain-helpers --head HEAD`
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the style guide (doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Carlos Villela <cvillela@nvidia.com>

---------

Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Co-authored-by: Prekshi Vyas <34834085+prekshivyas@users.noreply.github.com>
@cv cv marked this pull request as ready for review May 6, 2026 03:12
@cv cv changed the base branch from refactor/layer-openshell-adapters to main May 6, 2026 03:12
@cv cv enabled auto-merge (squash) May 6, 2026 03:12
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@ericksoa ericksoa added v0.0.36 Release target and removed v0.0.35 Release target labels May 6, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
scripts/check-layer-import-boundaries.ts (1)

306-308: 💤 Low value

Consider cross-platform compatible execution guard.

The current pattern import.meta.url === \file://${process.argv[1]}` may not match correctly on Windows due to path separator differences (/vs`). A more robust approach uses fileURLToPath:

♻️ Suggested cross-platform fix
-if (import.meta.url === `file://${process.argv[1]}`) {
+if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
   main();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-layer-import-boundaries.ts` around lines 306 - 308, Replace the
fragile URL string comparison with a cross-platform file path comparison:
convert import.meta.url to a filesystem path using fileURLToPath and
normalize/resolve process.argv[1] (using path.resolve or equivalent) and then
compare those values before calling main(); update the guard around main() (the
import.meta.url === `file://${process.argv[1]}` check) to use
fileURLToPath(import.meta.url) and a resolved process argv path so it works on
Windows and POSIX.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/check-layer-import-boundaries.ts`:
- Around line 306-308: Replace the fragile URL string comparison with a
cross-platform file path comparison: convert import.meta.url to a filesystem
path using fileURLToPath and normalize/resolve process.argv[1] (using
path.resolve or equivalent) and then compare those values before calling main();
update the guard around main() (the import.meta.url ===
`file://${process.argv[1]}` check) to use fileURLToPath(import.meta.url) and a
resolved process argv path so it works on Windows and POSIX.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ee39c540-f200-4fbf-8b80-f8a235a7a5d2

📥 Commits

Reviewing files that changed from the base of the PR and between 2d6dd4d and 3b6e5d2.

📒 Files selected for processing (3)
  • .pre-commit-config.yaml
  • scripts/check-layer-import-boundaries.ts
  • test/layer-import-boundaries.test.ts

@cv
Copy link
Copy Markdown
Contributor Author

cv commented May 6, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-layer-import-boundaries.ts`:
- Around line 267-275: The current check only errors when commandClassCount > 1,
allowing zero-command files to slip through; update the logic around
commandClassCount (the rule labeled "one-command-per-file") to enforce exactly
one command class by replacing the condition with a strict equality check (e.g.,
commandClassCount !== 1) and update the addViolation message to reflect "must
define exactly one registered oclif command class; found X". If certain helper
files under src/lib/commands are intentionally allowed to have zero commands,
add an explicit exemption (e.g., by file name or a helper directory pattern)
before the commandClassCount check so those files are skipped instead of
weakening the rule.
- Around line 83-89: The call-expression visitor currently only handles
identifier-based calls (ts.isIdentifier(node.expression)) so dynamic import(...)
expressions are skipped; update the condition around ts.isCallExpression to also
accept when node.expression is an Import keyword (check node.expression.kind ===
ts.SyntaxKind.ImportKeyword or use ts.isImportExpression/ts.isImportCall where
available), and then ensure you still verify node.arguments.length > 0 and
ts.isStringLiteralLike(node.arguments[0]) before calling
add(node.arguments[0].text, node.arguments[0]); this change touches the same
call-expression check that uses ts.isCallExpression, ts.isIdentifier,
node.expression, ts.SyntaxKind.ImportKeyword, and the add(...) invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3faea048-1978-42cf-9dc3-f14f8eb450f5

📥 Commits

Reviewing files that changed from the base of the PR and between 3b6e5d2 and b0b5f3a.

📒 Files selected for processing (1)
  • scripts/check-layer-import-boundaries.ts

Comment thread scripts/check-layer-import-boundaries.ts
Comment thread scripts/check-layer-import-boundaries.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/check-layer-import-boundaries.ts`:
- Around line 73-90: The visitor in collectImportRefs (function visit) misses
TypeScript's ImportEqualsDeclaration form; update visit to detect
ts.isImportEqualsDeclaration(node) and when node.moduleReference is an
ExternalModuleReference with a string literal expression extract that module
string and call add(moduleText, the string literal node) so import x =
require("...") references are collected like other import/require forms.
- Around line 257-259: The predicate that identifies command classes currently
treats any heritage clause (including implements) as matching; update the check
in the class detection logic (the ts.isClassDeclaration branch that iterates
node.heritageClauses and uses isCommandBase) to only consider extends clauses by
adding a filter that clause.token === ts.SyntaxKind.ExtendsKeyword before
calling clause.types.some(isCommandBase), so only classes that actually extend
the Command base are counted as commands.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ca040331-bb60-4f38-aecb-454879690a64

📥 Commits

Reviewing files that changed from the base of the PR and between b0b5f3a and f9e6ff6.

📒 Files selected for processing (1)
  • scripts/check-layer-import-boundaries.ts

Comment on lines +73 to +90
function visit(node: ts.Node): void {
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
add(node.moduleSpecifier.text, node.moduleSpecifier);
} else if (
ts.isExportDeclaration(node) &&
node.moduleSpecifier &&
ts.isStringLiteral(node.moduleSpecifier)
) {
add(node.moduleSpecifier.text, node.moduleSpecifier);
} else if (
ts.isCallExpression(node) &&
((ts.isIdentifier(node.expression) && node.expression.text === "require") ||
node.expression.kind === ts.SyntaxKind.ImportKeyword) &&
node.arguments.length > 0 &&
ts.isStringLiteralLike(node.arguments[0])
) {
add(node.arguments[0].text, node.arguments[0]);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and see its overall structure
fd scripts/check-layer-import-boundaries.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Let's also get the line count to understand file size
wc -l scripts/check-layer-import-boundaries.ts

Repository: NVIDIA/NemoClaw

Length of output: 104


🏁 Script executed:

# Now let's examine the collectImportRefs function around lines 73-90
sed -n '60,100p' scripts/check-layer-import-boundaries.ts

Repository: NVIDIA/NemoClaw

Length of output: 1499


🏁 Script executed:

# Let's also check if ImportEqualsDeclaration is mentioned anywhere in the file
rg "ImportEqualsDeclaration" scripts/check-layer-import-boundaries.ts

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Check if there's any configuration (tsconfig.json, biome.json) that might restrict import forms
fd -e json -e js | grep -E "(biome|tsconfig)" | head -20

Repository: NVIDIA/NemoClaw

Length of output: 162


🏁 Script executed:

# Check the biome.json configuration
cat biome.json

Repository: NVIDIA/NemoClaw

Length of output: 3139


🏁 Script executed:

# Check tsconfig files to see if there are any restrictions
cat tsconfig.cli.json

Repository: NVIDIA/NemoClaw

Length of output: 628


🏁 Script executed:

# Search the codebase for any use of "import ... = require"
rg "import\s+\w+\s*=\s*require" --type ts --type js

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Check if there are any tests for the check-layer-import-boundaries script
fd check-layer-import-boundaries

Repository: NVIDIA/NemoClaw

Length of output: 100


🏁 Script executed:

# Let's also check if this import form is used in the src directory at all
rg "import\s+\w+\s*=\s*require" src/ --type ts --type js

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Let's check the SPDX header compliance for this file
head -5 scripts/check-layer-import-boundaries.ts

Repository: NVIDIA/NemoClaw

Length of output: 304


🏁 Script executed:

# Let's verify the suggested diff is syntactically correct by checking TypeScript AST types
# Create a simple test to check if ImportEqualsDeclaration and ExternalModuleReference exist
python3 << 'EOF'
# The suggestion uses:
# ts.isImportEqualsDeclaration(node)
# ts.isExternalModuleReference(node.moduleReference)
# node.moduleReference.expression
# ts.isStringLiteralLike(node.moduleReference.expression)

# These are legitimate TypeScript compiler API methods/properties
# Let's verify by searching for any existing usage patterns in TypeScript

print("Verifying TypeScript AST API for ImportEqualsDeclaration...")
print("The following are valid TypeScript compiler API:")
print("- ts.isImportEqualsDeclaration(node): Checks if node is 'import x = require(...)'")
print("- ts.isExternalModuleReference(): Checks if moduleReference is external")
print("- ts.isStringLiteralLike(): Matches string literals and template literals")
print("\nSuggested fix appears syntactically correct for TypeScript Compiler API.")
EOF

Repository: NVIDIA/NemoClaw

Length of output: 470


🏁 Script executed:

# Let's check if there are any tests or comments in the file that mention ImportEqualsDeclaration
cat -n scripts/check-layer-import-boundaries.ts | grep -A 5 -B 5 -i "importequals\|import.*require"

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Check what import forms are actually used in src/lib (the directory being checked)
rg "^import\s" src/lib --type ts | head -20

Repository: NVIDIA/NemoClaw

Length of output: 1611


🏁 Script executed:

# Also check for any require patterns in src/lib
rg "require\(" src/lib --type ts | head -20

Repository: NVIDIA/NemoClaw

Length of output: 1538


Handle import foo = require("...") in the collector.

collectImportRefs() currently handles ES6 imports, exports, require() calls, and dynamic import(), but misses TypeScript's import x = require("...") form (ImportEqualsDeclaration). While this form is not currently used in the codebase, it remains a valid TypeScript import syntax that would bypass all boundary checks if introduced. The suggested fix is both correct and straightforward.

Suggested change
   function visit(node: ts.Node): void {
     if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
       add(node.moduleSpecifier.text, node.moduleSpecifier);
     } else if (
       ts.isExportDeclaration(node) &&
       node.moduleSpecifier &&
       ts.isStringLiteral(node.moduleSpecifier)
     ) {
       add(node.moduleSpecifier.text, node.moduleSpecifier);
+    } else if (
+      ts.isImportEqualsDeclaration(node) &&
+      ts.isExternalModuleReference(node.moduleReference) &&
+      node.moduleReference.expression &&
+      ts.isStringLiteralLike(node.moduleReference.expression)
+    ) {
+      add(node.moduleReference.expression.text, node.moduleReference.expression);
     } else if (
       ts.isCallExpression(node) &&
       ((ts.isIdentifier(node.expression) && node.expression.text === "require") ||
         node.expression.kind === ts.SyntaxKind.ImportKeyword) &&
       node.arguments.length > 0 &&
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-layer-import-boundaries.ts` around lines 73 - 90, The visitor
in collectImportRefs (function visit) misses TypeScript's
ImportEqualsDeclaration form; update visit to detect
ts.isImportEqualsDeclaration(node) and when node.moduleReference is an
ExternalModuleReference with a string literal expression extract that module
string and call add(moduleText, the string literal node) so import x =
require("...") references are collected like other import/require forms.

Comment on lines +257 to +259
if (
ts.isClassDeclaration(node) &&
node.heritageClauses?.some((clause) => clause.types.some(isCommandBase))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n scripts/check-layer-import-boundaries.ts | sed -n '250,270p'

Repository: NVIDIA/NemoClaw

Length of output: 767


🏁 Script executed:

cat -n scripts/check-layer-import-boundaries.ts | sed -n '240,280p'

Repository: NVIDIA/NemoClaw

Length of output: 1521


🏁 Script executed:

# Let's verify the TypeScript API for heritage clauses
# Check if there are examples of ts.SyntaxKind.ExtendsKeyword usage in the codebase
rg "SyntaxKind\." scripts/ -A 2 -B 2 | head -50

Repository: NVIDIA/NemoClaw

Length of output: 3099


🏁 Script executed:

# Search for heritage clause usage in TypeScript API
rg "heritageClauses" scripts/ -B 3 -A 3

Repository: NVIDIA/NemoClaw

Length of output: 555


🌐 Web query:

TypeScript heritage clause token extends keyword kind property

💡 Result:

Answer: In the TypeScript compiler API, the “extends”/“implements” keyword in a heritage clause is represented by the HeritageClause node’s token property, whose type is a SyntaxKind value (specifically SyntaxKind.ExtendsKeyword or SyntaxKind.ImplementsKeyword). token is not the kind property you’re asking about. Grounding from the sources: - A HeritageClause interface in the compiler API exposes a token property whose value is a SyntaxKind.ExtendsKeyword | SyntaxKind.ImplementsKeyword [1]. - The TypeScript compiler’s SyntaxKind includes ExtendsKeyword as the keyword kind for extends [2]. - A separate API surface (tsserver typings / services) for HeritageClauseSyntax similarly exposes a kind (node kind) and an extendsOrImplementsKeyword token, but that’s distinct from the heritage clause “token kind” you’re asking about in the compiler API [3]. Practical example (compiler API): - For class/interface declarations, iterate node.heritageClauses and check hc.token === ts.SyntaxKind.ExtendsKeyword to find extends clauses; then inspect hc.types for the base types [1]. If you tell me which library API you’re using (raw typescript compiler API, ts-morph, or TypeScript’s language-service node types), I can provide the exact property names/types for that surface.

Citations:


🏁 Script executed:

# Look for tests related to checkCommandFile or one-command-per-file
find . -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "one-command-per-file\|checkCommandFile" 2>/dev/null

Repository: NVIDIA/NemoClaw

Length of output: 43


🏁 Script executed:

# Search for test files related to check-layer-import-boundaries
fd -e test.ts -e spec.ts | xargs grep -l "check-layer-import-boundaries\|layer.*import" 2>/dev/null | head -5

Repository: NVIDIA/NemoClaw

Length of output: 96


🏁 Script executed:

cat -n test/layer-import-boundaries.test.ts | head -100

Repository: NVIDIA/NemoClaw

Length of output: 1075


🏁 Script executed:

# Search for actual command files that might inherit from Command or NemoClawCommand
find . -path ./node_modules -prune -o -name "*.ts" -type f -print | xargs grep -l "extends Command\|extends NemoClawCommand\|implements Command" 2>/dev/null | head -5

Repository: NVIDIA/NemoClaw

Length of output: 247


🏁 Script executed:

# Check one of the command files to see the pattern
cat -n ./src/lib/commands/credentials.ts | head -30

Repository: NVIDIA/NemoClaw

Length of output: 1136


🏁 Script executed:

# Search for implements Command (case-insensitive)
rg "implements\s+.*Command" --type ts | head -20

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Let's look for any helper classes or interfaces that might implement or extend Command
rg "class.*implements.*|interface.*extends.*" --type ts | grep -i command | head -20

Repository: NVIDIA/NemoClaw

Length of output: 172


🏁 Script executed:

# Let's verify the TypeScript API once more by checking if there's a ts.HeritageClause type definition being used
rg "HeritageClause" --type ts | head -10

Repository: NVIDIA/NemoClaw

Length of output: 41


Only count extends clauses as command classes.

The current predicate treats implements Command the same as extends Command, so a helper class can satisfy the exact-one rule without actually being a registered oclif command. Filter on clause.token === ts.SyntaxKind.ExtendsKeyword before checking the base type.

Suggested change
     if (
       ts.isClassDeclaration(node) &&
-      node.heritageClauses?.some((clause) => clause.types.some(isCommandBase))
+      node.heritageClauses?.some(
+        (clause) =>
+          clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.some(isCommandBase),
+      )
     ) {
       commandClassCount += 1;
     }
📝 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
if (
ts.isClassDeclaration(node) &&
node.heritageClauses?.some((clause) => clause.types.some(isCommandBase))
if (
ts.isClassDeclaration(node) &&
node.heritageClauses?.some(
(clause) =>
clause.token === ts.SyntaxKind.ExtendsKeyword && clause.types.some(isCommandBase),
)
) {
commandClassCount += 1;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-layer-import-boundaries.ts` around lines 257 - 259, The
predicate that identifies command classes currently treats any heritage clause
(including implements) as matching; update the check in the class detection
logic (the ts.isClassDeclaration branch that iterates node.heritageClauses and
uses isCommandBase) to only consider extends clauses by adding a filter that
clause.token === ts.SyntaxKind.ExtendsKeyword before calling
clause.types.some(isCommandBase), so only classes that actually extend the
Command base are counted as commands.

Copy link
Copy Markdown
Contributor

@cjagwani cjagwani left a comment

Choose a reason for hiding this comment

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

approved :)

@cv cv merged commit a3a32d6 into main May 6, 2026
16 checks passed
cv added a commit that referenced this pull request May 6, 2026
## Summary
Move sandbox-scoped workflow action modules into
`src/lib/actions/sandbox/**` so command adapters and sandbox workflow
orchestration have separate, visible layers.

## Stack Navigation
- Position: 57 of 60
- Previous PR: [#2986 — test(cli): enforce initial layer import
boundaries](#2986)
- Next PR: [#2988 — refactor(cli): group global
actions](#2988)

## Changes
- Moved sandbox
connect/destroy/rebuild/status/logs/doctor/gateway-state/process-recovery/skill/snapshot
actions under `src/lib/actions/sandbox/`.
- Moved sandbox policy/channel action helpers under
`src/lib/actions/sandbox/policy-channel.ts`.
- Updated command adapters, runtime dispatch facade, tests, and
remaining callers to the new paths.
- Kept behavior unchanged; this PR is a structural move plus import
updates.

## Type of Change
- [x] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Verification
- [x] `npx prek run --all-files --stage pre-push` passes
- [x] `npm run build:cli`
- [x] `npm run typecheck:cli`
- [x] `npx tsx scripts/check-layer-import-boundaries.ts`
- [x] Sandbox command adapter targeted tests pass
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the style guide (doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

---
Signed-off-by: Carlos Villela <cvillela@nvidia.com>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Enhanced sandbox cleanup during destruction to properly unload Ollama
models, terminate proxy processes, and stop host services for complete
resource cleanup.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD Use this label to identify issues with NemoClaw CI/CD pipeline or GitHub Actions. enhancement: testing Use this label to identify requests to improve NemoClaw test coverage. NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). refactor This is a refactor of the code and/or architecture. v0.0.36 Release target

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants