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,948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slop Detection Pipeline
|
|
3
|
+
*
|
|
4
|
+
* 3-phase detection pipeline orchestrator:
|
|
5
|
+
* - Phase 1 (built-in): regex patterns + multi-pass analyzers - always runs
|
|
6
|
+
* - Phase 2 (optional): CLI tools (jscpd, madge, escomplex) - if available
|
|
7
|
+
* - Phase 3 (LLM handoff): certainty-tagged findings for agent review
|
|
8
|
+
*
|
|
9
|
+
* Inherits modes from deslop: report (analyze only) vs apply (fix issues)
|
|
10
|
+
*
|
|
11
|
+
* @module patterns/pipeline
|
|
12
|
+
* @author Avi Fenesh
|
|
13
|
+
* @license MIT
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const fsPromises = require('fs').promises;
|
|
19
|
+
const slopPatterns = require('./slop-patterns');
|
|
20
|
+
const analyzers = require('./slop-analyzers');
|
|
21
|
+
|
|
22
|
+
// Concurrent file reads per batch
|
|
23
|
+
const FILE_READ_BATCH_SIZE = 50;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Global exclusions - files that should NEVER be flagged
|
|
27
|
+
* These are meta-files that define detection patterns, so they naturally
|
|
28
|
+
* contain the patterns they're detecting (not actual slop)
|
|
29
|
+
*/
|
|
30
|
+
const GLOBAL_EXCLUSIONS = [
|
|
31
|
+
'**/slop-patterns.js',
|
|
32
|
+
'**/slop-analyzers.js',
|
|
33
|
+
'**/cli-enhancers.js',
|
|
34
|
+
'**/pipeline.js',
|
|
35
|
+
'**/review-patterns.js',
|
|
36
|
+
'**/detect.js',
|
|
37
|
+
'**/*-patterns.js', // Any pattern definition file
|
|
38
|
+
'**/*-analyzers.js' // Any analyzer definition file
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Certainty levels for findings
|
|
43
|
+
* HIGH: Single regex match - definitive
|
|
44
|
+
* MEDIUM: Multi-pass analysis - requires context
|
|
45
|
+
* LOW: Heuristic/CLI tool - needs verification
|
|
46
|
+
*/
|
|
47
|
+
const CERTAINTY = {
|
|
48
|
+
HIGH: 'HIGH',
|
|
49
|
+
MEDIUM: 'MEDIUM',
|
|
50
|
+
LOW: 'LOW'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Thoroughness levels
|
|
55
|
+
* quick: Phase 1 regex only - fastest
|
|
56
|
+
* normal: Phase 1 + multi-pass analyzers - balanced
|
|
57
|
+
* deep: Phase 1 + Phase 2 CLI tools (if available) - thorough
|
|
58
|
+
*/
|
|
59
|
+
const THOROUGHNESS = {
|
|
60
|
+
QUICK: 'quick',
|
|
61
|
+
NORMAL: 'normal',
|
|
62
|
+
DEEP: 'deep'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Default timeout for pipeline execution (5 minutes)
|
|
66
|
+
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read multiple files asynchronously in batches
|
|
70
|
+
* @param {string[]} files - Array of file paths
|
|
71
|
+
* @param {number} batchSize - Concurrent reads per batch
|
|
72
|
+
* @returns {Promise<Map<string, {content: string|null, error: Error|null}>>}
|
|
73
|
+
*/
|
|
74
|
+
async function batchReadFiles(files, batchSize = FILE_READ_BATCH_SIZE) {
|
|
75
|
+
const results = new Map();
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
78
|
+
const batch = files.slice(i, i + batchSize);
|
|
79
|
+
const batchResults = await Promise.all(
|
|
80
|
+
batch.map(async (file) => {
|
|
81
|
+
try {
|
|
82
|
+
const content = await fsPromises.readFile(file, 'utf8');
|
|
83
|
+
return { file, content, error: null };
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return { file, content: null, error: err };
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
for (const result of batchResults) {
|
|
91
|
+
results.set(result.file, { content: result.content, error: result.error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run the slop detection pipeline
|
|
100
|
+
*
|
|
101
|
+
* @param {string} repoPath - Repository root path
|
|
102
|
+
* @param {Object} options - Pipeline options
|
|
103
|
+
* @param {string} [options.thoroughness='normal'] - quick | normal | deep
|
|
104
|
+
* @param {string[]} [options.targetFiles] - Specific files to analyze (defaults to all source files)
|
|
105
|
+
* @param {string} [options.language] - Filter to specific language
|
|
106
|
+
* @param {string} [options.mode='report'] - report | apply
|
|
107
|
+
* @param {Object} [options.cliTools] - Pre-detected CLI tools (from detectAvailableTools)
|
|
108
|
+
* @param {number} [options.timeout] - Timeout in ms (default: 5 minutes)
|
|
109
|
+
* @returns {Promise<Object>} Pipeline results: { findings, summary, phase3Prompt, missingTools }
|
|
110
|
+
*/
|
|
111
|
+
async function runPipeline(repoPath, options = {}) {
|
|
112
|
+
const thoroughness = options.thoroughness || THOROUGHNESS.NORMAL;
|
|
113
|
+
const mode = options.mode || 'report';
|
|
114
|
+
const language = options.language || null;
|
|
115
|
+
const timeout = options.timeout || DEFAULT_TIMEOUT_MS;
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
|
|
118
|
+
const findings = [];
|
|
119
|
+
const missingTools = [];
|
|
120
|
+
let cliTools = options.cliTools || null;
|
|
121
|
+
let timedOut = false;
|
|
122
|
+
|
|
123
|
+
// Helper to check if timeout exceeded
|
|
124
|
+
function isTimedOut() {
|
|
125
|
+
return Date.now() - startTime > timeout;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Get target files - limit to 200 to prevent memory exhaustion
|
|
129
|
+
// Users can pass targetFiles explicitly for larger scans
|
|
130
|
+
let targetFiles = options.targetFiles;
|
|
131
|
+
const maxFiles = options.maxFiles || 200;
|
|
132
|
+
if (!targetFiles || targetFiles.length === 0) {
|
|
133
|
+
const result = analyzers.countSourceFiles(repoPath, {
|
|
134
|
+
maxFiles,
|
|
135
|
+
includeTests: false
|
|
136
|
+
});
|
|
137
|
+
targetFiles = result.files;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Pre-load all file contents asynchronously (used by Phase 1 and Phase 1b)
|
|
141
|
+
let fileContents;
|
|
142
|
+
try {
|
|
143
|
+
fileContents = await batchReadFiles(targetFiles);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error('[WARN] File pre-loading failed:', err.message);
|
|
146
|
+
fileContents = new Map();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Phase 1: Built-in regex patterns (always runs)
|
|
150
|
+
// Wrapped in try-catch to prevent crashes on malformed files
|
|
151
|
+
try {
|
|
152
|
+
const phase1Results = runPhase1(repoPath, targetFiles, language, fileContents);
|
|
153
|
+
findings.push(...phase1Results);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// Log but continue with empty phase1 results rather than crash
|
|
156
|
+
console.error('[WARN] Phase 1 failed:', err.message);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check timeout after Phase 1
|
|
160
|
+
if (isTimedOut()) {
|
|
161
|
+
console.error('[WARN] Pipeline timeout after Phase 1');
|
|
162
|
+
timedOut = true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Phase 1b: Multi-pass analyzers (if normal or deep)
|
|
166
|
+
// Wrapped in try-catch as these are expensive and can fail
|
|
167
|
+
if (!timedOut && thoroughness !== THOROUGHNESS.QUICK) {
|
|
168
|
+
try {
|
|
169
|
+
// runMultiPassAnalyzers is async to support non-blocking I/O (PERF-007)
|
|
170
|
+
const multiPassResults = await runMultiPassAnalyzers(repoPath, targetFiles, fileContents);
|
|
171
|
+
findings.push(...multiPassResults);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// Log but continue with partial results rather than crash
|
|
174
|
+
console.error('[WARN] Phase 1b failed:', err.message);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check timeout after Phase 1b
|
|
178
|
+
if (isTimedOut()) {
|
|
179
|
+
console.error('[WARN] Pipeline timeout after Phase 1b');
|
|
180
|
+
timedOut = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Phase 2: CLI tools (only if deep and tools available)
|
|
185
|
+
// Detect project languages for language-aware tool recommendations
|
|
186
|
+
let detectedLanguages = [];
|
|
187
|
+
if (!timedOut && thoroughness === THOROUGHNESS.DEEP) {
|
|
188
|
+
// Lazy-load CLI enhancers to avoid circular dependencies
|
|
189
|
+
const cliEnhancers = require('./cli-enhancers');
|
|
190
|
+
|
|
191
|
+
// Detect project languages
|
|
192
|
+
detectedLanguages = cliEnhancers.detectProjectLanguages(repoPath);
|
|
193
|
+
|
|
194
|
+
if (!cliTools) {
|
|
195
|
+
// Get tools relevant for detected languages
|
|
196
|
+
cliTools = cliEnhancers.detectAvailableTools(detectedLanguages);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Track missing tools (only those relevant for project languages)
|
|
200
|
+
const relevantTools = cliEnhancers.getToolsForLanguages(detectedLanguages);
|
|
201
|
+
for (const toolName of Object.keys(relevantTools)) {
|
|
202
|
+
if (!cliTools[toolName]) {
|
|
203
|
+
missingTools.push(toolName);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const phase2Results = runPhase2(repoPath, cliTools, targetFiles);
|
|
208
|
+
findings.push(...phase2Results);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Build summary
|
|
212
|
+
const summary = buildSummary(findings);
|
|
213
|
+
|
|
214
|
+
// Generate Phase 3 handoff prompt
|
|
215
|
+
const phase3Prompt = formatHandoffPrompt(findings, mode);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
findings,
|
|
219
|
+
summary,
|
|
220
|
+
phase3Prompt,
|
|
221
|
+
missingTools,
|
|
222
|
+
detectedLanguages,
|
|
223
|
+
metadata: {
|
|
224
|
+
repoPath,
|
|
225
|
+
thoroughness,
|
|
226
|
+
mode,
|
|
227
|
+
filesAnalyzed: targetFiles.length,
|
|
228
|
+
timestamp: new Date().toISOString(),
|
|
229
|
+
timedOut,
|
|
230
|
+
elapsedMs: Date.now() - startTime
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Phase 1: Run built-in regex patterns against target files
|
|
237
|
+
*
|
|
238
|
+
* @param {string} repoPath - Repository root
|
|
239
|
+
* @param {string[]} targetFiles - Files to analyze
|
|
240
|
+
* @param {string|null} language - Optional language filter
|
|
241
|
+
* @param {Map<string, {content: string|null, error: Error|null}>} [fileContents] - Pre-loaded file contents (optional)
|
|
242
|
+
* @returns {Array} Findings with HIGH certainty
|
|
243
|
+
*/
|
|
244
|
+
function runPhase1(repoPath, targetFiles, language, fileContents) {
|
|
245
|
+
const findings = [];
|
|
246
|
+
const contentMap = fileContents || new Map();
|
|
247
|
+
|
|
248
|
+
// Get patterns (filtered by language if specified)
|
|
249
|
+
const patterns = language
|
|
250
|
+
? slopPatterns.getPatternsForLanguage(language)
|
|
251
|
+
: slopPatterns.slopPatterns;
|
|
252
|
+
|
|
253
|
+
for (const file of targetFiles) {
|
|
254
|
+
// Skip globally excluded files (pattern definition files)
|
|
255
|
+
if (slopPatterns.isFileExcluded(file, GLOBAL_EXCLUSIONS)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Detect file language once per file
|
|
260
|
+
const fileLanguage = analyzers.detectLanguage(file);
|
|
261
|
+
|
|
262
|
+
// Skip if language filter doesn't match file extension
|
|
263
|
+
if (language) {
|
|
264
|
+
// For JS/TS language filter, accept both 'javascript' and 'js' detection results
|
|
265
|
+
const isJsFamily = (language === 'javascript' || language === 'typescript') && fileLanguage === 'js';
|
|
266
|
+
if (fileLanguage !== language && !isJsFamily) continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Get pre-loaded content from map, fallback to synchronous read
|
|
270
|
+
const filePath = path.isAbsolute(file) ? file : path.join(repoPath, file);
|
|
271
|
+
const readResult = contentMap.get(file) || contentMap.get(filePath);
|
|
272
|
+
|
|
273
|
+
let content;
|
|
274
|
+
if (readResult && !readResult.error && readResult.content !== null) {
|
|
275
|
+
content = readResult.content;
|
|
276
|
+
} else {
|
|
277
|
+
// Fallback to synchronous read (for backward compatibility with direct calls)
|
|
278
|
+
try {
|
|
279
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
280
|
+
} catch {
|
|
281
|
+
continue; // Skip unreadable files
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const lines = content.split('\n');
|
|
286
|
+
|
|
287
|
+
for (const [patternName, pattern] of Object.entries(patterns)) {
|
|
288
|
+
// Skip multi-pass patterns (handled separately)
|
|
289
|
+
if (pattern.requiresMultiPass) continue;
|
|
290
|
+
|
|
291
|
+
// Skip if no regex pattern
|
|
292
|
+
if (!pattern.pattern) continue;
|
|
293
|
+
|
|
294
|
+
// Skip if file matches exclude patterns
|
|
295
|
+
if (slopPatterns.isFileExcluded(file, pattern.exclude)) continue;
|
|
296
|
+
|
|
297
|
+
// Skip language-specific patterns that don't match file's language
|
|
298
|
+
if (pattern.language) {
|
|
299
|
+
const patternLang = pattern.language;
|
|
300
|
+
// Map detection results to pattern language names
|
|
301
|
+
// Note: 'js' from detectLanguage covers both JavaScript and TypeScript
|
|
302
|
+
const langMatch = (patternLang === 'javascript' && fileLanguage === 'js') ||
|
|
303
|
+
(patternLang === 'python' && fileLanguage === 'python') ||
|
|
304
|
+
(patternLang === 'rust' && fileLanguage === 'rust') ||
|
|
305
|
+
(patternLang === 'go' && fileLanguage === 'go') ||
|
|
306
|
+
(patternLang === 'java' && fileLanguage === 'java') ||
|
|
307
|
+
patternLang === fileLanguage; // Direct match fallback
|
|
308
|
+
if (!langMatch) continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Handle patterns requiring consecutive line blocks
|
|
312
|
+
if (pattern.minConsecutiveLines) {
|
|
313
|
+
const minLines = pattern.minConsecutiveLines;
|
|
314
|
+
let consecutiveStart = -1;
|
|
315
|
+
let consecutiveCount = 0;
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i <= lines.length; i++) {
|
|
318
|
+
const line = i < lines.length ? lines[i] : ''; // Empty line at end to flush
|
|
319
|
+
const matches = i < lines.length && pattern.pattern.test(line);
|
|
320
|
+
|
|
321
|
+
if (matches) {
|
|
322
|
+
if (consecutiveStart === -1) {
|
|
323
|
+
consecutiveStart = i;
|
|
324
|
+
}
|
|
325
|
+
consecutiveCount++;
|
|
326
|
+
} else {
|
|
327
|
+
// End of consecutive block - check if it meets threshold
|
|
328
|
+
if (consecutiveCount >= minLines) {
|
|
329
|
+
findings.push({
|
|
330
|
+
file,
|
|
331
|
+
line: consecutiveStart + 1,
|
|
332
|
+
patternName,
|
|
333
|
+
severity: pattern.severity,
|
|
334
|
+
certainty: CERTAINTY.HIGH,
|
|
335
|
+
description: `${pattern.description} (${consecutiveCount} consecutive lines)`,
|
|
336
|
+
autoFix: pattern.autoFix,
|
|
337
|
+
content: `Lines ${consecutiveStart + 1}-${consecutiveStart + consecutiveCount}`,
|
|
338
|
+
phase: 1,
|
|
339
|
+
details: { startLine: consecutiveStart + 1, endLine: consecutiveStart + consecutiveCount, lineCount: consecutiveCount }
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
consecutiveStart = -1;
|
|
343
|
+
consecutiveCount = 0;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
// Standard per-line matching
|
|
348
|
+
for (let i = 0; i < lines.length; i++) {
|
|
349
|
+
const line = lines[i];
|
|
350
|
+
if (pattern.pattern.test(line)) {
|
|
351
|
+
findings.push({
|
|
352
|
+
file,
|
|
353
|
+
line: i + 1,
|
|
354
|
+
patternName,
|
|
355
|
+
severity: pattern.severity,
|
|
356
|
+
certainty: CERTAINTY.HIGH,
|
|
357
|
+
description: pattern.description,
|
|
358
|
+
autoFix: pattern.autoFix,
|
|
359
|
+
content: line.trim().substring(0, 100),
|
|
360
|
+
phase: 1
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return findings;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Run multi-pass analyzers (doc/code ratio, verbosity, etc.)
|
|
373
|
+
*
|
|
374
|
+
* @param {string} repoPath - Repository root
|
|
375
|
+
* @param {string[]} targetFiles - Files to analyze
|
|
376
|
+
* @param {Map<string, {content: string|null, error: Error|null}>} [fileContents] - Pre-loaded file contents (optional)
|
|
377
|
+
* @returns {Promise<Array>} Findings with MEDIUM certainty
|
|
378
|
+
*/
|
|
379
|
+
async function runMultiPassAnalyzers(repoPath, targetFiles, fileContents) {
|
|
380
|
+
const findings = [];
|
|
381
|
+
const contentMap = fileContents || new Map();
|
|
382
|
+
|
|
383
|
+
// Skip expensive analyzers for large file sets to prevent memory exhaustion
|
|
384
|
+
const isLargeRepo = targetFiles.length > 100;
|
|
385
|
+
|
|
386
|
+
// Get multi-pass pattern definitions for thresholds
|
|
387
|
+
const multiPassPatterns = slopPatterns.getMultiPassPatterns();
|
|
388
|
+
|
|
389
|
+
// Supported languages for doc/code and verbosity analysis
|
|
390
|
+
const docCodeLangs = /\.(js|jsx|ts|tsx|mjs|cjs|py|rs|java|go)$/i;
|
|
391
|
+
|
|
392
|
+
for (const file of targetFiles) {
|
|
393
|
+
if (!file.match(docCodeLangs)) continue;
|
|
394
|
+
if (analyzers.isTestFile(file)) continue;
|
|
395
|
+
// Skip globally excluded files (pattern definition files)
|
|
396
|
+
if (slopPatterns.isFileExcluded(file, GLOBAL_EXCLUSIONS)) continue;
|
|
397
|
+
|
|
398
|
+
// Get pre-loaded content from map, fallback to synchronous read
|
|
399
|
+
const filePath = path.isAbsolute(file) ? file : path.join(repoPath, file);
|
|
400
|
+
const readResult = contentMap.get(file) || contentMap.get(filePath);
|
|
401
|
+
|
|
402
|
+
let content;
|
|
403
|
+
if (readResult && !readResult.error && readResult.content !== null) {
|
|
404
|
+
content = readResult.content;
|
|
405
|
+
} else {
|
|
406
|
+
// Fallback to synchronous read (for backward compatibility with direct calls)
|
|
407
|
+
try {
|
|
408
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
409
|
+
} catch {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Doc/code ratio analysis (multi-language)
|
|
415
|
+
const docCodePattern = multiPassPatterns.doc_code_ratio_js;
|
|
416
|
+
if (docCodePattern) {
|
|
417
|
+
const docRatioViolations = analyzers.analyzeDocCodeRatio(content, {
|
|
418
|
+
minFunctionLines: docCodePattern.minFunctionLines || 3,
|
|
419
|
+
maxRatio: docCodePattern.maxRatio || 3.0,
|
|
420
|
+
filePath: file
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
for (const v of docRatioViolations) {
|
|
424
|
+
findings.push({
|
|
425
|
+
file,
|
|
426
|
+
line: v.line,
|
|
427
|
+
patternName: 'doc_code_ratio',
|
|
428
|
+
severity: docCodePattern.severity,
|
|
429
|
+
certainty: CERTAINTY.MEDIUM,
|
|
430
|
+
description: `${docCodePattern.description} (${v.docLines} doc lines / ${v.codeLines} code lines = ${v.ratio}x)`,
|
|
431
|
+
autoFix: docCodePattern.autoFix,
|
|
432
|
+
content: v.functionName ? `${v.functionName}()` : `Function at line ${v.line}`,
|
|
433
|
+
phase: 1,
|
|
434
|
+
details: { docLines: v.docLines, codeLines: v.codeLines, ratio: v.ratio, functionName: v.functionName }
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Verbosity ratio analysis (multi-language)
|
|
440
|
+
const verbosityPattern = multiPassPatterns.verbosity_ratio;
|
|
441
|
+
if (verbosityPattern) {
|
|
442
|
+
const verbosityViolations = analyzers.analyzeVerbosityRatio(content, {
|
|
443
|
+
minCodeLines: verbosityPattern.minCodeLines || 3,
|
|
444
|
+
maxCommentRatio: verbosityPattern.maxCommentRatio || 2.0,
|
|
445
|
+
filePath: file
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
for (const v of verbosityViolations) {
|
|
449
|
+
findings.push({
|
|
450
|
+
file,
|
|
451
|
+
line: v.line,
|
|
452
|
+
patternName: 'verbosity_ratio',
|
|
453
|
+
severity: verbosityPattern.severity,
|
|
454
|
+
certainty: CERTAINTY.MEDIUM,
|
|
455
|
+
description: `${verbosityPattern.description} (${v.commentLines} comment lines / ${v.codeLines} code lines = ${v.ratio}x)`,
|
|
456
|
+
autoFix: verbosityPattern.autoFix,
|
|
457
|
+
content: `Function at line ${v.line}`,
|
|
458
|
+
phase: 1,
|
|
459
|
+
details: { commentLines: v.commentLines, codeLines: v.codeLines, ratio: v.ratio }
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Project-level analyzers (run once, not per-file)
|
|
466
|
+
// Wrap in try-catch to prevent crashes
|
|
467
|
+
const overEngPattern = multiPassPatterns.over_engineering_metrics;
|
|
468
|
+
if (overEngPattern && !isLargeRepo) {
|
|
469
|
+
try {
|
|
470
|
+
// analyzeOverEngineering is async to avoid blocking I/O (PERF-007)
|
|
471
|
+
const overEngResult = await analyzers.analyzeOverEngineering(repoPath, {
|
|
472
|
+
fileRatioThreshold: overEngPattern.fileRatioThreshold || 20,
|
|
473
|
+
linesPerExportThreshold: overEngPattern.linesPerExportThreshold || 500,
|
|
474
|
+
depthThreshold: overEngPattern.depthThreshold || 4
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
for (const v of overEngResult.violations) {
|
|
478
|
+
findings.push({
|
|
479
|
+
file: 'project-level',
|
|
480
|
+
line: 0,
|
|
481
|
+
patternName: 'over_engineering_metrics',
|
|
482
|
+
severity: v.severity,
|
|
483
|
+
certainty: CERTAINTY.MEDIUM,
|
|
484
|
+
description: `Over-engineering: ${v.type} - ${v.value} (threshold: ${v.threshold})`,
|
|
485
|
+
autoFix: 'flag',
|
|
486
|
+
content: v.value,
|
|
487
|
+
phase: 1,
|
|
488
|
+
details: v.details
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
} catch (err) {
|
|
492
|
+
// Skip on error - don't crash the pipeline
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Buzzword inflation analysis - EXPENSIVE: reads files multiple times
|
|
497
|
+
// Skip for large repos to prevent memory exhaustion
|
|
498
|
+
const buzzwordPattern = multiPassPatterns.buzzword_inflation;
|
|
499
|
+
if (buzzwordPattern && !isLargeRepo) {
|
|
500
|
+
try {
|
|
501
|
+
const buzzwordResult = analyzers.analyzeBuzzwordInflation(repoPath, {
|
|
502
|
+
minEvidenceMatches: buzzwordPattern.minEvidenceMatches || 2
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
for (const v of buzzwordResult.violations) {
|
|
506
|
+
findings.push({
|
|
507
|
+
file: v.file,
|
|
508
|
+
line: v.line,
|
|
509
|
+
patternName: 'buzzword_inflation',
|
|
510
|
+
severity: v.severity,
|
|
511
|
+
certainty: CERTAINTY.MEDIUM,
|
|
512
|
+
description: v.message,
|
|
513
|
+
autoFix: 'flag',
|
|
514
|
+
content: v.claim,
|
|
515
|
+
phase: 1,
|
|
516
|
+
details: { buzzword: v.buzzword, category: v.category, evidenceCount: v.evidenceCount }
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
} catch (err) {
|
|
520
|
+
// Skip on error - don't crash the pipeline
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Infrastructure without implementation - EXPENSIVE: reads all files twice
|
|
525
|
+
// Skip for large repos to prevent memory exhaustion
|
|
526
|
+
const infraPattern = multiPassPatterns.infrastructure_without_implementation;
|
|
527
|
+
if (infraPattern && !isLargeRepo) {
|
|
528
|
+
try {
|
|
529
|
+
const infraResult = analyzers.analyzeInfrastructureWithoutImplementation(repoPath);
|
|
530
|
+
|
|
531
|
+
for (const v of infraResult.violations) {
|
|
532
|
+
findings.push({
|
|
533
|
+
file: v.file,
|
|
534
|
+
line: v.line,
|
|
535
|
+
patternName: 'infrastructure_without_implementation',
|
|
536
|
+
severity: v.severity,
|
|
537
|
+
certainty: CERTAINTY.MEDIUM,
|
|
538
|
+
description: v.message,
|
|
539
|
+
autoFix: 'flag',
|
|
540
|
+
content: v.content,
|
|
541
|
+
phase: 1,
|
|
542
|
+
details: { varName: v.varName, type: v.type }
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
} catch (err) {
|
|
546
|
+
// Skip on error - don't crash the pipeline
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Dead code analysis (per-file)
|
|
551
|
+
const deadCodePattern = multiPassPatterns.dead_code;
|
|
552
|
+
if (deadCodePattern) {
|
|
553
|
+
for (const file of targetFiles) {
|
|
554
|
+
// Skip test files
|
|
555
|
+
if (analyzers.isTestFile(file)) continue;
|
|
556
|
+
// Skip globally excluded files (pattern definition files)
|
|
557
|
+
if (slopPatterns.isFileExcluded(file, GLOBAL_EXCLUSIONS)) continue;
|
|
558
|
+
|
|
559
|
+
// Get pre-loaded content from map, fallback to synchronous read
|
|
560
|
+
const filePath = path.isAbsolute(file) ? file : path.join(repoPath, file);
|
|
561
|
+
const readResult = contentMap.get(file) || contentMap.get(filePath);
|
|
562
|
+
|
|
563
|
+
let content;
|
|
564
|
+
if (readResult && !readResult.error && readResult.content !== null) {
|
|
565
|
+
content = readResult.content;
|
|
566
|
+
} else {
|
|
567
|
+
// Fallback to synchronous read (for backward compatibility with direct calls)
|
|
568
|
+
try {
|
|
569
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
570
|
+
} catch {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const deadCodeViolations = analyzers.analyzeDeadCode(content, { filePath: file });
|
|
576
|
+
|
|
577
|
+
for (const v of deadCodeViolations) {
|
|
578
|
+
findings.push({
|
|
579
|
+
file,
|
|
580
|
+
line: v.line,
|
|
581
|
+
patternName: 'dead_code',
|
|
582
|
+
severity: deadCodePattern.severity,
|
|
583
|
+
certainty: CERTAINTY.MEDIUM,
|
|
584
|
+
description: `${deadCodePattern.description}: ${v.terminationType} at line ${v.terminationLine}`,
|
|
585
|
+
autoFix: deadCodePattern.autoFix,
|
|
586
|
+
content: v.content,
|
|
587
|
+
phase: 1,
|
|
588
|
+
details: { terminationType: v.terminationType, terminationLine: v.terminationLine }
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Stub function analysis (per-file, multi-language)
|
|
595
|
+
const stubPattern = multiPassPatterns.placeholder_stub_returns_js;
|
|
596
|
+
if (stubPattern) {
|
|
597
|
+
// Supported extensions for stub detection
|
|
598
|
+
const stubExtensions = /\.(js|jsx|ts|tsx|mjs|cjs|py|rs|java|go)$/i;
|
|
599
|
+
|
|
600
|
+
for (const file of targetFiles) {
|
|
601
|
+
if (analyzers.isTestFile(file)) continue;
|
|
602
|
+
if (!file.match(stubExtensions)) continue;
|
|
603
|
+
// Honor pattern exclude globs (e.g., *.config.*)
|
|
604
|
+
if (slopPatterns.isFileExcluded(file, stubPattern.exclude)) continue;
|
|
605
|
+
|
|
606
|
+
// Get pre-loaded content from map, fallback to synchronous read
|
|
607
|
+
const filePath = path.isAbsolute(file) ? file : path.join(repoPath, file);
|
|
608
|
+
const readResult = contentMap.get(file) || contentMap.get(filePath);
|
|
609
|
+
|
|
610
|
+
let content;
|
|
611
|
+
if (readResult && !readResult.error && readResult.content !== null) {
|
|
612
|
+
content = readResult.content;
|
|
613
|
+
} else {
|
|
614
|
+
// Fallback to synchronous read (for backward compatibility with direct calls)
|
|
615
|
+
try {
|
|
616
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
617
|
+
} catch {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const stubViolations = analyzers.analyzeStubFunctions(content, { filePath: file });
|
|
623
|
+
|
|
624
|
+
for (const v of stubViolations) {
|
|
625
|
+
findings.push({
|
|
626
|
+
file,
|
|
627
|
+
line: v.line,
|
|
628
|
+
patternName: 'placeholder_stub_returns',
|
|
629
|
+
severity: v.hasTodo ? 'high' : stubPattern.severity,
|
|
630
|
+
certainty: v.certainty,
|
|
631
|
+
description: `${stubPattern.description}: ${v.functionName}() returns ${v.returnValue}`,
|
|
632
|
+
autoFix: stubPattern.autoFix,
|
|
633
|
+
content: v.content,
|
|
634
|
+
phase: 1,
|
|
635
|
+
details: { functionName: v.functionName, returnValue: v.returnValue, hasTodo: v.hasTodo }
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Shotgun surgery analysis (git history) - can have large buffer issues
|
|
642
|
+
// Skip for large repos to prevent memory exhaustion
|
|
643
|
+
const shotgunPattern = multiPassPatterns.shotgun_surgery;
|
|
644
|
+
if (shotgunPattern && !isLargeRepo) {
|
|
645
|
+
try {
|
|
646
|
+
const shotgunResult = analyzers.analyzeShotgunSurgery(repoPath, {
|
|
647
|
+
commitLimit: Math.min(shotgunPattern.commitLimit || 100, 50), // Reduce commit limit
|
|
648
|
+
clusterThreshold: shotgunPattern.clusterThreshold || 5
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
for (const v of shotgunResult.violations) {
|
|
652
|
+
findings.push({
|
|
653
|
+
file: 'project-level',
|
|
654
|
+
line: 0,
|
|
655
|
+
patternName: 'shotgun_surgery',
|
|
656
|
+
severity: shotgunPattern.severity,
|
|
657
|
+
certainty: CERTAINTY.MEDIUM,
|
|
658
|
+
description: `${shotgunPattern.description}: ${v.files.length} files change together ${v.count} times`,
|
|
659
|
+
autoFix: shotgunPattern.autoFix,
|
|
660
|
+
content: v.files.join(', ').substring(0, 100),
|
|
661
|
+
phase: 1,
|
|
662
|
+
details: { files: v.files, changeCount: v.count }
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
// Git not available or not a git repo - skip silently
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return findings;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Phase 2: Run CLI tools (if available)
|
|
675
|
+
*
|
|
676
|
+
* @param {string} repoPath - Repository root
|
|
677
|
+
* @param {Object} cliTools - Available CLI tools { jscpd, madge, escomplex }
|
|
678
|
+
* @param {string[]} targetFiles - Files to analyze
|
|
679
|
+
* @returns {Array} Findings with LOW certainty
|
|
680
|
+
*/
|
|
681
|
+
function runPhase2(repoPath, cliTools, targetFiles) {
|
|
682
|
+
const findings = [];
|
|
683
|
+
const cliEnhancers = require('./cli-enhancers');
|
|
684
|
+
|
|
685
|
+
// Duplicate detection with jscpd
|
|
686
|
+
if (cliTools.jscpd) {
|
|
687
|
+
const duplicates = cliEnhancers.runDuplicateDetection(repoPath);
|
|
688
|
+
if (duplicates) {
|
|
689
|
+
for (const dup of duplicates) {
|
|
690
|
+
findings.push({
|
|
691
|
+
file: dup.firstFile,
|
|
692
|
+
line: dup.firstLine,
|
|
693
|
+
patternName: 'code_duplication',
|
|
694
|
+
severity: 'medium',
|
|
695
|
+
certainty: CERTAINTY.LOW,
|
|
696
|
+
description: `Code duplication: ${dup.lines} lines duplicated in ${dup.secondFile}:${dup.secondLine}`,
|
|
697
|
+
autoFix: 'flag',
|
|
698
|
+
content: `${dup.lines} lines duplicated`,
|
|
699
|
+
phase: 2,
|
|
700
|
+
details: dup
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Circular dependencies with madge
|
|
707
|
+
if (cliTools.madge) {
|
|
708
|
+
const circularDeps = cliEnhancers.runDependencyAnalysis(repoPath);
|
|
709
|
+
if (circularDeps) {
|
|
710
|
+
for (const cycle of circularDeps) {
|
|
711
|
+
findings.push({
|
|
712
|
+
file: cycle[0],
|
|
713
|
+
line: 0,
|
|
714
|
+
patternName: 'circular_dependency',
|
|
715
|
+
severity: 'high',
|
|
716
|
+
certainty: CERTAINTY.LOW,
|
|
717
|
+
description: `Circular dependency: ${cycle.join(' -> ')}`,
|
|
718
|
+
autoFix: 'flag',
|
|
719
|
+
content: cycle.join(' -> '),
|
|
720
|
+
phase: 2,
|
|
721
|
+
details: { cycle }
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Complexity analysis with escomplex
|
|
728
|
+
if (cliTools.escomplex) {
|
|
729
|
+
const complexityResults = cliEnhancers.runComplexityAnalysis(repoPath, targetFiles);
|
|
730
|
+
if (complexityResults) {
|
|
731
|
+
for (const result of complexityResults) {
|
|
732
|
+
if (result.complexity > 10) { // High cyclomatic complexity threshold
|
|
733
|
+
findings.push({
|
|
734
|
+
file: result.file,
|
|
735
|
+
line: result.line || 0,
|
|
736
|
+
patternName: 'high_complexity',
|
|
737
|
+
severity: result.complexity > 20 ? 'high' : 'medium',
|
|
738
|
+
certainty: CERTAINTY.LOW,
|
|
739
|
+
description: `High cyclomatic complexity: ${result.complexity} in ${result.name}`,
|
|
740
|
+
autoFix: 'flag',
|
|
741
|
+
content: `${result.name}: complexity ${result.complexity}`,
|
|
742
|
+
phase: 2,
|
|
743
|
+
details: result
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return findings;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Build summary statistics from findings
|
|
755
|
+
*
|
|
756
|
+
* @param {Array} findings - All findings
|
|
757
|
+
* @returns {Object} Summary statistics
|
|
758
|
+
*/
|
|
759
|
+
function buildSummary(findings) {
|
|
760
|
+
const summary = {
|
|
761
|
+
total: findings.length,
|
|
762
|
+
bySeverity: { critical: 0, high: 0, medium: 0, low: 0 },
|
|
763
|
+
byCertainty: { HIGH: 0, MEDIUM: 0, LOW: 0 },
|
|
764
|
+
byPhase: { 1: 0, 2: 0 },
|
|
765
|
+
byAutoFix: { remove: 0, replace: 0, add_logging: 0, flag: 0, none: 0 },
|
|
766
|
+
topPatterns: {}
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
for (const f of findings) {
|
|
770
|
+
summary.bySeverity[f.severity] = (summary.bySeverity[f.severity] || 0) + 1;
|
|
771
|
+
summary.byCertainty[f.certainty] = (summary.byCertainty[f.certainty] || 0) + 1;
|
|
772
|
+
summary.byPhase[f.phase] = (summary.byPhase[f.phase] || 0) + 1;
|
|
773
|
+
summary.byAutoFix[f.autoFix] = (summary.byAutoFix[f.autoFix] || 0) + 1;
|
|
774
|
+
summary.topPatterns[f.patternName] = (summary.topPatterns[f.patternName] || 0) + 1;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return summary;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Format handoff prompt for LLM (Phase 3)
|
|
782
|
+
*
|
|
783
|
+
* Creates a token-efficient prompt for the agent to review findings.
|
|
784
|
+
* Groups by certainty level with action guidance:
|
|
785
|
+
* - HIGH: Apply directly (if apply mode)
|
|
786
|
+
* - MEDIUM: Verify context before applying
|
|
787
|
+
* - LOW: Use judgment, may be false positive
|
|
788
|
+
*
|
|
789
|
+
* @param {Array} findings - All findings
|
|
790
|
+
* @param {string} mode - report | apply
|
|
791
|
+
* @param {Object} options - Formatting options
|
|
792
|
+
* @param {boolean} options.compact - Use compact table format (60-70% fewer tokens)
|
|
793
|
+
* @param {number} options.maxFindings - Maximum findings to include (default: 50)
|
|
794
|
+
* @returns {string} Formatted prompt
|
|
795
|
+
*/
|
|
796
|
+
function formatHandoffPrompt(findings, mode, options = {}) {
|
|
797
|
+
const { compact = false, maxFindings = 50 } = options;
|
|
798
|
+
|
|
799
|
+
if (findings.length === 0) {
|
|
800
|
+
return '## Slop Detection Results\n\nNo issues detected.';
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Use compact format if requested
|
|
804
|
+
if (compact) {
|
|
805
|
+
return formatCompactPrompt(findings, mode, maxFindings);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Group findings by certainty
|
|
809
|
+
const byGroup = {
|
|
810
|
+
HIGH: findings.filter(f => f.certainty === CERTAINTY.HIGH),
|
|
811
|
+
MEDIUM: findings.filter(f => f.certainty === CERTAINTY.MEDIUM),
|
|
812
|
+
LOW: findings.filter(f => f.certainty === CERTAINTY.LOW)
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
let prompt = '## Slop Detection Results\n\n';
|
|
816
|
+
prompt += `Mode: **${mode}** | Total: ${findings.length} findings\n\n`;
|
|
817
|
+
|
|
818
|
+
// HIGH certainty - definitive matches
|
|
819
|
+
if (byGroup.HIGH.length > 0) {
|
|
820
|
+
prompt += '### HIGH Certainty (Definitive - trust these)\n\n';
|
|
821
|
+
if (mode === 'apply') {
|
|
822
|
+
prompt += '_Action: Apply fixes directly for autoFix patterns._\n\n';
|
|
823
|
+
}
|
|
824
|
+
prompt += formatFindingsList(byGroup.HIGH);
|
|
825
|
+
prompt += '\n';
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// MEDIUM certainty - needs context verification
|
|
829
|
+
if (byGroup.MEDIUM.length > 0) {
|
|
830
|
+
prompt += '### MEDIUM Certainty (Verify context)\n\n';
|
|
831
|
+
prompt += '_Action: Review surrounding code before applying._\n\n';
|
|
832
|
+
prompt += formatFindingsList(byGroup.MEDIUM);
|
|
833
|
+
prompt += '\n';
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// LOW certainty - use judgment
|
|
837
|
+
if (byGroup.LOW.length > 0) {
|
|
838
|
+
prompt += '### LOW Certainty (Use judgment)\n\n';
|
|
839
|
+
prompt += '_Action: May be false positives. Investigate before acting._\n\n';
|
|
840
|
+
prompt += formatFindingsList(byGroup.LOW);
|
|
841
|
+
prompt += '\n';
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Action summary
|
|
845
|
+
prompt += '### Action Summary\n\n';
|
|
846
|
+
const autoFixable = findings.filter(f => f.autoFix && f.autoFix !== 'flag' && f.autoFix !== 'none');
|
|
847
|
+
const needsReview = findings.filter(f => f.autoFix === 'flag' || f.autoFix === 'none');
|
|
848
|
+
|
|
849
|
+
prompt += `- Auto-fixable: ${autoFixable.length}\n`;
|
|
850
|
+
prompt += `- Needs manual review: ${needsReview.length}\n`;
|
|
851
|
+
|
|
852
|
+
return prompt;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Format findings in compact table format for token efficiency
|
|
857
|
+
*
|
|
858
|
+
* Reduces token usage by ~60-70% compared to verbose format.
|
|
859
|
+
* Best for large finding sets where full descriptions aren't needed.
|
|
860
|
+
*
|
|
861
|
+
* @param {Array} findings - All findings
|
|
862
|
+
* @param {string} mode - report | apply
|
|
863
|
+
* @param {number} maxFindings - Maximum findings to include
|
|
864
|
+
* @returns {string} Compact formatted prompt
|
|
865
|
+
*/
|
|
866
|
+
function formatCompactPrompt(findings, mode, maxFindings) {
|
|
867
|
+
// Single pass to count certainty levels and auto-fixable findings
|
|
868
|
+
const { highCount, mediumCount, lowCount, autoFixableCount } = findings.reduce((acc, f) => {
|
|
869
|
+
switch (f.certainty) {
|
|
870
|
+
case CERTAINTY.HIGH: acc.highCount++; break;
|
|
871
|
+
case CERTAINTY.MEDIUM: acc.mediumCount++; break;
|
|
872
|
+
case CERTAINTY.LOW: acc.lowCount++; break;
|
|
873
|
+
}
|
|
874
|
+
if (f.autoFix && f.autoFix !== 'flag' && f.autoFix !== 'none') {
|
|
875
|
+
acc.autoFixableCount++;
|
|
876
|
+
}
|
|
877
|
+
return acc;
|
|
878
|
+
}, { highCount: 0, mediumCount: 0, lowCount: 0, autoFixableCount: 0 });
|
|
879
|
+
|
|
880
|
+
// Truncate if needed
|
|
881
|
+
const limited = findings.slice(0, maxFindings);
|
|
882
|
+
const truncated = findings.length > maxFindings;
|
|
883
|
+
|
|
884
|
+
// Summary header
|
|
885
|
+
let output = `## Slop: ${mode}|H:${highCount}|M:${mediumCount}|L:${lowCount}\n\n`;
|
|
886
|
+
|
|
887
|
+
// Table format
|
|
888
|
+
output += '|File|L|Pattern|Cert|Fix|\n';
|
|
889
|
+
output += '|---|---|---|---|---|\n';
|
|
890
|
+
|
|
891
|
+
for (const f of limited) {
|
|
892
|
+
const fix = f.autoFix && f.autoFix !== 'flag' && f.autoFix !== 'none' ? f.autoFix : '-';
|
|
893
|
+
const cert = f.certainty.charAt(0); // H, M, or L
|
|
894
|
+
output += `|${f.file}|${f.line}|${f.patternName}|${cert}|${fix}|\n`;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
if (truncated) {
|
|
898
|
+
output += `\n_+${findings.length - maxFindings} more findings (truncated)_\n`;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Auto-fix summary
|
|
902
|
+
output += `\n**Auto-fixable: ${autoFixableCount}** | Manual: ${findings.length - autoFixableCount}`;
|
|
903
|
+
|
|
904
|
+
return output;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Format a list of findings for the prompt
|
|
909
|
+
*
|
|
910
|
+
* @param {Array} findings - Findings to format
|
|
911
|
+
* @returns {string} Formatted list
|
|
912
|
+
*/
|
|
913
|
+
function formatFindingsList(findings) {
|
|
914
|
+
// Group by file for compact output
|
|
915
|
+
const byFile = {};
|
|
916
|
+
for (const f of findings) {
|
|
917
|
+
if (!byFile[f.file]) byFile[f.file] = [];
|
|
918
|
+
byFile[f.file].push(f);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
let output = '';
|
|
922
|
+
for (const [file, fileFindings] of Object.entries(byFile)) {
|
|
923
|
+
output += `**${file}**\n`;
|
|
924
|
+
for (const f of fileFindings) {
|
|
925
|
+
const fixTag = f.autoFix && f.autoFix !== 'flag' && f.autoFix !== 'none'
|
|
926
|
+
? ` [${f.autoFix}]`
|
|
927
|
+
: '';
|
|
928
|
+
output += `- L${f.line}: ${f.description}${fixTag}\n`;
|
|
929
|
+
}
|
|
930
|
+
output += '\n';
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return output;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
module.exports = {
|
|
937
|
+
runPipeline,
|
|
938
|
+
// Exported for testing
|
|
939
|
+
runPhase1,
|
|
940
|
+
runMultiPassAnalyzers,
|
|
941
|
+
runPhase2,
|
|
942
|
+
buildSummary,
|
|
943
|
+
formatHandoffPrompt,
|
|
944
|
+
formatCompactPrompt,
|
|
945
|
+
// Constants
|
|
946
|
+
CERTAINTY,
|
|
947
|
+
THOROUGHNESS
|
|
948
|
+
};
|