codehere 0.4.1 → 0.5.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/codehere)](https://www.npmjs.com/package/codehere)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow)](../LICENSE)
7
- [![Tests](https://img.shields.io/badge/tests-362%2F362-brightgreen)]()
7
+ [![Tests](https://img.shields.io/badge/tests-443%2F443-brightgreen)]()
8
8
  [![Vulnerabilities](https://img.shields.io/badge/vulnerabilities-0-brightgreen)]()
9
9
 
10
10
  codehere is **a PM for your AI coding agents.** Delegate a task. codehere picks the right specialist (Claude Code, Codex, Aider, OpenCode, or Gemini), spawns it, streams its work back to you, and hands you a plain-English Final Report with a **trust score on every file changed**. Today, Claude Code gets full pre-execution safety-hook enforcement on every tool call; other adapters are session-traced and reported, with per-tool hook parity planned for v0.4.
@@ -26,8 +26,9 @@ One manager, many specialists, every action recorded.
26
26
  | 2 | **Detect environment** | `codehere setup` — checks API key, finds your repo |
27
27
  | 3 | **Install the safety hook** | `codehere hook install` — every Claude Code tool call now audited |
28
28
  | 4 | **Install at least one specialist** | Claude Code: `npm i -g @anthropic-ai/claude-code` (Codex / Aider / OpenCode / Gemini also supported · see [docs/integrations/](../docs/integrations/) for Cursor MCP setup) |
29
- | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/CONTEXT.md`, `HANDOVER.md`, `PROMPTS.md` |
30
- | 6 | **Fill in CONTEXT.md** | 5 minutes. The agent will follow what's written there. |
29
+ | 5 | **Scaffold this repo** | `cd /your/repo && codehere init` — creates `.codehere/` with the F3 memory tree (working / episodic / semantic / personal / candidates) plus `AGENTS.md` (project briefing) and `skills/` (prompt templates) |
30
+ | 6 | **Fill in AGENTS.md** | 5 minutes. The agent will follow what's written there. |
31
+ | 7 | **Build the symbol map (optional)** | `codehere memory repo-map` — JSON map of every top-level export in your repo, ranked by reference count. Spawned agents read it on entry instead of cold-grepping. |
31
32
 
32
33
  You're done. `codehere start` opens the web UI at `:3773`; you delegate from there.
33
34
 
@@ -43,11 +44,20 @@ Context + End State + Constraints
43
44
 
44
45
  ### Folder convention (what `codehere init` writes)
45
46
 
46
- | File | Purpose | Committed? |
47
+ The v0.5 layout — F3 project memory:
48
+
49
+ | Path | Purpose | Committed? |
47
50
  |---|---|---|
48
- | `.codehere/CONTEXT.md` | What this repo is. Conventions. Style. Constraints. | yes |
49
- | `.codehere/HANDOVER.md` | Cross-session continuity (until F3 memory ships in v0.5). | **no** |
50
- | `.codehere/PROMPTS.md` | Your library of recurring task prompts. | yes |
51
+ | `.codehere/AGENTS.md` *(or legacy `CONTEXT.md`)* | What this repo is. Conventions. Style. Constraints. | yes |
52
+ | `.codehere/memory/working/session.md` *(or legacy `HANDOVER.md`)* | What's in flight in the current session. | **no** |
53
+ | `.codehere/memory/episodic/AGENT_LEARNINGS.jsonl` | Symlink to your full session history at `~/.codehere/sessions.jsonl`. | (symlink) |
54
+ | `.codehere/memory/episodic/repo_map.json` | Ranked symbol map. Built by `codehere memory repo-map`. | yes |
55
+ | `.codehere/memory/semantic/lessons.jsonl` | Project-specific lessons promoted from episodic patterns. | yes |
56
+ | `.codehere/memory/personal/preferences.json` | Per-developer prefs. | **no** |
57
+ | `.codehere/skills/<name>/SKILL.md` *(or legacy flat `PROMPTS.md`)* | Prompt templates — one folder per skill. | yes |
58
+ | `.codehere/protocols/permissions.md` + `hook_patterns.json` | Safety policy for the PreToolUse hook. | yes |
59
+
60
+ Upgrading from v0.4.1? Run `codehere memory init` once — it migrates `CONTEXT/HANDOVER/PROMPTS` into the new layout and leaves symlinks at the old paths so back-compat holds. `--dry-run` shows what would move first.
51
61
 
52
62
  ---
53
63
 
@@ -75,6 +85,52 @@ codehere status # quick dashboard · sessions, blocks, trust trends
75
85
 
76
86
  The hook intercepts every `Bash`, `Edit`, `Write`, `MultiEdit`, and `NotebookEdit` call from any Claude Code session running on your machine, runs it through the trust-scorer pattern bank in under 5 ms, blocks anything matching the destructive / credential / injection patterns, and appends every decision to an append-only JSONL audit log at `~/.codehere/audit/`. (Hook coverage today is Claude Code-only — Codex / Aider / OpenCode adapters spawn through their native CLIs and emit to `~/.codehere/sessions.jsonl`; full hook parity across all adapters is v0.4 work.)
77
87
 
88
+ ## Memory · F3 (v0.5)
89
+
90
+ Once you've run `codehere init` in a project, every spawned agent — Claude Code, Codex, Aider, OpenCode, or Gemini — automatically reads the project's memory on entry. The agent starts warm with the briefing, recent lessons, and the most-referenced symbols in your codebase, instead of cold-grepping every session.
91
+
92
+ **What gets injected, in priority order:**
93
+ 1. **`.codehere/AGENTS.md`** — your project briefing (conventions, style, constraints)
94
+ 2. **`.codehere/memory/working/session.md`** — what's in flight right now (per-machine, gitignored)
95
+ 3. **`.codehere/memory/semantic/lessons.jsonl`** — graduated project-wide lessons
96
+ 4. **`.codehere/memory/episodic/repo_map.json`** — top-N most-referenced symbols (run `codehere memory repo-map` to build / refresh)
97
+
98
+ The injection is token-budget-aware (default ~3000 tokens, drops lower-priority sections first to fit; AGENTS.md is never dropped). Pass `--no-memory-prefix` if you want the bare prompt for a one-off task.
99
+
100
+ **Inspect what would inject** before spawning anything:
101
+ ```bash
102
+ codehere memory show injection
103
+ ```
104
+
105
+ **Add a lesson** so future spawns learn from it:
106
+ ```bash
107
+ codehere memory graduate --add "auth tests live alongside source files"
108
+ ```
109
+
110
+ **See everything codehere has accumulated:**
111
+ ```bash
112
+ codehere memory show # one-line summary per layer
113
+ codehere memory show semantic # numbered lesson list
114
+ codehere memory show episodic # session count + top symbols
115
+ codehere memory show protocols # safety patterns by category
116
+ ```
117
+
118
+ **Other memory commands:**
119
+ - `codehere memory init` — migrate the v0.4.1 layout (`CONTEXT.md` / `HANDOVER.md` / `PROMPTS.md`) into the F3 layout (one shot, leaves back-compat symlinks; `--dry-run` previews)
120
+ - `codehere memory repo-map [--rebuild]` — build the symbol map; v0.5 ships a TS-native MVP, v0.6 swaps in tree-sitter
121
+ - `codehere protocols export [--rebuild]` — write the trust-scorer pattern bank to `.codehere/protocols/hook_patterns.json` for inspection / cross-tool ingestion
122
+
123
+ ## Going deeper
124
+
125
+ For technical readers — the living documents that describe codehere as a system, not just a CLI:
126
+
127
+ | Doc | What it is |
128
+ |---|---|
129
+ | [`docs/decisions/`](../docs/decisions/) | ADR registry — every locked architectural decision (audit-first moat, supervisor-agent positioning, local-first, F3 memory layout, generic-CLI adapter, ...) numbered, dated, with rejected alternatives + rollback triggers |
130
+ | [`docs/THREAT_MODEL.md`](../docs/THREAT_MODEL.md) | Every attack surface, current mitigation, residual risk, planned hardening — npm supply chain, hook bypass, symlink redirect, audit log tampering, MCP server, prompt injection |
131
+ | [`docs/CAPABILITIES.md`](../docs/CAPABILITIES.md) | Honest matrix of what each adapter can and cannot do today — Claude Code, Codex, Aider, OpenCode, Gemini, Generic CLI — across spawn, stream, per-tool block, audit, F3 memory, auth |
132
+ | [`agent/REPO_MAP.md`](./REPO_MAP.md) | One-line-per-file index of the 54 source files with their top-level exports — generated, regenerated via `npm run repomap` after renames |
133
+
78
134
  ## Commands
79
135
 
80
136
  ```
@@ -129,6 +185,7 @@ For depth beyond this README:
129
185
  - **[CODEHERE.md](../CODEHERE.md)** · product spec · positioning, six-ring adoption funnel, F0–F6 foundations, defensibility, GTM, revenue model
130
186
  - **[EXPERIENCE.md](../EXPERIENCE.md)** · the human story · how engineering teams, CISOs, and regulated-industry buyers adopt codehere
131
187
  - **[AUDIT.md](../AUDIT.md)** · pre-publish audit report · methodology, findings, remediation
188
+ - **[docs/SECURITY_AUDIT_2026-05-01.md](../docs/SECURITY_AUDIT_2026-05-01.md)** · first-party security audit · OWASP Top 10 + STRIDE · 0 critical / 0 high · 2 medium fixed inline · methodology disclosed · reproducible
132
189
  - **[CONTRIBUTING.md](../CONTRIBUTING.md)** · how to contribute · setup, commit discipline, testing philosophy
133
190
  - **[SECURITY.md](../SECURITY.md)** · responsible disclosure · `security@codehere.uk`
134
191
  - **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)** · community standards
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ import { join, dirname } from 'path';
16
16
  import { fileURLToPath } from 'url';
17
17
  import { executeSetupCommand } from './presentation/cli/commands/setup-command.js';
18
18
  import { executeInitCommand } from './presentation/cli/commands/init-command.js';
19
+ import { executeMemoryCommand } from './presentation/cli/commands/memory-command.js';
20
+ import { executeProtocolsCommand } from './presentation/cli/commands/protocols-command.js';
19
21
  import { executeConfigCommand } from './presentation/cli/commands/config-command.js';
20
22
  import { executeHelpCommand } from './presentation/cli/commands/help-command.js';
21
23
  import { executeStatusCommand } from './presentation/cli/commands/status-command.js';
@@ -103,6 +105,47 @@ program
103
105
  .action((options) => {
104
106
  executeInitCommand({ force: options.force, noGitignore: !options.gitignore });
105
107
  });
108
+ // ── memory ───────────────────────────────────────────────────────────────────
109
+ // F3 v0.5: project memory operations. `memory init` migrates v0.4.1 layout
110
+ // (CONTEXT/HANDOVER/PROMPTS) into the four-layer F3 layout (AGENTS.md +
111
+ // memory/ + skills/). `memory repo-map` builds a JSON symbol map. Future:
112
+ // memory show, memory graduate.
113
+ program
114
+ .command('memory')
115
+ .description('F3 project memory · init / repo-map / show / graduate / help')
116
+ .argument('[action]', 'Action: init, repo-map, show, graduate, help')
117
+ .argument('[layer]', 'Layer for `memory show` (working / episodic / semantic / personal / skills / protocols / injection)')
118
+ .option('-f, --force', 'Overwrite existing target files (memory init)')
119
+ .option('--no-symlinks', 'Delete old paths instead of leaving back-compat symlinks (memory init)')
120
+ .option('--dry-run', 'Print planned moves without touching disk (memory init)')
121
+ .option('--rebuild', 'Force a fresh build (memory repo-map)')
122
+ .option('--add <text>', 'Add a free-text lesson directly (memory graduate)')
123
+ .option('--accept <id>', 'Graduate a candidate by id (memory graduate)')
124
+ .option('--reject <id>', 'Reject a candidate by id without graduating (memory graduate)')
125
+ .action((action, layer, options) => {
126
+ executeMemoryCommand(action, {
127
+ force: options.force,
128
+ noSymlinks: !options.symlinks,
129
+ dryRun: options.dryRun,
130
+ rebuild: options.rebuild,
131
+ layer,
132
+ add: options.add,
133
+ accept: options.accept,
134
+ reject: options.reject,
135
+ });
136
+ });
137
+ // ── protocols ────────────────────────────────────────────────────────────────
138
+ // F3 v0.5: protocols layer operations. `protocols export` writes the
139
+ // trust-scorer pattern bank to .codehere/protocols/hook_patterns.json so
140
+ // auditors / cross-tool consumers can read codehere's safety surface.
141
+ program
142
+ .command('protocols')
143
+ .description('F3 protocols layer · export (write hook_patterns.json from trust-scorer bank)')
144
+ .argument('[action]', 'Action: export, help')
145
+ .option('--rebuild', 'Refresh built-in patterns (project patterns are preserved)')
146
+ .action((action, options) => {
147
+ executeProtocolsCommand(action, { rebuild: options.rebuild });
148
+ });
106
149
  // ── config ───────────────────────────────────────────────────────────────────
107
150
  program
108
151
  .command('config')
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * Docs: https://docs.anthropic.com/en/docs/claude-code/hooks
15
15
  */
16
- import { appendFileSync, existsSync, mkdirSync } from 'fs';
16
+ import { appendFileSync, chmodSync, existsSync, mkdirSync } from 'fs';
17
17
  import { join } from 'path';
18
18
  import { homedir } from 'os';
19
19
  const PATTERNS = [
@@ -392,9 +392,24 @@ export function appendAuditLog(entry) {
392
392
  try {
393
393
  const dir = AUDIT_FILE.substring(0, AUDIT_FILE.lastIndexOf('/'));
394
394
  if (dir && !existsSync(dir)) {
395
- mkdirSync(dir, { recursive: true });
395
+ // mode 0o700 — owner-only directory. The audit log records every tool call
396
+ // including bash commands and file paths; on multi-user systems a default
397
+ // 0o755 dir would let other users walk in and read the file. Surfaced by
398
+ // the May 1 2026 /cso security audit (Finding #1, MEDIUM).
399
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
400
+ }
401
+ // mode 0o600 — owner-only file. Same reasoning as the dir above. Only takes
402
+ // effect on first write (file creation); existing files are chmod'd below
403
+ // for backwards compatibility with audit logs created before this fix.
404
+ appendFileSync(AUDIT_FILE, JSON.stringify(entry) + '\n', { mode: 0o600 });
405
+ // Idempotent: if the file existed pre-fix with 0o644, tighten it now. The
406
+ // chmod is cheap and silent on already-0o600 files.
407
+ try {
408
+ chmodSync(AUDIT_FILE, 0o600);
409
+ }
410
+ catch {
411
+ /* fall through — chmod failure shouldn't block the audit append */
396
412
  }
397
- appendFileSync(AUDIT_FILE, JSON.stringify(entry) + '\n');
398
413
  }
399
414
  catch {
400
415
  // Audit log failure must not block tool execution
@@ -16,6 +16,7 @@ import { spawn, execFileSync } from 'child_process';
16
16
  import { EventEmitter } from 'events';
17
17
  import { randomUUID } from 'crypto';
18
18
  import { ProgressAggregator } from './progress-aggregator.js';
19
+ import { applyMemoryPrefix } from './memory-injector.js';
19
20
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
20
21
  const HEADLESS_FLAGS = [
21
22
  '--yes-always', // auto-accept all file-edit confirmations
@@ -70,8 +71,16 @@ export function spawnAider(prompt, options = {}) {
70
71
  let statusValue = 'running';
71
72
  let killRequested = false;
72
73
  const cwd = options.cwd ?? process.cwd();
74
+ // F3 Task 8 — memory injection (same shape as every other adapter).
75
+ const injected = applyMemoryPrefix(prompt, cwd, options);
76
+ const finalPrompt = injected.prompt;
73
77
  const snapshot = snapshotCwd(cwd);
74
- const aggregator = new ProgressAggregator('aider'); // no-op aggregator; emits no steps
78
+ // ProgressAggregator parses aider's stdout for `Running ...`, `Applied edit
79
+ // to ...`, and `Creating empty file ...` lines and emits Run/Edit/Write
80
+ // ProgressSteps. Observability parity (Path B) — see ingestAider() in
81
+ // progress-aggregator.ts. Blocking parity (Path A) needs the upstream
82
+ // `--pre-action-hook` PR.
83
+ const aggregator = new ProgressAggregator('aider');
75
84
  let stepsSnapshot = [];
76
85
  let artifactsSnapshot;
77
86
  let textAccum = '';
@@ -139,7 +148,7 @@ export function spawnAider(prompt, options = {}) {
139
148
  const spawnEnv = { ...process.env, ...(options.env ?? {}) };
140
149
  let child;
141
150
  try {
142
- child = spawn('aider', ['--message', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
151
+ child = spawn('aider', ['--message', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
143
152
  cwd,
144
153
  env: spawnEnv,
145
154
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -225,6 +234,7 @@ export function spawnAider(prompt, options = {}) {
225
234
  startedAt,
226
235
  prompt,
227
236
  agent: 'aider',
237
+ memoryInjection: injected.record,
228
238
  get status() {
229
239
  return statusValue;
230
240
  },
@@ -19,6 +19,7 @@ import { EventEmitter } from 'events';
19
19
  import { randomUUID } from 'crypto';
20
20
  import { ProgressAggregator } from './progress-aggregator.js';
21
21
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
22
+ import { applyMemoryPrefix } from './memory-injector.js';
22
23
  // Runtime-error fingerprints → structured codehere:error events with recovery hints.
23
24
  // Matched against any stderr line or assistant-text content we see on the stream.
24
25
  const ERROR_PATTERNS = [
@@ -78,6 +79,12 @@ export function spawnClaudeCode(prompt, options = {}) {
78
79
  let killRequested = false;
79
80
  // PM UX Layer 2+3 state. Shared helper modules; per-session instances.
80
81
  const cwd = options.cwd ?? process.cwd();
82
+ // F3 Task 8 — inject project memory (AGENTS.md + lessons + repo_map slice)
83
+ // into the prompt prefix unless the caller opted out. Pure, side-effect-
84
+ // free; returns unchanged prompt + record=undefined when there's no
85
+ // .codehere/ at cwd.
86
+ const injected = applyMemoryPrefix(prompt, cwd, options);
87
+ const finalPrompt = injected.prompt;
81
88
  const snapshot = snapshotCwd(cwd);
82
89
  const aggregator = new ProgressAggregator('claude-code');
83
90
  let stepsSnapshot = [];
@@ -176,7 +183,7 @@ export function spawnClaudeCode(prompt, options = {}) {
176
183
  }
177
184
  let child;
178
185
  try {
179
- child = spawn('claude', ['-p', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
186
+ child = spawn('claude', ['-p', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
180
187
  cwd,
181
188
  env: spawnEnv,
182
189
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -277,6 +284,7 @@ export function spawnClaudeCode(prompt, options = {}) {
277
284
  startedAt,
278
285
  prompt,
279
286
  agent: 'claude-code',
287
+ memoryInjection: injected.record,
280
288
  get status() {
281
289
  return statusValue;
282
290
  },
@@ -18,6 +18,7 @@ import { EventEmitter } from 'events';
18
18
  import { randomUUID } from 'crypto';
19
19
  import { ProgressAggregator } from './progress-aggregator.js';
20
20
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
21
+ import { applyMemoryPrefix } from './memory-injector.js';
21
22
  const HEADLESS_FLAGS = [
22
23
  'exec',
23
24
  '--json',
@@ -73,6 +74,9 @@ export function spawnCodex(prompt, options = {}) {
73
74
  // PM UX Layer 2+3 state — same shape as claude-code-adapter. Kept in-file (not
74
75
  // extracted to a base class) because the two CLIs diverge in more than they share.
75
76
  const cwd = options.cwd ?? process.cwd();
77
+ // F3 Task 8 — memory injection (same shape as every other adapter).
78
+ const injected = applyMemoryPrefix(prompt, cwd, options);
79
+ const finalPrompt = injected.prompt;
76
80
  const snapshot = snapshotCwd(cwd);
77
81
  const aggregator = new ProgressAggregator('codex');
78
82
  let stepsSnapshot = [];
@@ -151,7 +155,7 @@ export function spawnCodex(prompt, options = {}) {
151
155
  env: spawnEnv,
152
156
  stdio: ['pipe', 'pipe', 'pipe'],
153
157
  });
154
- child.stdin?.write(prompt);
158
+ child.stdin?.write(finalPrompt);
155
159
  child.stdin?.end();
156
160
  }
157
161
  catch (err) {
@@ -241,6 +245,7 @@ export function spawnCodex(prompt, options = {}) {
241
245
  startedAt,
242
246
  prompt,
243
247
  agent: 'codex',
248
+ memoryInjection: injected.record,
244
249
  get status() {
245
250
  return statusValue;
246
251
  },
@@ -284,8 +284,17 @@ function spawnConductor(prompt, options, lookup) {
284
284
  let artifactsSnapshot;
285
285
  specialistSession.subscribe((evt) => {
286
286
  buffer.push(evt);
287
- emitter.emit('event', evt);
288
- // Mirror progress + artifacts so PM-UX layers see them on the Conductor session, too.
287
+ // Mirror progress + artifacts BEFORE re-emitting so any subscriber that
288
+ // reads `session.status` / `session.steps` / `session.artifacts` from
289
+ // inside its event handler sees the up-to-date conductor state.
290
+ //
291
+ // BUG 2 (zombie RUNNING sessions, May 1 2026, 138/155 records affected):
292
+ // pre-fix this block emitted FIRST and updated status SECOND. session-
293
+ // manager.ts subscribes to the conductor session and persists `session.status`
294
+ // synchronously inside its on-exit handler. With emit-before-update,
295
+ // every conductor session was persisted with status='running' even though
296
+ // the underlying specialist had already finished. The fix reorders so the
297
+ // emit() at the end sees the freshly-mirrored statusValue.
289
298
  if (specialistSession.steps)
290
299
  stepsSnapshot = specialistSession.steps;
291
300
  if (specialistSession.artifacts)
@@ -298,6 +307,7 @@ function spawnConductor(prompt, options, lookup) {
298
307
  if (evt.type === 'exit' || evt.type === 'codehere:complete' || evt.type === 'codehere:error') {
299
308
  statusValue = specialistSession.status;
300
309
  }
310
+ emitter.emit('event', evt);
301
311
  });
302
312
  return {
303
313
  sessionId,
@@ -31,7 +31,11 @@
31
31
  import { spawn, execFileSync } from 'child_process';
32
32
  import { EventEmitter } from 'events';
33
33
  import { randomUUID } from 'crypto';
34
+ import { existsSync } from 'fs';
35
+ import { homedir } from 'os';
36
+ import { join } from 'path';
34
37
  import { ProgressAggregator } from './progress-aggregator.js';
38
+ import { applyMemoryPrefix } from './memory-injector.js';
35
39
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
36
40
  // `--approval-mode=yolo` auto-accepts every tool action without prompting (the
37
41
  // older `--yolo` flag is deprecated). codehere's safety hook is the external
@@ -91,6 +95,9 @@ export function spawnGemini(prompt, options = {}) {
91
95
  let statusValue = 'running';
92
96
  let killRequested = false;
93
97
  const cwd = options.cwd ?? process.cwd();
98
+ // F3 Task 8 — memory injection (same shape as every other adapter).
99
+ const injected = applyMemoryPrefix(prompt, cwd, options);
100
+ const finalPrompt = injected.prompt;
94
101
  const snapshot = snapshotCwd(cwd);
95
102
  const aggregator = new ProgressAggregator('gemini');
96
103
  let stepsSnapshot = [];
@@ -187,7 +194,7 @@ export function spawnGemini(prompt, options = {}) {
187
194
  };
188
195
  let child;
189
196
  try {
190
- child = spawn('gemini', ['-p', prompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
197
+ child = spawn('gemini', ['-p', finalPrompt, ...HEADLESS_FLAGS, ...(options.extraArgs ?? [])], {
191
198
  cwd,
192
199
  env: spawnEnv,
193
200
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -301,6 +308,7 @@ export function spawnGemini(prompt, options = {}) {
301
308
  startedAt,
302
309
  prompt,
303
310
  agent: 'gemini',
311
+ memoryInjection: injected.record,
304
312
  get status() {
305
313
  return statusValue;
306
314
  },
@@ -379,10 +387,38 @@ export const GeminiAdapter = {
379
387
  },
380
388
  };
381
389
  }
382
- // Gemini supports GEMINI_API_KEY (free tier at aistudio.google.com), GOOGLE_API_KEY,
383
- // Vertex AI (GOOGLE_GENAI_USE_VERTEXAI=true), GCA (GOOGLE_GENAI_USE_GCA=true), or
384
- // cached Google login. Can't verify without a roundtrip — let runtime catch
385
- // real failures via ERROR_PATTERNS.auth_missing on the result.error event.
390
+ // Gemini supports several auth paths:
391
+ // env: GEMINI_API_KEY (free tier), GOOGLE_API_KEY, GOOGLE_GENAI_USE_VERTEXAI,
392
+ // GOOGLE_GENAI_USE_GCA, GOOGLE_APPLICATION_CREDENTIALS
393
+ // file: ~/.gemini/settings.json (the file gemini's own error msg points at)
394
+ // file: ~/.gemini/oauth_creds.json (cached Google login)
395
+ //
396
+ // Pre-May-1 we returned 'ready' on install alone, deferring to runtime to
397
+ // catch missing-auth via ERROR_PATTERNS.auth_missing. That was dishonest:
398
+ // /api/readiness/agents would say 'ready' → user would spawn → 41s later
399
+ // the task errored with no clear next step. Surfaced May 1 2026 by the
400
+ // multi-agent dogfood test (see SECURITY_AUDIT_2026-05-01.md spirit —
401
+ // honest readiness signals are a UX security property).
402
+ const hasEnvAuth = !!process.env.GEMINI_API_KEY ||
403
+ !!process.env.GOOGLE_API_KEY ||
404
+ !!process.env.GOOGLE_GENAI_USE_VERTEXAI ||
405
+ !!process.env.GOOGLE_GENAI_USE_GCA ||
406
+ !!process.env.GOOGLE_APPLICATION_CREDENTIALS;
407
+ const hasFileAuth = existsSync(join(homedir(), '.gemini', 'settings.json')) ||
408
+ existsSync(join(homedir(), '.gemini', 'oauth_creds.json'));
409
+ if (!hasEnvAuth && !hasFileAuth) {
410
+ return {
411
+ id: this.id,
412
+ label: this.label,
413
+ state: 'needs_auth',
414
+ installed: true,
415
+ authHint: {
416
+ command: 'export GEMINI_API_KEY=... # free tier: https://aistudio.google.com/apikey',
417
+ url: 'https://google-gemini.github.io/gemini-cli/docs/get-started/authentication.html',
418
+ detail: 'Gemini found on PATH but no auth detected (no GEMINI_API_KEY env var, no ~/.gemini/settings.json, no ~/.gemini/oauth_creds.json). Free tier available at aistudio.google.com/apikey.',
419
+ },
420
+ };
421
+ }
386
422
  return { id: this.id, label: this.label, state: 'ready', installed: true };
387
423
  },
388
424
  };
@@ -37,6 +37,7 @@ import { join } from 'path';
37
37
  import { randomUUID } from 'crypto';
38
38
  import { ProgressAggregator } from './progress-aggregator.js';
39
39
  import { snapshotCwd, extractArtifacts } from './artifacts-extractor.js';
40
+ import { applyMemoryPrefix } from './memory-injector.js';
40
41
  const CONFIG_PATH = join(homedir(), '.codehere', 'agents.yaml');
41
42
  const CONFIG_PATH_JSON = join(homedir(), '.codehere', 'agents.json');
42
43
  /** Minimal YAML parser for the narrow shape above. Intentionally NOT a full YAML
@@ -134,6 +135,12 @@ function spawnGeneric(cfg, prompt, options = {}) {
134
135
  let statusValue = 'running';
135
136
  let killRequested = false;
136
137
  const cwd = options.cwd ?? process.cwd();
138
+ // F3 Task 8 — memory injection (same shape as every other adapter).
139
+ // Apr 29: caught via the adapter-injection-wiring trip-wire — generic
140
+ // adapters were silently exempt from F3 even though Task 8 said
141
+ // "every spawned agent." Now consistent.
142
+ const injected = applyMemoryPrefix(prompt, cwd, options);
143
+ const finalPrompt = injected.prompt;
137
144
  const snapshot = snapshotCwd(cwd);
138
145
  const aggregator = new ProgressAggregator(cfg.id);
139
146
  let stepsSnapshot = [];
@@ -155,7 +162,7 @@ function spawnGeneric(cfg, prompt, options = {}) {
155
162
  const spawnEnv = { ...process.env, ...(cfg.env ?? {}), ...(options.env ?? {}) };
156
163
  const usesStdin = cfg.prompt_via === 'stdin';
157
164
  const fixedArgs = cfg.args ?? [];
158
- const argv = usesStdin ? [...fixedArgs] : [...fixedArgs, prompt];
165
+ const argv = usesStdin ? [...fixedArgs] : [...fixedArgs, finalPrompt];
159
166
  let child;
160
167
  try {
161
168
  // SAFE · array form (not shell:true) means no shell interpretation.
@@ -172,7 +179,7 @@ function spawnGeneric(cfg, prompt, options = {}) {
172
179
  stdio: [usesStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
173
180
  });
174
181
  if (usesStdin) {
175
- child.stdin?.write(prompt);
182
+ child.stdin?.write(finalPrompt);
176
183
  child.stdin?.end();
177
184
  }
178
185
  }
@@ -264,6 +271,7 @@ function spawnGeneric(cfg, prompt, options = {}) {
264
271
  startedAt,
265
272
  prompt,
266
273
  agent: cfg.id,
274
+ memoryInjection: injected.record,
267
275
  get status() {
268
276
  return statusValue;
269
277
  },