agentsys 5.0.3 → 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 +18 -0
  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 +132 -57
  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 +122 -30
  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 +133 -57
  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 +33 -23
  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 +122 -29
  112. package/plugins/consult/commands/consult.md +135 -58
  113. package/plugins/consult/skills/consult/SKILL.md +31 -20
  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,713 @@
1
+ /**
2
+ * Documentation Patterns Collector
3
+ *
4
+ * Specialized patterns for sync-docs: finding related docs,
5
+ * detecting outdated references, and analyzing doc issues.
6
+ *
7
+ * @module lib/collectors/docs-patterns
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { execFileSync } = require('child_process');
15
+
16
+ // Lazy-load repo-map to avoid circular dependencies
17
+ let repoMapModule = null;
18
+ let repoMapLoadError = null;
19
+
20
+ /**
21
+ * Get the repo-map module, loading it lazily
22
+ * @returns {{module: Object|null, error: string|null}}
23
+ */
24
+ function getRepoMap() {
25
+ if (!repoMapModule && !repoMapLoadError) {
26
+ try {
27
+ repoMapModule = require('../repo-map');
28
+ } catch (err) {
29
+ // Module not found or failed to load - store error for diagnostics
30
+ repoMapLoadError = err.message || 'Failed to load repo-map module';
31
+ repoMapModule = null;
32
+ }
33
+ }
34
+ return repoMapModule;
35
+ }
36
+
37
+ /**
38
+ * Get the repo-map load error if any
39
+ * @returns {string|null}
40
+ */
41
+ function getRepoMapLoadError() {
42
+ return repoMapLoadError;
43
+ }
44
+
45
+ const DEFAULT_OPTIONS = {
46
+ cwd: process.cwd()
47
+ };
48
+
49
+ // Constants for configuration
50
+ const MAX_SCAN_DEPTH = 5;
51
+ const MAX_DOC_FILES = 200;
52
+ const INTERNAL_DIRS = ['internal', 'private', 'utils', 'helpers', '__tests__', 'test', 'tests'];
53
+ const ENTRY_NAMES = ['index', 'main', 'app', 'server', 'cli', 'bin'];
54
+
55
+ // Regex patterns for export detection (extracted for performance)
56
+ const EXPORT_PATTERNS = [
57
+ /export\s+(?:function|class|const|let|var)\s+(\w+)/g,
58
+ /export\s+\{([^}]+)\}/g,
59
+ /module\.exports\s*=\s*\{([^}]+)\}/
60
+ ];
61
+
62
+ /**
63
+ * Escape special regex characters in a string
64
+ * @param {string} str - String to escape
65
+ * @returns {string} Escaped string safe for use in RegExp
66
+ */
67
+ function escapeRegex(str) {
68
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
69
+ }
70
+
71
+ /**
72
+ * Check if an export should be considered internal (skip documentation checks)
73
+ * @param {string} name - Export name
74
+ * @param {string} filePath - File path
75
+ * @returns {boolean} True if export should be considered internal/private
76
+ */
77
+ function isInternalExport(name, filePath) {
78
+ // Underscore prefix convention
79
+ if (name.startsWith('_')) return true;
80
+
81
+ // Internal directory patterns
82
+ const pathLower = filePath.toLowerCase();
83
+ for (const dir of INTERNAL_DIRS) {
84
+ if (pathLower.includes(`/${dir}/`) || pathLower.includes(`\\${dir}\\`)) {
85
+ return true;
86
+ }
87
+ }
88
+
89
+ // Test files
90
+ if (/\.(test|spec)\.[jt]sx?$/.test(filePath)) return true;
91
+
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Check if a file is likely an entry point (should have docs but not flagged as undocumented)
97
+ * @param {string} filePath - File path
98
+ * @returns {boolean} True if file appears to be an entry point (index.js, main.js, etc.)
99
+ */
100
+ function isEntryPoint(filePath) {
101
+ const basename = path.basename(filePath);
102
+ const nameWithoutExt = basename.replace(/\.[^.]+$/, '').toLowerCase();
103
+ return ENTRY_NAMES.includes(nameWithoutExt);
104
+ }
105
+
106
+ /**
107
+ * Ensure repo-map is available, creating it if possible
108
+ * @param {Object} options - Options
109
+ * @param {string} [options.cwd=process.cwd()] - Working directory
110
+ * @param {Function} [options.askUser] - Async callback to ask user questions.
111
+ * Signature: async ({question: string, header: string, options: Array<{label, description}>}) => string
112
+ * Returns the selected option label or null if declined.
113
+ * @returns {Promise<{available: boolean, map: Object|null, fallbackReason: string|null, installInstructions?: string}>}
114
+ */
115
+ async function ensureRepoMap(options = {}) {
116
+ const { cwd = process.cwd(), askUser } = options;
117
+ const repoMap = getRepoMap();
118
+
119
+ // No repo-map module available
120
+ if (!repoMap) {
121
+ return { available: false, map: null, fallbackReason: 'repo-map-module-not-found' };
122
+ }
123
+
124
+ // 1. Already exists?
125
+ if (repoMap.exists(cwd)) {
126
+ const map = repoMap.load(cwd);
127
+ return { available: true, map, fallbackReason: null };
128
+ }
129
+
130
+ // 2. Check ast-grep installation
131
+ const installed = await repoMap.checkAstGrepInstalled();
132
+
133
+ if (!installed.found) {
134
+ // 3. Ask user if they want to install
135
+ if (askUser) {
136
+ const answer = await askUser({
137
+ question: 'ast-grep not found. Install for better doc sync accuracy?',
138
+ header: 'ast-grep Required',
139
+ options: [
140
+ { label: 'Yes, show instructions', description: 'Better accuracy with AST-based symbol detection' },
141
+ { label: 'No, use regex fallback', description: 'Less accurate but works without additional install' }
142
+ ]
143
+ });
144
+
145
+ if (answer && answer.includes('Yes')) {
146
+ const instructions = repoMap.getInstallInstructions();
147
+ return {
148
+ available: false,
149
+ map: null,
150
+ fallbackReason: 'ast-grep-install-pending',
151
+ installInstructions: instructions
152
+ };
153
+ }
154
+ }
155
+
156
+ return { available: false, map: null, fallbackReason: 'ast-grep-not-installed' };
157
+ }
158
+
159
+ // 4. ast-grep available, try to create repo-map
160
+ try {
161
+ const initResult = await repoMap.init(cwd, { force: false });
162
+
163
+ if (initResult.success) {
164
+ return { available: true, map: initResult.map, fallbackReason: null };
165
+ }
166
+
167
+ // Handle "already exists" case (race condition)
168
+ if (initResult.error && initResult.error.includes('already exists')) {
169
+ const map = repoMap.load(cwd);
170
+ return { available: true, map, fallbackReason: null };
171
+ }
172
+
173
+ // 5. Init failed (e.g., no supported languages)
174
+ return { available: false, map: null, fallbackReason: initResult.error || 'init-failed' };
175
+ } catch (err) {
176
+ return { available: false, map: null, fallbackReason: err.message || 'init-error' };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Synchronous version of ensureRepoMap (no user prompts, no auto-init)
182
+ * @param {Object} options - Options
183
+ * @returns {{available: boolean, map: Object|null, fallbackReason: string|null}}
184
+ */
185
+ function ensureRepoMapSync(options = {}) {
186
+ const { cwd = process.cwd() } = options;
187
+ const repoMap = getRepoMap();
188
+
189
+ if (!repoMap) {
190
+ return { available: false, map: null, fallbackReason: 'repo-map-module-not-found' };
191
+ }
192
+
193
+ if (repoMap.exists(cwd)) {
194
+ const map = repoMap.load(cwd);
195
+ return { available: true, map, fallbackReason: null };
196
+ }
197
+
198
+ return { available: false, map: null, fallbackReason: 'repo-map-not-initialized' };
199
+ }
200
+
201
+ /**
202
+ * Get exports from repo-map for a specific file
203
+ * @param {string} filePath - File path relative to repo root
204
+ * @param {Object} map - Loaded repo-map
205
+ * @returns {string[]|null} List of export names or null if not found
206
+ */
207
+ function getExportsFromRepoMap(filePath, map) {
208
+ if (!map || !map.files) return null;
209
+
210
+ // Normalize path separators
211
+ const normalizedPath = filePath.replace(/\\/g, '/');
212
+
213
+ // Try exact match first
214
+ let fileData = map.files[normalizedPath];
215
+
216
+ // Try without leading ./
217
+ if (!fileData && normalizedPath.startsWith('./')) {
218
+ fileData = map.files[normalizedPath.slice(2)];
219
+ }
220
+
221
+ // Try with leading ./
222
+ if (!fileData && !normalizedPath.startsWith('./')) {
223
+ fileData = map.files['./' + normalizedPath];
224
+ }
225
+
226
+ if (!fileData || !fileData.symbols || !fileData.symbols.exports) {
227
+ return null;
228
+ }
229
+
230
+ return fileData.symbols.exports.map(e => e.name);
231
+ }
232
+
233
+ /**
234
+ * Find exports that are not documented in any markdown file
235
+ * @param {string[]} changedFiles - List of changed file paths
236
+ * @param {Object} options - Options
237
+ * @param {Object} [options.repoMapStatus] - Pre-fetched repo-map status (avoids redundant calls)
238
+ * @returns {Array<{type: string, severity: string, file: string, name: string, line: number, certainty: string}>}
239
+ */
240
+ function findUndocumentedExports(changedFiles, options = {}) {
241
+ const opts = { ...DEFAULT_OPTIONS, ...options };
242
+
243
+ // Use pre-fetched status if provided, otherwise fetch
244
+ const repoMapStatus = opts.repoMapStatus || ensureRepoMapSync(opts);
245
+
246
+ if (!repoMapStatus.available || !repoMapStatus.map) {
247
+ return []; // Can't detect without repo-map
248
+ }
249
+
250
+ const map = repoMapStatus.map;
251
+ const allDocs = findMarkdownFiles(opts.cwd);
252
+
253
+ // Read all doc content for searching
254
+ let allDocContent = '';
255
+ for (const doc of allDocs) {
256
+ try {
257
+ allDocContent += fs.readFileSync(path.join(opts.cwd, doc), 'utf8') + '\n';
258
+ } catch {
259
+ // Skip unreadable docs
260
+ }
261
+ }
262
+
263
+ const issues = [];
264
+
265
+ for (const file of changedFiles) {
266
+ // Normalize path
267
+ const normalizedFile = file.replace(/\\/g, '/');
268
+ const fileData = map.files[normalizedFile] || map.files[normalizedFile.replace(/^\.\//, '')];
269
+
270
+ if (!fileData || !fileData.symbols || !fileData.symbols.exports) {
271
+ continue;
272
+ }
273
+
274
+ for (const exp of fileData.symbols.exports) {
275
+ // Skip internal exports
276
+ if (isInternalExport(exp.name, normalizedFile)) continue;
277
+
278
+ // Skip entry points (they're expected to have many exports)
279
+ if (isEntryPoint(normalizedFile)) continue;
280
+
281
+ // Check if mentioned in any doc
282
+ // Use word boundary to avoid partial matches
283
+ // Escape regex metacharacters to prevent injection/errors
284
+ const namePattern = new RegExp(`\\b${escapeRegex(exp.name)}\\b`);
285
+ if (!namePattern.test(allDocContent)) {
286
+ issues.push({
287
+ type: 'undocumented-export',
288
+ severity: 'low',
289
+ file: normalizedFile,
290
+ name: exp.name,
291
+ line: exp.line || 0,
292
+ kind: exp.kind || 'export',
293
+ certainty: 'MEDIUM',
294
+ suggestion: `Export '${exp.name}' in ${normalizedFile} is not mentioned in any documentation`
295
+ });
296
+ }
297
+ }
298
+ }
299
+
300
+ return issues;
301
+ }
302
+
303
+ /**
304
+ * Find documentation files related to changed source files
305
+ * @param {string[]} changedFiles - List of changed file paths
306
+ * @param {Object} options - Options
307
+ * @returns {Array<Object>} Related docs with reference types
308
+ */
309
+ function findRelatedDocs(changedFiles, options = {}) {
310
+ const opts = { ...DEFAULT_OPTIONS, ...options };
311
+ const basePath = opts.cwd;
312
+ const results = [];
313
+
314
+ // Find all markdown files
315
+ const docFiles = findMarkdownFiles(basePath);
316
+
317
+ for (const file of changedFiles) {
318
+ const basename = path.basename(file).replace(/\.[^.]+$/, '');
319
+ const modulePath = file.replace(/\.[^.]+$/, '');
320
+ const dirName = path.dirname(file);
321
+
322
+ for (const doc of docFiles) {
323
+ let content;
324
+ try {
325
+ content = fs.readFileSync(path.join(basePath, doc), 'utf8');
326
+ } catch {
327
+ // File unreadable (permissions, deleted after scan, etc.) - skip
328
+ continue;
329
+ }
330
+
331
+ const references = [];
332
+
333
+ // Check for various reference types
334
+ if (content.includes(basename)) {
335
+ references.push('filename');
336
+ }
337
+ if (content.includes(file)) {
338
+ references.push('full-path');
339
+ }
340
+ if (content.includes(`from '${modulePath}'`) || content.includes(`from "${modulePath}"`)) {
341
+ references.push('import');
342
+ }
343
+ if (content.includes(`require('${modulePath}')`) || content.includes(`require("${modulePath}")`)) {
344
+ references.push('require');
345
+ }
346
+ if (content.includes(`/${basename}`) || content.includes(`/${basename}.`)) {
347
+ references.push('url-path');
348
+ }
349
+
350
+ if (references.length > 0) {
351
+ results.push({
352
+ doc,
353
+ referencedFile: file,
354
+ referenceTypes: references
355
+ });
356
+ }
357
+ }
358
+ }
359
+
360
+ return results;
361
+ }
362
+
363
+ /**
364
+ * Find all markdown files in the repository
365
+ * @param {string} basePath - Repository root
366
+ * @returns {string[]} List of markdown file paths
367
+ */
368
+ function findMarkdownFiles(basePath) {
369
+ const files = [];
370
+ const excludeDirs = ['node_modules', 'dist', 'build', '.git', 'coverage', 'vendor'];
371
+
372
+ function scan(dir, depth = 0) {
373
+ if (depth > MAX_SCAN_DEPTH || files.length > MAX_DOC_FILES) return;
374
+
375
+ try {
376
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
377
+ for (const entry of entries) {
378
+ const fullPath = path.join(dir, entry.name);
379
+ const relativePath = path.relative(basePath, fullPath);
380
+
381
+ if (entry.isDirectory()) {
382
+ if (!excludeDirs.includes(entry.name) && !entry.name.startsWith('.')) {
383
+ scan(fullPath, depth + 1);
384
+ }
385
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
386
+ files.push(relativePath);
387
+ }
388
+ }
389
+ } catch {
390
+ // Skip unreadable directories
391
+ }
392
+ }
393
+
394
+ scan(basePath);
395
+ return files;
396
+ }
397
+
398
+ /**
399
+ * Analyze a documentation file for issues
400
+ * @param {string} docPath - Path to the doc file
401
+ * @param {string} changedFile - Path of the changed source file
402
+ * @param {Object} options - Options
403
+ * @returns {Array<Object>} List of issues found
404
+ */
405
+ function analyzeDocIssues(docPath, changedFile, options = {}) {
406
+ const opts = { ...DEFAULT_OPTIONS, ...options };
407
+ const basePath = opts.cwd;
408
+ const issues = [];
409
+
410
+ let content;
411
+ try {
412
+ content = fs.readFileSync(path.join(basePath, docPath), 'utf8');
413
+ } catch {
414
+ // Doc file unreadable - no issues to report
415
+ return issues;
416
+ }
417
+
418
+ const lines = content.split('\n');
419
+
420
+ // 1. Check code blocks for outdated imports
421
+ const codeBlockRegex = /```[\s\S]*?```/g;
422
+ const codeBlocks = content.match(codeBlockRegex) || [];
423
+
424
+ for (const block of codeBlocks) {
425
+ const importRegex = /import .* from ['"]([^'"]+)['"]/g;
426
+ let match;
427
+ while ((match = importRegex.exec(block)) !== null) {
428
+ const importPath = match[1];
429
+ const changedModulePath = changedFile.replace(/\.[^.]+$/, '');
430
+ if (importPath.includes(path.basename(changedModulePath))) {
431
+ issues.push({
432
+ type: 'code-example',
433
+ severity: 'medium',
434
+ line: findLineNumber(content, match[0]),
435
+ current: match[0],
436
+ suggestion: 'Verify import path is still valid'
437
+ });
438
+ }
439
+ }
440
+ }
441
+
442
+ // 2. Check for function/export references that may have changed
443
+ // Try repo-map first for accurate exports, fallback to git-based regex
444
+ const repoMapStatus = ensureRepoMapSync(opts);
445
+
446
+ let oldExports, newExports;
447
+ let usingRepoMap = false;
448
+
449
+ if (repoMapStatus.available && repoMapStatus.map) {
450
+ // Use repo-map for current exports (more accurate)
451
+ const repoMapExports = getExportsFromRepoMap(changedFile, repoMapStatus.map);
452
+ if (repoMapExports) {
453
+ newExports = repoMapExports;
454
+ // For old exports, still need git-based approach
455
+ oldExports = getExportsFromGit(changedFile, 'HEAD~1', opts);
456
+ usingRepoMap = true;
457
+ }
458
+ }
459
+
460
+ // Fallback to git-based detection
461
+ if (!usingRepoMap) {
462
+ oldExports = getExportsFromGit(changedFile, 'HEAD~1', opts);
463
+ newExports = getExportsFromGit(changedFile, 'HEAD', opts);
464
+ }
465
+
466
+ const removed = oldExports.filter(e => !newExports.includes(e));
467
+ for (const fn of removed) {
468
+ if (content.includes(fn)) {
469
+ issues.push({
470
+ type: 'removed-export',
471
+ severity: 'high',
472
+ reference: fn,
473
+ suggestion: `'${fn}' was removed or renamed`,
474
+ detectionMethod: usingRepoMap ? 'repo-map' : 'regex'
475
+ });
476
+ }
477
+ }
478
+
479
+ // 3. Check for outdated version numbers
480
+ try {
481
+ const pkgContent = fs.readFileSync(path.join(basePath, 'package.json'), 'utf8');
482
+ const pkg = JSON.parse(pkgContent);
483
+ const currentVersion = pkg.version;
484
+
485
+ const versionMatches = content.matchAll(/version[:\s]+['"]?(\d+\.\d+\.\d+)/gi);
486
+ for (const match of versionMatches) {
487
+ const docVersion = match[1];
488
+ if (docVersion !== currentVersion && compareVersions(docVersion, currentVersion) < 0) {
489
+ issues.push({
490
+ type: 'outdated-version',
491
+ severity: 'low',
492
+ line: findLineNumber(content, match[0]),
493
+ current: docVersion,
494
+ expected: currentVersion,
495
+ suggestion: `Update version from ${docVersion} to ${currentVersion}`
496
+ });
497
+ }
498
+ }
499
+ } catch {
500
+ // No package.json or parse error
501
+ }
502
+
503
+ return issues;
504
+ }
505
+
506
+ /**
507
+ * Find line number of a string in content
508
+ * @param {string} content - Full content
509
+ * @param {string} search - String to find
510
+ * @returns {number} Line number (1-indexed)
511
+ */
512
+ function findLineNumber(content, search) {
513
+ const index = content.indexOf(search);
514
+ if (index === -1) return 0;
515
+ return content.substring(0, index).split('\n').length;
516
+ }
517
+
518
+ /**
519
+ * Validate git ref format (e.g., HEAD, HEAD~1, branch names)
520
+ * @param {string} ref - Git ref to validate
521
+ * @returns {boolean} True if valid
522
+ */
523
+ function isValidGitRef(ref) {
524
+ if (typeof ref !== 'string' || !ref) return false;
525
+ // Allow: HEAD, HEAD~N, HEAD^N, branch names (alphanumeric, /, -, _, .)
526
+ // Reject: shell metacharacters, spaces, null bytes
527
+ return /^[a-zA-Z0-9_./-]+(?:[~^][0-9]+)?$/.test(ref);
528
+ }
529
+
530
+ /**
531
+ * Get exports from a file at a specific git ref
532
+ * @param {string} filePath - File path
533
+ * @param {string} ref - Git ref (HEAD, HEAD~1, etc.)
534
+ * @param {Object} options - Options
535
+ * @returns {string[]} List of export names
536
+ */
537
+ function getExportsFromGit(filePath, ref, options = {}) {
538
+ const opts = { ...DEFAULT_OPTIONS, ...options };
539
+
540
+ // Validate ref to prevent command injection
541
+ if (!isValidGitRef(ref)) {
542
+ return [];
543
+ }
544
+
545
+ try {
546
+ // Use execFileSync with arguments array to prevent command injection
547
+ // git show requires the ref:path as a single argument
548
+ const content = execFileSync('git', ['show', `${ref}:${filePath}`], {
549
+ cwd: opts.cwd,
550
+ encoding: 'utf8',
551
+ stdio: ['pipe', 'pipe', 'pipe']
552
+ });
553
+
554
+ const exports = [];
555
+
556
+ // Use module-level patterns - clone regex to reset lastIndex for global patterns
557
+ for (const pattern of EXPORT_PATTERNS) {
558
+ // Create new regex to avoid lastIndex issues with global patterns
559
+ const regex = new RegExp(pattern.source, pattern.flags);
560
+ let match;
561
+ while ((match = regex.exec(content)) !== null) {
562
+ if (match[1].includes(',')) {
563
+ // Multiple exports (e.g., export { a, b, c })
564
+ const names = match[1].split(',').map(s => s.trim().split(/\s+as\s+/)[0].trim());
565
+ exports.push(...names.filter(n => n && /^\w+$/.test(n)));
566
+ } else {
567
+ exports.push(match[1]);
568
+ }
569
+ }
570
+ }
571
+
572
+ return [...new Set(exports)];
573
+ } catch {
574
+ // Git command failed (file not in repo, invalid ref, etc.) - return empty
575
+ return [];
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Compare semantic versions
581
+ * @param {string} v1 - First version
582
+ * @param {string} v2 - Second version
583
+ * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
584
+ */
585
+ function compareVersions(v1, v2) {
586
+ const parts1 = v1.split('.').map(Number);
587
+ const parts2 = v2.split('.').map(Number);
588
+
589
+ for (let i = 0; i < 3; i++) {
590
+ const p1 = parts1[i] || 0;
591
+ const p2 = parts2[i] || 0;
592
+ if (p1 < p2) return -1;
593
+ if (p1 > p2) return 1;
594
+ }
595
+ return 0;
596
+ }
597
+
598
+ /**
599
+ * Check CHANGELOG for undocumented changes
600
+ * @param {string[]} changedFiles - Changed files
601
+ * @param {Object} options - Options
602
+ * @returns {Object} CHANGELOG status
603
+ */
604
+ function checkChangelog(changedFiles, options = {}) {
605
+ const opts = { ...DEFAULT_OPTIONS, ...options };
606
+ const basePath = opts.cwd;
607
+ const changelogPath = path.join(basePath, 'CHANGELOG.md');
608
+
609
+ if (!fs.existsSync(changelogPath)) {
610
+ return { exists: false };
611
+ }
612
+
613
+ let changelog;
614
+ try {
615
+ changelog = fs.readFileSync(changelogPath, 'utf8');
616
+ } catch {
617
+ return { exists: false, error: 'Could not read CHANGELOG.md' };
618
+ }
619
+
620
+ const hasUnreleased = changelog.includes('## [Unreleased]');
621
+
622
+ // Get recent commits
623
+ let recentCommits = [];
624
+ try {
625
+ // Use execFileSync with arguments array for safer execution
626
+ const output = execFileSync('git', ['log', '--oneline', '-10', 'HEAD'], {
627
+ cwd: basePath,
628
+ encoding: 'utf8',
629
+ stdio: ['pipe', 'pipe', 'pipe']
630
+ });
631
+ recentCommits = output.trim().split('\n');
632
+ } catch {
633
+ // Git command failed
634
+ }
635
+
636
+ const documented = [];
637
+ const undocumented = [];
638
+
639
+ for (const commit of recentCommits) {
640
+ if (!commit) continue;
641
+ const msg = commit.substring(8); // Skip hash
642
+ if (changelog.includes(msg) || changelog.includes(commit.substring(0, 7))) {
643
+ documented.push(msg);
644
+ } else if (msg.match(/^(feat|fix|breaking)/i)) {
645
+ undocumented.push(msg);
646
+ }
647
+ }
648
+
649
+ return {
650
+ exists: true,
651
+ hasUnreleased,
652
+ documented,
653
+ undocumented,
654
+ suggestion: undocumented.length > 0
655
+ ? `${undocumented.length} commits may need CHANGELOG entries`
656
+ : null
657
+ };
658
+ }
659
+
660
+ /**
661
+ * Collect all documentation-related data
662
+ * @param {Object} options - Collection options
663
+ * @returns {Object} Collected data
664
+ */
665
+ function collect(options = {}) {
666
+ const opts = { ...DEFAULT_OPTIONS, ...options };
667
+ const changedFiles = opts.changedFiles || [];
668
+
669
+ // Check repo-map availability
670
+ const repoMapStatus = ensureRepoMapSync(opts);
671
+
672
+ return {
673
+ relatedDocs: findRelatedDocs(changedFiles, opts),
674
+ changelog: checkChangelog(changedFiles, opts),
675
+ markdownFiles: findMarkdownFiles(opts.cwd),
676
+ // New: repo-map integration
677
+ repoMap: {
678
+ available: repoMapStatus.available,
679
+ fallbackReason: repoMapStatus.fallbackReason,
680
+ stats: repoMapStatus.map ? {
681
+ files: Object.keys(repoMapStatus.map.files || {}).length,
682
+ symbols: repoMapStatus.map.stats?.totalSymbols || 0
683
+ } : null
684
+ },
685
+ // New: undocumented exports detection (pass repoMapStatus to avoid redundant call)
686
+ undocumentedExports: repoMapStatus.available
687
+ ? findUndocumentedExports(changedFiles, { ...opts, repoMapStatus })
688
+ : []
689
+ };
690
+ }
691
+
692
+ module.exports = {
693
+ DEFAULT_OPTIONS,
694
+ findRelatedDocs,
695
+ findMarkdownFiles,
696
+ analyzeDocIssues,
697
+ checkChangelog,
698
+ getExportsFromGit,
699
+ compareVersions,
700
+ findLineNumber,
701
+ collect,
702
+ // New: repo-map integration
703
+ ensureRepoMap,
704
+ ensureRepoMapSync,
705
+ getExportsFromRepoMap,
706
+ findUndocumentedExports,
707
+ isInternalExport,
708
+ isEntryPoint,
709
+ // Utilities
710
+ escapeRegex,
711
+ // Diagnostic
712
+ getRepoMapLoadError
713
+ };