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.
- package/README.md +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- 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
|