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,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter Transform Functions
|
|
3
|
+
*
|
|
4
|
+
* Shared transforms for converting Claude Code plugin content into
|
|
5
|
+
* OpenCode and Codex adapter formats. Used by:
|
|
6
|
+
* - bin/cli.js (npm installer)
|
|
7
|
+
* - scripts/dev-install.js (development installer)
|
|
8
|
+
* - scripts/gen-adapters.js (static adapter generation)
|
|
9
|
+
*
|
|
10
|
+
* @module adapter-transforms
|
|
11
|
+
* @author Avi Fenesh
|
|
12
|
+
* @license MIT
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const discovery = require('./discovery');
|
|
16
|
+
|
|
17
|
+
function transformBodyForOpenCode(content, repoRoot) {
|
|
18
|
+
content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, '${PLUGIN_ROOT}');
|
|
19
|
+
content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, '$PLUGIN_ROOT');
|
|
20
|
+
|
|
21
|
+
// Replace .claude/ paths with .opencode/ but preserve platform documentation lists
|
|
22
|
+
// that enumerate all three platforms (Claude Code: .claude/, OpenCode: .opencode/, Codex: .codex/)
|
|
23
|
+
// Also preserve {AI_STATE_DIR} references which are platform-agnostic
|
|
24
|
+
content = content.replace(/\.claude\//g, (match, offset) => {
|
|
25
|
+
const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
|
|
26
|
+
// Skip if inside a platform enumeration (e.g., "Claude Code: `.claude/`")
|
|
27
|
+
if (/Claude Code:/.test(context)) return match;
|
|
28
|
+
return '.opencode/';
|
|
29
|
+
});
|
|
30
|
+
content = content.replace(/\.claude'/g, (match, offset) => {
|
|
31
|
+
const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
|
|
32
|
+
if (/Claude Code:/.test(context)) return match;
|
|
33
|
+
return ".opencode'";
|
|
34
|
+
});
|
|
35
|
+
content = content.replace(/\.claude"/g, (match, offset) => {
|
|
36
|
+
const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
|
|
37
|
+
if (/Claude Code:/.test(context)) return match;
|
|
38
|
+
return '.opencode"';
|
|
39
|
+
});
|
|
40
|
+
content = content.replace(/\.claude`/g, (match, offset) => {
|
|
41
|
+
const context = content.substring(Math.max(0, offset - 60), offset + match.length + 10);
|
|
42
|
+
if (/Claude Code:/.test(context)) return match;
|
|
43
|
+
return '.opencode`';
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const plugins = discovery.discoverPlugins(repoRoot);
|
|
47
|
+
if (plugins.length > 0) {
|
|
48
|
+
const pluginNames = plugins.join('|');
|
|
49
|
+
content = content.replace(new RegExp('`(' + pluginNames + '):([a-z-]+)`', 'g'), '`$2`');
|
|
50
|
+
content = content.replace(new RegExp('(' + pluginNames + '):([a-z-]+)', 'g'), '$2');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
content = content.replace(
|
|
54
|
+
/```(\w*)\n([\s\S]*?)```/g,
|
|
55
|
+
(match, lang, code) => {
|
|
56
|
+
const langLower = (lang || '').toLowerCase();
|
|
57
|
+
|
|
58
|
+
if (langLower === 'bash' || langLower === 'shell' || langLower === 'sh') {
|
|
59
|
+
if (code.includes('node -e') && code.includes('require(')) {
|
|
60
|
+
return '*(Bash command with Node.js require - adapt for OpenCode)*';
|
|
61
|
+
}
|
|
62
|
+
return match;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!lang && (code.trim().startsWith('gh ') || code.trim().startsWith('glab ') ||
|
|
66
|
+
code.trim().startsWith('git ') || code.trim().startsWith('#!'))) {
|
|
67
|
+
return match;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (code.includes('require(') || code.includes('Task(') ||
|
|
71
|
+
code.includes('const ') || code.includes('let ') ||
|
|
72
|
+
code.includes('function ') || code.includes('=>') ||
|
|
73
|
+
code.includes('async ') || code.includes('await ')) {
|
|
74
|
+
|
|
75
|
+
let instructions = '';
|
|
76
|
+
|
|
77
|
+
const taskMatches = [...code.matchAll(/(?:await\s+)?Task\s*\(\s*\{[^}]*subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["'][^}]*\}\s*\)/gs)];
|
|
78
|
+
for (const taskMatch of taskMatches) {
|
|
79
|
+
const agent = taskMatch[1];
|
|
80
|
+
instructions += `- Invoke \`@${agent}\` agent\n`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const phaseMatches = code.match(/startPhase\s*\(\s*['"]([^'"]+)['"]\s*\)/g);
|
|
84
|
+
if (phaseMatches) {
|
|
85
|
+
for (const pm of phaseMatches) {
|
|
86
|
+
const phase = pm.match(/['"]([^'"]+)['"]/)[1];
|
|
87
|
+
instructions += `- Phase: ${phase}\n`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (code.includes('AskUserQuestion')) {
|
|
92
|
+
instructions += '- Use AskUserQuestion tool for user input\n';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (code.includes('EnterPlanMode')) {
|
|
96
|
+
instructions += '- Use EnterPlanMode for user approval\n';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (instructions) {
|
|
100
|
+
return instructions;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return '*(JavaScript reference - not executable in OpenCode)*';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return match;
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
content = content.replace(/\*\(Reference - adapt for OpenCode\)\*/g, '');
|
|
111
|
+
|
|
112
|
+
content = content.replace(/await\s+Task\s*\(\s*\{[\s\S]*?\}\s*\);?/g, (match) => {
|
|
113
|
+
const agentMatch = match.match(/subagent_type:\s*["'](?:[^"':]+:)?([^"']+)["']/);
|
|
114
|
+
if (agentMatch) {
|
|
115
|
+
return `Invoke \`@${agentMatch[1]}\` agent`;
|
|
116
|
+
}
|
|
117
|
+
return '*(Task call - use @agent-name syntax)*';
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
content = content.replace(/(?:const|let|var)\s+\{?[^}=\n]+\}?\s*=\s*require\s*\([^)]+\);?/g, '');
|
|
121
|
+
content = content.replace(/require\s*\(['"][^'"]+['"]\)/g, '');
|
|
122
|
+
|
|
123
|
+
if (content.includes('agent')) {
|
|
124
|
+
const note = `
|
|
125
|
+
> **OpenCode Note**: Invoke agents using \`@agent-name\` syntax.
|
|
126
|
+
> Available agents: task-discoverer, exploration-agent, planning-agent,
|
|
127
|
+
> implementation-agent, deslop-agent, delivery-validator, sync-docs-agent, consult-agent
|
|
128
|
+
> Example: \`@exploration-agent analyze the codebase\`
|
|
129
|
+
|
|
130
|
+
`;
|
|
131
|
+
content = content.replace(/^(---\n[\s\S]*?---\n)/, `$1${note}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (content.includes('Master Workflow Orchestrator') && content.includes('No Shortcuts Policy')) {
|
|
135
|
+
const policySection = `
|
|
136
|
+
## Phase 1: Policy Selection (Built-in Options)
|
|
137
|
+
|
|
138
|
+
Ask the user these questions using AskUserQuestion:
|
|
139
|
+
|
|
140
|
+
**Question 1 - Source**: "Where should I look for tasks?"
|
|
141
|
+
- GitHub Issues - Use \`gh issue list\` to find issues
|
|
142
|
+
- GitLab Issues - Use \`glab issue list\` to find issues
|
|
143
|
+
- Local tasks.md - Read from PLAN.md, tasks.md, or TODO.md in the repo
|
|
144
|
+
- Custom - User specifies their own source
|
|
145
|
+
- Other - User describes source, you figure it out
|
|
146
|
+
|
|
147
|
+
**Question 2 - Priority**: "What type of tasks to prioritize?"
|
|
148
|
+
- All - Consider all tasks, pick by score
|
|
149
|
+
- Bugs - Focus on bug fixes
|
|
150
|
+
- Security - Security issues first
|
|
151
|
+
- Features - New feature development
|
|
152
|
+
|
|
153
|
+
**Question 3 - Stop Point**: "How far should I take this task?"
|
|
154
|
+
- Merged - Until PR is merged to main
|
|
155
|
+
- PR Created - Stop after creating PR
|
|
156
|
+
- Implemented - Stop after local implementation
|
|
157
|
+
- Deployed - Deploy to staging
|
|
158
|
+
- Production - Full production deployment
|
|
159
|
+
|
|
160
|
+
After user answers, proceed to Phase 2 with the selected policy.
|
|
161
|
+
|
|
162
|
+
`;
|
|
163
|
+
if (content.includes('OpenCode Note')) {
|
|
164
|
+
content = content.replace(/(Example:.*analyze the codebase\`\n\n)/, `$1${policySection}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return content;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function transformCommandFrontmatterForOpenCode(content) {
|
|
172
|
+
return content.replace(
|
|
173
|
+
/^---\n([\s\S]*?)^---/m,
|
|
174
|
+
(match, frontmatter) => {
|
|
175
|
+
// Parse existing frontmatter
|
|
176
|
+
const lines = frontmatter.trim().split('\n');
|
|
177
|
+
const parsed = {};
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
const colonIdx = line.indexOf(':');
|
|
180
|
+
if (colonIdx > 0) {
|
|
181
|
+
const key = line.substring(0, colonIdx).trim();
|
|
182
|
+
const value = line.substring(colonIdx + 1).trim();
|
|
183
|
+
parsed[key] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Build OpenCode command frontmatter
|
|
188
|
+
let opencodeFrontmatter = '---\n';
|
|
189
|
+
if (parsed.description) opencodeFrontmatter += `description: ${parsed.description}\n`;
|
|
190
|
+
opencodeFrontmatter += 'agent: general\n';
|
|
191
|
+
// Don't include argument-hint or allowed-tools (not supported)
|
|
192
|
+
opencodeFrontmatter += '---';
|
|
193
|
+
return opencodeFrontmatter;
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function transformAgentFrontmatterForOpenCode(content, options) {
|
|
199
|
+
const { stripModels = true } = options || {};
|
|
200
|
+
|
|
201
|
+
return content.replace(
|
|
202
|
+
/^---\n([\s\S]*?)^---/m,
|
|
203
|
+
(match, frontmatter) => {
|
|
204
|
+
// Parse existing frontmatter
|
|
205
|
+
const lines = frontmatter.trim().split('\n');
|
|
206
|
+
const parsed = {};
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
const colonIdx = line.indexOf(':');
|
|
209
|
+
if (colonIdx > 0) {
|
|
210
|
+
const key = line.substring(0, colonIdx).trim();
|
|
211
|
+
const value = line.substring(colonIdx + 1).trim();
|
|
212
|
+
parsed[key] = value;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Build OpenCode frontmatter
|
|
217
|
+
let opencodeFrontmatter = '---\n';
|
|
218
|
+
if (parsed.name) opencodeFrontmatter += `name: ${parsed.name}\n`;
|
|
219
|
+
if (parsed.description) opencodeFrontmatter += `description: ${parsed.description}\n`;
|
|
220
|
+
opencodeFrontmatter += 'mode: subagent\n';
|
|
221
|
+
|
|
222
|
+
// Map model names - only include if NOT stripping
|
|
223
|
+
if (parsed.model && !stripModels) {
|
|
224
|
+
const modelMap = {
|
|
225
|
+
'sonnet': 'anthropic/claude-sonnet-4',
|
|
226
|
+
'opus': 'anthropic/claude-opus-4',
|
|
227
|
+
'haiku': 'anthropic/claude-haiku-3-5'
|
|
228
|
+
};
|
|
229
|
+
opencodeFrontmatter += `model: ${modelMap[parsed.model] || parsed.model}\n`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Convert tools to permissions
|
|
233
|
+
if (parsed.tools) {
|
|
234
|
+
opencodeFrontmatter += 'permission:\n';
|
|
235
|
+
const tools = parsed.tools.toLowerCase();
|
|
236
|
+
opencodeFrontmatter += ` read: ${tools.includes('read') ? 'allow' : 'deny'}\n`;
|
|
237
|
+
opencodeFrontmatter += ` edit: ${tools.includes('edit') || tools.includes('write') ? 'allow' : 'deny'}\n`;
|
|
238
|
+
opencodeFrontmatter += ` bash: ${tools.includes('bash') ? 'allow' : 'ask'}\n`;
|
|
239
|
+
opencodeFrontmatter += ` glob: ${tools.includes('glob') ? 'allow' : 'deny'}\n`;
|
|
240
|
+
opencodeFrontmatter += ` grep: ${tools.includes('grep') ? 'allow' : 'deny'}\n`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
opencodeFrontmatter += '---';
|
|
244
|
+
return opencodeFrontmatter;
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function transformSkillBodyForOpenCode(content, repoRoot) {
|
|
250
|
+
return transformBodyForOpenCode(content, repoRoot);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function transformForCodex(content, options) {
|
|
254
|
+
const { skillName, description, pluginInstallPath } = options;
|
|
255
|
+
|
|
256
|
+
// Escape description for YAML: wrap in double quotes, escape backslashes and internal quotes
|
|
257
|
+
const escapedDescription = description.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
258
|
+
const yamlDescription = `"${escapedDescription}"`;
|
|
259
|
+
|
|
260
|
+
if (content.startsWith('---')) {
|
|
261
|
+
// Replace existing frontmatter with Codex-compatible format
|
|
262
|
+
content = content.replace(
|
|
263
|
+
/^---\n[\s\S]*?\n---\n/,
|
|
264
|
+
`---\nname: ${skillName}\ndescription: ${yamlDescription}\n---\n`
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
267
|
+
// Add new frontmatter
|
|
268
|
+
content = `---\nname: ${skillName}\ndescription: ${yamlDescription}\n---\n\n${content}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Transform PLUGIN_ROOT to actual installed path (or placeholder) for Codex
|
|
272
|
+
content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginInstallPath);
|
|
273
|
+
content = content.replace(/\$CLAUDE_PLUGIN_ROOT/g, pluginInstallPath);
|
|
274
|
+
content = content.replace(/\$\{PLUGIN_ROOT\}/g, pluginInstallPath);
|
|
275
|
+
content = content.replace(/\$PLUGIN_ROOT/g, pluginInstallPath);
|
|
276
|
+
|
|
277
|
+
// Transform AskUserQuestion → request_user_input for Codex native tool
|
|
278
|
+
content = content.replace(/AskUserQuestion/g, 'request_user_input');
|
|
279
|
+
|
|
280
|
+
// Remove multiSelect lines (not supported in Codex)
|
|
281
|
+
content = content.replace(/^[ \t]*multiSelect:.*\n?/gm, '');
|
|
282
|
+
|
|
283
|
+
// Inject Codex note about required id field after request_user_input blocks
|
|
284
|
+
content = content.replace(
|
|
285
|
+
/^([ \t]*request_user_input:\s*)$/gm,
|
|
286
|
+
'$1\n> **Codex**: Each question MUST include a unique `id` field (e.g., `id: "q1"`).'
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return content;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
transformBodyForOpenCode,
|
|
294
|
+
transformCommandFrontmatterForOpenCode,
|
|
295
|
+
transformAgentFrontmatterForOpenCode,
|
|
296
|
+
transformSkillBodyForOpenCode,
|
|
297
|
+
transformForCodex
|
|
298
|
+
};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codebase Collector
|
|
3
|
+
*
|
|
4
|
+
* Scans codebase structure, frameworks, and implemented features.
|
|
5
|
+
* Extracted from drift-detect/collectors.js for shared use.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/collectors/codebase
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const DEFAULT_OPTIONS = {
|
|
16
|
+
depth: 'thorough',
|
|
17
|
+
cwd: process.cwd()
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum file size to analyze (50KB)
|
|
22
|
+
* Larger files are skipped to avoid memory issues
|
|
23
|
+
*/
|
|
24
|
+
const MAX_FILE_SIZE = 50000;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Directories to exclude from analysis
|
|
28
|
+
*/
|
|
29
|
+
const EXCLUDE_DIRS = [
|
|
30
|
+
'node_modules', 'vendor', 'dist', 'build', 'out', 'target',
|
|
31
|
+
'.git', '.svn', '.hg', '__pycache__', '.pytest_cache',
|
|
32
|
+
'coverage', '.nyc_output', '.next', '.nuxt', '.cache'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Source file extensions per language
|
|
37
|
+
*/
|
|
38
|
+
const SOURCE_EXTENSIONS = {
|
|
39
|
+
js: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'],
|
|
40
|
+
rust: ['.rs'],
|
|
41
|
+
go: ['.go'],
|
|
42
|
+
python: ['.py'],
|
|
43
|
+
java: ['.java']
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Safe file read
|
|
48
|
+
*/
|
|
49
|
+
function safeReadFile(filePath, basePath) {
|
|
50
|
+
const fullPath = path.resolve(basePath, filePath);
|
|
51
|
+
const resolvedBase = path.resolve(basePath);
|
|
52
|
+
if (!fullPath.startsWith(resolvedBase)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
return fs.readFileSync(fullPath, 'utf8');
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if path should be excluded
|
|
64
|
+
*/
|
|
65
|
+
function shouldExclude(filePath, excludeDirs = EXCLUDE_DIRS) {
|
|
66
|
+
const parts = filePath.split(/[\\/]/);
|
|
67
|
+
return parts.some(part => excludeDirs.includes(part));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Detect frameworks from package.json
|
|
72
|
+
*/
|
|
73
|
+
function detectFrameworks(result, pkgJson) {
|
|
74
|
+
const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
75
|
+
const frameworkMap = {
|
|
76
|
+
react: 'React',
|
|
77
|
+
'react-dom': 'React',
|
|
78
|
+
next: 'Next.js',
|
|
79
|
+
vue: 'Vue.js',
|
|
80
|
+
nuxt: 'Nuxt',
|
|
81
|
+
angular: 'Angular',
|
|
82
|
+
express: 'Express',
|
|
83
|
+
fastify: 'Fastify',
|
|
84
|
+
koa: 'Koa',
|
|
85
|
+
nestjs: 'NestJS'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (const [pkgName, framework] of Object.entries(frameworkMap)) {
|
|
89
|
+
if (deps[pkgName]) {
|
|
90
|
+
result.frameworks.push(framework);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
result.frameworks = [...new Set(result.frameworks)];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect test framework
|
|
99
|
+
*/
|
|
100
|
+
function detectTestFramework(result, pkgJson) {
|
|
101
|
+
const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
|
|
102
|
+
const testFrameworks = ['jest', 'mocha', 'vitest', 'ava', 'tap', 'jasmine'];
|
|
103
|
+
|
|
104
|
+
for (const framework of testFrameworks) {
|
|
105
|
+
if (deps[framework]) {
|
|
106
|
+
result.testFramework = framework;
|
|
107
|
+
result.health.hasTests = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract symbols (functions, classes, exports) from a JS/TS file
|
|
115
|
+
*/
|
|
116
|
+
function extractSymbols(content) {
|
|
117
|
+
const symbols = {
|
|
118
|
+
functions: [],
|
|
119
|
+
classes: [],
|
|
120
|
+
exports: []
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const funcPattern = /(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
124
|
+
let match;
|
|
125
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
126
|
+
symbols.functions.push(match[1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const arrowPattern = /(?:const|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
|
|
130
|
+
while ((match = arrowPattern.exec(content)) !== null) {
|
|
131
|
+
symbols.functions.push(match[1]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const classPattern = /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
135
|
+
while ((match = classPattern.exec(content)) !== null) {
|
|
136
|
+
symbols.classes.push(match[1]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const namedExportPattern = /export\s+(?:(?:async\s+)?function|class|const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
140
|
+
while ((match = namedExportPattern.exec(content)) !== null) {
|
|
141
|
+
symbols.exports.push(match[1]);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const moduleExportsPattern = /module\.exports\s*=\s*\{([^}]+)\}/;
|
|
145
|
+
const moduleMatch = content.match(moduleExportsPattern);
|
|
146
|
+
if (moduleMatch) {
|
|
147
|
+
const keys = moduleMatch[1].split(',').map(k => k.trim().split(':')[0].trim());
|
|
148
|
+
symbols.exports.push(...keys.filter(k => k && /^[a-zA-Z_$]/.test(k)));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
symbols.functions = [...new Set(symbols.functions)];
|
|
152
|
+
symbols.classes = [...new Set(symbols.classes)];
|
|
153
|
+
symbols.exports = [...new Set(symbols.exports)];
|
|
154
|
+
|
|
155
|
+
return symbols;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Scan key source files for symbols
|
|
160
|
+
*/
|
|
161
|
+
function scanFileSymbols(basePath, topLevelDirs) {
|
|
162
|
+
const sourceSymbols = {};
|
|
163
|
+
const sourceDirs = ['lib', 'src', 'app', 'pages', 'components', 'utils', 'services', 'api'];
|
|
164
|
+
const dirsToScan = topLevelDirs.filter(d => sourceDirs.includes(d));
|
|
165
|
+
const allExts = Object.values(SOURCE_EXTENSIONS).flat();
|
|
166
|
+
|
|
167
|
+
let filesScanned = 0;
|
|
168
|
+
const maxFiles = 40;
|
|
169
|
+
|
|
170
|
+
function scanDir(dirPath, relativePath, depth = 0) {
|
|
171
|
+
if (filesScanned >= maxFiles || depth > 2) return;
|
|
172
|
+
if (!fs.existsSync(dirPath)) return;
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
176
|
+
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
if (filesScanned >= maxFiles) break;
|
|
179
|
+
|
|
180
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
181
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
182
|
+
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
if (['node_modules', '__tests__', 'test', 'tests', 'dist', 'build'].includes(entry.name)) continue;
|
|
185
|
+
scanDir(fullPath, relPath, depth + 1);
|
|
186
|
+
} else if (entry.isFile()) {
|
|
187
|
+
const ext = path.extname(entry.name);
|
|
188
|
+
if (!allExts.includes(ext)) continue;
|
|
189
|
+
if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const stat = fs.statSync(fullPath);
|
|
193
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
194
|
+
|
|
195
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
196
|
+
const symbols = extractSymbols(content);
|
|
197
|
+
|
|
198
|
+
if (symbols.functions.length || symbols.classes.length || symbols.exports.length) {
|
|
199
|
+
sourceSymbols[relPath] = symbols;
|
|
200
|
+
filesScanned++;
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// Skip unreadable files
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
// Skip unreadable dirs
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for (const dir of dirsToScan) {
|
|
213
|
+
if (filesScanned >= maxFiles) break;
|
|
214
|
+
scanDir(path.join(basePath, dir), dir);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return sourceSymbols;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Scan directory structure recursively
|
|
222
|
+
*/
|
|
223
|
+
function scanDirectory(result, basePath, relativePath, maxDepth, depth = 0) {
|
|
224
|
+
if (depth >= maxDepth) return;
|
|
225
|
+
|
|
226
|
+
const fullPath = path.join(basePath, relativePath);
|
|
227
|
+
if (!fs.existsSync(fullPath)) return;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const entries = fs.readdirSync(fullPath, { withFileTypes: true });
|
|
231
|
+
const dirs = [];
|
|
232
|
+
const files = [];
|
|
233
|
+
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
if (entry.isDirectory()) {
|
|
236
|
+
if (!EXCLUDE_DIRS.includes(entry.name)) {
|
|
237
|
+
dirs.push(entry.name);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
files.push(entry.name);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const key = relativePath || '.';
|
|
245
|
+
result.structure[key] = { dirs, fileCount: files.length };
|
|
246
|
+
|
|
247
|
+
for (const file of files) {
|
|
248
|
+
const ext = path.extname(file).toLowerCase() || 'no-ext';
|
|
249
|
+
result.fileStats[ext] = (result.fileStats[ext] || 0) + 1;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const dir of dirs) {
|
|
253
|
+
scanDirectory(result, basePath, path.join(relativePath, dir), maxDepth, depth + 1);
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
// Permission or read errors
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Detect project health indicators
|
|
262
|
+
*/
|
|
263
|
+
function detectHealth(result, basePath) {
|
|
264
|
+
result.health.hasReadme = fs.existsSync(path.join(basePath, 'README.md'));
|
|
265
|
+
|
|
266
|
+
const lintConfigs = ['.eslintrc', '.eslintrc.js', '.eslintrc.json', 'eslint.config.js', 'biome.json'];
|
|
267
|
+
result.health.hasLinting = lintConfigs.some(f => fs.existsSync(path.join(basePath, f)));
|
|
268
|
+
|
|
269
|
+
const ciConfigs = [
|
|
270
|
+
'.github/workflows',
|
|
271
|
+
'.gitlab-ci.yml',
|
|
272
|
+
'.circleci',
|
|
273
|
+
'Jenkinsfile',
|
|
274
|
+
'.travis.yml'
|
|
275
|
+
];
|
|
276
|
+
result.health.hasCi = ciConfigs.some(f => fs.existsSync(path.join(basePath, f)));
|
|
277
|
+
|
|
278
|
+
const testDirs = ['tests', '__tests__', 'test', 'spec'];
|
|
279
|
+
result.health.hasTests = result.health.hasTests || testDirs.some(d => fs.existsSync(path.join(basePath, d)));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Find implemented features from code patterns
|
|
284
|
+
*/
|
|
285
|
+
function findImplementedFeatures(result, basePath) {
|
|
286
|
+
const featurePatterns = {
|
|
287
|
+
authentication: ['auth', 'login', 'session', 'jwt', 'oauth'],
|
|
288
|
+
api: ['routes', 'controllers', 'handlers', 'endpoints'],
|
|
289
|
+
database: ['models', 'schemas', 'migrations', 'seeds'],
|
|
290
|
+
ui: ['components', 'views', 'pages', 'layouts'],
|
|
291
|
+
testing: ['__tests__', 'test', 'spec', '.test.', '.spec.'],
|
|
292
|
+
docs: ['docs', 'documentation', 'wiki']
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
for (const [feature, patterns] of Object.entries(featurePatterns)) {
|
|
296
|
+
const found = patterns.some(pattern => {
|
|
297
|
+
for (const dir of Object.keys(result.structure)) {
|
|
298
|
+
if (dir.toLowerCase().includes(pattern)) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (found) {
|
|
306
|
+
result.implementedFeatures.push(feature);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Scan codebase structure and features
|
|
313
|
+
* @param {Object} options - Collection options
|
|
314
|
+
* @returns {Object} Codebase analysis
|
|
315
|
+
*/
|
|
316
|
+
function scanCodebase(options = {}) {
|
|
317
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
318
|
+
const basePath = opts.cwd;
|
|
319
|
+
|
|
320
|
+
const result = {
|
|
321
|
+
summary: { totalDirs: 0, totalFiles: 0 },
|
|
322
|
+
topLevelDirs: [],
|
|
323
|
+
frameworks: [],
|
|
324
|
+
testFramework: null,
|
|
325
|
+
hasTypeScript: false,
|
|
326
|
+
implementedFeatures: [],
|
|
327
|
+
symbols: {},
|
|
328
|
+
health: {
|
|
329
|
+
hasTests: false,
|
|
330
|
+
hasLinting: false,
|
|
331
|
+
hasCi: false,
|
|
332
|
+
hasReadme: false
|
|
333
|
+
},
|
|
334
|
+
fileStats: {}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const internalStructure = {};
|
|
338
|
+
|
|
339
|
+
// Detect package.json dependencies
|
|
340
|
+
const pkgContent = safeReadFile('package.json', basePath);
|
|
341
|
+
if (pkgContent) {
|
|
342
|
+
try {
|
|
343
|
+
const pkg = JSON.parse(pkgContent);
|
|
344
|
+
detectFrameworks(result, pkg);
|
|
345
|
+
detectTestFramework(result, pkg);
|
|
346
|
+
} catch {
|
|
347
|
+
// Invalid JSON
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
result.hasTypeScript = fs.existsSync(path.join(basePath, 'tsconfig.json'));
|
|
352
|
+
|
|
353
|
+
scanDirectory({ structure: internalStructure, fileStats: result.fileStats }, basePath, '', opts.depth === 'thorough' ? 3 : 2);
|
|
354
|
+
|
|
355
|
+
result.summary.totalDirs = Object.keys(internalStructure).length;
|
|
356
|
+
result.summary.totalFiles = Object.values(internalStructure).reduce((sum, d) => sum + (d.fileCount || 0), 0);
|
|
357
|
+
|
|
358
|
+
const rootEntry = internalStructure['.'];
|
|
359
|
+
if (rootEntry) {
|
|
360
|
+
result.topLevelDirs = rootEntry.dirs || [];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
detectHealth(result, basePath);
|
|
364
|
+
|
|
365
|
+
if (opts.depth === 'thorough') {
|
|
366
|
+
findImplementedFeatures({ ...result, structure: internalStructure }, basePath);
|
|
367
|
+
result.symbols = scanFileSymbols(basePath, result.topLevelDirs);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const sortedStats = Object.entries(result.fileStats)
|
|
371
|
+
.sort((a, b) => b[1] - a[1])
|
|
372
|
+
.slice(0, 10);
|
|
373
|
+
result.fileStats = Object.fromEntries(sortedStats);
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
module.exports = {
|
|
379
|
+
DEFAULT_OPTIONS,
|
|
380
|
+
EXCLUDE_DIRS,
|
|
381
|
+
SOURCE_EXTENSIONS,
|
|
382
|
+
scanCodebase,
|
|
383
|
+
detectFrameworks,
|
|
384
|
+
detectTestFramework,
|
|
385
|
+
detectHealth,
|
|
386
|
+
findImplementedFeatures,
|
|
387
|
+
extractSymbols,
|
|
388
|
+
scanFileSymbols,
|
|
389
|
+
scanDirectory,
|
|
390
|
+
shouldExclude,
|
|
391
|
+
safeReadFile
|
|
392
|
+
};
|