Skip to content

Allow context roots to be declared in .impeccable/config.json (decoupled from package managers)#307

Open
CypherPoet wants to merge 1 commit into
pbakaus:mainfrom
CypherPoet:feat/decoupled-context-roots
Open

Allow context roots to be declared in .impeccable/config.json (decoupled from package managers)#307
CypherPoet wants to merge 1 commit into
pbakaus:mainfrom
CypherPoet:feat/decoupled-context-roots

Conversation

@CypherPoet

@CypherPoet CypherPoet commented Jun 25, 2026

Copy link
Copy Markdown

Why

After reading #300, I initially assumed that I could drop a DESIGN.md or PRODUCT.md into any folder of my project and have Impeccable treat it as a "monorepo app". Digging in, that capability is tightly coupled to package-manager configuration: a repo is only recognized as a monorepo when package.json workspaces, pnpm-workspace.yaml, or lerna.json (or a marker file beside apps//packages/) declares the workspaces.

This PR closes that gap.

What

Adds a contextRoots glob list to .impeccable/config.json (shared) and .impeccable/config.local.json (per-developer):

{ "contextRoots": ["docs/design/throwback-jerseys/*"] }

With it, a repo containing no package-manager files is still recognized as a monorepo, and each declared child resolves its own PRODUCT.md/DESIGN.md with per-file fallback to the root -- identical to how package-manager workspaces already behave. Same glob syntax as workspaces, including ! negation, and it composes with package-manager workspaces rather than replacing them.

How

One source added to the existing aggregator — everything else is unchanged, because detection (isMonorepoRoot), project resolution (resolveWorkspaceProjectRoot), and the app picker (discoverTargetCandidates) all already funnel through readWorkspacePatterns():

function readWorkspacePatterns(repoRoot) {
  return [
    ...readImpeccableContextRoots(repoRoot),   // new
    ...readPackageWorkspaces(repoRoot),
    ...readPnpmWorkspaces(repoRoot),
    ...readLernaWorkspaces(repoRoot),
  ].filter(Boolean);
}

readImpeccableContextRoots() reads contextRoots from .impeccable/config.json then config.local.json (per-dev extends shared). An .impeccable config without a contextRoots key changes nothing.

Tests

5 new cases in tests/context.test.mjs (full suite green — 63 pass, 0 fail):

  • detection with no package-manager files present
  • --target resolution and run-from-inside-the-folder resolution
  • config.local.json extending shared contextRoots
  • no regression for an .impeccable config without contextRoots (stays single-project)
  • the root app-picker listing config-declared children

Generated provider output regenerated with bun run build:skills:release.

Open Question

.impeccable/config.json property naming: I went with contextRoots, but we could also mirror workspaces -- or be more clear that these are additionalContextRoots. I'm open to any preference.


Note

Low Risk
Localized change to context path resolution with thorough tests; no auth, security, or data-path changes beyond how PRODUCT/DESIGN files are discovered.

Overview
Repos can be treated as Impeccable monorepos without package-manager workspace files by listing glob patterns in contextRoots on .impeccable/config.json (and extra patterns on config.local.json).

Those globs are merged into the existing readWorkspacePatterns() pipeline, so monorepo detection, --target / cwd project resolution, per-child PRODUCT.md/DESIGN.md with root fallback, and the root app picker behave the same as for npm/pnpm/lerna workspaces. An .impeccable config with no contextRoots is unchanged.

Five tests cover detection without package managers, target and in-folder resolution, local config extension, non-monorepo regression, and selection listing.

Reviewed by Cursor Bugbot for commit 944c97f. Bugbot is set up for automated code reviews on this repo. Configure here.

@CypherPoet CypherPoet requested a review from pbakaus as a code owner June 25, 2026 13:09
@greptile-apps

greptile-apps Bot commented Jun 25, 2026

Copy link
Copy Markdown

Greptile Summary

Adds contextRoots glob support to .impeccable/config.json and .impeccable/config.local.json so repos with no package-manager files can still declare monorepo child apps. One new function (readImpeccableContextRoots) is prepended to the existing readWorkspacePatterns aggregator; all downstream consumers — isMonorepoRoot, resolveWorkspaceProjectRoot, and discoverTargetCandidates — pick up the new patterns without any changes.

  • New source: readImpeccableContextRoots reads both config files in order and appends valid string patterns; config.local.json patterns stack on top of config.json rather than replacing them, enabling per-developer extension.
  • Tests: Five new cases in tests/context.test.mjs cover config-only monorepo detection, --target and cwd-based resolution, local-config extension, the no-op baseline (no contextRoots key), and root app-picker listing — all against a scratch directory with no package-manager files present.
  • Generated harness files: correctly absent from this PR; CI (sync-generated-output.yml) will propagate the source change after merge.

Confidence Score: 5/5

Safe to merge — the change is additive and confined to a single new function prepended to an existing aggregator; all downstream workspace consumers are untouched.

The new readImpeccableContextRoots function follows the exact same pattern as the three existing readers, handles error cases consistently (via readJson returning null), and the discoverTargetCandidates Map deduplication means duplicate patterns between config.json and config.local.json cause no user-visible harm. Five targeted tests verify the happy path, the extension path, the no-op path, and the app-picker integration with no package-manager files present.

No files require special attention — both changed files are straightforward source and test additions.

Important Files Changed

Filename Overview
skill/scripts/context.mjs Adds readImpeccableContextRoots prepended to readWorkspacePatterns; integrates cleanly with all downstream consumers (isMonorepoRoot, discoverTargetCandidates, resolveWorkspaceProjectRoot) without any changes to those callsites.
tests/context.test.mjs Five new test cases cover config-only monorepo detection, --target and cwd resolution, local config extension, no-op without contextRoots, and app-picker listing; all scenarios look correct and complete.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[readWorkspacePatterns] --> B[readImpeccableContextRoots NEW]
    A --> C[readPackageWorkspaces]
    A --> D[readPnpmWorkspaces]
    A --> E[readLernaWorkspaces]

    B --> B1[Read .impeccable/config.json]
    B --> B2[Read .impeccable/config.local.json]
    B1 -->|contextRoots array| BM[Merge patterns]
    B2 -->|contextRoots array| BM

    BM --> F[Combined patterns array]
    C --> F
    D --> F
    E --> F

    F --> G{Caller}
    G -->|isMonorepoRoot| H[Has any non-negated pattern - isMonorepo=true]
    G -->|resolveWorkspaceProjectRoot| I[First matching pattern - projectRoot]
    G -->|discoverTargetCandidates| J[Map keyed by rel path - deduplicated app list]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[readWorkspacePatterns] --> B[readImpeccableContextRoots NEW]
    A --> C[readPackageWorkspaces]
    A --> D[readPnpmWorkspaces]
    A --> E[readLernaWorkspaces]

    B --> B1[Read .impeccable/config.json]
    B --> B2[Read .impeccable/config.local.json]
    B1 -->|contextRoots array| BM[Merge patterns]
    B2 -->|contextRoots array| BM

    BM --> F[Combined patterns array]
    C --> F
    D --> F
    E --> F

    F --> G{Caller}
    G -->|isMonorepoRoot| H[Has any non-negated pattern - isMonorepo=true]
    G -->|resolveWorkspaceProjectRoot| I[First matching pattern - projectRoot]
    G -->|discoverTargetCandidates| J[Map keyed by rel path - deduplicated app list]
Loading

Reviews (4): Last reviewed commit: "Allow context roots to be declared in .i..." | Re-trigger Greptile

@CypherPoet CypherPoet force-pushed the feat/decoupled-context-roots branch from 5986161 to d044e3a Compare June 25, 2026 13:32
Monorepo detection previously read workspace roots only from package
managers (package.json workspaces, pnpm-workspace.yaml, lerna.json),
coupling "where design context lives" to the dependency graph. Add a
`contextRoots` glob list to .impeccable/config.json / config.local.json
so non-JS repos -- and design-context boundaries that don't match
packages -- can declare nested PRODUCT.md/DESIGN.md roots directly.

The new source is folded into readWorkspacePatterns(), so detection,
project resolution, and the app picker pick it up unchanged. Negation
and config.local.json extension work for free.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant