@hyperdrive.bot/gut 0.2.3 → 0.2.4

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.
package/README.md CHANGED
@@ -48,7 +48,7 @@ $ npm install -g @hyperdrive.bot/gut
48
48
  $ gut COMMAND
49
49
  running command...
50
50
  $ gut (--version)
51
- @hyperdrive.bot/gut/0.2.3-alpha.3 linux-x64 node-v22.22.3
51
+ @hyperdrive.bot/gut/0.2.4-alpha.0 linux-x64 node-v22.22.3
52
52
  $ gut --help [COMMAND]
53
53
  USAGE
54
54
  $ gut COMMAND
@@ -33,8 +33,8 @@ export default class Context extends BaseCommand {
33
33
  : focusedEntities;
34
34
  // Enrich each entity with branch + uncommitted status. Entities may not
35
35
  // exist on disk (not cloned), so be defensive.
36
- const enriched = [];
37
- for (const entity of entitiesToReport) {
36
+ const { existsSync } = await import('node:fs');
37
+ const enrichOne = async (entity) => {
38
38
  const relativePath = entity.path.replace(/^\.\//, '');
39
39
  const absolutePath = path.isAbsolute(entity.path)
40
40
  ? entity.path
@@ -43,27 +43,31 @@ export default class Context extends BaseCommand {
43
43
  let hasUncommitted = false;
44
44
  let pathExists = false;
45
45
  try {
46
- const { existsSync } = await import('node:fs');
47
46
  pathExists = existsSync(path.join(absolutePath, '.git')) || existsSync(absolutePath);
48
47
  }
49
48
  catch {
50
49
  pathExists = false;
51
50
  }
52
51
  if (pathExists) {
52
+ // context only needs branch + dirty flag. Call git directly (2 spawns)
53
+ // instead of getCurrentBranch + hasChanges, which together re-resolve
54
+ // the branch and compute ahead/behind tracking (rev-parse @{u} +
55
+ // rev-list) that this command never uses — ~5 spawns per entity.
53
56
  try {
54
- currentBranch = await this.gitService.getCurrentBranch(absolutePath);
57
+ currentBranch = (await this.gitService.exec(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: absolutePath })).trim() || null;
55
58
  }
56
59
  catch {
57
60
  currentBranch = null;
58
61
  }
59
62
  try {
60
- hasUncommitted = await this.gitService.hasChanges(absolutePath);
63
+ const porcelain = await this.gitService.exec(['status', '--porcelain'], { cwd: absolutePath });
64
+ hasUncommitted = porcelain.trim().length > 0;
61
65
  }
62
66
  catch {
63
67
  hasUncommitted = false;
64
68
  }
65
69
  }
66
- enriched.push({
70
+ return {
67
71
  currentBranch,
68
72
  focused: focusedNames.has(entity.name),
69
73
  hasUncommitted,
@@ -71,8 +75,26 @@ export default class Context extends BaseCommand {
71
75
  path: relativePath,
72
76
  pathExists,
73
77
  type: entity.type,
74
- });
75
- }
78
+ };
79
+ };
80
+ // Enrich entities concurrently with a bounded worker pool. A sequential
81
+ // await-loop across hundreds of submodules spawns ~4 git processes each
82
+ // (branch + status + tracking) and takes minutes on a large workspace;
83
+ // a bounded pool keeps the process count sane while finishing in seconds.
84
+ // Results are written by index so output order matches entitiesToReport.
85
+ const CONCURRENCY = 24;
86
+ const enriched = Array.from({ length: entitiesToReport.length });
87
+ let cursor = 0;
88
+ const workers = Array.from({ length: Math.min(CONCURRENCY, entitiesToReport.length) }, async () => {
89
+ while (true) {
90
+ const index = cursor;
91
+ cursor += 1;
92
+ if (index >= entitiesToReport.length)
93
+ break;
94
+ enriched[index] = await enrichOne(entitiesToReport[index]);
95
+ }
96
+ });
97
+ await Promise.all(workers);
76
98
  if (flags.json) {
77
99
  const payload = {
78
100
  entities: enriched,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/gut",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Git Unified Tooling - Enhanced git with workspace intelligence for entity-based organization",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",