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.
- package/.claude-plugin/marketplace.json +21 -14
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +18 -0
- package/README.md +7 -6
- package/adapters/codex/skills/agnix/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
- package/adapters/codex/skills/consult/SKILL.md +132 -57
- package/adapters/codex/skills/debate/SKILL.md +214 -0
- package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
- package/adapters/codex/skills/deslop/SKILL.md +0 -1
- package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
- package/adapters/codex/skills/enhance/SKILL.md +0 -1
- package/adapters/codex/skills/learn/SKILL.md +0 -1
- package/adapters/codex/skills/next-task/SKILL.md +0 -1
- package/adapters/codex/skills/perf/SKILL.md +0 -1
- package/adapters/codex/skills/repo-map/SKILL.md +0 -1
- package/adapters/codex/skills/ship/SKILL.md +0 -1
- package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
- package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
- package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
- package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
- package/adapters/opencode/agents/agent-enhancer.md +0 -1
- package/adapters/opencode/agents/agnix-agent.md +0 -1
- package/adapters/opencode/agents/ci-fixer.md +0 -1
- package/adapters/opencode/agents/ci-monitor.md +0 -1
- package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
- package/adapters/opencode/agents/consult-agent.md +122 -30
- package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
- package/adapters/opencode/agents/debate-orchestrator.md +169 -0
- package/adapters/opencode/agents/delivery-validator.md +0 -1
- package/adapters/opencode/agents/deslop-agent.md +0 -1
- package/adapters/opencode/agents/docs-enhancer.md +0 -1
- package/adapters/opencode/agents/exploration-agent.md +0 -1
- package/adapters/opencode/agents/hooks-enhancer.md +0 -1
- package/adapters/opencode/agents/implementation-agent.md +0 -1
- package/adapters/opencode/agents/learn-agent.md +0 -1
- package/adapters/opencode/agents/map-validator.md +0 -1
- package/adapters/opencode/agents/perf-analyzer.md +0 -1
- package/adapters/opencode/agents/perf-code-paths.md +0 -1
- package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
- package/adapters/opencode/agents/perf-orchestrator.md +0 -1
- package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
- package/adapters/opencode/agents/perf-theory-tester.md +0 -1
- package/adapters/opencode/agents/plan-synthesizer.md +0 -1
- package/adapters/opencode/agents/planning-agent.md +0 -1
- package/adapters/opencode/agents/plugin-enhancer.md +0 -1
- package/adapters/opencode/agents/prompt-enhancer.md +0 -1
- package/adapters/opencode/agents/simple-fixer.md +0 -1
- package/adapters/opencode/agents/skills-enhancer.md +0 -1
- package/adapters/opencode/agents/sync-docs-agent.md +0 -1
- package/adapters/opencode/agents/task-discoverer.md +0 -1
- package/adapters/opencode/agents/test-coverage-checker.md +0 -1
- package/adapters/opencode/agents/worktree-manager.md +0 -1
- package/adapters/opencode/commands/agnix.md +0 -1
- package/adapters/opencode/commands/audit-project-agents.md +0 -1
- package/adapters/opencode/commands/audit-project-github.md +0 -1
- package/adapters/opencode/commands/audit-project.md +0 -1
- package/adapters/opencode/commands/consult.md +133 -57
- package/adapters/opencode/commands/debate.md +224 -0
- package/adapters/opencode/commands/delivery-approval.md +0 -1
- package/adapters/opencode/commands/deslop.md +0 -1
- package/adapters/opencode/commands/drift-detect.md +0 -1
- package/adapters/opencode/commands/enhance.md +0 -1
- package/adapters/opencode/commands/learn.md +0 -1
- package/adapters/opencode/commands/next-task.md +0 -1
- package/adapters/opencode/commands/perf.md +0 -1
- package/adapters/opencode/commands/repo-map.md +0 -1
- package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
- package/adapters/opencode/commands/ship-deployment.md +0 -1
- package/adapters/opencode/commands/ship-error-handling.md +0 -1
- package/adapters/opencode/commands/ship.md +0 -1
- package/adapters/opencode/commands/sync-docs.md +0 -1
- package/adapters/opencode/skills/agnix/SKILL.md +1 -2
- package/adapters/opencode/skills/consult/SKILL.md +33 -23
- package/adapters/opencode/skills/debate/SKILL.md +245 -0
- package/adapters/opencode/skills/deslop/SKILL.md +1 -2
- package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
- package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
- package/adapters/opencode/skills/learn/SKILL.md +1 -2
- package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
- package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
- package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
- package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
- package/lib/adapter-transforms.js +24 -4
- package/package.json +1 -1
- package/plugins/agnix/.claude-plugin/plugin.json +1 -1
- package/plugins/agnix/skills/agnix/SKILL.md +1 -1
- package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
- package/plugins/audit-project/lib/adapter-transforms.js +24 -4
- package/plugins/consult/.claude-plugin/plugin.json +1 -1
- package/plugins/consult/agents/consult-agent.md +122 -29
- package/plugins/consult/commands/consult.md +135 -58
- package/plugins/consult/skills/consult/SKILL.md +31 -20
- package/plugins/debate/.claude-plugin/plugin.json +21 -0
- package/plugins/debate/agents/debate-orchestrator.md +175 -0
- package/plugins/debate/commands/debate.md +221 -0
- package/plugins/debate/lib/adapter-transforms.js +298 -0
- package/plugins/debate/lib/collectors/codebase.js +392 -0
- package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
- package/plugins/debate/lib/collectors/documentation.js +219 -0
- package/plugins/debate/lib/collectors/github.js +330 -0
- package/plugins/debate/lib/collectors/index.js +126 -0
- package/plugins/debate/lib/config/index.js +14 -0
- package/plugins/debate/lib/cross-platform/index.js +539 -0
- package/plugins/debate/lib/discovery/index.js +352 -0
- package/plugins/debate/lib/drift-detect/collectors.js +37 -0
- package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
- package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
- package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
- package/plugins/debate/lib/enhance/benchmark.js +417 -0
- package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
- package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
- package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
- package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
- package/plugins/debate/lib/enhance/fixer.js +721 -0
- package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
- package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
- package/plugins/debate/lib/enhance/index.js +127 -0
- package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
- package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
- package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
- package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
- package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
- package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
- package/plugins/debate/lib/enhance/reporter.js +1348 -0
- package/plugins/debate/lib/enhance/security-patterns.js +284 -0
- package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
- package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
- package/plugins/debate/lib/enhance/suppression.js +352 -0
- package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
- package/plugins/debate/lib/index.js +270 -0
- package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
- package/plugins/debate/lib/patterns/pipeline.js +948 -0
- package/plugins/debate/lib/patterns/review-patterns.js +558 -0
- package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
- package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
- package/plugins/debate/lib/perf/analyzer/index.js +22 -0
- package/plugins/debate/lib/perf/argument-parser.js +105 -0
- package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
- package/plugins/debate/lib/perf/baseline-store.js +127 -0
- package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
- package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
- package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
- package/plugins/debate/lib/perf/checkpoint.js +123 -0
- package/plugins/debate/lib/perf/code-paths.js +86 -0
- package/plugins/debate/lib/perf/consolidation.js +37 -0
- package/plugins/debate/lib/perf/constraint-runner.js +71 -0
- package/plugins/debate/lib/perf/experiment-runner.js +32 -0
- package/plugins/debate/lib/perf/index.js +41 -0
- package/plugins/debate/lib/perf/investigation-state.js +874 -0
- package/plugins/debate/lib/perf/optimization-runner.js +79 -0
- package/plugins/debate/lib/perf/profilers/go.js +22 -0
- package/plugins/debate/lib/perf/profilers/index.js +46 -0
- package/plugins/debate/lib/perf/profilers/java.js +23 -0
- package/plugins/debate/lib/perf/profilers/node.js +27 -0
- package/plugins/debate/lib/perf/profilers/python.js +23 -0
- package/plugins/debate/lib/perf/profilers/rust.js +23 -0
- package/plugins/debate/lib/perf/profiling-runner.js +75 -0
- package/plugins/debate/lib/perf/schemas.js +140 -0
- package/plugins/debate/lib/platform/detect-platform.js +413 -0
- package/plugins/debate/lib/platform/detection-configs.js +93 -0
- package/plugins/debate/lib/platform/state-dir.js +132 -0
- package/plugins/debate/lib/platform/verify-tools.js +182 -0
- package/plugins/debate/lib/repo-map/cache.js +152 -0
- package/plugins/debate/lib/repo-map/concurrency.js +29 -0
- package/plugins/debate/lib/repo-map/index.js +222 -0
- package/plugins/debate/lib/repo-map/installer.js +212 -0
- package/plugins/debate/lib/repo-map/queries/go.js +27 -0
- package/plugins/debate/lib/repo-map/queries/index.js +100 -0
- package/plugins/debate/lib/repo-map/queries/java.js +38 -0
- package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
- package/plugins/debate/lib/repo-map/queries/python.js +24 -0
- package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
- package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
- package/plugins/debate/lib/repo-map/runner.js +1364 -0
- package/plugins/debate/lib/repo-map/updater.js +562 -0
- package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
- package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
- package/plugins/debate/lib/schemas/validator.js +247 -0
- package/plugins/debate/lib/sources/custom-handler.js +199 -0
- package/plugins/debate/lib/sources/policy-questions.js +246 -0
- package/plugins/debate/lib/sources/source-cache.js +165 -0
- package/plugins/debate/lib/state/workflow-state.js +576 -0
- package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/debate/lib/types/index.d.ts +84 -0
- package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/debate/lib/utils/atomic-write.js +94 -0
- package/plugins/debate/lib/utils/cache-manager.js +159 -0
- package/plugins/debate/lib/utils/command-parser.js +0 -0
- package/plugins/debate/lib/utils/context-optimizer.js +300 -0
- package/plugins/debate/lib/utils/deprecation.js +37 -0
- package/plugins/debate/lib/utils/shell-escape.js +88 -0
- package/plugins/debate/lib/utils/state-helpers.js +61 -0
- package/plugins/debate/skills/debate/SKILL.md +264 -0
- package/plugins/deslop/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop/lib/adapter-transforms.js +24 -4
- package/plugins/deslop/skills/deslop/SKILL.md +1 -1
- package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
- package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
- package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
- package/plugins/enhance/.claude-plugin/plugin.json +1 -1
- package/plugins/enhance/lib/adapter-transforms.js +24 -4
- package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
- package/plugins/learn/.claude-plugin/plugin.json +1 -1
- package/plugins/learn/agents/learn-agent.md +1 -1
- package/plugins/learn/lib/adapter-transforms.js +24 -4
- package/plugins/learn/skills/learn/SKILL.md +1 -1
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/exploration-agent.md +1 -1
- package/plugins/next-task/lib/adapter-transforms.js +24 -4
- package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
- package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
- package/plugins/perf/.claude-plugin/plugin.json +1 -1
- package/plugins/perf/lib/adapter-transforms.js +24 -4
- package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
- package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
- package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
- package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
- package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
- package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
- package/plugins/repo-map/lib/adapter-transforms.js +24 -4
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
- package/scripts/gen-adapters.js +6 -7
- package/scripts/generate-docs.js +4 -2
- package/scripts/plugins.txt +1 -0
- 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
|
+
};
|