@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.
Files changed (129) hide show
  1. package/AGENTS.md +45 -0
  2. package/CONTRIBUTING.md +49 -0
  3. package/LICENSE +201 -0
  4. package/README.md +149 -0
  5. package/SECURITY.md +37 -0
  6. package/bin/cairn +73 -0
  7. package/claude/agents/code-reviewer.md +174 -0
  8. package/claude/agents/security-investigator.md +112 -0
  9. package/claude/agents/security-target-selector.md +90 -0
  10. package/claude/agents/security-validator.md +97 -0
  11. package/claude/agents/wiki-ingester.md +108 -0
  12. package/claude/agents/wiki-lint-auditor.md +94 -0
  13. package/claude/agents/wiki-query-analyst.md +84 -0
  14. package/claude/commands/context-explore.md +40 -0
  15. package/claude/commands/graphify.md +150 -0
  16. package/claude/commands/memory-review.md +85 -0
  17. package/claude/commands/memory-sync.md +71 -0
  18. package/claude/commands/recall.md +32 -0
  19. package/claude/commands/remember.md +43 -0
  20. package/claude/commands/repo-review.md +191 -0
  21. package/claude/commands/security-audit.md +174 -0
  22. package/claude/commands/wiki-ingest.md +112 -0
  23. package/claude/commands/wiki-lint.md +86 -0
  24. package/claude/commands/wiki-query.md +92 -0
  25. package/claude/hooks/context-explore-pretask.sh +93 -0
  26. package/claude/hooks/memory-capture.sh +59 -0
  27. package/claude/hooks/memory-recall.sh +84 -0
  28. package/claude/hooks/memory-wakeup.sh +45 -0
  29. package/docs/building-an-overlay.md +117 -0
  30. package/docs/domain-knowledge.md +86 -0
  31. package/docs/git-providers.md +36 -0
  32. package/docs/operating.md +366 -0
  33. package/mcp-memory-server/dist/embeddings.js +122 -0
  34. package/mcp-memory-server/dist/explore-cache.js +97 -0
  35. package/mcp-memory-server/dist/index.js +1256 -0
  36. package/mcp-memory-server/package.json +36 -0
  37. package/opencode/agents/code-reviewer.md +173 -0
  38. package/opencode/agents/security-investigator.md +111 -0
  39. package/opencode/agents/security-target-selector.md +89 -0
  40. package/opencode/agents/security-validator.md +96 -0
  41. package/opencode/agents/wiki-ingester.md +107 -0
  42. package/opencode/agents/wiki-lint-auditor.md +93 -0
  43. package/opencode/agents/wiki-query-analyst.md +83 -0
  44. package/opencode/command/code-review.md +199 -0
  45. package/opencode/command/context-explore.md +42 -0
  46. package/opencode/command/graphify.md +153 -0
  47. package/opencode/command/memory-review.md +44 -0
  48. package/opencode/command/memory-sync.md +94 -0
  49. package/opencode/command/recall.md +38 -0
  50. package/opencode/command/remember.md +46 -0
  51. package/opencode/command/security-audit.md +80 -0
  52. package/opencode/command/wiki-ingest.md +55 -0
  53. package/opencode/command/wiki-lint.md +51 -0
  54. package/opencode/command/wiki-query.md +50 -0
  55. package/opencode/plugins/memory-capture.ts +188 -0
  56. package/opencode/plugins/memory-recall.ts +132 -0
  57. package/opencode/plugins/memory-wakeup.ts +102 -0
  58. package/opencode/templates/security-finding-register.yaml.template +51 -0
  59. package/opencode/templates/security-issue.md.template +43 -0
  60. package/opencode/templates/security-policy.md.template +29 -0
  61. package/opencode/templates/security-report.md.template +43 -0
  62. package/opencode/templates/wiki-contradictions.md.template +31 -0
  63. package/opencode/templates/wiki-index.md.template +40 -0
  64. package/opencode/templates/wiki-lint-report.md.template +38 -0
  65. package/opencode/templates/wiki-log.md.template +11 -0
  66. package/opencode/templates/wiki-policy.md.template +47 -0
  67. package/opencode/templates/wiki-query-answer.md.template +26 -0
  68. package/opencode/templates/wiki-source-summary.md.template +33 -0
  69. package/opencode/workflows/memory-review-workflow.md +66 -0
  70. package/opencode/workflows/security-audit-workflow.md +197 -0
  71. package/opencode/workflows/wiki-ingest-workflow.md +149 -0
  72. package/opencode/workflows/wiki-lint-workflow.md +128 -0
  73. package/opencode/workflows/wiki-query-workflow.md +122 -0
  74. package/package.json +56 -0
  75. package/scripts/bootstrap.sh +99 -0
  76. package/scripts/doctor.sh +91 -0
  77. package/scripts/install-audit-timer.sh +68 -0
  78. package/scripts/lib/assert-tool-event.mjs +50 -0
  79. package/scripts/memory-store.sh +71 -0
  80. package/scripts/memory-wiki-audit.sh +156 -0
  81. package/scripts/sync-claude-assets.sh +182 -0
  82. package/scripts/sync-opencode-explore-assets.sh +187 -0
  83. package/scripts/sync-opencode-graphify-assets.sh +152 -0
  84. package/scripts/sync-opencode-memory-assets.sh +160 -0
  85. package/scripts/sync-opencode-plugin-assets.sh +163 -0
  86. package/scripts/sync-opencode-security-assets.sh +195 -0
  87. package/scripts/sync-opencode-wiki-assets.sh +250 -0
  88. package/scripts/test-audit-timer.sh +29 -0
  89. package/scripts/test-bootstrap-untracked.sh +41 -0
  90. package/scripts/test-cli-dispatch.sh +21 -0
  91. package/scripts/test-doctor.sh +32 -0
  92. package/scripts/test-launcher-hooks.sh +66 -0
  93. package/scripts/test-memory-store.sh +44 -0
  94. package/scripts/test-remember-recall-assertions.sh +70 -0
  95. package/scripts/transcript-to-text.mjs +53 -0
  96. package/scripts/verify-docs-parity.sh +112 -0
  97. package/scripts/verify-explore-maturation.sh +288 -0
  98. package/scripts/verify-fastcontext-reliability.sh +624 -0
  99. package/scripts/verify-no-private-references.sh +145 -0
  100. package/scripts/verify-opencode-live-parity.sh +864 -0
  101. package/scripts/verify-routing-seam.sh +169 -0
  102. package/scripts/verify-token-savings-ab.sh +557 -0
  103. package/templates/PROJECT-INSTRUCTIONS.md.template +42 -0
  104. package/templates/alignment-gap-register.yaml.template +56 -0
  105. package/templates/alignment-policy.md.template +28 -0
  106. package/templates/alignment-pr-template.md.template +18 -0
  107. package/templates/alignment-report.md.template +56 -0
  108. package/templates/env.example.template +25 -0
  109. package/templates/graph-policy.md.template +37 -0
  110. package/templates/graphs-gitignore.template +3 -0
  111. package/templates/memory-wiki-audit.service.template +6 -0
  112. package/templates/memory-wiki-audit.timer.template +9 -0
  113. package/templates/memory.json.template +4 -0
  114. package/templates/planning-config.json.template +6 -0
  115. package/templates/project-brief.md.template +40 -0
  116. package/templates/security-finding-register.yaml.template +51 -0
  117. package/templates/security-issue.md.template +43 -0
  118. package/templates/security-policy.md.template +29 -0
  119. package/templates/security-report.md.template +43 -0
  120. package/templates/start-claude.sh.template +46 -0
  121. package/templates/start-opencode.sh.template +41 -0
  122. package/templates/structured-change-canvas.md.template +63 -0
  123. package/templates/wiki-contradictions.md.template +31 -0
  124. package/templates/wiki-index.md.template +40 -0
  125. package/templates/wiki-lint-report.md.template +38 -0
  126. package/templates/wiki-log.md.template +11 -0
  127. package/templates/wiki-policy.md.template +47 -0
  128. package/templates/wiki-query-answer.md.template +26 -0
  129. package/templates/wiki-source-summary.md.template +33 -0
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ shopt -s nullglob
4
+
5
+ usage() {
6
+ cat <<'EOF'
7
+ Usage: sync-claude-assets.sh [--check|--apply] [--live-root PATH]
8
+
9
+ Compare or sync the repo-managed Claude Code assets (commands, agents) and the
10
+ security/wiki scaffold templates against the live Claude Code config tree.
11
+
12
+ Options:
13
+ --check Verify that the live assets match the repo copy (default)
14
+ --apply Copy the repo-managed assets into the live Claude tree, then verify
15
+ --live-root PATH Override the live Claude root (default: $CLAUDE_CONFIG_DIR or $HOME/.claude)
16
+ -h, --help Show this help text
17
+
18
+ Notes:
19
+ - Claude assets source of truth lives under ./claude/ (commands/, agents/).
20
+ - Installs claude/commands/*.md -> <live>/commands/ and claude/agents/*.md -> <live>/agents/.
21
+ - Also installs templates/{security,wiki}-*.template -> <live>/templates/ so the
22
+ /security-audit and /wiki-* commands can scaffold from $HOME/.claude/templates/.
23
+ - Renders claude/hooks/*.sh -> <live>/hooks/ (substituting @@INFRA_ROOT@@) and
24
+ idempotently registers the SessionStart wakeup hook in <live>/settings.json
25
+ (backs up settings.json.bak first; matches by command so re-runs are no-ops).
26
+ - New families are picked up automatically once added under ./claude/.
27
+ EOF
28
+ }
29
+
30
+ ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
31
+ CLAUDE_SOURCE="$ROOT_DIR/claude"
32
+ TEMPLATE_SOURCE="$ROOT_DIR/templates"
33
+ LIVE_ROOT="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
34
+ MODE="check"
35
+
36
+ while [[ $# -gt 0 ]]; do
37
+ case "$1" in
38
+ --check) MODE="check"; shift ;;
39
+ --apply) MODE="apply"; shift ;;
40
+ --live-root) LIVE_ROOT="$2"; shift 2 ;;
41
+ -h|--help) usage; exit 0 ;;
42
+ *) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;;
43
+ esac
44
+ done
45
+
46
+ if [[ ! -d "$CLAUDE_SOURCE" ]]; then
47
+ echo "No repo-managed Claude assets found at $CLAUDE_SOURCE" >&2
48
+ exit 1
49
+ fi
50
+
51
+ # Build the list of (source-abs, dest-abs) pairs.
52
+ SRCS=()
53
+ DSTS=()
54
+
55
+ # 1. claude/ tree -> <live>/...
56
+ while IFS= read -r rel; do
57
+ SRCS+=("$CLAUDE_SOURCE/$rel")
58
+ DSTS+=("$LIVE_ROOT/$rel")
59
+ done < <(cd "$CLAUDE_SOURCE" && find . -type f -name '*.md' | sed 's|^\./||' | sort)
60
+
61
+ # 2. security/wiki scaffold templates -> <live>/templates/
62
+ for tpl in "$TEMPLATE_SOURCE"/security-*.template "$TEMPLATE_SOURCE"/wiki-*.template; do
63
+ [[ -f "$tpl" ]] || continue
64
+ SRCS+=("$tpl")
65
+ DSTS+=("$LIVE_ROOT/templates/$(basename "$tpl")")
66
+ done
67
+
68
+ if ((${#SRCS[@]} == 0)); then
69
+ echo "No managed assets found to sync" >&2
70
+ exit 1
71
+ fi
72
+
73
+ status=0
74
+ for i in "${!SRCS[@]}"; do
75
+ src="${SRCS[$i]}"
76
+ dst="${DSTS[$i]}"
77
+ rel="${dst#"$LIVE_ROOT"/}"
78
+ if [[ "$MODE" == "apply" ]]; then
79
+ mkdir -p "$(dirname "$dst")"
80
+ cp "$src" "$dst"
81
+ echo "installed: $rel"
82
+ else
83
+ if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
84
+ echo "ok: $rel"
85
+ else
86
+ echo "DRIFT: $rel"
87
+ status=1
88
+ fi
89
+ fi
90
+ done
91
+
92
+ # Claude hooks: render @@INFRA_ROOT@@ and register each hook on its correct
93
+ # event in <live>/settings.json. This is the one place that touches the global
94
+ # user settings; it backs up first and matches by command so re-runs are no-ops.
95
+ HOOK_LIVE_DIR="$LIVE_ROOT/hooks"
96
+ SETTINGS_FILE="$LIVE_ROOT/settings.json"
97
+
98
+ # Map hook filename -> "event[:matcher][@timeout]". matcher only for
99
+ # PreToolUse-style events; an optional trailing "@timeout" (seconds) threads
100
+ # an explicit per-hook timeout into the settings.json registration entry so
101
+ # Claude Code's kill budget is documented, not assumed (Pitfall 1).
102
+ declare -A HOOK_EVENTS=(
103
+ ["memory-wakeup.sh"]="SessionStart"
104
+ ["memory-capture.sh"]="SessionEnd"
105
+ ["memory-recall.sh"]="PreToolUse:Edit|Write|MultiEdit"
106
+ ["context-explore-pretask.sh"]="UserPromptSubmit@25"
107
+ )
108
+
109
+ for hook_src in "$CLAUDE_SOURCE"/hooks/*.sh; do
110
+ [[ -f "$hook_src" ]] || continue
111
+ hook_name="$(basename "$hook_src")"
112
+ hook_dst="$HOOK_LIVE_DIR/$hook_name"
113
+ hook_cmd="bash \"$hook_dst\""
114
+ event_spec="${HOOK_EVENTS[$hook_name]:-}"
115
+ if [[ -z "$event_spec" ]]; then
116
+ # Unknown hook: still render it, but do not auto-register (avoid stomping settings).
117
+ if [[ "$MODE" == "apply" ]]; then
118
+ mkdir -p "$HOOK_LIVE_DIR"
119
+ sed "s|@@INFRA_ROOT@@|$ROOT_DIR|g" "$hook_src" > "$hook_dst"
120
+ chmod +x "$hook_dst"
121
+ echo "installed (unregistered): hooks/$hook_name"
122
+ fi
123
+ continue
124
+ fi
125
+ hook_timeout=""
126
+ if [[ "$event_spec" == *"@"* ]]; then
127
+ hook_timeout="${event_spec##*@}"
128
+ event_spec="${event_spec%@*}"
129
+ fi
130
+ event="${event_spec%%:*}"
131
+ matcher="${event_spec#*:}"
132
+ [[ "$matcher" == "$event_spec" ]] && matcher=""
133
+ if [[ "$MODE" == "apply" ]]; then
134
+ mkdir -p "$HOOK_LIVE_DIR"
135
+ sed "s|@@INFRA_ROOT@@|$ROOT_DIR|g" "$hook_src" > "$hook_dst"
136
+ chmod +x "$hook_dst"
137
+ echo "installed: hooks/$hook_name"
138
+ node -e '
139
+ const fs = require("fs");
140
+ const [p, cmd, token, event, matcher, timeoutStr] = process.argv.slice(1);
141
+ let s = {};
142
+ if (fs.existsSync(p)) {
143
+ try { s = JSON.parse(fs.readFileSync(p, "utf8")); }
144
+ catch (e) { console.error("settings.json parse failed, leaving untouched"); process.exit(1); }
145
+ }
146
+ s.hooks = s.hooks || {};
147
+ s.hooks[event] = Array.isArray(s.hooks[event]) ? s.hooks[event] : [];
148
+ const already = s.hooks[event].some((entry) =>
149
+ Array.isArray(entry.hooks) && entry.hooks.some((h) => typeof h.command === "string" && h.command.includes(token)));
150
+ if (already) { console.log(`ok: ${event} hook already registered`); process.exit(0); }
151
+ if (fs.existsSync(p)) fs.copyFileSync(p, p + ".bak");
152
+ const hookObj = { type: "command", command: cmd };
153
+ if (timeoutStr) hookObj.timeout = parseInt(timeoutStr, 10);
154
+ const entry = matcher
155
+ ? { matcher, hooks: [hookObj] }
156
+ : { hooks: [hookObj] };
157
+ s.hooks[event].push(entry);
158
+ fs.writeFileSync(p, JSON.stringify(s, null, 2) + "\n");
159
+ console.log(`registered: ${event} hook${matcher ? " (" + matcher + ")" : ""}${timeoutStr ? " (timeout=" + timeoutStr + "s)" : ""}`);
160
+ ' "$SETTINGS_FILE" "$hook_cmd" "$hook_dst" "$event" "$matcher" "$hook_timeout" || status=1
161
+ else
162
+ if [[ -f "$hook_dst" ]] && grep -qF "$hook_name" "$SETTINGS_FILE" 2>/dev/null; then
163
+ echo "ok: hooks/$hook_name"
164
+ else
165
+ echo "DRIFT: hooks/$hook_name"
166
+ status=1
167
+ fi
168
+ fi
169
+ done
170
+
171
+
172
+ if [[ "$MODE" == "apply" ]]; then
173
+ for i in "${!SRCS[@]}"; do
174
+ if ! cmp -s "${SRCS[$i]}" "${DSTS[$i]}"; then
175
+ echo "VERIFY FAILED: ${DSTS[$i]#"$LIVE_ROOT"/}" >&2
176
+ status=1
177
+ fi
178
+ done
179
+ [[ "$status" -eq 0 ]] && echo "All Claude assets installed and verified into $LIVE_ROOT."
180
+ fi
181
+
182
+ exit "$status"
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-explore-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed OpenCode explore assets against the live
9
+ OpenCode config tree.
10
+
11
+ Options:
12
+ --check Verify that the managed live assets match the repo copy (default)
13
+ --apply Copy the repo-managed assets into the live OpenCode tree, then verify
14
+ --live-root PATH Override the live OpenCode root (default: $OPENCODE_CONFIG_DIR or $HOME/.config/opencode)
15
+ -h, --help Show this help text
16
+
17
+ Notes:
18
+ - The repo-managed source of truth lives under ./opencode/
19
+ - This script manages only the explore-command OpenCode asset
20
+ - Extra live explore assets are reported as warnings but are not deleted automatically
21
+ EOF
22
+ }
23
+
24
+ ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
25
+ SOURCE_ROOT="$ROOT_DIR/opencode"
26
+ LIVE_ROOT="${OPENCODE_CONFIG_DIR:-$HOME/.config/opencode}"
27
+ MODE="check"
28
+
29
+ ASSETS=(
30
+ "command/context-explore.md"
31
+ )
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case "$1" in
35
+ --check)
36
+ MODE="check"
37
+ shift
38
+ ;;
39
+ --apply)
40
+ MODE="apply"
41
+ shift
42
+ ;;
43
+ --live-root)
44
+ LIVE_ROOT="$2"
45
+ shift 2
46
+ ;;
47
+ -h|--help)
48
+ usage
49
+ exit 0
50
+ ;;
51
+ *)
52
+ echo "Unknown argument: $1" >&2
53
+ usage >&2
54
+ exit 2
55
+ ;;
56
+ esac
57
+ done
58
+
59
+ collect_extra_live_assets() {
60
+ local root="$1"
61
+ local -A managed=()
62
+ local rel
63
+
64
+ for rel in "${ASSETS[@]}"; do
65
+ managed["$rel"]=1
66
+ done
67
+
68
+ while IFS= read -r rel; do
69
+ [[ -n "$rel" ]] || continue
70
+ if [[ -z "${managed[$rel]:-}" ]]; then
71
+ printf '%s\n' "$rel"
72
+ fi
73
+ done < <(
74
+ find "$root/command" -maxdepth 1 -type f -name 'context-explore*.md' -printf 'command/%f\n' 2>/dev/null | sort -u || true
75
+ )
76
+ }
77
+
78
+ ensure_source_assets_exist() {
79
+ local rel
80
+ local missing=0
81
+
82
+ for rel in "${ASSETS[@]}"; do
83
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
84
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
85
+ missing=1
86
+ fi
87
+ done
88
+
89
+ if [[ $missing -ne 0 ]]; then
90
+ exit 2
91
+ fi
92
+ }
93
+
94
+ report_extra_live_assets() {
95
+ local -a extras=()
96
+
97
+ if [[ ! -d "$LIVE_ROOT" ]]; then
98
+ return
99
+ fi
100
+
101
+ mapfile -t extras < <(collect_extra_live_assets "$LIVE_ROOT")
102
+ if ((${#extras[@]} == 0)); then
103
+ return
104
+ fi
105
+
106
+ printf 'Warning: found unmanaged live explore assets under %s:\n' "$LIVE_ROOT" >&2
107
+ printf ' - %s\n' "${extras[@]}" >&2
108
+ }
109
+
110
+ run_check() {
111
+ local rel
112
+ local src
113
+ local dst
114
+ local -a missing_live=()
115
+ local -a mismatched=()
116
+
117
+ ensure_source_assets_exist
118
+
119
+ for rel in "${ASSETS[@]}"; do
120
+ src="$SOURCE_ROOT/$rel"
121
+ dst="$LIVE_ROOT/$rel"
122
+
123
+ if [[ ! -f "$dst" ]]; then
124
+ missing_live+=("$rel")
125
+ continue
126
+ fi
127
+
128
+ if ! cmp -s "$src" "$dst"; then
129
+ mismatched+=("$rel")
130
+ fi
131
+ done
132
+
133
+ report_extra_live_assets
134
+
135
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
136
+ printf 'Explore assets are in sync: %s managed files match %s\n' "${#ASSETS[@]}" "$LIVE_ROOT"
137
+ return 0
138
+ fi
139
+
140
+ if ((${#missing_live[@]} > 0)); then
141
+ printf 'Missing live explore assets under %s:\n' "$LIVE_ROOT" >&2
142
+ printf ' - %s\n' "${missing_live[@]}" >&2
143
+ fi
144
+
145
+ if ((${#mismatched[@]} > 0)); then
146
+ printf 'Out-of-sync live explore assets under %s:\n' "$LIVE_ROOT" >&2
147
+ printf ' - %s\n' "${mismatched[@]}" >&2
148
+ fi
149
+
150
+ return 1
151
+ }
152
+
153
+ run_apply() {
154
+ local rel
155
+ local src
156
+ local dst
157
+ local updated=0
158
+ local unchanged=0
159
+
160
+ ensure_source_assets_exist
161
+
162
+ for rel in "${ASSETS[@]}"; do
163
+ src="$SOURCE_ROOT/$rel"
164
+ dst="$LIVE_ROOT/$rel"
165
+
166
+ mkdir -p "$(dirname "$dst")"
167
+ if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
168
+ unchanged=$((unchanged + 1))
169
+ continue
170
+ fi
171
+
172
+ install -m 0644 "$src" "$dst"
173
+ updated=$((updated + 1))
174
+ done
175
+
176
+ printf 'Applied %s explore asset(s); %s already matched.\n' "$updated" "$unchanged"
177
+ run_check
178
+ }
179
+
180
+ case "$MODE" in
181
+ check)
182
+ run_check
183
+ ;;
184
+ apply)
185
+ run_apply
186
+ ;;
187
+ esac
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-graphify-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed Graphify OpenCode command asset against the
9
+ live OpenCode config tree.
10
+
11
+ Options:
12
+ --check Verify that the managed live asset matches the repo copy (default)
13
+ --apply Copy the repo-managed asset into the live OpenCode tree, then verify
14
+ --live-root PATH Override the live OpenCode root (default: $OPENCODE_CONFIG_DIR or $HOME/.config/opencode)
15
+ -h, --help Show this help text
16
+
17
+ Notes:
18
+ - The repo-managed source of truth lives under ./opencode/
19
+ - This script manages only the /graphify command asset
20
+ EOF
21
+ }
22
+
23
+ ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
24
+ SOURCE_ROOT="$ROOT_DIR/opencode"
25
+ LIVE_ROOT="${OPENCODE_CONFIG_DIR:-$HOME/.config/opencode}"
26
+ MODE="check"
27
+
28
+ ASSETS=(
29
+ "command/graphify.md"
30
+ )
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --check)
35
+ MODE="check"
36
+ shift
37
+ ;;
38
+ --apply)
39
+ MODE="apply"
40
+ shift
41
+ ;;
42
+ --live-root)
43
+ LIVE_ROOT="$2"
44
+ shift 2
45
+ ;;
46
+ -h|--help)
47
+ usage
48
+ exit 0
49
+ ;;
50
+ *)
51
+ echo "Unknown argument: $1" >&2
52
+ usage >&2
53
+ exit 2
54
+ ;;
55
+ esac
56
+ done
57
+
58
+ ensure_source_assets_exist() {
59
+ local rel
60
+ local missing=0
61
+
62
+ for rel in "${ASSETS[@]}"; do
63
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
64
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
65
+ missing=1
66
+ fi
67
+ done
68
+
69
+ if [[ $missing -ne 0 ]]; then
70
+ exit 2
71
+ fi
72
+ }
73
+
74
+ check_asset_sync() {
75
+ local rel
76
+ local src
77
+ local dst
78
+ local -a missing_live=()
79
+ local -a mismatched=()
80
+
81
+ for rel in "${ASSETS[@]}"; do
82
+ src="$SOURCE_ROOT/$rel"
83
+ dst="$LIVE_ROOT/$rel"
84
+
85
+ if [[ ! -f "$dst" ]]; then
86
+ missing_live+=("$rel")
87
+ continue
88
+ fi
89
+
90
+ if ! cmp -s "$src" "$dst"; then
91
+ mismatched+=("$rel")
92
+ fi
93
+ done
94
+
95
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
96
+ return 0
97
+ fi
98
+
99
+ if ((${#missing_live[@]} > 0)); then
100
+ printf 'Missing live Graphify assets under %s:\n' "$LIVE_ROOT" >&2
101
+ printf ' - %s\n' "${missing_live[@]}" >&2
102
+ fi
103
+
104
+ if ((${#mismatched[@]} > 0)); then
105
+ printf 'Out-of-sync live Graphify assets under %s:\n' "$LIVE_ROOT" >&2
106
+ printf ' - %s\n' "${mismatched[@]}" >&2
107
+ fi
108
+
109
+ return 1
110
+ }
111
+
112
+ run_check() {
113
+ ensure_source_assets_exist
114
+ check_asset_sync
115
+ printf 'Graphify command asset is in sync under %s\n' "$LIVE_ROOT"
116
+ }
117
+
118
+ run_apply() {
119
+ local rel
120
+ local src
121
+ local dst
122
+ local updated=0
123
+ local unchanged=0
124
+
125
+ ensure_source_assets_exist
126
+
127
+ for rel in "${ASSETS[@]}"; do
128
+ src="$SOURCE_ROOT/$rel"
129
+ dst="$LIVE_ROOT/$rel"
130
+
131
+ mkdir -p "$(dirname "$dst")"
132
+ if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
133
+ unchanged=$((unchanged + 1))
134
+ continue
135
+ fi
136
+
137
+ install -m 0644 "$src" "$dst"
138
+ updated=$((updated + 1))
139
+ done
140
+
141
+ printf 'Applied %s Graphify asset(s); %s already matched.\n' "$updated" "$unchanged"
142
+ run_check
143
+ }
144
+
145
+ case "$MODE" in
146
+ check)
147
+ run_check
148
+ ;;
149
+ apply)
150
+ run_apply
151
+ ;;
152
+ esac
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-memory-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed OpenCode memory and review assets against the
9
+ live OpenCode config tree.
10
+
11
+ Options:
12
+ --check Verify that the managed live assets match the repo copy (default)
13
+ --apply Copy the repo-managed assets into the live OpenCode tree, then verify
14
+ --live-root PATH Override the live OpenCode root (default: $OPENCODE_CONFIG_DIR or $HOME/.config/opencode)
15
+ -h, --help Show this help text
16
+
17
+ Notes:
18
+ - The repo-managed source of truth lives under ./opencode/
19
+ - This script manages the memory-sync / memory-review / code-review commands,
20
+ the code-reviewer agent, and the memory-review workflow. The memory-wakeup
21
+ plugin is handled by sync-opencode-plugin-assets.sh.
22
+ EOF
23
+ }
24
+
25
+ ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
26
+ SOURCE_ROOT="$ROOT_DIR/opencode"
27
+ LIVE_ROOT="${OPENCODE_CONFIG_DIR:-$HOME/.config/opencode}"
28
+ MODE="check"
29
+
30
+ ASSETS=(
31
+ "command/memory-sync.md"
32
+ "command/memory-review.md"
33
+ "command/code-review.md"
34
+ "command/remember.md"
35
+ "command/recall.md"
36
+ "agents/code-reviewer.md"
37
+ "workflows/memory-review-workflow.md"
38
+ )
39
+
40
+ while [[ $# -gt 0 ]]; do
41
+ case "$1" in
42
+ --check)
43
+ MODE="check"
44
+ shift
45
+ ;;
46
+ --apply)
47
+ MODE="apply"
48
+ shift
49
+ ;;
50
+ --live-root)
51
+ LIVE_ROOT="$2"
52
+ shift 2
53
+ ;;
54
+ -h|--help)
55
+ usage
56
+ exit 0
57
+ ;;
58
+ *)
59
+ echo "Unknown argument: $1" >&2
60
+ usage >&2
61
+ exit 2
62
+ ;;
63
+ esac
64
+ done
65
+
66
+ ensure_source_assets_exist() {
67
+ local rel
68
+ local missing=0
69
+
70
+ for rel in "${ASSETS[@]}"; do
71
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
72
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
73
+ missing=1
74
+ fi
75
+ done
76
+
77
+ if [[ $missing -ne 0 ]]; then
78
+ exit 2
79
+ fi
80
+ }
81
+
82
+ check_asset_sync() {
83
+ local rel
84
+ local src
85
+ local dst
86
+ local -a missing_live=()
87
+ local -a mismatched=()
88
+
89
+ for rel in "${ASSETS[@]}"; do
90
+ src="$SOURCE_ROOT/$rel"
91
+ dst="$LIVE_ROOT/$rel"
92
+
93
+ if [[ ! -f "$dst" ]]; then
94
+ missing_live+=("$rel")
95
+ continue
96
+ fi
97
+
98
+ if ! cmp -s "$src" "$dst"; then
99
+ mismatched+=("$rel")
100
+ fi
101
+ done
102
+
103
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
104
+ return 0
105
+ fi
106
+
107
+ if ((${#missing_live[@]} > 0)); then
108
+ printf 'Missing live memory/review assets under %s:\n' "$LIVE_ROOT" >&2
109
+ printf ' - %s\n' "${missing_live[@]}" >&2
110
+ fi
111
+
112
+ if ((${#mismatched[@]} > 0)); then
113
+ printf 'Out-of-sync live memory/review assets under %s:\n' "$LIVE_ROOT" >&2
114
+ printf ' - %s\n' "${mismatched[@]}" >&2
115
+ fi
116
+
117
+ return 1
118
+ }
119
+
120
+ run_check() {
121
+ ensure_source_assets_exist
122
+ check_asset_sync
123
+ printf 'Memory/review assets are in sync: %s managed files match %s\n' "${#ASSETS[@]}" "$LIVE_ROOT"
124
+ }
125
+
126
+ run_apply() {
127
+ local rel
128
+ local src
129
+ local dst
130
+ local updated=0
131
+ local unchanged=0
132
+
133
+ ensure_source_assets_exist
134
+
135
+ for rel in "${ASSETS[@]}"; do
136
+ src="$SOURCE_ROOT/$rel"
137
+ dst="$LIVE_ROOT/$rel"
138
+
139
+ mkdir -p "$(dirname "$dst")"
140
+ if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
141
+ unchanged=$((unchanged + 1))
142
+ continue
143
+ fi
144
+
145
+ install -m 0644 "$src" "$dst"
146
+ updated=$((updated + 1))
147
+ done
148
+
149
+ printf 'Applied %s memory/review asset(s); %s already matched.\n' "$updated" "$unchanged"
150
+ run_check
151
+ }
152
+
153
+ case "$MODE" in
154
+ check)
155
+ run_check
156
+ ;;
157
+ apply)
158
+ run_apply
159
+ ;;
160
+ esac