golem-cc 2.1.2 → 3.0.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/commands/golem/build.md +18 -0
- package/.claude/commands/golem/config.md +39 -0
- package/.claude/commands/golem/continue.md +73 -0
- package/.claude/commands/golem/doctor.md +46 -0
- package/.claude/commands/golem/document.md +138 -0
- package/.claude/commands/golem/help.md +58 -0
- package/.claude/commands/golem/pause.md +130 -0
- package/.claude/commands/golem/plan.md +111 -0
- package/.claude/commands/golem/review.md +166 -0
- package/.claude/commands/golem/security.md +186 -0
- package/.claude/commands/golem/simplify.md +76 -0
- package/.claude/commands/golem/spec.md +105 -0
- package/.claude/commands/golem/status.md +33 -0
- package/.golem/agents/code-simplifier.md +54 -0
- package/.golem/agents/review-architecture.md +59 -0
- package/.golem/agents/review-logic.md +50 -0
- package/.golem/agents/review-security.md +50 -0
- package/.golem/agents/review-style.md +48 -0
- package/.golem/agents/review-tests.md +48 -0
- package/.golem/agents/spec-builder.md +60 -0
- package/.golem/bin/golem.mjs +270 -0
- package/.golem/lib/build.mjs +557 -0
- package/.golem/lib/claude.mjs +95 -0
- package/.golem/lib/config.mjs +421 -0
- package/.golem/lib/display.mjs +191 -0
- package/.golem/lib/doctor.mjs +197 -0
- package/.golem/lib/document.mjs +792 -0
- package/.golem/lib/gates.mjs +78 -0
- package/.golem/lib/init.mjs +166 -0
- package/.golem/lib/output.mjs +40 -0
- package/.golem/lib/ratelimit.mjs +86 -0
- package/.golem/lib/security.mjs +603 -0
- package/.golem/lib/simplify.mjs +101 -0
- package/.golem/lib/tui.mjs +368 -0
- package/.golem/lib/usage.mjs +119 -0
- package/.golem/lib/worktree.mjs +509 -0
- package/.golem/prompts/build.md +23 -0
- package/.golem/prompts/document-inline.md +66 -0
- package/.golem/prompts/document-markdown.md +80 -0
- package/.golem/prompts/simplify.md +35 -0
- package/README.md +141 -142
- package/bin/golem-shim.mjs +36 -0
- package/bin/install.mjs +193 -0
- package/package.json +27 -32
- package/.env.example +0 -17
- package/bin/golem +0 -1040
- package/commands/golem/build.md +0 -235
- package/commands/golem/config.md +0 -55
- package/commands/golem/doctor.md +0 -137
- package/commands/golem/help.md +0 -212
- package/commands/golem/plan.md +0 -214
- package/commands/golem/review.md +0 -376
- package/commands/golem/security.md +0 -204
- package/commands/golem/simplify.md +0 -94
- package/commands/golem/spec.md +0 -226
- package/commands/golem/status.md +0 -60
- package/dist/api/freshworks.d.ts +0 -61
- package/dist/api/freshworks.d.ts.map +0 -1
- package/dist/api/freshworks.js +0 -119
- package/dist/api/freshworks.js.map +0 -1
- package/dist/api/gitea.d.ts +0 -96
- package/dist/api/gitea.d.ts.map +0 -1
- package/dist/api/gitea.js +0 -154
- package/dist/api/gitea.js.map +0 -1
- package/dist/cli/index.d.ts +0 -9
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -352
- package/dist/cli/index.js.map +0 -1
- package/dist/sync/ticket-sync.d.ts +0 -53
- package/dist/sync/ticket-sync.d.ts.map +0 -1
- package/dist/sync/ticket-sync.js +0 -226
- package/dist/sync/ticket-sync.js.map +0 -1
- package/dist/types.d.ts +0 -125
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -5
- package/dist/types.js.map +0 -1
- package/dist/worktree/manager.d.ts +0 -54
- package/dist/worktree/manager.d.ts.map +0 -1
- package/dist/worktree/manager.js +0 -190
- package/dist/worktree/manager.js.map +0 -1
- package/golem/agents/code-simplifier.md +0 -81
- package/golem/agents/spec-builder.md +0 -90
- package/golem/prompts/PROMPT_build.md +0 -71
- package/golem/prompts/PROMPT_plan.md +0 -102
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const TIMEOUT_MS = 120_000;
|
|
4
|
+
const MAX_OUTPUT_LINES = 200;
|
|
5
|
+
|
|
6
|
+
function truncateOutput(text) {
|
|
7
|
+
const lines = text.split('\n');
|
|
8
|
+
if (lines.length <= MAX_OUTPUT_LINES) return text;
|
|
9
|
+
return lines.slice(0, MAX_OUTPUT_LINES).join('\n') + `\n... (truncated ${lines.length - MAX_OUTPUT_LINES} lines)`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runCommand(command, timeoutMs) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
let stdout = '';
|
|
16
|
+
let stderr = '';
|
|
17
|
+
let killed = false;
|
|
18
|
+
|
|
19
|
+
const child = spawn('sh', ['-c', command], { stdio: 'pipe' });
|
|
20
|
+
|
|
21
|
+
const timer = setTimeout(() => {
|
|
22
|
+
killed = true;
|
|
23
|
+
child.kill('SIGKILL');
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
|
|
26
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
27
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
28
|
+
|
|
29
|
+
child.on('close', (code) => {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
const duration = Date.now() - start;
|
|
32
|
+
const output = truncateOutput((stdout + stderr).trimEnd());
|
|
33
|
+
|
|
34
|
+
if (killed) {
|
|
35
|
+
resolve({ exitCode: 1, output: output + `\n[TIMEOUT] Gate killed after ${timeoutMs / 1000}s`, duration });
|
|
36
|
+
} else {
|
|
37
|
+
resolve({ exitCode: code ?? 1, output, duration });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.on('error', (err) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
resolve({ exitCode: 1, output: err.message, duration: Date.now() - start });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function runGates(config, { timeoutMs = TIMEOUT_MS } = {}) {
|
|
49
|
+
const gates = config.gates;
|
|
50
|
+
if (!gates || gates.length === 0) {
|
|
51
|
+
return { passed: true, results: [], failedGate: null };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const results = [];
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < gates.length; i++) {
|
|
57
|
+
const gate = gates[i];
|
|
58
|
+
if (!gate.enabled) {
|
|
59
|
+
results.push({ name: gate.name, passed: true, exitCode: 0, output: '', duration: 0, skipped: true });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { exitCode, output, duration } = await runCommand(gate.command, timeoutMs);
|
|
64
|
+
const passed = exitCode === 0;
|
|
65
|
+
results.push({ name: gate.name, passed, exitCode, output, duration });
|
|
66
|
+
|
|
67
|
+
if (!passed) {
|
|
68
|
+
for (let j = i + 1; j < gates.length; j++) {
|
|
69
|
+
if (gates[j].enabled) {
|
|
70
|
+
results.push({ name: gates[j].name, passed: false, exitCode: -1, output: '', duration: 0, skipped: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { passed: false, results, failedGate: gate.name };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { passed: true, results, failedGate: null };
|
|
78
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { readFile, writeFile, access, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { header, success, warn, info } from './output.mjs';
|
|
5
|
+
import { saveConfig, loadConfig, detectFramework } from './config.mjs';
|
|
6
|
+
|
|
7
|
+
async function fileExists(path) {
|
|
8
|
+
try { await access(path); return true; } catch { return false; }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readPkg(dir) {
|
|
12
|
+
try { return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8')); }
|
|
13
|
+
catch { return null; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function detectTestCommand(dir) {
|
|
17
|
+
const pkg = readPkg(dir);
|
|
18
|
+
const testScript = pkg?.scripts?.test;
|
|
19
|
+
if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') return testScript;
|
|
20
|
+
if (existsSync(join(dir, 'vitest.config.ts')) || existsSync(join(dir, 'vitest.config.js'))) return 'npx vitest run';
|
|
21
|
+
if (existsSync(join(dir, 'jest.config.ts')) || existsSync(join(dir, 'jest.config.js')) || existsSync(join(dir, 'jest.config.mjs'))) return 'npx jest';
|
|
22
|
+
return 'node --test';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function detectBuildCommand(dir, framework) {
|
|
26
|
+
if (framework === 'nuxt') return 'npx nuxt build';
|
|
27
|
+
if (framework === 'next') return 'npx next build';
|
|
28
|
+
if (readPkg(dir)?.scripts?.build) return 'npm run build';
|
|
29
|
+
return '# No build step — ESM modules run directly';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function detectLintCommand(dir) {
|
|
33
|
+
if (readPkg(dir)?.scripts?.lint) return 'npm run lint';
|
|
34
|
+
for (const f of ['eslint.config.js', 'eslint.config.mjs', '.eslintrc.json', '.eslintrc.js', '.eslintrc.yml']) {
|
|
35
|
+
if (existsSync(join(dir, f))) return 'npx eslint .';
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(join(dir, 'biome.json'))) return 'npx biome check .';
|
|
38
|
+
return '# TBD — will be set up during implementation';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function detectTypeCheck(dir) {
|
|
42
|
+
if (existsSync(join(dir, 'tsconfig.json'))) return 'npx tsc --noEmit';
|
|
43
|
+
return '# N/A — plain JavaScript, no TypeScript';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function generateAgentsMd(dir, framework) {
|
|
47
|
+
const testCmd = detectTestCommand(dir);
|
|
48
|
+
const buildCmd = detectBuildCommand(dir, framework);
|
|
49
|
+
const lintCmd = detectLintCommand(dir);
|
|
50
|
+
const typeCheck = detectTypeCheck(dir);
|
|
51
|
+
|
|
52
|
+
const pkg = readPkg(dir);
|
|
53
|
+
const learnings = [];
|
|
54
|
+
if (framework) learnings.push(`Project uses ${framework} framework`);
|
|
55
|
+
if (pkg?.type === 'module') learnings.push('Project uses ESM (type: module in package.json)');
|
|
56
|
+
if (!framework && pkg?.type !== 'module') learnings.push('Standard Node.js project');
|
|
57
|
+
|
|
58
|
+
return `# Operational Commands
|
|
59
|
+
|
|
60
|
+
## Testing
|
|
61
|
+
|
|
62
|
+
\`\`\`bash
|
|
63
|
+
${testCmd}
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
## Type Checking
|
|
67
|
+
|
|
68
|
+
\`\`\`bash
|
|
69
|
+
${typeCheck}
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
## Linting
|
|
73
|
+
|
|
74
|
+
\`\`\`bash
|
|
75
|
+
${lintCmd}
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Build
|
|
79
|
+
|
|
80
|
+
\`\`\`bash
|
|
81
|
+
${buildCmd}
|
|
82
|
+
\`\`\`
|
|
83
|
+
|
|
84
|
+
## Learnings
|
|
85
|
+
|
|
86
|
+
${learnings.map(l => `- ${l}`).join('\n')}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function ensureGitignore(dir) {
|
|
91
|
+
const giPath = join(dir, '.gitignore');
|
|
92
|
+
const entries = ['.env', '.claude/*', '!.claude/commands/', '!.claude/commands/**'];
|
|
93
|
+
let content = '';
|
|
94
|
+
|
|
95
|
+
if (await fileExists(giPath)) {
|
|
96
|
+
content = await readFile(giPath, 'utf-8');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const lines = content.split('\n');
|
|
100
|
+
const missing = entries.filter(e => !lines.some(l => l.trim() === e));
|
|
101
|
+
|
|
102
|
+
if (missing.length === 0) return false;
|
|
103
|
+
|
|
104
|
+
const addition = '\n# Golem\n' + missing.join('\n') + '\n';
|
|
105
|
+
await writeFile(giPath, content.trimEnd() + addition);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function runInit() {
|
|
110
|
+
const cwd = process.cwd();
|
|
111
|
+
|
|
112
|
+
header('Golem Init');
|
|
113
|
+
info('Analyzing project...');
|
|
114
|
+
|
|
115
|
+
// Detect framework
|
|
116
|
+
const framework = await detectFramework();
|
|
117
|
+
if (framework) {
|
|
118
|
+
success(`Detected framework: ${framework}`);
|
|
119
|
+
} else {
|
|
120
|
+
info('No framework detected (plain Node.js project)');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Detect tools
|
|
124
|
+
const testCmd = detectTestCommand(cwd);
|
|
125
|
+
const buildCmd = detectBuildCommand(cwd, framework);
|
|
126
|
+
const lintCmd = detectLintCommand(cwd);
|
|
127
|
+
const typeCheck = detectTypeCheck(cwd);
|
|
128
|
+
|
|
129
|
+
info(`Test: ${testCmd}`);
|
|
130
|
+
info(`Build: ${buildCmd}`);
|
|
131
|
+
info(`Lint: ${lintCmd}`);
|
|
132
|
+
info(`Type check: ${typeCheck}`);
|
|
133
|
+
|
|
134
|
+
// Ensure .golem/ directories exist
|
|
135
|
+
for (const dir of ['specs', 'prompts', 'agents', 'lib', 'bin']) {
|
|
136
|
+
await mkdir(join(cwd, '.golem', dir), { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
success('Ensured .golem/ directory structure');
|
|
139
|
+
|
|
140
|
+
// Generate AGENTS.md
|
|
141
|
+
const agentsPath = join(cwd, '.golem', 'AGENTS.md');
|
|
142
|
+
if (await fileExists(agentsPath)) {
|
|
143
|
+
warn('AGENTS.md already exists — overwriting with detected settings');
|
|
144
|
+
}
|
|
145
|
+
await writeFile(agentsPath, generateAgentsMd(cwd, framework));
|
|
146
|
+
success('Generated AGENTS.md');
|
|
147
|
+
|
|
148
|
+
// Create/update config.json
|
|
149
|
+
const config = await loadConfig();
|
|
150
|
+
if (framework) config.framework = framework;
|
|
151
|
+
await saveConfig(config);
|
|
152
|
+
success('Updated .golem/config.json');
|
|
153
|
+
|
|
154
|
+
// Ensure .gitignore entries
|
|
155
|
+
if (await ensureGitignore(cwd)) {
|
|
156
|
+
success('Updated .gitignore');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
success('Init complete! Next steps:');
|
|
161
|
+
info(' 1. Run /golem:spec in Claude Code to build your specs');
|
|
162
|
+
info(' 2. Run /golem:plan to generate an implementation plan');
|
|
163
|
+
info(' 3. Run npx golem build to start building');
|
|
164
|
+
|
|
165
|
+
return { framework, testCmd, buildCmd, lintCmd, typeCheck };
|
|
166
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
|
|
5
|
+
export function header(text) {
|
|
6
|
+
console.log();
|
|
7
|
+
console.log(chalk.bold.cyan(`◆ ${text}`));
|
|
8
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function success(text) {
|
|
12
|
+
console.log(chalk.green(`✓ ${text}`));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function fail(text) {
|
|
16
|
+
console.log(chalk.red(`✗ ${text}`));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function warn(text) {
|
|
20
|
+
console.log(chalk.yellow(`⚠ ${text}`));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function info(text) {
|
|
24
|
+
console.log(chalk.blue(`ℹ ${text}`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function spinner(text) {
|
|
28
|
+
return ora({ text, color: 'cyan' }).start();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function table(headers, rows) {
|
|
32
|
+
const t = new Table({
|
|
33
|
+
head: headers.map(h => chalk.cyan(h)),
|
|
34
|
+
style: { head: [], border: ['dim'] },
|
|
35
|
+
});
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
t.push(row);
|
|
38
|
+
}
|
|
39
|
+
console.log(t.toString());
|
|
40
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
4
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
|
|
6
|
+
let cache = { data: null, fetchedAt: 0 };
|
|
7
|
+
|
|
8
|
+
function getOAuthToken() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = execSync(
|
|
11
|
+
'security find-generic-password -s "Claude Code-credentials" -w',
|
|
12
|
+
{ stdio: 'pipe', encoding: 'utf-8' },
|
|
13
|
+
).trim();
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
// Nested format: { claudeAiOauth: { accessToken } } or flat: { accessToken }
|
|
16
|
+
return parsed.claudeAiOauth?.accessToken || parsed.accessToken || null;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function fetchUsage() {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
if (cache.data && (now - cache.fetchedAt) < CACHE_TTL_MS) {
|
|
25
|
+
return cache.data;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const token = getOAuthToken();
|
|
29
|
+
if (!token) return null;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(USAGE_URL, {
|
|
33
|
+
headers: {
|
|
34
|
+
'Authorization': `Bearer ${token}`,
|
|
35
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) return null;
|
|
39
|
+
|
|
40
|
+
const body = await res.json();
|
|
41
|
+
|
|
42
|
+
function parseBucket(bucket) {
|
|
43
|
+
if (!bucket) return null;
|
|
44
|
+
return {
|
|
45
|
+
utilization: bucket.utilization ?? 0,
|
|
46
|
+
resetsAt: bucket.resets_at ?? null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const extra = body.extra_usage;
|
|
51
|
+
|
|
52
|
+
const data = {
|
|
53
|
+
fiveHour: parseBucket(body.five_hour) || { utilization: 0, resetsAt: null },
|
|
54
|
+
sevenDay: parseBucket(body.seven_day) || { utilization: 0, resetsAt: null },
|
|
55
|
+
opus: parseBucket(body.seven_day_opus),
|
|
56
|
+
sonnet: parseBucket(body.seven_day_sonnet),
|
|
57
|
+
extraUsage: extra ? {
|
|
58
|
+
enabled: extra.is_enabled,
|
|
59
|
+
limit: extra.monthly_limit / 100,
|
|
60
|
+
used: extra.used_credits / 100,
|
|
61
|
+
utilization: extra.utilization,
|
|
62
|
+
} : null,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
cache = { data, fetchedAt: now };
|
|
66
|
+
return data;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function clearUsageCache() {
|
|
73
|
+
cache = { data: null, fetchedAt: 0 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function formatResetTime(isoString) {
|
|
77
|
+
if (!isoString) return 'unknown';
|
|
78
|
+
const reset = new Date(isoString);
|
|
79
|
+
const diff = reset - Date.now();
|
|
80
|
+
if (diff <= 0) return 'now';
|
|
81
|
+
const mins = Math.ceil(diff / 60_000);
|
|
82
|
+
if (mins < 60) return `${mins}m`;
|
|
83
|
+
const h = Math.floor(mins / 60);
|
|
84
|
+
const m = mins % 60;
|
|
85
|
+
return `${h}h ${m}m`;
|
|
86
|
+
}
|