@cairnkeep/cli 1.0.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/AGENTS.md +45 -0
- package/CONTRIBUTING.md +49 -0
- package/LICENSE +201 -0
- package/README.md +149 -0
- package/SECURITY.md +37 -0
- package/bin/cairn +73 -0
- package/claude/agents/code-reviewer.md +174 -0
- package/claude/agents/security-investigator.md +112 -0
- package/claude/agents/security-target-selector.md +90 -0
- package/claude/agents/security-validator.md +97 -0
- package/claude/agents/wiki-ingester.md +108 -0
- package/claude/agents/wiki-lint-auditor.md +94 -0
- package/claude/agents/wiki-query-analyst.md +84 -0
- package/claude/commands/context-explore.md +40 -0
- package/claude/commands/graphify.md +150 -0
- package/claude/commands/memory-review.md +85 -0
- package/claude/commands/memory-sync.md +71 -0
- package/claude/commands/recall.md +32 -0
- package/claude/commands/remember.md +43 -0
- package/claude/commands/repo-review.md +191 -0
- package/claude/commands/security-audit.md +174 -0
- package/claude/commands/wiki-ingest.md +112 -0
- package/claude/commands/wiki-lint.md +86 -0
- package/claude/commands/wiki-query.md +92 -0
- package/claude/hooks/context-explore-pretask.sh +93 -0
- package/claude/hooks/memory-capture.sh +59 -0
- package/claude/hooks/memory-recall.sh +84 -0
- package/claude/hooks/memory-wakeup.sh +45 -0
- package/docs/building-an-overlay.md +117 -0
- package/docs/domain-knowledge.md +86 -0
- package/docs/git-providers.md +36 -0
- package/docs/operating.md +366 -0
- package/mcp-memory-server/dist/embeddings.js +122 -0
- package/mcp-memory-server/dist/explore-cache.js +97 -0
- package/mcp-memory-server/dist/index.js +1256 -0
- package/mcp-memory-server/package.json +36 -0
- package/opencode/agents/code-reviewer.md +173 -0
- package/opencode/agents/security-investigator.md +111 -0
- package/opencode/agents/security-target-selector.md +89 -0
- package/opencode/agents/security-validator.md +96 -0
- package/opencode/agents/wiki-ingester.md +107 -0
- package/opencode/agents/wiki-lint-auditor.md +93 -0
- package/opencode/agents/wiki-query-analyst.md +83 -0
- package/opencode/command/code-review.md +199 -0
- package/opencode/command/context-explore.md +42 -0
- package/opencode/command/graphify.md +153 -0
- package/opencode/command/memory-review.md +44 -0
- package/opencode/command/memory-sync.md +94 -0
- package/opencode/command/recall.md +38 -0
- package/opencode/command/remember.md +46 -0
- package/opencode/command/security-audit.md +80 -0
- package/opencode/command/wiki-ingest.md +55 -0
- package/opencode/command/wiki-lint.md +51 -0
- package/opencode/command/wiki-query.md +50 -0
- package/opencode/plugins/memory-capture.ts +188 -0
- package/opencode/plugins/memory-recall.ts +132 -0
- package/opencode/plugins/memory-wakeup.ts +102 -0
- package/opencode/templates/security-finding-register.yaml.template +51 -0
- package/opencode/templates/security-issue.md.template +43 -0
- package/opencode/templates/security-policy.md.template +29 -0
- package/opencode/templates/security-report.md.template +43 -0
- package/opencode/templates/wiki-contradictions.md.template +31 -0
- package/opencode/templates/wiki-index.md.template +40 -0
- package/opencode/templates/wiki-lint-report.md.template +38 -0
- package/opencode/templates/wiki-log.md.template +11 -0
- package/opencode/templates/wiki-policy.md.template +47 -0
- package/opencode/templates/wiki-query-answer.md.template +26 -0
- package/opencode/templates/wiki-source-summary.md.template +33 -0
- package/opencode/workflows/memory-review-workflow.md +66 -0
- package/opencode/workflows/security-audit-workflow.md +197 -0
- package/opencode/workflows/wiki-ingest-workflow.md +149 -0
- package/opencode/workflows/wiki-lint-workflow.md +128 -0
- package/opencode/workflows/wiki-query-workflow.md +122 -0
- package/package.json +56 -0
- package/scripts/bootstrap.sh +99 -0
- package/scripts/doctor.sh +91 -0
- package/scripts/install-audit-timer.sh +68 -0
- package/scripts/lib/assert-tool-event.mjs +50 -0
- package/scripts/memory-store.sh +71 -0
- package/scripts/memory-wiki-audit.sh +156 -0
- package/scripts/sync-claude-assets.sh +182 -0
- package/scripts/sync-opencode-explore-assets.sh +187 -0
- package/scripts/sync-opencode-graphify-assets.sh +152 -0
- package/scripts/sync-opencode-memory-assets.sh +160 -0
- package/scripts/sync-opencode-plugin-assets.sh +163 -0
- package/scripts/sync-opencode-security-assets.sh +195 -0
- package/scripts/sync-opencode-wiki-assets.sh +250 -0
- package/scripts/test-audit-timer.sh +29 -0
- package/scripts/test-bootstrap-untracked.sh +41 -0
- package/scripts/test-cli-dispatch.sh +21 -0
- package/scripts/test-doctor.sh +32 -0
- package/scripts/test-launcher-hooks.sh +66 -0
- package/scripts/test-memory-store.sh +44 -0
- package/scripts/test-remember-recall-assertions.sh +70 -0
- package/scripts/transcript-to-text.mjs +53 -0
- package/scripts/verify-docs-parity.sh +112 -0
- package/scripts/verify-explore-maturation.sh +288 -0
- package/scripts/verify-fastcontext-reliability.sh +624 -0
- package/scripts/verify-no-private-references.sh +145 -0
- package/scripts/verify-opencode-live-parity.sh +864 -0
- package/scripts/verify-routing-seam.sh +169 -0
- package/scripts/verify-token-savings-ab.sh +557 -0
- package/templates/PROJECT-INSTRUCTIONS.md.template +42 -0
- package/templates/alignment-gap-register.yaml.template +56 -0
- package/templates/alignment-policy.md.template +28 -0
- package/templates/alignment-pr-template.md.template +18 -0
- package/templates/alignment-report.md.template +56 -0
- package/templates/env.example.template +25 -0
- package/templates/graph-policy.md.template +37 -0
- package/templates/graphs-gitignore.template +3 -0
- package/templates/memory-wiki-audit.service.template +6 -0
- package/templates/memory-wiki-audit.timer.template +9 -0
- package/templates/memory.json.template +4 -0
- package/templates/planning-config.json.template +6 -0
- package/templates/project-brief.md.template +40 -0
- package/templates/security-finding-register.yaml.template +51 -0
- package/templates/security-issue.md.template +43 -0
- package/templates/security-policy.md.template +29 -0
- package/templates/security-report.md.template +43 -0
- package/templates/start-claude.sh.template +46 -0
- package/templates/start-opencode.sh.template +41 -0
- package/templates/structured-change-canvas.md.template +63 -0
- package/templates/wiki-contradictions.md.template +31 -0
- package/templates/wiki-index.md.template +40 -0
- package/templates/wiki-lint-report.md.template +38 -0
- package/templates/wiki-log.md.template +11 -0
- package/templates/wiki-policy.md.template +47 -0
- package/templates/wiki-query-answer.md.template +26 -0
- package/templates/wiki-source-summary.md.template +33 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# context-explore-pretask.sh — UserPromptSubmit hook (fires when the user
|
|
3
|
+
# submits a prompt, before the model responds).
|
|
4
|
+
#
|
|
5
|
+
# Auto-invokes context_explore for the submitted prompt so exploration runs
|
|
6
|
+
# for a task with no manual /context-explore call (CTX-09). Double opt-in
|
|
7
|
+
# (D-07): inert unless CAIRN_EXPLORE_BINARY is set AND
|
|
8
|
+
# CAIRN_EXPLORE_AUTOINVOKE=1. High-signal gated: skips short prompts, slash
|
|
9
|
+
# commands, and bare acknowledgements. Shells out to the shared `explore` CLI
|
|
10
|
+
# subcommand (D-06) with an explicit short timeout (Pitfall 1) so cache
|
|
11
|
+
# (CTX-10) and cross-refs (CTX-08) apply identically to hook and MCP-tool
|
|
12
|
+
# invocations. Fail-open: any error injects nothing, exit 0 always.
|
|
13
|
+
#
|
|
14
|
+
# Protocol: JSON on stdin { prompt, ... }. Output: JSON
|
|
15
|
+
# { hookSpecificOutput: { hookEventName, additionalContext } } only when the
|
|
16
|
+
# explore result is ok:true with non-empty citations; empty output otherwise.
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# Double opt-in (D-07): inert unless both are set.
|
|
20
|
+
[ -n "${CAIRN_EXPLORE_BINARY:-}" ] || exit 0
|
|
21
|
+
[ "${CAIRN_EXPLORE_AUTOINVOKE:-}" = "1" ] || exit 0
|
|
22
|
+
|
|
23
|
+
INFRA_ROOT="@@INFRA_ROOT@@"
|
|
24
|
+
SERVER_ENTRY="$INFRA_ROOT/mcp-memory-server/dist/index.js"
|
|
25
|
+
[ -f "$SERVER_ENTRY" ] || exit 0
|
|
26
|
+
|
|
27
|
+
input="$(cat)"
|
|
28
|
+
|
|
29
|
+
# Extract the submitted prompt text.
|
|
30
|
+
prompt="$(printf '%s' "$input" | python3 -c 'import sys,json
|
|
31
|
+
try:
|
|
32
|
+
d=json.load(sys.stdin)
|
|
33
|
+
print(d.get("prompt") or "")
|
|
34
|
+
except Exception: print("")' 2>/dev/null || true)"
|
|
35
|
+
[ -n "$prompt" ] || exit 0
|
|
36
|
+
|
|
37
|
+
# Low-signal skip (Open Question 2 -- ponytail: first-pass heuristic, not a
|
|
38
|
+
# rigorously derived one; revisit thresholds if noise/misses show up live).
|
|
39
|
+
# Skips: too short, a slash command, or a bare acknowledgement.
|
|
40
|
+
[ "${#prompt}" -ge 10 ] || exit 0
|
|
41
|
+
case "$prompt" in
|
|
42
|
+
/*) exit 0 ;;
|
|
43
|
+
esac
|
|
44
|
+
if printf '%s' "$prompt" | grep -Eqi '^[[:space:]]*(ok|yes|no|thanks?)\.?[[:space:]]*$'; then
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# Explicit short timeout (Pitfall 1) -- never the tool's own 120s default.
|
|
49
|
+
# `|| true` so a timeout/failure never aborts the hook (fail-open).
|
|
50
|
+
result="$(timeout 20 node "$SERVER_ENTRY" explore "$prompt" 2>/dev/null || true)"
|
|
51
|
+
[ -n "$result" ] || exit 0
|
|
52
|
+
|
|
53
|
+
# Inject only on ok:true + non-empty citations (D-07); compact citations +
|
|
54
|
+
# cross-ref flags only, never expanded_snippets (D-14).
|
|
55
|
+
context="$(printf '%s' "$result" | python3 -c 'import sys,json
|
|
56
|
+
try:
|
|
57
|
+
d=json.load(sys.stdin)
|
|
58
|
+
if d.get("ok") is not True:
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
citations = d.get("citations") or []
|
|
61
|
+
if not citations:
|
|
62
|
+
sys.exit(0)
|
|
63
|
+
lines=[]
|
|
64
|
+
for c in citations:
|
|
65
|
+
line = str(c.get("path")) + ":" + str(c.get("start_line")) + "-" + str(c.get("end_line"))
|
|
66
|
+
parts=[]
|
|
67
|
+
if c.get("memory_refs"):
|
|
68
|
+
parts.append("memory: " + ", ".join(c["memory_refs"]))
|
|
69
|
+
if c.get("wiki_refs"):
|
|
70
|
+
parts.append("wiki: " + ", ".join(c["wiki_refs"]))
|
|
71
|
+
if parts:
|
|
72
|
+
line += " <- " + " - ".join(parts)
|
|
73
|
+
lines.append(line)
|
|
74
|
+
print("\n".join(lines))
|
|
75
|
+
except Exception:
|
|
76
|
+
pass' 2>/dev/null || true)"
|
|
77
|
+
[ -n "$context" ] || exit 0
|
|
78
|
+
|
|
79
|
+
context="$(printf '%s\n' "$context" | head -40)"
|
|
80
|
+
|
|
81
|
+
# Emit the hook output JSON that injects additionalContext for this prompt.
|
|
82
|
+
# The prefix identifies the block as auto-invoked exploration context so the
|
|
83
|
+
# model knows its provenance (D-14).
|
|
84
|
+
python3 -c 'import sys,json
|
|
85
|
+
ctx = sys.stdin.read()
|
|
86
|
+
print(json.dumps({
|
|
87
|
+
"hookSpecificOutput": {
|
|
88
|
+
"hookEventName": "UserPromptSubmit",
|
|
89
|
+
"additionalContext": "Auto-invoked exploration context (context_explore, no manual /context-explore call):\n\n" + ctx
|
|
90
|
+
}
|
|
91
|
+
}))' <<<"$context" 2>/dev/null || true
|
|
92
|
+
|
|
93
|
+
exit 0
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# memory-capture.sh — SessionEnd hook (Claude Code + OpenCode share this protocol).
|
|
3
|
+
#
|
|
4
|
+
# At session end, extract durable memory candidates from the session transcript
|
|
5
|
+
# using the configured extraction model, and stage them for review — NOT write
|
|
6
|
+
# them directly. The next session's memory-wakeup.sh surfaces staged candidates;
|
|
7
|
+
# /memory-review is the accept gate that writes accepted ones to AgentFS via the
|
|
8
|
+
# cairn-memory MCP. This keeps writes agent-gated (no premature/auto writes, no
|
|
9
|
+
# AgentFS lock contention) while making extraction fully automatic.
|
|
10
|
+
#
|
|
11
|
+
# Protocol: JSON on stdin with { transcript_path, cwd, hook_event_name, ... }.
|
|
12
|
+
# ponytail: no-op outside a bootstrapped repo or when the extraction config is
|
|
13
|
+
# absent, so it is safe as a global hook.
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
repo="$(pwd)"
|
|
17
|
+
[ -f "$repo/.agentfs/project.db" ] || exit 0
|
|
18
|
+
|
|
19
|
+
INFRA_ROOT="@@INFRA_ROOT@@"
|
|
20
|
+
SERVER_ENTRY="$INFRA_ROOT/mcp-memory-server/dist/index.js"
|
|
21
|
+
TEXT_HELPER="$INFRA_ROOT/scripts/transcript-to-text.mjs"
|
|
22
|
+
[ -f "$SERVER_ENTRY" ] || exit 0
|
|
23
|
+
[ -z "${CAIRN_LLM_API_KEY:-}" ] && exit 0
|
|
24
|
+
EXTRACT_MODEL="${CAIRN_LLM_EXTRACTION_MODEL:-}"
|
|
25
|
+
[ -z "$EXTRACT_MODEL" ] && exit 0
|
|
26
|
+
|
|
27
|
+
# Read hook JSON from stdin; pull transcript_path (fail-open if absent/malformed).
|
|
28
|
+
input="$(cat)"
|
|
29
|
+
transcript_path="$(printf '%s' "$input" | python3 -c 'import sys,json
|
|
30
|
+
try:
|
|
31
|
+
d=json.load(sys.stdin); print(d.get("transcript_path",""))
|
|
32
|
+
except Exception: print("")' 2>/dev/null || true)"
|
|
33
|
+
[ -n "$transcript_path" ] && [ -f "$transcript_path" ] || exit 0
|
|
34
|
+
|
|
35
|
+
# Convert transcript JSONL → readable text, then extract candidates.
|
|
36
|
+
text="$(node "$TEXT_HELPER" "$transcript_path" 2>/dev/null || true)"
|
|
37
|
+
[ -n "$text" ] || exit 0
|
|
38
|
+
|
|
39
|
+
candidates_json="$(printf '%s' "$text" | node "$SERVER_ENTRY" extract "$EXTRACT_MODEL" 2>/dev/null || true)"
|
|
40
|
+
[ -n "$candidates_json" ] || exit 0
|
|
41
|
+
|
|
42
|
+
count="$(printf '%s' "$candidates_json" | python3 -c 'import sys,json
|
|
43
|
+
try:
|
|
44
|
+
d=json.load(sys.stdin); print(len(d.get("candidates",[])))
|
|
45
|
+
except Exception: print(0)' 2>/dev/null || echo 0)"
|
|
46
|
+
[ "$count" -gt 0 ] || exit 0
|
|
47
|
+
|
|
48
|
+
# Stage for the next session's accept gate. One file per session.
|
|
49
|
+
mkdir -p "$repo/.planning/memory-staging"
|
|
50
|
+
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
51
|
+
stage_file="$repo/.planning/memory-staging/$ts.json"
|
|
52
|
+
printf '%s\n' "$candidates_json" > "$stage_file"
|
|
53
|
+
|
|
54
|
+
# Keep the staging dir bounded — drop the oldest beyond 5 sessions.
|
|
55
|
+
ls -1t "$repo/.planning/memory-staging/"*.json 2>/dev/null | tail -n +6 | while read -r old; do
|
|
56
|
+
rm -f "$old"
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
exit 0
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# memory-recall.sh — PreToolUse hook (fires before Edit/Write/MultiEdit).
|
|
3
|
+
#
|
|
4
|
+
# When the model is about to edit a file, surface AgentFS facts and wiki pages
|
|
5
|
+
# that specifically mention that file. This turns "the model might search memory"
|
|
6
|
+
# into "the model always sees relevant pitfalls/decisions for the exact file."
|
|
7
|
+
#
|
|
8
|
+
# High-signal / low-noise design: injects context ONLY when there is a specific
|
|
9
|
+
# match (memory preview or wiki page mentioning the file path or basename). If
|
|
10
|
+
# nothing specific matches, it injects nothing — no noise on routine edits.
|
|
11
|
+
#
|
|
12
|
+
# Protocol: JSON on stdin { tool_name, tool_input: { file_path, ... }, cwd, ... }.
|
|
13
|
+
# Output: JSON { hookSpecificOutput: { hookEventName, additionalContext } } when
|
|
14
|
+
# there is a match; empty output otherwise. Fail-open: any error exits 0.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
repo="$(pwd)"
|
|
18
|
+
[ -f "$repo/.agentfs/project.db" ] || [ -d "$repo/.planning/wiki/sources" ] || exit 0
|
|
19
|
+
|
|
20
|
+
INFRA_ROOT="@@INFRA_ROOT@@"
|
|
21
|
+
SERVER_ENTRY="$INFRA_ROOT/mcp-memory-server/dist/index.js"
|
|
22
|
+
|
|
23
|
+
input="$(cat)"
|
|
24
|
+
|
|
25
|
+
# Extract the target file path from the tool input.
|
|
26
|
+
file_path="$(printf '%s' "$input" | python3 -c 'import sys,json
|
|
27
|
+
try:
|
|
28
|
+
d=json.load(sys.stdin); ti=d.get("tool_input",{})
|
|
29
|
+
print(ti.get("file_path") or ti.get("path") or "")
|
|
30
|
+
except Exception: print("")' 2>/dev/null || true)"
|
|
31
|
+
[ -n "$file_path" ] || exit 0
|
|
32
|
+
|
|
33
|
+
# Derive search tokens: basename and the repo-relative stem.
|
|
34
|
+
base="$(basename "$file_path")"
|
|
35
|
+
stem="${base%.*}"
|
|
36
|
+
# Avoid matching on tiny/generic stems that would noise up results.
|
|
37
|
+
[ "${#stem}" -lt 4 ] && exit 0
|
|
38
|
+
|
|
39
|
+
matches=()
|
|
40
|
+
|
|
41
|
+
# 1. AgentFS project memory: filter the compact wakeup index by the stem.
|
|
42
|
+
if [ -f "$repo/.agentfs/project.db" ] && [ -f "$SERVER_ENTRY" ]; then
|
|
43
|
+
idx="$(node "$SERVER_ENTRY" wakeup 2>/dev/null || true)"
|
|
44
|
+
if [ -n "$idx" ]; then
|
|
45
|
+
hit_lines="$(printf '%s' "$idx" | grep -iF "$stem" 2>/dev/null | head -8 || true)"
|
|
46
|
+
if [ -n "$hit_lines" ]; then
|
|
47
|
+
matches+=("## Relevant project memory for $base" "" "$hit_lines" "")
|
|
48
|
+
fi
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# 2. Wiki source pages whose name or content mentions the path/basename.
|
|
53
|
+
if [ -d "$repo/.planning/wiki/sources" ]; then
|
|
54
|
+
wiki_hits=""
|
|
55
|
+
while IFS= read -r page; do
|
|
56
|
+
[[ -n "$page" ]] || continue
|
|
57
|
+
if grep -qiF "$stem" "$page" 2>/dev/null; then
|
|
58
|
+
# Pull the first stable-fact bullet as a teaser.
|
|
59
|
+
teaser="$(grep -m1 -E '^- \*\*' "$page" 2>/dev/null | cut -c1-160 || true)"
|
|
60
|
+
wiki_hits+="- [$(basename "$page")] ${teaser}"$'\n'
|
|
61
|
+
fi
|
|
62
|
+
done < <(find "$repo/.planning/wiki/sources" -maxdepth 1 -name '*.md' -type f 2>/dev/null)
|
|
63
|
+
if [ -n "$wiki_hits" ]; then
|
|
64
|
+
matches+=("## Relevant wiki pages for $base" "" "${wiki_hits}" "")
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# Inject only when there is something specific.
|
|
69
|
+
if [ "${#matches[@]}" -eq 0 ]; then
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
context="$(printf '%s\n' "${matches[@]}" | head -40)"
|
|
74
|
+
# Emit the hook output JSON that injects additionalContext before the tool runs.
|
|
75
|
+
python3 -c 'import sys,json
|
|
76
|
+
ctx = sys.stdin.read()
|
|
77
|
+
print(json.dumps({
|
|
78
|
+
"hookSpecificOutput": {
|
|
79
|
+
"hookEventName": "PreToolUse",
|
|
80
|
+
"additionalContext": "Memory recall (auto-injected for this file edit):\n\n" + ctx
|
|
81
|
+
}
|
|
82
|
+
}))' <<<"$context" 2>/dev/null || true
|
|
83
|
+
|
|
84
|
+
exit 0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SessionStart hook: surface the memory layers that are otherwise invisible at
|
|
3
|
+
# session start (AgentFS project scope + wiki index). File-memory (MEMORY.md)
|
|
4
|
+
# already auto-loads, so it is not duplicated here.
|
|
5
|
+
# ponytail: no-op outside a managed repo, so it is safe as a global hook.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
repo="$(pwd)"
|
|
9
|
+
[ -f "$repo/.agentfs/project.db" ] || [ -f "$repo/.planning/wiki/index.md" ] || exit 0
|
|
10
|
+
|
|
11
|
+
if [ -f "$repo/.agentfs/project.db" ]; then
|
|
12
|
+
echo "## Project memory (AgentFS)"
|
|
13
|
+
node "@@INFRA_ROOT@@/mcp-memory-server/dist/index.js" wakeup 2>/dev/null || true
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
if [ -f "$repo/.planning/wiki/index.md" ]; then
|
|
17
|
+
echo
|
|
18
|
+
echo "## Wiki index"
|
|
19
|
+
cat "$repo/.planning/wiki/index.md"
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# Surface open HARD wiki contradictions so the agent (and user) see them at
|
|
23
|
+
# session start without anyone having to remember to scan the register. Hard
|
|
24
|
+
# entries cannot both be correct and must be resolved before dependent work.
|
|
25
|
+
contradictions="$repo/.planning/wiki/CONTRADICTIONS.md"
|
|
26
|
+
if [ -f "$contradictions" ]; then
|
|
27
|
+
open_hard="$(awk '/<!-- wiki:contradictions:open:start -->/{f=1;next} /<!-- wiki:contradictions:open:end -->/{f=0} f' "$contradictions" | grep -iE 'severity:[[:space:]]*hard')"
|
|
28
|
+
if [ -n "$open_hard" ]; then
|
|
29
|
+
echo
|
|
30
|
+
echo "## Open HARD contradictions — resolve before dependent work"
|
|
31
|
+
echo "$open_hard"
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Surface staged memory candidates captured by the SessionEnd hook
|
|
36
|
+
# (memory-capture.sh). These are extracted automatically from the last session
|
|
37
|
+
# but NOT yet written to AgentFS — /memory-review is the accept gate.
|
|
38
|
+
if [ -d "$repo/.planning/memory-staging" ]; then
|
|
39
|
+
staged="$(ls -1 "$repo/.planning/memory-staging/"*.json 2>/dev/null | wc -l)"
|
|
40
|
+
if [ "$staged" -gt 0 ]; then
|
|
41
|
+
echo
|
|
42
|
+
echo "## Staged memory candidates ($staged session(s)) — UNREVIEWED"
|
|
43
|
+
echo "Run /memory-review to accept (→ AgentFS) or discard these before doing other work."
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Building a private overlay
|
|
2
|
+
|
|
3
|
+
Cairnkeep is deliberately provider-neutral: it hardcodes no endpoints, no
|
|
4
|
+
credentials, and no organization-specific launchers. Everything specific to
|
|
5
|
+
*your* environment — internal LLM endpoints, proxies, credentials, corporate
|
|
6
|
+
launchers, policy — belongs in a small private **overlay** that wraps cairnkeep.
|
|
7
|
+
This guide shows the pattern.
|
|
8
|
+
|
|
9
|
+
## Principles
|
|
10
|
+
|
|
11
|
+
1. **Consume, don't fork.** Treat cairnkeep as an upstream dependency: install it
|
|
12
|
+
from npm (`npm i -g @cairnkeep/cli`) or clone it at a **pinned tag**. Never copy or
|
|
13
|
+
patch core files — if core behaviour must change, request it upstream. That way
|
|
14
|
+
you pick up fixes by bumping one version.
|
|
15
|
+
2. **One-way references.** Your overlay may reference cairnkeep freely. Nothing
|
|
16
|
+
private — internal hosts, IPs, tokens, employer/vendor names — should ever end
|
|
17
|
+
up *in cairnkeep* (in code, docs, or commit messages). Keep those in your
|
|
18
|
+
overlay only, and keep secrets in untracked `.env` files or a secret store,
|
|
19
|
+
never committed.
|
|
20
|
+
3. **Wrap at the seams, don't reimplement.** Cairnkeep's launchers and tools
|
|
21
|
+
expose extension points (below). Use them instead of replacing core behaviour.
|
|
22
|
+
|
|
23
|
+
## The launcher seams
|
|
24
|
+
|
|
25
|
+
The generic launchers (`.ai/start-claude.sh`, `.ai/start-opencode.sh`, scaffolded
|
|
26
|
+
by `cairn bootstrap`) run three optional hooks, each a no-op when absent:
|
|
27
|
+
|
|
28
|
+
| Seam | When | Use it for |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `.ai/pre-launch.sh` | sourced after `.env`, before launch | export a provider base URL / credentials, refresh a token, run a connectivity check, or **abort** the launch by returning non-zero |
|
|
31
|
+
| `CAIRN_EXTRA_SETTINGS` | read just before launch | path to a settings file layered onto the harness (`--settings` for Claude Code, `--config` for OpenCode); process env still wins, so an inline value beats the file |
|
|
32
|
+
| `.ai/post-exit.sh` | sourced after the harness exits | teardown; `CAIRN_EXIT_STATUS` holds the exit code |
|
|
33
|
+
|
|
34
|
+
**Typical overlay launcher** — a corporate wrapper needs a non-default provider
|
|
35
|
+
plus a credential refresh. Instead of forking the launcher, drop a
|
|
36
|
+
`.ai/pre-launch.sh`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# .ai/pre-launch.sh (sourced by the generic launcher)
|
|
40
|
+
# 1. refresh whatever credential your provider needs
|
|
41
|
+
./.ai/refresh-credentials.sh || return 1 # non-zero aborts the launch
|
|
42
|
+
# 2. render your provider settings and hand them to the harness
|
|
43
|
+
envsubst < ./.ai/provider-settings.json.template > /tmp/provider.$$.json
|
|
44
|
+
export CAIRN_EXTRA_SETTINGS=/tmp/provider.$$.json
|
|
45
|
+
export ANTHROPIC_BASE_URL="$MY_INTERNAL_GATEWAY"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The user still runs the standard `./.ai/start-claude.sh`; the overlay's behaviour
|
|
49
|
+
is injected by the file's presence.
|
|
50
|
+
|
|
51
|
+
## An overlay install script
|
|
52
|
+
|
|
53
|
+
Make it one idempotent command from a fresh machine:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 1. Get cairnkeep at a pinned version (npm or a pinned clone)
|
|
57
|
+
npm i -g @cairnkeep/cli@1.0.2 # or: git clone … && git checkout v1.0.2
|
|
58
|
+
|
|
59
|
+
# 2. Register the memory server with your harness
|
|
60
|
+
claude mcp add cairn-memory -s user -- cairn memory-server
|
|
61
|
+
|
|
62
|
+
# 3. Scaffold the target project (contributor mode if you don't own the repo)
|
|
63
|
+
cairn bootstrap --untracked /path/to/project
|
|
64
|
+
|
|
65
|
+
# 4. Install the operating layer — GLOBAL or PROJECT-SCOPED (below)
|
|
66
|
+
sync-claude-assets.sh --apply # global ~/.claude
|
|
67
|
+
# or keep it to one project:
|
|
68
|
+
sync-claude-assets.sh --apply --live-root /path/to/project/.claude
|
|
69
|
+
|
|
70
|
+
# 5. Drop your corporate launcher/helpers/.env template into /path/to/project/.ai/
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Global vs project-scoped operating layer
|
|
74
|
+
|
|
75
|
+
`sync-claude-assets.sh` installs commands/agents/hooks into `~/.claude` by default
|
|
76
|
+
(global — affects every project). To onboard a single project **without** touching
|
|
77
|
+
global config or other projects, install into that project's `.claude/` with
|
|
78
|
+
`--live-root <project>/.claude`; the project's local assets take precedence there
|
|
79
|
+
while the rest of the machine is untouched.
|
|
80
|
+
|
|
81
|
+
### Contributor mode
|
|
82
|
+
|
|
83
|
+
If you don't own the target repo, `cairn bootstrap --untracked` writes the
|
|
84
|
+
scaffold paths into the repo's `.git/info/exclude` — nothing to commit, invisible
|
|
85
|
+
to other contributors, no edit to the shared `.gitignore`. To move the durable
|
|
86
|
+
memory itself between machines, use `cairn memory export` / `cairn memory import`.
|
|
87
|
+
|
|
88
|
+
## Isolating your memory server
|
|
89
|
+
|
|
90
|
+
If your overlay runs its own memory server and you don't want a globally-registered
|
|
91
|
+
one bleeding in, launch the harness with `--strict-mcp-config --mcp-config
|
|
92
|
+
.ai/mcp.json`, listing only the servers you want. A launcher that adds these flags
|
|
93
|
+
when `.ai/mcp.json` is present gives you per-project MCP isolation.
|
|
94
|
+
|
|
95
|
+
## Wiring the optional integrations
|
|
96
|
+
|
|
97
|
+
All optional, all env-gated — a wrapper turns them on by setting the env:
|
|
98
|
+
|
|
99
|
+
| Integration | Turn on with | See |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| Memory extraction / semantic search | `CAIRN_LLM_API_KEY` + `CAIRN_LLM_API_URL` (+ embedding vars) | [operating.md](operating.md) |
|
|
102
|
+
| Document RAG (`domain_knowledge_*`) | `ANYTHINGLLM_API_KEY` (+ `ANYTHINGLLM_BASE_URL`, `CAIRN_ANYTHINGLLM_SYNC_SCRIPT`) | [domain-knowledge.md](domain-knowledge.md) |
|
|
103
|
+
| Routing seam (`route_check`) | `CAIRN_ROUTE_ENDPOINT` | [operating.md](operating.md) |
|
|
104
|
+
| Context exploration (`context_explore`) | `CAIRN_EXPLORE_BINARY` (+ `CAIRN_EXPLORE_REPO_ROOT`) | [operating.md](operating.md) |
|
|
105
|
+
| Networked memory (HTTP transport) | `MCP_HTTP_PORT` + `CAIRN_MEMORY_HTTP_TOKEN` | [operating.md](operating.md) |
|
|
106
|
+
|
|
107
|
+
Point the RAG sync script at your own copy (`CAIRN_ANYTHINGLLM_SYNC_SCRIPT`) so the
|
|
108
|
+
integration scripts live in your overlay, not the core.
|
|
109
|
+
|
|
110
|
+
## Checklist
|
|
111
|
+
|
|
112
|
+
- [ ] cairnkeep pinned to a version; no core files copied or patched
|
|
113
|
+
- [ ] All internal hosts/tokens in untracked `.env` / secret store, never committed
|
|
114
|
+
- [ ] Provider/credential setup in `.ai/pre-launch.sh`, not a forked launcher
|
|
115
|
+
- [ ] Operating layer scoped as intended (global vs `--live-root`)
|
|
116
|
+
- [ ] Optional integrations wired only where wanted
|
|
117
|
+
- [ ] A single documented command takes a fresh machine to a working setup
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Domain knowledge (RAG) — optional
|
|
2
|
+
|
|
3
|
+
Cairnkeep's memory server exposes two **optional** document-RAG tools alongside
|
|
4
|
+
the `memory_*` tools:
|
|
5
|
+
|
|
6
|
+
- **`domain_knowledge_query`** — ask a question against a document workspace and
|
|
7
|
+
get a grounded answer with citations.
|
|
8
|
+
- **`domain_knowledge_sync`** — (re)embed a project's documentation into a
|
|
9
|
+
workspace.
|
|
10
|
+
|
|
11
|
+
These are a thin bridge to a running **[AnythingLLM](https://anythingllm.com/)**
|
|
12
|
+
instance (a generic, self-hostable document-RAG product). They are **off unless
|
|
13
|
+
you configure them** — cairnkeep hosts no RAG engine, ships no documents, and
|
|
14
|
+
hardcodes no endpoint. If you never set `ANYTHINGLLM_API_KEY`, the tools simply
|
|
15
|
+
error at call time and nothing else is affected; `memory_*` and the wiki layer
|
|
16
|
+
work exactly the same without them.
|
|
17
|
+
|
|
18
|
+
## What you need
|
|
19
|
+
|
|
20
|
+
1. A running AnythingLLM instance with at least one **workspace** that has your
|
|
21
|
+
documents embedded. (Local via podman/docker, or any reachable instance.)
|
|
22
|
+
2. An AnythingLLM **API key**.
|
|
23
|
+
3. cairnkeep pointed at it (below).
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
| Variable | Purpose |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `ANYTHINGLLM_API_KEY` | **Required** for the domain-knowledge tools. Unset → the tools error at call time (everything else works). |
|
|
30
|
+
| `ANYTHINGLLM_BASE_URL` | AnythingLLM base URL. Default `http://localhost:3001`. |
|
|
31
|
+
| `CAIRN_ANYTHINGLLM_SYNC_SCRIPT` | Path to the document-sync script used by `domain_knowledge_sync`. The core does not bundle one — point this at your own (see below). Unset → `domain_knowledge_sync` uses the in-repo default path. |
|
|
32
|
+
|
|
33
|
+
The workspaces the tools target come from the project's **memory config** — the
|
|
34
|
+
first of `.agent/memory.json`, `.opencode/memory.json`, `.claude/memory.json`, or
|
|
35
|
+
`memory.json` that exists:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"scopes": ["identity"],
|
|
40
|
+
"anythingllm_workspaces": ["engineering-patterns", "my-project-docs"]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`domain_knowledge_query` takes an explicit `workspace`, or falls back to the first
|
|
45
|
+
configured workspace that is not `engineering-patterns` (the conventional shared
|
|
46
|
+
workspace). With no workspace given and none configured, it errors.
|
|
47
|
+
|
|
48
|
+
## Setup (query)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 1. Start AnythingLLM (example: local podman on :3001) and create/populate a
|
|
52
|
+
# workspace with your docs (via the AnythingLLM UI or its API).
|
|
53
|
+
# 2. Point cairnkeep at it, in the project's .ai/.env:
|
|
54
|
+
ANYTHINGLLM_BASE_URL=http://localhost:3001
|
|
55
|
+
ANYTHINGLLM_API_KEY=<your AnythingLLM API key>
|
|
56
|
+
# 3. List the workspace(s) in the project's memory config (see JSON above).
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then `domain_knowledge_query { "workspace": "my-project-docs", "query": "..." }`
|
|
60
|
+
returns a grounded answer.
|
|
61
|
+
|
|
62
|
+
## Setup (sync / embedding)
|
|
63
|
+
|
|
64
|
+
`domain_knowledge_sync` shells out to a document-upload/embed script. Because the
|
|
65
|
+
scripts that talk to your AnythingLLM (and any include/exclude rules) are
|
|
66
|
+
deployment-specific, they live in **your** wrapper, not the core — point cairnkeep
|
|
67
|
+
at yours:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
CAIRN_ANYTHINGLLM_SYNC_SCRIPT=/abs/path/to/your/sync_to_anythingllm.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The script receives the workspace slug and is expected to upload + embed the
|
|
74
|
+
project's documents into that AnythingLLM workspace. See
|
|
75
|
+
[building-an-overlay.md](building-an-overlay.md) for the wrapper pattern.
|
|
76
|
+
|
|
77
|
+
A working, deployment-agnostic starting point lives in
|
|
78
|
+
[`examples/anythingllm/`](../examples/anythingllm/): an incremental
|
|
79
|
+
(sha256-tracked) multi-project sync script plus a config schema. Copy it, adapt
|
|
80
|
+
`anythingllm-projects.json`, and point `CAIRN_ANYTHINGLLM_SYNC_SCRIPT` at your
|
|
81
|
+
copy.
|
|
82
|
+
|
|
83
|
+
## Not using RAG?
|
|
84
|
+
|
|
85
|
+
Do nothing. The tools stay inert, cost nothing, and the rest of cairnkeep is
|
|
86
|
+
fully functional without an AnythingLLM instance.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Git provider configuration
|
|
2
|
+
|
|
3
|
+
Cairnkeep's collaboration commands — `memory-sync`, `repo-review` / `code-review`,
|
|
4
|
+
and `security-audit` — talk to your git host through whatever git-provider MCP
|
|
5
|
+
server you have registered. The host is selected per project by one setting, so
|
|
6
|
+
the same commands work on GitHub, GitLab, Codeberg/Forgejo, and anything else
|
|
7
|
+
that has an MCP server.
|
|
8
|
+
|
|
9
|
+
## Configure
|
|
10
|
+
|
|
11
|
+
Set the provider in the project's `.ai/.env`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
CAIRN_GIT_PROVIDER=github # github | gitlab | codeberg | forgejo | none
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`none` disables the provider steps — the commands run locally only (still write
|
|
18
|
+
memory, still produce local review/audit reports).
|
|
19
|
+
|
|
20
|
+
## Operations the commands use
|
|
21
|
+
|
|
22
|
+
The commands need a small, stable set of operations. Map each to the tool your
|
|
23
|
+
provider's MCP server exposes:
|
|
24
|
+
|
|
25
|
+
| Operation | Purpose | GitHub (example) | GitLab (example) |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| list open changes | find tracked PRs/MRs | `list_pull_requests` | `list_merge_requests` |
|
|
28
|
+
| get change diff | review a PR/MR diff | `get_pull_request_diff` | `get_merge_request_diffs` |
|
|
29
|
+
| post inline comment | post review findings | `create_review_comment` | `create_merge_request_discussion` |
|
|
30
|
+
| get change state | sync memory with PR/MR status | `get_pull_request` | `get_merge_request` |
|
|
31
|
+
| create issue | file an accepted finding | `create_issue` | `create_issue` |
|
|
32
|
+
|
|
33
|
+
Exact tool names depend on the specific MCP server you install; the table shows
|
|
34
|
+
the common shape. Codeberg/Forgejo and other hosts follow the same operations
|
|
35
|
+
with their own tool names. If no git-provider MCP is registered (or
|
|
36
|
+
`CAIRN_GIT_PROVIDER=none`), the commands skip the provider steps and keep working.
|