codehere 0.8.0 → 1.0.1
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 +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +269 -25
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +3 -2
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +90 -15
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +133 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +22 -10
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +0 -10
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +132 -1
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +164 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +238 -21
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1462 -380
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4214
- package/dist/infrastructure/protocols/verify-page.js +849 -155
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +150 -3
- package/dist/infrastructure/storage/project-thread-reader.js +261 -24
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +26 -14
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -19,13 +19,19 @@
|
|
|
19
19
|
import { existsSync, readFileSync } from 'fs';
|
|
20
20
|
import { colors, symbols } from '../../../ui.js';
|
|
21
21
|
import { AUDIT_FILE, verifyAuditChain } from '../../../infrastructure/hooks/claude-sdk-hook.js';
|
|
22
|
+
import { verifyAuditTip, tipBlocksVerification } from '../../../infrastructure/hooks/audit-tip-signature.js';
|
|
23
|
+
import { exportAat, aatToJsonl } from '../../../application/use-cases/export-aat.js';
|
|
24
|
+
import { writeFileSync as writeFileSyncFs } from 'fs';
|
|
25
|
+
import { trustedKeySet } from '../../../infrastructure/orchestration/thread-key-store.js';
|
|
22
26
|
export function executeAuditCommand(sub, arg, options = {}) {
|
|
23
27
|
if (!sub || sub === 'verify')
|
|
24
28
|
return verify(options.json ?? false);
|
|
25
29
|
if (sub === 'show')
|
|
26
30
|
return show(arg, options.json ?? false);
|
|
31
|
+
if (sub === 'export')
|
|
32
|
+
return exportAatCmd(arg);
|
|
27
33
|
process.stderr.write(colors.error(`${symbols.error} Unknown audit subcommand: ${sub}\n`) +
|
|
28
|
-
colors.dim('Try: codehere audit verify | codehere audit show <seq
|
|
34
|
+
colors.dim('Try: codehere audit verify | codehere audit show <seq> | codehere audit export [out.jsonl]\n'));
|
|
29
35
|
return 2;
|
|
30
36
|
}
|
|
31
37
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -33,16 +39,45 @@ export function executeAuditCommand(sub, arg, options = {}) {
|
|
|
33
39
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
34
40
|
function verify(json) {
|
|
35
41
|
const result = verifyAuditChain(AUDIT_FILE);
|
|
42
|
+
// R2 · the tip signature catches a re-walked forgery the hash chain alone
|
|
43
|
+
// can't (a determined edit + recomputed hashes verifies green, but the signed
|
|
44
|
+
// entry's hash changes). ANCESTOR check: honest appends since signing are
|
|
45
|
+
// expected and pass; an edit at/below the signed seq, truncation, an untrusted
|
|
46
|
+
// re-sign, or a deleted .tipsig on a previously-signed chain all fail.
|
|
47
|
+
const tip = verifyAuditTip(AUDIT_FILE, trustedKeySet());
|
|
48
|
+
const tipTampered = tipBlocksVerification(tip);
|
|
49
|
+
// An empty or missing log has nothing to verify, and a chain walk over zero entries
|
|
50
|
+
// trivially succeeds. Reporting that as valid:true is the failure this product exists to
|
|
51
|
+
// catch: deleting the audit log is exactly the attack the hash chain is meant to detect,
|
|
52
|
+
// and the machine interface was answering "intact" to a machine that asked. The human
|
|
53
|
+
// path already says "No audit entries yet" and claims nothing, so only --json was lying.
|
|
54
|
+
// "Is my chain intact?" over no chain is not answered by yes.
|
|
55
|
+
const verifiable = result.totalEntries > 0;
|
|
56
|
+
const ok = verifiable && result.valid && !tipTampered;
|
|
36
57
|
if (json) {
|
|
37
58
|
process.stdout.write(JSON.stringify({
|
|
38
59
|
path: AUDIT_FILE,
|
|
39
|
-
valid:
|
|
60
|
+
valid: ok,
|
|
61
|
+
verifiable,
|
|
62
|
+
...(verifiable ? {} : { reason: 'no audit entries — nothing to verify (is the hook installed?)' }),
|
|
63
|
+
chainHashesValid: result.valid,
|
|
64
|
+
writerMix: result.writerMix,
|
|
65
|
+
tipSignature: {
|
|
66
|
+
present: tip.present,
|
|
67
|
+
expectedButMissing: tip.expectedButMissing,
|
|
68
|
+
signatureValid: tip.signatureValid,
|
|
69
|
+
ancestorIntact: tip.ancestorIntact,
|
|
70
|
+
matchesTip: tip.matchesTip,
|
|
71
|
+
trusted: tip.trusted,
|
|
72
|
+
signer: tip.signerKeyB64,
|
|
73
|
+
reason: tip.reason,
|
|
74
|
+
},
|
|
40
75
|
totalEntries: result.totalEntries,
|
|
41
76
|
chainedEntries: result.chainedEntries,
|
|
42
77
|
legacyEntries: result.legacyEntries,
|
|
43
78
|
errors: result.errors,
|
|
44
79
|
}, null, 2) + '\n');
|
|
45
|
-
return
|
|
80
|
+
return ok ? 0 : 1;
|
|
46
81
|
}
|
|
47
82
|
// Human output · header, verdict line, counts, then errors if any.
|
|
48
83
|
process.stdout.write('\n' + colors.dim('audit log: ') + colors.gray(AUDIT_FILE) + '\n\n');
|
|
@@ -63,6 +98,24 @@ function verify(json) {
|
|
|
63
98
|
colors.dim(` ${result.errors.length} break point${result.errors.length === 1 ? '' : 's'} detected.`) +
|
|
64
99
|
'\n\n');
|
|
65
100
|
}
|
|
101
|
+
// R1.5 · writer-provenance mix — honest about WHO wrote the entries. The
|
|
102
|
+
// verify surface must never imply everything went through the daemon when
|
|
103
|
+
// some entries are the hook's direct fallback or predate provenance.
|
|
104
|
+
const wm = result.writerMix;
|
|
105
|
+
if (result.chainedEntries > 0) {
|
|
106
|
+
process.stdout.write(colors.dim(` written by: ${wm.daemon} daemon \u00B7 ${wm.hookDirect} hook-direct (no daemon reachable) \u00B7 ${wm.unattributed} unattributed (no writer field)\n\n`));
|
|
107
|
+
}
|
|
108
|
+
// R2 · tip-signature line — catches a re-walked forgery the hash chain can't.
|
|
109
|
+
if (tip.present && tip.signatureValid && tip.ancestorIntact && tip.trusted) {
|
|
110
|
+
process.stdout.write(colors.green(`${symbols.success} Chain signed`) +
|
|
111
|
+
colors.dim(tip.matchesTip
|
|
112
|
+
? " by a trusted key · no edits by anyone without this machine's key."
|
|
113
|
+
: ' by a trusted key · the signed prefix is intact (the chain has grown since).') +
|
|
114
|
+
'\n\n');
|
|
115
|
+
}
|
|
116
|
+
else if (tipTampered) {
|
|
117
|
+
process.stdout.write(colors.error(`${symbols.error} Tip signature FAILED`) + colors.dim(` ${tip.reason}.`) + '\n\n');
|
|
118
|
+
}
|
|
66
119
|
process.stdout.write(colors.bold('Counts:\n'));
|
|
67
120
|
process.stdout.write(` ${result.totalEntries.toString().padStart(6)} total entries\n`);
|
|
68
121
|
process.stdout.write(` ${result.chainedEntries.toString().padStart(6)} hash-chained (verifiable)\n`);
|
|
@@ -80,7 +133,7 @@ function verify(json) {
|
|
|
80
133
|
}
|
|
81
134
|
process.stdout.write('\n');
|
|
82
135
|
}
|
|
83
|
-
return
|
|
136
|
+
return ok ? 0 : 1;
|
|
84
137
|
}
|
|
85
138
|
function show(seqStr, json) {
|
|
86
139
|
if (!seqStr || !/^\d+$/.test(seqStr)) {
|
|
@@ -123,4 +176,26 @@ function show(seqStr, json) {
|
|
|
123
176
|
colors.dim(' Tip: run `codehere audit verify` to see the chained-entry count.\n'));
|
|
124
177
|
return 2;
|
|
125
178
|
}
|
|
179
|
+
/* ──────────────────────────────────────────────────────────────────────── */
|
|
180
|
+
/** R-AAT · export the chain as IETF draft-sharif-agent-audit-trail-00 JSONL.
|
|
181
|
+
* The header line carries the honest scope notes (L0 trust level, codehere
|
|
182
|
+
* canonicalization, hashed inputs) so the FILE can't claim more than it is. */
|
|
183
|
+
function exportAatCmd(outPath) {
|
|
184
|
+
const exp = exportAat(AUDIT_FILE);
|
|
185
|
+
const target = outPath && outPath.trim() ? outPath.trim() : 'codehere-audit-aat.jsonl';
|
|
186
|
+
try {
|
|
187
|
+
writeFileSyncFs(target, aatToJsonl(exp), 'utf-8');
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
process.stderr.write(colors.error(`${symbols.error} Could not write ${target}: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
193
|
+
process.stdout.write(colors.green(`${symbols.success} Exported ${exp.header.record_count} records`) +
|
|
194
|
+
colors.dim(` › ${target} (AAT draft-00 JSONL)\n`) +
|
|
195
|
+
(exp.skippedLegacy > 0
|
|
196
|
+
? colors.dim(` ${exp.skippedLegacy} legacy pre-chain entries skipped (no hash-chained position to export)\n`)
|
|
197
|
+
: '') +
|
|
198
|
+
colors.dim(' trust_level=L0 throughout · agents carry no cryptographic identity; the daemon signs the chain tip (recorder identity). Details in the header line.\n'));
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
126
201
|
//# sourceMappingURL=audit-command.js.map
|
|
@@ -41,7 +41,7 @@ function showBudget() {
|
|
|
41
41
|
newline();
|
|
42
42
|
console.log(info("Today's estimated spend:"), fmtUsd(spend.totalUsd), `(${spend.sessionCount} session${spend.sessionCount === 1 ? '' : 's'})`);
|
|
43
43
|
if (cfg.dailyUsd === null) {
|
|
44
|
-
console.log(info('Daily budget:'), 'none
|
|
44
|
+
console.log(info('Daily budget:'), 'none · no enforcement');
|
|
45
45
|
console.log(info('Set one with:'), 'codehere budget set 5');
|
|
46
46
|
}
|
|
47
47
|
else {
|
|
@@ -60,7 +60,7 @@ function showBudget() {
|
|
|
60
60
|
function setBudget(value) {
|
|
61
61
|
const n = Number(value.replace(/[$,]/g, ''));
|
|
62
62
|
if (!Number.isFinite(n) || n <= 0) {
|
|
63
|
-
console.error(error('Invalid amount:'), value, '
|
|
63
|
+
console.error(error('Invalid amount:'), value, '· expected a positive USD number.');
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
66
|
const cfg = loadBudget();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: check — the simplest proof. codehere independently RE-RUNS your project's
|
|
3
|
+
* tests and reports the real result, so you trust what the AI actually did, not
|
|
4
|
+
* what it claimed. Zero setup, no daemon, no API. Exit 1 on failure (CI-usable).
|
|
5
|
+
*/
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { colors, symbols } from '../../../ui.js';
|
|
9
|
+
import { rerunTests } from '../../../application/use-cases/rerun-tests.js';
|
|
10
|
+
/** Short commit SHA of `cwd`'s repo, or null when it isn't a git checkout. */
|
|
11
|
+
function gitShortSha(cwd) {
|
|
12
|
+
try {
|
|
13
|
+
return execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
14
|
+
.toString()
|
|
15
|
+
.trim();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A paste-ready "verified outcome receipt": the one thing a reviewer can trust
|
|
23
|
+
* without re-running anything — codehere independently re-ran the suite, here's the
|
|
24
|
+
* observed result, pinned to a commit + timestamp, WITH the exact command to
|
|
25
|
+
* reproduce it independently. Plain markdown so it drops into a PR description or
|
|
26
|
+
* review comment as-is.
|
|
27
|
+
*
|
|
28
|
+
* Why reproducibility is the trust mechanism (not a digest): a self-computed hash
|
|
29
|
+
* over self-reported facts proves nothing — a sender could edit the facts AND the
|
|
30
|
+
* hash. What a third party can actually trust is REPRODUCING it: check out the
|
|
31
|
+
* pinned commit, run `codehere check`, get the same exit code. So the receipt leads
|
|
32
|
+
* with the reproduce command and is honest about SCOPE (a test re-run, not a
|
|
33
|
+
* security/logic audit) — it never says more than it verified.
|
|
34
|
+
*
|
|
35
|
+
* ponytail: this is the standalone (no-daemon) receipt. Upgrade path to true
|
|
36
|
+
* tamper-EVIDENCE (authenticity, not just reproducibility): when a ~/.codehere audit
|
|
37
|
+
* chain covers this commit, fold in its verified hash (audit-chain.ts) so the receipt
|
|
38
|
+
* also carries a digest a third party can check against an append-only log.
|
|
39
|
+
*/
|
|
40
|
+
function buildProofBlock(cwd, r) {
|
|
41
|
+
const sha = gitShortSha(cwd);
|
|
42
|
+
const when = new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
|
|
43
|
+
const at = sha ? `commit \`${sha}\`` : 'the working tree';
|
|
44
|
+
if (!r.ran) {
|
|
45
|
+
return `> **codehere · verified outcome receipt** — no suite re-run (${r.skippedReason || 'none detected'}) · nothing verified · ${when}\n`;
|
|
46
|
+
}
|
|
47
|
+
if (r.exitCode === 126 || r.exitCode === 127) {
|
|
48
|
+
return `> **codehere · verified outcome receipt** — couldn't run \`${r.command}\` on ${at} (runner not installed, exit ${r.exitCode}) · unverified · ${when}\n`;
|
|
49
|
+
}
|
|
50
|
+
const passed = (r.exitCode ?? 1) === 0;
|
|
51
|
+
const counts = typeof r.passed === 'number' || typeof r.failed === 'number'
|
|
52
|
+
? ` (${r.passed ?? '?'} passed · ${r.failed ?? 0} failed)`
|
|
53
|
+
: '';
|
|
54
|
+
const verdict = passed ? '✓ tests pass' : '✕ tests FAIL';
|
|
55
|
+
// Reproduce line: the real, verifiable trust — anyone can re-run and confirm.
|
|
56
|
+
const reproduce = sha ? `> Reproduce independently: \`git checkout ${sha} && codehere check\`\n` : '';
|
|
57
|
+
return (`> **codehere · verified outcome receipt** · ${verdict}${counts}\n` +
|
|
58
|
+
`> codehere independently re-ran \`${r.command}\` on ${at} — exit ${r.exitCode}. Observed, not the AI's claim. · ${when}\n` +
|
|
59
|
+
reproduce +
|
|
60
|
+
`> Scope: the project's test suite — not a security or logic audit.\n`);
|
|
61
|
+
}
|
|
62
|
+
export async function executeCheckCommand(target, options = {}) {
|
|
63
|
+
const cwd = resolve(target || '.');
|
|
64
|
+
const r = await rerunTests(cwd);
|
|
65
|
+
if (options.json) {
|
|
66
|
+
process.stdout.write(JSON.stringify({ cwd, ...r }, null, 2) + '\n');
|
|
67
|
+
return r.ran && (r.exitCode ?? 1) !== 0 ? 1 : 0;
|
|
68
|
+
}
|
|
69
|
+
if (options.proof) {
|
|
70
|
+
process.stdout.write(buildProofBlock(cwd, r));
|
|
71
|
+
return r.ran && (r.exitCode ?? 1) !== 0 ? 1 : 0;
|
|
72
|
+
}
|
|
73
|
+
if (!r.ran) {
|
|
74
|
+
process.stdout.write(colors.dim(`No test suite codehere can run here — ${r.skippedReason || 'none detected'}.\n`) +
|
|
75
|
+
colors.dim('codehere proves the AI by re-running your tests; add one (npm test / node --test / pytest / go test / cargo test) and run check again.\n'));
|
|
76
|
+
return 0; // nothing to verify is not a failure
|
|
77
|
+
}
|
|
78
|
+
// Honesty: exit 126/127 means the test command couldn't be RUN (binary missing /
|
|
79
|
+
// deps not installed), not that the tests failed. Never report "FAIL" for that.
|
|
80
|
+
if (r.exitCode === 126 || r.exitCode === 127) {
|
|
81
|
+
process.stdout.write('\n' +
|
|
82
|
+
colors.error(`${symbols.error} Couldn't run the tests`) +
|
|
83
|
+
colors.dim(` — '${r.command}' exited ${r.exitCode} (the runner isn't installed; try installing deps, then re-run).\n\n`));
|
|
84
|
+
return 1; // non-zero for CI (unverified), but NOT a false "tests failed"
|
|
85
|
+
}
|
|
86
|
+
const passed = (r.exitCode ?? 1) === 0;
|
|
87
|
+
const counts = typeof r.passed === 'number' || typeof r.failed === 'number'
|
|
88
|
+
? ` ${r.passed ?? '?'} passed · ${r.failed ?? 0} failed`
|
|
89
|
+
: '';
|
|
90
|
+
process.stdout.write('\n' +
|
|
91
|
+
(passed ? colors.success(`${symbols.success} Tests pass`) : colors.error(`${symbols.error} Tests FAIL`)) +
|
|
92
|
+
colors.dim(` — codehere re-ran them itself (${r.command}, exit ${r.exitCode})${counts}\n`) +
|
|
93
|
+
colors.dim("This is observed reality, not the agent's claim.\n\n"));
|
|
94
|
+
return passed ? 0 : 1;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=check-command.js.map
|
|
@@ -92,6 +92,10 @@ function statusTag(s) {
|
|
|
92
92
|
return colors.gray('● manual ');
|
|
93
93
|
case 'disconnected':
|
|
94
94
|
return colors.gray('○ disconnected');
|
|
95
|
+
default:
|
|
96
|
+
// A status this CLI doesn't know yet must never print "undefined"
|
|
97
|
+
// (2026-06-07 audit, Lane C: draw.io/GitHub rows did exactly that).
|
|
98
|
+
return colors.gray(('○ ' + String(s ?? 'unknown')).padEnd(14).slice(0, 14));
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
/* ──────────────────────────────────────────────────────────────────────── */
|
|
@@ -128,9 +132,24 @@ async function connect(providerArg, json, openBrowser) {
|
|
|
128
132
|
if (json) {
|
|
129
133
|
process.stdout.write(JSON.stringify({ error: 'connect_failed', detail: r.error }, null, 2) + '\n');
|
|
130
134
|
}
|
|
131
|
-
else {
|
|
135
|
+
else if (r.status === 0) {
|
|
136
|
+
// Only a NETWORK failure means the daemon is unreachable.
|
|
132
137
|
process.stderr.write(daemonUnreachableNote(r.error ?? ''));
|
|
133
138
|
}
|
|
139
|
+
else {
|
|
140
|
+
// The daemon answered — show ITS message, don't blame the connection
|
|
141
|
+
// (2026-06-07 audit, Lane C: a 404 from a live daemon printed
|
|
142
|
+
// "Daemon not reachable").
|
|
143
|
+
let msg = r.error ?? `request failed (${r.status})`;
|
|
144
|
+
try {
|
|
145
|
+
const j = JSON.parse(msg);
|
|
146
|
+
msg = j.error || j.message || msg;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* raw text is fine */
|
|
150
|
+
}
|
|
151
|
+
process.stderr.write(colors.error(`${symbols.error} ${providerArg}: ${msg}\n`));
|
|
152
|
+
}
|
|
134
153
|
return 2;
|
|
135
154
|
}
|
|
136
155
|
const url = r.data?.authorizeUrl;
|
|
@@ -47,7 +47,7 @@ const COMMAND_INFO = {
|
|
|
47
47
|
},
|
|
48
48
|
trust: {
|
|
49
49
|
name: 'trust',
|
|
50
|
-
description: 'Scan your project for security issues
|
|
50
|
+
description: 'Scan your project for security issues · instant score, no API key needed',
|
|
51
51
|
usage: 'codehere trust [path] [options]',
|
|
52
52
|
examples: [
|
|
53
53
|
'codehere trust .',
|
|
@@ -82,7 +82,7 @@ const COMMAND_INFO = {
|
|
|
82
82
|
},
|
|
83
83
|
status: {
|
|
84
84
|
name: 'status',
|
|
85
|
-
description: 'Dashboard
|
|
85
|
+
description: 'Dashboard · sessions, blocked calls, audit trends',
|
|
86
86
|
usage: 'codehere status [options]',
|
|
87
87
|
examples: ['codehere status', 'codehere status --sessions', 'codehere status --weekly', 'codehere status --json'],
|
|
88
88
|
options: [
|
|
@@ -172,7 +172,7 @@ const COMMAND_INFO = {
|
|
|
172
172
|
},
|
|
173
173
|
health: {
|
|
174
174
|
name: 'health',
|
|
175
|
-
description: 'System health check
|
|
175
|
+
description: 'System health check · filesystem, memory, audit log accessible',
|
|
176
176
|
usage: 'codehere health [options]',
|
|
177
177
|
examples: ['codehere health', 'codehere health --json'],
|
|
178
178
|
options: [{ flag: '--json', description: 'Output as JSON' }],
|
|
@@ -269,7 +269,7 @@ function displayCommandHelp(commandName) {
|
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
newline();
|
|
272
|
-
console.log(colors.bold(command.name) + colors.dim('
|
|
272
|
+
console.log(colors.bold(command.name) + colors.dim(' · ' + command.description));
|
|
273
273
|
newline();
|
|
274
274
|
console.log(colors.bold('Usage'));
|
|
275
275
|
console.log(' ' + colors.cyan(command.usage));
|
|
@@ -302,7 +302,7 @@ function printCmd(name, desc) {
|
|
|
302
302
|
}
|
|
303
303
|
function displayGeneralHelp() {
|
|
304
304
|
newline();
|
|
305
|
-
console.log(colors.bold('codehere') + colors.dim('
|
|
305
|
+
console.log(colors.bold('codehere') + colors.dim(' · Your AI coding agents: audited, cost-bounded, cross-vendor'));
|
|
306
306
|
newline();
|
|
307
307
|
console.log(colors.bold('Delegate'));
|
|
308
308
|
for (const cmd of ['start', 'task', 'kill']) {
|
|
@@ -132,10 +132,10 @@ export async function executeHookCommand(subcommand, options = {}) {
|
|
|
132
132
|
console.log(colors.dim(` Skills installed to: ${SKILLS_TARGET_DIR}`));
|
|
133
133
|
newline();
|
|
134
134
|
console.log(colors.bold('Slash commands now available in your AI assistant:'));
|
|
135
|
-
console.log(colors.dim(' /codehere:trust
|
|
136
|
-
console.log(colors.dim(' /codehere:report
|
|
137
|
-
console.log(colors.dim(' /codehere:audit
|
|
138
|
-
console.log(colors.dim(' /codehere:hook-status
|
|
135
|
+
console.log(colors.dim(' /codehere:trust · scan current project'));
|
|
136
|
+
console.log(colors.dim(' /codehere:report · generate shareable audit report'));
|
|
137
|
+
console.log(colors.dim(' /codehere:audit · pre-merge security check'));
|
|
138
|
+
console.log(colors.dim(' /codehere:hook-status · check hook health'));
|
|
139
139
|
}
|
|
140
140
|
newline();
|
|
141
141
|
console.log(colors.dim(' Restart your AI assistant for changes to take effect.'));
|
|
@@ -219,7 +219,7 @@ export async function executeHookCommand(subcommand, options = {}) {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
else {
|
|
222
|
-
console.log(colors.dim(" No events logged yet
|
|
222
|
+
console.log(colors.dim(" No events logged yet · run Claude Code and it'll start tracking."));
|
|
223
223
|
newline();
|
|
224
224
|
}
|
|
225
225
|
break;
|
|
@@ -117,7 +117,7 @@ Things you've been burned by.]
|
|
|
117
117
|
|
|
118
118
|
## Implement a change
|
|
119
119
|
|
|
120
|
-
> "Read .codehere/CONTEXT.md. [Describe the change in user-visible terms
|
|
120
|
+
> "Read .codehere/CONTEXT.md. [Describe the change in user-visible terms · what
|
|
121
121
|
> behavior is added, removed, or changed.] Match existing conventions. Don't
|
|
122
122
|
> touch unrelated files. Don't add new dependencies."
|
|
123
123
|
|
|
@@ -225,7 +225,7 @@ export const F3_TEMPLATES = [
|
|
|
225
225
|
"version": "v0.5",
|
|
226
226
|
"patterns_builtin": [],
|
|
227
227
|
"patterns_project": [],
|
|
228
|
-
"_note": "Run 'codehere protocols export' to populate patterns_builtin[] from the trust-scorer pattern bank. Add project-specific patterns to patterns_project[]
|
|
228
|
+
"_note": "Run 'codehere protocols export' to populate patterns_builtin[] from the trust-scorer pattern bank. Add project-specific patterns to patterns_project[] · those are preserved across re-exports. Do NOT hand-edit patterns_builtin[] · it's regenerated on each export."
|
|
229
229
|
}
|
|
230
230
|
`,
|
|
231
231
|
},
|
|
@@ -436,13 +436,25 @@ export function scaffoldProjectAt(root, options = {}) {
|
|
|
436
436
|
const noEpisodic = { result: 'failed', sourcePath: '', targetPath: '' };
|
|
437
437
|
// Refuse if .codehere itself is a symlink — would redirect every subsequent write.
|
|
438
438
|
if (existsSync(codehereDir) && isSymlink(codehereDir)) {
|
|
439
|
-
return {
|
|
439
|
+
return {
|
|
440
|
+
ok: false,
|
|
441
|
+
reason: 'codehere-dir-is-symlink',
|
|
442
|
+
...empty,
|
|
443
|
+
episodic: noEpisodic,
|
|
444
|
+
gitignore: { result: 'skipped' },
|
|
445
|
+
};
|
|
440
446
|
}
|
|
441
447
|
if (!existsSync(codehereDir)) {
|
|
442
448
|
mkdirSync(codehereDir, { recursive: true });
|
|
443
449
|
}
|
|
444
450
|
else if (!statSync(codehereDir).isDirectory()) {
|
|
445
|
-
return {
|
|
451
|
+
return {
|
|
452
|
+
ok: false,
|
|
453
|
+
reason: 'codehere-not-a-directory',
|
|
454
|
+
...empty,
|
|
455
|
+
episodic: noEpisodic,
|
|
456
|
+
gitignore: { result: 'skipped' },
|
|
457
|
+
};
|
|
446
458
|
}
|
|
447
459
|
// F3 layout: ensure all directories exist before writing any seeds.
|
|
448
460
|
// Per-segment symlink check on EACH directory's ancestors; if .codehere/memory
|
|
@@ -495,7 +507,7 @@ export function executeInitCommand(options = {}) {
|
|
|
495
507
|
const r = scaffoldProjectAt(root, options);
|
|
496
508
|
if (!r.ok) {
|
|
497
509
|
if (r.reason === 'codehere-dir-is-symlink') {
|
|
498
|
-
console.log(` ${error('refused')} .codehere is a symlink
|
|
510
|
+
console.log(` ${error('refused')} .codehere is a symlink · refusing to follow. Remove it first.`);
|
|
499
511
|
}
|
|
500
512
|
else {
|
|
501
513
|
console.log(` ${error('refused')} .codehere exists but is not a directory.`);
|
|
@@ -512,31 +524,31 @@ export function executeInitCommand(options = {}) {
|
|
|
512
524
|
console.log(` ${info(`exists `)} .codehere/${f} ${colors.dim('(use --force to overwrite)')}`);
|
|
513
525
|
}
|
|
514
526
|
for (const f of refused) {
|
|
515
|
-
console.log(` ${error(`refused`)} .codehere/${f} ${colors.dim('(symlink
|
|
527
|
+
console.log(` ${error(`refused`)} .codehere/${f} ${colors.dim('(symlink · remove it first)')}`);
|
|
516
528
|
}
|
|
517
529
|
for (const d of refusedDirs) {
|
|
518
|
-
console.log(` ${error(`refused`)} .codehere/${d}/ ${colors.dim('(intermediate symlink
|
|
530
|
+
console.log(` ${error(`refused`)} .codehere/${d}/ ${colors.dim('(intermediate symlink · remove it first)')}`);
|
|
519
531
|
}
|
|
520
532
|
// F3 Task 3 — episodic AGENT_LEARNINGS.jsonl symlink to user's session log.
|
|
521
533
|
const episodic = r.episodic;
|
|
522
534
|
switch (episodic.result) {
|
|
523
535
|
case 'created':
|
|
524
|
-
console.log(` ${success('linked ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl
|
|
536
|
+
console.log(` ${success('linked ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath}`);
|
|
525
537
|
break;
|
|
526
538
|
case 're-linked':
|
|
527
|
-
console.log(` ${success('re-linked')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl
|
|
539
|
+
console.log(` ${success('re-linked')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl › ${episodic.targetPath} ${colors.dim('(target updated)')}`);
|
|
528
540
|
break;
|
|
529
541
|
case 'exists':
|
|
530
542
|
// Quiet on the no-op case to avoid output noise on re-runs.
|
|
531
543
|
break;
|
|
532
544
|
case 'refused':
|
|
533
|
-
console.log(` ${error('refused ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl ${colors.dim('(real file or symlinked ancestor present
|
|
545
|
+
console.log(` ${error('refused ')} .codehere/memory/episodic/AGENT_LEARNINGS.jsonl ${colors.dim('(real file or symlinked ancestor present · remove it first to enable the symlink)')}`);
|
|
534
546
|
break;
|
|
535
547
|
case 'root-skipped':
|
|
536
|
-
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(running as root
|
|
548
|
+
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(running as root · would create root-owned files in HOME; re-run as your normal user)')}`);
|
|
537
549
|
break;
|
|
538
550
|
case 'failed':
|
|
539
|
-
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(could not write
|
|
551
|
+
console.log(` ${warning('skipped ')} episodic symlink ${colors.dim('(could not write · HOME may be read-only)')}`);
|
|
540
552
|
break;
|
|
541
553
|
}
|
|
542
554
|
if (!options.noGitignore) {
|
|
@@ -553,7 +565,7 @@ export function executeInitCommand(options = {}) {
|
|
|
553
565
|
console.log(` ${warning('skipped')} .gitignore ${colors.dim('(no .git directory at root)')}`);
|
|
554
566
|
break;
|
|
555
567
|
case 'refused-symlink':
|
|
556
|
-
console.log(` ${error('refused')} .gitignore is a symlink
|
|
568
|
+
console.log(` ${error('refused')} .gitignore is a symlink · left untouched.`);
|
|
557
569
|
break;
|
|
558
570
|
}
|
|
559
571
|
}
|
|
@@ -571,7 +583,7 @@ export function executeInitCommand(options = {}) {
|
|
|
571
583
|
}
|
|
572
584
|
catch (e) {
|
|
573
585
|
const msg = e instanceof Error ? e.message : String(e);
|
|
574
|
-
console.error(`\n ${error('failed')} codehere init
|
|
586
|
+
console.error(`\n ${error('failed')} codehere init · ${msg}\n`);
|
|
575
587
|
process.exitCode = 1;
|
|
576
588
|
}
|
|
577
589
|
}
|