agentsys 5.0.2 → 5.1.0

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.
Files changed (264) hide show
  1. package/.claude-plugin/marketplace.json +21 -14
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +2 -1
  4. package/CHANGELOG.md +24 -1
  5. package/README.md +7 -6
  6. package/adapters/codex/skills/agnix/SKILL.md +0 -1
  7. package/adapters/codex/skills/audit-project/SKILL.md +0 -1
  8. package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
  9. package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
  10. package/adapters/codex/skills/consult/SKILL.md +133 -59
  11. package/adapters/codex/skills/debate/SKILL.md +214 -0
  12. package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
  13. package/adapters/codex/skills/deslop/SKILL.md +0 -1
  14. package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
  15. package/adapters/codex/skills/enhance/SKILL.md +0 -1
  16. package/adapters/codex/skills/learn/SKILL.md +0 -1
  17. package/adapters/codex/skills/next-task/SKILL.md +0 -1
  18. package/adapters/codex/skills/perf/SKILL.md +0 -1
  19. package/adapters/codex/skills/repo-map/SKILL.md +0 -1
  20. package/adapters/codex/skills/ship/SKILL.md +0 -1
  21. package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
  22. package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
  23. package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
  24. package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
  25. package/adapters/opencode/agents/agent-enhancer.md +0 -1
  26. package/adapters/opencode/agents/agnix-agent.md +0 -1
  27. package/adapters/opencode/agents/ci-fixer.md +0 -1
  28. package/adapters/opencode/agents/ci-monitor.md +0 -1
  29. package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
  30. package/adapters/opencode/agents/consult-agent.md +123 -31
  31. package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
  32. package/adapters/opencode/agents/debate-orchestrator.md +169 -0
  33. package/adapters/opencode/agents/delivery-validator.md +0 -1
  34. package/adapters/opencode/agents/deslop-agent.md +0 -1
  35. package/adapters/opencode/agents/docs-enhancer.md +0 -1
  36. package/adapters/opencode/agents/exploration-agent.md +0 -1
  37. package/adapters/opencode/agents/hooks-enhancer.md +0 -1
  38. package/adapters/opencode/agents/implementation-agent.md +0 -1
  39. package/adapters/opencode/agents/learn-agent.md +0 -1
  40. package/adapters/opencode/agents/map-validator.md +0 -1
  41. package/adapters/opencode/agents/perf-analyzer.md +0 -1
  42. package/adapters/opencode/agents/perf-code-paths.md +0 -1
  43. package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
  44. package/adapters/opencode/agents/perf-orchestrator.md +0 -1
  45. package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
  46. package/adapters/opencode/agents/perf-theory-tester.md +0 -1
  47. package/adapters/opencode/agents/plan-synthesizer.md +0 -1
  48. package/adapters/opencode/agents/planning-agent.md +0 -1
  49. package/adapters/opencode/agents/plugin-enhancer.md +0 -1
  50. package/adapters/opencode/agents/prompt-enhancer.md +0 -1
  51. package/adapters/opencode/agents/simple-fixer.md +0 -1
  52. package/adapters/opencode/agents/skills-enhancer.md +0 -1
  53. package/adapters/opencode/agents/sync-docs-agent.md +0 -1
  54. package/adapters/opencode/agents/task-discoverer.md +0 -1
  55. package/adapters/opencode/agents/test-coverage-checker.md +0 -1
  56. package/adapters/opencode/agents/worktree-manager.md +0 -1
  57. package/adapters/opencode/commands/agnix.md +0 -1
  58. package/adapters/opencode/commands/audit-project-agents.md +0 -1
  59. package/adapters/opencode/commands/audit-project-github.md +0 -1
  60. package/adapters/opencode/commands/audit-project.md +0 -1
  61. package/adapters/opencode/commands/consult.md +134 -59
  62. package/adapters/opencode/commands/debate.md +224 -0
  63. package/adapters/opencode/commands/delivery-approval.md +0 -1
  64. package/adapters/opencode/commands/deslop.md +0 -1
  65. package/adapters/opencode/commands/drift-detect.md +0 -1
  66. package/adapters/opencode/commands/enhance.md +0 -1
  67. package/adapters/opencode/commands/learn.md +0 -1
  68. package/adapters/opencode/commands/next-task.md +0 -1
  69. package/adapters/opencode/commands/perf.md +0 -1
  70. package/adapters/opencode/commands/repo-map.md +0 -1
  71. package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
  72. package/adapters/opencode/commands/ship-deployment.md +0 -1
  73. package/adapters/opencode/commands/ship-error-handling.md +0 -1
  74. package/adapters/opencode/commands/ship.md +0 -1
  75. package/adapters/opencode/commands/sync-docs.md +0 -1
  76. package/adapters/opencode/skills/agnix/SKILL.md +1 -2
  77. package/adapters/opencode/skills/consult/SKILL.md +41 -27
  78. package/adapters/opencode/skills/debate/SKILL.md +245 -0
  79. package/adapters/opencode/skills/deslop/SKILL.md +1 -2
  80. package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
  81. package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
  82. package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
  83. package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
  84. package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
  85. package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
  86. package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
  87. package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
  88. package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
  89. package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
  90. package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
  91. package/adapters/opencode/skills/learn/SKILL.md +1 -2
  92. package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
  93. package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
  94. package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
  95. package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
  96. package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
  97. package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
  98. package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
  99. package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
  100. package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
  101. package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
  102. package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
  103. package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
  104. package/lib/adapter-transforms.js +24 -4
  105. package/package.json +1 -1
  106. package/plugins/agnix/.claude-plugin/plugin.json +1 -1
  107. package/plugins/agnix/skills/agnix/SKILL.md +1 -1
  108. package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
  109. package/plugins/audit-project/lib/adapter-transforms.js +24 -4
  110. package/plugins/consult/.claude-plugin/plugin.json +1 -1
  111. package/plugins/consult/agents/consult-agent.md +123 -30
  112. package/plugins/consult/commands/consult.md +136 -60
  113. package/plugins/consult/skills/consult/SKILL.md +39 -24
  114. package/plugins/debate/.claude-plugin/plugin.json +21 -0
  115. package/plugins/debate/agents/debate-orchestrator.md +175 -0
  116. package/plugins/debate/commands/debate.md +221 -0
  117. package/plugins/debate/lib/adapter-transforms.js +298 -0
  118. package/plugins/debate/lib/collectors/codebase.js +392 -0
  119. package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
  120. package/plugins/debate/lib/collectors/documentation.js +219 -0
  121. package/plugins/debate/lib/collectors/github.js +330 -0
  122. package/plugins/debate/lib/collectors/index.js +126 -0
  123. package/plugins/debate/lib/config/index.js +14 -0
  124. package/plugins/debate/lib/cross-platform/index.js +539 -0
  125. package/plugins/debate/lib/discovery/index.js +352 -0
  126. package/plugins/debate/lib/drift-detect/collectors.js +37 -0
  127. package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
  128. package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
  129. package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
  130. package/plugins/debate/lib/enhance/benchmark.js +417 -0
  131. package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
  132. package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
  133. package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
  134. package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
  135. package/plugins/debate/lib/enhance/fixer.js +721 -0
  136. package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
  137. package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
  138. package/plugins/debate/lib/enhance/index.js +127 -0
  139. package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
  140. package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
  141. package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
  142. package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
  143. package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
  144. package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
  145. package/plugins/debate/lib/enhance/reporter.js +1348 -0
  146. package/plugins/debate/lib/enhance/security-patterns.js +284 -0
  147. package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
  148. package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
  149. package/plugins/debate/lib/enhance/suppression.js +352 -0
  150. package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
  151. package/plugins/debate/lib/index.js +270 -0
  152. package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
  153. package/plugins/debate/lib/patterns/pipeline.js +948 -0
  154. package/plugins/debate/lib/patterns/review-patterns.js +558 -0
  155. package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
  156. package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
  157. package/plugins/debate/lib/perf/analyzer/index.js +22 -0
  158. package/plugins/debate/lib/perf/argument-parser.js +105 -0
  159. package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
  160. package/plugins/debate/lib/perf/baseline-store.js +127 -0
  161. package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
  162. package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
  163. package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
  164. package/plugins/debate/lib/perf/checkpoint.js +123 -0
  165. package/plugins/debate/lib/perf/code-paths.js +86 -0
  166. package/plugins/debate/lib/perf/consolidation.js +37 -0
  167. package/plugins/debate/lib/perf/constraint-runner.js +71 -0
  168. package/plugins/debate/lib/perf/experiment-runner.js +32 -0
  169. package/plugins/debate/lib/perf/index.js +41 -0
  170. package/plugins/debate/lib/perf/investigation-state.js +874 -0
  171. package/plugins/debate/lib/perf/optimization-runner.js +79 -0
  172. package/plugins/debate/lib/perf/profilers/go.js +22 -0
  173. package/plugins/debate/lib/perf/profilers/index.js +46 -0
  174. package/plugins/debate/lib/perf/profilers/java.js +23 -0
  175. package/plugins/debate/lib/perf/profilers/node.js +27 -0
  176. package/plugins/debate/lib/perf/profilers/python.js +23 -0
  177. package/plugins/debate/lib/perf/profilers/rust.js +23 -0
  178. package/plugins/debate/lib/perf/profiling-runner.js +75 -0
  179. package/plugins/debate/lib/perf/schemas.js +140 -0
  180. package/plugins/debate/lib/platform/detect-platform.js +413 -0
  181. package/plugins/debate/lib/platform/detection-configs.js +93 -0
  182. package/plugins/debate/lib/platform/state-dir.js +132 -0
  183. package/plugins/debate/lib/platform/verify-tools.js +182 -0
  184. package/plugins/debate/lib/repo-map/cache.js +152 -0
  185. package/plugins/debate/lib/repo-map/concurrency.js +29 -0
  186. package/plugins/debate/lib/repo-map/index.js +222 -0
  187. package/plugins/debate/lib/repo-map/installer.js +212 -0
  188. package/plugins/debate/lib/repo-map/queries/go.js +27 -0
  189. package/plugins/debate/lib/repo-map/queries/index.js +100 -0
  190. package/plugins/debate/lib/repo-map/queries/java.js +38 -0
  191. package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
  192. package/plugins/debate/lib/repo-map/queries/python.js +24 -0
  193. package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
  194. package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
  195. package/plugins/debate/lib/repo-map/runner.js +1364 -0
  196. package/plugins/debate/lib/repo-map/updater.js +562 -0
  197. package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
  198. package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
  199. package/plugins/debate/lib/schemas/validator.js +247 -0
  200. package/plugins/debate/lib/sources/custom-handler.js +199 -0
  201. package/plugins/debate/lib/sources/policy-questions.js +246 -0
  202. package/plugins/debate/lib/sources/source-cache.js +165 -0
  203. package/plugins/debate/lib/state/workflow-state.js +576 -0
  204. package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
  205. package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
  206. package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
  207. package/plugins/debate/lib/types/index.d.ts +84 -0
  208. package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
  209. package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
  210. package/plugins/debate/lib/utils/atomic-write.js +94 -0
  211. package/plugins/debate/lib/utils/cache-manager.js +159 -0
  212. package/plugins/debate/lib/utils/command-parser.js +0 -0
  213. package/plugins/debate/lib/utils/context-optimizer.js +300 -0
  214. package/plugins/debate/lib/utils/deprecation.js +37 -0
  215. package/plugins/debate/lib/utils/shell-escape.js +88 -0
  216. package/plugins/debate/lib/utils/state-helpers.js +61 -0
  217. package/plugins/debate/skills/debate/SKILL.md +264 -0
  218. package/plugins/deslop/.claude-plugin/plugin.json +1 -1
  219. package/plugins/deslop/lib/adapter-transforms.js +24 -4
  220. package/plugins/deslop/skills/deslop/SKILL.md +1 -1
  221. package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
  222. package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
  223. package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
  224. package/plugins/enhance/.claude-plugin/plugin.json +1 -1
  225. package/plugins/enhance/lib/adapter-transforms.js +24 -4
  226. package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
  227. package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
  228. package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
  229. package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
  230. package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
  231. package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
  232. package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
  233. package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
  234. package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
  235. package/plugins/learn/.claude-plugin/plugin.json +1 -1
  236. package/plugins/learn/agents/learn-agent.md +1 -1
  237. package/plugins/learn/lib/adapter-transforms.js +24 -4
  238. package/plugins/learn/skills/learn/SKILL.md +1 -1
  239. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  240. package/plugins/next-task/agents/exploration-agent.md +1 -1
  241. package/plugins/next-task/lib/adapter-transforms.js +24 -4
  242. package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
  243. package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
  244. package/plugins/perf/.claude-plugin/plugin.json +1 -1
  245. package/plugins/perf/lib/adapter-transforms.js +24 -4
  246. package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
  247. package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
  248. package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
  249. package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
  250. package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
  251. package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
  252. package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
  253. package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
  254. package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
  255. package/plugins/repo-map/lib/adapter-transforms.js +24 -4
  256. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  257. package/plugins/ship/lib/adapter-transforms.js +24 -4
  258. package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
  259. package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
  260. package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
  261. package/scripts/gen-adapters.js +6 -7
  262. package/scripts/generate-docs.js +4 -2
  263. package/scripts/plugins.txt +1 -0
  264. package/site/content.json +6 -6
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Repo map incremental updater
3
+ *
4
+ * @module lib/repo-map/updater
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fsPromises = require('fs').promises;
10
+ const path = require('path');
11
+ const { execFileSync } = require('child_process');
12
+
13
+ const runner = require('./runner');
14
+ const cache = require('./cache');
15
+ const installer = require('./installer');
16
+ const { runWithConcurrency } = require('./concurrency');
17
+
18
+ const SCAN_CONCURRENCY = 8;
19
+ const SCANNABLE_EXTENSIONS = new Set(Object.values(runner.LANGUAGE_EXTENSIONS).flat());
20
+
21
+ function isScannableFile(filePath) {
22
+ const ext = path.extname(filePath).toLowerCase();
23
+ return SCANNABLE_EXTENSIONS.has(ext);
24
+ }
25
+
26
+ /**
27
+ * Perform incremental update based on git diff
28
+ * @param {string} basePath - Repository root
29
+ * @param {Object} map - Existing repo map
30
+ * @returns {Promise<{success: boolean, map?: Object, changes?: Object, error?: string, needsFullRebuild?: boolean}>}
31
+ */
32
+ async function incrementalUpdate(basePath, map) {
33
+ // Validate ast-grep
34
+ const installed = installer.checkInstalledSync();
35
+ if (!installed.found) {
36
+ return {
37
+ success: false,
38
+ error: 'ast-grep not found',
39
+ installSuggestion: installer.getInstallInstructions()
40
+ };
41
+ }
42
+
43
+ if (!installer.meetsMinimumVersion(installed.version)) {
44
+ return {
45
+ success: false,
46
+ error: `ast-grep version ${installed.version || 'unknown'} is too old. Minimum required: ${installer.getMinimumVersion()}`,
47
+ installSuggestion: installer.getInstallInstructions()
48
+ };
49
+ }
50
+
51
+ if (!map || !map.files) {
52
+ return {
53
+ success: false,
54
+ error: 'Invalid repo map',
55
+ needsFullRebuild: true
56
+ };
57
+ }
58
+ map.stats = map.stats || {};
59
+ if (!Array.isArray(map.stats.errors)) {
60
+ map.stats.errors = [];
61
+ }
62
+ if (map.docs) {
63
+ delete map.docs;
64
+ }
65
+
66
+ // Try git-based update first
67
+ const gitInfo = runner.getGitInfo(basePath);
68
+ if (!gitInfo || !map.git?.commit) {
69
+ return updateWithoutGit(basePath, map, installed.command);
70
+ }
71
+
72
+ // Check if base commit exists
73
+ if (!commitExists(basePath, map.git.commit)) {
74
+ return {
75
+ success: false,
76
+ error: 'Base commit not found (history rewritten). Full rebuild required.',
77
+ needsFullRebuild: true
78
+ };
79
+ }
80
+
81
+ const diff = getGitDiff(basePath, map.git.commit);
82
+ if (diff === null) {
83
+ return updateWithoutGit(basePath, map, installed.command);
84
+ }
85
+
86
+ const changes = parseDiff(diff);
87
+
88
+ // No changes - just update metadata
89
+ if (changes.total === 0) {
90
+ map.git = gitInfo;
91
+ map.updated = new Date().toISOString();
92
+ return {
93
+ success: true,
94
+ map,
95
+ changes: { total: 0, updated: 0, added: 0, deleted: 0, renamed: 0 }
96
+ };
97
+ }
98
+
99
+ // Apply deletions
100
+ for (const file of changes.deleted) {
101
+ delete map.files[file];
102
+ delete map.dependencies[file];
103
+ }
104
+
105
+ // Apply renames
106
+ for (const { from, to } of changes.renamed) {
107
+ if (map.files[from]) {
108
+ map.files[to] = map.files[from];
109
+ delete map.files[from];
110
+ }
111
+ if (map.dependencies[from]) {
112
+ map.dependencies[to] = map.dependencies[from];
113
+ delete map.dependencies[from];
114
+ }
115
+ }
116
+
117
+ // Apply added/modified - batch file existence checks
118
+ const updatedFiles = [...changes.added, ...changes.modified];
119
+ const fullPaths = updatedFiles.map(file => ({ file, fullPath: path.join(basePath, file) }));
120
+
121
+ // Batch check file existence
122
+ const existenceChecks = await Promise.all(
123
+ fullPaths.map(async ({ file, fullPath }) => {
124
+ try {
125
+ await fsPromises.access(fullPath);
126
+ return { file, fullPath, exists: true };
127
+ } catch {
128
+ return { file, fullPath, exists: false };
129
+ }
130
+ })
131
+ );
132
+
133
+ // Process files that exist with bounded concurrency
134
+ const scanTargets = existenceChecks.filter(({ file, exists }) => exists && isScannableFile(file));
135
+ const scanResults = await runWithConcurrency(scanTargets, SCAN_CONCURRENCY, async ({ file, fullPath }) => {
136
+ const astErrors = [];
137
+ const fileData = await runner.scanSingleFileAsync(installed.command, fullPath, basePath, {
138
+ onError: (error) => astErrors.push(error)
139
+ });
140
+ return { file, fileData, astErrors };
141
+ });
142
+
143
+ const scanFailures = [];
144
+ for (const result of scanResults) {
145
+ if (!result) continue;
146
+
147
+ if (result.astErrors.length > 0) {
148
+ map.stats.errors.push(...result.astErrors);
149
+ }
150
+
151
+ if (!result.fileData) {
152
+ if (result.astErrors.length > 0) {
153
+ scanFailures.push(result.file);
154
+ }
155
+ continue;
156
+ }
157
+
158
+ map.files[result.file] = result.fileData;
159
+ if (result.fileData.imports && result.fileData.imports.length > 0) {
160
+ map.dependencies[result.file] = Array.from(new Set(result.fileData.imports.map(imp => imp.source)));
161
+ } else {
162
+ delete map.dependencies[result.file];
163
+ }
164
+ }
165
+
166
+ if (scanFailures.length > 0) {
167
+ return {
168
+ success: false,
169
+ error: `Failed to rescan ${scanFailures.length} file(s) during incremental update`,
170
+ needsFullRebuild: true,
171
+ failedFiles: scanFailures
172
+ };
173
+ }
174
+
175
+ // Recalculate stats
176
+ recalculateStats(map);
177
+
178
+ // Update git metadata
179
+ map.git = gitInfo;
180
+ map.updated = new Date().toISOString();
181
+
182
+ return {
183
+ success: true,
184
+ map,
185
+ changes: {
186
+ total: changes.total,
187
+ updated: updatedFiles.length,
188
+ added: changes.added.length,
189
+ deleted: changes.deleted.length,
190
+ renamed: changes.renamed.length
191
+ }
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Update without git (hash comparison)
197
+ * @param {string} basePath - Repository root
198
+ * @param {Object} map - Existing repo map
199
+ * @param {string} cmd - ast-grep command
200
+ * @returns {Promise<{success: boolean, map?: Object, changes?: Object}>}
201
+ */
202
+ async function updateWithoutGit(basePath, map, cmd) {
203
+ const currentFiles = new Set();
204
+ const languages = map.project?.languages || [];
205
+ map.stats = map.stats || {};
206
+ if (!Array.isArray(map.stats.errors)) {
207
+ map.stats.errors = [];
208
+ }
209
+ if (map.docs) {
210
+ delete map.docs;
211
+ }
212
+
213
+ for (const lang of languages) {
214
+ const files = runner.findFilesForLanguage(basePath, lang);
215
+ for (const file of files) {
216
+ currentFiles.add(path.relative(basePath, file).replace(/\\/g, '/'));
217
+ }
218
+ }
219
+
220
+ const changes = {
221
+ added: [],
222
+ modified: [],
223
+ deleted: [],
224
+ renamed: [],
225
+ total: 0
226
+ };
227
+
228
+ // Collect existing files to check
229
+ const existingFiles = Object.keys(map.files);
230
+ const filesToCheck = [];
231
+ const filesToDelete = [];
232
+
233
+ for (const file of existingFiles) {
234
+ if (!currentFiles.has(file)) {
235
+ filesToDelete.push(file);
236
+ } else {
237
+ filesToCheck.push(file);
238
+ currentFiles.delete(file);
239
+ }
240
+ }
241
+
242
+ // Process deletions
243
+ for (const file of filesToDelete) {
244
+ changes.deleted.push(file);
245
+ delete map.files[file];
246
+ delete map.dependencies[file];
247
+ }
248
+
249
+ // Process existing files for modifications (async file reads)
250
+ const checkResults = await runWithConcurrency(filesToCheck, SCAN_CONCURRENCY, async (file) => {
251
+ const fullPath = path.join(basePath, file);
252
+ const astErrors = [];
253
+ const fileData = await runner.scanSingleFileAsync(cmd, fullPath, basePath, {
254
+ onError: (error) => astErrors.push(error)
255
+ });
256
+ return { file, fileData, astErrors };
257
+ });
258
+
259
+ const scanFailures = [];
260
+ for (const result of checkResults) {
261
+ if (!result) continue;
262
+
263
+ if (result.astErrors.length > 0) {
264
+ map.stats.errors.push(...result.astErrors);
265
+ }
266
+
267
+ if (!result.fileData) {
268
+ scanFailures.push(result.file);
269
+ continue;
270
+ }
271
+
272
+ if (result.fileData.hash !== map.files[result.file].hash) {
273
+ map.files[result.file] = result.fileData;
274
+ if (result.fileData.imports && result.fileData.imports.length > 0) {
275
+ map.dependencies[result.file] = Array.from(new Set(result.fileData.imports.map(imp => imp.source)));
276
+ } else {
277
+ delete map.dependencies[result.file];
278
+ }
279
+ changes.modified.push(result.file);
280
+ }
281
+ }
282
+
283
+ // Process new files (async file reads)
284
+ const addedFiles = Array.from(currentFiles);
285
+ const addResults = await runWithConcurrency(addedFiles, SCAN_CONCURRENCY, async (file) => {
286
+ const fullPath = path.join(basePath, file);
287
+ const astErrors = [];
288
+ const fileData = await runner.scanSingleFileAsync(cmd, fullPath, basePath, {
289
+ onError: (error) => astErrors.push(error)
290
+ });
291
+ return { file, fileData, astErrors };
292
+ });
293
+
294
+ for (const result of addResults) {
295
+ if (!result) continue;
296
+
297
+ if (result.astErrors.length > 0) {
298
+ map.stats.errors.push(...result.astErrors);
299
+ }
300
+
301
+ if (!result.fileData) {
302
+ scanFailures.push(result.file);
303
+ continue;
304
+ }
305
+
306
+ map.files[result.file] = result.fileData;
307
+ if (result.fileData.imports && result.fileData.imports.length > 0) {
308
+ map.dependencies[result.file] = Array.from(new Set(result.fileData.imports.map(imp => imp.source)));
309
+ }
310
+ changes.added.push(result.file);
311
+ }
312
+
313
+ if (scanFailures.length > 0) {
314
+ return {
315
+ success: false,
316
+ error: `Failed to rescan ${scanFailures.length} file(s) during non-git update`,
317
+ needsFullRebuild: true,
318
+ failedFiles: scanFailures
319
+ };
320
+ }
321
+
322
+ changes.total = changes.added.length + changes.modified.length + changes.deleted.length;
323
+
324
+ recalculateStats(map);
325
+ map.updated = new Date().toISOString();
326
+
327
+ return {
328
+ success: true,
329
+ map,
330
+ changes: {
331
+ total: changes.total,
332
+ updated: changes.modified.length,
333
+ added: changes.added.length,
334
+ deleted: changes.deleted.length,
335
+ renamed: changes.renamed.length
336
+ }
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Check if repo-map is stale
342
+ * @param {string} basePath - Repository root
343
+ * @param {Object} map - Repo map
344
+ * @returns {Object} Staleness info
345
+ */
346
+ function checkStaleness(basePath, map) {
347
+ const result = {
348
+ isStale: false,
349
+ reason: null,
350
+ commitsBehind: 0,
351
+ suggestFullRebuild: false
352
+ };
353
+
354
+ if (!map?.git?.commit) {
355
+ result.isStale = true;
356
+ result.reason = 'Missing base commit in repo-map';
357
+ result.suggestFullRebuild = true;
358
+ return result;
359
+ }
360
+
361
+ if (cache.isMarkedStale(basePath)) {
362
+ result.isStale = true;
363
+ result.reason = 'Marked stale by hook';
364
+ }
365
+
366
+ if (!commitExists(basePath, map.git.commit)) {
367
+ result.isStale = true;
368
+ result.reason = 'Base commit no longer exists (rebased?)';
369
+ result.suggestFullRebuild = true;
370
+ return result;
371
+ }
372
+
373
+ const currentBranch = getCurrentBranch(basePath);
374
+ if (currentBranch && map.git.branch && currentBranch !== map.git.branch) {
375
+ result.isStale = true;
376
+ result.reason = `Branch changed from ${map.git.branch} to ${currentBranch}`;
377
+ result.suggestFullRebuild = true;
378
+ }
379
+
380
+ const commitsBehind = getCommitsBehind(basePath, map.git.commit);
381
+ if (commitsBehind > 0) {
382
+ result.isStale = true;
383
+ result.commitsBehind = commitsBehind;
384
+ if (!result.reason) {
385
+ result.reason = `${commitsBehind} commits behind HEAD`;
386
+ }
387
+ }
388
+
389
+ return result;
390
+ }
391
+
392
+ /**
393
+ * Parse git diff output
394
+ * @param {string} diff - Git diff output
395
+ * @returns {Object}
396
+ */
397
+ function parseDiff(diff) {
398
+ const changes = {
399
+ added: [],
400
+ modified: [],
401
+ deleted: [],
402
+ renamed: [],
403
+ total: 0
404
+ };
405
+
406
+ const lines = diff.split('\n').filter(Boolean);
407
+ for (const line of lines) {
408
+ const parts = line.split('\t');
409
+ const status = parts[0];
410
+
411
+ if (status.startsWith('R')) {
412
+ const from = normalizePath(parts[1]);
413
+ const to = normalizePath(parts[2]);
414
+ changes.renamed.push({ from, to });
415
+ const renameScore = Number(status.slice(1));
416
+ if (!Number.isNaN(renameScore) && renameScore < 100 && to) {
417
+ changes.modified.push(to);
418
+ }
419
+ continue;
420
+ }
421
+
422
+ const file = normalizePath(parts[1]);
423
+ if (!file) continue;
424
+
425
+ if (status === 'A') changes.added.push(file);
426
+ else if (status === 'M') changes.modified.push(file);
427
+ else if (status === 'D') changes.deleted.push(file);
428
+
429
+ }
430
+
431
+ changes.total = changes.added.length + changes.modified.length + changes.deleted.length + changes.renamed.length;
432
+ return changes;
433
+ }
434
+
435
+ /**
436
+ * Validate git commit hash format
437
+ * @param {string} commit - Commit hash to validate
438
+ * @returns {boolean} True if valid hex commit hash
439
+ */
440
+ function isValidCommitHash(commit) {
441
+ // Git commit hashes are 4-40 hex characters (short to full SHA)
442
+ return typeof commit === 'string' && /^[0-9a-fA-F]{4,40}$/.test(commit);
443
+ }
444
+
445
+ /**
446
+ * Get git diff name-status
447
+ * @param {string} basePath - Repository root
448
+ * @param {string} sinceCommit - Base commit
449
+ * @returns {string|null}
450
+ */
451
+ function getGitDiff(basePath, sinceCommit) {
452
+ // Validate commit hash to prevent command injection
453
+ if (!isValidCommitHash(sinceCommit)) {
454
+ return null;
455
+ }
456
+ try {
457
+ // Use execFileSync with arg array to prevent command injection
458
+ return execFileSync('git', ['diff', '--name-status', '-M', sinceCommit, 'HEAD'], {
459
+ cwd: basePath,
460
+ encoding: 'utf8',
461
+ stdio: ['pipe', 'pipe', 'pipe']
462
+ }).trim();
463
+ } catch {
464
+ return null;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Check if commit exists
470
+ * @param {string} basePath - Repository root
471
+ * @param {string} commit - Commit hash
472
+ * @returns {boolean}
473
+ */
474
+ function commitExists(basePath, commit) {
475
+ // Validate commit hash to prevent command injection
476
+ if (!isValidCommitHash(commit)) {
477
+ return false;
478
+ }
479
+ try {
480
+ // Use execFileSync with arg array to prevent command injection
481
+ execFileSync('git', ['cat-file', '-e', commit], {
482
+ cwd: basePath,
483
+ stdio: ['pipe', 'pipe', 'pipe']
484
+ });
485
+ return true;
486
+ } catch {
487
+ return false;
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Get current branch name
493
+ * @param {string} basePath - Repository root
494
+ * @returns {string|null}
495
+ */
496
+ function getCurrentBranch(basePath) {
497
+ try {
498
+ // Use execFileSync with arg array for consistency
499
+ return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
500
+ cwd: basePath,
501
+ encoding: 'utf8',
502
+ stdio: ['pipe', 'pipe', 'pipe']
503
+ }).trim();
504
+ } catch {
505
+ return null;
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Get number of commits behind HEAD
511
+ * @param {string} basePath - Repository root
512
+ * @param {string} commit - Base commit
513
+ * @returns {number}
514
+ */
515
+ function getCommitsBehind(basePath, commit) {
516
+ // Validate commit hash to prevent command injection
517
+ if (!isValidCommitHash(commit)) {
518
+ return 0;
519
+ }
520
+ try {
521
+ // Use execFileSync with arg array to prevent command injection
522
+ const out = execFileSync('git', ['rev-list', `${commit}..HEAD`, '--count'], {
523
+ cwd: basePath,
524
+ encoding: 'utf8',
525
+ stdio: ['pipe', 'pipe', 'pipe']
526
+ }).trim();
527
+ return Number(out) || 0;
528
+ } catch {
529
+ return 0;
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Normalize file path to forward slashes
535
+ * @param {string} filePath - Path to normalize
536
+ * @returns {string}
537
+ */
538
+ function normalizePath(filePath) {
539
+ return filePath ? filePath.replace(/\\/g, '/') : filePath;
540
+ }
541
+
542
+ /**
543
+ * Recalculate map stats
544
+ * @param {Object} map - Repo map
545
+ */
546
+ function recalculateStats(map) {
547
+ const files = Object.values(map.files || {});
548
+ map.stats.totalFiles = files.length;
549
+ map.stats.totalSymbols = files.reduce((sum, file) => {
550
+ return sum +
551
+ (file.symbols?.functions?.length || 0) +
552
+ (file.symbols?.classes?.length || 0) +
553
+ (file.symbols?.types?.length || 0) +
554
+ (file.symbols?.constants?.length || 0);
555
+ }, 0);
556
+ }
557
+
558
+ module.exports = {
559
+ incrementalUpdate,
560
+ updateWithoutGit,
561
+ checkStaleness
562
+ };