@cairnkeep/cli 2.18.0 → 2.19.1
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 +25 -1
- package/CONTRIBUTING.md +1 -1
- package/docs/harness-compatibility.md +2 -1
- package/docs/learning/CURRICULUM-MAP.md +1 -1
- package/docs/learning/FEATURE-GUIDE.md +2 -1
- package/docs/learning/lessons/L00-why-cairnkeep.md +1 -1
- package/docs/learning/lessons/L01-safe-trial.md +1 -1
- package/docs/learning/lessons/L02-installation.md +1 -1
- 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 +1 -1
- package/docs/learning/lessons/L23-guided-setup.md +18 -7
- package/docs/learning/lessons/L24-okf-exchange.md +1 -1
- package/docs/learning/lessons/L25-playbooks.md +1 -1
- package/docs/operating.md +11 -6
- package/examples/eval/bundled-fake.json +1 -1
- package/mcp-memory-server/dist/artifact-schema.js +1 -1
- package/mcp-memory-server/dist/work-evidence-schema.js +1 -1
- package/mcp-memory-server/dist/work-evidence-store.js +1 -1
- package/mcp-memory-server/package.json +1 -1
- package/package.json +2 -2
- package/schemas/artifact.schema.json +1 -1
- package/schemas/cairnkeep-setup-policy.schema.json +3 -3
- package/schemas/cairnkeep-setup.schema.json +1 -1
- package/scripts/harness-registry.mjs +15 -3
- package/scripts/setup.mjs +10 -5
- package/scripts/test-bootstrap-untracked.sh +1 -1
- package/scripts/test-completion.sh +1 -1
- package/scripts/test-harness-registry.sh +28 -1
- package/scripts/test-setup-output.sh +20 -1
- package/scripts/test-setup-preflight.mjs +1 -1
- package/scripts/test-work-evidence-cli.sh +1 -1
- package/scripts/uninstall.sh +1 -0
- package/scripts/verify-dependency-security.mjs +20 -2
- package/templates/start-omp.sh.template +47 -0
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.19.1] - 2026-10-06
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- Upgrade the MCP SDK floor to 1.32.1 and refresh both production lock graphs
|
|
13
|
+
so `fast-uri` 3.1.8, `hono` 4.13.13, `ip-address` 10.7.3, and
|
|
14
|
+
`proxy-addr` 2.0.8 exclude the newly disclosed URI-normalization, request
|
|
15
|
+
parsing, SSRF, denial-of-service, XSS, and proxy-trust advisories.
|
|
16
|
+
- Extend the dependency-security gate to reject future lockfile regressions
|
|
17
|
+
below those patched transitive versions.
|
|
18
|
+
|
|
19
|
+
## [2.19.0] - 2026-09-28
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Include oh-my-pi (OMP) in guided setup with a project launcher and explicit,
|
|
24
|
+
opt-in machine sync instructions.
|
|
25
|
+
- Report every required machine sync command when multiple harnesses are
|
|
26
|
+
selected, while retaining the singular JSON field for compatibility.
|
|
27
|
+
- Support OMP in opt-in Git-linked work evidence and preserve its diff-artifact
|
|
28
|
+
provenance.
|
|
29
|
+
|
|
8
30
|
## [2.18.0] - 2026-09-28
|
|
9
31
|
|
|
10
32
|
### Added
|
|
@@ -561,7 +583,9 @@ stored memories.
|
|
|
561
583
|
- Preserve executable permissions and Bash 3.2 portability on macOS.
|
|
562
584
|
- Add backup-first uninstall and SQLite-safe memory export/import guidance.
|
|
563
585
|
|
|
564
|
-
[Unreleased]: https://github.com/cairnkeep/cairnkeep/compare/v2.
|
|
586
|
+
[Unreleased]: https://github.com/cairnkeep/cairnkeep/compare/v2.19.1...HEAD
|
|
587
|
+
[2.19.1]: https://github.com/cairnkeep/cairnkeep/compare/v2.19.0...v2.19.1
|
|
588
|
+
[2.19.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.18.0...v2.19.0
|
|
565
589
|
[2.18.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.17.3...v2.18.0
|
|
566
590
|
[2.15.1]: https://github.com/cairnkeep/cairnkeep/compare/v2.15.0...v2.15.1
|
|
567
591
|
[2.15.0]: https://github.com/cairnkeep/cairnkeep/compare/v2.14.0...v2.15.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -43,7 +43,7 @@ endpoints. They are not part of the default offline test suite.
|
|
|
43
43
|
Node-based CLI implementations.
|
|
44
44
|
- `bin/cairn` is the installed command entry point.
|
|
45
45
|
- `templates/` contains project scaffolding and managed instruction assets.
|
|
46
|
-
- `claude/`, `opencode/`, `pi/`, and `kimi/` contain harness-specific operating
|
|
46
|
+
- `claude/`, `opencode/`, `pi/`, `omp/`, and `kimi/` contain harness-specific operating
|
|
47
47
|
assets.
|
|
48
48
|
- `scripts/` contains sync, lifecycle, packaging, and contract checks.
|
|
49
49
|
- `docs/` contains the versioned user and operator references indexed from
|
|
@@ -80,7 +80,8 @@ registry-current release.
|
|
|
80
80
|
|
|
81
81
|
## oh-my-pi (omp)
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Select OMP in `cairn setup` to create the project launcher, then run
|
|
84
|
+
`cairn sync-omp --apply` explicitly to install
|
|
84
85
|
`extensions/cairnkeep-memory.ts` and `extensions/cairnkeep-capture.ts` under
|
|
85
86
|
the omp agent root (default
|
|
86
87
|
`~/.omp/agent`; `PI_CODING_AGENT_DIR` overrides it, matching omp's agent
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cairnkeep feature guide
|
|
2
2
|
|
|
3
|
-
**Baseline:** Cairnkeep 2.
|
|
3
|
+
**Baseline:** Cairnkeep 2.19.1
|
|
4
4
|
|
|
5
5
|
This is the step-back map for choosing and operating Cairnkeep features. The
|
|
6
6
|
[course labs](https://github.com/cairnkeep/cairnkeep-course-labs) provide one
|
|
@@ -86,6 +86,7 @@ prerequisites for ordinary memory.
|
|
|
86
86
|
| Context packs | Manual local or commit-pinned Git install plus project enablement | Immutable objects and atomic pointers under `CAIRN_PACK_BASE_DIR`; no background sync | Validate, inspect digest, check/apply update, disable/remove | `course-10-trust-context` |
|
|
87
87
|
| OKF exchange | `cairn pack validate-okf`, `import-okf`, and preview-confirm `export-okf` | Local import/export by default; explicit file/shared-note allowlist; redacted output | Inspect diagnostics/provenance/output list; exact-digest apply; disable/remove imported pack | `course-10-trust-context` |
|
|
88
88
|
| Pi local stdio memory | Select Pi during setup, then explicitly run `cairn sync-pi --apply` | Local memory-server child; dynamic effective tool catalog; annotations retained in trusted details, not a native Pi field | `cairn sync-pi --check`, `cairn doctor`, cancel one call, then observe awaited shutdown with no orphan | `course-12-guided-setup` |
|
|
89
|
+
| OMP local stdio memory | Select OMP during setup, then explicitly run `cairn sync-omp --apply` | Local memory-server child; dynamic effective tool catalog; capture stages candidates for review | `cairn sync-omp --check`, `cairn doctor`, cancel one call, then verify session-stop staging and no orphan | `course-12-guided-setup` |
|
|
89
90
|
|
|
90
91
|
## Evidence, governance, and measurement
|
|
91
92
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Status:** Ready
|
|
4
4
|
**Track:** Quickstart and Operator
|
|
5
5
|
**Time:** 35 minutes
|
|
6
|
-
**Tested with:** Cairnkeep 2.
|
|
6
|
+
**Tested with:** Cairnkeep 2.19.1; Pi 0.84.1; Node.js 22, 24, and 26
|
|
7
7
|
|
|
8
8
|
## Outcome
|
|
9
9
|
|
|
@@ -13,13 +13,14 @@ Pi's local stdio memory bridge, and recover or uninstall every managed asset
|
|
|
13
13
|
without hidden machine changes.
|
|
14
14
|
|
|
15
15
|
The selected harnesses come from Cairnkeep's declarative registry. Codex is a
|
|
16
|
-
supported selection: it receives `.codex/config.toml` and a launcher
|
|
17
|
-
|
|
16
|
+
supported selection: it receives `.codex/config.toml` and a launcher. Pi and
|
|
17
|
+
OMP receive project launchers and each require a separate, explicit machine
|
|
18
|
+
sync; selecting both reports both sync commands.
|
|
18
19
|
|
|
19
20
|
## Prerequisites
|
|
20
21
|
|
|
21
22
|
- Node.js 22 or newer, npm, and Git for the main exercise.
|
|
22
|
-
- Pi 0.84.1 or newer.
|
|
23
|
+
- Pi 0.84.1 or newer; OMP is optional.
|
|
23
24
|
- A disposable parent directory containing no credentials, secrets, private
|
|
24
25
|
state, or identifying project data.
|
|
25
26
|
|
|
@@ -32,7 +33,7 @@ cairn setup /tmp/cairnkeep-guided-selector-lab
|
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
Choose Git with Up/Down and Enter, tick Pi with Space in the harness checklist,
|
|
35
|
-
keep local memory, then inspect the plan before confirming. Pressing Escape or
|
|
36
|
+
optionally tick OMP as well, keep local memory, then inspect the plan before confirming. Pressing Escape or
|
|
36
37
|
Ctrl-C cancels; redirected and limited terminals use the documented text-input
|
|
37
38
|
fallback. Remove that disposable selector lab before continuing if you applied
|
|
38
39
|
the plan.
|
|
@@ -63,6 +64,14 @@ cairn doctor
|
|
|
63
64
|
./.ai/start-pi.sh
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
If OMP was selected, run its separate sync and use the generated launcher:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cairn sync-omp --apply
|
|
71
|
+
cairn sync-omp --check
|
|
72
|
+
./.ai/start-omp.sh
|
|
73
|
+
```
|
|
74
|
+
|
|
66
75
|
In the disposable Pi session, list the registered Cairnkeep tools and make one
|
|
67
76
|
harmless read-only memory call. The extension starts `cairn memory-server` as a
|
|
68
77
|
local stdio child in this project; it does not select HTTP or a remote server.
|
|
@@ -107,9 +116,10 @@ cairn sync-pi --check
|
|
|
107
116
|
cairn doctor
|
|
108
117
|
```
|
|
109
118
|
|
|
110
|
-
Doctor reports project-state drift separately from missing or drifted
|
|
119
|
+
Doctor reports project-state drift separately from missing or drifted harness
|
|
111
120
|
machine assets. Setup repairs only recorded project assets; `cairn sync-pi
|
|
112
|
-
--apply`
|
|
121
|
+
--apply` and `cairn sync-omp --apply` repair only their respective package-owned
|
|
122
|
+
machine paths.
|
|
113
123
|
|
|
114
124
|
Preview backup-first removal before confirming it:
|
|
115
125
|
|
|
@@ -128,6 +138,7 @@ and context packs remain unless their separate purge flags are supplied.
|
|
|
128
138
|
| Setup refuses a missing target | The non-interactive command omitted an explicit choice or `--yes` | Run `cairn setup PATH` in a terminal, or supply the target, `--git`, `--harness`, `--memory`, and `--yes` together |
|
|
129
139
|
| Setup refuses `--git existing` | The target is not inside a Git work tree | Choose an existing repository, or explicitly authorize `--git init`; use `--git none` only when limited mode is intended |
|
|
130
140
|
| Doctor reports missing or drifted Pi assets | Project setup selected Pi but machine sync is incomplete | Run `cairn sync-pi --apply`, then `cairn sync-pi --check` and `cairn doctor` |
|
|
141
|
+
| OMP is missing an extension | Project setup selected OMP but its explicit machine sync was not applied | Run `cairn sync-omp --apply`, then `cairn sync-omp --check` and `cairn doctor` |
|
|
131
142
|
| A cancelled call ends the session | The Pi/bridge version does not satisfy the per-call cancellation contract | Preserve sanitized diagnostics, close the session, and do not claim release readiness |
|
|
132
143
|
| Pi exits but a child process remains | Shutdown did not complete or the tested extension drifted | Preserve process-state evidence, run the sync check, and treat the release gate as failed |
|
|
133
144
|
| A client expects native annotation hints | Pi's public tool API has no annotations field | Inspect trusted bridge metadata/result `details`; never claim native propagation |
|
package/docs/operating.md
CHANGED
|
@@ -75,7 +75,7 @@ cairn setup /path/to/project --git init --harness claude,pi --memory local --yes
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
The deterministic form accepts `--git init|existing|none`, a comma-separated
|
|
78
|
-
subset of `claude,opencode,pi,kimi,qwen,codex`, and `--memory local|none`. `--git
|
|
78
|
+
subset of `claude,opencode,pi,omp,kimi,qwen,codex`, and `--memory local|none`. `--git
|
|
79
79
|
existing` requires the target to be in an existing work tree. `--git init`
|
|
80
80
|
requires Git and is the only non-interactive authorization to initialize a
|
|
81
81
|
repository. `--git none` is an explicit limited mode: repository-aware features
|
|
@@ -95,16 +95,21 @@ selected harnesses, and digests/modes/template identifiers for setup-owned
|
|
|
95
95
|
assets. It contains no credentials, endpoints, or absolute paths.
|
|
96
96
|
|
|
97
97
|
Setup never installs or refreshes machine-level harness assets. Its
|
|
98
|
-
`machine_sync.automatic` field is always false, and the human output labels
|
|
99
|
-
reported command as not run automatically.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
`machine_sync.automatic` field is always false, and the human output labels each
|
|
99
|
+
reported command as not run automatically. The JSON result's
|
|
100
|
+
`machine_sync.commands` lists every required command; the older singular
|
|
101
|
+
`machine_sync.command` field remains as the first command for compatibility.
|
|
102
|
+
For selections such as Codex and Qwen that need no machine sync,
|
|
103
|
+
`machine_sync.commands` is empty and the human output says that sync is not
|
|
104
|
+
required. Apply each reported command explicitly, check the corresponding
|
|
105
|
+
assets, then diagnose and launch:
|
|
103
106
|
|
|
104
107
|
```bash
|
|
105
108
|
cairn sync --apply # Claude Code operating assets
|
|
106
109
|
cairn sync-pi --apply # Pi extension and prompt
|
|
107
110
|
cairn sync-pi --check
|
|
111
|
+
cairn sync-omp --apply # OMP extensions
|
|
112
|
+
cairn sync-omp --check
|
|
108
113
|
cd /path/to/project && cairn doctor
|
|
109
114
|
./.ai/start-pi.sh
|
|
110
115
|
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"identifier":"cairn-offline-fake-v1","package_version":"2.
|
|
1
|
+
{"identifier":"cairn-offline-fake-v1","package_version":"2.19.1","schema_version":1,"task_set_digest":"13e95379f35839593047ec34521a65a8fa3ab92bd3993bca9dc8bf5c4a9ff36f"}
|
|
@@ -32,7 +32,7 @@ export const artifactNodeRefSchema = z.object({
|
|
|
32
32
|
export const artifactProvenanceSchema = z.object({
|
|
33
33
|
producer: z.string().min(1).max(256).refine((value) => !/[\u0000-\u001f\u007f]/.test(value)),
|
|
34
34
|
source_event: z.string().min(1).max(256).optional(),
|
|
35
|
-
harness: z.enum(["claude-code", "opencode", "pi"]).optional(),
|
|
35
|
+
harness: z.enum(["claude-code", "opencode", "pi", "omp"]).optional(),
|
|
36
36
|
harness_version: z.string().min(1).max(128).optional(),
|
|
37
37
|
native_id: z.string().min(1).max(256).optional(),
|
|
38
38
|
}).strict();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export const WORK_EVIDENCE_SCHEMA_VERSION = 1;
|
|
3
|
-
export const WORK_EVIDENCE_HARNESSES = ["claude", "opencode", "pi", "kimi", "qwen", "codex"];
|
|
3
|
+
export const WORK_EVIDENCE_HARNESSES = ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"];
|
|
4
4
|
export const WORK_EVIDENCE_DEFAULT_RETENTION_DAYS = 30;
|
|
5
5
|
export const WORK_EVIDENCE_DEFAULT_STORE_MAX_BYTES = 64 * 1024 * 1024;
|
|
6
6
|
export const WORK_EVIDENCE_DEFAULT_MAX_TOUCHED_PATHS = 4096;
|
|
@@ -292,7 +292,7 @@ export async function finishWorkEvidence(projectRoot, evidenceId, exitStatus, op
|
|
|
292
292
|
provenance: {
|
|
293
293
|
producer: "cairn-work-evidence",
|
|
294
294
|
source_event: "launcher-exit",
|
|
295
|
-
harness: start.harness === "claude" ? "claude-code" : ["opencode", "pi"].includes(start.harness) ? start.harness : undefined,
|
|
295
|
+
harness: start.harness === "claude" ? "claude-code" : ["opencode", "pi", "omp"].includes(start.harness) ? start.harness : undefined,
|
|
296
296
|
},
|
|
297
297
|
content: { text: patchCapture.text },
|
|
298
298
|
}, { ...artifactLimits, artifactMaxBytes: Math.min(artifactLimits.artifactMaxBytes, limits.patchMaxBytes) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnkeep/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.19.1",
|
|
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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=22"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
42
42
|
"agentfs-sdk": "^0.6.4",
|
|
43
43
|
"yaml": "2.9.0",
|
|
44
44
|
"zod": "^4.4.3"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"properties": {
|
|
28
28
|
"producer": { "type": "string", "minLength": 1, "maxLength": 256, "pattern": "^[^\\u0000-\\u001f\\u007f]+$" },
|
|
29
29
|
"source_event": { "type": "string", "minLength": 1, "maxLength": 256 },
|
|
30
|
-
"harness": { "enum": ["claude-code", "opencode", "pi"] },
|
|
30
|
+
"harness": { "enum": ["claude-code", "opencode", "pi", "omp"] },
|
|
31
31
|
"harness_version": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
32
32
|
"native_id": { "type": "string", "minLength": 1, "maxLength": 256 }
|
|
33
33
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"type": "array",
|
|
17
17
|
"minItems": 1,
|
|
18
18
|
"uniqueItems": true,
|
|
19
|
-
"items": {"enum": ["claude", "opencode", "pi", "kimi", "qwen", "codex"]}
|
|
19
|
+
"items": {"enum": ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]}
|
|
20
20
|
},
|
|
21
21
|
"memory": {"enum": ["local", "none"]}
|
|
22
22
|
}
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
"type": "array",
|
|
36
36
|
"minItems": 1,
|
|
37
37
|
"uniqueItems": true,
|
|
38
|
-
"items": {"enum": ["claude", "opencode", "pi", "kimi", "qwen", "codex"]}
|
|
38
|
+
"items": {"enum": ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]}
|
|
39
39
|
},
|
|
40
40
|
"required_harnesses": {
|
|
41
41
|
"type": "array",
|
|
42
42
|
"minItems": 1,
|
|
43
43
|
"uniqueItems": true,
|
|
44
|
-
"items": {"enum": ["claude", "opencode", "pi", "kimi", "qwen", "codex"]}
|
|
44
|
+
"items": {"enum": ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]}
|
|
45
45
|
},
|
|
46
46
|
"memory": {
|
|
47
47
|
"type": "array",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"type": "array",
|
|
15
15
|
"minItems": 1,
|
|
16
16
|
"uniqueItems": true,
|
|
17
|
-
"items": {"enum": ["claude", "opencode", "pi", "kimi", "qwen", "codex"]}
|
|
17
|
+
"items": {"enum": ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]}
|
|
18
18
|
},
|
|
19
19
|
"assets": {
|
|
20
20
|
"type": "object",
|
|
@@ -28,6 +28,13 @@ const DEFINITIONS = [
|
|
|
28
28
|
project_assets: [],
|
|
29
29
|
machine_sync: "cairn sync-pi --apply",
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
id: "omp",
|
|
33
|
+
title: "Oh My Pi (OMP)",
|
|
34
|
+
launcher: { template: "start-omp.sh.template", path: ".ai/start-omp.sh", mode: 0o755 },
|
|
35
|
+
project_assets: [],
|
|
36
|
+
machine_sync: "cairn sync-omp --apply",
|
|
37
|
+
},
|
|
31
38
|
{
|
|
32
39
|
id: "kimi",
|
|
33
40
|
title: "Kimi Code",
|
|
@@ -90,9 +97,14 @@ export function requiredHarnessAssetPaths(harnesses, memory = "local") {
|
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
export function machineSyncCommand(harnesses) {
|
|
93
|
-
const commands = harnesses
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
const commands = machineSyncCommands(harnesses);
|
|
101
|
+
return commands.find((command) => command === "cairn sync-pi --apply") ?? commands[0] ?? null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function machineSyncCommands(harnesses) {
|
|
105
|
+
return Object.freeze([...new Set(
|
|
106
|
+
harnesses.map(harnessDefinition).map((definition) => definition?.machine_sync).filter(Boolean),
|
|
107
|
+
)]);
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
function validateRegistry() {
|
package/scripts/setup.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveSetupChoices,
|
|
23
23
|
} from "./setup-core.mjs";
|
|
24
24
|
import { reconcileSetupPlan } from "./setup-reconcile.mjs";
|
|
25
|
-
import { HARNESS_IDS, HARNESS_REGISTRY, machineSyncCommand, requiredHarnessAssetPaths } from "./harness-registry.mjs";
|
|
25
|
+
import { HARNESS_IDS, HARNESS_REGISTRY, machineSyncCommand, machineSyncCommands, requiredHarnessAssetPaths } from "./harness-registry.mjs";
|
|
26
26
|
import { reconcilePlaybookInstructions } from "./playbook-instructions.mjs";
|
|
27
27
|
import { selectManyPrompt, selectOnePrompt, supportsTerminalPrompts } from "./terminal-prompts.mjs";
|
|
28
28
|
|
|
@@ -189,6 +189,7 @@ function setupRecovery(plan) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
function resultFor(plan, reconciliation, platform) {
|
|
192
|
+
const machineCommands = machineSyncCommands(plan.harnesses);
|
|
192
193
|
const machineCommand = machineSyncCommand(plan.harnesses);
|
|
193
194
|
return Object.freeze({
|
|
194
195
|
schema_version: 1,
|
|
@@ -203,7 +204,7 @@ function resultFor(plan, reconciliation, platform) {
|
|
|
203
204
|
launch_commands: Object.freeze(launchCommands(plan, platform)),
|
|
204
205
|
recovery: Object.freeze(setupRecovery(plan)),
|
|
205
206
|
limitations: Object.freeze(plan.limited ? ["Git integration is disabled; repository-aware features are limited."] : []),
|
|
206
|
-
machine_sync: Object.freeze({ automatic: false, command: machineCommand }),
|
|
207
|
+
machine_sync: Object.freeze({ automatic: false, command: machineCommand, commands: machineCommands }),
|
|
207
208
|
});
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -223,9 +224,13 @@ export function renderSetupResult(result, stream) {
|
|
|
223
224
|
if (result.harnesses.includes("codex") && result.memory === "local") {
|
|
224
225
|
writeLine(stream, "Codex: review .codex/config.toml and accept the project trust prompt before use");
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
if (result.machine_sync.commands.length > 0) {
|
|
228
|
+
for (const command of result.machine_sync.commands) {
|
|
229
|
+
writeLine(stream, `Machine sync: ${command} (not run automatically)`);
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
writeLine(stream, "Machine sync: not required for the selected harnesses");
|
|
233
|
+
}
|
|
229
234
|
}
|
|
230
235
|
|
|
231
236
|
function createTarget(target) {
|
|
@@ -90,7 +90,7 @@ grep -qxF "/.agentfs/" "$tmp/repo/.git/info/exclude" || fail "missing /.agentfs/
|
|
|
90
90
|
printf '%s\n' '{"schema_version":1,"capabilities":{"wiki":false},"logging":{"callbacks":true}}' >"$tmp/repo/.ai/capabilities.json"
|
|
91
91
|
chmod 600 "$tmp/repo/.ai/capabilities.json"
|
|
92
92
|
printf '%s\n' '# operator-owned environment' >"$tmp/repo/.ai/env.example"
|
|
93
|
-
asset_paths='.ai/start-claude.sh .ai/start-opencode.sh .ai/start-pi.sh .ai/start-kimi.sh .ai/start-qwen.sh .ai/start-codex.sh .ai/env.example .ai/trajectory-redaction.json .ai/capabilities.json .agentfs/.gitignore .planning/config.json .planning/wiki/index.md .planning/wiki/policy.md .planning/wiki/CONTRADICTIONS.md .planning/wiki/LOG.md .planning/alignment/policy.md .planning/alignment/gap-register.yaml .planning/graphs/policy.md .planning/graphs/.gitignore .planning/security/policy.md'
|
|
93
|
+
asset_paths='.ai/start-claude.sh .ai/start-opencode.sh .ai/start-pi.sh .ai/start-omp.sh .ai/start-kimi.sh .ai/start-qwen.sh .ai/start-codex.sh .ai/env.example .ai/trajectory-redaction.json .ai/capabilities.json .agentfs/.gitignore .planning/config.json .planning/wiki/index.md .planning/wiki/policy.md .planning/wiki/CONTRADICTIONS.md .planning/wiki/LOG.md .planning/alignment/policy.md .planning/alignment/gap-register.yaml .planning/graphs/policy.md .planning/graphs/.gitignore .planning/security/policy.md'
|
|
94
94
|
mkdir -p "$tmp/assets-before"
|
|
95
95
|
for path in $asset_paths; do
|
|
96
96
|
cp "$tmp/repo/$path" "$tmp/assets-before/${path//\//__}"
|
|
@@ -165,7 +165,7 @@ if [[ "$completion_surface_complete" != true ]]; then
|
|
|
165
165
|
fi
|
|
166
166
|
|
|
167
167
|
setup_flags='--git --harness --memory --policy --yes --json'
|
|
168
|
-
setup_values='init existing none claude opencode pi kimi qwen local'
|
|
168
|
+
setup_values='init existing none claude opencode pi omp kimi qwen codex local'
|
|
169
169
|
for shell in bash zsh fish powershell; do
|
|
170
170
|
for flag in $setup_flags; do
|
|
171
171
|
grep -q -- "$flag\|${flag#--}" "$tmp/$shell" || {
|
|
@@ -25,7 +25,7 @@ for (const path of [
|
|
|
25
25
|
assert.match(content, /approval/);
|
|
26
26
|
}
|
|
27
27
|
const registry = await import(pathToFileURL(join(root, "scripts", "harness-registry.mjs")).href);
|
|
28
|
-
assert.deepEqual(registry.HARNESS_IDS, ["claude", "opencode", "pi", "kimi", "qwen", "codex"]);
|
|
28
|
+
assert.deepEqual(registry.HARNESS_IDS, ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]);
|
|
29
29
|
assert.equal(new Set(registry.HARNESS_IDS).size, registry.HARNESS_IDS.length);
|
|
30
30
|
for (const id of registry.HARNESS_IDS) {
|
|
31
31
|
const definition = registry.harnessDefinition(id);
|
|
@@ -40,6 +40,14 @@ assert.deepEqual(registry.harnessProjectAssets("codex", "local").map(({ path })
|
|
|
40
40
|
assert.deepEqual(registry.requiredHarnessAssetPaths(["codex"], "local"), [".ai/start-codex.sh"]);
|
|
41
41
|
assert.deepEqual(registry.requiredHarnessAssetPaths(["codex"], "none"), [".ai/start-codex.sh"]);
|
|
42
42
|
assert.equal(registry.machineSyncCommand(["codex"]), null);
|
|
43
|
+
assert.deepEqual(registry.machineSyncCommands(["claude", "opencode", "pi", "omp", "kimi"]), [
|
|
44
|
+
"cairn sync --apply",
|
|
45
|
+
"cairn sync-pi --apply",
|
|
46
|
+
"cairn sync-omp --apply",
|
|
47
|
+
"cairn sync-kimi --apply",
|
|
48
|
+
]);
|
|
49
|
+
assert.equal(registry.machineSyncCommand(["claude", "pi"]), "cairn sync-pi --apply", "legacy singular machine sync should retain its Pi preference");
|
|
50
|
+
assert.deepEqual(registry.machineSyncCommands(["codex"]), []);
|
|
43
51
|
|
|
44
52
|
const schema = JSON.parse(readFileSync(join(root, "schemas", "cairnkeep-setup.schema.json"), "utf8"));
|
|
45
53
|
assert.deepEqual(schema.properties.harnesses.items.enum, registry.HARNESS_IDS, "setup schema drifted from harness registry");
|
|
@@ -56,6 +64,7 @@ assert.equal(setup.status, 0, setup.stderr);
|
|
|
56
64
|
const result = JSON.parse(setup.stdout);
|
|
57
65
|
assert.deepEqual(result.harnesses, ["codex"]);
|
|
58
66
|
assert.equal(result.machine_sync.command, null);
|
|
67
|
+
assert.deepEqual(result.machine_sync.commands, []);
|
|
59
68
|
assert.deepEqual(result.launch_commands, [".ai/start-codex.sh"]);
|
|
60
69
|
assert.match(readFileSync(join(target, ".codex", "config.toml"), "utf8"), /\[mcp_servers\.cairn-memory-local\][\s\S]*command = "cairn"[\s\S]*args = \["memory-server"\]/);
|
|
61
70
|
|
|
@@ -73,6 +82,24 @@ const launch = spawnSync(join(target, ".ai", "start-codex.sh"), ["--fixture"], {
|
|
|
73
82
|
assert.equal(launch.status, 0, launch.stderr);
|
|
74
83
|
assert.deepEqual(launch.stdout.trim().split("\n"), [target, "loaded", "--fixture"]);
|
|
75
84
|
|
|
85
|
+
const ompTarget = join(sandbox, "omp-project");
|
|
86
|
+
const ompSetup = spawnSync(join(root, "bin", "cairn"), [
|
|
87
|
+
"setup", ompTarget, "--git", "init", "--harness", "omp", "--memory", "local", "--yes", "--json",
|
|
88
|
+
], { encoding: "utf8", shell: false });
|
|
89
|
+
assert.equal(ompSetup.status, 0, ompSetup.stderr);
|
|
90
|
+
assert.deepEqual(JSON.parse(ompSetup.stdout).harnesses, ["omp"]);
|
|
91
|
+
const fakeOmp = join(fakeBin, "omp");
|
|
92
|
+
writeFileSync(fakeOmp, "#!/bin/sh\nprintf '%s\\n' \"$PWD\" \"${CAIRN_REGISTRY_TEST:-}\" \"$*\"\n");
|
|
93
|
+
chmodSync(fakeOmp, 0o755);
|
|
94
|
+
writeFileSync(join(ompTarget, ".ai", ".env"), "CAIRN_REGISTRY_TEST=omp-loaded\n", { mode: 0o600 });
|
|
95
|
+
const ompLaunch = spawnSync(join(ompTarget, ".ai", "start-omp.sh"), ["--fixture"], {
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
shell: false,
|
|
98
|
+
env: { ...process.env, PATH: `${fakeBin}:${process.env.PATH}` },
|
|
99
|
+
});
|
|
100
|
+
assert.equal(ompLaunch.status, 0, ompLaunch.stderr);
|
|
101
|
+
assert.deepEqual(ompLaunch.stdout.trim().split("\n"), [ompTarget, "omp-loaded", "--fixture"]);
|
|
102
|
+
|
|
76
103
|
const noMemory = join(sandbox, "codex-no-memory");
|
|
77
104
|
const noMemorySetup = spawnSync(join(root, "bin", "cairn"), [
|
|
78
105
|
"setup", noMemory, "--git", "init", "--harness", "codex", "--memory", "none", "--yes",
|
|
@@ -68,6 +68,7 @@ assert.equal(result.launch_commands.some((line) => /start-pi\.sh/.test(line)), t
|
|
|
68
68
|
assert.equal(Array.isArray(result.recovery), true);
|
|
69
69
|
assert.equal(result.machine_sync.automatic, false);
|
|
70
70
|
assert.match(result.machine_sync.command, /^cairn sync(?:-pi)? --(?:check|apply)/);
|
|
71
|
+
assert.deepEqual(result.machine_sync.commands, ["cairn sync --apply", "cairn sync-pi --apply"]);
|
|
71
72
|
NODE
|
|
72
73
|
|
|
73
74
|
complete_human="$tmp/complete.txt"
|
|
@@ -86,7 +87,25 @@ for expected in \
|
|
|
86
87
|
"Recovery:"; do
|
|
87
88
|
grep -qF "$expected" "$complete_human" || fail "human output missing $expected"
|
|
88
89
|
done
|
|
89
|
-
grep -
|
|
90
|
+
grep -qF 'Machine sync: cairn sync --apply (not run automatically)' "$complete_human" || fail "human output omitted Claude machine sync"
|
|
91
|
+
grep -qF 'Machine sync: cairn sync-pi --apply (not run automatically)' "$complete_human" || fail "human output omitted Pi machine sync"
|
|
92
|
+
|
|
93
|
+
omp_json="$tmp/omp.json"
|
|
94
|
+
"$ROOT/bin/cairn" setup "$tmp/omp target" \
|
|
95
|
+
--git init --harness omp --memory local --yes --json >"$omp_json"
|
|
96
|
+
node --input-type=module - "$omp_json" <<'NODE'
|
|
97
|
+
import assert from "node:assert/strict";
|
|
98
|
+
import fs from "node:fs";
|
|
99
|
+
|
|
100
|
+
const result = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
101
|
+
assert.deepEqual(result.harnesses, ["omp"]);
|
|
102
|
+
assert.deepEqual(result.launch_commands, [".ai/start-omp.sh"]);
|
|
103
|
+
assert.equal(result.machine_sync.automatic, false);
|
|
104
|
+
assert.equal(result.machine_sync.command, "cairn sync-omp --apply");
|
|
105
|
+
assert.deepEqual(result.machine_sync.commands, ["cairn sync-omp --apply"]);
|
|
106
|
+
NODE
|
|
107
|
+
[[ -x "$tmp/omp target/.ai/start-omp.sh" ]] || fail "OMP launcher missing"
|
|
108
|
+
grep -qF 'cairn evidence run --harness omp -- omp' "$tmp/omp target/.ai/start-omp.sh" || fail "OMP launcher work-evidence integration missing"
|
|
90
109
|
|
|
91
110
|
limited_json="$tmp/limited.json"
|
|
92
111
|
"$ROOT/bin/cairn" setup "$tmp/limited target" \
|
|
@@ -345,7 +345,7 @@ async function testStructuredSetupSelectors(setup, fixture) {
|
|
|
345
345
|
"Apply this setup plan?",
|
|
346
346
|
]);
|
|
347
347
|
const harnessPrompt = selections.find(({ message }) => message === "Coding harnesses");
|
|
348
|
-
assert.deepEqual(harnessPrompt.choices.map(({ value }) => value), ["claude", "opencode", "pi", "kimi", "qwen", "codex"]);
|
|
348
|
+
assert.deepEqual(harnessPrompt.choices.map(({ value }) => value), ["claude", "opencode", "pi", "omp", "kimi", "qwen", "codex"]);
|
|
349
349
|
assert.match(harnessPrompt.choices.find(({ value }) => value === "codex").label, /Codex CLI/);
|
|
350
350
|
assertNoManagedPaths(fixture.empty, "cancelled structured setup");
|
|
351
351
|
}
|
|
@@ -16,7 +16,7 @@ exec node "$ROOT/bin/cairn" "\$@"
|
|
|
16
16
|
EOF
|
|
17
17
|
chmod +x "$fake_bin/cairn"
|
|
18
18
|
|
|
19
|
-
for harness in claude opencode pi kimi qwen codex; do
|
|
19
|
+
for harness in claude opencode pi omp kimi qwen codex; do
|
|
20
20
|
project="$tmp/$harness"
|
|
21
21
|
mkdir -p "$project/.ai" "$project/.agentfs"
|
|
22
22
|
cp "$ROOT/templates/start-$harness.sh.template" "$project/.ai/start-$harness.sh"
|
package/scripts/uninstall.sh
CHANGED
|
@@ -4,8 +4,14 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
const ROOT = resolve(process.cwd());
|
|
7
|
-
const MINIMUM_SDK = [1,
|
|
7
|
+
const MINIMUM_SDK = [1, 32, 1];
|
|
8
8
|
const MINIMUM_NODE_SERVER = [2, 0, 5];
|
|
9
|
+
const TRANSITIVE_MINIMUMS = new Map([
|
|
10
|
+
["fast-uri", [3, 1, 8]],
|
|
11
|
+
["hono", [4, 13, 13]],
|
|
12
|
+
["ip-address", [10, 7, 3]],
|
|
13
|
+
["proxy-addr", [2, 0, 8]],
|
|
14
|
+
]);
|
|
9
15
|
const DIRECT_HONO_PACKAGES = new Set(["hono", "@hono/node-server"]);
|
|
10
16
|
const PACKAGE_ROOTS = [
|
|
11
17
|
{ label: "root", directory: "." },
|
|
@@ -110,11 +116,23 @@ function verifyPackageRoot({ label, directory }) {
|
|
|
110
116
|
`${label} resolved @hono/node-server`,
|
|
111
117
|
);
|
|
112
118
|
}
|
|
119
|
+
|
|
120
|
+
for (const [packageName, minimum] of TRANSITIVE_MINIMUMS) {
|
|
121
|
+
const packages = resolvedPackages(lock, packageName);
|
|
122
|
+
assert.ok(packages.length > 0, `${label}: resolved ${packageName} is missing`);
|
|
123
|
+
for (const [path, packageValue] of packages) {
|
|
124
|
+
assertMinimum(
|
|
125
|
+
parseVersion(packageValue.version, `${label} ${path}`),
|
|
126
|
+
minimum,
|
|
127
|
+
`${label} resolved ${packageName}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
try {
|
|
116
134
|
PACKAGE_ROOTS.forEach(verifyPackageRoot);
|
|
117
|
-
process.stdout.write("PASS: dependency manifests and lock graphs
|
|
135
|
+
process.stdout.write("PASS: dependency manifests and lock graphs exclude known MCP transport advisories\n");
|
|
118
136
|
} catch (error) {
|
|
119
137
|
process.stderr.write(`FAIL: dependency security policy: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
120
138
|
process.exitCode = 1;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Cairnkeep launcher for oh-my-pi (OMP). Loads this repo's private .ai/.env,
|
|
3
|
+
# runs the optional pre/post seams, and launches OMP from the project root.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
6
|
+
|
|
7
|
+
if [[ -f "$ROOT_DIR/.ai/.env" ]]; then
|
|
8
|
+
set -a
|
|
9
|
+
# shellcheck disable=SC1091
|
|
10
|
+
. "$ROOT_DIR/.ai/.env"
|
|
11
|
+
set +a
|
|
12
|
+
fi
|
|
13
|
+
if [[ -f "$ROOT_DIR/.ai/pre-launch.sh" ]]; then
|
|
14
|
+
# shellcheck disable=SC1091
|
|
15
|
+
. "$ROOT_DIR/.ai/pre-launch.sh"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
cd "$ROOT_DIR"
|
|
19
|
+
|
|
20
|
+
case "${CAIRN_WORK_EVIDENCE:-}" in
|
|
21
|
+
1|true|TRUE|yes|YES|on|ON)
|
|
22
|
+
if command -v cairn >/dev/null 2>&1 && cairn evidence --help >/dev/null 2>&1; then
|
|
23
|
+
set +e
|
|
24
|
+
cairn evidence run --harness omp -- omp "$@"
|
|
25
|
+
CAIRN_EXIT_STATUS=$?
|
|
26
|
+
set -e
|
|
27
|
+
export CAIRN_EXIT_STATUS
|
|
28
|
+
if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
|
|
29
|
+
# shellcheck disable=SC1091
|
|
30
|
+
. "$ROOT_DIR/.ai/post-exit.sh" || true
|
|
31
|
+
fi
|
|
32
|
+
exit "$CAIRN_EXIT_STATUS"
|
|
33
|
+
fi
|
|
34
|
+
echo "cairn: compatible evidence command unavailable; launching without capture" >&2
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
if [[ -f "$ROOT_DIR/.ai/post-exit.sh" ]]; then
|
|
38
|
+
set +e
|
|
39
|
+
omp "$@"
|
|
40
|
+
CAIRN_EXIT_STATUS=$?
|
|
41
|
+
set -e
|
|
42
|
+
export CAIRN_EXIT_STATUS
|
|
43
|
+
# shellcheck disable=SC1091
|
|
44
|
+
. "$ROOT_DIR/.ai/post-exit.sh" || true
|
|
45
|
+
exit "$CAIRN_EXIT_STATUS"
|
|
46
|
+
fi
|
|
47
|
+
exec omp "$@"
|