Skip to content

feat: add monorepo workspace configuration and auto-detection#23

Merged
digitarald merged 2 commits intomainfrom
feat/workspace-config
Mar 2, 2026
Merged

feat: add monorepo workspace configuration and auto-detection#23
digitarald merged 2 commits intomainfrom
feat/workspace-config

Conversation

@digitarald
Copy link
Collaborator

Summary

Adds workspaces support to agentrc.config.json for monorepo projects, enabling architects to define sub-project boundaries with scoped areas, auto-detection, and eval session scoping.

Changes

Configuration (agentrc.config.json)

  • New workspaces array: { name, path, areas[] } — groups areas under a sub-project directory
  • Workspace area applyTo patterns are relative to the workspace path
  • Full validation: rejects traversal, absolute paths, root ., malformed entries
  • parseConfigAreas() extracted as shared validator for flat and workspace areas
  • parentArea validation spans both flat and workspace areas

Auto-detection (detectWorkspaces())

  • Strategy 1: Scans for .vscode folders as workspace markers (depth 3)
  • Strategy 2: Groups sibling auto-detected areas by common parent directory (2+ siblings)
  • Exported for use in CLI and VS Code extension

Init command

  • Bootstraps agentrc.config.json with detected workspaces and standalone areas
  • Path-based deduplication (areas inside a workspace are excluded from standalone list)

Eval integration

  • EvalCase.workingDirectory — per-case workspace scoping
  • askOnce() passes workingDirectory to Copilot SDK createSession()
  • Boundary validation prevents directory escape

Area model

  • Area.workingDirectory — set for workspace areas, used in eval and instruction generation
  • Workspace areas get namespaced names (frontend/app)

Documentation

  • README: new Configuration File section with schema and examples
  • examples/agentrc.config.json: full monorepo example
  • docs/plugins.md: cross-reference to config example

Tests

  • 16 new tests across 4 describe blocks covering config parsing, workspace detection, area flattening, and edge cases (traversal, malformed entries, path .)

Verification

  • npm run typecheck — clean
  • npm run lint — clean
  • npm run test — 524/524 pass
  • Extension build + typecheck — clean

Copilot AI review requested due to automatic review settings March 2, 2026 16:40
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds monorepo workspace support to AgentRC’s config/analysis pipeline so areas can be grouped under workspace roots, auto-detected, and used to scope eval sessions and generated artifacts across both the CLI and VS Code extension.

Changes:

  • Extend agentrc.config.json schema with workspaces[] and add workspace auto-detection (detectWorkspaces()).
  • Flatten workspace areas into namespaced Area entries and propagate workspace scoping into eval scaffolding + eval runs.
  • Update init to bootstrap an agentrc.config.json with detected workspaces/areas and document the new configuration in README/examples/docs.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
vscode-extension/src/types.ts Re-exports new config/workspace types to keep extension aligned with CLI types.
vscode-extension/src/services.ts Re-exports detectWorkspaces for extension feature reuse via the CLI service layer.
src/services/evaluator.ts Adds per-eval-case workingDirectory support and passes it into Copilot SDK sessions with boundary checks.
src/services/evalScaffold.ts Adds workingDirectory to eval case schema and scaffolding prompt guidance for workspace-scoped evals.
src/services/analyzer.ts Implements workspace config parsing, workspace area flattening, and detectWorkspaces() logic.
src/services/tests/analyzer.test.ts Adds tests for workspace config parsing, workspace detection, and workspace area flattening.
src/commands/init.ts Bootstraps agentrc.config.json based on detected workspaces + standalone areas.
examples/agentrc.config.json Adds a monorepo example config demonstrating areas, workspaces, and policies.
examples/README.md Links to the new sample project config.
docs/plugins.md Cross-references the new monorepo config example.
README.md Documents workspace configuration and init auto-bootstrapping behavior.
Comments suppressed due to low confidence (1)

src/services/analyzer.ts:1329

  • parentArea validation is currently done against a single global set of un-namespaced area names aggregated from all workspaces (and flat areas). If two workspaces contain the same area name, a workspace area can incorrectly “validate” a parentArea that only exists in a different workspace, and then later flattening/namespacing will still produce a broken reference.

Consider validating workspace-area parentArea references within the same workspace’s areas (or validating against the post-namespacing names) to avoid cross-workspace name collisions.

    // Validate parentArea references across all areas (flat + workspace)
    const allConfigAreas = [...areas];
    for (const ws of workspaces) {
      allConfigAreas.push(...ws.areas);
    }
    const areaNames = new Set(allConfigAreas.map((a) => a.name.toLowerCase()));
    for (const area of allConfigAreas) {
      if (area.parentArea && !areaNames.has(area.parentArea.toLowerCase())) {
        area.parentArea = undefined;

@digitarald digitarald marked this pull request as ready for review March 2, 2026 17:41
@digitarald digitarald requested a review from pierceboggan as a code owner March 2, 2026 17:41
Copilot AI review requested due to automatic review settings March 2, 2026 17:41
@digitarald digitarald merged commit 86d3e08 into main Mar 2, 2026
10 checks passed
@digitarald digitarald deleted the feat/workspace-config branch March 2, 2026 17:41
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +1146 to +1153
if (depth >= maxDepth) return [];
const results: string[] = [];
let entries: string[];
try {
entries = await safeReadDir(dir);
} catch {
return [];
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

findVSCodeDirs() stops scanning when depth >= maxDepth, which means with WORKSPACE_SCAN_MAX_DEPTH = 3 it never inspects directories at depth 3 (only 0–2). That makes the “depth 3” behavior in the PR description effectively off by one and can miss .vscode markers in deeper nested workspaces. Consider allowing scanning at depth === maxDepth and only preventing recursion beyond the limit. Also, the try/catch around safeReadDir() is redundant because safeReadDir already swallows errors and returns an empty array.

Suggested change
if (depth >= maxDepth) return [];
const results: string[] = [];
let entries: string[];
try {
entries = await safeReadDir(dir);
} catch {
return [];
}
if (depth > maxDepth) return [];
const results: string[] = [];
const entries = await safeReadDir(dir);

Copilot uses AI. Check for mistakes.
Comment on lines +1243 to +1246
if (allPatterns.some((p) => p.split("/").includes(".."))) continue;
areas.push({
name: e.name as string,
applyTo,
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

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

parseConfigAreas() validates that applyTo is a string or string[], but it does not actually normalize/trim the stored patterns. This allows entries like applyTo: " docs/**" or applyTo: ["src/**", " "] to be accepted, which can produce broken/ineffective glob matching downstream (frontmatter, area detection, etc.). Suggest trimming each pattern (and filtering/rejecting empty patterns) before pushing the area.

Suggested change
if (allPatterns.some((p) => p.split("/").includes(".."))) continue;
areas.push({
name: e.name as string,
applyTo,
const normalizedPatterns = allPatterns.map((p) => p.trim()).filter((p) => p.length > 0);
if (normalizedPatterns.length === 0) continue;
if (normalizedPatterns.some((p) => p.split("/").includes(".."))) continue;
areas.push({
name: e.name as string,
applyTo: Array.isArray(applyTo) ? normalizedPatterns : normalizedPatterns[0],

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants