@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,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)"
|