codehere 1.0.1 → 1.0.3

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 (163) hide show
  1. package/README.md +35 -193
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,328 +0,0 @@
1
- /**
2
- * CLI Command: trust
3
- *
4
- * Layer 0 trust scorer — instant static analysis, zero API calls.
5
- * Runs on a file, directory, or piped git diff.
6
- *
7
- * Produces a trust score 0–100 with plain-English explanation:
8
- * 100 = no findings
9
- * 80+ = minor issues, safe to merge
10
- * 60-79 = review recommended
11
- * 40-59 = high-risk, human review required
12
- * < 40 = block until resolved
13
- *
14
- * Usage:
15
- * codehere trust src/utils.ts # score a file
16
- * codehere trust . # score all files in directory
17
- * git diff HEAD | codehere trust # score a diff
18
- * codehere trust --diff HEAD # equivalent using git directly
19
- * codehere trust --badge # output badge markdown
20
- */
21
- import { existsSync } from 'fs';
22
- import { join, relative, isAbsolute } from 'path';
23
- import { execFileSync } from 'child_process';
24
- import { scoreFile, aggregateTrust } from '../../../infrastructure/security/trust-scorer.js';
25
- import { collectCodeFiles } from '../../../infrastructure/security/file-collection.js';
26
- import { colors, sectionHeader, error, newline, info, randomCleanScan } from '../../../ui.js';
27
- import { loadProductContext, adjustRecommendation } from '../../../infrastructure/config/product-context.js';
28
- // ── File collection ──────────────────────────────────────────────────────────
29
- // See src/infrastructure/security/file-collection.ts for exclusion rules.
30
- /**
31
- * Read a file safely — handles iCloud-evicted files on macOS.
32
- * readFileSync blocks forever on iCloud-evicted files (dehydrated placeholders in ~/Documents).
33
- * We use cat with a timeout as a subprocess so the main thread never blocks.
34
- */
35
- function safeReadFile(filepath) {
36
- try {
37
- return execFileSync('cat', [filepath], { encoding: 'utf-8', timeout: 2000, maxBuffer: 5 * 1024 * 1024 });
38
- }
39
- catch {
40
- return null; // File unreadable (iCloud-evicted, permissions, too large) — skip silently
41
- }
42
- }
43
- // ── Git diff mode ────────────────────────────────────────────────────────────
44
- function parseDiffFiles(diff) {
45
- const files = [];
46
- const fileBlocks = diff.split(/^diff --git /m).filter(Boolean);
47
- for (const block of fileBlocks) {
48
- const filenameMatch = block.match(/^a\/(.+?) b\/(.+?)$/m);
49
- if (!filenameMatch)
50
- continue;
51
- const filepath = filenameMatch[2];
52
- // Skip internal backup/cache directories to avoid false findings from old code
53
- if (filepath.startsWith('.codehere/') || filepath.startsWith('.codehere-cache/'))
54
- continue;
55
- // Extract added lines only (lines starting with +, excluding +++)
56
- const addedLines = block
57
- .split('\n')
58
- .filter((l) => l.startsWith('+') && !l.startsWith('+++'))
59
- .map((l) => l.substring(1))
60
- .join('\n');
61
- if (addedLines.trim()) {
62
- files.push({ filepath, code: addedLines });
63
- }
64
- }
65
- return files;
66
- }
67
- // ── Report formatting ────────────────────────────────────────────────────────
68
- function gradeColor(grade) {
69
- switch (grade) {
70
- case 'A':
71
- return colors.green(grade);
72
- case 'B':
73
- return colors.cyan(grade);
74
- case 'C':
75
- return colors.yellow(grade);
76
- case 'D':
77
- return colors.red(grade);
78
- case 'F':
79
- return colors.red(colors.bold(grade));
80
- default:
81
- return grade;
82
- }
83
- }
84
- function scoreBar(score, width = 20) {
85
- const filled = Math.round((score / 100) * width);
86
- const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
87
- if (score >= 75)
88
- return colors.green(bar);
89
- if (score >= 60)
90
- return colors.yellow(bar);
91
- return colors.red(bar);
92
- }
93
- function badgeMarkdown(report) {
94
- const color = report.grade === 'A'
95
- ? 'brightgreen'
96
- : report.grade === 'B'
97
- ? 'green'
98
- : report.grade === 'C'
99
- ? 'yellow'
100
- : report.grade === 'D'
101
- ? 'orange'
102
- : 'red';
103
- return `![codehere trust](https://img.shields.io/badge/codehere%20trust-${report.overallScore}%25%20${report.grade}-${color}?logo=shield)`;
104
- }
105
- // ── Main command ─────────────────────────────────────────────────────────────
106
- export async function executeTrustCommand(target, options = {}) {
107
- const cwd = process.cwd();
108
- let filesToScan = [];
109
- // ── Input source ──────────────────────────────────────────────────────────
110
- if (options.diff !== undefined) {
111
- // Run git diff and scan added lines
112
- try {
113
- // THREAT-2 fix · ref comes from a user-supplied CLI flag. Using template-
114
- // string interpolation with execSync allows shell metacharacters in `ref`
115
- // (e.g. `--diff "main; rm -rf /"`) to escape the git invocation. Use the
116
- // execFileSync array form so the shell is never invoked.
117
- const ref = options.diff || 'HEAD';
118
- const diffOutput = execFileSync('git', ['diff', ref], { cwd, encoding: 'utf-8' });
119
- filesToScan = parseDiffFiles(diffOutput);
120
- if (filesToScan.length === 0) {
121
- console.log(info('No changed files to scan'));
122
- return;
123
- }
124
- }
125
- catch (e) {
126
- console.error(error('git diff failed: ' + String(e)));
127
- process.exit(1);
128
- }
129
- }
130
- else if (!process.stdin.isTTY && !target) {
131
- // Piped input (e.g., git diff HEAD | codehere trust)
132
- let diffInput = '';
133
- for await (const chunk of process.stdin) {
134
- diffInput += chunk;
135
- }
136
- if (diffInput.trim().startsWith('diff --git')) {
137
- filesToScan = parseDiffFiles(diffInput);
138
- }
139
- else {
140
- // Treat as raw code
141
- filesToScan = [{ filepath: 'stdin', code: diffInput }];
142
- }
143
- if (filesToScan.length === 0) {
144
- console.log(info('No content to scan'));
145
- return;
146
- }
147
- }
148
- else {
149
- // File or directory
150
- const resolved = target ? (isAbsolute(target) ? target : join(cwd, target)) : cwd;
151
- if (!existsSync(resolved)) {
152
- console.error(error(`Not found: ${resolved}`));
153
- process.exit(1);
154
- }
155
- const paths = collectCodeFiles(resolved);
156
- let skippedCount = 0;
157
- for (const p of paths) {
158
- const code = safeReadFile(p);
159
- if (code !== null) {
160
- filesToScan.push({ filepath: relative(cwd, p), code });
161
- }
162
- else {
163
- skippedCount++;
164
- }
165
- }
166
- if (skippedCount > 0) {
167
- console.log(colors.dim(` Skipped ${skippedCount} unreadable file${skippedCount > 1 ? 's' : ''}`));
168
- }
169
- if (filesToScan.length === 0 && skippedCount > 0) {
170
- console.log(info('No readable files to scan.'));
171
- console.log(colors.dim(' Check file permissions or availability and retry.'));
172
- return;
173
- }
174
- }
175
- // ── Scan ──────────────────────────────────────────────────────────────────
176
- const results = filesToScan.map((f) => scoreFile(f.filepath, f.code));
177
- // S8 · score + grade come from the ONE shared aggregation (trust-scorer's
178
- // aggregateTrust) — `trust`, `--badge`, and `report` can no longer drift.
179
- const { overallScore, grade, criticalCount, highCount, mediumCount, lowCount, totalFindings } = aggregateTrust(results);
180
- // Nothing scanned is not a clean scan.
181
- //
182
- // aggregateTrust over zero files returns 100/A, and the recommendation below then read
183
- // "Clean scan. Nothing blocking a merge." — an explicit merge recommendation earned by
184
- // looking at nothing. `trust --ci` advertises itself as a gate ("Exit 1 on critical
185
- // findings"), so a CI job pointed at the wrong path, or one whose diff resolution returned
186
- // no files, went green and told the reader the code was fine. Same defect as `audit verify
187
- // --json` reporting valid:true over an empty chain (f030769): a walk over nothing trivially
188
- // succeeds, and success over nothing is not success.
189
- const scanned = filesToScan.length > 0;
190
- const baseRecommendation = !scanned
191
- ? 'No files scanned — nothing was graded. Point trust at a path with source in it, or check your diff range.'
192
- : criticalCount > 0
193
- ? `${criticalCount} critical ${criticalCount === 1 ? 'issue' : 'issues'} · worth sorting before this merges.`
194
- : highCount > 0
195
- ? `Nothing critical, but ${highCount} high-severity ${highCount === 1 ? 'pattern' : 'patterns'} worth a look.`
196
- : mediumCount + lowCount > 0
197
- ? `${mediumCount + lowCount} low-priority ${mediumCount + lowCount === 1 ? 'finding' : 'findings'} · nothing blocking, but worth a pass.`
198
- : 'Clean scan. Nothing blocking a merge.';
199
- const productCtx = loadProductContext(process.cwd());
200
- const recommendation = adjustRecommendation(baseRecommendation, criticalCount, highCount, overallScore, productCtx);
201
- const report = {
202
- files: results,
203
- // `scanned` is the field a machine should branch on. Without it, files:[] plus grade:'A'
204
- // reads as a pass to anything that only looks at the grade.
205
- scanned,
206
- overallScore,
207
- grade,
208
- totalFindings,
209
- criticalCount,
210
- highCount,
211
- mediumCount,
212
- lowCount,
213
- recommendation,
214
- scannedAt: new Date().toISOString(),
215
- };
216
- // Track CI failure — will throw after display so the CLI action handler catches it
217
- const ciFailed = options.ci && (!scanned || overallScore < 60 || criticalCount > 0);
218
- // ── Output ────────────────────────────────────────────────────────────────
219
- if (options.json) {
220
- console.log(JSON.stringify(report, null, 2));
221
- if (options.ci && (!scanned || overallScore < 60 || criticalCount > 0))
222
- process.exit(1);
223
- return;
224
- }
225
- if (options.badge) {
226
- console.log(badgeMarkdown(report));
227
- return;
228
- }
229
- // Human-readable report
230
- newline();
231
- sectionHeader('codehere Trust Report');
232
- newline();
233
- // Score — but only when something was scored.
234
- //
235
- // A full green bar reading 100/100 A over zero files is the headline lie, and it survived
236
- // fixing the JSON and the exit code: those matter to a pipeline, this is what the human
237
- // reads. DESIGN.md §1 reserves green for a measurement, and an aggregation over nothing is
238
- // not one. No score, no grade, no green tick — just what happened.
239
- if (!scanned) {
240
- console.log(` ${colors.dim('—')} no files scanned, so there is no score`);
241
- newline();
242
- }
243
- else {
244
- console.log(` Score ${scoreBar(overallScore)} ${colors.bold(String(overallScore))}/100 ${gradeColor(grade)}`);
245
- newline();
246
- // Findings summary (inline, compact)
247
- const parts = [
248
- criticalCount > 0 ? colors.red(`${criticalCount} critical`) : null,
249
- highCount > 0 ? colors.yellow(`${highCount} high`) : null,
250
- mediumCount > 0 ? colors.dim(`${mediumCount} medium`) : null,
251
- lowCount > 0 ? colors.dim(`${lowCount} low`) : null,
252
- ].filter(Boolean);
253
- if (parts.length === 0) {
254
- console.log(` ${colors.green('✓')} ${randomCleanScan()}`);
255
- }
256
- else {
257
- console.log(` ${parts.join(colors.dim(' · '))}`);
258
- }
259
- }
260
- newline();
261
- // Per-file results (show worst first)
262
- if (results.length > 1) {
263
- const sorted = [...results].sort((a, b) => a.score - b.score);
264
- console.log(colors.dim(' FILES'));
265
- for (const r of sorted.slice(0, 10)) {
266
- const icon = r.score >= 75 ? colors.green('✓') : r.score >= 60 ? colors.yellow('!') : colors.red('✕');
267
- console.log(` ${icon} ${gradeColor(r.grade)} ${String(r.score).padStart(3)}/100 ${r.filepath}`);
268
- }
269
- if (sorted.length > 10) {
270
- console.log(colors.dim(` + ${sorted.length - 10} more files`));
271
- }
272
- newline();
273
- }
274
- // Findings detail
275
- if (options.verbose) {
276
- console.log(colors.dim(' FINDINGS'));
277
- for (const r of results) {
278
- if (r.findings.length === 0)
279
- continue;
280
- console.log(` ${colors.bold(r.filepath)}`);
281
- for (const f of r.findings) {
282
- const sev = f.severity === 'critical'
283
- ? colors.red('crit')
284
- : f.severity === 'high'
285
- ? colors.yellow('high')
286
- : colors.dim(f.severity.slice(0, 4));
287
- const ln = f.line ? colors.dim(`L${f.line}`) : '';
288
- console.log(` ${sev} ${ln.padEnd(6)} ${f.description}`);
289
- }
290
- }
291
- newline();
292
- }
293
- else if (totalFindings > 0) {
294
- console.log(colors.dim(' FINDINGS'));
295
- const severityRank = { critical: 0, high: 1, medium: 2, low: 3 };
296
- const allFindings = results.flatMap((r) => r.findings.map((f) => ({ ...f, filepath: r.filepath })));
297
- const sorted = allFindings.sort((a, b) => severityRank[a.severity] - severityRank[b.severity]);
298
- for (const f of sorted.slice(0, 5)) {
299
- const sev = f.severity === 'critical'
300
- ? colors.red('crit')
301
- : f.severity === 'high'
302
- ? colors.yellow('high')
303
- : colors.dim(f.severity.slice(0, 4));
304
- const ln = f.line ? `:${f.line}` : '';
305
- console.log(` ${sev} ${f.filepath}${ln}`);
306
- console.log(` ${colors.dim(f.description)}`);
307
- }
308
- if (sorted.length > 5) {
309
- console.log(colors.dim(` + ${sorted.length - 5} more findings (use --verbose to see all)`));
310
- }
311
- newline();
312
- }
313
- // Recommendation
314
- const recColor = criticalCount > 0 ? colors.red : highCount > 0 ? colors.yellow : colors.green;
315
- console.log(recColor(recommendation));
316
- newline();
317
- // Tips footer (quiet, separated)
318
- if (results.length > 0 && !options.ci) {
319
- console.log(colors.dim(' ─────'));
320
- console.log(colors.dim(' trust --badge Add a badge to your README'));
321
- console.log(colors.dim(' trust --ci Exit 1 on critical findings'));
322
- newline();
323
- }
324
- if (ciFailed) {
325
- throw new Error(`CI_FAIL:${criticalCount > 0 ? criticalCount + ' critical finding(s)' : 'score ' + overallScore + ' below threshold 60'}`);
326
- }
327
- }
328
- //# sourceMappingURL=trust-command.js.map
@@ -1,298 +0,0 @@
1
- /**
2
- * CLI command: verify
3
- *
4
- * v0.7 verifier-agent · Slice 2 (CLI surface for the pure verify-claim use case).
5
- *
6
- * Compares what the agent's Final Report TEXT claims against the structured
7
- * reality (artifacts + steps) captured during the session. Catches the
8
- * easy lies before they reach a PR description.
9
- *
10
- * Usage:
11
- * codehere verify <session-id> # run verifier on a completed session
12
- * codehere verify <session-id> --json # machine-readable output for CI
13
- * codehere verify # list recent sessions to pick from
14
- *
15
- * Exit codes:
16
- * 0 = verified OR partial (warnings only · unverifiable claims)
17
- * 0 = no-report (nothing to verify; not a failure)
18
- * 1 = broken (at least one claim contradicts recorded reality)
19
- * 2 = session not found / invalid input
20
- *
21
- * The verifier is intentionally conservative: only claims that map to
22
- * structured data are checked. Prose claims pass through silently.
23
- */
24
- import { colors, symbols } from '../../../ui.js';
25
- import { loadSessionById, loadRecent, } from '../../../infrastructure/orchestration/session-persistence.js';
26
- import { assessSession } from '../../../application/services/verdict-service.js';
27
- import { describeCompleted } from '../../../domain/task.js';
28
- import { buildCrossVendorVerdict } from '../../../application/use-cases/build-cross-vendor-verdict.js';
29
- export async function executeVerifyCommand(sessionId, options = {}) {
30
- // No session id: show a picker list and exit gracefully.
31
- if (!sessionId) {
32
- return showSessionPicker(options.json ?? false);
33
- }
34
- const session = loadSessionById(sessionId);
35
- if (!session) {
36
- if (options.json) {
37
- process.stdout.write(JSON.stringify({
38
- error: 'session-not-found',
39
- sessionId,
40
- hint: 'Run `codehere verify` (no args) to list recent sessions.',
41
- }, null, 2) + '\n');
42
- }
43
- else {
44
- process.stderr.write(colors.error(`${symbols.error} No completed session found matching "${sessionId}".\n`) +
45
- colors.dim(' Tip: run `codehere verify` (no args) to see recent sessions.\n'));
46
- }
47
- return 2;
48
- }
49
- const result = assessSession(session);
50
- const crossVendor = buildCrossVendorVerdict(session);
51
- if (options.json) {
52
- process.stdout.write(JSON.stringify({ ...serializeResult(session, result), crossVendor: crossVendor ?? null }, null, 2) + '\n');
53
- }
54
- else {
55
- printHumanReadable(session, result, crossVendor);
56
- }
57
- // Exit code: only `broken` is a hard failure (CI-friendly).
58
- return result.status === 'broken' ? 1 : 0;
59
- }
60
- /* ──────────────────────────────────────────────────────────────────────── */
61
- /* Picker mode · no session id given */
62
- /* ──────────────────────────────────────────────────────────────────────── */
63
- function showSessionPicker(json) {
64
- const recent = loadRecent(15);
65
- if (recent.length === 0) {
66
- if (json) {
67
- process.stdout.write(JSON.stringify({ sessions: [], note: 'No completed sessions found.' }, null, 2) + '\n');
68
- }
69
- else {
70
- process.stdout.write(colors.dim('No completed sessions found. Run a task first, then verify it:\n') +
71
- colors.dim(' codehere task "describe what you want done"\n'));
72
- }
73
- return 0;
74
- }
75
- if (json) {
76
- const items = recent.map((s) => ({
77
- sessionId: s.sessionId,
78
- agent: s.agent,
79
- prompt: s.prompt,
80
- completedAt: s.completedAt,
81
- hasFinalReport: Boolean(s.finalReport),
82
- }));
83
- process.stdout.write(JSON.stringify({ sessions: items }, null, 2) + '\n');
84
- return 0;
85
- }
86
- process.stdout.write('\n' + colors.bold('Recent sessions') + colors.dim(' (pass --json for machine output)\n\n'));
87
- for (const s of recent) {
88
- const shortId = s.sessionId.slice(0, 8);
89
- const when = formatRelativeTime(s.completedAt);
90
- const reportTag = s.finalReport ? colors.gray('· report ✓') : colors.gray('· no report');
91
- const promptLine = truncate(s.prompt.replace(/\s+/g, ' ').trim(), 70);
92
- process.stdout.write(` ${colors.cyan(shortId)} ${colors.gray(s.agent.padEnd(11))}${colors.dim(when.padEnd(14))} ${reportTag}\n` +
93
- ` ${colors.dim(promptLine)}\n\n`);
94
- }
95
- process.stdout.write(colors.dim('To verify one of these: ') + colors.bold('codehere verify <id>') + '\n\n');
96
- return 0;
97
- }
98
- /* ──────────────────────────────────────────────────────────────────────── */
99
- /* Cross-vendor review rendering */
100
- /* ──────────────────────────────────────────────────────────────────────── */
101
- function printCrossVendor(cv) {
102
- process.stdout.write(colors.bold('Cross-vendor review\n'));
103
- const lead = cv.proven ? colors.success(`${symbols.success} `) : ' ';
104
- process.stdout.write(`${lead}${cv.summary}\n`);
105
- for (const c of cv.caveats)
106
- process.stdout.write(colors.dim(` · ${c}\n`));
107
- if (cv.reviewText) {
108
- process.stdout.write('\n' + colors.dim(`${cv.reviewer.agent}'s findings (verbatim · codehere does not judge them):\n`));
109
- process.stdout.write(colors.gray(truncate(cv.reviewText.replace(/\s+/g, ' ').trim(), 400)) + '\n');
110
- }
111
- process.stdout.write('\n');
112
- }
113
- /* ──────────────────────────────────────────────────────────────────────── */
114
- /* Human-readable output */
115
- /* ──────────────────────────────────────────────────────────────────────── */
116
- function printHumanReadable(session, result, crossVendor) {
117
- const shortId = session.sessionId.slice(0, 8);
118
- const when = formatRelativeTime(session.completedAt);
119
- // Header
120
- process.stdout.write('\n' +
121
- colors.dim('session ') +
122
- colors.cyan(shortId) +
123
- colors.dim(' · ') +
124
- colors.gray(session.agent) +
125
- colors.dim(' · ') +
126
- colors.gray(when) +
127
- '\n' +
128
- colors.dim('task: ') +
129
- truncate(session.prompt.replace(/\s+/g, ' ').trim(), 80) +
130
- '\n\n');
131
- // What the agent did — lead with the deliverable, not the audit. A
132
- // non-technical user opening this wants "what happened" first, then "does
133
- // it check out". Both come straight from recorded state (no screen lies).
134
- if (session.finalReport?.summary) {
135
- process.stdout.write(session.finalReport.summary.trim() + '\n\n');
136
- }
137
- // Files changed + trust, or the honest can't-track note.
138
- const files = session.artifacts?.files ?? [];
139
- if (session.artifacts?.noGitTracking) {
140
- process.stdout.write(colors.dim("Files: codehere couldn't track changes here (this folder isn't a git repository).\n\n"));
141
- }
142
- else if (files.length > 0) {
143
- process.stdout.write(colors.bold('Files changed:\n'));
144
- for (const f of files) {
145
- const trust = typeof f.trust === 'number' ? colors.dim(` · trust ${f.trust}/100`) : '';
146
- process.stdout.write(` • ${f.path} ${colors.success('+' + f.added)}/${colors.error('-' + f.removed)}${trust}\n`);
147
- }
148
- process.stdout.write('\n');
149
- }
150
- // Verdict line · color-coded by status
151
- const verdict = verdictLine(result);
152
- process.stdout.write(colors.bold('Fact-check: ') + verdict + '\n\n');
153
- // Cross-vendor review — when a DIFFERENT vendor reviewed an earlier writer in
154
- // this room, this is the receipt no single-vendor tool can produce.
155
- if (crossVendor)
156
- printCrossVendor(crossVendor);
157
- // Findings detail
158
- if (result.findings.length > 0 && result.status !== 'no-report') {
159
- process.stdout.write(colors.bold('Findings:\n\n'));
160
- for (let i = 0; i < result.findings.length; i++) {
161
- const f = result.findings[i];
162
- const num = colors.dim(` ${(i + 1).toString().padStart(2)}. `);
163
- const sev = severityTag(f.severity);
164
- const kind = colors.gray(`[${f.kind}]`);
165
- process.stdout.write(`${num}${sev} ${kind} ${f.message}\n`);
166
- process.stdout.write(` ${colors.dim('claim:')} ${colors.gray(f.claim)}\n`);
167
- process.stdout.write(` ${colors.dim('reality:')} ${colors.gray(f.reality)}\n\n`);
168
- }
169
- }
170
- // What was checked (so users know the verifier's scope)
171
- process.stdout.write(colors.dim('What was checked:\n'));
172
- process.stdout.write(colors.dim(' · numeric test claims (X tests passed, X of Y, etc) vs artifacts.tests\n'));
173
- process.stdout.write(colors.dim(' · universal claims (all tests pass, no failures) vs artifacts.tests.failed\n'));
174
- process.stdout.write(colors.dim(' · file claims (X files changed, no files changed) vs artifacts.files\n'));
175
- process.stdout.write(colors.dim('Not checked: prose claims, intent, semantic correctness of changes.\n\n'));
176
- // Tier 1.1/0.2 · be explicit about which kind of test ✓ this is. A re-run is
177
- // ground truth (exit-code observed); a scrape is the agent's own claim. The
178
- // skeptic deserves to know which one they're looking at.
179
- const tests = session.artifacts?.tests;
180
- if (tests) {
181
- if (tests.observed) {
182
- process.stdout.write(colors.green(`${symbols.success} tests RE-RUN by codehere`) +
183
- colors.dim(` (${tests.command || 'test suite'}, exit ${tests.exitCode}) · observed, not just reported\n\n`));
184
- }
185
- else {
186
- process.stdout.write(colors.dim('Note: test counts above are AS REPORTED by the agent · codehere did not re-run the suite, so the exit code is not independently confirmed.\n\n'));
187
- }
188
- }
189
- // R13/M3 · the typed completeness invariant — every verdict input is either
190
- // present or carries the explicit honest reason it isn't. This block is what
191
- // makes "the verdict said X" auditable: the reader sees exactly what fed it
192
- // and what couldn't (e.g. "couldn't observe — no test runner detected"),
193
- // never a silent blank.
194
- const completeness = describeCompleted({
195
- sessionId: session.sessionId,
196
- agent: session.agent,
197
- status: session.status,
198
- completedAt: session.completedAt,
199
- durationMs: session.durationMs,
200
- steps: session.steps,
201
- artifacts: session.artifacts,
202
- finalReport: session.finalReport,
203
- });
204
- process.stdout.write(colors.bold('What fed this verdict\n'));
205
- const inputLine = (label, input) => 'present' in input
206
- ? ` ${colors.green(symbols.success)} ${label}\n`
207
- : ` ${colors.dim(symbols.dot)} ${label} ${colors.dim('· ' + input.absent)}\n`;
208
- process.stdout.write(inputLine('file changes', completeness.files));
209
- // A trust surface must say WHICH kind of test ✓ this is: a codehere re-run
210
- // (exit-code observed) or the agent's own scrape (a claim, not ground truth).
211
- const testsLabel = 'present' in completeness.tests
212
- ? completeness.tests.present.observed
213
- ? 'test result (re-run by codehere)'
214
- : 'test result (agent-reported, not re-run)'
215
- : 'test result';
216
- process.stdout.write(inputLine(testsLabel, completeness.tests));
217
- process.stdout.write(inputLine('written report', completeness.report));
218
- process.stdout.write(inputLine('audit reconciliation', completeness.audit));
219
- process.stdout.write('\n');
220
- }
221
- function verdictLine(result) {
222
- switch (result.status) {
223
- case 'verified':
224
- return colors.green(`${symbols.success} Verified`) + colors.dim(' ' + result.summary);
225
- case 'partial':
226
- return colors.warning(`${symbols.warning} Partial`) + colors.dim(' ' + result.summary);
227
- case 'broken':
228
- return colors.error(`${symbols.error} Broken`) + colors.dim(' ' + result.summary);
229
- case 'no-report':
230
- return colors.gray(`${symbols.dot} No report`) + colors.dim(' ' + result.summary);
231
- case 'nothing-to-verify':
232
- // Honest neutral, not a green pass — the report made no checkable claim.
233
- return colors.gray(`${symbols.dot} Nothing to verify`) + colors.dim(' ' + result.summary);
234
- }
235
- }
236
- function severityTag(sev) {
237
- switch (sev) {
238
- case 'fail':
239
- return colors.error('fail');
240
- case 'warn':
241
- return colors.warning('warn');
242
- case 'info':
243
- return colors.info('info');
244
- }
245
- }
246
- /* ──────────────────────────────────────────────────────────────────────── */
247
- /* JSON serialization */
248
- /* ──────────────────────────────────────────────────────────────────────── */
249
- function serializeResult(session, result) {
250
- return {
251
- sessionId: session.sessionId,
252
- agent: session.agent,
253
- prompt: session.prompt,
254
- completedAt: session.completedAt,
255
- durationMs: session.durationMs,
256
- hasFinalReport: Boolean(session.finalReport),
257
- verdict: {
258
- status: result.status,
259
- summary: result.summary,
260
- findings: result.findings.map((f) => ({
261
- severity: f.severity,
262
- kind: f.kind,
263
- claim: f.claim,
264
- reality: f.reality,
265
- message: f.message,
266
- })),
267
- },
268
- };
269
- }
270
- /* ──────────────────────────────────────────────────────────────────────── */
271
- /* Small utilities */
272
- /* ──────────────────────────────────────────────────────────────────────── */
273
- function formatRelativeTime(ts) {
274
- if (!ts || ts <= 0)
275
- return 'unknown';
276
- const deltaMs = Date.now() - ts;
277
- if (deltaMs < 0)
278
- return 'just now';
279
- const seconds = Math.floor(deltaMs / 1000);
280
- if (seconds < 60)
281
- return `${seconds}s ago`;
282
- const minutes = Math.floor(seconds / 60);
283
- if (minutes < 60)
284
- return `${minutes}m ago`;
285
- const hours = Math.floor(minutes / 60);
286
- if (hours < 24)
287
- return `${hours}h ago`;
288
- const days = Math.floor(hours / 24);
289
- if (days < 30)
290
- return `${days}d ago`;
291
- return new Date(ts).toISOString().split('T')[0];
292
- }
293
- function truncate(s, max) {
294
- if (s.length <= max)
295
- return s;
296
- return s.slice(0, max - 1).trimEnd() + '…';
297
- }
298
- //# sourceMappingURL=verify-command.js.map