Skip to content

Commit 29d6cd6

Browse files
authored
Merge branch 'main' into fix-codex-toml-splice
2 parents e5997a4 + a7c238d commit 29d6cd6

23 files changed

Lines changed: 303 additions & 113 deletions

.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Checkout must stay LF everywhere: biome's format gate and the docs-as-code
2+
# evals compare exact bytes, and the default autocrlf=true on Windows would
3+
# turn every file CRLF and fail them.
4+
* text=auto eol=lf
5+
*.so binary
6+
*.dylib binary
7+
*.dll binary

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,32 @@ jobs:
3939

4040
- name: Hot-path bench
4141
run: npx tsx src/cli.ts bench
42+
43+
test-windows:
44+
name: test (windows)
45+
runs-on: windows-latest
46+
steps:
47+
- uses: actions/checkout@v4
48+
49+
- uses: actions/setup-node@v4
50+
with:
51+
node-version: "20"
52+
cache: npm
53+
54+
- name: Install dependencies
55+
run: npm ci
56+
57+
- name: Type-check
58+
run: npx tsc --noEmit
59+
60+
- name: Lint
61+
run: npx biome check src test
62+
63+
- name: Unit and integration tests
64+
run: npx vitest run
65+
66+
- name: Offline eval gates
67+
run: npx tsx src/cli.ts eval all
68+
69+
- name: Hot-path bench
70+
run: npx tsx src/cli.ts bench

AGENT_USAGE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ You should see exactly 8 tools listed.
191191
If multiple agents in the same repo will hit graphCTX at the same time
192192
(Claude Desktop + Cursor + a CI worker), start one daemon and point all of
193193
them at the same Unix socket. There is no TCP listener; the path is
194-
filesystem-local only.
194+
filesystem-local only. On Windows the same flag works: the path is mapped
195+
into the named-pipe namespace (`\\.\pipe\...`) automatically, since Node
196+
has no Unix-socket support there.
195197

196198
```bash
197199
# start once per machine / repo

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ npx tsx src/cli.ts bench
202202

203203
Expected current state:
204204

205-
- `npx vitest run`: 299 tests pass.
205+
- `npx vitest run`: 297 tests pass.
206206
- `npm run pack:smoke`: packed tarball installs, runs the demo, and serves
207207
MCP from a clean temp app.
208208
- `npx tsx src/cli.ts eval all`: 19 gate suites pass.

docs/STATUS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@
7676
|---|---|---|
7777
| `hook <event>` p95 | < 150ms | 15.51ms ✅ |
7878

79-
_Last updated: Codex config splicing now survives commented table headers, array-of-tables sections, and graphctx env subtables without touching neighboring config, Private-key scanning now covers DSA, encrypted PEM, and PGP private key block headers, MCP `resolve_conflict` now evaluates user, workspace, and current-session facts together, `graphctx resolve` now refuses non-open-loop facts instead of silently superseding durable memory, Storage migration 0006 adds a composite invalidation conflict lookup index, Session-scoped invalidation now keeps identical open loops isolated across sessions while deduping repeats within a session, Demo seed memory now uses Runtime.rememberFact, open-loop session memory now carries current git anchors, CLI `tui --tab` now fails fast on invalid tabs, CLI/MCP fact-kind validation uses a shared `FACT_KINDS` domain enum, TUI promotion uses Runtime/Probation gates instead of direct activation, Runtime/TUI/CLI/MCP explicit remember paths share safe git-anchored invalidating writes, Runtime/TUI/MCP forget paths share git-anchored temporal closeout, MCP forget stamps git closeout anchors, retrieval candidates are scope-filtered before semantic reranking, retrieval candidates receive final workspace/session scope filtering, superseded facts close temporal validity with `t_expired` and commit anchors, retrieval recency ranking uses the runtime clock, ranking/conflict tests use deterministic fact IDs and clocks, vector embedding cache uses the runtime clock, outcome telemetry uses an injectable clock, injection anti-repetition ledger uses the runtime clock, promotion verification timestamps use the runtime clock, promotion gates rescan secret-shaped fact identity, why renders invalidating commit closeout anchors, revert revalidation respects branch representation, revert revalidation skips bad historical anchors, atomic temporal lifecycle closeout anchors, TUI read model redacts secret fact text, conflict notes redact identity/object secrets, structured why redacts evidence chain metadata, structured why redacts fact identity and scope strings, structured why redacts promotion audit text, structured why redacts git anchors, structured why redacts evidence payloads and tags, why output shows expired/invalidated temporal closeout, send-edge safety scans fact tags for secrets, command cards avoid false HEAD verification claims, low-trust claims blocked from PreToolUse guardrail injection, candidate facts kept out of vector search until promotion, lifecycle-synced vector index entries for expired/reactivated facts, secret-redacted semantic rerank text, retriever-level send-unsafe fact suppression, tag-update secret sensitivity restamping, secret-redacted FTS/vector fact indexing, FTS path-query fail-soft handling, runtime-level injection context redaction, hook tool-result redaction before injection context, shared retrieval-context redaction for CLI/MCP/hooks, atomic invalidation effects, supported-by temporal evidence edges, tool-arg redaction before retrieval, hook prompt redaction before retrieval, transcript-tail redaction before retrieval, symlink-safe workspace config reads, symlink-safe local store paths, symlink-safe AGENTS.md boot capsule writes, symlink-safe generic adapter marker writes, symlink-safe OpenCode config writes, symlink-safe Cursor mcp writes, symlink-safe Claude settings writes, workspace-confined CI workflow extraction, workspace-confined agent prose extraction, workspace-confined Docker/Compose extraction, workspace-confined test config extraction, workspace-confined tooling config extraction, workspace-confined tsconfig extraction, workspace-confined editorconfig extraction, workspace-confined runtime version extraction, workspace-confined lockfile extraction, workspace-confined package script evidence, shared realpath workspace evidence checks, symlink-aware injection staleness checks, generated-marker symlink skip, procedure verifier secret scanning, extraction subject secret scanning, hook session-id redaction, MCP session-reference secret refusal, open-loop session metadata secret refusal, explicit memory metadata secret refusal, packageManager canonical facts, package-manager-aware script extraction, runtime-version deterministic extraction, multi-cookie session redaction, MCP error redaction, fail-closed retrieval for unvalidated commit-scoped facts, semantic CLI recall ranking, auth/cookie header secret scanning, scoped semantic retrieval expansion, workspace-confined injection staleness checks, package metadata extraction, high-trust test/Docker/tooling/tsconfig deterministic extraction, high-trust Python toolchain extraction, high-trust pyproject package-manager extraction, pyproject dependency-group Python command extraction, high-entropy redaction hardening, repo-id-isolated temporal validity, measured hook latency, fail-closed adapter install/uninstall, typed CLI error formatting, answer-bearing coding query expansion, Python package-manager retrieval expansion, redacted TUI selected-fact detail panels, TUI page/jump keyboard navigation, composed TUI empty states with operator status surfaces, SessionStart suppression of superseded user preferences, chunk-safe TUI key decoding with left/right and j/k navigation, bounded secret-redacted TUI prompt input, vector-safe commit-hygiene query expansion, searchable TUI memory lists, and tox/nox Python test harness extraction. 299 tests, 19 gate suites green, all I1-I9 hold._
79+
_Last updated: Windows is now a first-class dev platform (homedir-derived user-global paths, named-pipe daemon sockets, shim-free dev-tool spawns in eval suites, forward-slash generated-marker facts, windows-latest CI job), Private-key scanning now covers DSA, encrypted PEM, and PGP private key block headers, MCP `resolve_conflict` now evaluates user, workspace, and current-session facts together, `graphctx resolve` now refuses non-open-loop facts instead of silently superseding durable memory, Storage migration 0006 adds a composite invalidation conflict lookup index, Session-scoped invalidation now keeps identical open loops isolated across sessions while deduping repeats within a session, Demo seed memory now uses Runtime.rememberFact, open-loop session memory now carries current git anchors, CLI `tui --tab` now fails fast on invalid tabs, CLI/MCP fact-kind validation uses a shared `FACT_KINDS` domain enum, TUI promotion uses Runtime/Probation gates instead of direct activation, Runtime/TUI/CLI/MCP explicit remember paths share safe git-anchored invalidating writes, Runtime/TUI/MCP forget paths share git-anchored temporal closeout, MCP forget stamps git closeout anchors, retrieval candidates are scope-filtered before semantic reranking, retrieval candidates receive final workspace/session scope filtering, superseded facts close temporal validity with `t_expired` and commit anchors, retrieval recency ranking uses the runtime clock, ranking/conflict tests use deterministic fact IDs and clocks, vector embedding cache uses the runtime clock, outcome telemetry uses an injectable clock, injection anti-repetition ledger uses the runtime clock, promotion verification timestamps use the runtime clock, promotion gates rescan secret-shaped fact identity, why renders invalidating commit closeout anchors, revert revalidation respects branch representation, revert revalidation skips bad historical anchors, atomic temporal lifecycle closeout anchors, TUI read model redacts secret fact text, conflict notes redact identity/object secrets, structured why redacts evidence chain metadata, structured why redacts fact identity and scope strings, structured why redacts promotion audit text, structured why redacts git anchors, structured why redacts evidence payloads and tags, why output shows expired/invalidated temporal closeout, send-edge safety scans fact tags for secrets, command cards avoid false HEAD verification claims, low-trust claims blocked from PreToolUse guardrail injection, candidate facts kept out of vector search until promotion, lifecycle-synced vector index entries for expired/reactivated facts, secret-redacted semantic rerank text, retriever-level send-unsafe fact suppression, tag-update secret sensitivity restamping, secret-redacted FTS/vector fact indexing, FTS path-query fail-soft handling, runtime-level injection context redaction, hook tool-result redaction before injection context, shared retrieval-context redaction for CLI/MCP/hooks, atomic invalidation effects, supported-by temporal evidence edges, tool-arg redaction before retrieval, hook prompt redaction before retrieval, transcript-tail redaction before retrieval, symlink-safe workspace config reads, symlink-safe local store paths, symlink-safe AGENTS.md boot capsule writes, symlink-safe generic adapter marker writes, symlink-safe OpenCode config writes, symlink-safe Cursor mcp writes, symlink-safe Claude settings writes, workspace-confined CI workflow extraction, workspace-confined agent prose extraction, workspace-confined Docker/Compose extraction, workspace-confined test config extraction, workspace-confined tooling config extraction, workspace-confined tsconfig extraction, workspace-confined editorconfig extraction, workspace-confined runtime version extraction, workspace-confined lockfile extraction, workspace-confined package script evidence, shared realpath workspace evidence checks, symlink-aware injection staleness checks, generated-marker symlink skip, procedure verifier secret scanning, extraction subject secret scanning, hook session-id redaction, MCP session-reference secret refusal, open-loop session metadata secret refusal, explicit memory metadata secret refusal, packageManager canonical facts, package-manager-aware script extraction, runtime-version deterministic extraction, multi-cookie session redaction, MCP error redaction, fail-closed retrieval for unvalidated commit-scoped facts, semantic CLI recall ranking, auth/cookie header secret scanning, scoped semantic retrieval expansion, workspace-confined injection staleness checks, package metadata extraction, high-trust test/Docker/tooling/tsconfig deterministic extraction, high-trust Python toolchain extraction, high-trust pyproject package-manager extraction, pyproject dependency-group Python command extraction, high-entropy redaction hardening, repo-id-isolated temporal validity, measured hook latency, fail-closed adapter install/uninstall, typed CLI error formatting, answer-bearing coding query expansion, Python package-manager retrieval expansion, redacted TUI selected-fact detail panels, TUI page/jump keyboard navigation, composed TUI empty states with operator status surfaces, SessionStart suppression of superseded user preferences, chunk-safe TUI key decoding with left/right and j/k navigation, bounded secret-redacted TUI prompt input, vector-safe commit-hygiene query expansion, searchable TUI memory lists, and tox/nox Python test harness extraction. 297 tests, 19 gate suites green, all I1-I9 hold._
8080

81-
_Quality counters: Tests: 299. Gate suites: 19._
81+
_Quality counters: Tests: 297. Gate suites: 19._
8282

8383
_Runtime-clock note: direct CLI, eval, and benchmark retrievers pass
8484
`Runtime.clock`, so pull and push retrieval use the same recency clock seam._
@@ -119,4 +119,4 @@ now have explicit scanners instead of relying only on entropy fallback._
119119
| Code quality || new `eval quality` passes 6/6: full-repo Biome, strict TS config/scripts, shared command-surface helpers for CLI help/docs/README reachability, eval-suite runner/test coverage, final README docs-as-code, and generated migration packaging guard |
120120

121121
_Loop note: composite metric = (failing_gates × 100) + (un-perfected aspects); within-aspect
122-
measured gains are recorded in the `memory` graph. Tests: 299, gate suites: 19 (`eval all` includes run/memory/promote/drift/retrieval/gate/security/branch/temporal/conflict/procedure/mcp/storage/telemetry/provenance/resilience/benchmarks/cli-docs-demo/quality)._
122+
measured gains are recorded in the `memory` graph. Tests: 297, gate suites: 19 (`eval all` includes run/memory/promote/drift/retrieval/gate/security/branch/temporal/conflict/procedure/mcp/storage/telemetry/provenance/resilience/benchmarks/cli-docs-demo/quality)._

src/adapters/claude-code/install.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2+
import { homedir } from "node:os";
23
import { join } from "node:path";
34
import { AdapterError } from "../../core/errors.js";
45
import type { InstallOptions } from "../adapter.js";
@@ -25,9 +26,7 @@ interface ClaudeSettings {
2526
// event payload on stdin. Idempotent: replaces any prior graphctx entries.
2627
export function installClaudeHooks(opts: InstallOptions): { settingsPath: string } {
2728
const bin = opts.binPath ?? "graphctx";
28-
const dir = opts.global
29-
? join(process.env.HOME ?? "", ".claude")
30-
: join(opts.workspaceDir, ".claude");
29+
const dir = opts.global ? join(homedir(), ".claude") : join(opts.workspaceDir, ".claude");
3130
mkdirSync(dir, { recursive: true });
3231
const settingsPath = join(dir, "settings.json");
3332
assertWritableConfigPath(settingsPath, "Claude settings.json file");
@@ -58,9 +57,7 @@ export function installClaudeHooks(opts: InstallOptions): { settingsPath: string
5857
}
5958

6059
export function uninstallClaudeHooks(opts: InstallOptions): void {
61-
const dir = opts.global
62-
? join(process.env.HOME ?? "", ".claude")
63-
: join(opts.workspaceDir, ".claude");
60+
const dir = opts.global ? join(homedir(), ".claude") : join(opts.workspaceDir, ".claude");
6461
const settingsPath = join(dir, "settings.json");
6562
if (!existsSync(settingsPath)) return;
6663
assertWritableConfigPath(settingsPath, "Claude settings.json file");
@@ -84,9 +81,7 @@ export function uninstallClaudeHooks(opts: InstallOptions): void {
8481
}
8582

8683
export function hasClaudeGraphctxHooks(opts: InstallOptions): boolean {
87-
const dir = opts.global
88-
? join(process.env.HOME ?? "", ".claude")
89-
: join(opts.workspaceDir, ".claude");
84+
const dir = opts.global ? join(homedir(), ".claude") : join(opts.workspaceDir, ".claude");
9085
const settingsPath = join(dir, "settings.json");
9186
if (!existsSync(settingsPath)) return false;
9287
if (isSymlink(settingsPath)) return false;

src/adapters/skill/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2+
import { homedir } from "node:os";
23
import { join } from "node:path";
34
import { ValidationError } from "../../core/errors.js";
45
import { assertWritableConfigPath } from "../config-path.js";
@@ -31,11 +32,7 @@ export function isSkillClient(value: string): value is SkillClient {
3132
return (SKILL_CLIENTS as readonly string[]).includes(value);
3233
}
3334

34-
export function skillPath(
35-
client: SkillClient,
36-
workspaceDir: string,
37-
homeDir = process.env.HOME ?? "",
38-
): string {
35+
export function skillPath(client: SkillClient, workspaceDir: string, homeDir = homedir()): string {
3936
switch (client) {
4037
case "claude":
4138
return join(workspaceDir, ".claude", "skills", "graphctx", "SKILL.md");

src/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import { mkdirSync, writeFileSync } from "node:fs";
3-
import { tmpdir } from "node:os";
3+
import { homedir, tmpdir } from "node:os";
44
import { join, resolve } from "node:path";
55
import { Command } from "commander";
66
import { writeAgentsCapsule } from "./adapters/boot-capsule.js";
@@ -281,7 +281,7 @@ program
281281
.option("--mcp", "run as MCP server (stdio JSON-RPC)")
282282
.option(
283283
"--socket <path>",
284-
"(daemon mode) bind to a local Unix socket; one server, many concurrent clients",
284+
"(daemon mode) bind to a local Unix socket (win32: named pipe); one server, many clients",
285285
)
286286
.option("-C, --cwd <dir>", "workspace directory", process.cwd())
287287
.description("run the graphCTX MCP server (stdio by default, 8 tools)")
@@ -915,7 +915,7 @@ function handleCliError(e: unknown): never {
915915

916916
function logError(e: unknown): void {
917917
try {
918-
const dir = join(process.env.HOME ?? "", ".local", "share", "graphctx", "logs");
918+
const dir = join(homedir(), ".local", "share", "graphctx", "logs");
919919
mkdirSync(dir, { recursive: true });
920920
const line = `${isoNow()} ${e instanceof GraphCtxError ? `[${e.code}] ` : ""}${(e as Error)?.message ?? String(e)}\n`;
921921
writeFileSync(join(dir, "hook-errors.log"), line, { flag: "a" });

src/config/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { existsSync, readFileSync } from "node:fs";
2+
import { homedir } from "node:os";
23
import { isAbsolute, join, resolve } from "node:path";
34
import { ConfigError } from "../core/errors.js";
45
import { symlinkedPathComponent } from "../core/path-safety.js";
@@ -93,7 +94,7 @@ function readWorkspaceJsonIfSafe(path: string, workspaceDir: string): unknown {
9394

9495
function expandPath(p: string, workspaceDir: string): string {
9596
let out = p;
96-
if (out.startsWith("~")) out = join(process.env.HOME ?? "", out.slice(1));
97+
if (out.startsWith("~")) out = join(homedir(), out.slice(1));
9798
if (out.includes("~/.local/share/graphctx")) {
9899
out = out.replace(/^.*\.local\/share\/graphctx/, dataDir());
99100
}

src/eval/dev-tools.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { join } from "node:path";
2+
3+
// JS entry points for the dev tools the eval suites and tests spawn.
4+
// Spawned via process.execPath instead of the node_modules/.bin shims: on
5+
// win32 those shims are .cmd batch files, which execFile refuses to spawn
6+
// without shell:true (CVE-2024-27980 hardening), failing with EINVAL.
7+
// Running the current node binary against each tool's real entry is
8+
// shim-free, injection-safe, and identical on every platform.
9+
10+
export function tsxEntry(repoRoot: string): string {
11+
return join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs");
12+
}
13+
14+
export function vitestEntry(repoRoot: string): string {
15+
return join(repoRoot, "node_modules", "vitest", "vitest.mjs");
16+
}
17+
18+
export function biomeEntry(repoRoot: string): string {
19+
return join(repoRoot, "node_modules", "@biomejs", "biome", "bin", "biome");
20+
}

0 commit comments

Comments
 (0)