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,805 +0,0 @@
1
- /**
2
- * Interactive REPL · raw-mode live command palette.
3
- *
4
- * Faithful port of v0.2.2's session.ts (commit f4828c2 · "REPL fixes,
5
- * Palette Enter completes selected command, stdin multi-char chunk
6
- * processing"). Same banner with state badges, same `> ` prompt with
7
- * brand color, same filter-as-you-type palette under the prompt, same
8
- * escape-buffering keystroke handler, same Tab/Enter complete, same
9
- * arrow-key palette navigation when palette is visible / history when not.
10
- *
11
- * Backends adapted to v0.3 (chat/edit/review/planner removed in D7):
12
- * - Task slash commands (ask, explain, fix, review, plan, test, refactor,
13
- * orchestrate, run) → spawn via Conductor
14
- * - Subcommand slash (trust, report, hook, status, health, serve, setup,
15
- * config) → invoke `node dist/index.js <cmd>` inline
16
- * - /undo → "queued for v0.5 (F3 memory layer)"
17
- *
18
- * Citing f4828c2 for lineage. Don't redesign.
19
- */
20
- import { existsSync, readdirSync, readFileSync } from 'fs';
21
- import { join, basename } from 'path';
22
- import { spawn as childSpawn } from 'child_process';
23
- import { fileURLToPath } from 'url';
24
- import { dirname } from 'path';
25
- import { colors, success, error, warning, newline, createCodehereLogo } from '../../ui.js';
26
- import { getVersion } from '../../utils/version.js';
27
- import { SessionManager, availableAgents } from '../../infrastructure/orchestration/session-manager.js';
28
- import { collectCodeFiles } from '../../infrastructure/security/file-collection.js';
29
- import chalk from 'chalk';
30
- const brandBlue = chalk.rgb(74, 144, 226);
31
- const stdin = process.stdin;
32
- const stdout = process.stdout;
33
- const SLASH_COMMANDS = [
34
- // Tasks · delegate via Conductor
35
- {
36
- name: 'ask',
37
- args: '<question>',
38
- description: 'Ask anything about your codebase',
39
- kind: 'task',
40
- promptTemplate: (a) => a,
41
- },
42
- {
43
- name: 'explain',
44
- args: '<file>',
45
- description: 'Explain a file',
46
- kind: 'task',
47
- promptTemplate: (a) => `Explain this file in plain English: ${a}`,
48
- },
49
- {
50
- name: 'fix',
51
- args: '<file> <hint>',
52
- description: 'Apply a targeted fix',
53
- kind: 'task',
54
- promptTemplate: (a) => {
55
- const parts = a.split(/\s+/);
56
- const file = parts[0];
57
- const hint = parts.slice(1).join(' ');
58
- return `fix ${file}: ${hint}`;
59
- },
60
- },
61
- {
62
- name: 'review',
63
- args: '<file>',
64
- description: 'Code review with findings',
65
- kind: 'task',
66
- promptTemplate: (a) => `Review ${a} for bugs, security issues, and risks. Concrete findings only.`,
67
- },
68
- {
69
- name: 'plan',
70
- args: '<goal>',
71
- description: 'Break a goal into steps',
72
- kind: 'task',
73
- promptTemplate: (a) => `Plan: ${a}. Break into concrete steps before any execution.`,
74
- },
75
- {
76
- name: 'test',
77
- args: '<file>',
78
- description: 'Generate tests for a file',
79
- kind: 'task',
80
- promptTemplate: (a) => `add tests for ${a}. Cover edge cases and likely failure modes.`,
81
- },
82
- {
83
- name: 'refactor',
84
- args: '<pattern> <instr>',
85
- description: 'Refactor across files',
86
- kind: 'task',
87
- promptTemplate: (a) => `refactor: ${a}`,
88
- },
89
- // Subcommands · run an existing CLI subcommand inline
90
- {
91
- name: 'trust',
92
- args: '[path]',
93
- description: 'Scan for vulnerabilities',
94
- kind: 'sub',
95
- noKey: true,
96
- subcommand: 'trust',
97
- },
98
- {
99
- name: 'report',
100
- args: '[path]',
101
- description: 'Security audit report',
102
- kind: 'sub',
103
- noKey: true,
104
- subcommand: 'report',
105
- },
106
- {
107
- name: 'hook',
108
- args: '[install|status]',
109
- description: 'Safety hook management',
110
- kind: 'sub',
111
- noKey: true,
112
- subcommand: 'hook',
113
- },
114
- {
115
- name: 'status',
116
- args: '',
117
- description: 'Dashboard — hook activity',
118
- kind: 'sub',
119
- noKey: true,
120
- subcommand: 'status',
121
- },
122
- {
123
- name: 'verify',
124
- args: '[session-id]',
125
- description: "Verify a session's Final Report against recorded reality",
126
- kind: 'sub',
127
- noKey: true,
128
- subcommand: 'verify',
129
- },
130
- {
131
- name: 'audit',
132
- args: '[verify|show]',
133
- description: 'Inspect the hash-chained audit log',
134
- kind: 'sub',
135
- noKey: true,
136
- subcommand: 'audit',
137
- },
138
- {
139
- name: 'search',
140
- args: '<query>',
141
- description: 'Search past sessions · every hit cites its evidence',
142
- kind: 'sub',
143
- noKey: true,
144
- subcommand: 'search',
145
- },
146
- {
147
- name: 'context-source',
148
- args: '[list|connect|disconnect]',
149
- description: 'Connect Linear/Figma via OAuth',
150
- kind: 'sub',
151
- noKey: true,
152
- subcommand: 'context-source',
153
- },
154
- {
155
- name: 'thread',
156
- args: '<sub> [arg]',
157
- description: 'Share or import a signed session bundle',
158
- kind: 'sub',
159
- noKey: true,
160
- subcommand: 'thread',
161
- },
162
- { name: 'health', args: '', description: 'System health check', kind: 'sub', noKey: true, subcommand: 'health' },
163
- {
164
- name: 'setup',
165
- args: '',
166
- description: 'Configure first-run preferences',
167
- kind: 'sub',
168
- noKey: true,
169
- subcommand: 'setup',
170
- },
171
- {
172
- name: 'config',
173
- args: '[key] [value]',
174
- description: 'Manage preferences',
175
- kind: 'sub',
176
- noKey: true,
177
- subcommand: 'config',
178
- },
179
- { name: 'agents', args: '', description: 'List installed specialists', kind: 'session', noKey: true },
180
- { name: 'undo', args: '<file>', description: 'Undo file changes (queued for v0.5)', kind: 'session', noKey: true },
181
- { name: 'history', args: '', description: 'Show command history', kind: 'session', noKey: true },
182
- // Helpers
183
- { name: 'help', args: '', description: 'Command reference', kind: 'session', noKey: true },
184
- { name: 'clear', args: '', description: 'Clear the screen', kind: 'session', noKey: true },
185
- { name: 'exit', args: '', description: 'Leave the session', kind: 'session', noKey: true },
186
- ];
187
- const PALETTE_CMDS = SLASH_COMMANDS.map((c) => ({
188
- sig: `/${c.name}${c.args ? ' ' + c.args : ''}`,
189
- name: c.name,
190
- desc: c.description,
191
- noKey: c.noKey,
192
- }));
193
- function maxPalette() {
194
- const rows = process.stdout.rows || 24;
195
- return Math.min(rows - 8, 20);
196
- }
197
- // ── State detection (badges in welcome) ──────────────────────────────────
198
- function detectState() {
199
- const cwd = process.cwd();
200
- const name = basename(cwd);
201
- const settingsPath = join(process.env.HOME || '', '.claude', 'settings.json');
202
- let hasHooks = false;
203
- try {
204
- hasHooks = existsSync(settingsPath) && readFileSync(settingsPath, 'utf-8').includes('codehere');
205
- }
206
- catch {
207
- /* ignore */
208
- }
209
- const hasAnyAgent = availableAgents().some((a) => a.installed && a.id !== 'conductor');
210
- const isGitRepo = existsSync(join(cwd, '.git'));
211
- let fileCount = 0;
212
- let scannableCount = 0;
213
- try {
214
- const count = (dir, d) => {
215
- if (d > 3)
216
- return 0;
217
- let n = 0;
218
- for (const e of readdirSync(dir, { withFileTypes: true })) {
219
- if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist')
220
- continue;
221
- if (e.isDirectory())
222
- n += count(join(dir, e.name), d + 1);
223
- else
224
- n++;
225
- }
226
- return n;
227
- };
228
- fileCount = count(cwd, 0);
229
- // rc.21: surface the scannable count — code files that trust/scan
230
- // would actually look at. Honest count beats a single inflated number.
231
- // UX_OBSERVATIONS P3. Static ESM import (rc.20 used `require()` which is
232
- // undefined in this package's ESM runtime — Codex caught the silent fail).
233
- try {
234
- scannableCount = collectCodeFiles(cwd).length;
235
- }
236
- catch {
237
- /* ignore — banner stays silent rather than crashes */
238
- }
239
- }
240
- catch {
241
- /* ignore */
242
- }
243
- return { name, hasHooks, hasAgents: hasAnyAgent, isGitRepo, fileCount, scannableCount };
244
- }
245
- /**
246
- * Format the banner's "X files (Y scannable)" line.
247
- *
248
- * Pure function (no I/O) so the rc.20 silent-fail regression that Codex
249
- * caught — banner falling back to total-only when scannable load fails —
250
- * has a tight unit test.
251
- *
252
- * Contract:
253
- * - scannable > 0 AND scannable < total → "<total> files (<scannable> scannable)"
254
- * - otherwise → "<total> files"
255
- *
256
- * Exported for test/cli/rc15-fixes.test.ts banner integration test.
257
- */
258
- export function formatFileLine(total, scannable) {
259
- if (scannable > 0 && scannable < total) {
260
- return `${total} files (${scannable} scannable)`;
261
- }
262
- return `${total} files`;
263
- }
264
- export function buildWelcomeLines(state, version) {
265
- const logo = createCodehereLogo();
266
- const ver = colors.dim(`v${version}`);
267
- const badges = [
268
- state.hasHooks ? colors.green('● hooks') : colors.dim('○ hooks'),
269
- state.hasAgents ? colors.green('● agents') : colors.dim('○ agents'),
270
- ];
271
- const lines = [
272
- '',
273
- ` ${logo} ${ver}`,
274
- '',
275
- ' ' + badges.join(' '),
276
- '',
277
- colors.dim(` ${state.name}`) +
278
- colors.dim(` · ${state.isGitRepo ? 'git' : 'dir'} · ${formatFileLine(state.fileCount, state.scannableCount)}`),
279
- '',
280
- ];
281
- if (!state.hasHooks) {
282
- lines.push(' ' + colors.yellow('›') + ' ' + colors.cyan('/hook install') + colors.dim(' to activate safety scanning'));
283
- }
284
- else if (!state.hasAgents) {
285
- lines.push(' ' +
286
- colors.yellow('›') +
287
- ' Install ' +
288
- colors.cyan('claude-code') +
289
- colors.dim(' / ') +
290
- colors.cyan('codex') +
291
- colors.dim(' / aider / opencode to delegate tasks'));
292
- }
293
- lines.push(colors.dim(' Type / for commands, or ask anything'));
294
- lines.push('');
295
- return lines;
296
- }
297
- function printWelcome() {
298
- const state = detectState();
299
- for (const line of buildWelcomeLines(state, getVersion())) {
300
- console.log(line);
301
- }
302
- }
303
- // ── Main session ─────────────────────────────────────────────────────────
304
- export async function runInteractiveRepl() {
305
- printWelcome();
306
- if (stdin.isTTY)
307
- stdin.setRawMode(true);
308
- stdin.resume();
309
- stdin.setEncoding('utf8');
310
- const state = { history: [] };
311
- const historyBuf = [];
312
- let historyIdx = -1;
313
- let line = '';
314
- let selected = 0;
315
- let pLines = 0;
316
- let processing = false;
317
- let escBuf = '';
318
- let escTimer = null;
319
- const cols = () => stdout.columns || 80;
320
- function truncate(s, maxWidth) {
321
- const visible = s.replace(/\x1b\[[0-9;]*m/g, '');
322
- if (visible.length <= maxWidth)
323
- return s;
324
- let vis = 0;
325
- let cut = 0;
326
- for (let i = 0; i < s.length; i++) {
327
- if (s[i] === '\x1b') {
328
- const end = s.indexOf('m', i);
329
- if (end !== -1) {
330
- i = end;
331
- continue;
332
- }
333
- }
334
- vis++;
335
- if (vis >= maxWidth - 1) {
336
- cut = i + 1;
337
- break;
338
- }
339
- }
340
- return cut > 0 ? s.slice(0, cut) + '\x1b[0m' : s;
341
- }
342
- function render() {
343
- stdout.write('\r\x1b[J');
344
- stdout.write(brandBlue('> ') + line);
345
- if (line.startsWith('/') && !line.includes(' ')) {
346
- const filter = line.slice(1).toLowerCase();
347
- const allMatching = PALETTE_CMDS.filter((c) => c.name.startsWith(filter));
348
- const limit = maxPalette();
349
- const filtered = allMatching.slice(0, limit);
350
- const overflow = allMatching.length - filtered.length;
351
- if (filtered.length > 0) {
352
- const maxLen = Math.max(...filtered.map((c) => c.sig.length));
353
- const w = cols();
354
- let drawnLines = 0;
355
- for (let i = 0; i < filtered.length; i++) {
356
- const c = filtered[i];
357
- const pad = ' '.repeat(Math.max(1, maxLen - c.sig.length + 2));
358
- const badge = c.noKey ? colors.dim(' (free)') : '';
359
- const prefix = i === selected ? brandBlue('▸') : ' ';
360
- const text = i === selected ? brandBlue(c.sig) : c.sig;
361
- const row = ` ${prefix} ${text}${pad}${colors.dim(c.desc)}${badge}`;
362
- stdout.write('\n' + truncate(row, w));
363
- drawnLines++;
364
- }
365
- if (overflow > 0) {
366
- stdout.write('\n' + colors.dim(` ... +${overflow} more (type to filter)`));
367
- drawnLines++;
368
- }
369
- stdout.write(`\x1b[${drawnLines}A`);
370
- stdout.write(`\x1b[${line.length + 3}G`);
371
- pLines = drawnLines;
372
- }
373
- else {
374
- stdout.write('\n' + colors.dim(' No matching commands'));
375
- stdout.write('\x1b[1A');
376
- stdout.write(`\x1b[${line.length + 3}G`);
377
- pLines = 1;
378
- }
379
- }
380
- else {
381
- pLines = 0;
382
- }
383
- }
384
- function handle(key) {
385
- if (processing)
386
- return;
387
- // Ctrl+C
388
- if (key === '\x03') {
389
- if (line.length > 0) {
390
- line = '';
391
- selected = 0;
392
- stdout.write('\r\x1b[J\n');
393
- render();
394
- }
395
- else {
396
- stdout.write('\r\x1b[J\n');
397
- process.exit(0);
398
- }
399
- return;
400
- }
401
- // Ctrl+D
402
- if (key === '\x04') {
403
- stdout.write('\r\x1b[J\n' + colors.dim(' See you.') + '\n');
404
- process.exit(0);
405
- }
406
- // Ctrl+L
407
- if (key === '\x0c') {
408
- line = '';
409
- selected = 0;
410
- stdout.write('\x1b[2J\x1b[H');
411
- printWelcome();
412
- render();
413
- return;
414
- }
415
- // Arrow up
416
- if (key === '\x1b[A') {
417
- if (pLines > 0) {
418
- selected = Math.max(0, selected - 1);
419
- }
420
- else if (historyBuf.length > 0 && historyIdx < historyBuf.length - 1) {
421
- historyIdx++;
422
- line = historyBuf[historyIdx];
423
- }
424
- render();
425
- return;
426
- }
427
- // Arrow down
428
- if (key === '\x1b[B') {
429
- if (pLines > 0) {
430
- selected = Math.min(pLines - 1, selected + 1);
431
- }
432
- else {
433
- if (historyIdx > 0) {
434
- historyIdx--;
435
- line = historyBuf[historyIdx];
436
- }
437
- else {
438
- historyIdx = -1;
439
- line = '';
440
- }
441
- }
442
- render();
443
- return;
444
- }
445
- // Tab
446
- if (key === '\t' && pLines > 0) {
447
- const filter = line.slice(1).toLowerCase();
448
- const filtered = PALETTE_CMDS.filter((c) => c.name.startsWith(filter)).slice(0, maxPalette());
449
- if (filtered[selected]) {
450
- line = `/${filtered[selected].name} `;
451
- selected = 0;
452
- }
453
- render();
454
- return;
455
- }
456
- // Enter
457
- if (key === '\r' || key === '\n') {
458
- if (pLines > 0 && line.startsWith('/') && !line.includes(' ')) {
459
- const filter = line.slice(1).toLowerCase();
460
- const filtered = PALETTE_CMDS.filter((c) => c.name.startsWith(filter)).slice(0, maxPalette());
461
- if (filtered[selected]) {
462
- line = `/${filtered[selected].name}`;
463
- }
464
- }
465
- stdout.write('\r\x1b[J\n');
466
- const trimmed = line.trim();
467
- if (trimmed) {
468
- historyBuf.unshift(trimmed);
469
- historyIdx = -1;
470
- }
471
- line = '';
472
- selected = 0;
473
- pLines = 0;
474
- if (!trimmed) {
475
- render();
476
- return;
477
- }
478
- processing = true;
479
- executeInput(trimmed, state)
480
- .catch((err) => {
481
- console.log(error(' ' + (err instanceof Error ? err.message : String(err))));
482
- })
483
- .finally(() => {
484
- processing = false;
485
- stdin.resume();
486
- stdin.ref();
487
- if (stdin.isTTY && !stdin.isRaw)
488
- stdin.setRawMode(true);
489
- console.log();
490
- render();
491
- });
492
- return;
493
- }
494
- // Backspace
495
- if (key === '\x7f' || key === '\b') {
496
- if (line.length > 0) {
497
- line = line.slice(0, -1);
498
- selected = 0;
499
- render();
500
- }
501
- return;
502
- }
503
- // Ignore stray escape sequences
504
- if (key.startsWith('\x1b'))
505
- return;
506
- // Regular character
507
- line += key;
508
- selected = 0;
509
- render();
510
- }
511
- render();
512
- stdin.on('data', (raw) => {
513
- if (processing)
514
- return;
515
- if (raw.startsWith('\x1b') && raw.length >= 2) {
516
- handle(raw);
517
- return;
518
- }
519
- for (let i = 0; i < raw.length; i++) {
520
- if (processing)
521
- return;
522
- const ch = raw[i];
523
- if (escBuf.length > 0) {
524
- escBuf += ch;
525
- if (escTimer)
526
- clearTimeout(escTimer);
527
- if (escBuf.length >= 3) {
528
- const seq = escBuf;
529
- escBuf = '';
530
- handle(seq);
531
- }
532
- else {
533
- escTimer = setTimeout(() => {
534
- const seq = escBuf;
535
- escBuf = '';
536
- handle(seq);
537
- }, 50);
538
- }
539
- continue;
540
- }
541
- if (ch === '\x1b') {
542
- escBuf = ch;
543
- escTimer = setTimeout(() => {
544
- const seq = escBuf;
545
- escBuf = '';
546
- handle(seq);
547
- }, 50);
548
- continue;
549
- }
550
- handle(ch);
551
- }
552
- });
553
- }
554
- /** One-shot · `codehere "review src/auth.ts"` runs that task and exits. */
555
- export async function runOneShot(prompt) {
556
- const installed = availableAgents().filter((a) => a.installed);
557
- if (installed.length === 0) {
558
- console.log(warning('No specialist installed. Run `codehere setup` and try again.'));
559
- process.exit(1);
560
- }
561
- try {
562
- await spawnViaConductor(prompt);
563
- }
564
- catch (err) {
565
- console.error(error('Error:'), err instanceof Error ? err.message : String(err));
566
- process.exit(1);
567
- }
568
- }
569
- // ── Dispatch ─────────────────────────────────────────────────────────────
570
- async function executeInput(input, state) {
571
- const lower = input.toLowerCase();
572
- if (lower.startsWith('/')) {
573
- const parts = input.substring(1).trim().split(/\s+/);
574
- const cmd = parts[0].toLowerCase();
575
- const args = parts.slice(1).join(' ');
576
- await handleSlash(cmd, args, state);
577
- return;
578
- }
579
- // Legacy non-slash syntax (v0.2 backwards compat)
580
- if (lower.startsWith('explain '))
581
- return runTask(`Explain this file in plain English: ${input.substring(8).trim()}`, state);
582
- if (lower.startsWith('e '))
583
- return runTask(`Explain this file in plain English: ${input.substring(2).trim()}`, state);
584
- if (lower.startsWith('fix ')) {
585
- const rest = input.substring(4).trim();
586
- const parts = rest.split(/\s+/);
587
- return runTask(`fix ${parts[0]}: ${parts.slice(1).join(' ')}`, state);
588
- }
589
- if (lower.startsWith('review '))
590
- return runTask(`Review ${input.substring(7).trim()} for bugs, security, and risks.`, state);
591
- if (lower.startsWith('plan '))
592
- return runTask(`Plan: ${input.substring(5).trim()}. Break into steps.`, state);
593
- // Default: ask via Conductor
594
- await runTask(input, state);
595
- }
596
- async function handleSlash(cmd, args, state) {
597
- if (cmd === 'help' || cmd === '?') {
598
- showSlashCommandMenu();
599
- return;
600
- }
601
- if (cmd === 'clear') {
602
- stdout.write('\x1b[2J\x1b[H');
603
- printWelcome();
604
- return;
605
- }
606
- if (cmd === 'exit' || cmd === 'quit' || cmd === 'q') {
607
- console.log(colors.dim('Goodbye!'));
608
- process.exit(0);
609
- }
610
- if (cmd === 'agents') {
611
- const all = availableAgents();
612
- newline();
613
- console.log(colors.bold(' Installed specialists:'));
614
- for (const a of all) {
615
- const tag = a.installed ? colors.green(' ✓ ') : colors.dim(' · ');
616
- const status = a.installed ? '' : colors.dim(' (not installed)');
617
- console.log(tag + colors.cyan(a.id.padEnd(14)) + colors.dim(a.label) + status);
618
- }
619
- newline();
620
- return;
621
- }
622
- if (cmd === 'history' || cmd === 'h') {
623
- if (state.history.length === 0) {
624
- console.log(colors.dim('No command history yet.'));
625
- }
626
- else {
627
- console.log(colors.bold('Recent Commands:'));
628
- state.history.slice(-10).forEach((h, i) => {
629
- const preview = h.command.length > 60 ? h.command.slice(0, 57) + '…' : h.command;
630
- console.log(colors.dim(` ${i + 1}. ${preview}`));
631
- });
632
- }
633
- return;
634
- }
635
- if (cmd === 'undo') {
636
- console.log(colors.dim(' /undo is queued for v0.5 (F3 memory layer).'));
637
- console.log(colors.dim(' For now: ') + colors.cyan('git diff') + colors.dim(' / ') + colors.cyan('git checkout -- <file>'));
638
- return;
639
- }
640
- const def = SLASH_COMMANDS.find((c) => c.name === cmd);
641
- if (!def) {
642
- console.log(warning(`Unknown command: /${cmd}`));
643
- console.log(colors.dim('Type /help to see available commands'));
644
- return;
645
- }
646
- if (def.kind === 'task' && def.promptTemplate) {
647
- if (!args && def.args) {
648
- console.log(warning(`Usage: /${def.name} ${def.args}`));
649
- return;
650
- }
651
- await runTask(def.promptTemplate(args), state);
652
- return;
653
- }
654
- if (def.kind === 'sub' && def.subcommand) {
655
- await runSubcommand(def.subcommand, args ? args.split(/\s+/) : []);
656
- return;
657
- }
658
- }
659
- function showSlashCommandMenu() {
660
- newline();
661
- console.log(colors.bold(' Slash commands'));
662
- newline();
663
- const maxNameLen = Math.max(...SLASH_COMMANDS.map((c) => c.name.length + c.args.length)) + 1;
664
- for (const cmd of SLASH_COMMANDS) {
665
- const signature = `/${cmd.name}${cmd.args ? ' ' + cmd.args : ''}`;
666
- const pad = ' '.repeat(Math.max(0, maxNameLen + 2 - signature.length + 8));
667
- const badge = cmd.noKey ? colors.dim(' (free)') : '';
668
- console.log(` ${colors.cyan(signature)}${pad}${colors.dim(cmd.description)}${badge}`);
669
- }
670
- newline();
671
- console.log(colors.dim(' Type any command with / prefix, or just ask a question directly.'));
672
- newline();
673
- }
674
- // ── Run-task / Run-subcommand helpers ────────────────────────────────────
675
- async function runTask(prompt, state) {
676
- await spawnViaConductor(prompt, state);
677
- }
678
- async function runSubcommand(name, extraArgs) {
679
- const here = fileURLToPath(import.meta.url);
680
- const cliPath = join(dirname(here), '..', '..', 'index.js');
681
- await new Promise((resolve) => {
682
- const child = childSpawn('node', [cliPath, name, ...extraArgs], { stdio: 'inherit' });
683
- child.on('close', () => resolve());
684
- child.on('error', (err) => {
685
- console.log(error(' ' + err.message));
686
- resolve();
687
- });
688
- });
689
- }
690
- /**
691
- * UAT-002 — exit-intent words typed at the REPL prompt without a leading
692
- * slash should leave the session, not get routed to a Conductor spawn.
693
- *
694
- * Pre-fix: typing bare `exit` (no `/`) fell through to spawnViaConductor,
695
- * which booted Conductor, picked claude-code under default routing, made
696
- * a real API call (network or quota), and emitted a confusing Final Report
697
- * echoing the provider error verbatim. First-impression UX bug found by
698
- * UAT 2026-05-19.
699
- */
700
- const EXIT_INTENT_WORDS = new Set(['exit', 'quit', 'q', 'bye', 'goodbye', 'logout', ':q']);
701
- async function spawnViaConductor(prompt, state) {
702
- // UAT-002 guard — exit-intent before any spawn / API call.
703
- const trimmed = prompt.trim().toLowerCase();
704
- if (EXIT_INTENT_WORDS.has(trimmed)) {
705
- console.log(colors.dim('Goodbye!'));
706
- process.exit(0);
707
- }
708
- const installed = availableAgents().filter((a) => a.installed);
709
- if (installed.length === 0) {
710
- console.log(warning('No specialist installed — install one and try again.'));
711
- return;
712
- }
713
- const manager = SessionManager.getInstance();
714
- const session = manager.spawn(prompt, 'conductor', { cwd: process.cwd() });
715
- console.log(colors.dim(' ┌─ ') +
716
- colors.bold(session.sessionId.slice(0, 8)) +
717
- colors.dim(' · ' + new Date(session.startedAt).toLocaleTimeString()));
718
- let captured = '';
719
- await new Promise((resolve) => {
720
- const unsubscribe = session.subscribe((event) => {
721
- const text = renderEvent(event);
722
- if (text)
723
- captured += text + '\n';
724
- if (event.type === 'codehere:complete' || event.type === 'codehere:error' || event.type === 'exit') {
725
- unsubscribe();
726
- resolve();
727
- }
728
- });
729
- });
730
- const status = session.status;
731
- const verdict = status === 'completed'
732
- ? success(' └─ done')
733
- : status === 'killed'
734
- ? warning(' └─ killed')
735
- : error(' └─ ' + status);
736
- console.log(verdict);
737
- if (state) {
738
- state.history.push({ command: prompt, response: captured, timestamp: new Date().toISOString() });
739
- }
740
- }
741
- function renderEvent(event) {
742
- switch (event.type) {
743
- case 'codehere:conductor-routing': {
744
- const data = event.data;
745
- console.log(colors.dim(' │ ') +
746
- colors.bold('routing › ') +
747
- colors.cyan(data.delegatedTo ?? '?') +
748
- colors.dim(' · ' + (data.reason ?? '')));
749
- return null;
750
- }
751
- case 'codehere:steps': {
752
- const data = event.data;
753
- const steps = data?.steps;
754
- if (!steps || steps.length === 0)
755
- return null;
756
- const lastStep = steps[steps.length - 1];
757
- if (!lastStep)
758
- return null;
759
- const state = lastStep.state || 'pending';
760
- const label = String(lastStep.label || '');
761
- if (state === 'active') {
762
- console.log(colors.dim(' │ ') + colors.cyan('●') + ' ' + label);
763
- return label;
764
- }
765
- if (state === 'done' || state === 'failed') {
766
- const icon = state === 'done' ? colors.green('✓') : colors.red('✕');
767
- console.log(colors.dim(' │ ') + icon + ' ' + label);
768
- // P22 / Track B #8 — render last 3 lines of step output for done/failed
769
- if (lastStep.output && String(lastStep.output).trim()) {
770
- const outLines = String(lastStep.output)
771
- .split(String.fromCharCode(10))
772
- .filter((l) => l.trim());
773
- const preview = outLines.slice(-3);
774
- for (const ln of preview) {
775
- console.log(colors.dim(' │ ') + colors.gray(ln));
776
- }
777
- }
778
- return label;
779
- }
780
- return null;
781
- }
782
- case 'stdout':
783
- case 'stderr': {
784
- const data = event.data;
785
- const line = data?.line;
786
- if (line) {
787
- const text = String(line).trimEnd();
788
- if (text) {
789
- console.log(colors.dim(' │ ') + text);
790
- return text;
791
- }
792
- }
793
- return null;
794
- }
795
- case 'error': {
796
- const data = event.data;
797
- if (data?.message)
798
- console.log(colors.red(' │ ✕ ') + data.message);
799
- return null;
800
- }
801
- default:
802
- return null;
803
- }
804
- }
805
- //# sourceMappingURL=repl.js.map