@cairnkeep/cli 2.2.0 → 2.3.0
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 +23 -0
- package/README.md +6 -3
- package/bin/cairn +5 -0
- package/containers/workspace-entrypoint.sh +4 -0
- package/docs/containers.md +8 -2
- package/docs/learning/PRODUCTION-PLAN.md +122 -0
- package/docs/learning/README.md +58 -0
- package/docs/learning/instructor-guide.md +60 -0
- package/docs/learning/lessons/L00-why-cairnkeep.md +87 -0
- package/docs/learning/lessons/L01-safe-trial.md +96 -0
- package/docs/learning/lessons/L02-installation.md +115 -0
- package/docs/learning/lessons/L03-first-project.md +116 -0
- package/docs/learning/lessons/L04-memory-fundamentals.md +44 -0
- package/docs/learning/lessons/L05-daily-workflow.md +43 -0
- package/docs/learning/lessons/L06-repository-quality.md +45 -0
- package/docs/learning/lessons/L07-privacy-and-storage.md +42 -0
- package/docs/learning/lessons/L08-multiple-machines.md +41 -0
- package/docs/learning/lessons/L09-domain-knowledge.md +42 -0
- package/docs/learning/lessons/L10-context-exploration.md +42 -0
- package/docs/learning/lessons/L11-containers.md +42 -0
- package/docs/learning/lessons/L12-managed-overlays.md +43 -0
- package/docs/learning/templates/lesson-template.md +61 -0
- package/docs/learning/templates/video-script-template.md +50 -0
- package/docs/learning/tracks/operator.md +14 -0
- package/docs/learning/tracks/practitioner.md +15 -0
- package/docs/learning/tracks/quickstart.md +14 -0
- package/docs/learning/video-scripts/L00-why-cairnkeep.md +76 -0
- package/docs/learning/video-scripts/L01-safe-trial.md +78 -0
- package/docs/learning/video-scripts/L02-installation.md +90 -0
- package/docs/learning/video-scripts/L03-first-project.md +96 -0
- package/docs/operating.md +14 -0
- package/package.json +1 -1
- package/scripts/cairn-container +2 -1
- package/scripts/completion.sh +80 -0
- package/scripts/test-completion.sh +24 -0
- package/scripts/test-container-runner.sh +4 -0
- package/scripts/test-container.sh +32 -1
- package/scripts/test-learning-docs.sh +78 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# L00 Video Script - Why Cairnkeep?
|
|
2
|
+
|
|
3
|
+
**Target duration:** 7 minutes
|
|
4
|
+
**Companion lesson:** [L00](../lessons/L00-why-cairnkeep.md)
|
|
5
|
+
|
|
6
|
+
## Recording setup
|
|
7
|
+
|
|
8
|
+
- Show only the four-layer table from the lesson.
|
|
9
|
+
- Prepare one slide containing the question: “What should survive this session?”
|
|
10
|
+
- No terminal or private project is required.
|
|
11
|
+
|
|
12
|
+
## 00:00 - Hook
|
|
13
|
+
|
|
14
|
+
**Say:** “A coding agent can solve a difficult problem today and make the same
|
|
15
|
+
mistake tomorrow. The missing piece is often not more context. It is a small,
|
|
16
|
+
reviewed record of what the project already learned.”
|
|
17
|
+
|
|
18
|
+
**Show:** The question slide.
|
|
19
|
+
|
|
20
|
+
## 00:35 - Outcome
|
|
21
|
+
|
|
22
|
+
**Say:** “In this lesson you will learn where Cairnkeep fits, what it should
|
|
23
|
+
remember, and what must remain in the repository or another canonical system.”
|
|
24
|
+
|
|
25
|
+
**Show:** The lesson outcome.
|
|
26
|
+
|
|
27
|
+
## 01:00 - Four layers
|
|
28
|
+
|
|
29
|
+
**Say:** “Start with four different layers. The current conversation is working
|
|
30
|
+
context. Cairnkeep memory is a set of concise operational facts. The project
|
|
31
|
+
wiki is derived, cited knowledge. Code, tests, specifications, policies, and
|
|
32
|
+
issue trackers remain canonical.”
|
|
33
|
+
|
|
34
|
+
**Show:** Reveal one table row at a time.
|
|
35
|
+
|
|
36
|
+
**Say:** “If these disagree, the canonical source wins. Cairnkeep helps the
|
|
37
|
+
agent find and apply knowledge; it does not redefine truth.”
|
|
38
|
+
|
|
39
|
+
## 02:40 - Memory versus RAG
|
|
40
|
+
|
|
41
|
+
**Say:** “Document RAG answers a different question: which parts of a larger
|
|
42
|
+
document are relevant right now? Cairnkeep can integrate with a RAG system, but
|
|
43
|
+
it does not require one. A decision such as ‘use transactional migrations’ is a
|
|
44
|
+
memory. The complete migration policy remains a document.”
|
|
45
|
+
|
|
46
|
+
**Show:** Memory on the left, document retrieval on the right.
|
|
47
|
+
|
|
48
|
+
## 03:40 - Classification exercise
|
|
49
|
+
|
|
50
|
+
**Say:** “Consider four statements. A confirmed API behavior can become memory
|
|
51
|
+
and should still be traceable to code or a specification. A complete API
|
|
52
|
+
specification remains canonical. A debugging hypothesis stays in the session
|
|
53
|
+
until confirmed. A verified operational prohibition is a durable constraint.”
|
|
54
|
+
|
|
55
|
+
**Show:** The four L00 exercise statements and reveal answers after a pause.
|
|
56
|
+
|
|
57
|
+
## 05:10 - Boundary
|
|
58
|
+
|
|
59
|
+
**Say:** “Cairnkeep does not store every sentence from a session. The default
|
|
60
|
+
stdio setup stores memory on this computer. Remote storage, model extraction,
|
|
61
|
+
embeddings, and document RAG are all explicit, optional choices.”
|
|
62
|
+
|
|
63
|
+
**Show:** “Local by default. Optional means opt-in.”
|
|
64
|
+
|
|
65
|
+
## 06:10 - Recap
|
|
66
|
+
|
|
67
|
+
**Say:** “Remember three things: durable memory is selective, derived knowledge
|
|
68
|
+
does not overrule canonical sources, and optional services are not prerequisites.
|
|
69
|
+
In L01 we will inspect the published container without installing the complete
|
|
70
|
+
workflow.”
|
|
71
|
+
|
|
72
|
+
## Editing notes
|
|
73
|
+
|
|
74
|
+
- Chapters: Problem, Four layers, Memory vs RAG, Exercise, Boundary.
|
|
75
|
+
- Link L00 and L01 in the description.
|
|
76
|
+
- Re-record the version-independent slide only if the conceptual model changes.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# L01 Video Script - Try it safely
|
|
2
|
+
|
|
3
|
+
**Target duration:** 8 minutes
|
|
4
|
+
**Companion lesson:** [L01](../lessons/L01-safe-trial.md)
|
|
5
|
+
|
|
6
|
+
## Recording setup
|
|
7
|
+
|
|
8
|
+
- Use a terminal with no registry credentials or unrelated containers visible.
|
|
9
|
+
- Remove any prior `cairnkeep-course-data` volume before recording.
|
|
10
|
+
- Put the complete Podman command in a prepared text file to avoid typing errors.
|
|
11
|
+
|
|
12
|
+
## 00:00 - Hook
|
|
13
|
+
|
|
14
|
+
**Say:** “Before installing commands or changing a coding harness, we can inspect
|
|
15
|
+
how the Cairnkeep server is packaged and what it is allowed to persist.”
|
|
16
|
+
|
|
17
|
+
**Show:** An empty terminal and `podman --version`.
|
|
18
|
+
|
|
19
|
+
## 00:30 - Outcome
|
|
20
|
+
|
|
21
|
+
**Say:** “We will run the server as an unprivileged, read-only container, expose
|
|
22
|
+
no network port, and identify the single named volume that would hold memory.”
|
|
23
|
+
|
|
24
|
+
## 01:00 - Pull the image
|
|
25
|
+
|
|
26
|
+
**Say:** “I am pinning the course version rather than using `latest`, so this
|
|
27
|
+
recording and the written instructions refer to the same artifact.”
|
|
28
|
+
|
|
29
|
+
**Do:** Run the L01 `podman pull` command.
|
|
30
|
+
|
|
31
|
+
**Point out:** The exact image name and version, without dwelling on layer output.
|
|
32
|
+
|
|
33
|
+
## 02:00 - Explain the isolation flags
|
|
34
|
+
|
|
35
|
+
**Say:** “The root filesystem is read-only, Linux capabilities are dropped,
|
|
36
|
+
new privileges are disabled, and only a constrained temporary directory plus a
|
|
37
|
+
named data volume are writable. No home directory or project is mounted.”
|
|
38
|
+
|
|
39
|
+
**Show:** Highlight the command in sections before running it.
|
|
40
|
+
|
|
41
|
+
## 03:15 - Start and stop
|
|
42
|
+
|
|
43
|
+
**Do:** Run the direct Podman stdio command.
|
|
44
|
+
|
|
45
|
+
**Say:** “The quiet process is expected. It is waiting for MCP messages on
|
|
46
|
+
standard input; it is not a web application.”
|
|
47
|
+
|
|
48
|
+
**Do:** Pause for two seconds, then press `Ctrl-C`.
|
|
49
|
+
|
|
50
|
+
## 04:30 - Inspect persistence
|
|
51
|
+
|
|
52
|
+
**Do:** Run `podman volume inspect cairnkeep-course-data`.
|
|
53
|
+
|
|
54
|
+
**Say:** “The container was disposable. The named volume is separate and would
|
|
55
|
+
survive replacement of the container. Removing that volume would remove its
|
|
56
|
+
stored databases.”
|
|
57
|
+
|
|
58
|
+
## 05:40 - Boundary and recovery
|
|
59
|
+
|
|
60
|
+
**Say:** “This test exposed no port and contacted no remote Cairnkeep server.
|
|
61
|
+
If your environment blocks GHCR or Podman, skip this optional trial and use the
|
|
62
|
+
npm installation in L02.”
|
|
63
|
+
|
|
64
|
+
**Do:** Run `podman volume exists cairnkeep-course-data` and show status `0`.
|
|
65
|
+
|
|
66
|
+
## 06:50 - Cleanup and recap
|
|
67
|
+
|
|
68
|
+
**Do:** Run `podman volume rm cairnkeep-course-data`.
|
|
69
|
+
|
|
70
|
+
**Say:** “Never run that cleanup against a real memory volume. We proved three
|
|
71
|
+
things: the server runs unprivileged, stdio exposes no listener, and persistence
|
|
72
|
+
is explicit. In L02 we install the complete local workflow.”
|
|
73
|
+
|
|
74
|
+
## Editing notes
|
|
75
|
+
|
|
76
|
+
- Blur unrelated image and volume names if they appear.
|
|
77
|
+
- Add a warning caption before volume removal.
|
|
78
|
+
- Link the Containers documentation and L02.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# L02 Video Script - Install the local workflow
|
|
2
|
+
|
|
3
|
+
**Target duration:** 12 minutes
|
|
4
|
+
**Companion lesson:** [L02](../lessons/L02-installation.md)
|
|
5
|
+
|
|
6
|
+
## Recording setup
|
|
7
|
+
|
|
8
|
+
- Use a clean user profile or disposable VM with Node.js 22 or newer.
|
|
9
|
+
- Install Claude Code but remove any existing `cairn-memory` registration.
|
|
10
|
+
- Ensure no private MCP servers, provider URLs, or environment variables appear.
|
|
11
|
+
- Prepare a snapshot before installation for a repeatable retake.
|
|
12
|
+
|
|
13
|
+
## 00:00 - Hook
|
|
14
|
+
|
|
15
|
+
**Say:** “The most common incomplete installation has a running memory server
|
|
16
|
+
but no Cairnkeep commands or hooks. We will install and verify all three parts
|
|
17
|
+
of the local workflow.”
|
|
18
|
+
|
|
19
|
+
**Show:** A three-part diagram: CLI, MCP registration, operating layer.
|
|
20
|
+
|
|
21
|
+
## 00:40 - Outcome
|
|
22
|
+
|
|
23
|
+
**Say:** “At the end, Claude Code can start the local `cairn-memory` server and
|
|
24
|
+
the managed command assets pass a non-writing drift check.”
|
|
25
|
+
|
|
26
|
+
## 01:10 - Runtime preflight
|
|
27
|
+
|
|
28
|
+
**Do:** Run `node --version` and `npm --version`.
|
|
29
|
+
|
|
30
|
+
**Say:** “Cairnkeep requires Node.js 22 or newer. I verify that before changing
|
|
31
|
+
the machine.”
|
|
32
|
+
|
|
33
|
+
## 02:00 - Install the CLI
|
|
34
|
+
|
|
35
|
+
**Do:** Run the pinned npm install and `cairn --version` from L02.
|
|
36
|
+
|
|
37
|
+
**Say:** “For a course recording I pin the version. Users can follow the current
|
|
38
|
+
release instructions when the lesson is updated.”
|
|
39
|
+
|
|
40
|
+
## 03:30 - Register MCP
|
|
41
|
+
|
|
42
|
+
**Say:** “The stable server name is `cairn-memory`. This command tells Claude
|
|
43
|
+
Code to launch a local stdio child process. It does not configure a remote URL.”
|
|
44
|
+
|
|
45
|
+
**Do:** Run the `claude mcp add` command, followed by `claude mcp get cairn-memory`.
|
|
46
|
+
|
|
47
|
+
**Point out:** The command is `cairn memory-server`.
|
|
48
|
+
|
|
49
|
+
## 05:15 - Install the operating layer
|
|
50
|
+
|
|
51
|
+
**Say:** “MCP tools alone do not install `/remember`, `/recall`, wiki commands,
|
|
52
|
+
review workflows, agents, or hooks. That is the job of `cairn sync`.”
|
|
53
|
+
|
|
54
|
+
**Do:** Run `cairn sync --apply`.
|
|
55
|
+
|
|
56
|
+
**Pause:** Allow the learner to see the asset summary.
|
|
57
|
+
|
|
58
|
+
## 07:00 - Verify without writing
|
|
59
|
+
|
|
60
|
+
**Do:** Run `cairn sync --check`.
|
|
61
|
+
|
|
62
|
+
**Say:** “This is the repeatable drift check. A healthy result means the
|
|
63
|
+
operating layer matches the installed distribution. MCP registration plus this
|
|
64
|
+
check distinguishes a complete setup from a partial one.”
|
|
65
|
+
|
|
66
|
+
## 08:30 - Common failure demonstration
|
|
67
|
+
|
|
68
|
+
**Say:** “If slash commands are missing, do not reinstall everything blindly.
|
|
69
|
+
First check the MCP registration, then check operating-layer drift, and restart
|
|
70
|
+
the harness after applying assets.”
|
|
71
|
+
|
|
72
|
+
**Show:** The two verification commands together.
|
|
73
|
+
|
|
74
|
+
## 09:40 - Boundary
|
|
75
|
+
|
|
76
|
+
**Say:** “This default server is local. We did not configure embeddings,
|
|
77
|
+
document RAG, or remote HTTP memory. Those choices have separate lessons
|
|
78
|
+
because they change data flow.”
|
|
79
|
+
|
|
80
|
+
## 10:30 - Recap
|
|
81
|
+
|
|
82
|
+
**Say:** “The machine setup has three pieces: CLI, MCP registration, and the
|
|
83
|
+
operating layer. In L03 we add the fourth piece, a bootstrapped project, and
|
|
84
|
+
prove memory survives a new session.”
|
|
85
|
+
|
|
86
|
+
## Editing notes
|
|
87
|
+
|
|
88
|
+
- Chapter markers: Preflight, CLI, MCP, Operating layer, Verify, Boundary.
|
|
89
|
+
- Do not show the general MCP list if it contains unrelated server names.
|
|
90
|
+
- Link L02, the operating guide, and L03.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# L03 Video Script - Bootstrap the first project
|
|
2
|
+
|
|
3
|
+
**Target duration:** 13 minutes
|
|
4
|
+
**Companion lesson:** [L03](../lessons/L03-first-project.md)
|
|
5
|
+
|
|
6
|
+
## Recording setup
|
|
7
|
+
|
|
8
|
+
- Start from the completed L02 VM snapshot.
|
|
9
|
+
- Confirm `$HOME/cairnkeep-course/first-project` does not exist.
|
|
10
|
+
- Use only the synthetic UTC convention from the lesson.
|
|
11
|
+
- Prepare to stop and restart the harness during the recording.
|
|
12
|
+
|
|
13
|
+
## 00:00 - Hook
|
|
14
|
+
|
|
15
|
+
**Say:** “An installation is not proven by files on disk. The useful proof is a
|
|
16
|
+
fact accepted in one session and recalled in a new session.”
|
|
17
|
+
|
|
18
|
+
## 00:30 - Outcome
|
|
19
|
+
|
|
20
|
+
**Say:** “We will bootstrap a disposable Git repository, run diagnostics, store
|
|
21
|
+
one synthetic convention, restart Claude Code, and retrieve it again.”
|
|
22
|
+
|
|
23
|
+
## 01:00 - Create the project
|
|
24
|
+
|
|
25
|
+
**Do:** Run the directory, `git init`, and README commands from L03.
|
|
26
|
+
|
|
27
|
+
**Say:** “I use a disposable repository so no course artifact can reach a real
|
|
28
|
+
project.”
|
|
29
|
+
|
|
30
|
+
## 02:00 - Bootstrap
|
|
31
|
+
|
|
32
|
+
**Say:** “Bootstrap adds project launchers and derived-knowledge scaffolding. It
|
|
33
|
+
does not reinstall the machine-level MCP server or operating assets.”
|
|
34
|
+
|
|
35
|
+
**Do:** Run `cairn bootstrap "$PWD"` and copy `.ai/env.example` to `.ai/.env`.
|
|
36
|
+
|
|
37
|
+
**Point out:** The generated next steps without reading every created filename.
|
|
38
|
+
|
|
39
|
+
## 03:45 - Diagnose
|
|
40
|
+
|
|
41
|
+
**Do:** Run `cairn doctor`.
|
|
42
|
+
|
|
43
|
+
**Say:** “Optional unconfigured integrations may be skipped. A configured but
|
|
44
|
+
unreachable dependency is a failure and should be fixed before relying on it.”
|
|
45
|
+
|
|
46
|
+
## 05:00 - Launch and remember
|
|
47
|
+
|
|
48
|
+
**Do:** Start `./.ai/start-claude.sh`.
|
|
49
|
+
|
|
50
|
+
**Say:** “I launch through the project script so every session loads the same
|
|
51
|
+
project environment.”
|
|
52
|
+
|
|
53
|
+
**Do in Claude:** `/remember Course convention: examples use UTC timestamps.`
|
|
54
|
+
|
|
55
|
+
**Point out:** The durable key returned by the workflow.
|
|
56
|
+
|
|
57
|
+
## 07:00 - Recall
|
|
58
|
+
|
|
59
|
+
**Do in Claude:** `/recall UTC timestamps`
|
|
60
|
+
|
|
61
|
+
**Say:** “This confirms retrieval inside the first session, but not yet
|
|
62
|
+
persistence across sessions.”
|
|
63
|
+
|
|
64
|
+
## 08:00 - Persistence proof
|
|
65
|
+
|
|
66
|
+
**Do:** Exit Claude Code, run the launcher again, and repeat `/recall UTC timestamps`.
|
|
67
|
+
|
|
68
|
+
**Pause:** Keep the retrieved result visible.
|
|
69
|
+
|
|
70
|
+
**Say:** “This is the important proof. The second harness process recovered the
|
|
71
|
+
accepted fact from durable local storage.”
|
|
72
|
+
|
|
73
|
+
## 10:00 - Locate storage
|
|
74
|
+
|
|
75
|
+
**Do:** Exit the harness and run `cairn memory path`.
|
|
76
|
+
|
|
77
|
+
**Say:** “The command reports the local store location. Nothing in bootstrap
|
|
78
|
+
silently selected a remote server.”
|
|
79
|
+
|
|
80
|
+
## 11:00 - Recovery and boundary
|
|
81
|
+
|
|
82
|
+
**Say:** “If recall fails after restart, compare the launcher and environment,
|
|
83
|
+
check the MCP registration, and verify the storage path. Do not solve routing
|
|
84
|
+
problems by copying database files while the server is running.”
|
|
85
|
+
|
|
86
|
+
## 12:00 - Recap
|
|
87
|
+
|
|
88
|
+
**Say:** “We bootstrapped one project, used its launcher, and proved persistence
|
|
89
|
+
across a new session. The quickstart is complete. L04 will teach how to choose
|
|
90
|
+
keys, scopes, search, history, and the memory review gate.”
|
|
91
|
+
|
|
92
|
+
## Editing notes
|
|
93
|
+
|
|
94
|
+
- Do not cut away the harness exit/restart; it is the central proof.
|
|
95
|
+
- Add captions for “first session” and “second session”.
|
|
96
|
+
- Link L03 and L04 in the description.
|
package/docs/operating.md
CHANGED
|
@@ -26,6 +26,20 @@ installs. This guide covers all three in order.
|
|
|
26
26
|
- Optional: rootless Podman for the containerized memory server and isolated
|
|
27
27
|
workspace base described in [Containers](containers.md).
|
|
28
28
|
|
|
29
|
+
## Shell completion
|
|
30
|
+
|
|
31
|
+
Generate completion definitions directly from the CLI:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cairn completion bash
|
|
35
|
+
cairn completion zsh
|
|
36
|
+
cairn completion fish
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Distributions can install these outputs into the platform's normal completion
|
|
40
|
+
directories. They can also be loaded for the current shell, for example with
|
|
41
|
+
`source <(cairn completion bash)`.
|
|
42
|
+
|
|
29
43
|
## Setup order (Claude Code)
|
|
30
44
|
|
|
31
45
|
The commands below install the default local topology: `cairn-memory` runs as a
|
package/package.json
CHANGED
package/scripts/cairn-container
CHANGED
|
@@ -171,7 +171,8 @@ run_workspace() {
|
|
|
171
171
|
repo=$(absolute_dir "$repo")
|
|
172
172
|
case "$mode" in sandbox | shared) ;; *) fail "--mode must be sandbox or shared" ;; esac
|
|
173
173
|
if [ -z "$workspace_volume" ]; then
|
|
174
|
-
|
|
174
|
+
repo_id=$(printf '%s' "$repo" | cksum | awk '{print $1}')
|
|
175
|
+
workspace_volume="cairnkeep-workspace-$(safe_name "$(basename "$repo")")-$repo_id"
|
|
175
176
|
fi
|
|
176
177
|
|
|
177
178
|
require_engine
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
shell=${1:-}
|
|
5
|
+
|
|
6
|
+
case "$shell" in
|
|
7
|
+
bash)
|
|
8
|
+
cat <<'EOF'
|
|
9
|
+
_cairn_complete() {
|
|
10
|
+
local current previous commands
|
|
11
|
+
COMPREPLY=()
|
|
12
|
+
current=${COMP_WORDS[COMP_CWORD]}
|
|
13
|
+
previous=${COMP_WORDS[COMP_CWORD-1]:-}
|
|
14
|
+
commands="bootstrap memory-server sync doctor memory audit-timer uninstall completion version help"
|
|
15
|
+
if (( COMP_CWORD == 1 )); then
|
|
16
|
+
COMPREPLY=( $(compgen -W "$commands" -- "$current") )
|
|
17
|
+
return
|
|
18
|
+
fi
|
|
19
|
+
case "${COMP_WORDS[1]}" in
|
|
20
|
+
bootstrap) COMPREPLY=( $(compgen -W "--untracked" -- "$current") ) ;;
|
|
21
|
+
sync) COMPREPLY=( $(compgen -W "--apply --live-root" -- "$current") ) ;;
|
|
22
|
+
memory) COMPREPLY=( $(compgen -W "path export import" -- "$current") ) ;;
|
|
23
|
+
audit-timer) COMPREPLY=( $(compgen -W "--on-calendar --para-root --render-only" -- "$current") ) ;;
|
|
24
|
+
uninstall) COMPREPLY=( $(compgen -W "--dry-run --yes --purge-memory --live-root" -- "$current") ) ;;
|
|
25
|
+
completion) COMPREPLY=( $(compgen -W "bash zsh fish" -- "$current") ) ;;
|
|
26
|
+
esac
|
|
27
|
+
}
|
|
28
|
+
complete -F _cairn_complete cairn
|
|
29
|
+
EOF
|
|
30
|
+
;;
|
|
31
|
+
zsh)
|
|
32
|
+
cat <<'EOF'
|
|
33
|
+
#compdef cairn
|
|
34
|
+
_cairn() {
|
|
35
|
+
local -a commands
|
|
36
|
+
commands=(
|
|
37
|
+
'bootstrap:scaffold a project'
|
|
38
|
+
'memory-server:run the memory MCP server'
|
|
39
|
+
'sync:install the Claude operating layer'
|
|
40
|
+
'doctor:check runtime dependencies and endpoints'
|
|
41
|
+
'memory:manage the durable memory store'
|
|
42
|
+
'audit-timer:install a memory and wiki audit timer'
|
|
43
|
+
'uninstall:remove installed Cairnkeep components safely'
|
|
44
|
+
'completion:generate shell completion'
|
|
45
|
+
'version:show the installed version'
|
|
46
|
+
'help:show help'
|
|
47
|
+
)
|
|
48
|
+
if (( CURRENT == 2 )); then
|
|
49
|
+
_describe 'command' commands
|
|
50
|
+
return
|
|
51
|
+
fi
|
|
52
|
+
case $words[2] in
|
|
53
|
+
bootstrap) _arguments '--untracked[keep scaffold out of Git]' '*:project directory:_files -/' ;;
|
|
54
|
+
sync) _arguments '--apply[apply changes]' '--live-root[project Claude root]:directory:_files -/' ;;
|
|
55
|
+
memory) _values 'memory command' path export import ;;
|
|
56
|
+
audit-timer) _arguments '--on-calendar[systemd calendar]:calendar:' '--para-root[PARA root]:directory:_files -/' '--render-only[render directory]:directory:_files -/' ;;
|
|
57
|
+
uninstall) _arguments '--dry-run[show changes]' '--yes[skip confirmation]' '--purge-memory[delete memory]' '--live-root[project Claude root]:directory:_files -/' '*:project directory:_files -/' ;;
|
|
58
|
+
completion) _values 'shell' bash zsh fish ;;
|
|
59
|
+
*) _arguments '*:argument:_files' ;;
|
|
60
|
+
esac
|
|
61
|
+
}
|
|
62
|
+
_cairn "$@"
|
|
63
|
+
EOF
|
|
64
|
+
;;
|
|
65
|
+
fish)
|
|
66
|
+
cat <<'EOF'
|
|
67
|
+
complete -c cairn -f
|
|
68
|
+
for command in bootstrap memory-server sync doctor memory audit-timer uninstall completion version help
|
|
69
|
+
complete -c cairn -n "not __fish_seen_subcommand_from bootstrap memory-server sync doctor memory audit-timer uninstall completion version help" -a $command
|
|
70
|
+
end
|
|
71
|
+
complete -c cairn -n "__fish_seen_subcommand_from bootstrap" -l untracked
|
|
72
|
+
complete -c cairn -n "__fish_seen_subcommand_from memory" -a "path export import"
|
|
73
|
+
complete -c cairn -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
74
|
+
EOF
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
printf 'usage: cairn completion bash|zsh|fish\n' >&2
|
|
78
|
+
exit 2
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
5
|
+
tmp=$(mktemp -d)
|
|
6
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
7
|
+
|
|
8
|
+
for shell in bash zsh fish; do
|
|
9
|
+
"$ROOT/bin/cairn" completion "$shell" > "$tmp/$shell"
|
|
10
|
+
[[ -s $tmp/$shell ]]
|
|
11
|
+
grep -q 'completion' "$tmp/$shell"
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
grep -q 'complete -F _cairn_complete cairn' "$tmp/bash"
|
|
15
|
+
grep -q '#compdef cairn' "$tmp/zsh"
|
|
16
|
+
grep -q 'complete -c cairn' "$tmp/fish"
|
|
17
|
+
|
|
18
|
+
if "$ROOT/bin/cairn" completion unsupported >/dev/null 2>&1; then
|
|
19
|
+
echo "completion accepted an unsupported shell" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
git -C "$ROOT" diff --check
|
|
24
|
+
echo "PASS: shell completion generation"
|
|
@@ -80,4 +80,8 @@ has_arg CAIRN_WORKSPACE_MODE=shared
|
|
|
80
80
|
lacks_arg "$repo:/source:ro,Z"
|
|
81
81
|
has_arg bash
|
|
82
82
|
|
|
83
|
+
repo_id=$(printf '%s' "$repo" | cksum | awk '{print $1}')
|
|
84
|
+
run_launcher workspace --repo "$repo" --image example/workspace:1
|
|
85
|
+
has_arg "cairnkeep-workspace-repository-$repo_id:/workspace:Z,U"
|
|
86
|
+
|
|
83
87
|
echo "PASS: container launcher security and workspace contracts"
|
|
@@ -21,12 +21,13 @@ server_image="localhost/cairnkeep-test-server:$suffix"
|
|
|
21
21
|
workspace_image="localhost/cairnkeep-test-workspace:$suffix"
|
|
22
22
|
volume="cairnkeep-test-data-$suffix"
|
|
23
23
|
sandbox_volume="cairnkeep-test-workspace-$suffix"
|
|
24
|
+
conflict_volume="cairnkeep-test-conflict-$suffix"
|
|
24
25
|
name="cairnkeep-test-$suffix"
|
|
25
26
|
tmp=$(mktemp -d)
|
|
26
27
|
|
|
27
28
|
cleanup() {
|
|
28
29
|
"$ENGINE" rm -f "$name" >/dev/null 2>&1 || true
|
|
29
|
-
"$ENGINE" volume rm -f "$volume" "$sandbox_volume" >/dev/null 2>&1 || true
|
|
30
|
+
"$ENGINE" volume rm -f "$volume" "$sandbox_volume" "$conflict_volume" >/dev/null 2>&1 || true
|
|
30
31
|
"$ENGINE" image rm -f "$server_image" "$workspace_image" >/dev/null 2>&1 || true
|
|
31
32
|
rm -rf "$tmp"
|
|
32
33
|
}
|
|
@@ -180,4 +181,34 @@ else
|
|
|
180
181
|
"$workspace_image" sh -c 'test "$(cat sandbox.txt)" = sandbox'
|
|
181
182
|
fi
|
|
182
183
|
|
|
184
|
+
# A pre-populated, unmarked volume must not be merged with repository content.
|
|
185
|
+
"$ENGINE" volume create "$conflict_volume" >/dev/null
|
|
186
|
+
if [[ "$ENGINE" == podman ]]; then
|
|
187
|
+
"$ENGINE" run --rm --userns=keep-id:uid=10001,gid=10001 \
|
|
188
|
+
--volume "$conflict_volume:/workspace:Z,U" \
|
|
189
|
+
--env CAIRN_WORKSPACE_MODE=shared \
|
|
190
|
+
"$workspace_image" sh -c 'printf unrelated > unrelated.txt'
|
|
191
|
+
else
|
|
192
|
+
"$ENGINE" run --rm --user root --entrypoint sh \
|
|
193
|
+
--volume "$conflict_volume:/workspace" "$workspace_image" \
|
|
194
|
+
-c 'printf unrelated > /workspace/unrelated.txt; chown -R 10001:10001 /workspace'
|
|
195
|
+
fi
|
|
196
|
+
if [[ "$ENGINE" == podman ]]; then
|
|
197
|
+
conflict_mounts=(
|
|
198
|
+
--userns=keep-id:uid=10001,gid=10001
|
|
199
|
+
--volume "$tmp/repo:/source:ro,Z"
|
|
200
|
+
--volume "$conflict_volume:/workspace:Z,U"
|
|
201
|
+
)
|
|
202
|
+
else
|
|
203
|
+
conflict_mounts=(
|
|
204
|
+
--volume "$tmp/repo:/source:ro"
|
|
205
|
+
--volume "$conflict_volume:/workspace"
|
|
206
|
+
)
|
|
207
|
+
fi
|
|
208
|
+
if "$ENGINE" run --rm "${conflict_mounts[@]}" \
|
|
209
|
+
--env CAIRN_WORKSPACE_MODE=sandbox "$workspace_image" true >/dev/null 2>&1; then
|
|
210
|
+
echo "FAIL: sandbox merged into a non-empty unmarked volume" >&2
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
183
214
|
echo "PASS: container stdio, guarded HTTP, persistence, shared, and sandbox modes"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
5
|
+
cd "$ROOT"
|
|
6
|
+
|
|
7
|
+
version=$(node -p "require('./package.json').version")
|
|
8
|
+
landing=docs/learning/README.md
|
|
9
|
+
production=docs/learning/PRODUCTION-PLAN.md
|
|
10
|
+
|
|
11
|
+
[[ -f "$landing" && -f "$production" ]]
|
|
12
|
+
grep -qF 'docs/learning/README.md' README.md
|
|
13
|
+
|
|
14
|
+
ready=0
|
|
15
|
+
brief=0
|
|
16
|
+
for number in $(seq -w 0 12); do
|
|
17
|
+
matches=(docs/learning/lessons/L"$number"-*.md)
|
|
18
|
+
[[ ${#matches[@]} -eq 1 && -f ${matches[0]} ]] || {
|
|
19
|
+
echo "learning path must contain exactly one L$number lesson" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
lesson=${matches[0]}
|
|
23
|
+
grep -q '^## Outcome$' "$lesson"
|
|
24
|
+
if grep -q '^\*\*Status:\*\* Ready' "$lesson"; then
|
|
25
|
+
ready=$((ready + 1))
|
|
26
|
+
grep -qF "Tested with:** Cairnkeep $version" "$lesson"
|
|
27
|
+
script=$(sed -n 's|.*(\.\./video-scripts/\([^)]*\)).*|docs/learning/video-scripts/\1|p' "$lesson")
|
|
28
|
+
[[ -n "$script" && -f "$script" ]] || {
|
|
29
|
+
echo "ready lesson has no presenter script: $lesson" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
}
|
|
32
|
+
grep -q '^## Common failures$' "$lesson"
|
|
33
|
+
grep -q '^## Privacy and trust boundary$' "$lesson"
|
|
34
|
+
elif grep -q '^\*\*Status:\*\* Brief' "$lesson"; then
|
|
35
|
+
brief=$((brief + 1))
|
|
36
|
+
grep -q '^## Acceptance criteria$' "$lesson"
|
|
37
|
+
grep -q '^## Planned video$' "$lesson"
|
|
38
|
+
else
|
|
39
|
+
echo "lesson has an unsupported status: $lesson" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
[[ $ready -eq 4 && $brief -eq 9 ]] || {
|
|
45
|
+
echo "unexpected learning status totals: ready=$ready brief=$brief" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for track in quickstart practitioner operator; do
|
|
50
|
+
file=docs/learning/tracks/$track.md
|
|
51
|
+
[[ -f "$file" ]]
|
|
52
|
+
grep -q '^# ' "$file"
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
node <<'NODE'
|
|
56
|
+
const { existsSync, readdirSync, readFileSync } = require("node:fs");
|
|
57
|
+
const { dirname, resolve } = require("node:path");
|
|
58
|
+
|
|
59
|
+
function markdownFiles(root) {
|
|
60
|
+
return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
|
|
61
|
+
const path = `${root}/${entry.name}`;
|
|
62
|
+
return entry.isDirectory() ? markdownFiles(path) : path.endsWith(".md") ? [path] : [];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const file of markdownFiles("docs/learning")) {
|
|
67
|
+
const text = readFileSync(file, "utf8");
|
|
68
|
+
for (const match of text.matchAll(/\]\(([^)]+)\)/g)) {
|
|
69
|
+
const target = match[1].split("#", 1)[0];
|
|
70
|
+
if (!target || /^[a-z]+:/i.test(target)) continue;
|
|
71
|
+
if (!existsSync(resolve(dirname(file), target))) {
|
|
72
|
+
throw new Error(`broken learning link in ${file}: ${match[1]}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
NODE
|
|
77
|
+
|
|
78
|
+
echo "PASS: learning path structure, readiness, scripts, and version alignment"
|