gitnexushub 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -592,10 +592,16 @@ async function handlePostToolUse(input, config, entry, agentName) {
592
592
  const exitCode = readExitCode(out);
593
593
  if (exitCode !== null && exitCode !== 0) return;
594
594
 
595
+ // Use the resolved registered repo path (entry.localPath) rather
596
+ // than input.cwd. Cursor 3.x passes input.cwd: "" and spawns hooks
597
+ // from ~/.cursor, which is not a git repo — `git rev-parse HEAD`
598
+ // there returns empty, localHead becomes "", and the staleness
599
+ // check bails before even hitting /meta. The registry entry already
600
+ // has the canonical absolute path; use it.
595
601
  let localHead = '';
596
602
  try {
597
603
  const r = spawnSync('git', ['rev-parse', 'HEAD'], {
598
- cwd: input.cwd || process.cwd(),
604
+ cwd: entry.localPath,
599
605
  encoding: 'utf-8',
600
606
  timeout: 2000,
601
607
  });
@@ -729,52 +735,104 @@ async function runKiroEditCapture(agentName) {
729
735
 
730
736
  const entries = readRegistry();
731
737
  const cwd = process.cwd();
732
- const entry = resolveCwdToRepo(cwd, entries);
733
- if (!entry) return;
734
-
735
- // Two parallel git invocations: tracked-file modifications via
736
- // `git diff --name-only HEAD` (catches modified + staged + deleted-
737
- // tracked), and untracked files via `git ls-files --others
738
- // --exclude-standard`. Both produce one absolute-relative path per
739
- // line, no status-code prefix far more robust than parsing
740
- // `git status --porcelain` (whose XY field varies across git
741
- // versions and edge cases).
742
- const collected = new Set();
743
- for (const args of [
744
- ['diff', '--name-only', 'HEAD'],
745
- ['ls-files', '--others', '--exclude-standard'],
746
- ]) {
747
- try {
748
- const r = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 2000 });
749
- const out = (r.stdout || '').trim();
750
- if (!out) continue;
751
- for (const line of out.split('\n')) {
752
- const trimmed = line.trim();
753
- if (trimmed) collected.add(trimmed);
738
+
739
+ // Pick which repos to scan. Two cases:
740
+ //
741
+ // A. cwd is INSIDE a registered repo (e.g. Kiro opened directly
742
+ // on the project root, or any child path). Standard
743
+ // resolveCwdToRepo path scan that single repo.
744
+ //
745
+ // B. cwd is the PARENT of one or more registered repos (e.g.
746
+ // Kiro opened on `~/AkonLabs/` with multiple sub-repos
747
+ // inside). resolveCwdToRepo returns null in this case
748
+ // because the cwd↔localPath direction is reversed. Find all
749
+ // registered repos whose localPath is inside cwd and scan
750
+ // each. The single-repo case (A) takes precedence so we
751
+ // don't double-scan when both relations match.
752
+ let resolvedCwd;
753
+ try {
754
+ resolvedCwd = fs.realpathSync(path.resolve(cwd));
755
+ } catch {
756
+ resolvedCwd = path.resolve(cwd);
757
+ }
758
+ const isWin = process.platform === 'win32';
759
+ const normCwd = isWin ? resolvedCwd.toLowerCase() : resolvedCwd;
760
+ const sep = path.sep;
761
+
762
+ /** @type {Array<{ hubRepoId: string, localPath: string }>} */
763
+ const targets = [];
764
+ const inside = resolveCwdToRepo(cwd, entries);
765
+ if (inside) {
766
+ targets.push({ hubRepoId: inside.hubRepoId, localPath: inside.localPath });
767
+ } else {
768
+ for (const entry of entries) {
769
+ if (!entry || !entry.localPath || !entry.hubRepoId) continue;
770
+ let ep;
771
+ try {
772
+ ep = fs.realpathSync(path.resolve(entry.localPath));
773
+ } catch {
774
+ ep = path.resolve(entry.localPath);
775
+ }
776
+ const nep = isWin ? ep.toLowerCase() : ep;
777
+ if (nep === normCwd || nep.startsWith(normCwd + sep)) {
778
+ targets.push({ hubRepoId: entry.hubRepoId, localPath: ep });
754
779
  }
755
- } catch {
756
- // Best effort — proceed with whatever we got from prior args.
757
780
  }
758
781
  }
759
- if (collected.size === 0) return;
760
-
761
- // Cap at 20 files per fire so a big rewrite or a fresh branch
762
- // pull doesn't hammer the hub with hundreds of edit-observed
763
- // posts. The 20 cap is arbitrary but matches the per-call
764
- // ladybugdb worker pool's typical session length.
765
- let posted = 0;
766
- for (const filePath of collected) {
767
- if (posted >= 20) break;
768
- const absPath = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
769
- await httpPostJson(`${config.hubUrl}/api/activity/edit-observed`, authHeaders(config), {
770
- sessionId: null,
771
- repoId: entry.hubRepoId,
772
- filePath: absPath,
773
- line: null,
774
- tool: 'Write',
775
- agentName,
776
- });
777
- posted++;
782
+ if (targets.length === 0) return;
783
+
784
+ // Run git diff + ls-files inside each target repo. Cap at 5 repos
785
+ // per fire so a `~/code/` workspace with 50 sibling repos doesn't
786
+ // spawn 100 git processes on every postToolUse. The 20-file cap
787
+ // is applied per-repo.
788
+ let postedTotal = 0;
789
+ const MAX_REPOS = 5;
790
+ const MAX_FILES_PER_REPO = 20;
791
+ for (const target of targets.slice(0, MAX_REPOS)) {
792
+ const collected = new Set();
793
+ for (const args of [
794
+ ['diff', '--name-only', 'HEAD'],
795
+ ['ls-files', '--others', '--exclude-standard'],
796
+ ]) {
797
+ try {
798
+ const r = spawnSync('git', args, {
799
+ cwd: target.localPath,
800
+ encoding: 'utf-8',
801
+ timeout: 2000,
802
+ });
803
+ const out = (r.stdout || '').trim();
804
+ if (!out) continue;
805
+ for (const line of out.split('\n')) {
806
+ const trimmed = line.trim();
807
+ if (trimmed) collected.add(trimmed);
808
+ }
809
+ } catch {
810
+ // Best effort
811
+ }
812
+ }
813
+ if (collected.size === 0) continue;
814
+
815
+ let perRepo = 0;
816
+ for (const filePath of collected) {
817
+ if (perRepo >= MAX_FILES_PER_REPO) break;
818
+ const absPath = path.isAbsolute(filePath) ? filePath : path.join(target.localPath, filePath);
819
+ await httpPostJson(`${config.hubUrl}/api/activity/edit-observed`, authHeaders(config), {
820
+ sessionId: null,
821
+ repoId: target.hubRepoId,
822
+ filePath: absPath,
823
+ line: null,
824
+ tool: 'Write',
825
+ agentName,
826
+ });
827
+ perRepo++;
828
+ postedTotal++;
829
+ }
830
+ }
831
+
832
+ if (process.env.GITNEXUS_DEBUG) {
833
+ process.stderr.write(
834
+ `kiro-edit-capture: targets=${targets.length} posted=${postedTotal}\n`,
835
+ );
778
836
  }
779
837
  }
780
838
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexushub",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Connect your editor to GitNexus Hub — one command MCP setup + project context",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",