Skip to content

feat: wire manifest resolution into install execution#509

Merged
affaan-m merged 1 commit into
mainfrom
feat/install-pipeline
Mar 16, 2026
Merged

feat: wire manifest resolution into install execution#509
affaan-m merged 1 commit into
mainfrom
feat/install-pipeline

Conversation

@affaan-m

@affaan-m affaan-m commented Mar 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • wire scripts/lib/install-manifests.js resolution into the real scripts/install-apply.js execution flow so --profile <name> works end-to-end
  • add manifest-backed --modules <id,id,...> parsing and validation in the installer
  • normalize legacy language installs to mode: 'legacy-compat' with legacyLanguages, then translate them into manifest-backed module selections
  • skip target-incompatible dependency trees deterministically during resolution so profile installs remain usable on targets like antigravity
  • extend installer/request/manifest/ECC CLI tests to cover the new execution path and compatibility behavior

Issues

Testing

  • node tests/run-all.js
  • Result: 1149/1149 passing

Summary by cubic

Wires manifest resolution into the installer so --profile and manifest-backed --modules execute end-to-end, and adds a legacy compatibility flow that maps language installs to manifest modules with target-safe skipping. Closes #463, #464, #465.

  • New Features

    • Manifest plans now execute in scripts/install-apply.js via install-executor.
    • Added --modules <id,id,...> with dedupe and catalog validation.
    • Legacy languages normalize to mode: 'legacy-compat' with legacyLanguages, resolving to manifest modules (e.g., golanggo) and target-specific base sets.
    • Deterministic target filtering: skip incompatible dependency chains to keep profiles usable on antigravity.
    • New helpers: listLegacyCompatibilityLanguages, resolveLegacyCompatibilitySelection, validateInstallModuleIds, createLegacyCompatInstallPlan; expanded CLI and unit tests.
  • Migration

    • Do not mix legacy language args with --profile, --modules, --with, or --without (now a hard error).
    • Plan/state output now reports mode: 'legacy-compat' and legacyLanguages.
    • Unknown module IDs are rejected early with a clear error.

Written for commit b320fb3. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced "legacy-compat" installation mode for enhanced language support and module selection
    • Added explicit module selection capability during installation setup
  • Improvements

    • Enhanced validation for installation modules and language configurations
    • Improved handling of incompatible installation dependencies—now gracefully skips unsupported modules instead of failing
    • Better error reporting for unknown or invalid module and language selections

@coderabbitai

coderabbitai Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds comprehensive legacy compatibility support to the install system, introducing language-to-module mapping for legacy install arguments, explicit module selection via --modules CLI flag, and manifest-driven profile resolution. It establishes a new 'legacy-compat' mode with supporting functions and updates request/runtime handling accordingly.

Changes

Cohort / File(s) Summary
Legacy Compatibility Core
scripts/lib/install-manifests.js, scripts/lib/install-executor.js
Introduce legacy language-to-module mapping with new functions: listLegacyCompatibilityLanguages, resolveLegacyCompatibilitySelection, validateInstallModuleIds, and createLegacyCompatInstallPlan. Enhance module resolution with rootRequesterId tracking and extend listAvailableLanguages to include legacy languages.
Request & Runtime Handling
scripts/lib/install/request.js, scripts/lib/install/runtime.js
Add moduleIds validation and introduce legacyLanguages concept; shift mode naming from 'legacy' to 'legacy-compat'. Wire 'legacy-compat' mode into runtime to call createLegacyCompatInstallPlan with normalized selections.
Execution Integration
scripts/install-apply.js
Update to use listLegacyCompatibilityLanguages instead of listAvailableLanguages; support legacy-compat plan mode printing; defer loading of three modules (loadInstallConfig, applyInstallPlan, createInstallPlanFromRequest) into main function.
Test Coverage
tests/lib/install-manifests.test.js, tests/lib/install-request.test.js, tests/scripts/ecc.test.js, tests/scripts/install-apply.test.js
Comprehensive test additions for legacy language validation, module selection resolution, moduleIds parsing and validation, mode/field name updates (mode: 'legacy-compat', languages → legacyLanguages), and manifest-driven install path verification across all targets.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI Arguments
    participant Request as Request Parser
    participant Manifest as Manifest Loader
    participant Executor as Install Executor
    participant Runtime as Runtime Resolver
    
    CLI->>Request: Parse legacy languages & modules
    Request->>Request: Validate moduleIds via Manifests
    Request->>Request: Normalize to legacyLanguages
    Request-->>Runtime: Return {mode: 'legacy-compat', legacyLanguages}
    Runtime->>Executor: createLegacyCompatInstallPlan()
    Executor->>Manifest: resolveLegacyCompatibilitySelection()
    Manifest->>Manifest: Map languages to canonical form
    Manifest->>Manifest: Build base + extra module IDs
    Manifest-->>Executor: Return {languages, moduleIds}
    Executor->>Manifest: resolveInstallPlan()
    Manifest-->>Executor: Return manifest-driven install plan
    Executor-->>Runtime: Return install plan with legacy metadata
    Runtime-->>CLI: Execute file operations per plan
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰 A hop through languages, old paths made new,
Module maps and legacy dreams come true!
From python to golang, each finds its way,
Through manifest forests, the rabbit does play. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main change: integrating manifest resolution into the install execution pipeline.
Linked Issues check ✅ Passed All three linked issues are addressed: profile resolution wired into execution [#463], --modules flag implemented with validation [#464], legacy languages mapped to manifest modules with 'legacy-compat' mode [#465].
Out of Scope Changes check ✅ Passed All changes directly support the three linked objectives: manifest integration, --modules CLI flag, and legacy-to-module mapping. No out-of-scope modifications detected.

✏️ 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 feat/install-pipeline
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@greptile-apps

greptile-apps Bot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR wires install-manifests.js resolution into the real install-apply.js execution flow, replacing the old 'legacy' mode with a new 'legacy-compat' mode that maps legacy language arguments to manifest-backed module selections. It also adds --modules <id,...> validation against the real manifest catalog and makes the dependency resolver skip target-incompatible trees (e.g. antigravity) rather than throwing.

Key changes:

  • New resolveLegacyCompatibilitySelection: maps legacy language aliases to canonical module IDs via LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET + LEGACY_LANGUAGE_EXTRA_MODULE_IDS.
  • resolveModule now returns false instead of throwing when a dependency's target is unsupported, allowing profile installs to skip incompatible subtrees deterministically.
  • validateInstallModuleIds added to normalizeInstallRequest, so unknown --modules IDs are caught early at parse time.
  • Lazy require() wrappers (createStatePreview, applyInstallPlan) introduced in install-executor.js to break circular dependencies.
  • Two logic issues were found: (1) resolveLegacyCompatibilitySelection validates target against the full SUPPORTED_INSTALL_TARGETS list but its configuration table only covers 3 targets, causing a silent claude-fallback for codex/opencode; (2) when a module's Nth dependency is target-incompatible, any dependencies already resolved in the same loop iteration remain in selectedIds even though the requesting module was ultimately skipped.

Confidence Score: 3/5

  • Safe for the primary use cases (claude/cursor/antigravity legacy-compat and manifest installs), but two logic edge cases in install-manifests.js should be addressed before wider adoption.
  • The core flow is well-tested (1149 passing) and the happy-path scenarios are solid. The two flagged logic issues — silent target-fallback in resolveLegacyCompatibilitySelection and orphaned modules after a partial dependency chain failure — are not triggered by existing tests but could surface in deeper dependency graphs or when codex/opencode targets are used programmatically. The claudeRulesDir silent drop is a behavioural regression for existing CLAUDE_RULES_DIR users.
  • scripts/lib/install-manifests.js — contains both the silent target-fallback and the partial-dependency-resolution logic issue.

Important Files Changed

Filename Overview
scripts/lib/install-manifests.js Core manifest resolution logic; adds legacy-compat constants, resolveLegacyCompatibilitySelection, and validateInstallModuleIds. Two logic issues: silent fallback to claude base modules for codex/opencode, and partially-resolved dependency siblings left in selectedIds when a later dependency fails target compatibility.
scripts/lib/install-executor.js Adds createLegacyCompatInstallPlan which bridges legacy language inputs to the manifest-backed install path. Lazy-loads install-state and install/apply via wrapper functions to avoid circular deps. createManifestInstallPlan now correctly threads requestProfileId, requestModuleIds, legacyLanguages, and legacyMode into the state preview.
scripts/lib/install/request.js Renames languageslegacyLanguages, changes legacy mode from 'legacy' to 'legacy-compat', and validates --modules IDs against the real manifest catalog. Minor perf issue: validateInstallModuleIds always loads manifests even when the module list is empty.
scripts/lib/install/runtime.js Adds legacy-compat routing to createInstallPlanFromRequest. The claudeRulesDir option is forwarded to createLegacyCompatInstallPlan but silently dropped; the CLAUDE_RULES_DIR env var no longer works for legacy-compat installs.
scripts/install-apply.js Heavy imports moved into main() body for lazy loading; listAvailableLanguages swapped for listLegacyCompatibilityLanguages; printHumanPlan extended to display legacyLanguages in legacy-compat mode. Clean and straightforward.
tests/lib/install-manifests.test.js Good test coverage added for listLegacyCompatibilityLanguages, validateInstallModuleIds, resolveLegacyCompatibilitySelection, and the new antigravity skip behavior. Existing "throws when dependency incompatible" test correctly updated to reflect the new skip-not-throw semantics.
tests/lib/install-request.test.js Tests updated to use legacyLanguages and legacy-compat mode. New test covers manifest module ID validation against the catalog on unknown IDs.
tests/scripts/ecc.test.js E2E CLI tests updated to assert legacy-compat mode and legacyLanguages field, plus verify framework-language module is selected in the resolved plan.
tests/scripts/install-apply.test.js Integration tests significantly expanded to verify manifest-backed file layout, install-state content, and the new antigravity profile skip behavior. Assertions updated from flat legacy paths to the new nested manifest paths.

Sequence Diagram

sequenceDiagram
    participant CLI as install-apply.js
    participant Req as install/request.js
    participant RT as install/runtime.js
    participant Exec as install-executor.js
    participant Mfst as install-manifests.js

    CLI->>Req: parseInstallArgs(argv)
    CLI->>Req: normalizeInstallRequest(options)
    Req->>Mfst: validateInstallModuleIds(moduleIds)
    Mfst-->>Req: validated & deduped IDs
    Req-->>CLI: request { mode: 'legacy-compat' | 'manifest' }

    CLI->>RT: createInstallPlanFromRequest(request)

    alt mode === 'legacy-compat'
        RT->>Exec: createLegacyCompatInstallPlan(options)
        Exec->>Mfst: resolveLegacyCompatibilitySelection({ target, legacyLanguages })
        Mfst-->>Exec: { moduleIds, legacyLanguages, canonicalLegacyLanguages }
        Exec->>Exec: createManifestInstallPlan(selection + legacyMode=true)
    else mode === 'manifest'
        RT->>Exec: createManifestInstallPlan(options)
    else mode === 'legacy'
        RT->>Exec: createLegacyInstallPlan(options)
    end

    Exec->>Mfst: resolveInstallPlan({ profileId, moduleIds, target })
    Note over Mfst: resolveModule() skips incompatible<br/>dependency trees (returns false)<br/>instead of throwing
    Mfst-->>Exec: { selectedModuleIds, skippedModuleIds, operations }
    Exec-->>RT: plan { mode, legacyLanguages, statePreview, operations }
    RT-->>CLI: plan

    alt --dry-run
        CLI->>CLI: printHumanPlan(plan) / JSON.stringify
    else apply
        CLI->>Exec: applyInstallPlan(plan)
        Exec-->>CLI: result
    end
Loading

Last reviewed commit: b320fb3

Comment on lines +260 to +261
const baseModuleIds = LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET[target || 'claude']
|| LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET.claude;

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.

Silent fallback to claude base modules for unsupported targets

resolveLegacyCompatibilitySelection validates target against the full SUPPORTED_INSTALL_TARGETS list (which includes codex and opencode), but LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET only defines entries for claude, cursor, and antigravity. An unknown key lookup returns undefined, which then silently falls back to the claude config via the || chain:

const baseModuleIds = LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET[target || 'claude']
  || LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET.claude;

If this function is called with target: 'codex' or target: 'opencode' (both valid according to the guard on line 229), it will silently install claude-flavoured base modules instead of throwing a clear error. While the current call site in createLegacyCompatInstallPlan is protected by validateLegacyTarget, the public export of resolveLegacyCompatibilitySelection means future callers, tests, or direct usage could hit this silently wrong path.

The validation on line 229 should mirror the entries in LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET rather than the broader SUPPORTED_INSTALL_TARGETS:

Suggested change
const baseModuleIds = LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET[target || 'claude']
|| LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET.claude;
if (target && !Object.hasOwn(LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET, target || 'claude')) {
throw new Error(
`Unknown install target: ${target}. Expected one of ${Object.keys(LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET).join(', ')}`
);
}

Comment on lines 370 to +387
for (const dependencyId of module.dependencies) {
resolveModule(dependencyId, moduleId);
const dependencyResolved = resolveModule(
dependencyId,
moduleId,
rootRequesterId || moduleId
);
if (!dependencyResolved) {
visitingIds.delete(moduleId);
if (!dependencyOf) {
skippedTargetIds.add(moduleId);
}
return false;
}
}
visitingIds.delete(moduleId);
resolvedIds.add(moduleId);
selectedIds.add(moduleId);
return true;

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.

Orphaned modules left in selectedIds when a later sibling dependency fails target check

When resolving module A's dependencies in order [D1, D2]:

  1. D1 is target-compatible → D1 (and all its transitive deps) are added to selectedIds and resolvedIds
  2. D2 is target-incompatible → A is added to skippedTargetIds, false is returned
  3. A is not added to selectedIds — but D1 is still there from step 1

D1 now appears in the final selectedModules even though the only top-level requester (A) was ultimately skipped. In shallow dependency trees this may be harmless (D1 might be requested elsewhere), but in deep or unique chains this results in modules being installed that have no remaining requester.

A minimal fix is to track which modules were added during the resolution of a particular root module and roll them back on failure:

visitingIds.add(moduleId);
const addedThisRound = [];
for (const dependencyId of module.dependencies) {
  const dependencyResolved = resolveModule(dependencyId, moduleId, rootRequesterId || moduleId);
  if (!dependencyResolved) {
    // roll back any modules added while resolving this branch
    for (const id of addedThisRound) { selectedIds.delete(id); resolvedIds.delete(id); }
    visitingIds.delete(moduleId);
    if (!dependencyOf) { skippedTargetIds.add(moduleId); }
    return false;
  }
  addedThisRound.push(dependencyId);
}

At minimum, this should be documented so future contributors are aware of the partial-selection behaviour.

Comment on lines +75 to +77
const moduleIds = validateInstallModuleIds(
dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])])
);

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.

Manifest always loaded even when moduleIds is empty

validateInstallModuleIds unconditionally calls loadInstallManifests, which reads and parses three JSON files from disk. When moduleIds is an empty array there are no IDs to validate, so the file I/O is wasted on every normalizeInstallRequest call that doesn't supply modules (e.g., all legacy-language installs).

A cheap guard avoids the unnecessary I/O:

Suggested change
const moduleIds = validateInstallModuleIds(
dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])])
);
const rawModuleIds = dedupeStrings([...(config?.moduleIds || []), ...(options.moduleIds || [])]);
const moduleIds = rawModuleIds.length > 0
? validateInstallModuleIds(rawModuleIds)
: rawModuleIds;

Comment on lines +27 to +36
if (request.mode === 'legacy-compat') {
return createLegacyCompatInstallPlan({
target: request.target,
legacyLanguages: request.legacyLanguages,
projectRoot: options.projectRoot,
homeDir: options.homeDir,
claudeRulesDir: options.claudeRulesDir,
sourceRoot: options.sourceRoot,
});
}

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.

claudeRulesDir forwarded but silently dropped in legacy-compat path

createLegacyCompatInstallPlan accepts claudeRulesDir in its options object (forwarded here from install-apply.js via CLAUDE_RULES_DIR), but neither createLegacyCompatInstallPlan nor the downstream createManifestInstallPlan consumes it. The old legacy mode honoured this env var to redirect the Claude rules destination; the new legacy-compat path silently ignores it.

This is a quiet breaking change for users who set CLAUDE_RULES_DIR. Consider either:

  • Explicitly documenting that CLAUDE_RULES_DIR is no longer respected in legacy-compat mode, or
  • Surfacing a warning when claudeRulesDir is non-null and the mode is legacy-compat.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b320fb3539

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +377 to +381
visitingIds.delete(moduleId);
if (!dependencyOf) {
skippedTargetIds.add(moduleId);
}
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Roll back resolved deps when skipping a module

When resolveModule bails out on an incompatible dependency, it returns false but keeps dependencies that were already added to selectedIds earlier in the same traversal. For mixed-support trees this installs modules even though the requested root is skipped: for example, resolving framework-language on antigravity selects rules-core/agents-core/commands-core before failing on platform-configs, so the plan applies partial modules instead of skipping the incompatible tree. This changes install results for explicit module requests and can unexpectedly mutate targets with modules users did not successfully request.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
scripts/lib/install-manifests.js (1)

235-237: Minor: Redundant deduplication.

Line 235 calls dedupeStrings() on options.legacyLanguages, then line 237 calls it again on legacyLanguages. Since dedupeStrings already returns deduplicated values, the second call is redundant.

♻️ Simplify deduplication
-  const legacyLanguages = dedupeStrings(options.legacyLanguages)
-    .map(language => language.toLowerCase());
-  const normalizedLegacyLanguages = dedupeStrings(legacyLanguages);
+  const normalizedLegacyLanguages = dedupeStrings(options.legacyLanguages)
+    .map(language => language.toLowerCase());

Note: This assumes case-insensitive deduplication is acceptable. If ['Go', 'go'] should dedupe to one entry, you'd need to dedupe after lowercasing:

const normalizedLegacyLanguages = [...new Set(
  dedupeStrings(options.legacyLanguages).map(l => l.toLowerCase())
)];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/lib/install-manifests.js` around lines 235 - 237, The code calls
dedupeStrings twice (on options.legacyLanguages and then again on
legacyLanguages) causing redundant work; update the logic around dedupeStrings,
legacyLanguages, and normalizedLegacyLanguages so you only deduplicate once and
then normalize case as needed: call dedupeStrings on options.legacyLanguages (or
lowercase first then dedupe if you need case-insensitive results), assign that
to legacyLanguages, then derive normalizedLegacyLanguages without re-calling
dedupeStrings (e.g., map toLowerCase and use a Set or dedupe once), ensuring the
variable names legacyLanguages and normalizedLegacyLanguages reflect the single
deduplication step.
tests/scripts/install-apply.test.js (1)

176-176: Avoid order-coupled assertions for selected modules.

These deepStrictEqual([...]) checks are brittle if resolver ordering changes while module membership remains correct. Prefer order-insensitive comparison.

♻️ Suggested refactor
-      assert.deepStrictEqual(state.resolution.selectedModules, ['rules-core', 'agents-core', 'commands-core']);
+      assert.deepStrictEqual(
+        [...state.resolution.selectedModules].sort(),
+        ['rules-core', 'agents-core', 'commands-core'].sort()
+      );

Also applies to: 277-277

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

In `@tests/scripts/install-apply.test.js` at line 176, The test uses
assert.deepStrictEqual on state.resolution.selectedModules which is
order-sensitive and brittle; change these assertions (the ones calling
assert.deepStrictEqual with state.resolution.selectedModules) to an
order-insensitive comparison instead — e.g., compare sorted arrays or compare
Sets so membership is asserted regardless of order (replace the deepStrictEqual
usage with a sorted-deepStrictEqual or Set-based membership check for
state.resolution.selectedModules in both occurrences).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/lib/install-executor.js`:
- Around line 501-531: The createLegacyCompatInstallPlan function accepts
options.claudeRulesDir but does not forward it to createManifestInstallPlan;
update the createManifestInstallPlan call inside createLegacyCompatInstallPlan
to include claudeRulesDir: options.claudeRulesDir (or options.claudeRulesDir ||
undefined) so the provided CLAUDE_RULES_DIR is honored when building the
legacy-compat install plan; reference createLegacyCompatInstallPlan and
createManifestInstallPlan when making this change.

In `@scripts/lib/install/runtime.js`:
- Around line 27-36: The call to createLegacyCompatInstallPlan currently
supplies claudeRulesDir but that parameter is unused downstream; either remove
claudeRulesDir from the argument list in the request.mode === 'legacy-compat'
branch, or (preferred for parity) forward it into the compat plan by updating
createLegacyCompatInstallPlan to accept and pass claudeRulesDir through to
whatever downstream function createLegacyInstallPlan or
createManifestInstallPlan uses; locate the branch keyed by request.mode, adjust
the call site to match createLegacyInstallPlan's signature or update
createLegacyCompatInstallPlan to accept a claudeRulesDir parameter and use it
consistently.

In `@tests/scripts/install-apply.test.js`:
- Around line 316-320: The test "rejects unknown explicit manifest modules
before resolution" currently runs in the real environment; modify it to create
and use a temporary directory as both process cwd and HOME before invoking
run(['--modules', 'ghost-module']) (e.g., by chdir or passing env to run), run
the command there, assert result.code === 1 and stderr contains 'Unknown install
module: ghost-module', then assert that no state file (the project's state file
name used elsewhere in tests) was created in that temp dir, and finally clean
up/reset cwd and HOME; update references in the test body surrounding run(...)
and assertion calls to use the temp dir environment.

---

Nitpick comments:
In `@scripts/lib/install-manifests.js`:
- Around line 235-237: The code calls dedupeStrings twice (on
options.legacyLanguages and then again on legacyLanguages) causing redundant
work; update the logic around dedupeStrings, legacyLanguages, and
normalizedLegacyLanguages so you only deduplicate once and then normalize case
as needed: call dedupeStrings on options.legacyLanguages (or lowercase first
then dedupe if you need case-insensitive results), assign that to
legacyLanguages, then derive normalizedLegacyLanguages without re-calling
dedupeStrings (e.g., map toLowerCase and use a Set or dedupe once), ensuring the
variable names legacyLanguages and normalizedLegacyLanguages reflect the single
deduplication step.

In `@tests/scripts/install-apply.test.js`:
- Line 176: The test uses assert.deepStrictEqual on
state.resolution.selectedModules which is order-sensitive and brittle; change
these assertions (the ones calling assert.deepStrictEqual with
state.resolution.selectedModules) to an order-insensitive comparison instead —
e.g., compare sorted arrays or compare Sets so membership is asserted regardless
of order (replace the deepStrictEqual usage with a sorted-deepStrictEqual or
Set-based membership check for state.resolution.selectedModules in both
occurrences).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0e29697-6be4-4240-992d-b605d0cfa30f

📥 Commits

Reviewing files that changed from the base of the PR and between c53bba9 and b320fb3.

📒 Files selected for processing (9)
  • scripts/install-apply.js
  • scripts/lib/install-executor.js
  • scripts/lib/install-manifests.js
  • scripts/lib/install/request.js
  • scripts/lib/install/runtime.js
  • tests/lib/install-manifests.test.js
  • tests/lib/install-request.test.js
  • tests/scripts/ecc.test.js
  • tests/scripts/install-apply.test.js

Comment on lines +501 to +531
function createLegacyCompatInstallPlan(options = {}) {
const sourceRoot = options.sourceRoot || getSourceRoot();
const projectRoot = options.projectRoot || process.cwd();
const target = options.target || 'claude';

validateLegacyTarget(target);

const selection = resolveLegacyCompatibilitySelection({
repoRoot: sourceRoot,
target,
legacyLanguages: options.legacyLanguages || [],
});

return createManifestInstallPlan({
sourceRoot,
projectRoot,
homeDir: options.homeDir,
target,
profileId: null,
moduleIds: selection.moduleIds,
includeComponentIds: [],
excludeComponentIds: [],
legacyLanguages: selection.legacyLanguages,
legacyMode: true,
requestProfileId: null,
requestModuleIds: [],
requestIncludeComponentIds: [],
requestExcludeComponentIds: [],
mode: 'legacy-compat',
});
}

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 | 🟡 Minor

claudeRulesDir option is accepted but not forwarded to createManifestInstallPlan.

The function accepts options.claudeRulesDir (documented by the caller in runtime.js) but does not pass it to createManifestInstallPlan. If a user sets CLAUDE_RULES_DIR expecting custom rules directory support, the legacy-compat path will silently ignore it.

🔧 Proposed fix to forward claudeRulesDir
   return createManifestInstallPlan({
     sourceRoot,
     projectRoot,
     homeDir: options.homeDir,
+    claudeRulesDir: options.claudeRulesDir,
     target,
     profileId: null,
     moduleIds: selection.moduleIds,
#!/bin/bash
# Check if createManifestInstallPlan accepts claudeRulesDir
rg -n 'claudeRulesDir' scripts/lib/install-executor.js
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/lib/install-executor.js` around lines 501 - 531, The
createLegacyCompatInstallPlan function accepts options.claudeRulesDir but does
not forward it to createManifestInstallPlan; update the
createManifestInstallPlan call inside createLegacyCompatInstallPlan to include
claudeRulesDir: options.claudeRulesDir (or options.claudeRulesDir || undefined)
so the provided CLAUDE_RULES_DIR is honored when building the legacy-compat
install plan; reference createLegacyCompatInstallPlan and
createManifestInstallPlan when making this change.

Comment on lines +27 to +36
if (request.mode === 'legacy-compat') {
return createLegacyCompatInstallPlan({
target: request.target,
legacyLanguages: request.legacyLanguages,
projectRoot: options.projectRoot,
homeDir: options.homeDir,
claudeRulesDir: options.claudeRulesDir,
sourceRoot: options.sourceRoot,
});
}

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify createLegacyCompatInstallPlan does not use claudeRulesDir
rg -n -A 30 'function createLegacyCompatInstallPlan' scripts/lib/install-executor.js | head -40

Repository: affaan-m/everything-claude-code

Length of output: 1065


🏁 Script executed:

# Check if createManifestInstallPlan accepts claudeRulesDir
rg -n -B 5 'function createManifestInstallPlan' scripts/lib/install-executor.js | head -20

Repository: affaan-m/everything-claude-code

Length of output: 202


🏁 Script executed:

# Check the non-legacy path around line 38-47 in runtime.js to see how it handles claudeRulesDir
sed -n '27,50p' scripts/lib/install/runtime.js

Repository: affaan-m/everything-claude-code

Length of output: 787


🏁 Script executed:

# Get full createManifestInstallPlan function signature and first 50 lines to see parameters
sed -n '570,620p' scripts/lib/install-executor.js

Repository: affaan-m/everything-claude-code

Length of output: 2339


🏁 Script executed:

# Check createLegacyInstallPlan to see if it uses claudeRulesDir
rg -n -A 20 'function createLegacyInstallPlan' scripts/lib/install-executor.js

Repository: affaan-m/everything-claude-code

Length of output: 858


Remove unused claudeRulesDir parameter or pass it through to downstream functions.

claudeRulesDir is passed to createLegacyCompatInstallPlan but is never extracted or used within the function. In contrast, createLegacyInstallPlan (the non-compat legacy path) properly extracts and uses this parameter. This inconsistency creates a misleading API and could cause bugs if custom rules directories are configured.

Either pass claudeRulesDir to createManifestInstallPlan for consistency, or remove the parameter from the call site if legacy-compat mode intentionally does not support custom rules directories.

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

In `@scripts/lib/install/runtime.js` around lines 27 - 36, The call to
createLegacyCompatInstallPlan currently supplies claudeRulesDir but that
parameter is unused downstream; either remove claudeRulesDir from the argument
list in the request.mode === 'legacy-compat' branch, or (preferred for parity)
forward it into the compat plan by updating createLegacyCompatInstallPlan to
accept and pass claudeRulesDir through to whatever downstream function
createLegacyInstallPlan or createManifestInstallPlan uses; locate the branch
keyed by request.mode, adjust the call site to match createLegacyInstallPlan's
signature or update createLegacyCompatInstallPlan to accept a claudeRulesDir
parameter and use it consistently.

Comment on lines +316 to +320
if (test('rejects unknown explicit manifest modules before resolution', () => {
const result = run(['--modules', 'ghost-module']);
assert.strictEqual(result.code, 1);
assert.ok(result.stderr.includes('Unknown install module: ghost-module'));
})) passed++; else failed++;

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 | 🟡 Minor

Isolate the unknown-module failure test from real environment paths.

This case currently runs without temp cwd/HOME. If failure timing regresses, the test can write into the invoking environment. Run it in temp dirs and assert no state file was created.

🧪 Suggested fix
-  if (test('rejects unknown explicit manifest modules before resolution', () => {
-    const result = run(['--modules', 'ghost-module']);
-    assert.strictEqual(result.code, 1);
-    assert.ok(result.stderr.includes('Unknown install module: ghost-module'));
-  })) passed++; else failed++;
+  if (test('rejects unknown explicit manifest modules before resolution', () => {
+    const homeDir = createTempDir('install-apply-home-');
+    const projectDir = createTempDir('install-apply-project-');
+
+    try {
+      const result = run(['--modules', 'ghost-module'], { cwd: projectDir, homeDir });
+      assert.strictEqual(result.code, 1);
+      assert.ok(result.stderr.includes('Unknown install module: ghost-module'));
+      assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json')));
+      assert.ok(!fs.existsSync(path.join(projectDir, '.cursor', 'ecc-install-state.json')));
+    } finally {
+      cleanup(homeDir);
+      cleanup(projectDir);
+    }
+  })) passed++; else failed++;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/scripts/install-apply.test.js` around lines 316 - 320, The test
"rejects unknown explicit manifest modules before resolution" currently runs in
the real environment; modify it to create and use a temporary directory as both
process cwd and HOME before invoking run(['--modules', 'ghost-module']) (e.g.,
by chdir or passing env to run), run the command there, assert result.code === 1
and stderr contains 'Unknown install module: ghost-module', then assert that no
state file (the project's state file name used elsewhere in tests) was created
in that temp dir, and finally clean up/reset cwd and HOME; update references in
the test body surrounding run(...) and assertion calls to use the temp dir
environment.

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

1 issue found across 9 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/lib/install-manifests.js">

<violation number="1" location="scripts/lib/install-manifests.js:376">
P1: Skipping a module due to an incompatible dependency can still leave earlier dependencies selected, producing orphan installs from a tree that should have been skipped.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

moduleId,
rootRequesterId || moduleId
);
if (!dependencyResolved) {

@cubic-dev-ai cubic-dev-ai Bot Mar 16, 2026

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.

P1: Skipping a module due to an incompatible dependency can still leave earlier dependencies selected, producing orphan installs from a tree that should have been skipped.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/lib/install-manifests.js, line 376:

<comment>Skipping a module due to an incompatible dependency can still leave earlier dependencies selected, producing orphan installs from a tree that should have been skipped.</comment>

<file context>
@@ -248,15 +368,27 @@ function resolveInstallPlan(options = {}) {
+        moduleId,
+        rootRequesterId || moduleId
+      );
+      if (!dependencyResolved) {
+        visitingIds.delete(moduleId);
+        if (!dependencyOf) {
</file context>
Fix with Cubic

@affaan-m affaan-m merged commit 1e0238d into main Mar 16, 2026
30 of 40 checks passed
@affaan-m affaan-m deleted the feat/install-pipeline branch March 20, 2026 07:15
floatingman pushed a commit to floatingman/everything-claude-code that referenced this pull request Mar 22, 2026
FrancescoRosciano pushed a commit to FRosciano-Mambo/everything-claude-code that referenced this pull request Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant