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.
Files changed (134) hide show
  1. package/README.md +22 -13
  2. package/dist/application/services/verdict-service.js +154 -0
  3. package/dist/application/use-cases/assess-cross-vendor.js +126 -0
  4. package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
  5. package/dist/application/use-cases/export-aat.js +107 -0
  6. package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
  7. package/dist/application/use-cases/generate-final-report.js +140 -15
  8. package/dist/application/use-cases/generate-narration.js +5 -3
  9. package/dist/application/use-cases/generate-thread-title.js +5 -2
  10. package/dist/application/use-cases/plan-auto-review.js +57 -0
  11. package/dist/application/use-cases/reconcile-audit.js +100 -0
  12. package/dist/application/use-cases/rerun-tests.js +226 -0
  13. package/dist/application/use-cases/search-sessions.js +70 -0
  14. package/dist/application/use-cases/should-auto-review.js +71 -0
  15. package/dist/application/use-cases/thread-export.js +90 -0
  16. package/dist/application/use-cases/verify-claim.js +187 -5
  17. package/dist/domain/audit.js +11 -0
  18. package/dist/domain/services/codebase-detector.js +0 -6
  19. package/dist/domain/task.js +91 -0
  20. package/dist/error-handler.js +3 -115
  21. package/dist/index.js +269 -25
  22. package/dist/infrastructure/completion/bash-completion.js +7 -3
  23. package/dist/infrastructure/completion/fish-completion.js +7 -12
  24. package/dist/infrastructure/completion/zsh-completion.js +7 -12
  25. package/dist/infrastructure/config/config-manager.js +4 -3
  26. package/dist/infrastructure/config/product-context.js +1 -19
  27. package/dist/infrastructure/context/adapters/figma-source.js +37 -2
  28. package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
  29. package/dist/infrastructure/context/github-api.js +110 -0
  30. package/dist/infrastructure/context/github-device-flow.js +65 -0
  31. package/dist/infrastructure/context/oauth-store.js +5 -2
  32. package/dist/infrastructure/context/registry-bootstrap.js +0 -4
  33. package/dist/infrastructure/cost/budget-config.js +3 -2
  34. package/dist/infrastructure/cost/cost-router.js +16 -23
  35. package/dist/infrastructure/cost/spend-aggregator.js +90 -15
  36. package/dist/infrastructure/cost/team-ledger.js +355 -0
  37. package/dist/infrastructure/discovery/discovery-log.js +85 -0
  38. package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
  39. package/dist/infrastructure/hooks/claude-sdk-hook.js +133 -3
  40. package/dist/infrastructure/hooks/hook-mode.js +20 -4
  41. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
  42. package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
  43. package/dist/infrastructure/observability/execution-tracer.js +1 -1
  44. package/dist/infrastructure/orchestration/agents-config-seed.js +22 -10
  45. package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
  46. package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
  47. package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
  48. package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
  49. package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
  50. package/dist/infrastructure/orchestration/context-handoff.js +32 -14
  51. package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
  52. package/dist/infrastructure/orchestration/failover-config.js +0 -10
  53. package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
  54. package/dist/infrastructure/orchestration/generic-cli-adapter.js +132 -1
  55. package/dist/infrastructure/orchestration/graphify-context.js +14 -59
  56. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +164 -9
  57. package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
  58. package/dist/infrastructure/orchestration/progress-aggregator.js +238 -21
  59. package/dist/infrastructure/orchestration/session-manager.js +593 -94
  60. package/dist/infrastructure/orchestration/session-persistence.js +177 -41
  61. package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
  62. package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
  63. package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
  64. package/dist/infrastructure/protocols/mcp-server-impl.js +1462 -380
  65. package/dist/infrastructure/protocols/mcp-tools.js +401 -0
  66. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
  67. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
  68. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
  69. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
  70. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
  71. package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
  72. package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
  73. package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
  74. package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
  75. package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
  76. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
  77. package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
  78. package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
  79. package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
  80. package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
  81. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
  82. package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
  83. package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
  84. package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
  85. package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
  86. package/dist/infrastructure/protocols/ui/styles.js +2147 -0
  87. package/dist/infrastructure/protocols/ui/tokens.js +96 -0
  88. package/dist/infrastructure/protocols/ui-page-v2.js +116 -4214
  89. package/dist/infrastructure/protocols/verify-page.js +849 -155
  90. package/dist/infrastructure/security/fix-code.js +144 -0
  91. package/dist/infrastructure/security/fix-secret.js +89 -0
  92. package/dist/infrastructure/security/permissions-config.js +3 -2
  93. package/dist/infrastructure/security/scan-types.js +10 -0
  94. package/dist/infrastructure/security/ship-scan.js +195 -0
  95. package/dist/infrastructure/security/trust-scorer.js +70 -3
  96. package/dist/infrastructure/security/vibe-checks.js +99 -0
  97. package/dist/infrastructure/storage/atomic-write.js +86 -0
  98. package/dist/infrastructure/storage/project-thread-mirror.js +150 -3
  99. package/dist/infrastructure/storage/project-thread-reader.js +261 -24
  100. package/dist/monitoring.js +2 -2
  101. package/dist/presentation/cli/commands/audit-command.js +79 -4
  102. package/dist/presentation/cli/commands/budget-command.js +2 -2
  103. package/dist/presentation/cli/commands/check-command.js +96 -0
  104. package/dist/presentation/cli/commands/context-source-command.js +20 -1
  105. package/dist/presentation/cli/commands/help-command.js +5 -5
  106. package/dist/presentation/cli/commands/hook-command.js +5 -5
  107. package/dist/presentation/cli/commands/init-command.js +26 -14
  108. package/dist/presentation/cli/commands/memory-command.js +28 -28
  109. package/dist/presentation/cli/commands/permission-command.js +1 -12
  110. package/dist/presentation/cli/commands/protocols-builder.js +2 -2
  111. package/dist/presentation/cli/commands/protocols-command.js +4 -4
  112. package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
  113. package/dist/presentation/cli/commands/report-command.js +48 -32
  114. package/dist/presentation/cli/commands/scan-command.js +52 -0
  115. package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
  116. package/dist/presentation/cli/commands/search-command.js +51 -0
  117. package/dist/presentation/cli/commands/setup-command.js +31 -8
  118. package/dist/presentation/cli/commands/status-command.js +39 -86
  119. package/dist/presentation/cli/commands/team-command.js +256 -0
  120. package/dist/presentation/cli/commands/thread-command.js +87 -5
  121. package/dist/presentation/cli/commands/trust-command.js +53 -31
  122. package/dist/presentation/cli/commands/verify-command.js +74 -9
  123. package/dist/presentation/cli/error-display.js +0 -8
  124. package/dist/presentation/cli/repl.js +15 -7
  125. package/dist/presentation/cli/task-deliverable.js +36 -10
  126. package/dist/testing/replay-adapter.js +0 -19
  127. package/dist/ui.js +51 -239
  128. package/dist/utils/version.js +0 -6
  129. package/package.json +30 -10
  130. package/dist/domain/entities/task.js +0 -61
  131. package/dist/infrastructure/protocols/ui-page.js +0 -2377
  132. package/dist/infrastructure/security/input-validator.js +0 -152
  133. package/dist/infrastructure/storage/embeddings-path.js +0 -37
  134. 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>\n'));
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: result.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 result.valid ? 0 : 1;
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 result.valid ? 0 : 1;
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 — no enforcement');
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, '— expected a positive USD number.');
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 — instant score, no API key needed',
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 — sessions, blocked calls, audit trends',
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 — filesystem, memory, audit log accessible',
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(' — ' + command.description));
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(' — A PM for your AI coding agents'));
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 — 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'));
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 — run Claude Code and it'll start tracking."));
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 — what
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[] — those are preserved across re-exports. Do NOT hand-edit patterns_builtin[] — it's regenerated on each export."
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 { ok: false, reason: 'codehere-dir-is-symlink', ...empty, episodic: noEpisodic, gitignore: { result: 'skipped' } };
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 { ok: false, reason: 'codehere-not-a-directory', ...empty, episodic: noEpisodic, gitignore: { result: 'skipped' } };
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 — refusing to follow. Remove it first.`);
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 — remove it first)')}`);
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 — remove it first)')}`);
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 → ${episodic.targetPath}`);
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 → ${episodic.targetPath} ${colors.dim('(target updated)')}`);
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 — remove it first to enable the symlink)')}`);
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 — would create root-owned files in HOME; re-run as your normal user)')}`);
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 — HOME may be read-only)')}`);
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 — left untouched.`);
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 — ${msg}\n`);
586
+ console.error(`\n ${error('failed')} codehere init · ${msg}\n`);
575
587
  process.exitCode = 1;
576
588
  }
577
589
  }