@cairnkeep/cli 2.17.0 → 2.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/docs/ecosystem.md +0 -1
- package/docs/harness-compatibility.md +8 -3
- package/docs/learning/CURRICULUM-MAP.md +1 -1
- package/docs/learning/FEATURE-GUIDE.md +1 -1
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +3 -3
- package/docs/learning/lessons/L02-installation.md +2 -2
- package/docs/learning/lessons/L03-first-project.md +1 -1
- package/docs/learning/lessons/L19-validated-skill-improvement.md +1 -1
- package/docs/learning/lessons/L20-mcp-tool-profiles.md +1 -1
- package/docs/learning/lessons/L21-context-packs.md +1 -1
- package/docs/learning/lessons/L22-native-windows.md +2 -2
- package/docs/learning/lessons/L23-guided-setup.md +1 -1
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/native-windows.md +1 -1
- package/docs/operating.md +1 -1
- package/docs/quickstart.md +7 -4
- package/examples/eval/bundled-fake.json +1 -1
- package/package.json +2 -2
- package/scripts/setup.mjs +46 -12
- package/scripts/terminal-prompts.mjs +5 -3
- package/scripts/test-harness-registry.sh +15 -1
- package/scripts/test-release-sbom.sh +1 -1
- package/scripts/test-setup-preflight.mjs +13 -0
- package/templates/PROJECT-INSTRUCTIONS.md.template +0 -1
- package/templates/codex-config.toml.template +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ All notable user-facing changes are documented here. This project follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [2.17.2] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Generate the project Codex memory entry as `mcp_servers.cairn-memory-local`
|
|
13
|
+
instead of `cairn-memory`: Codex merges the user-wide and project MCP server
|
|
14
|
+
tables by id, so a project stdio entry collided with a user-wide remote
|
|
15
|
+
(url) entry of the same id and Codex >= 0.152 refused to start ("url is not
|
|
16
|
+
supported for stdio"). `cairn doctor` now diagnoses that legacy conflict
|
|
17
|
+
with a targeted recovery, and still accepts the old id on machines without
|
|
18
|
+
a user-wide remote entry.
|
|
19
|
+
|
|
20
|
+
## [2.17.1] - 2026-08-31
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Release TTY stdin after every interactive `cairn setup` selector so setup
|
|
25
|
+
returns to the shell after completion, including through SSH terminals that
|
|
26
|
+
report stdin as already flowing.
|
|
27
|
+
- Keep the context-usage receipt smoke fixture relative to the test run so its
|
|
28
|
+
doctor assertion cannot fail merely because a hard-coded timestamp aged out.
|
|
29
|
+
|
|
8
30
|
## [2.17.0] - 2026-08-29
|
|
9
31
|
|
|
10
32
|
### Added
|
package/docs/ecosystem.md
CHANGED
|
@@ -14,7 +14,6 @@ graph workflows have their own explicit companion requirements.
|
|
|
14
14
|
|
|
15
15
|
| Tool | What it adds | Integration |
|
|
16
16
|
|---|---|---|
|
|
17
|
-
| [lean-ctx](https://github.com/yvgude/lean-ctx) | Compressed file reads, searches, shell output, and directory maps | Register it as an MCP server in the chosen harness |
|
|
18
17
|
| [token-miser](https://github.com/cairnkeep/token-miser) | Model routing and compact codebase exploration | Configure `CAIRN_ROUTE_ENDPOINT` and/or `CAIRN_EXPLORE_BINARY` |
|
|
19
18
|
| [rtk](https://github.com/rtk-ai/rtk) | Token-reduced output for common Git, npm, and Cargo commands | Install as a shell-level proxy; Cairnkeep wiring is not required |
|
|
20
19
|
|
|
@@ -229,11 +229,16 @@ Native Windows uses `.\.ai\start-codex.cmd`. The generated
|
|
|
229
229
|
`.codex/config.toml` contains only:
|
|
230
230
|
|
|
231
231
|
```toml
|
|
232
|
-
[mcp_servers.cairn-memory]
|
|
232
|
+
[mcp_servers.cairn-memory-local]
|
|
233
233
|
command = "cairn"
|
|
234
234
|
args = ["memory-server"]
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
+
The id is `cairn-memory-local`, not `cairn-memory`: Codex merges the user-wide
|
|
238
|
+
and project `mcp_servers` tables by id, so a project stdio entry named
|
|
239
|
+
`cairn-memory` collides with a user-wide remote (url) entry of the same id and
|
|
240
|
+
Codex >= 0.152 refuses to start.
|
|
241
|
+
|
|
237
242
|
Review this file before accepting Codex's project-trust prompt. Cairnkeep does
|
|
238
243
|
not grant trust, edit the user-wide Codex configuration, or start Codex during
|
|
239
244
|
setup. If an operator-owned `.codex/config.toml` already differs, setup reports
|
|
@@ -252,10 +257,10 @@ writes during setup. An operator who has explicitly authorized a bounded,
|
|
|
252
257
|
unattended workflow may add narrow per-tool policy to the isolated Codex config:
|
|
253
258
|
|
|
254
259
|
```toml
|
|
255
|
-
[mcp_servers.cairn-memory.tools.memory_write]
|
|
260
|
+
[mcp_servers.cairn-memory-local.tools.memory_write]
|
|
256
261
|
approval_mode = "approve"
|
|
257
262
|
|
|
258
|
-
[mcp_servers.cairn-memory.tools.memory_supersede]
|
|
263
|
+
[mcp_servers.cairn-memory-local.tools.memory_supersede]
|
|
259
264
|
approval_mode = "approve"
|
|
260
265
|
```
|
|
261
266
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Status:** Ready
|
|
4
4
|
**Track:** Quickstart
|
|
5
5
|
**Time:** 15 minutes
|
|
6
|
-
**Tested with:** Cairnkeep 2.17.
|
|
6
|
+
**Tested with:** Cairnkeep 2.17.2
|
|
7
7
|
|
|
8
8
|
## Outcome
|
|
9
9
|
|
|
@@ -26,7 +26,7 @@ ephemeral; the named course volume is persistent.
|
|
|
26
26
|
1. Pull the public image:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
podman pull ghcr.io/cairnkeep/cairnkeep:2.17.
|
|
29
|
+
podman pull ghcr.io/cairnkeep/cairnkeep:2.17.1
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
2. Start the stdio server with a read-only root filesystem and no Linux
|
|
@@ -38,7 +38,7 @@ ephemeral; the named course volume is persistent.
|
|
|
38
38
|
--read-only --cap-drop=all --security-opt=no-new-privileges \
|
|
39
39
|
--tmpfs=/tmp:rw,noexec,nosuid,size=64m,mode=1777 \
|
|
40
40
|
--volume cairnkeep-course-data:/data:Z,U \
|
|
41
|
-
ghcr.io/cairnkeep/cairnkeep:2.17.
|
|
41
|
+
ghcr.io/cairnkeep/cairnkeep:2.17.1 stdio
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
3. The process waits quietly for MCP input. Press `Ctrl-C` after confirming it
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Status:** Ready
|
|
4
4
|
**Track:** Quickstart
|
|
5
5
|
**Time:** 25 minutes
|
|
6
|
-
**Tested with:** Cairnkeep 2.17.
|
|
6
|
+
**Tested with:** Cairnkeep 2.17.2 and Node.js 22 or newer
|
|
7
7
|
|
|
8
8
|
## Outcome
|
|
9
9
|
|
|
@@ -47,7 +47,7 @@ sync. Codex project trust remains an explicit operator decision.
|
|
|
47
47
|
2. Install Cairnkeep:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
npm install --global @cairnkeep/cli@2.17.
|
|
50
|
+
npm install --global @cairnkeep/cli@2.17.1
|
|
51
51
|
cairn --version
|
|
52
52
|
```
|
|
53
53
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Status:** Ready
|
|
4
4
|
**Track:** Operator
|
|
5
5
|
**Time:** 30 minutes
|
|
6
|
-
**Tested with:** Cairnkeep 2.17.
|
|
6
|
+
**Tested with:** Cairnkeep 2.17.2, Windows x64, and Node.js 22 or newer
|
|
7
7
|
|
|
8
8
|
## Outcome
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ perform a reversible uninstall without relying on WSL or Git Bash.
|
|
|
16
16
|
From PowerShell:
|
|
17
17
|
|
|
18
18
|
```powershell
|
|
19
|
-
npm install --global @cairnkeep/cli@2.17.
|
|
19
|
+
npm install --global @cairnkeep/cli@2.17.1
|
|
20
20
|
cairn version
|
|
21
21
|
|
|
22
22
|
$Lab = Join-Path $env:TEMP 'Cairnkeep Windows Lab'
|
package/docs/native-windows.md
CHANGED
|
@@ -54,7 +54,7 @@ cairn doctor
|
|
|
54
54
|
Review `.codex\config.toml` and accept Codex's project-trust prompt. Setup does
|
|
55
55
|
not edit the user-wide Codex configuration or grant trust. It preserves a
|
|
56
56
|
different existing project configuration and doctor verifies a manually merged
|
|
57
|
-
`mcp_servers.cairn-memory` table.
|
|
57
|
+
`mcp_servers.cairn-memory-local` table.
|
|
58
58
|
|
|
59
59
|
Setup and bootstrap write both the established Unix launchers and native Windows
|
|
60
60
|
launchers. Each `.cmd` file enters `start-harness.ps1`, loads `.ai/.env`, changes
|
package/docs/operating.md
CHANGED
|
@@ -127,7 +127,7 @@ cairn doctor
|
|
|
127
127
|
|
|
128
128
|
Review `.codex/config.toml` and accept Codex's project-trust prompt before use.
|
|
129
129
|
An existing different file is preserved and reported as skipped; merge the
|
|
130
|
-
generated `mcp_servers.cairn-memory` table into it, then run `cairn doctor`.
|
|
130
|
+
generated `mcp_servers.cairn-memory-local` table into it, then run `cairn doctor`.
|
|
131
131
|
Uninstall leaves that operator-owned file intact. Selecting
|
|
132
132
|
`--memory none` creates the launcher but deliberately omits the MCP entry.
|
|
133
133
|
|
package/docs/quickstart.md
CHANGED
|
@@ -22,16 +22,19 @@ The wizard recommends the existing repository mode when the target is already
|
|
|
22
22
|
a Git work tree. On native Windows, launch with `.\.ai\start-codex.cmd`.
|
|
23
23
|
|
|
24
24
|
Before the first session, review `.codex/config.toml` and accept Codex's project
|
|
25
|
-
trust prompt. The generated entry runs `cairn memory-server` locally
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
trust prompt. The generated entry runs `cairn memory-server` locally under the
|
|
26
|
+
server id `cairn-memory-local`: Codex merges the user-wide and project
|
|
27
|
+
`mcp_servers` tables by id, so a project entry named `cairn-memory` collides
|
|
28
|
+
with a user-wide remote (url) entry of the same id and Codex >= 0.152 refuses
|
|
29
|
+
to start. Setup does not edit the user-wide Codex configuration and does not
|
|
30
|
+
enable networking, telemetry, embeddings, session capture, or context packs.
|
|
28
31
|
|
|
29
32
|
If `.codex/config.toml` already exists and differs, setup reports it as
|
|
30
33
|
`skipped` and preserves it byte-for-byte. Merge this table into the existing
|
|
31
34
|
file, then run `cairn doctor`:
|
|
32
35
|
|
|
33
36
|
```toml
|
|
34
|
-
[mcp_servers.cairn-memory]
|
|
37
|
+
[mcp_servers.cairn-memory-local]
|
|
35
38
|
command = "cairn"
|
|
36
39
|
args = ["memory-server"]
|
|
37
40
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"identifier":"cairn-offline-fake-v1","package_version":"2.17.
|
|
1
|
+
{"identifier":"cairn-offline-fake-v1","package_version":"2.17.2","schema_version":1,"task_set_digest":"13e95379f35839593047ec34521a65a8fa3ab92bd3993bca9dc8bf5c4a9ff36f"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnkeep/cli",
|
|
3
|
-
"version": "2.17.
|
|
3
|
+
"version": "2.17.2",
|
|
4
4
|
"description": "A durable, harness-agnostic memory + context layer for coding agents and MCP clients.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -73,6 +73,6 @@
|
|
|
73
73
|
"rag"
|
|
74
74
|
],
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@cyclonedx/cyclonedx-npm": "6.0.
|
|
76
|
+
"@cyclonedx/cyclonedx-npm": "6.0.1"
|
|
77
77
|
}
|
|
78
78
|
}
|
package/scripts/setup.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
readFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
|
+
import { homedir } from "node:os";
|
|
13
14
|
import { join, resolve } from "node:path";
|
|
14
15
|
import { createInterface } from "node:readline/promises";
|
|
15
16
|
import { stdin as defaultInput, stdout as defaultOutput, stderr as defaultError } from "node:process";
|
|
@@ -353,33 +354,63 @@ function readPrivateState(path) {
|
|
|
353
354
|
}
|
|
354
355
|
}
|
|
355
356
|
|
|
356
|
-
function
|
|
357
|
-
|
|
358
|
-
if (!existsSync(path)) return false;
|
|
357
|
+
function readTomlConfig(path) {
|
|
358
|
+
if (!existsSync(path)) return null;
|
|
359
359
|
const info = lstatSync(path);
|
|
360
|
-
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) return
|
|
360
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 1024 * 1024) return null;
|
|
361
361
|
let descriptor;
|
|
362
|
-
let text;
|
|
363
362
|
try {
|
|
364
363
|
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
365
364
|
const opened = fstatSync(descriptor);
|
|
366
|
-
if (!opened.isFile() || opened.dev !== info.dev || opened.ino !== info.ino || opened.size > 1024 * 1024) return
|
|
367
|
-
|
|
365
|
+
if (!opened.isFile() || opened.dev !== info.dev || opened.ino !== info.ino || opened.size > 1024 * 1024) return null;
|
|
366
|
+
return readFileSync(descriptor, "utf8");
|
|
368
367
|
} finally {
|
|
369
368
|
if (descriptor !== undefined) closeSync(descriptor);
|
|
370
369
|
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function codexMemorySection(text, id) {
|
|
371
373
|
const lines = text.split(/\r?\n/);
|
|
372
374
|
const headers = lines.flatMap((line, index) => (
|
|
373
|
-
[
|
|
375
|
+
[`[mcp_servers.${id}]`, `[mcp_servers."${id}"]`].includes(line.trim()) ? [index] : []
|
|
374
376
|
));
|
|
375
|
-
if (headers.length !== 1) return
|
|
377
|
+
if (headers.length !== 1) return null;
|
|
376
378
|
const [start] = headers;
|
|
377
379
|
const next = lines.findIndex((line, index) => index > start && /^\s*\[[^\]]+\]\s*$/.test(line));
|
|
378
|
-
|
|
380
|
+
return lines.slice(start + 1, next < 0 ? lines.length : next).join("\n");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function isLocalMemorySection(section) {
|
|
379
384
|
return (section.match(/^\s*command\s*=\s*["']cairn["']\s*$/gm) ?? []).length === 1
|
|
380
385
|
&& (section.match(/^\s*args\s*=\s*\[\s*["']memory-server["']\s*\]\s*$/gm) ?? []).length === 1;
|
|
381
386
|
}
|
|
382
387
|
|
|
388
|
+
function hasCodexMemoryConfig(project) {
|
|
389
|
+
const text = readTomlConfig(join(project, ".codex", "config.toml"));
|
|
390
|
+
if (text === null) return false;
|
|
391
|
+
// `cairn-memory-local` is the current template id; the bare `cairn-memory`
|
|
392
|
+
// stdio form is accepted so pre-existing setups still diagnose complete.
|
|
393
|
+
return ["cairn-memory-local", "cairn-memory"].some((id) => {
|
|
394
|
+
const section = codexMemorySection(text, id);
|
|
395
|
+
return section !== null && isLocalMemorySection(section);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Codex merges the user-wide and project mcp_servers tables field by field, so
|
|
400
|
+
// a legacy project stdio `cairn-memory` collides with a user-wide remote (url)
|
|
401
|
+
// entry of the same id and Codex >= 0.152 refuses to start at all.
|
|
402
|
+
function hasCodexRemoteMemoryConflict(project) {
|
|
403
|
+
const projectText = readTomlConfig(join(project, ".codex", "config.toml"));
|
|
404
|
+
if (projectText === null) return false;
|
|
405
|
+
const legacy = codexMemorySection(projectText, "cairn-memory");
|
|
406
|
+
if (legacy === null || !isLocalMemorySection(legacy)) return false;
|
|
407
|
+
const userHome = process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
408
|
+
const userText = readTomlConfig(join(userHome, "config.toml"));
|
|
409
|
+
if (userText === null) return false;
|
|
410
|
+
const remote = codexMemorySection(userText, "cairn-memory");
|
|
411
|
+
return remote !== null && /^\s*url\s*=/m.test(remote);
|
|
412
|
+
}
|
|
413
|
+
|
|
383
414
|
export function diagnoseSetup(target = ".") {
|
|
384
415
|
const project = resolve(target);
|
|
385
416
|
const statePath = join(project, ".ai", "cairnkeep.json");
|
|
@@ -414,8 +445,11 @@ export function diagnoseSetup(target = ".") {
|
|
|
414
445
|
}
|
|
415
446
|
const requiredAssets = [...COMMON_SETUP_ASSETS, ...requiredHarnessAssetPaths(state.harnesses, state.memory)];
|
|
416
447
|
if (requiredAssets.some((path) => !Object.hasOwn(state.assets, path))) return incompleteDiagnosis(recovery);
|
|
417
|
-
if (state.memory === "local" && state.harnesses.includes("codex")
|
|
418
|
-
return incompleteDiagnosis(recovery);
|
|
448
|
+
if (state.memory === "local" && state.harnesses.includes("codex")) {
|
|
449
|
+
if (!hasCodexMemoryConfig(project)) return incompleteDiagnosis(recovery);
|
|
450
|
+
if (hasCodexRemoteMemoryConflict(project)) {
|
|
451
|
+
return incompleteDiagnosis(`rename the project table to [mcp_servers.cairn-memory-local] in .codex/config.toml (Codex >= 0.152 rejects a project stdio 'cairn-memory' when the user-wide config defines a remote 'cairn-memory'), or delete the file and re-run: ${recovery}`);
|
|
452
|
+
}
|
|
419
453
|
}
|
|
420
454
|
return Object.freeze({
|
|
421
455
|
schema_version: 1,
|
|
@@ -37,7 +37,6 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
37
37
|
let settled = false;
|
|
38
38
|
let keypressHandler = null;
|
|
39
39
|
let rawChanged = false;
|
|
40
|
-
let resumed = false;
|
|
41
40
|
let cursorHidden = false;
|
|
42
41
|
|
|
43
42
|
const render = () => {
|
|
@@ -59,7 +58,6 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
59
58
|
}
|
|
60
59
|
if (wasPaused && typeof input.resume === "function") {
|
|
61
60
|
input.resume();
|
|
62
|
-
resumed = true;
|
|
63
61
|
}
|
|
64
62
|
output.write("\x1b[?25l");
|
|
65
63
|
cursorHidden = true;
|
|
@@ -92,7 +90,11 @@ async function runKeypressPrompt({ input, output, draw, accept }) {
|
|
|
92
90
|
clear();
|
|
93
91
|
if (cursorHidden) output.write("\x1b[?25h");
|
|
94
92
|
if (rawChanged) input.setRawMode(false);
|
|
95
|
-
|
|
93
|
+
// A TTY can report itself as already flowing before the first prompt (for
|
|
94
|
+
// example through an SSH pseudo-terminal). Leaving it flowing keeps Node's
|
|
95
|
+
// event loop alive after setup has printed its final result. Always pause
|
|
96
|
+
// prompt input after removing our listener; the next selector resumes it.
|
|
97
|
+
if (typeof input.pause === "function") input.pause();
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -57,7 +57,7 @@ const result = JSON.parse(setup.stdout);
|
|
|
57
57
|
assert.deepEqual(result.harnesses, ["codex"]);
|
|
58
58
|
assert.equal(result.machine_sync.command, null);
|
|
59
59
|
assert.deepEqual(result.launch_commands, [".ai/start-codex.sh"]);
|
|
60
|
-
assert.match(readFileSync(join(target, ".codex", "config.toml"), "utf8"), /\[mcp_servers\.cairn-memory\][\s\S]*command = "cairn"[\s\S]*args = \["memory-server"\]/);
|
|
60
|
+
assert.match(readFileSync(join(target, ".codex", "config.toml"), "utf8"), /\[mcp_servers\.cairn-memory-local\][\s\S]*command = "cairn"[\s\S]*args = \["memory-server"\]/);
|
|
61
61
|
|
|
62
62
|
const fakeBin = join(sandbox, "bin");
|
|
63
63
|
mkdirSync(fakeBin);
|
|
@@ -94,7 +94,21 @@ assert.equal(setupModule.diagnoseSetup(collision).status, "incomplete");
|
|
|
94
94
|
writeFileSync(join(collision, ".codex", "config.toml"), [
|
|
95
95
|
"[features]", "fixture = true", "", "[mcp_servers.cairn-memory]", 'command = "cairn"', 'args = ["memory-server"]', "",
|
|
96
96
|
].join("\n"));
|
|
97
|
+
// Isolate the user-wide Codex config: the legacy stdio id only conflicts when
|
|
98
|
+
// the user-wide layer defines a remote (url) cairn-memory.
|
|
99
|
+
const codexHome = join(sandbox, "codex-home");
|
|
100
|
+
mkdirSync(codexHome);
|
|
101
|
+
process.env.CODEX_HOME = codexHome;
|
|
97
102
|
assert.equal(setupModule.diagnoseSetup(collision).status, "complete");
|
|
103
|
+
writeFileSync(join(codexHome, "config.toml"), '[mcp_servers.cairn-memory]\nurl = "http://127.0.0.1:8788/mcp"\n');
|
|
104
|
+
const conflict = setupModule.diagnoseSetup(collision);
|
|
105
|
+
assert.equal(conflict.status, "incomplete");
|
|
106
|
+
assert.match(conflict.recovery[0], /cairn-memory-local/);
|
|
107
|
+
writeFileSync(join(collision, ".codex", "config.toml"), [
|
|
108
|
+
"[features]", "fixture = true", "", "[mcp_servers.cairn-memory-local]", 'command = "cairn"', 'args = ["memory-server"]', "",
|
|
109
|
+
].join("\n"));
|
|
110
|
+
assert.equal(setupModule.diagnoseSetup(collision).status, "complete");
|
|
111
|
+
delete process.env.CODEX_HOME;
|
|
98
112
|
const uninstall = spawnSync(join(root, "bin", "cairn"), ["uninstall", "--yes", collision], {
|
|
99
113
|
encoding: "utf8", shell: false, env: { ...process.env, HOME: join(sandbox, "home") },
|
|
100
114
|
});
|
|
@@ -19,7 +19,7 @@ const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
|
19
19
|
const lock = JSON.parse(fs.readFileSync(process.argv[3], "utf8"));
|
|
20
20
|
const candidateWorkflow = fs.readFileSync(process.argv[4], "utf8");
|
|
21
21
|
const publishWorkflow = fs.readFileSync(process.argv[5], "utf8");
|
|
22
|
-
const expected = "6.0.
|
|
22
|
+
const expected = "6.0.1";
|
|
23
23
|
|
|
24
24
|
if (manifest.devDependencies?.["@cyclonedx/cyclonedx-npm"] !== expected) {
|
|
25
25
|
throw new Error(`package.json must pin @cyclonedx/cyclonedx-npm exactly to ${expected}`);
|
|
@@ -300,6 +300,19 @@ async function testTerminalSelectors(prompts) {
|
|
|
300
300
|
await assert.rejects(cancelled, (error) => error?.status === 130 && /cancelled/i.test(error.message));
|
|
301
301
|
assert.equal(input.isRaw, false);
|
|
302
302
|
assert.equal(input.isPaused(), true);
|
|
303
|
+
|
|
304
|
+
const flowingInput = new FakeTTY();
|
|
305
|
+
flowingInput.paused = false;
|
|
306
|
+
const flowing = prompts.selectOnePrompt({
|
|
307
|
+
message: "Release flowing input",
|
|
308
|
+
choices: [{ value: "done", label: "Done" }],
|
|
309
|
+
input: flowingInput,
|
|
310
|
+
output: output.stream,
|
|
311
|
+
});
|
|
312
|
+
setImmediate(() => flowingInput.emit("keypress", "", { name: "return" }));
|
|
313
|
+
assert.equal(await flowing, "done");
|
|
314
|
+
assert.equal(flowingInput.isRaw, false);
|
|
315
|
+
assert.equal(flowingInput.isPaused(), true, "terminal prompt must release flowing stdin so the CLI can exit");
|
|
303
316
|
}
|
|
304
317
|
|
|
305
318
|
async function testStructuredSetupSelectors(setup, fixture) {
|
|
@@ -38,5 +38,4 @@ Scopes defined in `.opencode/memory.json`:
|
|
|
38
38
|
## Tool Roles
|
|
39
39
|
- AgentFS is the durable structured memory layer.
|
|
40
40
|
- AnythingLLM is the long-form document retrieval layer.
|
|
41
|
-
- `lean-ctx` is the default context layer (reads, search, exploration), not durable project memory.
|
|
42
41
|
- RTK is a recommended companion for shell-heavy sessions (git, listings, failing builds); Token Savior is not default.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Cairnkeep project-scoped memory server. Codex loads project configuration only
|
|
2
2
|
# after the project has been trusted.
|
|
3
|
-
|
|
3
|
+
#
|
|
4
|
+
# The id is `cairn-memory-local`, not `cairn-memory`: Codex merges the user-wide
|
|
5
|
+
# and project mcp_servers tables field by field, so a project stdio entry named
|
|
6
|
+
# `cairn-memory` collides with a user-wide remote (url) entry of the same id and
|
|
7
|
+
# Codex >= 0.152 refuses to start ("url is not supported for stdio").
|
|
8
|
+
[mcp_servers.cairn-memory-local]
|
|
4
9
|
command = "cairn"
|
|
5
10
|
args = ["memory-server"]
|