codehere 1.0.1 → 1.0.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.
Files changed (163) hide show
  1. package/README.md +36 -192
  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,209 +0,0 @@
1
- /**
2
- * graphify-context — shared structural memory layer (P1).
3
- *
4
- * Wraps `graphify-ts` CLI to compile compact context packs for agent prompts.
5
- * Every spawn can optionally get a graphify context pack that replaces
6
- * raw file reads + unstructured JSONL scans.
7
- *
8
- * All functions are synchronous (execFileSync) to slot into the sync
9
- * spawn() call chain without breaking the API.
10
- *
11
- * P1 integration points:
12
- * - Session spawn: inject context pack into prompt prefix
13
- * - Failover handoff (P2): replacement agent inherits the same pack
14
- * - S7 compaction (future): use graph to prune stale session logs
15
- * - PM-brain routing (future): community_overview for agent selection
16
- */
17
- import { execFileSync } from 'child_process';
18
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
19
- import { join } from 'path';
20
- import { randomUUID } from 'crypto';
21
- // ── Constants ──────────────────────────────────────────────────────────────────
22
- const GRAPHIFY_OUT = 'graphify-out';
23
- const GRAPH_JSON = 'graph.json';
24
- const CONTEXT_PACKS_DIR = '.codehere';
25
- const CONTEXT_PACKS_FILE = 'context-packs.jsonl';
26
- // ── Helpers ────────────────────────────────────────────────────────────────────
27
- function getGraphPath(cwd) {
28
- return join(cwd, GRAPHIFY_OUT, GRAPH_JSON);
29
- }
30
- function getGraphStats(cwd) {
31
- const graphPath = getGraphPath(cwd);
32
- try {
33
- if (!existsSync(graphPath)) {
34
- return { nodeCount: 0, edgeCount: 0 };
35
- }
36
- const graphJson = JSON.parse(readFileSync(graphPath, 'utf-8'));
37
- return {
38
- nodeCount: graphJson.nodes?.length ?? 0,
39
- edgeCount: graphJson.edges?.length ?? 0,
40
- };
41
- }
42
- catch {
43
- return { nodeCount: 0, edgeCount: 0 };
44
- }
45
- }
46
- // ── Graph management (sync) ────────────────────────────────────────────────────
47
- /**
48
- * Build or update the knowledge graph for a workspace directory.
49
- * Writes to <cwd>/graphify-out/graph.json (graphify-ts default).
50
- * Synchronous — runs synchronously so it can be called from spawn().
51
- */
52
- export function buildGraphSync(cwd, { incremental = true } = {}) {
53
- const startTime = Date.now();
54
- const graphPath = getGraphPath(cwd);
55
- const args = ['generate', cwd];
56
- if (incremental && existsSync(graphPath)) {
57
- args.push('--update');
58
- }
59
- try {
60
- // SECURITY (2026-08-08) · `--no-install` is load-bearing, do not drop it.
61
- // This shelled out to bare `npx graphify-ts`. The dependency is the SCOPED
62
- // @mohammednagy/graphify-ts, but npx resolves the BIN NAME and falls back to the
63
- // public registry when it is not installed locally — and an unrelated
64
- // `graphify-ts` (v0.2.0, publisher "howell5") exists on npm. Reproduced: with the
65
- // scoped package absent, npx downloaded that stranger's package into
66
- // ~/.npm/_npx and tried to run it, in the user's repo cwd, with stdio ignored so
67
- // nothing surfaced, and the catch below swallowed the failure as "degrade
68
- // gracefully". It only failed to EXECUTE because that package's bin is named
69
- // `graphify`, which is luck rather than safety. `--no-install` makes npx use a
70
- // locally-installed bin or fail — never fetch. Dependency confusion is not a
71
- // thing a product that sells provenance gets to ship.
72
- execFileSync('npx', ['--no-install', 'graphify-ts', ...args], {
73
- cwd,
74
- stdio: 'ignore',
75
- timeout: 120_000,
76
- });
77
- }
78
- catch {
79
- // Non-zero exit is fine — graphify-ts returns 1 when no files changed.
80
- // Also swallow ENOENT (graphify-ts not installed) — degrade gracefully.
81
- }
82
- return { elapsedMs: Date.now() - startTime };
83
- }
84
- // ── Context pack compilation (sync) ────────────────────────────────────────────
85
- /**
86
- * Compile a compact context pack for a prompt + workspace.
87
- * Synchronous — calls graphify-ts pack and returns the payload.
88
- */
89
- export function compileContextPackSync(prompt, cwd, { task = 'explain', budget = 3000 } = {}) {
90
- const startTime = Date.now();
91
- const graphPath = getGraphPath(cwd);
92
- // Build graph if it doesn't exist yet
93
- if (!existsSync(graphPath)) {
94
- buildGraphSync(cwd, { incremental: false });
95
- }
96
- // Degrade gracefully if graph still doesn't exist
97
- if (!existsSync(graphPath)) {
98
- return null;
99
- }
100
- try {
101
- const output = execFileSync('npx', ['--no-install', 'graphify-ts', 'pack', prompt, '--graph', graphPath, '--task', task, '--budget', String(budget)], {
102
- cwd,
103
- encoding: 'utf-8',
104
- maxBuffer: 512 * 1024,
105
- timeout: 30_000,
106
- });
107
- const { nodeCount, edgeCount } = getGraphStats(cwd);
108
- return {
109
- graphPath,
110
- payload: output.trim(),
111
- budget,
112
- task,
113
- nodeCount,
114
- edgeCount,
115
- elapsedMs: Date.now() - startTime,
116
- packId: randomUUID(),
117
- };
118
- }
119
- catch {
120
- // graphify-ts may return non-zero or hit timeout — degrade gracefully.
121
- return null;
122
- }
123
- }
124
- /**
125
- * Compile a context pack from a session's final state (prompt + artifacts + steps).
126
- * Used for P1 session-exit persistence and P2 handoff enrichment.
127
- */
128
- export function compileSessionPackSync(cwd, { prompt, artifacts, steps, agent }) {
129
- // Combine prompt + artifacts + steps into a rich prompt for the pack
130
- const sessionContext = [
131
- prompt,
132
- agent ? `Agent: ${agent}` : undefined,
133
- artifacts ? `Artifacts:\n${artifacts}` : undefined,
134
- steps ? `Steps taken:\n${steps}` : undefined,
135
- ]
136
- .filter(Boolean)
137
- .join('\n\n');
138
- return compileContextPackSync(sessionContext, cwd, { task: 'impact', budget: 2500 });
139
- }
140
- // ── Context pack persistence ───────────────────────────────────────────────────
141
- /**
142
- * Persist a context pack to disk for cross-session handoff (P2).
143
- * Returns the path to the packs file.
144
- */
145
- export function persistContextPack(pack, cwd) {
146
- const packsDir = join(cwd, CONTEXT_PACKS_DIR);
147
- if (!existsSync(packsDir)) {
148
- mkdirSync(packsDir, { recursive: true });
149
- }
150
- const packsFile = join(packsDir, CONTEXT_PACKS_FILE);
151
- const entry = {
152
- packId: pack.packId,
153
- timestamp: Date.now(),
154
- task: pack.task,
155
- nodeCount: pack.nodeCount,
156
- edgeCount: pack.edgeCount,
157
- graphPath: pack.graphPath,
158
- payloadPreview: pack.payload.slice(0, 500), // truncate for log
159
- payloadLength: pack.payload.length,
160
- };
161
- writeFileSync(packsFile, JSON.stringify(entry) + '\n', { flag: 'a' });
162
- return packsFile;
163
- }
164
- // ── Prompt enrichment (sync) ───────────────────────────────────────────────────
165
- /**
166
- * Prepend a context pack to a prompt if graphify-ts is available.
167
- *
168
- * Synchronous — can be called directly from spawn().
169
- * Returns the original prompt if graphify-ts fails or is unavailable.
170
- *
171
- * Respects CODEHERE_GRAPHIFY env var:
172
- * - unset or "auto" (default): enrich ONLY from a pre-existing graph/pack;
173
- * NEVER compile on the spawn hot path (root-cause fix 2026-06-02 for
174
- * "every task calls graphify" — `npx graphify-ts pack` was shelling out
175
- * synchronously on every spawn, slowing crucial tasks + prepending a
176
- * verbose context blob the user never asked for).
177
- * - "force": compile a fresh pack on spawn (explicit opt-in for the cost).
178
- * - "off": skip entirely.
179
- */
180
- export function enrichPromptWithPackSync(prompt, cwd, { force = false } = {}) {
181
- const mode = process.env.CODEHERE_GRAPHIFY ?? 'auto';
182
- if (mode === 'off')
183
- return prompt;
184
- const forceCompile = force || mode === 'force';
185
- // 'auto' must NEVER touch the user's prompt — not by compiling (npx subprocess
186
- // on the hot path) AND not by prepending an existing pack's "[graphify context:
187
- // …]" reference blob. That blob leaked into the visible prompt, the Final
188
- // Report summary, and the conversation ("On it — [graphify context: 1017
189
- // nodes…]") — user-reported 2026-06-02 with a graph present. Structural
190
- // context enrichment is now 'force'-only opt-in; a normal task gets a clean prompt.
191
- if (!forceCompile)
192
- return prompt;
193
- // Compile a fresh pack (force only)
194
- // Compile a fresh pack (force only)
195
- const pack = compileContextPackSync(prompt, cwd, { task: 'explain', budget: 2500 });
196
- if (!pack) {
197
- return prompt;
198
- }
199
- // Persist for P2 handoff
200
- persistContextPack(pack, cwd);
201
- // Prepend context pack as a system hint so the agent sees it first
202
- return [
203
- `[graphify-ts context pack — ${pack.nodeCount} nodes, ${pack.edgeCount} edges, ${pack.elapsedMs}ms]`,
204
- pack.payload,
205
- '---',
206
- prompt,
207
- ].join('\n\n');
208
- }
209
- //# sourceMappingURL=graphify-context.js.map
@@ -1,380 +0,0 @@
1
- /**
2
- * F3 Task 8 — memory injection into spawned-agent prompts.
3
- *
4
- * Builds a prompt prefix from the project's `.codehere/` memory layers
5
- * (AGENTS.md briefing + working/session.md + semantic/lessons.jsonl + a
6
- * slice of episodic/repo_map.json topSymbols) so spawned agents start
7
- * warm — with the project's conventions, in-flight context, accumulated
8
- * lessons, and the most-referenced symbols already in their context window.
9
- *
10
- * Pure, side-effect-free, fully unit-tested. Each adapter calls
11
- * `buildMemoryPrefix(projectRoot)` near the top of `spawn()`, prepends
12
- * the resulting prefix to the user's prompt, and exposes the metadata via
13
- * `TaskSession.memoryInjection`.
14
- *
15
- * Token budget is char-count-approximated (4 chars ≈ 1 token, the
16
- * Anthropic / OpenAI rule-of-thumb). Default cap is 3000 tokens —
17
- * generous but well under any realistic context window. When budget is
18
- * exceeded, sections are dropped in priority order:
19
- *
20
- * keep AGENTS.md — never dropped if user-edited (the briefing
21
- * is the primary value)
22
- * drop 3 repo_map slice — symbols are nice-to-have; the agent can
23
- * discover them via grep if missing
24
- * drop 2 lessons — useful but not load-bearing
25
- * drop 1 session.md — useful for hand-off but redundant if the
26
- * user re-states their task
27
- *
28
- * Default-seed detection skips placeholder content (the init seeds for
29
- * AGENTS.md / session.md). Same byte-equality-based detection pattern as
30
- * memory-command.ts (per feedback_implementation_patterns_v05.md Pattern #1).
31
- */
32
- import { existsSync, readFileSync, realpathSync, writeFileSync } from 'fs';
33
- import { join, dirname, resolve } from 'path';
34
- const DEFAULT_MAX_TOKENS = 3000;
35
- const DEFAULT_MAX_LESSONS = 10;
36
- const DEFAULT_MAX_SYMBOLS = 30;
37
- // 3 chars per token — conservative ratio per K2 Round 1 feedback. Code
38
- // tokenizes denser than prose under typical BPE, so the safer-defaults 4/1
39
- // rule-of-thumb underestimates by 1.5-2x for code-heavy prefixes (which
40
- // repo_map + lessons.jsonl frequently are). 3/1 stays well clear of model
41
- // per-message caps without dropping useful content from typical repos.
42
- const CHARS_PER_TOKEN = 3;
43
- /** Char-count → token estimate. Conservative — actual tokenization may differ. */
44
- function estimateTokens(s) {
45
- return Math.ceil(s.length / CHARS_PER_TOKEN);
46
- }
47
- /**
48
- * Detect whether the given file content is still the init-time placeholder.
49
- * If so, the user hasn't customized it — injecting the placeholder would
50
- * just confuse the agent with template instructions.
51
- *
52
- * Heuristic: presence of bracketed-instruction lines like `[One-paragraph
53
- * description...]` or the `> Per-machine, gitignored` boilerplate, AND no
54
- * substantive non-template content after the markers. Conservative — when
55
- * in doubt, INCLUDE rather than exclude (better to over-share than miss
56
- * the briefing).
57
- */
58
- function looksLikeInitSeed(content, kind) {
59
- if (kind === 'agents') {
60
- // The init AGENTS/CONTEXT seed has these distinctive bracketed placeholders.
61
- // If at least 2 of them are present, treat as untouched.
62
- const markers = [
63
- /\[One-paragraph description of what this repo does/,
64
- /\[How this codebase is organized/,
65
- /\[Tone for generated code\./,
66
- /\[Don't do X\. Always do Y\./,
67
- ];
68
- const hits = markers.filter((re) => re.test(content)).length;
69
- return hits >= 2;
70
- }
71
- if (kind === 'session') {
72
- // The init session.md seed has this header + structure
73
- const markers = [/^# Working session/m, /^> Per-machine, gitignored/m, /^## In flight\s*\n+- \[ \]\s*$/m];
74
- const hits = markers.filter((re) => re.test(content)).length;
75
- return hits >= 2;
76
- }
77
- return false;
78
- }
79
- /**
80
- * Read the project briefing. Search order (highest-priority first):
81
- * 1. <projectRoot>/AGENTS.md — agentic-stack convention; if the user
82
- * has this file at the repo root with real content, it IS their
83
- * canonical briefing and codehere must respect it (Bug 2 from the
84
- * Apr 28 EXP-2 dogfood on `~/code/clipa`).
85
- * 2. <codehereDir>/AGENTS.md — F3-canonical (codehere's own)
86
- * 3. <codehereDir>/CONTEXT.md — v0.4.1 back-compat
87
- *
88
- * Returns the body string + the path that was read (for the prefix
89
- * header), or null if no usable briefing exists at any tier (every
90
- * candidate is missing, empty, or looks like the init placeholder).
91
- */
92
- function readBriefing(projectRoot, codehereDir) {
93
- const candidates = [
94
- { path: join(projectRoot, 'AGENTS.md'), label: 'AGENTS.md (repo root)' },
95
- { path: join(codehereDir, 'AGENTS.md'), label: '.codehere/AGENTS.md' },
96
- { path: join(codehereDir, 'CONTEXT.md'), label: '.codehere/CONTEXT.md' },
97
- ];
98
- for (const c of candidates) {
99
- if (existsSync(c.path)) {
100
- try {
101
- const body = readFileSync(c.path, 'utf8').trim();
102
- if (body.length === 0)
103
- continue;
104
- if (looksLikeInitSeed(body, 'agents'))
105
- continue; // placeholder — try next
106
- return { body, sourcePath: c.label };
107
- }
108
- catch {
109
- continue;
110
- }
111
- }
112
- }
113
- return null;
114
- }
115
- /** Read working/session.md. Skip if seed-shaped or empty. */
116
- function readSession(codehereDir) {
117
- const p = join(codehereDir, 'memory/working/session.md');
118
- if (!existsSync(p))
119
- return null;
120
- try {
121
- const body = readFileSync(p, 'utf8').trim();
122
- if (body.length === 0)
123
- return null;
124
- if (looksLikeInitSeed(body, 'session'))
125
- return null;
126
- return body;
127
- }
128
- catch {
129
- return null;
130
- }
131
- }
132
- /** Read up to N lessons from semantic/lessons.jsonl. */
133
- function readLessons(codehereDir, n) {
134
- const p = join(codehereDir, 'memory/semantic/lessons.jsonl');
135
- if (!existsSync(p))
136
- return [];
137
- try {
138
- const body = readFileSync(p, 'utf8');
139
- return body
140
- .split('\n')
141
- .filter((line) => line.trim().length > 0)
142
- .slice(0, n)
143
- .map((line) => {
144
- try {
145
- const obj = JSON.parse(line);
146
- return typeof obj.text === 'string' ? obj.text : line;
147
- }
148
- catch {
149
- return line;
150
- }
151
- });
152
- }
153
- catch {
154
- return [];
155
- }
156
- }
157
- function readTopSymbols(codehereDir, n) {
158
- const p = join(codehereDir, 'memory/episodic/repo_map.json');
159
- if (!existsSync(p))
160
- return [];
161
- try {
162
- const body = JSON.parse(readFileSync(p, 'utf8'));
163
- if (!Array.isArray(body.topSymbols))
164
- return [];
165
- return body.topSymbols.slice(0, n);
166
- }
167
- catch {
168
- return [];
169
- }
170
- }
171
- /**
172
- * Format individual sections. Each returns `null` if empty/skipped.
173
- */
174
- function formatBriefing(briefing, sourcePath) {
175
- return `# Project briefing (${sourcePath})\n\n${briefing}\n`;
176
- }
177
- function formatSession(session) {
178
- return `# Current session scratch (.codehere/memory/working/session.md)\n\n${session}\n`;
179
- }
180
- function formatLessons(lessons) {
181
- const items = lessons.map((l) => `- ${l}`).join('\n');
182
- return `# Project lessons (.codehere/memory/semantic/lessons.jsonl)\n\n${items}\n`;
183
- }
184
- function formatSymbols(symbols) {
185
- const lines = symbols.map((s) => `- \`${s.name}\` — ${s.file}:${s.line} (${s.references} refs)`).join('\n');
186
- return `# Top symbols in this repo (.codehere/memory/episodic/repo_map.json)\n\n${lines}\n`;
187
- }
188
- /**
189
- * Build the memory-injection prefix for the project at `projectRoot`.
190
- *
191
- * If `.codehere/` doesn't exist, returns empty (nothing to inject).
192
- * Otherwise reads the four sections, formats each, fits to the token
193
- * budget by dropping low-priority sections first.
194
- */
195
- export function buildMemoryPrefix(projectRoot, options = {}) {
196
- const codehereDir = join(projectRoot, '.codehere');
197
- const hasCodehereDir = existsSync(codehereDir);
198
- const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
199
- const maxLessons = options.maxLessons ?? DEFAULT_MAX_LESSONS;
200
- const maxSymbols = options.maxSymbols ?? DEFAULT_MAX_SYMBOLS;
201
- // Briefing discovery is INDEPENDENT of .codehere/ presence — a pure
202
- // agentic-stack repo (root AGENTS.md, no .codehere/) is a valid
203
- // injection source on its own (codex Round 1 fix on Bug 2). Only
204
- // session/lessons/repo_map require the .codehere/ tree.
205
- const briefing = readBriefing(projectRoot, codehereDir);
206
- const session = hasCodehereDir ? readSession(codehereDir) : null;
207
- const lessons = hasCodehereDir ? readLessons(codehereDir, maxLessons) : [];
208
- const symbols = hasCodehereDir ? readTopSymbols(codehereDir, maxSymbols) : [];
209
- // Build candidate sections in PRIORITY order (most-essential first).
210
- // Drop direction is REVERSE — least-essential drops first.
211
- const candidates = [];
212
- if (briefing)
213
- candidates.push({ key: 'agents', body: formatBriefing(briefing.body, briefing.sourcePath) });
214
- if (session)
215
- candidates.push({ key: 'session', body: formatSession(session) });
216
- if (lessons.length > 0)
217
- candidates.push({ key: 'lessons', body: formatLessons(lessons) });
218
- if (symbols.length > 0)
219
- candidates.push({ key: 'repo_map', body: formatSymbols(symbols) });
220
- if (candidates.length === 0) {
221
- return { prefix: '', included: [], tokens: 0, truncated: false };
222
- }
223
- // Closing separator, always added at the end.
224
- const SEPARATOR = '\n---\n\n';
225
- const separatorTokens = estimateTokens(SEPARATOR);
226
- // Drop from least-essential (last) until everything fits in budget.
227
- let truncated = false;
228
- let kept = [...candidates];
229
- while (kept.length > 0) {
230
- const totalChars = kept.reduce((acc, c) => acc + c.body.length, 0);
231
- const totalTokens = estimateTokens(kept.map((c) => c.body).join('\n')) + separatorTokens;
232
- if (totalTokens <= maxTokens)
233
- break;
234
- // Drop the last (least-essential) section.
235
- kept.pop();
236
- truncated = true;
237
- void totalChars;
238
- }
239
- // Edge: even AGENTS alone is over budget — truncate AGENTS body itself.
240
- if (kept.length === 1 && estimateTokens(kept[0].body) + separatorTokens > maxTokens) {
241
- const room = (maxTokens - separatorTokens) * CHARS_PER_TOKEN;
242
- const truncatedBody = kept[0].body.slice(0, Math.max(0, room - 32)) + '\n... [truncated to fit]\n';
243
- kept = [{ key: kept[0].key, body: truncatedBody }];
244
- truncated = true;
245
- }
246
- if (kept.length === 0) {
247
- return { prefix: '', included: [], tokens: 0, truncated };
248
- }
249
- const prefix = kept.map((c) => c.body).join('\n') + SEPARATOR;
250
- return {
251
- prefix,
252
- included: kept.map((c) => c.key),
253
- tokens: estimateTokens(prefix),
254
- truncated,
255
- };
256
- }
257
- /**
258
- * Walk up from `startPath` looking for the nearest ancestor (inclusive)
259
- * that codehere can inject memory FROM — meaning EITHER:
260
- * - has a `.codehere/` directory (codehere-initialized project), OR
261
- * - has a root-level `AGENTS.md` (agentic-stack convention)
262
- *
263
- * Returns the REAL filesystem ancestor path (symlinks resolved), or
264
- * `null` if no such ancestor exists.
265
- *
266
- * codex Round 1 BLOCKER fix: spawning a task from a subdirectory (e.g.
267
- * `cd src/auth && codehere task ...`) would miss the project root if we
268
- * only used the immediate cwd. Hence the upward walk.
269
- *
270
- * codex Round 2 BLOCKER fix: the upward walk must walk the REAL
271
- * filesystem ancestry, not the LEXICAL one. Without `realpathSync`,
272
- * `cwd` inside a symlinked subtree ascends through the lexical path and
273
- * misses the real project root. Real scenario for iCloud-symlinked /
274
- * shared-volume / mounted-drive projects.
275
- *
276
- * Bug 2 Round 2 BLOCKER fix: also accept a project root that has a
277
- * root-level `AGENTS.md` even WITHOUT `.codehere/`. A pure agentic-stack
278
- * repo (clipa-style) is a valid injection source on its own.
279
- *
280
- * Stops at the filesystem root. Symlink-following on read is the right
281
- * asymmetry per ADR-012 (the symlink defense applies to writes, not reads).
282
- *
283
- * Exported for the daemon's /api/memory/status route (the room's init nudge
284
- * must report the SAME readiness the injector itself would act on).
285
- */
286
- export function findProjectRoot(startPath) {
287
- let current;
288
- try {
289
- current = realpathSync(resolve(startPath));
290
- }
291
- catch {
292
- // Broken symlink at startPath itself — fall back to lexical resolve.
293
- // (Better to attempt some lookup than fail outright.)
294
- current = resolve(startPath);
295
- }
296
- // Bound the loop at the filesystem root by checking dirname stability.
297
- for (;;) {
298
- if (existsSync(join(current, '.codehere')) || existsSync(join(current, 'AGENTS.md'))) {
299
- return current;
300
- }
301
- const parent = dirname(current);
302
- if (parent === current)
303
- return null;
304
- current = parent;
305
- }
306
- }
307
- /**
308
- * Adapter-side convenience: turn a raw user prompt into the spawn-ready
309
- * prompt + the diagnostic record. Each of the 5 built-in adapters calls
310
- * this near the top of `spawn()` and uses the returned `prompt` going
311
- * forward, plus exposes `record` via `TaskSession.memoryInjection`.
312
- *
313
- * Walks UPWARD from `cwd` to find the nearest ancestor with `.codehere/`,
314
- * so spawning from a subdirectory still gets injection (codex Round 1 fix).
315
- *
316
- * Returns `{ prompt: <unchanged>, record: undefined }` when:
317
- * - `options.noMemoryPrefix` is true (caller opted out)
318
- * - no ancestor of `cwd` has `.codehere/` (nothing to inject)
319
- * - all sections are empty / placeholder seeds
320
- */
321
- export function applyMemoryPrefix(prompt, cwd, options) {
322
- if (options.noMemoryPrefix)
323
- return { prompt, record: undefined };
324
- const projectRoot = findProjectRoot(cwd);
325
- if (projectRoot === null)
326
- return { prompt, record: undefined };
327
- const r = buildMemoryPrefix(projectRoot);
328
- if (r.prefix === '')
329
- return { prompt, record: undefined };
330
- return {
331
- prompt: r.prefix + prompt,
332
- record: { included: r.included, tokens: r.tokens, truncated: r.truncated },
333
- };
334
- }
335
- /**
336
- * Write-back side of the shared-room hand-off: append a finished turn's outcome
337
- * to the project's `working/session.md` so the NEXT agent spawned in this
338
- * project reads it via buildMemoryPrefix() — closing the loop ("agents take
339
- * turns in a shared room, each building on the last").
340
- *
341
- * Resolves the SAME project root as the read path (findProjectRoot), so it only
342
- * writes when the project is codehere-initialized — it never creates `.codehere/`
343
- * in an uninitialized repo. The scratch is capped (the injector also budgets it).
344
- * Best-effort: a memory-write failure must never break a run.
345
- */
346
- export function recordTurnToWorkingMemory(cwd, entry) {
347
- try {
348
- const projectRoot = findProjectRoot(cwd);
349
- if (projectRoot === null)
350
- return; // not a codehere-initialized project
351
- const dir = join(projectRoot, '.codehere', 'memory', 'working');
352
- if (!existsSync(dir))
353
- return;
354
- const p = join(dir, 'session.md');
355
- const HEADER = '# Working session log - codehere auto-records each agent turn so the next one picks up here\n';
356
- const agent = (entry.agent || 'agent').replace(/\s+/g, ' ').trim().slice(0, 40);
357
- const prompt = (entry.prompt || '').replace(/\s+/g, ' ').trim().slice(0, 120);
358
- const summary = (entry.summary || '').replace(/\s+/g, ' ').trim().slice(0, 600);
359
- const files = entry.files ? ` - ${entry.files} file${entry.files === 1 ? '' : 's'} changed` : '';
360
- const block = `\n## ${agent}${prompt ? ' - ' + prompt : ''}${files}\n${summary || '(no written summary)'}\n`;
361
- let cur = '';
362
- try {
363
- cur = existsSync(p) ? readFileSync(p, 'utf8') : '';
364
- }
365
- catch {
366
- cur = '';
367
- }
368
- if (!cur.trim() || looksLikeInitSeed(cur, 'session'))
369
- cur = HEADER;
370
- cur += block;
371
- const CAP = 8000;
372
- if (cur.length > CAP)
373
- cur = HEADER + cur.slice(cur.length - (CAP - HEADER.length));
374
- writeFileSync(p, cur, 'utf8');
375
- }
376
- catch {
377
- /* never break a run on a memory-write failure */
378
- }
379
- }
380
- //# sourceMappingURL=memory-injector.js.map