Skip to content

feat(cli): analyze --all to re-index all registered repos (#253)#1010

Closed
azizur100389 wants to merge 3 commits into
abhigyanpatwari:mainfrom
azizur100389:feat/analyze-all
Closed

feat(cli): analyze --all to re-index all registered repos (#253)#1010
azizur100389 wants to merge 3 commits into
abhigyanpatwari:mainfrom
azizur100389:feat/analyze-all

Conversation

@azizur100389

Copy link
Copy Markdown
Contributor

Problem (#253)

Users with many indexed repos have no batch re-index command. clean --all exists; analyze --all is the natural counterpart. After pulling updates across multiple projects, or running a scheduled re-index cron, users currently have to either script a loop over paths externally or iterate over gitnexus list output manually.

What this PR adds

New flag: gitnexus analyze --all [--force] [--embeddings] [--skills] [--skip-agents-md] [--no-stats] [-v]

Iterates ~/.gitnexus/registry.json and spawns a child gitnexus analyze <path> for each entry.

Mutually exclusive with [path], --name <alias>, --allow-duplicate-name — those are inherently single-repo. The exclusion check runs BEFORE any registry read or filesystem access; users see a clear error with exit 1.

Why child-process isolation (not in-process loop)

Each repo gets:

  • Fresh LadybugDB handle (single-writer invariant preserved)
  • Fresh progress bar + monkey-patched console
  • Fresh heap and stack
  • Fresh ensureHeap() re-exec path
  • Independent SIGINT handler scope

A crash or unhandled rejection in one repo's analyze cannot corrupt sibling state. The existing analyze flow (SIGINT cleanup, heap re-spawn, progress bar, runFullAnalysis) is reused verbatim — no in-process state-reset hazards, no need to prove idempotency of a 1000-line orchestrator across batch iterations.

Overhead: ~1-2s per repo for process startup. Acceptable — --all is cron/maintenance, not interactive.

Missing-path tolerance

If a registry entry's path no longer exists on disk (user rm -rf'd a project), the entry is skipped with a warning before spawning. Matches clean --all's per-repo error-tolerance: one stale entry doesn't halt the batch.

execArgv inheritance

Child spawn passes process.execArgv through — critical for tsx/tsnode dev setups and for the vitest integration tests. Without it, children die with ERR_UNKNOWN_FILE_EXTENSION trying to load .ts sources. No-op in production (compiled .js, empty execArgv).

Summary output

  Summary: N succeeded, M skipped, K failed.
  Failed: <names>

Exit 1 if K > 0, else 0. Skipped entries are not failures (they're registry self-heal candidates). Cron / CI sees partial-failure correctly.

Safety — no guardrails added or weakened

Risk Assessment
Destructive fs operations None. analyze --all only spawns child analyze processes. No fs.rm sites added.
Registry corruption Impossible. I only READ the registry via listRegisteredRepos(). Children write via their own registerRepo calls — the existing, tested path (with RegistryNameCollisionError + canonicalizePath + assertSafeStoragePath from #1003 still in force).
Input validation Mutual-exclusion check fires before any I/O. Bails cleanly with exit 1.
Missing-path ENOENT Skipped with warning; no crash, no spawn, no partial state.
Cross-repo state pollution Child-process boundary — impossible by construction.

Per the #1003 lineage: zero new safety surface, zero new fs.rm sites.

Tests

5 new integration tests in test/integration/cli-e2e.test.ts:

  1. Happy path: register 2 repos → analyze --all --force → assert indexedAt advances on both, summary reports 2 succeeded, 0 skipped, 0 failed, per-repo headings format correctly
  2. Empty registry → friendly message, exit 0, no spawn
  3. Mutual exclusion vs [path] → exit 1 with --all cannot be combined with [path]...
  4. Mutual exclusion vs --name → exit 1 with same shape, mentions --name
  5. Missing-path tolerance → one repo has its working tree rm -rf'd between registration and --all; summary reports 1 succeeded, 1 skipped, 0 failed, the keep-repo's .gitnexus/ stays intact

Verification (local)

  • tsc --noEmit clean
  • npm run test:unit — 4184 pass (2 pre-existing git-utils env failures unchanged, unrelated; they also fail on pristine upstream main)
  • cli-e2e.test.ts -t "analyze --all|remove|clean --all|analyze --name" — 10/10 pass (5 new + 5 pre-existing)

Out of scope

  • analyze --all --parallel <n> for concurrent re-indexing. Flagged in the issue comments; declined for v1 because it raises LadybugDB single-writer contention, progress-bar multiplexing, and memory-pressure design questions — all better handled as a follow-up after this ships and real usage patterns emerge.

Closes #253.

…twari#253)

Mirrors `clean --all` — iterates ~/.gitnexus/registry.json and spawns
a child `gitnexus analyze <path>` for each entry. Rationale for
child-process isolation: each repo gets fresh LadybugDB handles,
progress bar, heap, and monkey-patched console — a crash in one repo
cannot corrupt siblings, and the existing analyze flow (SIGINT,
ensureHeap, progress-bar setup) is reused verbatim without
in-process state-reset hazards.

- New `--all` flag on the analyze command. Mutually exclusive with
  [path], --name, --allow-duplicate-name — rejection happens BEFORE
  any filesystem or registry access.
- Flags forwarded to each child: --force, --embeddings, --skills,
  --skip-agents-md, --no-stats, --skip-git, -v.
- process.execArgv is propagated to the child spawn so tsx/tsnode
  dev setups and vitest-spawned integration tests work (otherwise
  children die with ERR_UNKNOWN_FILE_EXTENSION on .ts sources).
- Missing-path entries (repo deleted on disk) are skipped with a
  warning; one failure does not halt the batch. Matches clean --all
  per-repo error-tolerance semantics.
- Summary: N succeeded, M skipped, K failed. Exit 1 if K > 0,
  else 0 — surfaces partial-failure to cron / CI.
- 5 integration tests cover: happy path (indexedAt advances for both
  registered repos), empty registry, two mutual-exclusion branches
  ([path], --name), missing-path tolerance (one deleted repo gets
  skipped, sibling still re-indexes).

No destructive operations added. analyze --all only reads the
registry; children write entries via their own registerRepo calls
(existing, tested path). Zero new fs.rm sites, zero new safety
surface.
@vercel

vercel Bot commented Apr 21, 2026

Copy link
Copy Markdown

@azizur100389 is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
7192 7093 0 97 303s

✅ All 7093 tests passed

97 test(s) skipped — expand for details
  • withTestLbugDB(lock-retry) > withLbugDb retry behaviour > propagates non-BUSY errors immediately without retrying
  • withTestLbugDB(lock-retry) > withLbugDb retry behaviour > throws after max retry attempts
  • Swift MethodExtractor > isTypeDeclaration > recognizes class_declaration
  • Swift MethodExtractor > isTypeDeclaration > recognizes protocol_declaration
  • Swift MethodExtractor > isTypeDeclaration > rejects import_declaration
  • Swift MethodExtractor > visibility > extracts public method
  • Swift MethodExtractor > visibility > extracts private method
  • Swift MethodExtractor > visibility > defaults to internal when no modifier
  • Swift MethodExtractor > protocol methods > marks protocol method as abstract
  • Swift MethodExtractor > static and class methods > detects static func as isStatic
  • Swift MethodExtractor > static and class methods > detects class func as isStatic
  • Swift MethodExtractor > parameters > extracts parameters with types and default values
  • Swift MethodExtractor > return type > extracts return type from -> annotation
  • Swift MethodExtractor > annotations > extracts @objc attribute
  • Swift MethodExtractor > isFinal > detects final func
  • Swift MethodExtractor > isFinal > is false when not final
  • Swift MethodExtractor > isAsync > detects async func
  • Swift MethodExtractor > isOverride > detects override method
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift explicit init inference uses lookupClassByName
  • buildTypeEnv > constructor inference (Tier 1 fallback) > lookupClassByName regression coverage > Swift lookupClassByName regression coverage > Swift cross-file constructor inference does not bind plain functions
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature
  • Swift constructor-inferred type resolution > detects User and Repo classes, both with save methods
  • Swift constructor-inferred type resolution > resolves user.save() to Models/User.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > resolves repo.save() to Models/Repo.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > emits exactly 2 save() CALLS edges (one per receiver type)
  • Swift self resolution > detects User and Repo classes, each with a save function
  • Swift self resolution > resolves self.save() inside User.process to User.save, not Repo.save
  • Swift parent resolution > detects BaseModel and User classes plus Serializable protocol
  • Swift parent resolution > emits EXTENDS edge: User → BaseModel
  • Swift parent resolution > emits IMPLEMENTS edge: User → Serializable (protocol conformance)
  • Swift cross-file User.init() inference > resolves user.save() via User.init(name:) inference
  • Swift cross-file User.init() inference > resolves user.greet() via User.init(name:) inference
  • Swift return type inference > detects User class and getUser function
  • Swift return type inference > detects save function on User (Swift class methods are Function nodes)
  • Swift return type inference > resolves user.save() to User#save via return type of getUser() -> User
  • Swift return-type inference via function return type > resolves user.save() to User#save via return type of getUser()
  • Swift return-type inference via function return type > user.save() does NOT resolve to Repo#save
  • Swift return-type inference via function return type > resolves repo.save() to Repo#save via return type of getRepo()
  • Swift implicit imports (cross-file visibility) > detects UserService class in Models.swift
  • Swift implicit imports (cross-file visibility) > resolves UserService() constructor call across files (no explicit import)
  • Swift implicit imports (cross-file visibility) > resolves service.fetchUser() member call across files
  • Swift implicit imports (cross-file visibility) > creates IMPORTS edges between files in the same module
  • Swift extension deduplication > detects Product class
  • Swift extension deduplication > resolves Product() constructor despite extension creating duplicate class node
  • Swift extension deduplication > resolves product.save() to Product.swift (primary definition)
  • Swift constructor call fallback (no new keyword) > resolves OCRService() as constructor call across files
  • Swift constructor call fallback (no new keyword) > resolves ocr.recognize() member call via constructor-inferred type
  • Swift export visibility (internal vs private) > resolves PublicService() constructor across files
  • Swift export visibility (internal vs private) > resolves internalHelper() across files (internal = module-scoped)
  • Swift if let / guard let binding resolution > detects User and Repo classes
  • Swift if let / guard let binding resolution > resolves user.save() inside if-let to User#save
  • Swift if let / guard let binding resolution > resolves repo.save() inside guard-let to Repo#save
  • Swift if let / guard let binding resolution > user.save() in if-let does NOT resolve to Repo#save
  • Swift await / try expression unwrapping > resolves user.save() via await fetchUser() return type
  • Swift await / try expression unwrapping > resolves repo.save() via try parseRepo() return type
  • Swift await / try expression unwrapping > detects fetchUser and parseRepo as functions
  • Swift for-in loop element type inference > detects User and Repo classes
  • Swift for-in loop element type inference > creates implicit import edges between files
  • Swift field-type resolution > detects classes and their properties
  • Swift field-type resolution > emits HAS_PROPERTY edges from class to field
  • Swift field-type resolution > resolves field-chain call user.address.save() → Address#save
  • Swift field-type resolution > emits ACCESSES edges for field reads in chains
  • Swift field-type resolution > populates field metadata (visibility, declaredType) on Property nodes
  • Swift call-result binding > resolves call-result-bound method call user.save() → User#save
  • Swift call-result binding > getUser() is present as a defined function
  • Swift call-result binding > emits processUser -> getUser CALLS edge for let-assigned free function call
  • Swift method enrichment > detects Animal protocol and Dog class
  • Swift method enrichment > emits IMPLEMENTS edge Dog -> Animal
  • Swift method enrichment > emits HAS_METHOD edges for Dog methods
  • Swift method enrichment > marks protocol Animal.speak as isAbstract
  • Swift method enrichment > marks Dog.speak as NOT isAbstract
  • Swift method enrichment > marks breathe as isFinal
  • Swift method enrichment > marks classify as isStatic
  • Swift method enrichment > captures @objc annotation on breathe
  • Swift method enrichment > populates parameterTypes for classify(_ name: String)
  • Swift method enrichment > records parameterCount for classify
  • Swift method enrichment > records returnType for speak
  • Swift method enrichment > resolves dog.speak() CALLS edge
  • Swift method enrichment > resolves Dog.classify("dog") CALLS edge
  • Swift abstract dispatch > detects Repository protocol and SqlRepository class
  • Swift abstract dispatch > emits IMPLEMENTS edge SqlRepository -> Repository
  • Swift abstract dispatch > emits HAS_METHOD edges for Repository.find and Repository.save
  • Swift abstract dispatch > emits HAS_METHOD edges for SqlRepository.find and SqlRepository.save
  • Swift abstract dispatch > marks base Repository.find as isAbstract
  • Swift abstract dispatch > marks base Repository.save as isAbstract
  • Swift abstract dispatch > marks concrete SqlRepository.find as NOT isAbstract
  • Swift abstract dispatch > resolves repo.find(id: 42) CALLS edge
  • Swift abstract dispatch > resolves repo.save(entity: user) CALLS edge
  • Swift abstract dispatch > populates parameterTypes for Repository.find
  • Swift abstract dispatch > populates parameterTypes for Repository.save
  • Swift abstract dispatch > records returnType for SqlRepository.find
  • Swift abstract dispatch > emits METHOD_IMPLEMENTS edges from SqlRepository methods → Repository protocol methods
  • Swift overloaded method disambiguation > detects 2 distinct find Method nodes on SqlRepository
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edges for both find overloads
  • Swift overloaded method disambiguation > emits METHOD_IMPLEMENTS edge for save
  • Swift overloaded method disambiguation > emits exactly 3 METHOD_IMPLEMENTS edges total
  • Swift Child extends Parent — inherited method resolution (SM-9) > detects Parent and Child classes
  • Swift Child extends Parent — inherited method resolution (SM-9) > resolves c.parentMethod() to Parent.parentMethod via first-wins MRO walk

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 74.27% 20283/27309 74.48% 📉 -0.2 🔴 ██████████████░░░░░░
Branches 62.9% 12807/20358 63.02% 📉 -0.1 🔴 ████████████░░░░░░░░
Functions 79.52% 1961/2466 79.74% 📉 -0.2 🔴 ███████████████░░░░░
Lines 77.02% 18377/23860 77.23% 📉 -0.2 🔴 ███████████████░░░░░

📋 View full run · Generated by CI

@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 4m 0s —— View job


Production-ready bar for this PR

This is a new --all flag on the gitnexus analyze CLI command. Based on AGENTS.md, GUARDRAILS.md, CONTRIBUTING.md, TESTING.md, and ARCHITECTURE.md, production-ready for this area means:

  • TypeScript compile-clean: tsc --noEmit passes in gitnexus/ — no type regressions.
  • Full vitest suite passes (unit + integration on all 3 platforms, verified by CI).
  • LadybugDB single-writer invariant preserved: no concurrent writers to the same repo's .gitnexus/lbug; each child process must get exclusive access.
  • No new fs.rm or destructive filesystem operations without assertSafeStoragePath guards (GUARDRAILS.md § LadybugDB lock / "database busy"; GUARDRAILS § Signs).
  • Mutual-exclusion checks fire before any I/O — user sees a clear error and exit 1 rather than a partial-state inconsistency.
  • Integration tests cover happy path, empty registry, mutual-exclusion, and missing-path tolerance (per TESTING.md integration-test mandate).
  • No secrets, tokens, or machine-specific paths committed (GUARDRAILS.md §Non-negotiables).
  • PR title follows conventional-commit format (feat(scope): description) enforced by pr-labeler.yml.
  • No changes to MCP tools, graph schema, or node/edge types — contract stability required (ARCHITECTURE.md §MCP tools).
  • No drive-by refactors outside stated scope (CONTRIBUTING.md §Code review; GUARDRAILS.md §Scope).

Findings

1. Correctness & Functional Completeness

No blocking correctness issues. Key invariants verified:

  • --all is excluded from the forwarded array so children cannot recursively enter --all mode (analyze.ts:185–188). ✓
  • ensureHeap() runs before the --all branch, so the parent gets a large heap if needed, and each child calls ensureHeap() independently (analyze.ts:219, analyze.ts:32–55). ✓
  • process.exitCode = 1 is used (not process.exit(1)) for the --all branch, which is correct: the function returns early (before process.exit(0) at the bottom, analyze.ts:~400), so natural Node exit picks up exitCode. No LadybugDB handles are opened in the parent for the --all path. ✓
  • result.status === null (signal-terminated child) correctly falls into the failure branch, with result.signal !== null checked first in the reason string (analyze.ts:200–204). ✓
  • Mutual exclusions ([path], --name, --allow-duplicate-name) are all present and fire before any registry or filesystem access (analyze.ts:229–248). ✓

NON-BLOCKING: Missing integration test for --allow-duplicate-name mutual exclusion

  • Evidence: analyze.ts:236if (options.allowDuplicateName) violations.push('--allow-duplicate-name') is wired, but no corresponding test exists in cli-e2e.test.ts. Tests cover [path] (line ~847) and --name (line ~869), but not --allow-duplicate-name.
  • Why it matters: The PR description claims mutual exclusion is validated for all three per-repo flags; the test suite only exercises two of the three. The third is a one-liner to add and the code path is identical in shape.
  • Recommendation: Add a 6th test: runCliWithEnv(['analyze', '--all', '--allow-duplicate-name'], ...) → expect exit 1 + output matching /--all cannot be combined with/i and /--allow-duplicate-name/.
  • Confidence: High (code path present but test absent from diff).

2. Code Clarity & Clean Code

No issues found in naming, cohesion, dead code, or drive-by refactors based on the diff and reviewed context.

NIT: GITNEXUS_VERBOSE=1 set in parent env AND --verbose forwarded as arg

  • Evidence: analyze.ts:224 sets process.env.GITNEXUS_VERBOSE = '1' before calling analyzeAllBranch. analyzeAllBranch at line ~147 also pushes --verbose into forwarded. Children inherit the mutated process.env (passed as { env: process.env } at line ~191) AND receive --verbose in argv.
  • Why it matters: Harmless — the child's own analyzeCommand re-sets GITNEXUS_VERBOSE=1 from the flag anyway. The double mechanism is not incorrect, just slightly redundant.
  • Recommendation: Either rely on env inheritance (drop --verbose from forwarded) or rely on the flag (don't mutate process.env before delegating). Either is fine; pick one.
  • Confidence: High.

3. Test Coverage & Change Safety

  • 5 integration tests in test/integration/cli-e2e.test.ts (correct location per TESTING.md). ✓
  • Happy-path test asserts indexedAt advances, not just that exit 0 was returned — meaningful, non-tautological. ✓
  • Spin-wait (while (Date.now() < waitUntil)) to guarantee ISO-second precision change is a pragmatic approach for a synchronous test; acceptable. ✓
  • tsc --noEmit clean (CI stage ✅). ✓
  • All 6934 tests pass across 3 platforms (CI ✅). Coverage delta of -0.2% statements is within acceptable range for integration-heavy features.
  • See NON-BLOCKING above for the missing --allow-duplicate-name test.

4. Performance

  • No issues in this area based on the diff. spawnSync (synchronous, serial) is correct and intentional for a maintenance/cron operation where concurrency would raise LadybugDB single-writer contention. PR description explicitly notes ~1–2s per-repo overhead is acceptable for the --all use case.
  • The parent process allocates nothing beyond a registry JSON read and a small forwarded string array. No O(n²) patterns, no Cypher round-trips.

5. Operational Risk

  • LadybugDB single-writer invariant preserved. Parent never opens LadybugDB; children are spawned serially via spawnSync (blocking), so two children can never have concurrent write access to the same repo's database. Even the degenerate case (same repo registered twice via --allow-duplicate-name) is safe because serial execution guarantees one child completes before the next starts. ✓
  • Embeddings preserved. --embeddings is in forwarded (analyze.ts:140). Users who pass analyze --all --embeddings get per-repo embedding preservation via the existing flag contract. ✓
  • MCP contracts unchanged. No changes to tools.ts, resources.ts, server.ts, or any MCP handler. ✓
  • Staleness. --all re-indexes each repo, updating lastCommit via the existing runFullAnalysis path. Staleness detection is unaffected. ✓
  • Rollback safety. Reverting this PR removes the --all flag. No graph schema changes, no new storage format, no migration required. Safe to revert. ✓
  • Windows cross-platform. process.argv[1] (used as cliEntry) and entry.path (from registry) are passed to Node directly, not constructed via string concatenation. fsSync.existsSync handles OS-native path separators. No hardcoded Unix path separators in the diff. Plausible risk (not verified): globally installed gitnexus on Windows may have a different process.argv[1] format (e.g., a .cmd wrapper path rather than the .js entrypoint). This is a pre-existing concern for all process-spawning in the codebase, not introduced here.

6. Maintainability

  • Pipeline Phase DAG unaffected. --all is a CLI orchestration wrapper; it does not touch gitnexus/src/core/ingestion/. ✓
  • analyzeAllBranch is discoverable — defined at the top of analyze.ts with JSDoc explaining isolation rationale and forwarded flags. Next contributor knows where to extend (add a flag → add to forwarded). ✓
  • Shared logic placement is appropriate — batch orchestration lives in analyze.ts (CLI layer), not duplicated into server/api.ts or gitnexus-shared/. ✓

Non-Negotiables

Check Result
No secrets / tokens committed ✅ Verified
No blind find-and-replace renames ✅ Verified
Conventional commit PR title feat(cli): analyze --all …
No HIGH/CRITICAL impact unaddressed ✅ New flag, no shared symbol edits
PolyForm license metadata untouched ✅ Verified
Docs updated for public CLI contract change ✅ In-CLI help text updated in index.ts; --all not in internal AGENTS.md but the block is auto-regenerated by analyze itself

What I could not fully verify

  1. listRegisteredRepos() error handling for a malformed registry.json. If the JSON is corrupted, the await listRegisteredRepos() call in analyzeAllBranch would throw. This would surface as an unhandled rejection in the async function, which Node.js would turn into a process exit with a non-zero code. The behavior is survivable but not explicitly tested. (Pre-existing concern, not introduced by this PR.)
  2. Windows behavior of process.argv[1] for globally-installed npm packages. On Windows, global npm binaries are typically invoked via a .cmd wrapper, and process.argv[1] may or may not point to the actual .js file depending on the Node/npm version. Cannot verify from diff alone.
  3. Whether the 5-minute and 7-minute integration test timeout budgets are reliably met in CI. The CI run completed in 304s total (all tests), which suggests these are acceptable — but individual test timing is not visible in the CI report.

Final verdict

Ready to merge — no blocking issues.

The implementation is correct, well-isolated, and consistent with the clean --all pattern already in the repo. All three safety invariants (LadybugDB single-writer, no new fs.rm surfaces, mutual-exclusion checks before I/O) are preserved. CI is green on all platforms. The one NON-BLOCKING gap (missing --allow-duplicate-name test) is real but the code path itself is wired and verified by inspection — acceptable to merge and add the test as a follow-up.


* (missing-path entries). 1 when at least one entry's child exited
* non-zero — surfaces batch partial-failure to cron / CI.
*/
const analyzeAllBranch = async (options: AnalyzeOptions | undefined): Promise<void> => {

@magyargergo magyargergo Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We need to be careful with this! Imagine having 100s of cloned repositories and somebody runs GitNexus with --all option and there's a possibility it may crash the computer because of CPU exhaustion. I'd implement a queue and depending on resource availability i'd start processing them. Also don't forget the pipeline itself also spawns subproceses. Maybe it would be faster to not to call the whole gitnexus but to call the ingestion pipeline with the necessary arguments.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review — good catch, and I want to make sure I implement the right queue shape rather than guess and iterate.

Current state (for calibration)

The current implementation is strictly sequentialspawnSync blocks the for-loop until each child exits (analyze.ts:190–198), so only ONE analyze runs at any moment. Pipeline-internal parse workers are already CPU-bounded inside each child. That said, your concern about sustained load over 100s of repos is still valid — even sequential, that's hours of 100% CPU with no graceful pause.

Which queue shape do you have in mind?

A few options with tradeoffs; happy to implement whichever you prefer:

(a) Throttled sequential with resource check. Stay 1-at-a-time, but check os.loadavg() between children and pause if load > threshold. Downside: os.loadavg() returns [0, 0, 0] on Windows, so the gate is Unix-only unless we proxy through something else.

(b) --concurrency <n> with default 1. Opt-in parallelism for users who know their machine can handle it. Default 1 = current sequential behaviour. Downside: contradicts the CPU-exhaustion framing — parallel makes CPU pressure worse, not better — so I don't think this is what you meant, flagging for disambiguation.

(c) Confirmation gate + estimated duration. Print "About to re-index N repos sequentially. Estimated time: ~{N × avg}s. Pass --yes to confirm." and require -y, --yes for non-interactive runs. Same pattern as clean --all --force. Makes the resource cost visible before the user commits to it.

(d) Something else — happy to build it if you have a specific shape in mind.

The pipeline-direct suggestion

On "call the ingestion pipeline with the necessary arguments" — I deliberately went child-process-per-repo to keep LadybugDB handles, progress bar, SIGINT, and the monkey-patched console cleanly isolated per repo. Calling runFullAnalysis in-process would save ~1–2s process-startup per repo but means (i) a crash in one repo aborts the batch, (ii) state-reset hazards between iterations we'd need to prove idempotent. Happy to revisit if the queue design ends up needing in-process for some other reason — but I'd rather not bundle it in unless there's a concrete motivation.

Will hold on code changes until you point at the shape you want. 🙏

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is very primitive but could be a good starting point

import si from 'systeminformation';

const CPU_THRESHOLD = 80;
const MEM_THRESHOLD = 85;

setInterval(async () => {
  const [load, mem] = await Promise.all([
    si.currentLoad(),
    si.mem(),
  ]);

  const cpu = load.currentLoad;                  // %
  const memUsed = (mem.used / mem.total) * 100; // %

  const shouldThrottle = cpu > CPU_THRESHOLD || memUsed > MEM_THRESHOLD;

  console.log({
    cpu: cpu.toFixed(1) + '%',
    mem: memUsed.toFixed(1) + '%',
    shouldThrottle,
  });
}, 1000);

More information about the npm package: https://www.npmjs.com/package/systeminformation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the concrete pointer — implemented in `1fb6bc36` using `systeminformation` exactly as you sketched.

What shipped

Defaults match your sketch 1:1: 80% CPU / 85% memory thresholds, 1-second poll interval, simple `cpu > CPU_THRESHOLD || memUsed > MEM_THRESHOLD` predicate.

Shape: between each `--all` child spawn, the batch polls CPU + memory. If either metric is above threshold, it prints `⏸ Throttling — CPU X.X% / mem Y.Y% (thresholds 80% / 85%) — waiting before [i/N]...` and re-polls every second. When both drop below threshold, it prints `▶ Resuming — CPU X.X% / mem Y.Y%` and spawns the next child.

No max-wait timeout by design. A host pinned above threshold is exactly the case you asked us to protect, so bailing after some arbitrary deadline would silently defeat the safety. Ctrl-C is the user's graceful exit.

Escape hatches:

  • `--no-throttle` CLI flag for dedicated CI / build agents that accept the full resource cost
  • `GITNEXUS_THROTTLE_CPU` / `GITNEXUS_THROTTLE_MEM` env vars for advanced tuning without code changes

On broken metrics provider (e.g. `systeminformation` platform issue): the batch falls through WITHOUT throttling rather than hanging indefinitely. Unit-tested.

Pipeline-direct suggestion

Kept child-process-per-repo. The isolation story (each repo gets fresh LadybugDB handles, progress bar, monkey-patched console, SIGINT handler) is what lets one repo crash without corrupting the batch. Happy to revisit if you want to swap in-process for a specific reason later — but since your main concern (resource pressure) is now addressed at the orchestration layer, I'd rather not bundle that bigger change in here.

Tests

  • 15 new unit tests on `resource-throttle.ts` covering the predicate (both-under / CPU-only / mem-only / both-over / boundary), polling trajectory (idle-on-first-poll, multi-poll-then-clear), provider-throw tolerance, and env-var parsing (valid / NaN / 0 / negative / >100 / empty / fractional).
  • 1 new integration test locks in `--no-throttle` bypass: sets thresholds to 1% (which would hang indefinitely without bypass) and asserts the batch completes AND the Throttling messaging is absent from output.

Local verification on the final commit:

  • `tsc --noEmit` clean
  • `resource-throttle.test.ts` 15/15
  • Full unit suite 4216 pass (2 pre-existing git-utils env failures unchanged, unrelated — exist on pristine upstream main)
  • `cli-e2e.test.ts -t "analyze --all|remove|clean --all|analyze --name|no-throttle"` 11/11 in 178s

CI running.

@magyargergo

Copy link
Copy Markdown
Collaborator

@azizur100389 Please take into consideration the commend I left about CPU exhaustion. 🙏

…1010 review)

Address @magyargergo's CPU-exhaustion concern on PR abhigyanpatwari#1010. Per his
review sketch, pause between --all iterations when CPU > 80% or
memory > 85% so the batch paces itself to the host's real available
headroom rather than running flat-out across 100s of repos.

- New src/cli/resource-throttle.ts with:
  - Pure shouldThrottle(cpu, mem, thresholds) predicate
  - waitForResourceAvailability polling loop (no max-wait by design:
    the reviewer wants the protection to actually work, and Ctrl-C
    is the user's exit)
  - readThresholdsFromEnv honouring GITNEXUS_THROTTLE_CPU and
    GITNEXUS_THROTTLE_MEM for advanced tuning
  - Lazy import of systeminformation so CLI startup cost is unchanged
    when --all isn't used
  - Metrics-provider contract as a test seam — a broken provider
    (throw / platform issue) falls through WITHOUT hanging the batch
    (regression test locks this in)
- analyze --all now calls waitForResourceAvailability before each
  child spawn; prints Throttling and Resuming messages with the
  current percentages at the right moments
- --no-throttle CLI flag disables the check entirely for dedicated
  CI / build agents that accept the full resource cost
- Commander 14 stores --no-throttle as throttle: false; AnalyzeOptions
  field name follows that convention
- Uses systeminformation (already suggested by @magyargergo) — added
  as a runtime dependency
- 15 new unit tests cover the predicate, polling trajectory, provider-
  throw tolerance, and env parsing (including fallback on NaN / zero /
  negative / over-100 / empty)
- 1 new integration test proves --no-throttle actually bypasses the
  loop by setting the threshold to 1% (which would hang indefinitely
  without --no-throttle)

Defaults (80% / 85%) match the reviewer's sketch exactly. No
destructive operations added; throttling is additional caution, not
reduced caution.
@github-actions

github-actions Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 2m 53s —— View job


Production-ready bar for this PR

This PR adds analyze --all (CLI orchestration) with a resource-aware throttle. Based on AGENTS.md, GUARDRAILS.md, CONTRIBUTING.md, TESTING.md:

  • TypeScript compile-clean: tsc --noEmit passes in gitnexus/ — no type regressions.
  • Full vitest suite passes (unit + integration on all 3 platforms).
  • LadybugDB single-writer invariant preserved: each repo must be processed by one child at a time; no concurrent writes to the same .gitnexus/lbug.
  • No new fs.rm/destructive filesystem ops without assertSafeStoragePath guards (GUARDRAILS.md §Signs / §LadybugDB lock).
  • Mutual-exclusion checks fire before any I/O — user sees a clear error and exit 1.
  • New dependencies addition is auditable — minimal surface, lazy-loaded, tested with injected provider; no native build step required at install.
  • Integration tests cover happy path, empty registry, mutual exclusion (path, name), and missing-path tolerance per TESTING.md.
  • No MCP tool/contract changes — group_*, query, context, impact, detect_changes, rename, cypher all unaffected.
  • No secrets, tokens, or machine-specific paths committed (GUARDRAILS.md §Non-negotiables).
  • PR title follows conventional-commit format (feat(cli): ... / fix(cli): ...).

Findings

1. Correctness & Functional Completeness

No blocking correctness issues.

  • --all correctly excluded from forwarded (analyze.ts:156–162). Infinite recursion is impossible. ✓
  • process.exitCode = 1 (not process.exit(1)) used for the --all branch (analyze.ts:265). No LadybugDB handles are open in the parent for the --all path. ✓
  • result.status === 0 check (analyze.ts:241): signal-killed children (status === null) fall correctly into the failure branch. ✓
  • options?.throttle !== false check (analyze.ts:176): Commander sets throttle: true (not undefined) as default when only --no-<option> is defined. The check handles true, undefined, and false correctly — all evaluate correctly. ✓
  • waitForResourceAvailability is called BEFORE printing the [i/N] Analyzing: heading (analyze.ts:184–203), so the heading accurately reflects work that is actually starting. ✓
  • defaultMetricsProvider uses systeminformation via lazy dynamic import (resource-throttle.ts:92) — import cost is not paid on every CLI invocation, only when --all is used. ✓
  • Provider-throw fallthrough (resource-throttle.ts:155–158) ensures a broken systeminformation install on any platform never hangs the batch. ✓

NON-BLOCKING: No --allow-duplicate-name mutual-exclusion integration test

  • Evidence: analyze.ts:288if (options.allowDuplicateName) violations.push('--allow-duplicate-name') is wired and covered by the previous automated review. But no integration test exercises this case (the new tests cover [path] and --name only).
  • Why it matters: The PR description claims all three per-repo flags are mutually exclusive with --all; two of three have tests. The gap is minimal (one-liner to add) and the code path is identical in shape to the tested cases.
  • Recommendation: Add a 6th test: runCliWithEnv(['analyze', '--all', '--allow-duplicate-name'], ...) → exit 1 + output matching /--all cannot be combined with/i. Can be a follow-up.
  • Confidence: High (verified by inspection of both test file and implementation).

NON-BLOCKING: Indefinite hang if machine stays permanently above throttle threshold

  • Evidence: resource-throttle.ts:151–168while (true) loop with no max-wait timeout. waitForResourceAvailability will poll forever if shouldThrottle never returns false. The only user escape is Ctrl-C.
  • Why it matters: A cron job running gitnexus analyze --all on a machine under sustained load (stuck process, runaway GC, etc.) will hang indefinitely. The batch never proceeds and never fails — it disappears silently into the throttle loop. CI agents using --all that forget --no-throttle would also hang if the agent hits a memory spike.
  • Recommendation: A --throttle-timeout <seconds> escape hatch would let cron jobs fail-fast and report rather than hang. The current --no-throttle bypass requires knowing the problem exists in advance. That said, the PR explicitly chose this design and documented the reasoning — the reviewer who requested throttling (@magyargergo) accepted this shape. Acceptable as-is with the --no-throttle escape documented in help text.
  • Confidence: High (verified from code structure; behavior is by design per JSDoc).

2. Code Clarity & Clean Code

No issues found with naming, cohesion, dead code, or repo conventions.

NIT: --verbose forwarded twice (env var + flag arg)

  • Evidence: analyze.ts:271–273 sets process.env.GITNEXUS_VERBOSE = '1' in the parent process before the --all branch. analyzeAllBranch also pushes '--verbose' into forwarded at analyze.ts:162. Children then inherit the env var AND receive --verbose in argv (analyze.ts:234).
  • Why it matters: Harmless — the child's analyzeCommand re-sets GITNEXUS_VERBOSE=1 from the flag anyway. The double mechanism is redundant but not incorrect.
  • Recommendation: Either rely on env inheritance (drop --verbose from forwarded) or rely on the forwarded flag (don't mutate process.env before delegating). Pick one for clarity.
  • Confidence: High.

3. Test Coverage & Change Safety

  • 15 unit tests in test/unit/resource-throttle.test.ts covering the pure predicate (all boundary/quadrant cases), polling trajectory (idle-on-first-poll, multi-poll-then-clear, provider-throw), and env-var parsing (valid, NaN, 0, negative, >100, fractional). ✓
  • Unit tests inject fake metrics provider and fake sleep — no dependency on host CPU state. Correct approach per TESTING.md §Unit. ✓
  • 6 integration tests in test/integration/cli-e2e.test.ts covering: happy path (2 repos re-indexed, indexedAt advances), empty registry, [path] exclusion, --name exclusion, missing-path tolerance, and --no-throttle bypass. ✓
  • --no-throttle integration test sets threshold to 1% (always throttled on any real machine), verifies batch completes without Throttling output. Regression guard is meaningful, not tautological. ✓
  • Happy-path test uses a 1.1s spin-wait to guarantee ISO-second indexedAt advances — pragmatic approach for synchronous test. ✓
  • tsc --noEmit clean (CI stage ✅). All 6934 tests pass across 3 platforms (CI ✅). Coverage delta of -0.2% is within acceptable range.
  • See NON-BLOCKING above for the missing --allow-duplicate-name test.

4. Performance

No performance issues in this area.

spawnSync (synchronous, serial) is correct and intentional — parallel spawning would introduce LadybugDB single-writer contention. The waitForResourceAvailability async poll runs between children (not during analysis), adding at most one systeminfo round-trip per repo in the non-throttled case (fast-path return on first poll). No O(n²) patterns, no Cypher round-trips, no heap allocations beyond a small forwarded array.

The systeminformation dynamic import is lazy — await import('systeminformation') inside defaultMetricsProvider. The import is re-executed on each waitForResourceAvailability call, but Node's module cache means it only loads once. ✓


5. Operational Risk

No new operational risks introduced.

  • LadybugDB single-writer: spawnSync (blocking) ensures strict serial execution. Even the degenerate case of a repo registered twice is safe. ✓
  • systeminformation cross-platform: si.mem() semantics differ slightly between Linux (excludes buffers/cache) and macOS/Windows. On Linux mem.used includes application memory only; on Windows it reflects committed bytes. This means the 85% memory threshold may trigger at different absolute loads across platforms — acceptable for a best-effort throttle.
  • MCP contracts unchanged: No changes to tools.ts, resources.ts, server.ts, or any MCP handler. ✓
  • Rollback safety: Reverting removes the --all flag and the resource-throttle.ts module. No graph schema changes, no storage format changes, no migration required. Safe to revert. ✓
  • Embeddings preserved: --embeddings is in forwarded (analyze.ts:157). ✓
  • Windows process.argv[1] concern for globally-installed npm packages (.cmd wrapper vs .js entrypoint) is pre-existing and not introduced here. Cannot be verified from diff.

6. Maintainability

  • analyzeAllBranch is defined at the top of analyze.ts with a JSDoc explaining isolation rationale and forwarded flags. Next contributor knows where to extend (add flag → add to forwarded). ✓
  • resource-throttle.ts is a standalone module with a clean provider seam (MetricsProvider) for testing. The pure predicate shouldThrottle is directly unit-testable. ✓
  • Batch orchestration lives in analyze.ts (CLI layer) — not duplicated into server/api.ts or gitnexus-shared/. ✓
  • Pipeline Phase DAG is unaffected — --all is a CLI-layer wrapper. ✓

Non-Negotiables

Check Result
No secrets / tokens committed ✅ Verified
No blind find-and-replace renames ✅ Verified
No HIGH/CRITICAL impact left unaddressed ✅ New CLI flag, no shared symbol edits
PR titles follow conventional-commit format feat(cli): ... and fix(cli): ...
PolyForm license metadata untouched ✅ Verified (package.json:license unchanged)
New dependency auditable + in dependencies (not devDependencies) systeminformation@^5.31.5 — runtime dep, lazy-loaded, CI green on 3 platforms
Docs updated for public CLI contract change --no-throttle, GITNEXUS_THROTTLE_CPU, GITNEXUS_THROTTLE_MEM documented in help text (index.ts:47–59)

What I could not fully verify

  1. Windows process.argv[1] format for a globally-installed npm binary. On Windows, the global shim may resolve to a .cmd wrapper path, not the .js entry. Cannot verify from diff.
  2. systeminformation mem.used semantics across platforms — whether the 85% threshold triggers at consistent "effective" memory utilization on macOS vs Linux vs Windows.
  3. Whether the spin-wait in the happy-path integration test (Date.now() < waitUntil loop) reliably creates a distinct ISO-second timestamp boundary in all CI environments (very fast machines). In practice, 1.1s is generous and CI completed in 304s total, suggesting this is reliable.
  4. listRegisteredRepos() error handling for a malformed registry.json — would surface as an unhandled rejection. Pre-existing concern, not introduced here.

Final verdict

Ready to merge — no blocking issues.

The throttle implementation directly addresses @magyargergo's CPU-exhaustion concern. It is well-designed: a pure, mockable predicate; a lazy-loaded systeminformation provider; safe provider-throw fallthrough; and a clean --no-throttle escape for CI agents. Unit coverage (15 tests) and integration coverage (6 tests) are thorough. CI is green on all three platforms.

The two NON-BLOCKING gaps (missing --allow-duplicate-name test; indefinite hang on perpetually-throttled machine) are both documented, pre-accepted tradeoffs or minor follow-up items. Neither blocks merge.

@magyargergo

magyargergo commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

--no-throttle CLI flag for dedicated CI / build agents that accept the full resource cost

I'm not sure about this! I don't think we should let LLMs to be able to bypass this. Generally speaking, it is good to keep in mind that reseource exhaustion comes with a great cost.

GITNEXUS_THROTTLE_CPU / GITNEXUS_THROTTLE_MEM env vars for advanced tuning without code changes

I'd put a warning on overusing resrouces

On "call the ingestion pipeline with the necessary arguments" — I deliberately went child-process-per-repo to keep LadybugDB handles, progress bar, SIGINT, and the monkey-patched console cleanly isolated per repo. Calling runFullAnalysis in-process would save ~1–2s process-startup per repo but means (i) a crash in one repo aborts the batch, (ii) state-reset hazards between iterations we'd need to prove idempotent. Happy to revisit if the queue design ends up needing in-process for some other reason — but I'd rather not bundle it in unless there's a concrete motivation.

I don't think this is a good argument, mainly because we will run subprocesses concurrently and the progressbar and other details from the subprocesses may cause a mess in the terminal. We need to write a manager for this that will handle this cleanly. Please try to spec out a ProcessManager for this featue that will directly run the pipeline instead of the whole GitNexus. Each pipeline will have its own connection with ladybug.

@azizur100389 This feature needs more work but with this we will head to the right direction.

…olds (abhigyanpatwari#1010 review)

Address 2 of 4 points from @magyargergo's round-2 review on PR abhigyanpatwari#1010.

Done in this commit:

- Remove the --no-throttle CLI flag entirely. Per his direction:
  "I don't think we should let LLMs to be able to bypass this.
  Resource exhaustion comes with a great cost." The safeguard is
  now non-bypassable by any user-surface flag; analyze --all always
  pauses when CPU > 80% or memory > 85%.

- Add warnIfThresholdsRisky (fires at analyze --all start when
  either GITNEXUS_THROTTLE_CPU or GITNEXUS_THROTTLE_MEM is set to
  >= 90%). Matches his "I'd put a warning on overusing resources".
  Operators can still tune the env vars, but if they push the
  threshold so high the safeguard barely engages, they see a clear
  one-time warning naming the specific metric at risk (CPU-only vs
  memory-only vs both).

- Remove the --no-throttle integration test; replace with an
  integration test that asserts the overuse warning fires with
  correct specificity (CPU=95% shown, memory= not shown because
  default), and the batch still completes.

- 10 new unit tests on the warning helper cover defaults (no warn),
  CPU-only / memory-only / both-risky trigger paths, the 89.9%
  safe boundary, and the constant's documented 90% value.

Deferred to a follow-up PR (the other 2 review points):

- Concurrent execution across repos
- ProcessManager that runs the ingestion pipeline in-process
  (instead of spawning the whole gitnexus CLI) with per-repo
  LadybugDB connections

Rationale for deferring: this is a significant architecture shift —
concurrent LadybugDB connections (the single-writer invariant @magyargergo
himself flagged earlier needs to be revisited), progress-bar
multiplexing for concurrent children, and state-reset hazards for
in-process pipeline calls. Too large to bundle onto an already-wide
PR. Will open a separate RFC-style PR to spec the ProcessManager
design before implementation.

Local verification:
- tsc --noEmit clean
- resource-throttle.test.ts 25/25 (10 new warning-helper tests)
- Full unit suite 4226 pass + 2 pre-existing git-utils env failures
- cli-e2e.test.ts -t "analyze --all|remove|clean --all|analyze --name|throttle" 11/11 in 179s
@azizur100389

Copy link
Copy Markdown
Contributor Author

@magyargergo — thanks for the round-2 direction. Pushed `6892c0d5`.

2 of 4 addressed in this commit

1. Removed `--no-throttle`
You're right that a CLI bypass defeats the purpose. Flag fully removed from `index.ts`, the `AnalyzeOptions` interface, the help text, and the integration test. The safeguard on `analyze --all` is now non-bypassable — it always pauses when CPU > 80% or memory > 85%.

2. Warning on overusing resources
New `warnIfThresholdsRisky` helper fires a one-time warning at `analyze --all` start when either `GITNEXUS_THROTTLE_CPU` or `GITNEXUS_THROTTLE_MEM` is set to ≥ 90%. Warning names the specific metric at risk (e.g. `CPU=95%`, memory not mentioned if memory is at default) and recommends the defaults. Operators can still tune the env vars, but if they push thresholds so high the safeguard barely engages, they see it explicitly.

Tests: 10 new unit tests on the warning helper (defaults / CPU-only / memory-only / both-risky / 89.9% boundary / constant check) and a new integration test that exercises the full CLI path — sets `GITNEXUS_THROTTLE_CPU=95`, asserts the warning fires with correct specificity (CPU-only, no false memory mention), and that the batch still completes.

2 of 4 deferred to a follow-up PR — significant architecture shift

3. Concurrent execution + 4. ProcessManager running the pipeline in-process with per-repo LadybugDB connections

I've deferred these to a separate follow-up PR rather than bundling them on #1010. Reason: this is a significant architecture shift with real design questions, not a mechanical change:

  • LadybugDB concurrency model — the single-writer invariant you flagged yourself earlier applies per-process-per-db. In-process concurrent pipelines each holding their own `.gitnexus/lbug` connection is likely fine (different DB files), but needs verification. Worth spec-ing rather than trying blind.
  • State-reset hazards — `runFullAnalysis` in the same process needs its LadybugDB handle, SIGINT handler, monkey-patched console, and progress bar reset cleanly between iterations. Currently analyze-command does these once, at top level. Factoring it into a reusable per-iteration callable is a refactor in itself.
  • Progress-bar multiplexing — you're right the current `stdio: 'inherit'` approach would be messy with concurrency. Multi-bar orchestration (or a status-line model) needs a concrete design, not a quick hack.
  • Concurrency pacing — how many concurrent pipelines? Static limit? Dynamic based on the same `systeminformation` polling? Needs a decision.

Proposal

Ship #1010 with the trim above (the non-bypassable safeguard + env warning) as the "headed in the right direction" first step you called out, and I'll open a separate RFC-style PR to spec the `ProcessManager` design — you can weigh in on the shape before I implement. That keeps the architecture work visible and reviewable, rather than hidden inside a feature PR.

If you'd rather block #1010 on the full redesign, say so and I'll either (a) close #1010 and open a fresh PR starting from the ProcessManager design, or (b) spike the ProcessManager here — your call.

Local verification on 6892c0d

  • `tsc --noEmit` clean
  • `resource-throttle.test.ts` 25/25 (10 new warning-helper tests)
  • Full unit suite 4226 pass + 2 pre-existing git-utils env failures (unchanged)
  • `cli-e2e.test.ts -t "analyze --all|remove|clean --all|analyze --name|throttle"` 11/11 in 179s

@magyargergo

Copy link
Copy Markdown
Collaborator

I'm sorry but we cannot ship your changes without it. We must maintain high-quality and good user experinece. This would move it to the wrong direction.

@magyargergo magyargergo self-requested a review April 21, 2026 20:39
@azizur100389

azizur100389 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

You're right — I'll build the ProcessManager as you described: concurrent execution, pipeline called directly (not via a spawned gitnexus CLI), each pipeline with its own LadybugDB connection, clean terminal.

Implementation approach. Each pipeline will run in a worker_threads worker. lbug-adapter.ts:172-174 currently holds the db handle as a module-level global guarded by a process-wide sessionLock — so each worker booting its own module graph gives each pipeline its own isolated adapter instance and its own LadybugDB connection, exactly as you asked, without needing a cross-cutting refactor of the adapter. The master process owns a single cli-progress MultiBar and pumps per-worker progress in via parentPort.postMessage, so the terminal stays coherent under concurrency. Crash isolation is preserved by the worker boundary.

Defaults I'll use unless you want different:

  • Concurrency cap: min(4, Math.floor(os.cpus() / 2)), overridable via GITNEXUS_MAX_CONCURRENCY
  • Throttle + env-var warning from 6892c0d5 stay — they now gate worker launches instead of spawnSync calls
  • SIGINT during --all: terminate all active workers, exit 130

OK to proceed with this shape?

Repository owner deleted a comment from github-actions Bot Apr 22, 2026
@azizur100389 azizur100389 deleted the feat/analyze-all branch April 22, 2026 22:13
@azizur100389 azizur100389 restored the feat/analyze-all branch April 22, 2026 22:14
@azizur100389 azizur100389 reopened this Apr 22, 2026
@azizur100389

Copy link
Copy Markdown
Contributor Author

Thanks for the direction on this one, @magyargergo. Closing this PR — the ProcessManager / worker_threads / in-process-pipeline architecture you've outlined is substantial enough to warrant its own focused PR rather than being bolted on top of this one. Mixing a significant architecture review with ~500 lines of partial implementation muddies both.

Two things from this branch worth preserving for the redesign:

  • src/cli/resource-throttle.ts + 25 unit tests — the predicate, polling loop, env-var parsing, and warnIfThresholdsRisky are architecture-agnostic. They gate spawnSync calls today but work identically gating worker_threads launches. The 80/85 defaults you endorsed stay as-is.
  • The 6 --all integration tests — they assert observable CLI behaviour (exit codes, summary shape, registry state, mutual-exclusion errors), not spawn mechanics. Lift cleanly.

Happy to open a doc-only RFC PR for the ProcessManager design separately if that's useful — the architecture review then happens on a diff that's specifically about shape, not tangled with code.

Your point about non-bypassable resource safeguards landed, and the 80/85 + env-var warning design is what ships. That's the piece of this PR that doesn't go to waste.

@magyargergo

Copy link
Copy Markdown
Collaborator

Apologies for any inconvenience this may have caused. I agree that this may need some additional thought. It would be best to open a new issue so we can discuss the design and agree on the right direction before moving forward with the PR. We appreciate the effort you’ve put into this work, and there is clear value in it as it stands.

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.

feat: analyze --all to re-index all known repositories

2 participants