@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,163 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-plugin-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed OpenCode plugin assets against the live
9
+ OpenCode config tree.
10
+
11
+ Options:
12
+ --check Verify that the managed live assets match the repo copies (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/plugins/
19
+ - OpenCode auto-discovers plugins/*.ts, so applying here activates them on the next session
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
+ "plugins/memory-wakeup.ts"
30
+ "plugins/memory-capture.ts"
31
+ "plugins/memory-recall.ts"
32
+ )
33
+
34
+ while [[ $# -gt 0 ]]; do
35
+ case "$1" in
36
+ --check)
37
+ MODE="check"
38
+ shift
39
+ ;;
40
+ --apply)
41
+ MODE="apply"
42
+ shift
43
+ ;;
44
+ --live-root)
45
+ LIVE_ROOT="$2"
46
+ shift 2
47
+ ;;
48
+ -h|--help)
49
+ usage
50
+ exit 0
51
+ ;;
52
+ *)
53
+ echo "Unknown argument: $1" >&2
54
+ usage >&2
55
+ exit 2
56
+ ;;
57
+ esac
58
+ done
59
+
60
+ render_asset() {
61
+ local src="$1"
62
+ sed "s|@@INFRA_ROOT@@|$ROOT_DIR|g" "$src"
63
+ }
64
+
65
+ ensure_source_assets_exist() {
66
+ local rel
67
+ local missing=0
68
+
69
+ for rel in "${ASSETS[@]}"; do
70
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
71
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
72
+ missing=1
73
+ fi
74
+ done
75
+
76
+ if [[ $missing -ne 0 ]]; then
77
+ exit 2
78
+ fi
79
+ }
80
+
81
+ check_asset_sync() {
82
+ local rel
83
+ local src
84
+ local dst
85
+ local -a missing_live=()
86
+ local -a mismatched=()
87
+
88
+ for rel in "${ASSETS[@]}"; do
89
+ src="$SOURCE_ROOT/$rel"
90
+ dst="$LIVE_ROOT/$rel"
91
+
92
+ if [[ ! -f "$dst" ]]; then
93
+ missing_live+=("$rel")
94
+ continue
95
+ fi
96
+
97
+ if ! cmp -s <(render_asset "$src") "$dst"; then
98
+ mismatched+=("$rel")
99
+ fi
100
+ done
101
+
102
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
103
+ return 0
104
+ fi
105
+
106
+ if ((${#missing_live[@]} > 0)); then
107
+ printf 'Missing live OpenCode plugin assets under %s:\n' "$LIVE_ROOT" >&2
108
+ printf ' - %s\n' "${missing_live[@]}" >&2
109
+ fi
110
+
111
+ if ((${#mismatched[@]} > 0)); then
112
+ printf 'Out-of-sync live OpenCode plugin assets under %s:\n' "$LIVE_ROOT" >&2
113
+ printf ' - %s\n' "${mismatched[@]}" >&2
114
+ fi
115
+
116
+ return 1
117
+ }
118
+
119
+ run_check() {
120
+ ensure_source_assets_exist
121
+ check_asset_sync
122
+ printf 'OpenCode plugin assets are in sync under %s\n' "$LIVE_ROOT"
123
+ }
124
+
125
+ run_apply() {
126
+ local rel
127
+ local src
128
+ local dst
129
+ local rendered_tmp
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 <(render_asset "$src") "$dst"; then
141
+ unchanged=$((unchanged + 1))
142
+ continue
143
+ fi
144
+
145
+ rendered_tmp=$(mktemp)
146
+ render_asset "$src" > "$rendered_tmp"
147
+ install -m 0644 "$rendered_tmp" "$dst"
148
+ rm -f "$rendered_tmp"
149
+ updated=$((updated + 1))
150
+ done
151
+
152
+ printf 'Applied %s OpenCode plugin asset(s); %s already matched.\n' "$updated" "$unchanged"
153
+ run_check
154
+ }
155
+
156
+ case "$MODE" in
157
+ check)
158
+ run_check
159
+ ;;
160
+ apply)
161
+ run_apply
162
+ ;;
163
+ esac
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-security-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed OpenCode security-audit 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 security-audit OpenCode assets
20
+ - --apply also removes the legacy gsd-overrides security-audit workflow/template files
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/security-audit.md"
31
+ "agents/security-target-selector.md"
32
+ "agents/security-investigator.md"
33
+ "agents/security-validator.md"
34
+ "workflows/security-audit-workflow.md"
35
+ "templates/security-policy.md.template"
36
+ "templates/security-report.md.template"
37
+ "templates/security-finding-register.yaml.template"
38
+ "templates/security-issue.md.template"
39
+ )
40
+
41
+ LEGACY_ASSETS=(
42
+ "gsd-overrides/workflows/security-audit.md"
43
+ "gsd-overrides/templates/security-policy.md.template"
44
+ "gsd-overrides/templates/security-report.md.template"
45
+ "gsd-overrides/templates/security-finding-register.yaml.template"
46
+ "gsd-overrides/templates/security-issue.md.template"
47
+ )
48
+
49
+ while [[ $# -gt 0 ]]; do
50
+ case "$1" in
51
+ --check)
52
+ MODE="check"
53
+ shift
54
+ ;;
55
+ --apply)
56
+ MODE="apply"
57
+ shift
58
+ ;;
59
+ --live-root)
60
+ LIVE_ROOT="$2"
61
+ shift 2
62
+ ;;
63
+ -h|--help)
64
+ usage
65
+ exit 0
66
+ ;;
67
+ *)
68
+ echo "Unknown argument: $1" >&2
69
+ usage >&2
70
+ exit 2
71
+ ;;
72
+ esac
73
+ done
74
+
75
+ ensure_source_assets_exist() {
76
+ local rel
77
+ local missing=0
78
+
79
+ for rel in "${ASSETS[@]}"; do
80
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
81
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
82
+ missing=1
83
+ fi
84
+ done
85
+
86
+ if [[ $missing -ne 0 ]]; then
87
+ exit 2
88
+ fi
89
+ }
90
+
91
+ report_legacy_live_assets() {
92
+ local rel
93
+ local -a legacy_found=()
94
+
95
+ for rel in "${LEGACY_ASSETS[@]}"; do
96
+ if [[ -f "$LIVE_ROOT/$rel" ]]; then
97
+ legacy_found+=("$rel")
98
+ fi
99
+ done
100
+
101
+ if ((${#legacy_found[@]} == 0)); then
102
+ return
103
+ fi
104
+
105
+ printf 'Warning: found legacy live security-audit assets under %s:\n' "$LIVE_ROOT" >&2
106
+ printf ' - %s\n' "${legacy_found[@]}" >&2
107
+ }
108
+
109
+ run_check() {
110
+ local rel
111
+ local src
112
+ local dst
113
+ local -a missing_live=()
114
+ local -a mismatched=()
115
+
116
+ ensure_source_assets_exist
117
+
118
+ for rel in "${ASSETS[@]}"; do
119
+ src="$SOURCE_ROOT/$rel"
120
+ dst="$LIVE_ROOT/$rel"
121
+
122
+ if [[ ! -f "$dst" ]]; then
123
+ missing_live+=("$rel")
124
+ continue
125
+ fi
126
+
127
+ if ! cmp -s "$src" "$dst"; then
128
+ mismatched+=("$rel")
129
+ fi
130
+ done
131
+
132
+ report_legacy_live_assets
133
+
134
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
135
+ printf 'Security-audit assets are in sync: %s managed files match %s\n' "${#ASSETS[@]}" "$LIVE_ROOT"
136
+ return 0
137
+ fi
138
+
139
+ if ((${#missing_live[@]} > 0)); then
140
+ printf 'Missing live security-audit assets under %s:\n' "$LIVE_ROOT" >&2
141
+ printf ' - %s\n' "${missing_live[@]}" >&2
142
+ fi
143
+
144
+ if ((${#mismatched[@]} > 0)); then
145
+ printf 'Out-of-sync live security-audit assets under %s:\n' "$LIVE_ROOT" >&2
146
+ printf ' - %s\n' "${mismatched[@]}" >&2
147
+ fi
148
+
149
+ return 1
150
+ }
151
+
152
+ run_apply() {
153
+ local rel
154
+ local src
155
+ local dst
156
+ local updated=0
157
+ local unchanged=0
158
+ local removed_legacy=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
+ for rel in "${LEGACY_ASSETS[@]}"; do
177
+ dst="$LIVE_ROOT/$rel"
178
+ if [[ -f "$dst" ]]; then
179
+ rm -f "$dst"
180
+ removed_legacy=$((removed_legacy + 1))
181
+ fi
182
+ done
183
+
184
+ printf 'Applied %s security-audit asset(s); %s already matched; removed %s legacy asset(s).\n' "$updated" "$unchanged" "$removed_legacy"
185
+ run_check
186
+ }
187
+
188
+ case "$MODE" in
189
+ check)
190
+ run_check
191
+ ;;
192
+ apply)
193
+ run_apply
194
+ ;;
195
+ esac
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'EOF'
6
+ Usage: sync-opencode-wiki-assets.sh [--check|--apply] [--live-root PATH]
7
+
8
+ Compare or sync the repo-managed OpenCode wiki 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 wiki-specific OpenCode assets
20
+ - Extra live wiki assets are reported as warnings but are not deleted automatically
21
+ - --apply also removes the legacy gsd-overrides wiki workflow/template files
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/wiki-ingest.md"
32
+ "command/wiki-query.md"
33
+ "command/wiki-lint.md"
34
+ "agents/wiki-ingester.md"
35
+ "agents/wiki-query-analyst.md"
36
+ "agents/wiki-lint-auditor.md"
37
+ "workflows/wiki-ingest-workflow.md"
38
+ "workflows/wiki-query-workflow.md"
39
+ "workflows/wiki-lint-workflow.md"
40
+ "templates/wiki-policy.md.template"
41
+ "templates/wiki-index.md.template"
42
+ "templates/wiki-log.md.template"
43
+ "templates/wiki-source-summary.md.template"
44
+ "templates/wiki-query-answer.md.template"
45
+ "templates/wiki-lint-report.md.template"
46
+ "templates/wiki-contradictions.md.template"
47
+ )
48
+
49
+ LEGACY_ASSETS=(
50
+ "gsd-overrides/workflows/wiki-ingest.md"
51
+ "gsd-overrides/workflows/wiki-query.md"
52
+ "gsd-overrides/workflows/wiki-lint.md"
53
+ "gsd-overrides/templates/wiki-policy.md.template"
54
+ "gsd-overrides/templates/wiki-index.md.template"
55
+ "gsd-overrides/templates/wiki-log.md.template"
56
+ "gsd-overrides/templates/wiki-source-summary.md.template"
57
+ "gsd-overrides/templates/wiki-query-answer.md.template"
58
+ "gsd-overrides/templates/wiki-lint-report.md.template"
59
+ )
60
+
61
+ while [[ $# -gt 0 ]]; do
62
+ case "$1" in
63
+ --check)
64
+ MODE="check"
65
+ shift
66
+ ;;
67
+ --apply)
68
+ MODE="apply"
69
+ shift
70
+ ;;
71
+ --live-root)
72
+ LIVE_ROOT="$2"
73
+ shift 2
74
+ ;;
75
+ -h|--help)
76
+ usage
77
+ exit 0
78
+ ;;
79
+ *)
80
+ echo "Unknown argument: $1" >&2
81
+ usage >&2
82
+ exit 2
83
+ ;;
84
+ esac
85
+ done
86
+
87
+ collect_extra_live_assets() {
88
+ local root="$1"
89
+ local -A managed=()
90
+ local rel
91
+
92
+ for rel in "${ASSETS[@]}"; do
93
+ managed["$rel"]=1
94
+ done
95
+
96
+ while IFS= read -r rel; do
97
+ [[ -n "$rel" ]] || continue
98
+ if [[ -z "${managed[$rel]:-}" ]]; then
99
+ printf '%s\n' "$rel"
100
+ fi
101
+ done < <(
102
+ (
103
+ find "$root/command" -maxdepth 1 -type f \( -name 'gsd-wiki-*.md' -o -name 'wiki-*.md' \) -printf 'command/%f\n' 2>/dev/null || true
104
+ find "$root/agents" -maxdepth 1 -type f \( -name 'gsd-wiki-*.md' -o -name 'wiki-*.md' \) -printf 'agents/%f\n' 2>/dev/null || true
105
+ find "$root/workflows" -maxdepth 1 -type f -name 'wiki-*.md' -printf 'workflows/%f\n' 2>/dev/null || true
106
+ find "$root/templates" -maxdepth 1 -type f -name 'wiki-*.template' -printf 'templates/%f\n' 2>/dev/null || true
107
+ find "$root/gsd-overrides/workflows" -maxdepth 1 -type f -name 'wiki-*.md' -printf 'gsd-overrides/workflows/%f\n' 2>/dev/null || true
108
+ find "$root/gsd-overrides/templates" -maxdepth 1 -type f -name 'wiki-*.template' -printf 'gsd-overrides/templates/%f\n' 2>/dev/null || true
109
+ ) | sort -u
110
+ )
111
+ }
112
+
113
+ ensure_source_assets_exist() {
114
+ local rel
115
+ local missing=0
116
+
117
+ for rel in "${ASSETS[@]}"; do
118
+ if [[ ! -f "$SOURCE_ROOT/$rel" ]]; then
119
+ echo "Missing repo-managed source asset: $SOURCE_ROOT/$rel" >&2
120
+ missing=1
121
+ fi
122
+ done
123
+
124
+ if [[ $missing -ne 0 ]]; then
125
+ exit 2
126
+ fi
127
+ }
128
+
129
+ report_extra_live_assets() {
130
+ local -a extras=()
131
+
132
+ if [[ ! -d "$LIVE_ROOT" ]]; then
133
+ return
134
+ fi
135
+
136
+ mapfile -t extras < <(collect_extra_live_assets "$LIVE_ROOT")
137
+ if ((${#extras[@]} == 0)); then
138
+ return
139
+ fi
140
+
141
+ printf 'Warning: found unmanaged live wiki assets under %s:\n' "$LIVE_ROOT" >&2
142
+ printf ' - %s\n' "${extras[@]}" >&2
143
+ }
144
+
145
+ report_legacy_live_assets() {
146
+ local -a extras=()
147
+ local rel
148
+
149
+ for rel in "${LEGACY_ASSETS[@]}"; do
150
+ if [[ -f "$LIVE_ROOT/$rel" ]]; then
151
+ extras+=("$rel")
152
+ fi
153
+ done
154
+
155
+ if ((${#extras[@]} == 0)); then
156
+ return
157
+ fi
158
+
159
+ printf 'Warning: found legacy live wiki assets under %s:\n' "$LIVE_ROOT" >&2
160
+ printf ' - %s\n' "${extras[@]}" >&2
161
+ }
162
+
163
+ run_check() {
164
+ local rel
165
+ local src
166
+ local dst
167
+ local -a missing_live=()
168
+ local -a mismatched=()
169
+
170
+ ensure_source_assets_exist
171
+
172
+ for rel in "${ASSETS[@]}"; do
173
+ src="$SOURCE_ROOT/$rel"
174
+ dst="$LIVE_ROOT/$rel"
175
+
176
+ if [[ ! -f "$dst" ]]; then
177
+ missing_live+=("$rel")
178
+ continue
179
+ fi
180
+
181
+ if ! cmp -s "$src" "$dst"; then
182
+ mismatched+=("$rel")
183
+ fi
184
+ done
185
+
186
+ report_extra_live_assets
187
+ report_legacy_live_assets
188
+
189
+ if ((${#missing_live[@]} == 0 && ${#mismatched[@]} == 0)); then
190
+ printf 'Wiki assets are in sync: %s managed files match %s\n' "${#ASSETS[@]}" "$LIVE_ROOT"
191
+ return 0
192
+ fi
193
+
194
+ if ((${#missing_live[@]} > 0)); then
195
+ printf 'Missing live wiki assets under %s:\n' "$LIVE_ROOT" >&2
196
+ printf ' - %s\n' "${missing_live[@]}" >&2
197
+ fi
198
+
199
+ if ((${#mismatched[@]} > 0)); then
200
+ printf 'Out-of-sync live wiki assets under %s:\n' "$LIVE_ROOT" >&2
201
+ printf ' - %s\n' "${mismatched[@]}" >&2
202
+ fi
203
+
204
+ return 1
205
+ }
206
+
207
+ run_apply() {
208
+ local rel
209
+ local src
210
+ local dst
211
+ local updated=0
212
+ local unchanged=0
213
+ local removed_legacy=0
214
+
215
+ ensure_source_assets_exist
216
+
217
+ for rel in "${ASSETS[@]}"; do
218
+ src="$SOURCE_ROOT/$rel"
219
+ dst="$LIVE_ROOT/$rel"
220
+
221
+ mkdir -p "$(dirname "$dst")"
222
+ if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then
223
+ unchanged=$((unchanged + 1))
224
+ continue
225
+ fi
226
+
227
+ install -m 0644 "$src" "$dst"
228
+ updated=$((updated + 1))
229
+ done
230
+
231
+ for rel in "${LEGACY_ASSETS[@]}"; do
232
+ dst="$LIVE_ROOT/$rel"
233
+ if [[ -f "$dst" ]]; then
234
+ rm -f "$dst"
235
+ removed_legacy=$((removed_legacy + 1))
236
+ fi
237
+ done
238
+
239
+ printf 'Applied %s wiki asset(s); %s already matched; removed %s legacy asset(s).\n' "$updated" "$unchanged" "$removed_legacy"
240
+ run_check
241
+ }
242
+
243
+ case "$MODE" in
244
+ check)
245
+ run_check
246
+ ;;
247
+ apply)
248
+ run_apply
249
+ ;;
250
+ esac
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # Render test for install-audit-timer.sh: --render-only substitutes every
3
+ # placeholder and produces valid-looking unit files (no systemd required).
4
+ set -euo pipefail
5
+
6
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
7
+ tmp=$(mktemp -d)
8
+ trap 'rm -rf "$tmp"' EXIT
9
+ fail() { echo "FAIL: $1" >&2; exit 1; }
10
+
11
+ "$ROOT/scripts/install-audit-timer.sh" \
12
+ --para-root "$tmp/PARA" --on-calendar "Mon *-*-* 04:00:00" \
13
+ --report "$tmp/audit.md" --render-only "$tmp/units" >/dev/null
14
+
15
+ svc="$tmp/units/memory-wiki-audit.service"
16
+ tmr="$tmp/units/memory-wiki-audit.timer"
17
+ [[ -f "$svc" && -f "$tmr" ]] || fail "unit files not rendered"
18
+
19
+ # No placeholders left.
20
+ ! grep -q "@@" "$svc" || fail "unsubstituted @@ placeholder left in service"
21
+ ! grep -q "@@" "$tmr" || fail "unsubstituted @@ placeholder left in timer"
22
+
23
+ # Substitutions landed.
24
+ grep -q "scripts/memory-wiki-audit.sh --para-root $tmp/PARA --report $tmp/audit.md" "$svc" \
25
+ || fail "service ExecStart not substituted as expected"
26
+ grep -q "OnCalendar=Mon \*-\*-\* 04:00:00" "$tmr" || fail "timer OnCalendar not substituted"
27
+ grep -q "WantedBy=timers.target" "$tmr" || fail "timer missing [Install] WantedBy"
28
+
29
+ echo "PASS: audit-timer render (placeholders substituted, units well-formed)"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # Smoke test for `cairn bootstrap --untracked` (contributor mode).
3
+ set -euo pipefail
4
+
5
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
6
+ tmp=$(mktemp -d)
7
+ trap 'rm -rf "$tmp"' EXIT
8
+
9
+ fail() {
10
+ echo "FAIL: $1" >&2
11
+ exit 1
12
+ }
13
+
14
+ # --untracked outside a git repo fails before creating anything
15
+ mkdir "$tmp/plain"
16
+ if "$ROOT/scripts/bootstrap.sh" --untracked "$tmp/plain" >/dev/null 2>&1; then
17
+ fail "--untracked should refuse a non-git target"
18
+ fi
19
+ [[ ! -e "$tmp/plain/.ai" ]] || fail "non-git target was partially scaffolded"
20
+
21
+ # In a git repo: scaffold exists, git sees nothing, entries are anchored
22
+ mkdir "$tmp/repo"
23
+ git -C "$tmp/repo" init -q
24
+ "$ROOT/scripts/bootstrap.sh" --untracked "$tmp/repo" >/dev/null
25
+ [[ -f "$tmp/repo/.ai/start-claude.sh" ]] || fail "scaffold missing"
26
+ [[ -f "$tmp/repo/.planning/config.json" ]] || fail "planning layer missing"
27
+ [[ -z "$(git -C "$tmp/repo" status --porcelain)" ]] || fail "scaffold visible to git"
28
+ grep -qxF "/.ai/" "$tmp/repo/.git/info/exclude" || fail "missing /.ai/ exclude entry"
29
+ grep -qxF "/.planning/" "$tmp/repo/.git/info/exclude" || fail "missing /.planning/ exclude entry"
30
+
31
+ # Re-run is idempotent: no duplicate exclude entries
32
+ "$ROOT/scripts/bootstrap.sh" --untracked "$tmp/repo" >/dev/null
33
+ [[ $(grep -cxF "/.ai/" "$tmp/repo/.git/info/exclude") -eq 1 ]] || fail "duplicate exclude entries"
34
+
35
+ # Default mode is unchanged: scaffold stays visible to git
36
+ mkdir "$tmp/repo2"
37
+ git -C "$tmp/repo2" init -q
38
+ "$ROOT/scripts/bootstrap.sh" "$tmp/repo2" >/dev/null
39
+ git -C "$tmp/repo2" status --porcelain | grep -q "\.ai/" || fail "default mode should leave the scaffold tracked"
40
+
41
+ echo "PASS: bootstrap --untracked contributor mode"
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env bash
2
+ # Smoke test: bin/cairn dispatches the new subcommands.
3
+ set -euo pipefail
4
+ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
5
+ fail() { echo "FAIL: $1" >&2; exit 1; }
6
+ cairn="$ROOT/bin/cairn"
7
+
8
+ "$cairn" help | grep -q "cairn doctor" || fail "help missing doctor"
9
+ "$cairn" help | grep -q "cairn memory" || fail "help missing memory"
10
+ "$cairn" help | grep -q "cairn audit-timer" || fail "help missing audit-timer"
11
+ "$cairn" help | grep -q "cairn sync" || fail "help missing sync"
12
+ "$cairn" help | grep -q "cairn memory-server" || fail "help missing memory-server"
13
+ "$cairn" sync --help >/dev/null 2>&1 || fail "cairn sync dispatch"
14
+
15
+ tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
16
+ CAIRN_AGENTFS_BASE_DIR="$tmp/store" "$cairn" memory path | grep -qx "$tmp/store" || fail "cairn memory path dispatch"
17
+ ( cd "$tmp" && "$cairn" doctor ) >/dev/null || fail "cairn doctor dispatch (clean env should exit 0)"
18
+ "$cairn" audit-timer --render-only "$tmp/u" >/dev/null || fail "cairn audit-timer dispatch"
19
+ [[ -f "$tmp/u/memory-wiki-audit.timer" ]] || fail "audit-timer render via cairn produced no units"
20
+
21
+ echo "PASS: cairn dispatch (doctor, memory, audit-timer)"