codehere 0.6.1 → 0.7.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/README.md +5 -3
- package/dist/application/use-cases/domain-trust.js +110 -0
- package/dist/application/use-cases/generate-final-report.js +84 -5
- package/dist/application/use-cases/generate-narration.js +20 -3
- package/dist/application/use-cases/generate-thread-title.js +4 -0
- package/dist/application/use-cases/thread-export.js +155 -0
- package/dist/application/use-cases/verify-claim.js +274 -0
- package/dist/application/use-cases/write-pass.js +277 -0
- package/dist/index.js +218 -19
- package/dist/infrastructure/completion/bash-completion.js +2 -2
- package/dist/infrastructure/config/config-manager.js +1 -1
- package/dist/infrastructure/context/adapters/drawio-source.js +157 -0
- package/dist/infrastructure/context/adapters/figma-source.js +163 -0
- package/dist/infrastructure/context/adapters/github-issue-source.js +159 -0
- package/dist/infrastructure/context/adapters/linear-source.js +190 -0
- package/dist/infrastructure/context/context-resolver.js +144 -0
- package/dist/infrastructure/context/context-source.js +114 -0
- package/dist/infrastructure/context/oauth-flow.js +207 -0
- package/dist/infrastructure/context/oauth-store.js +119 -0
- package/dist/infrastructure/context/registry-bootstrap.js +99 -0
- package/dist/infrastructure/context/write-pass-resolver.js +106 -0
- package/dist/infrastructure/cost/budget-config.js +1 -1
- package/dist/infrastructure/cost/spend-aggregator.js +5 -5
- package/dist/infrastructure/hooks/claude-sdk-hook.js +79 -59
- package/dist/infrastructure/hooks/session-analyzer.js +65 -8
- package/dist/infrastructure/observability/execution-tracer.js +0 -5
- package/dist/infrastructure/orchestration/aider-adapter.js +45 -17
- package/dist/infrastructure/orchestration/artifacts-extractor.js +39 -5
- package/dist/infrastructure/orchestration/cassette-recorder.js +120 -0
- package/dist/infrastructure/orchestration/claude-code-adapter.js +33 -4
- package/dist/infrastructure/orchestration/codex-adapter.js +157 -19
- package/dist/infrastructure/orchestration/context-handoff.js +4 -3
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +99 -0
- package/dist/infrastructure/orchestration/control/control-surface.js +15 -0
- package/dist/infrastructure/orchestration/gemini-adapter.js +15 -17
- package/dist/infrastructure/orchestration/graphify-context.js +6 -13
- package/dist/infrastructure/orchestration/memory-injector.js +11 -16
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +233 -0
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +73 -0
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +83 -0
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +37 -0
- package/dist/infrastructure/orchestration/opencode-adapter.js +23 -3
- package/dist/infrastructure/orchestration/progress-aggregator.js +178 -5
- package/dist/infrastructure/orchestration/session-manager.js +142 -29
- package/dist/infrastructure/orchestration/session-persistence.js +139 -30
- package/dist/infrastructure/orchestration/thread-key-store.js +127 -0
- package/dist/infrastructure/orchestration/write-pass-store.js +46 -0
- package/dist/infrastructure/protocols/cloud-sync.js +10 -11
- package/dist/infrastructure/protocols/mcp-server-impl.js +1295 -17
- package/dist/infrastructure/protocols/thread-phase.js +105 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +3768 -75
- package/dist/infrastructure/protocols/ui-page.js +12 -3299
- package/dist/infrastructure/protocols/verify-page.js +941 -0
- package/dist/infrastructure/security/trust-scorer.js +76 -1
- package/dist/infrastructure/storage/cross-tool-agents.js +20 -3
- package/dist/infrastructure/storage/project-thread-migration.js +14 -9
- package/dist/infrastructure/storage/project-thread-reader.js +11 -1
- package/dist/infrastructure/storage/project-thread-title-backfill.js +7 -3
- package/dist/monitoring.js +1 -1
- package/dist/presentation/cli/commands/audit-command.js +126 -0
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/config-command.js +2 -2
- package/dist/presentation/cli/commands/context-source-command.js +198 -0
- package/dist/presentation/cli/commands/help-command.js +51 -1
- package/dist/presentation/cli/commands/init-command.js +5 -4
- package/dist/presentation/cli/commands/memory-command.js +29 -15
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/repo-map-builder.js +4 -4
- package/dist/presentation/cli/commands/report-command.js +6 -4
- package/dist/presentation/cli/commands/setup-command.js +71 -18
- package/dist/presentation/cli/commands/status-command.js +0 -4
- package/dist/presentation/cli/commands/thread-command.js +288 -0
- package/dist/presentation/cli/commands/verify-command.js +233 -0
- package/dist/presentation/cli/error-display.js +1 -1
- package/dist/presentation/cli/repl.js +52 -2
- package/dist/presentation/cli/task-deliverable.js +127 -0
- package/dist/testing/cassette.js +125 -0
- package/dist/testing/replay-adapter.js +166 -0
- package/dist/ui.js +54 -29
- package/dist/utils/typo-guard.js +1 -1
- package/package.json +4 -2
|
@@ -91,7 +91,57 @@ const COMMAND_INFO = {
|
|
|
91
91
|
{ flag: '--json', description: 'Output as JSON' },
|
|
92
92
|
{ flag: '--export <fmt>', description: 'Export raw audit feed (jsonl, json, csv, md)' },
|
|
93
93
|
],
|
|
94
|
-
related: ['hook', 'trust'],
|
|
94
|
+
related: ['hook', 'trust', 'verify'],
|
|
95
|
+
},
|
|
96
|
+
verify: {
|
|
97
|
+
name: 'verify',
|
|
98
|
+
description: "Verify a session's Final Report against the recorded reality (catches optimistic claims)",
|
|
99
|
+
usage: 'codehere verify [session-id] [options]',
|
|
100
|
+
examples: ['codehere verify', 'codehere verify a1b2c3d4', 'codehere verify a1b2c3d4 --json'],
|
|
101
|
+
options: [{ flag: '--json', description: 'Machine-readable verdict for CI' }],
|
|
102
|
+
related: ['status', 'report', 'audit'],
|
|
103
|
+
},
|
|
104
|
+
audit: {
|
|
105
|
+
name: 'audit',
|
|
106
|
+
description: 'Inspect the hash-chained audit log · verify integrity or show one entry',
|
|
107
|
+
usage: 'codehere audit [verify|show <seq>] [options]',
|
|
108
|
+
examples: ['codehere audit', 'codehere audit verify', 'codehere audit verify --json', 'codehere audit show 42'],
|
|
109
|
+
options: [{ flag: '--json', description: 'Machine-readable output' }],
|
|
110
|
+
related: ['status', 'verify', 'hook'],
|
|
111
|
+
},
|
|
112
|
+
'context-source': {
|
|
113
|
+
name: 'context-source',
|
|
114
|
+
description: 'Connect external tools (Linear, Figma) via OAuth · list status · disconnect',
|
|
115
|
+
usage: 'codehere context-source [list|connect <provider>|disconnect <provider>]',
|
|
116
|
+
examples: [
|
|
117
|
+
'codehere context-source list',
|
|
118
|
+
'codehere context-source connect linear',
|
|
119
|
+
'codehere context-source connect figma --no-open',
|
|
120
|
+
'codehere context-source disconnect linear',
|
|
121
|
+
],
|
|
122
|
+
options: [
|
|
123
|
+
{ flag: '--json', description: 'Machine-readable output' },
|
|
124
|
+
{ flag: '--no-open', description: 'For connect: print URL but skip auto-opening the browser' },
|
|
125
|
+
],
|
|
126
|
+
related: ['serve', 'start'],
|
|
127
|
+
},
|
|
128
|
+
thread: {
|
|
129
|
+
name: 'thread',
|
|
130
|
+
description: 'Share or import a signed session bundle · local-first, no relay',
|
|
131
|
+
usage: 'codehere thread <export|import|keys|trust|untrust> [args] [options]',
|
|
132
|
+
examples: [
|
|
133
|
+
'codehere thread export <session-id> --out my-task.codehere.json',
|
|
134
|
+
'codehere thread import my-task.codehere.json',
|
|
135
|
+
'codehere thread import my-task.codehere.json --trust',
|
|
136
|
+
'codehere thread keys',
|
|
137
|
+
'codehere thread trust <pubkey-b64> "alice@laptop"',
|
|
138
|
+
],
|
|
139
|
+
options: [
|
|
140
|
+
{ flag: '--out <file>', description: 'For export: write to file (otherwise stdout)' },
|
|
141
|
+
{ flag: '--trust', description: 'For import: trust the signer key then proceed' },
|
|
142
|
+
{ flag: '--json', description: 'Machine-readable output' },
|
|
143
|
+
],
|
|
144
|
+
related: ['start', 'verify'],
|
|
95
145
|
},
|
|
96
146
|
serve: {
|
|
97
147
|
name: 'serve',
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* Refuses to follow symlinks (lstat check) so a hostile/misplaced symlink at
|
|
40
40
|
* `.codehere/CONTEXT.md` or `.gitignore` cannot redirect writes outside the repo.
|
|
41
41
|
*/
|
|
42
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, lstatSync, statSync, symlinkSync, readlinkSync, rmSync } from 'fs';
|
|
42
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, appendFileSync, lstatSync, statSync, symlinkSync, readlinkSync, rmSync, } from 'fs';
|
|
43
43
|
import { homedir } from 'os';
|
|
44
44
|
import { join, dirname } from 'path';
|
|
45
45
|
import { colors, success, info, warning, error, newline } from '../../../ui.js';
|
|
@@ -316,7 +316,7 @@ function findSymlinkAncestor(codehereDir, relativePath) {
|
|
|
316
316
|
// Defensive: reject any relative path containing .. — even though templates
|
|
317
317
|
// are hard-coded, this protects against future schema additions slipping a
|
|
318
318
|
// traversal through.
|
|
319
|
-
if (segments.some(seg => seg === '..' || seg === '')) {
|
|
319
|
+
if (segments.some((seg) => seg === '..' || seg === '')) {
|
|
320
320
|
return relativePath;
|
|
321
321
|
}
|
|
322
322
|
// Walk codehereDir, codehereDir/seg1, codehereDir/seg1/seg2, ... (excluding
|
|
@@ -338,12 +338,13 @@ function ensureGitignore(repoRoot) {
|
|
|
338
338
|
return { result: 'refused-symlink', addedLines: 0 };
|
|
339
339
|
const existing = existsSync(gitignorePath) ? readFileSync(gitignorePath, 'utf8') : '';
|
|
340
340
|
// De-duplicate: only append lines that aren't already present.
|
|
341
|
-
const missing = GITIGNORE_LINES.filter(line => !existing.includes(line));
|
|
341
|
+
const missing = GITIGNORE_LINES.filter((line) => !existing.includes(line));
|
|
342
342
|
if (missing.length === 0)
|
|
343
343
|
return { result: 'already-present', addedLines: 0 };
|
|
344
344
|
const block = (existing.length > 0 && !existing.endsWith('\n') ? '\n' : '') +
|
|
345
345
|
`\n${GITIGNORE_MARKER}\n` +
|
|
346
|
-
missing.join('\n') +
|
|
346
|
+
missing.join('\n') +
|
|
347
|
+
'\n';
|
|
347
348
|
appendFileSync(gitignorePath, block);
|
|
348
349
|
return { result: 'updated', addedLines: missing.length };
|
|
349
350
|
}
|
|
@@ -48,7 +48,7 @@ import { buildMemoryPrefix } from '../../../infrastructure/orchestration/memory-
|
|
|
48
48
|
* Built lazily from init-command.ts's F3_TEMPLATES so the seed bodies stay
|
|
49
49
|
* in one place — no duplication, no drift.
|
|
50
50
|
*/
|
|
51
|
-
const DEFAULT_SEED_BODIES = new Map(F3_TEMPLATES.map(t => [t.filename, t.body]));
|
|
51
|
+
const DEFAULT_SEED_BODIES = new Map(F3_TEMPLATES.map((t) => [t.filename, t.body]));
|
|
52
52
|
function lessonIdForText(text) {
|
|
53
53
|
return createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
54
54
|
}
|
|
@@ -58,8 +58,8 @@ function readLessonsFile(path) {
|
|
|
58
58
|
return readFileSync(path, 'utf8')
|
|
59
59
|
.trim()
|
|
60
60
|
.split('\n')
|
|
61
|
-
.filter(l => l.length > 0)
|
|
62
|
-
.map(l => {
|
|
61
|
+
.filter((l) => l.length > 0)
|
|
62
|
+
.map((l) => {
|
|
63
63
|
try {
|
|
64
64
|
return JSON.parse(l);
|
|
65
65
|
}
|
|
@@ -83,7 +83,7 @@ function readLessonsFile(path) {
|
|
|
83
83
|
function appendLessonIfNew(path, text, source) {
|
|
84
84
|
const id = lessonIdForText(text);
|
|
85
85
|
const existing = readLessonsFile(path);
|
|
86
|
-
const dup = existing.find(l => l.id === id);
|
|
86
|
+
const dup = existing.find((l) => l.id === id);
|
|
87
87
|
if (dup)
|
|
88
88
|
return { result: 'duplicate', lesson: dup };
|
|
89
89
|
const lesson = {
|
|
@@ -326,7 +326,9 @@ function gatherLayerSummary(codehereDir, projectRoot) {
|
|
|
326
326
|
const episodicLearnings = join(codehereDir, 'memory/episodic/AGENT_LEARNINGS.jsonl');
|
|
327
327
|
const repoMapPath = join(codehereDir, 'memory/episodic/repo_map.json');
|
|
328
328
|
const learningsLine = existsSync(episodicLearnings) || isSymlink(episodicLearnings)
|
|
329
|
-
?
|
|
329
|
+
? isSymlink(episodicLearnings)
|
|
330
|
+
? `AGENT_LEARNINGS.jsonl → ${readlinkSync(episodicLearnings)}`
|
|
331
|
+
: 'AGENT_LEARNINGS.jsonl (real file)'
|
|
330
332
|
: '(no AGENT_LEARNINGS.jsonl)';
|
|
331
333
|
const repoMapLine = existsSync(repoMapPath)
|
|
332
334
|
? `repo_map.json (${fmtSize(statSync(repoMapPath).size)})`
|
|
@@ -354,9 +356,12 @@ function gatherLayerSummary(codehereDir, projectRoot) {
|
|
|
354
356
|
const skillsDir = join(codehereDir, 'skills');
|
|
355
357
|
if (existsSync(skillsDir)) {
|
|
356
358
|
const subdirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
357
|
-
.filter(e => e.isDirectory())
|
|
358
|
-
.filter(e => existsSync(join(skillsDir, e.name, 'SKILL.md')));
|
|
359
|
-
out.push({
|
|
359
|
+
.filter((e) => e.isDirectory())
|
|
360
|
+
.filter((e) => existsSync(join(skillsDir, e.name, 'SKILL.md')));
|
|
361
|
+
out.push({
|
|
362
|
+
layer: 'skills',
|
|
363
|
+
line: `skills · ${subdirs.length} skill folder${subdirs.length === 1 ? '' : 's'}`,
|
|
364
|
+
});
|
|
360
365
|
}
|
|
361
366
|
else {
|
|
362
367
|
out.push({ layer: 'skills', line: `skills · (no skills/ dir)` });
|
|
@@ -368,7 +373,10 @@ function gatherLayerSummary(codehereDir, projectRoot) {
|
|
|
368
373
|
const parsed = JSON.parse(readFileSync(patternsPath, 'utf8'));
|
|
369
374
|
const builtin = Array.isArray(parsed.patterns_builtin) ? parsed.patterns_builtin.length : 0;
|
|
370
375
|
const project = Array.isArray(parsed.patterns_project) ? parsed.patterns_project.length : 0;
|
|
371
|
-
out.push({
|
|
376
|
+
out.push({
|
|
377
|
+
layer: 'protocols',
|
|
378
|
+
line: `protocols · hook_patterns.json (${builtin} built-in + ${project} project)`,
|
|
379
|
+
});
|
|
372
380
|
}
|
|
373
381
|
catch {
|
|
374
382
|
out.push({ layer: 'protocols', line: `protocols · hook_patterns.json (parse error)` });
|
|
@@ -389,7 +397,10 @@ function gatherLayerSummary(codehereDir, projectRoot) {
|
|
|
389
397
|
}
|
|
390
398
|
else {
|
|
391
399
|
const truncNote = inj.truncated ? ' TRUNCATED' : '';
|
|
392
|
-
out.push({
|
|
400
|
+
out.push({
|
|
401
|
+
layer: 'injection',
|
|
402
|
+
line: `injection · ${inj.tokens} tokens · ${inj.included.join(', ')}${truncNote}`,
|
|
403
|
+
});
|
|
393
404
|
}
|
|
394
405
|
return out;
|
|
395
406
|
}
|
|
@@ -526,7 +537,10 @@ function executeMemoryShow(layerArg) {
|
|
|
526
537
|
console.log(` ${info('empty ')} no lessons.jsonl yet`);
|
|
527
538
|
break;
|
|
528
539
|
}
|
|
529
|
-
const lines = readFileSync(path, 'utf8')
|
|
540
|
+
const lines = readFileSync(path, 'utf8')
|
|
541
|
+
.trim()
|
|
542
|
+
.split('\n')
|
|
543
|
+
.filter((l) => l.length > 0);
|
|
530
544
|
if (lines.length === 0) {
|
|
531
545
|
console.log(` ${info('empty ')} lessons.jsonl is empty — run ${colors.cyan('codehere memory graduate')} (coming in v0.5)`);
|
|
532
546
|
break;
|
|
@@ -561,8 +575,8 @@ function executeMemoryShow(layerArg) {
|
|
|
561
575
|
break;
|
|
562
576
|
}
|
|
563
577
|
const subdirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
564
|
-
.filter(e => e.isDirectory())
|
|
565
|
-
.filter(e => existsSync(join(skillsDir, e.name, 'SKILL.md')));
|
|
578
|
+
.filter((e) => e.isDirectory())
|
|
579
|
+
.filter((e) => existsSync(join(skillsDir, e.name, 'SKILL.md')));
|
|
566
580
|
if (subdirs.length === 0) {
|
|
567
581
|
console.log(` ${info('empty ')} no skill folders yet (skills/<name>/SKILL.md)`);
|
|
568
582
|
break;
|
|
@@ -907,8 +921,8 @@ function executeMemoryInit(options) {
|
|
|
907
921
|
}
|
|
908
922
|
// Idempotency hint when nothing was actually migrated.
|
|
909
923
|
const allOutcomes = [contextOutcome, handoverOutcome, promptsResult.outcome];
|
|
910
|
-
const anyMigrated = allOutcomes.some(o => o === 'migrated');
|
|
911
|
-
const allAlreadyOrMissing = allOutcomes.every(o => o === 'already-migrated' || o === 'missing');
|
|
924
|
+
const anyMigrated = allOutcomes.some((o) => o === 'migrated');
|
|
925
|
+
const allAlreadyOrMissing = allOutcomes.every((o) => o === 'already-migrated' || o === 'missing');
|
|
912
926
|
if (!anyMigrated && allAlreadyOrMissing) {
|
|
913
927
|
newline();
|
|
914
928
|
console.log(` ${info('no-op')} everything already migrated or no source files to migrate.`);
|
|
@@ -122,8 +122,8 @@ export function buildHookPatternsExport() {
|
|
|
122
122
|
source: 'agent/src/infrastructure/security/trust-scorer.ts',
|
|
123
123
|
_note: "Exported from codehere's trust-scorer pattern bank via 'codehere protocols export'. " +
|
|
124
124
|
"DO NOT hand-edit `patterns_builtin[]` — it's regenerated on every export. " +
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
'Add project-specific patterns to `patterns_project[]` — those are preserved across re-exports. ' +
|
|
126
|
+
'Pattern ids are stable slugs of category + description; reference them deterministically.',
|
|
127
127
|
patterns_builtin: builtin,
|
|
128
128
|
patterns_project: [],
|
|
129
129
|
};
|
|
@@ -107,8 +107,8 @@ function gitListFiles(repoRoot) {
|
|
|
107
107
|
return null;
|
|
108
108
|
return r.stdout
|
|
109
109
|
.split('\0')
|
|
110
|
-
.filter(p => p.length > 0)
|
|
111
|
-
.filter(p => SUPPORTED_EXTENSIONS.has(extname(p)));
|
|
110
|
+
.filter((p) => p.length > 0)
|
|
111
|
+
.filter((p) => SUPPORTED_EXTENSIONS.has(extname(p)));
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
114
|
* Count references to a symbol name across the repo via git grep.
|
|
@@ -191,13 +191,13 @@ export function buildRepoMap(repoRoot) {
|
|
|
191
191
|
// acceptable for v0.5 — codehere's own 249-symbol repo runs in ~5s.
|
|
192
192
|
// v0.6's tree-sitter port will swap raw grep for AST-aware reference
|
|
193
193
|
// resolution + PageRank, which is faster AND more accurate.
|
|
194
|
-
const uniqueNames = Array.from(new Set(allSymbols.map(s => s.name)));
|
|
194
|
+
const uniqueNames = Array.from(new Set(allSymbols.map((s) => s.name)));
|
|
195
195
|
const refCounts = new Map();
|
|
196
196
|
for (const name of uniqueNames) {
|
|
197
197
|
refCounts.set(name, countReferences(repoRoot, name));
|
|
198
198
|
}
|
|
199
199
|
const topSymbols = allSymbols
|
|
200
|
-
.map(s => ({ ...s, references: refCounts.get(s.name) ?? 0 }))
|
|
200
|
+
.map((s) => ({ ...s, references: refCounts.get(s.name) ?? 0 }))
|
|
201
201
|
// Stable sort: references DESC, then name ASC for tie-breaking.
|
|
202
202
|
.sort((a, b) => b.references - a.references || a.name.localeCompare(b.name))
|
|
203
203
|
.slice(0, 50);
|
|
@@ -49,7 +49,7 @@ function badgeUrl(score, grade) {
|
|
|
49
49
|
return `https://img.shields.io/badge/codehere%20trust-${score}%25%20${grade}-${color}?logo=shield&logoColor=white`;
|
|
50
50
|
}
|
|
51
51
|
function narrativeSummary(report) {
|
|
52
|
-
const { overallScore,
|
|
52
|
+
const { overallScore, criticalCount, highCount, mediumCount, files } = report;
|
|
53
53
|
if (criticalCount === 0 && highCount === 0) {
|
|
54
54
|
if (overallScore >= 90) {
|
|
55
55
|
return (`Clean codebase. ${files.length} file${files.length !== 1 ? 's' : ''} scanned, nothing blocking a merge. ` +
|
|
@@ -66,7 +66,7 @@ function narrativeSummary(report) {
|
|
|
66
66
|
return `No critical issues, but ${highCount} high-severity pattern${highCount !== 1 ? 's' : ''} worth addressing before merge.`;
|
|
67
67
|
}
|
|
68
68
|
function recommendationsMarkdown(report) {
|
|
69
|
-
const { criticalCount, highCount, mediumCount, lowCount
|
|
69
|
+
const { criticalCount, highCount, mediumCount, lowCount } = report;
|
|
70
70
|
const lines = [];
|
|
71
71
|
if (criticalCount > 0) {
|
|
72
72
|
lines.push(`1. **Resolve all ${criticalCount} critical issue${criticalCount !== 1 ? 's' : ''} before merging.** These represent active security risks (credentials in code, eval injection, SQL without parameterization, etc.). They're usually quick fixes.`);
|
|
@@ -85,7 +85,7 @@ function recommendationsMarkdown(report) {
|
|
|
85
85
|
}
|
|
86
86
|
return lines.join('\n\n');
|
|
87
87
|
}
|
|
88
|
-
function findingsMarkdown(files
|
|
88
|
+
function findingsMarkdown(files) {
|
|
89
89
|
const sections = [];
|
|
90
90
|
for (const severity of ['critical', 'high', 'medium', 'low']) {
|
|
91
91
|
const allFindings = [];
|
|
@@ -133,6 +133,8 @@ ${contextLine}
|
|
|
133
133
|
|
|
134
134
|
## Summary
|
|
135
135
|
|
|
136
|
+
${summary}
|
|
137
|
+
|
|
136
138
|
| | |
|
|
137
139
|
|---|---|
|
|
138
140
|
| Files scanned | ${files.length} |
|
|
@@ -145,7 +147,7 @@ ${contextLine}
|
|
|
145
147
|
|
|
146
148
|
## Findings
|
|
147
149
|
|
|
148
|
-
${findingsMarkdown(files
|
|
150
|
+
${findingsMarkdown(files)}
|
|
149
151
|
|
|
150
152
|
---
|
|
151
153
|
|
|
@@ -6,9 +6,47 @@
|
|
|
6
6
|
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { getConfigManager } from '../../../infrastructure/config/config-manager.js';
|
|
9
|
-
import { colors, separator, newline, sectionHeader, success, error, warning, info
|
|
9
|
+
import { colors, separator, newline, sectionHeader, success, error, warning, info } from '../../../ui.js';
|
|
10
10
|
import { executionTracer } from '../../../infrastructure/observability/execution-tracer.js';
|
|
11
11
|
import { findCodebaseRoot } from '../../../domain/services/codebase-detector.js';
|
|
12
|
+
import { readinessForAllAgents } from '../../../infrastructure/orchestration/session-manager.js';
|
|
13
|
+
/**
|
|
14
|
+
* What codehere actually needs to run a task: at least one coding-agent CLI
|
|
15
|
+
* that's installed AND signed in. Most engineers have `claude` or `codex`
|
|
16
|
+
* logged in by subscription with no API key set, so framing setup around
|
|
17
|
+
* `ANTHROPIC_API_KEY` told them they were blocked when they weren't. This
|
|
18
|
+
* reuses the same readiness check that powers the web dashboard's squad bar.
|
|
19
|
+
*/
|
|
20
|
+
function summarizeAgents() {
|
|
21
|
+
let all;
|
|
22
|
+
try {
|
|
23
|
+
all = readinessForAllAgents();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
all = [];
|
|
27
|
+
}
|
|
28
|
+
return { ready: all.filter((a) => a.state === 'ready'), all };
|
|
29
|
+
}
|
|
30
|
+
/** One status line per agent, plain English. Used by both the TTY wizard and
|
|
31
|
+
* the non-TTY summary so they say the same thing. */
|
|
32
|
+
function printAgentLines(all) {
|
|
33
|
+
for (const a of all) {
|
|
34
|
+
if (a.state === 'ready') {
|
|
35
|
+
console.log(success(`${a.label}: ready`));
|
|
36
|
+
}
|
|
37
|
+
else if (a.state === 'needs_auth') {
|
|
38
|
+
const hint = a.authHint?.command ? colors.dim(` (sign in: ${a.authHint.command})`) : '';
|
|
39
|
+
console.log(warning(`${a.label}: installed, needs sign-in`) + hint);
|
|
40
|
+
}
|
|
41
|
+
else if (a.state === 'billed_key_required') {
|
|
42
|
+
console.log(colors.dim(`○ ${a.label}: needs a billed API key (paid)`));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const hint = a.installHint?.command ? colors.dim(` (install: ${a.installHint.command})`) : '';
|
|
46
|
+
console.log(colors.dim(`○ ${a.label}: not installed`) + hint);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
12
50
|
/**
|
|
13
51
|
* Check if any API key is configured
|
|
14
52
|
*/
|
|
@@ -113,19 +151,26 @@ export async function executeSetupCommand(force = false) {
|
|
|
113
151
|
// Check for interactive terminal
|
|
114
152
|
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
115
153
|
if (!isTTY) {
|
|
116
|
-
|
|
154
|
+
const { ready, all } = summarizeAgents();
|
|
155
|
+
console.log(colors.bold('Setup'));
|
|
117
156
|
console.log();
|
|
118
|
-
|
|
157
|
+
if (all.length)
|
|
158
|
+
printAgentLines(all);
|
|
119
159
|
console.log();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
160
|
+
if (ready.length) {
|
|
161
|
+
console.log(success(`You're ready. ${ready[0].label} is set up.`));
|
|
162
|
+
console.log(colors.dim(' Next: codehere start (open the dashboard)'));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(warning('No coding agent is set up yet. Install or sign in to one:'));
|
|
166
|
+
console.log(colors.cyan(' npm i -g @anthropic-ai/claude-code') + colors.dim(' then run: claude (sign in)'));
|
|
167
|
+
console.log(colors.dim(' Already use one by API key? export ANTHROPIC_API_KEY=sk-ant-...'));
|
|
168
|
+
}
|
|
124
169
|
return;
|
|
125
170
|
}
|
|
126
171
|
// Start execution tracing
|
|
127
172
|
const codebaseInfo = findCodebaseRoot(process.cwd());
|
|
128
|
-
|
|
173
|
+
executionTracer.startRun('setup', {
|
|
129
174
|
codebaseId: codebaseInfo?.rootPath || process.cwd(),
|
|
130
175
|
});
|
|
131
176
|
try {
|
|
@@ -147,21 +192,29 @@ export async function executeSetupCommand(force = false) {
|
|
|
147
192
|
codebaseDetected: false,
|
|
148
193
|
configPath: join(process.cwd(), 'data'),
|
|
149
194
|
};
|
|
150
|
-
// Step 1: API
|
|
151
|
-
sectionHeader('Step 1:
|
|
195
|
+
// Step 1: Coding agents — codehere drives agent CLIs, not raw API keys.
|
|
196
|
+
sectionHeader('Step 1: Your coding agents');
|
|
197
|
+
const { ready, all } = summarizeAgents();
|
|
198
|
+
if (all.length)
|
|
199
|
+
printAgentLines(all);
|
|
200
|
+
newline();
|
|
152
201
|
const apiKeyCheck = checkAPIKey();
|
|
153
|
-
if (
|
|
154
|
-
console.log(success(`
|
|
155
|
-
|
|
202
|
+
if (ready.length) {
|
|
203
|
+
console.log(success(`You're ready. ${ready[0].label} is set up.`));
|
|
204
|
+
result.apiKeyConfigured = true; // at least one agent can spawn right now
|
|
205
|
+
}
|
|
206
|
+
else if (apiKeyCheck.configured) {
|
|
207
|
+
// No agent CLI ready, but a key is present — some adapters (e.g. Aider) run off it.
|
|
208
|
+
console.log(success(`${apiKeyCheck.provider} API key found.`));
|
|
209
|
+
console.log(colors.dim(' No agent CLI detected yet — install one for the full experience.'));
|
|
156
210
|
result.apiKeyConfigured = true;
|
|
157
211
|
}
|
|
158
212
|
else {
|
|
159
|
-
console.log(warning('
|
|
213
|
+
console.log(warning('No coding agent is set up yet.'));
|
|
160
214
|
newline();
|
|
161
|
-
console.log(colors.bold('
|
|
162
|
-
console.log(colors.cyan('
|
|
163
|
-
console.log(colors.
|
|
164
|
-
console.log(colors.cyan(' export COHERE_API_KEY=...'));
|
|
215
|
+
console.log(colors.bold('Install or sign in to at least one:'));
|
|
216
|
+
console.log(colors.cyan(' npm i -g @anthropic-ai/claude-code') + colors.dim(' then run: claude (sign in)'));
|
|
217
|
+
console.log(colors.dim(' Already use one by API key? export ANTHROPIC_API_KEY=sk-ant-...'));
|
|
165
218
|
newline();
|
|
166
219
|
}
|
|
167
220
|
newline();
|
|
@@ -185,10 +185,6 @@ function extractFilePath(entry) {
|
|
|
185
185
|
}
|
|
186
186
|
return null;
|
|
187
187
|
}
|
|
188
|
-
function shortReason(entry, maxLen = 60) {
|
|
189
|
-
const r = extractBlockReason(entry);
|
|
190
|
-
return r.length <= maxLen ? r : r.substring(0, maxLen - 1) + '…';
|
|
191
|
-
}
|
|
192
188
|
const BLOCK_CATEGORY_LABEL = {
|
|
193
189
|
'self-test': "codehere's own canary tests",
|
|
194
190
|
'cache-cleanup': 'routine cache/tmp deletion',
|