coder-config 0.45.12 → 0.45.14
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/config-loader.js +6 -1
- package/lib/constants.js +1 -1
- package/lib/workstreams.js +234 -32
- package/package.json +1 -1
- package/ui/dist/assets/index-B4hzEzCz.css +32 -0
- package/ui/dist/assets/{index-DFzCZrS_.js → index-DoeTG2Q1.js} +102 -102
- package/ui/dist/index.html +2 -2
- package/ui/routes/workstreams.js +35 -4
- package/ui/server.cjs +10 -1
- package/ui/dist/assets/index-ChBU02w_.css +0 -32
package/config-loader.js
CHANGED
|
@@ -28,7 +28,7 @@ const { init, show } = require('./lib/init');
|
|
|
28
28
|
const { memoryList, memoryInit, memoryAdd, memorySearch } = require('./lib/memory');
|
|
29
29
|
const { envList, envSet, envUnset } = require('./lib/env');
|
|
30
30
|
const { getProjectsRegistryPath, loadProjectsRegistry, saveProjectsRegistry, projectList, projectAdd, projectRemove } = require('./lib/projects');
|
|
31
|
-
const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, generateRulesFromRepos, generateRulesWithClaude } = require('./lib/workstreams');
|
|
31
|
+
const { getWorkstreamsPath, loadWorkstreams, saveWorkstreams, workstreamList, workstreamCreate, workstreamUpdate, workstreamDelete, workstreamUse, workstreamActive, workstreamAddProject, workstreamRemoveProject, workstreamInject, workstreamDetect, workstreamGet, getActiveWorkstream, countWorkstreamsForProject, workstreamInstallHook, workstreamInstallHookGemini, workstreamInstallHookCodex, workstreamDeactivate, workstreamCheckPath, getSettingsPath, loadSettings, saveSettings, workstreamAddTrigger, workstreamRemoveTrigger, workstreamSetAutoActivate, setGlobalAutoActivate, shouldAutoActivate, workstreamCheckFolder, workstreamInstallCdHook, workstreamUninstallCdHook, workstreamCdHookStatus, discoverSubProjects, generateRulesFromRepos, generateRulesWithClaude, generateRulesWithAI, getAvailableAITools, findAIBinary, AI_TOOLS } = require('./lib/workstreams');
|
|
32
32
|
const { getActivityPath, getDefaultActivity, loadActivity, saveActivity, detectProjectRoot, activityLog, activitySummary, generateWorkstreamName, activitySuggestWorkstreams, activityClear } = require('./lib/activity');
|
|
33
33
|
const { getLoopsPath, loadLoops, saveLoops, loadLoopState, saveLoopState, loadHistory, saveHistory, loopList, loopCreate, loopGet, loopUpdate, loopDelete, loopStart, loopPause, loopResume, loopCancel, loopApprove, loopComplete, loopFail, loopStatus, loopHistory, loopConfig, getActiveLoop, recordIteration, saveClarifications, savePlan, loadClarifications, loadPlan, loopInject, archiveLoop } = require('./lib/loops');
|
|
34
34
|
const { getSessionStatus, showSessionStatus, flushContext, clearContext, installHooks: sessionInstallHooks, getFlushedContext, installFlushCommand, installAll: sessionInstallAll, SESSION_DIR, FLUSHED_CONTEXT_FILE } = require('./lib/sessions');
|
|
@@ -208,6 +208,11 @@ class ClaudeConfigManager {
|
|
|
208
208
|
workstreamCdHookStatus() { return workstreamCdHookStatus(); }
|
|
209
209
|
generateRulesFromRepos(projects) { return generateRulesFromRepos(projects); }
|
|
210
210
|
generateRulesWithClaude(projects) { return generateRulesWithClaude(projects); }
|
|
211
|
+
generateRulesWithAI(projects, toolId, options) { return generateRulesWithAI(projects, toolId, options); }
|
|
212
|
+
getAvailableAITools() { return getAvailableAITools(); }
|
|
213
|
+
findAIBinary(toolId) { return findAIBinary(toolId); }
|
|
214
|
+
get AI_TOOLS() { return AI_TOOLS; }
|
|
215
|
+
discoverSubProjects(rootPath, maxDepth) { return discoverSubProjects(rootPath, maxDepth); }
|
|
211
216
|
|
|
212
217
|
// Loops (Ralph Loop)
|
|
213
218
|
getLoopsPath() { return getLoopsPath(this.installDir); }
|
package/lib/constants.js
CHANGED
package/lib/workstreams.js
CHANGED
|
@@ -688,40 +688,153 @@ function workstreamCheckPath(installDir, targetPath, silent = false) {
|
|
|
688
688
|
}
|
|
689
689
|
|
|
690
690
|
/**
|
|
691
|
-
*
|
|
692
|
-
* Runs `claude -p` to analyze repos and generate smart context
|
|
691
|
+
* Supported AI tools for context generation
|
|
693
692
|
*/
|
|
694
|
-
|
|
693
|
+
const AI_TOOLS = {
|
|
694
|
+
claude: {
|
|
695
|
+
name: 'Claude',
|
|
696
|
+
binary: 'claude',
|
|
697
|
+
buildArgs: (prompt) => ['-p', prompt],
|
|
698
|
+
candidates: (os) => [
|
|
699
|
+
path.join(os.homedir(), '.local', 'bin', 'claude'),
|
|
700
|
+
'/usr/local/bin/claude',
|
|
701
|
+
'/opt/homebrew/bin/claude',
|
|
702
|
+
path.join(os.homedir(), '.npm-global', 'bin', 'claude'),
|
|
703
|
+
],
|
|
704
|
+
},
|
|
705
|
+
gemini: {
|
|
706
|
+
name: 'Gemini',
|
|
707
|
+
binary: 'gemini',
|
|
708
|
+
buildArgs: (prompt) => ['-p', prompt],
|
|
709
|
+
candidates: (os) => [
|
|
710
|
+
path.join(os.homedir(), '.local', 'bin', 'gemini'),
|
|
711
|
+
'/usr/local/bin/gemini',
|
|
712
|
+
'/opt/homebrew/bin/gemini',
|
|
713
|
+
path.join(os.homedir(), '.npm-global', 'bin', 'gemini'),
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
codex: {
|
|
717
|
+
name: 'Codex',
|
|
718
|
+
binary: 'codex',
|
|
719
|
+
buildArgs: (prompt) => ['exec', prompt],
|
|
720
|
+
candidates: (os) => [
|
|
721
|
+
path.join(os.homedir(), '.local', 'bin', 'codex'),
|
|
722
|
+
'/usr/local/bin/codex',
|
|
723
|
+
'/opt/homebrew/bin/codex',
|
|
724
|
+
path.join(os.homedir(), '.npm-global', 'bin', 'codex'),
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
ollama: {
|
|
728
|
+
name: 'Ollama',
|
|
729
|
+
binary: 'ollama',
|
|
730
|
+
// Model must be specified in options
|
|
731
|
+
buildArgs: (prompt, options) => ['run', options.model || 'llama3.2', prompt],
|
|
732
|
+
candidates: (os) => [
|
|
733
|
+
path.join(os.homedir(), '.local', 'bin', 'ollama'),
|
|
734
|
+
'/usr/local/bin/ollama',
|
|
735
|
+
'/opt/homebrew/bin/ollama',
|
|
736
|
+
],
|
|
737
|
+
},
|
|
738
|
+
aider: {
|
|
739
|
+
name: 'Aider',
|
|
740
|
+
binary: 'aider',
|
|
741
|
+
buildArgs: (prompt) => ['--message', prompt, '--yes', '--no-git'],
|
|
742
|
+
candidates: (os) => [
|
|
743
|
+
path.join(os.homedir(), '.local', 'bin', 'aider'),
|
|
744
|
+
'/usr/local/bin/aider',
|
|
745
|
+
'/opt/homebrew/bin/aider',
|
|
746
|
+
path.join(os.homedir(), '.local', 'pipx', 'venvs', 'aider-chat', 'bin', 'aider'),
|
|
747
|
+
],
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Find the binary path for an AI tool
|
|
753
|
+
*/
|
|
754
|
+
function findAIBinary(toolId) {
|
|
755
|
+
const { execFileSync } = require('child_process');
|
|
756
|
+
const os = require('os');
|
|
757
|
+
|
|
758
|
+
const tool = AI_TOOLS[toolId];
|
|
759
|
+
if (!tool) {
|
|
760
|
+
throw new Error(`Unknown AI tool: ${toolId}`);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Check candidate paths
|
|
764
|
+
for (const p of tool.candidates(os)) {
|
|
765
|
+
if (fs.existsSync(p)) return p;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Try which command
|
|
769
|
+
try {
|
|
770
|
+
const resolved = execFileSync('which', [tool.binary], { encoding: 'utf8' }).trim();
|
|
771
|
+
if (resolved && fs.existsSync(resolved)) return resolved;
|
|
772
|
+
} catch (e) {}
|
|
773
|
+
|
|
774
|
+
// Fall back to bare binary name (let shell resolve it)
|
|
775
|
+
return tool.binary;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Get list of available AI tools (ones that are installed)
|
|
780
|
+
*/
|
|
781
|
+
function getAvailableAITools() {
|
|
782
|
+
const available = [];
|
|
783
|
+
for (const [id, tool] of Object.entries(AI_TOOLS)) {
|
|
784
|
+
try {
|
|
785
|
+
const binaryPath = findAIBinary(id);
|
|
786
|
+
if (fs.existsSync(binaryPath)) {
|
|
787
|
+
available.push({ id, name: tool.name, path: binaryPath });
|
|
788
|
+
}
|
|
789
|
+
} catch (e) {
|
|
790
|
+
// Tool not available
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return available;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Generate rules/context from project repositories using an AI tool
|
|
798
|
+
* Supports: claude, gemini, codex, ollama, aider
|
|
799
|
+
* @param {string[]} projects - Array of project paths
|
|
800
|
+
* @param {string} toolId - AI tool to use (default: 'claude')
|
|
801
|
+
* @param {object} options - Tool-specific options (e.g., { model: 'llama3.2' } for ollama)
|
|
802
|
+
*/
|
|
803
|
+
async function generateRulesWithAI(projects, toolId = 'claude', options = {}) {
|
|
695
804
|
if (!projects || projects.length === 0) {
|
|
696
805
|
return '';
|
|
697
806
|
}
|
|
698
807
|
|
|
699
808
|
const { execFileSync } = require('child_process');
|
|
700
|
-
const os = require('os');
|
|
701
809
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
810
|
+
const tool = AI_TOOLS[toolId];
|
|
811
|
+
if (!tool) {
|
|
812
|
+
console.error(`Unknown AI tool: ${toolId}. Available: ${Object.keys(AI_TOOLS).join(', ')}`);
|
|
813
|
+
return generateRulesFromRepos(projects);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Expand projects to include discovered sub-projects
|
|
817
|
+
const allProjects = [];
|
|
818
|
+
const seen = new Set();
|
|
819
|
+
|
|
820
|
+
for (const projectPath of projects) {
|
|
821
|
+
const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
|
|
822
|
+
if (!seen.has(absPath)) {
|
|
823
|
+
seen.add(absPath);
|
|
824
|
+
allProjects.push(absPath);
|
|
712
825
|
}
|
|
713
|
-
try {
|
|
714
|
-
const resolved = execFileSync('which', ['claude'], { encoding: 'utf8' }).trim();
|
|
715
|
-
if (resolved && fs.existsSync(resolved)) return resolved;
|
|
716
|
-
} catch (e) {}
|
|
717
|
-
return 'claude';
|
|
718
|
-
};
|
|
719
826
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
827
|
+
// Discover sub-projects
|
|
828
|
+
const subProjects = discoverSubProjects(absPath);
|
|
829
|
+
for (const subPath of subProjects) {
|
|
830
|
+
if (!seen.has(subPath)) {
|
|
831
|
+
seen.add(subPath);
|
|
832
|
+
allProjects.push(subPath);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
723
836
|
|
|
724
|
-
const projectList =
|
|
837
|
+
const projectList = allProjects.map(p => `- ${p}`).join('\n');
|
|
725
838
|
|
|
726
839
|
const prompt = `Analyze these project repositories and generate concise workstream context rules for an AI coding assistant. Focus on:
|
|
727
840
|
1. What each project does (brief description)
|
|
@@ -735,40 +848,124 @@ ${projectList}
|
|
|
735
848
|
Output markdown suitable for injecting into an AI assistant's context. Keep it concise (under 500 words). Do not include code blocks or examples - just descriptions and guidelines.`;
|
|
736
849
|
|
|
737
850
|
try {
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
851
|
+
const binaryPath = findAIBinary(toolId);
|
|
852
|
+
const args = tool.buildArgs(prompt, options);
|
|
853
|
+
|
|
854
|
+
console.log(`Generating context with ${tool.name}...`);
|
|
855
|
+
|
|
856
|
+
const result = execFileSync(binaryPath, args, {
|
|
857
|
+
cwd: allProjects[0],
|
|
742
858
|
encoding: 'utf8',
|
|
743
|
-
timeout:
|
|
859
|
+
timeout: 120000, // 2 minute timeout (some models are slower)
|
|
744
860
|
maxBuffer: 1024 * 1024, // 1MB buffer
|
|
745
861
|
});
|
|
746
862
|
|
|
747
863
|
return result.trim();
|
|
748
864
|
} catch (error) {
|
|
749
|
-
console.error(
|
|
865
|
+
console.error(`${tool.name} generation failed:`, error.message);
|
|
750
866
|
// Fall back to simple generation
|
|
751
867
|
return generateRulesFromRepos(projects);
|
|
752
868
|
}
|
|
753
869
|
}
|
|
754
870
|
|
|
871
|
+
/**
|
|
872
|
+
* Generate rules/context from project repositories using Claude Code
|
|
873
|
+
* @deprecated Use generateRulesWithAI(projects, 'claude') instead
|
|
874
|
+
*/
|
|
875
|
+
async function generateRulesWithClaude(projects) {
|
|
876
|
+
return generateRulesWithAI(projects, 'claude');
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Discover sub-projects within a directory
|
|
881
|
+
* Looks for directories containing project markers (package.json, pyproject.toml, etc.)
|
|
882
|
+
* Returns array of absolute paths to discovered sub-projects
|
|
883
|
+
*/
|
|
884
|
+
function discoverSubProjects(rootPath, maxDepth = 2) {
|
|
885
|
+
const subProjects = [];
|
|
886
|
+
const skipDirs = new Set([
|
|
887
|
+
'node_modules', '.git', '__pycache__', '.venv', 'venv', 'env',
|
|
888
|
+
'dist', 'build', '.next', '.nuxt', 'target', 'vendor', '.tox',
|
|
889
|
+
'coverage', '.pytest_cache', '.mypy_cache', '.ruff_cache'
|
|
890
|
+
]);
|
|
891
|
+
const projectMarkers = [
|
|
892
|
+
'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod',
|
|
893
|
+
'CLAUDE.md', 'setup.py', 'pom.xml', 'build.gradle'
|
|
894
|
+
];
|
|
895
|
+
|
|
896
|
+
function scan(dir, depth) {
|
|
897
|
+
if (depth > maxDepth) return;
|
|
898
|
+
|
|
899
|
+
let entries;
|
|
900
|
+
try {
|
|
901
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
902
|
+
} catch (e) {
|
|
903
|
+
return; // Can't read directory
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
for (const entry of entries) {
|
|
907
|
+
if (!entry.isDirectory()) continue;
|
|
908
|
+
if (skipDirs.has(entry.name)) continue;
|
|
909
|
+
if (entry.name.startsWith('.')) continue;
|
|
910
|
+
|
|
911
|
+
const subPath = path.join(dir, entry.name);
|
|
912
|
+
|
|
913
|
+
// Check if this subdirectory is a project
|
|
914
|
+
const hasMarker = projectMarkers.some(marker =>
|
|
915
|
+
fs.existsSync(path.join(subPath, marker))
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
if (hasMarker) {
|
|
919
|
+
subProjects.push(subPath);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Continue scanning deeper
|
|
923
|
+
scan(subPath, depth + 1);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
scan(rootPath, 0);
|
|
928
|
+
return subProjects;
|
|
929
|
+
}
|
|
930
|
+
|
|
755
931
|
/**
|
|
756
932
|
* Generate rules/context from project repositories
|
|
757
933
|
* Reads README.md, package.json, CLAUDE.md, etc. to create a summary
|
|
934
|
+
* Automatically discovers sub-projects within each project directory
|
|
758
935
|
*/
|
|
759
936
|
function generateRulesFromRepos(projects) {
|
|
760
937
|
if (!projects || projects.length === 0) {
|
|
761
938
|
return '';
|
|
762
939
|
}
|
|
763
940
|
|
|
941
|
+
// Expand projects to include discovered sub-projects
|
|
942
|
+
const allProjects = [];
|
|
943
|
+
const seen = new Set();
|
|
944
|
+
|
|
945
|
+
for (const projectPath of projects) {
|
|
946
|
+
const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
|
|
947
|
+
if (!seen.has(absPath)) {
|
|
948
|
+
seen.add(absPath);
|
|
949
|
+
allProjects.push(absPath);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// Discover sub-projects
|
|
953
|
+
const subProjects = discoverSubProjects(absPath);
|
|
954
|
+
for (const subPath of subProjects) {
|
|
955
|
+
if (!seen.has(subPath)) {
|
|
956
|
+
seen.add(subPath);
|
|
957
|
+
allProjects.push(subPath);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
764
962
|
const lines = [];
|
|
765
963
|
lines.push('# Workstream Context');
|
|
766
964
|
lines.push('');
|
|
767
965
|
lines.push('## Repositories');
|
|
768
966
|
lines.push('');
|
|
769
967
|
|
|
770
|
-
for (const
|
|
771
|
-
const absPath = path.resolve(projectPath.replace(/^~/, process.env.HOME || ''));
|
|
968
|
+
for (const absPath of allProjects) {
|
|
772
969
|
const name = path.basename(absPath);
|
|
773
970
|
|
|
774
971
|
lines.push(`### ${name}`);
|
|
@@ -1367,8 +1564,13 @@ module.exports = {
|
|
|
1367
1564
|
workstreamInstallHookCodex,
|
|
1368
1565
|
workstreamDeactivate,
|
|
1369
1566
|
workstreamCheckPath,
|
|
1567
|
+
discoverSubProjects,
|
|
1370
1568
|
generateRulesFromRepos,
|
|
1371
1569
|
generateRulesWithClaude,
|
|
1570
|
+
generateRulesWithAI,
|
|
1571
|
+
getAvailableAITools,
|
|
1572
|
+
findAIBinary,
|
|
1573
|
+
AI_TOOLS,
|
|
1372
1574
|
// New folder auto-activation functions
|
|
1373
1575
|
getSettingsPath,
|
|
1374
1576
|
loadSettings,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.14",
|
|
4
4
|
"description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
|
|
5
5
|
"author": "regression.io",
|
|
6
6
|
"main": "config-loader.js",
|