skillwiki 0.9.63 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5TBIMLTZ.js +343 -0
- package/dist/chunk-C5OLZRRM.js +357 -0
- package/dist/{chunk-TUFQZ5K4.js → chunk-DR7KFHNH.js} +792 -1983
- package/dist/chunk-IZABIE44.js +647 -0
- package/dist/chunk-S5ABQCXQ.js +580 -0
- package/dist/cli.js +1540 -550
- package/dist/index-projection-ERFX76U5.js +10 -0
- package/dist/managed-write-preflight-SILEUQEV.js +11 -0
- package/dist/skillwiki-mcp.js +4 -1
- package/dist/vault-sync/scripts/lib/conflict-markers.sh +69 -0
- package/dist/vault-sync/scripts/lib/delete-intent.sh +74 -0
- package/dist/vault-sync/scripts/lib/fleet.sh +103 -0
- package/dist/vault-sync/scripts/lib/git-case.sh +71 -0
- package/dist/vault-sync/scripts/lib/git-materialization.sh +264 -0
- package/dist/vault-sync/scripts/lib/git-operation-journal.sh +469 -0
- package/dist/vault-sync/scripts/lib/git-rebase-state.sh +180 -0
- package/dist/vault-sync/scripts/lib/lockfile.sh +70 -0
- package/dist/vault-sync/scripts/lib/managed-write-lock.sh +80 -0
- package/dist/vault-sync/scripts/lib/platform.sh +184 -0
- package/dist/vault-sync/scripts/lib/runtime-manifest.sh +223 -0
- package/dist/vault-sync/scripts/wiki-fetch-notify.sh +207 -0
- package/dist/vault-sync/scripts/wiki-fuse-refresh.sh +405 -0
- package/dist/vault-sync/scripts/wiki-pull-with-auto-resolve.sh +631 -0
- package/dist/vault-sync/scripts/wiki-push.sh +364 -0
- package/dist/vault-sync/scripts/wiki-snapshot.sh +587 -0
- package/package.json +2 -2
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/README.md +13 -0
- package/skills/package.json +1 -1
- package/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/proj-work/SKILL.md +3 -0
- package/skills/skills/using-skillwiki/SKILL.md +13 -0
- package/skills/skills/wiki-crystallize/SKILL.md +3 -0
- package/skills/using-skillwiki/SKILL.md +13 -0
- package/skills/wiki-crystallize/SKILL.md +3 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# wiki-snapshot.sh — Linux-only S3 → git snapshot/promotion job.
|
|
3
|
+
#
|
|
4
|
+
# Source-of-truth: this is the canonical vault-sync copy. The deployed copy on
|
|
5
|
+
# sg01 lives under /root/.local/share/vault-sync/bin; Hermes is not part of the
|
|
6
|
+
# production snapshot path. See work item:
|
|
7
|
+
# projects/llm-wiki/work/2026-05-25-vault-sync-plugin-scaffold/
|
|
8
|
+
#
|
|
9
|
+
# Origin: migrated 2026-05-25 from sg01 wiki-snapshot-v3.sh (sha256:
|
|
10
|
+
# 037b05ddb6b47e377c3ef493e69730bc3ff3e3ccaa51d56edb7027091582f383).
|
|
11
|
+
#
|
|
12
|
+
# Hard Rule (NON-NEGOTIABLE):
|
|
13
|
+
# The --max-delete 10 guard in RCLONE_OPTS MUST be preserved.
|
|
14
|
+
# Without it, momentary S3 inconsistency mass-deletes files from GitHub.
|
|
15
|
+
# Reference: raw/transcripts/2026-05-23-bug-sg01-snapshot-destructive-rclone-sync.md
|
|
16
|
+
#
|
|
17
|
+
# Single-writer-git invariant: only ONE host per vault remote may run this.
|
|
18
|
+
# sg01 is the current snapshotter (role: snapshotter in fleet.yaml).
|
|
19
|
+
# Running on a second host violates the authority model.
|
|
20
|
+
|
|
21
|
+
set -o pipefail
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
24
|
+
|
|
25
|
+
if [ -n "${GIT_DIR:-}" ]; then
|
|
26
|
+
echo "[wiki-snapshot] WARNING: ignoring exported GIT_DIR; use WIKI_GIT_WORKTREE for the snapshot worktree" >&2
|
|
27
|
+
unset GIT_DIR
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Source platform.sh — handles both dev (scripts/lib/) and deployed (lib/) layouts
|
|
31
|
+
if [ -f "$SCRIPT_DIR/lib/platform.sh" ]; then
|
|
32
|
+
source "$SCRIPT_DIR/lib/platform.sh"
|
|
33
|
+
elif [ -f "$SCRIPT_DIR/scripts/lib/platform.sh" ]; then
|
|
34
|
+
source "$SCRIPT_DIR/scripts/lib/platform.sh"
|
|
35
|
+
elif [ -f "$SCRIPT_DIR/../../scripts/lib/platform.sh" ]; then
|
|
36
|
+
source "$SCRIPT_DIR/../../scripts/lib/platform.sh"
|
|
37
|
+
fi
|
|
38
|
+
platform_detect_os
|
|
39
|
+
|
|
40
|
+
if [ -f "$SCRIPT_DIR/lib/git-case.sh" ]; then
|
|
41
|
+
source "$SCRIPT_DIR/lib/git-case.sh"
|
|
42
|
+
elif [ -f "$SCRIPT_DIR/scripts/lib/git-case.sh" ]; then
|
|
43
|
+
source "$SCRIPT_DIR/scripts/lib/git-case.sh"
|
|
44
|
+
elif [ -f "$SCRIPT_DIR/../../scripts/lib/git-case.sh" ]; then
|
|
45
|
+
source "$SCRIPT_DIR/../../scripts/lib/git-case.sh"
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [ -f "$SCRIPT_DIR/lib/conflict-markers.sh" ]; then
|
|
49
|
+
source "$SCRIPT_DIR/lib/conflict-markers.sh"
|
|
50
|
+
elif [ -f "$SCRIPT_DIR/scripts/lib/conflict-markers.sh" ]; then
|
|
51
|
+
source "$SCRIPT_DIR/scripts/lib/conflict-markers.sh"
|
|
52
|
+
elif [ -f "$SCRIPT_DIR/../../scripts/lib/conflict-markers.sh" ]; then
|
|
53
|
+
source "$SCRIPT_DIR/../../scripts/lib/conflict-markers.sh"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [ -f "$SCRIPT_DIR/lib/delete-intent.sh" ]; then
|
|
57
|
+
source "$SCRIPT_DIR/lib/delete-intent.sh"
|
|
58
|
+
elif [ -f "$SCRIPT_DIR/scripts/lib/delete-intent.sh" ]; then
|
|
59
|
+
source "$SCRIPT_DIR/scripts/lib/delete-intent.sh"
|
|
60
|
+
elif [ -f "$SCRIPT_DIR/../../scripts/lib/delete-intent.sh" ]; then
|
|
61
|
+
source "$SCRIPT_DIR/../../scripts/lib/delete-intent.sh"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if ! command -v vault_sync_scan_conflict_markers >/dev/null 2>&1; then
|
|
65
|
+
echo "[wiki-snapshot] ERROR: conflict-marker helper unavailable; refusing to run." >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# ── Guard: Linux-only operation ────────────────────────────
|
|
70
|
+
platform_require linux
|
|
71
|
+
|
|
72
|
+
# ── Path configuration (env-overridable for plugin portability) ─
|
|
73
|
+
WIKI_DIR="${WIKI_DIR:-/root/wiki}"
|
|
74
|
+
SNAPSHOT_WORKTREE="${WIKI_GIT_WORKTREE:-${SNAPSHOT_WORKTREE:-/root/wiki-git}}"
|
|
75
|
+
LOCK_FILE="${WIKI_SNAPSHOT_LOCK:-/var/lock/wiki-snapshot.lock}"
|
|
76
|
+
DEFAULT_LOG_DIR="$(platform_log_dir)"
|
|
77
|
+
LOG_FILE="${WIKI_SNAPSHOT_LOG:-$DEFAULT_LOG_DIR/wiki-snapshot.log}"
|
|
78
|
+
CLOUD_REMOTE="${CLOUD_REMOTE:-cloud:cloud/wiki}"
|
|
79
|
+
REPAIR_SCRIPT="${WIKI_GIT_REPAIR_SCRIPT:-$SCRIPT_DIR/wiki-git-repair-v3.sh}"
|
|
80
|
+
MAX_S3_ONLY_NOTES="${WIKI_SNAPSHOT_MAX_S3_ONLY_NOTES:-200}"
|
|
81
|
+
DATE=$(date +%Y%m%d_%H%M%S)
|
|
82
|
+
RCLONE_LOG="/tmp/rclone-${DATE}.log"
|
|
83
|
+
SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT=0
|
|
84
|
+
|
|
85
|
+
fetch_origin_main_ref() {
|
|
86
|
+
# The snapshot guards read origin/main directly. Use an explicit destination
|
|
87
|
+
# refspec so a damaged/minimal remote.fetch config cannot leave it stale.
|
|
88
|
+
git fetch --quiet origin +refs/heads/main:refs/remotes/origin/main
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
snapshot_direct_s3_preflight() {
|
|
92
|
+
local tmp_dir
|
|
93
|
+
tmp_dir="$(mktemp -d)"
|
|
94
|
+
# Ensure tmp_dir is cleaned up on any return path or interrupt.
|
|
95
|
+
trap 'rm -rf "$tmp_dir"' RETURN
|
|
96
|
+
local direct_paths="$tmp_dir/direct-s3.paths"
|
|
97
|
+
local direct_notes="$tmp_dir/direct-s3-notes.paths"
|
|
98
|
+
local git_paths="$tmp_dir/git.paths"
|
|
99
|
+
local direct_not_git="$tmp_dir/direct-s3-not-git.paths"
|
|
100
|
+
|
|
101
|
+
if ! rclone lsf "$CLOUD_REMOTE" --recursive --files-only 2>/dev/null | LC_ALL=C sort > "$direct_paths"; then
|
|
102
|
+
echo "[wiki-snapshot] WARN: direct-S3 preflight could not list $CLOUD_REMOTE"
|
|
103
|
+
return 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
grep -vE '^(\.skillwiki/|\.claude/|\.obsidian/|\.antigravitycli/|\.playwright-cli/|raw/\._\.DS_Store$|\._\.DS_Store$)' "$direct_paths" | LC_ALL=C sort -u > "$direct_notes" || true
|
|
107
|
+
(
|
|
108
|
+
cd "$SNAPSHOT_WORKTREE" || exit 1
|
|
109
|
+
fetch_origin_main_ref 2>/dev/null || true
|
|
110
|
+
if git rev-parse --verify --quiet origin/main >/dev/null 2>&1; then
|
|
111
|
+
git -c core.quotePath=false ls-tree -r --name-only origin/main
|
|
112
|
+
else
|
|
113
|
+
git -c core.quotePath=false ls-files
|
|
114
|
+
fi | LC_ALL=C sort -u
|
|
115
|
+
) > "$git_paths" 2>/dev/null || {
|
|
116
|
+
echo "[wiki-snapshot] WARN: direct-S3 preflight could not list tracked files in snapshot worktree $SNAPSHOT_WORKTREE"
|
|
117
|
+
return 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
LC_ALL=C comm -23 "$direct_notes" "$git_paths" > "$direct_not_git"
|
|
121
|
+
SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT="$(wc -l < "$direct_not_git" | tr -d ' ')"
|
|
122
|
+
if [ "$SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT" != "0" ]; then
|
|
123
|
+
echo "[wiki-snapshot] WARN: direct-S3-not-git warning: $SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT note path(s) exist in direct S3 but not in $SNAPSHOT_WORKTREE"
|
|
124
|
+
sed -n '1,20p' "$direct_not_git" | sed 's/^/[wiki-snapshot] WARN: direct-S3-not-git: /'
|
|
125
|
+
return 2
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
return 0
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handle_direct_s3_preflight_before_sync() {
|
|
132
|
+
snapshot_direct_s3_preflight
|
|
133
|
+
local rc=$?
|
|
134
|
+
if [ "$rc" -eq 0 ]; then
|
|
135
|
+
return 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [ "${WIKI_SNAPSHOT_ALLOW_S3_ONLY_NOTES:-0}" = "1" ]; then
|
|
139
|
+
log "WARNING: direct-S3 preflight found note paths missing from Git; WIKI_SNAPSHOT_ALLOW_S3_ONLY_NOTES=1 allows this live snapshot"
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
case "$MAX_S3_ONLY_NOTES" in
|
|
144
|
+
''|*[!0-9]*)
|
|
145
|
+
log "ERROR: invalid WIKI_SNAPSHOT_MAX_S3_ONLY_NOTES=$MAX_S3_ONLY_NOTES; refusing live snapshot"
|
|
146
|
+
return 1
|
|
147
|
+
;;
|
|
148
|
+
esac
|
|
149
|
+
|
|
150
|
+
if [ "$SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT" -le "$MAX_S3_ONLY_NOTES" ]; then
|
|
151
|
+
log "WARNING: direct-S3 preflight found $SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT note path(s) missing from Git; within limit $MAX_S3_ONLY_NOTES, allowing live snapshot"
|
|
152
|
+
return 0
|
|
153
|
+
fi
|
|
154
|
+
|
|
155
|
+
log "ERROR: direct-S3-not-git count exceeds limit: $SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT > $MAX_S3_ONLY_NOTES"
|
|
156
|
+
log "ERROR: Review and promote/delete the S3-only note paths first, raise WIKI_SNAPSHOT_MAX_S3_ONLY_NOTES, or set WIKI_SNAPSHOT_ALLOW_S3_ONLY_NOTES=1 for an explicitly approved promotion run"
|
|
157
|
+
return 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ── Guard: --max-delete verification ───────────────────────
|
|
161
|
+
# Verify that this script (or a target script) has the --max-delete guard.
|
|
162
|
+
# Returns 0 if guard is present, 1 if missing.
|
|
163
|
+
wiki_snapshot_assert_guards() {
|
|
164
|
+
local script_path="${1:-$0}"
|
|
165
|
+
|
|
166
|
+
if [ ! -f "$script_path" ]; then
|
|
167
|
+
echo "FATAL: snapshot script not found: $script_path" >&2
|
|
168
|
+
return 1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if grep -q -- '--max-delete' "$script_path" 2>/dev/null; then
|
|
172
|
+
return 0
|
|
173
|
+
else
|
|
174
|
+
echo "FATAL: --max-delete guard MISSING from $script_path" >&2
|
|
175
|
+
echo "Do NOT run this script on production until the guard is added." >&2
|
|
176
|
+
echo "Reference: raw/transcripts/2026-05-23-bug-sg01-snapshot-destructive-rclone-sync.md" >&2
|
|
177
|
+
return 1
|
|
178
|
+
fi
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Self-check: ensure THIS script contains its own --max-delete guard.
|
|
182
|
+
if ! wiki_snapshot_assert_guards "$0"; then
|
|
183
|
+
echo "[wiki-snapshot] Self-guard check failed — refusing to run." >&2
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
# ── Dry-run mode ────────────────────────────────────────────
|
|
188
|
+
DRY_RUN=false
|
|
189
|
+
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true
|
|
190
|
+
|
|
191
|
+
if [ "$DRY_RUN" = true ]; then
|
|
192
|
+
echo "[wiki-snapshot] DRY RUN"
|
|
193
|
+
echo " WIKI_DIR = $WIKI_DIR"
|
|
194
|
+
echo " SNAPSHOT_WORKTREE = $SNAPSHOT_WORKTREE"
|
|
195
|
+
echo " LOCK_FILE = $LOCK_FILE"
|
|
196
|
+
echo " LOG_FILE = $LOG_FILE"
|
|
197
|
+
echo " CLOUD_REMOTE = $CLOUD_REMOTE"
|
|
198
|
+
echo " REPAIR_SCRIPT = $REPAIR_SCRIPT"
|
|
199
|
+
echo "[wiki-snapshot] DRY RUN: --max-delete guard verified (present in $0)"
|
|
200
|
+
snapshot_direct_s3_preflight || true
|
|
201
|
+
echo "[wiki-snapshot] DRY RUN: would acquire $LOCK_FILE, rclone sync, git commit, push."
|
|
202
|
+
echo "[wiki-snapshot] DRY RUN: Complete. No changes made."
|
|
203
|
+
exit 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
mkdir -p "$(dirname "$LOG_FILE")"
|
|
207
|
+
|
|
208
|
+
# ── Lock file to prevent concurrent execution ──────────────
|
|
209
|
+
exec 200>"$LOCK_FILE"
|
|
210
|
+
if ! flock -n 200; then
|
|
211
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: Another instance of wiki-snapshot is running. Exiting."
|
|
212
|
+
exit 1
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
# Logging helper
|
|
216
|
+
log() {
|
|
217
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
raw_dedup_guard() {
|
|
221
|
+
if [ "${WIKI_SNAPSHOT_RAW_DEDUP_GUARD:-1}" = "0" ]; then
|
|
222
|
+
log "raw_dedup guard skipped by WIKI_SNAPSHOT_RAW_DEDUP_GUARD=0"
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
SKILLWIKI_BIN="${WIKI_SNAPSHOT_SKILLWIKI_BIN:-skillwiki}"
|
|
227
|
+
if ! command -v "$SKILLWIKI_BIN" >/dev/null 2>&1; then
|
|
228
|
+
log "ERROR: skillwiki CLI unavailable; refusing to commit snapshot without raw_dedup guard"
|
|
229
|
+
return 1
|
|
230
|
+
fi
|
|
231
|
+
|
|
232
|
+
GUARD_LOG="${RCLONE_LOG}.raw-dedup-guard"
|
|
233
|
+
if ! "$SKILLWIKI_BIN" lint "$SNAPSHOT_WORKTREE" --only raw_dedup --summary >"$GUARD_LOG" 2>&1; then
|
|
234
|
+
log "ERROR: raw_dedup guard failed after cloud sync; refusing to commit snapshot"
|
|
235
|
+
cat "$GUARD_LOG" >>"$LOG_FILE" 2>/dev/null || true
|
|
236
|
+
rm -f "$GUARD_LOG"
|
|
237
|
+
return 1
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
rm -f "$GUARD_LOG"
|
|
241
|
+
log "raw_dedup guard passed"
|
|
242
|
+
return 0
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
conflict_marker_guard() {
|
|
246
|
+
if [ "${WIKI_SNAPSHOT_CONFLICT_MARKER_GUARD:-1}" = "0" ]; then
|
|
247
|
+
log "conflict-marker guard skipped by WIKI_SNAPSHOT_CONFLICT_MARKER_GUARD=0"
|
|
248
|
+
return 0
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
local findings
|
|
252
|
+
findings="$(mktemp)" || { log "ERROR: could not create conflict-marker scan temp file"; return 1; }
|
|
253
|
+
if ! vault_sync_scan_conflict_markers "$SNAPSHOT_WORKTREE" "$findings"; then
|
|
254
|
+
log "ERROR: conflict marker blocks found after cloud sync; refusing to commit snapshot"
|
|
255
|
+
vault_sync_log_conflict_marker_findings "$findings" "$LOG_FILE"
|
|
256
|
+
rm -f "$findings"
|
|
257
|
+
return 1
|
|
258
|
+
fi
|
|
259
|
+
rm -f "$findings"
|
|
260
|
+
log "conflict-marker guard passed"
|
|
261
|
+
return 0
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
refresh_git_baseline() {
|
|
265
|
+
(
|
|
266
|
+
cd "$SNAPSHOT_WORKTREE" || exit 1
|
|
267
|
+
fetch_origin_main_ref 2>/dev/null || exit 2
|
|
268
|
+
|
|
269
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
270
|
+
exit 3
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
if git merge-base --is-ancestor HEAD origin/main 2>/dev/null; then
|
|
274
|
+
if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]; then
|
|
275
|
+
git merge --ff-only origin/main >/dev/null
|
|
276
|
+
exit 4
|
|
277
|
+
fi
|
|
278
|
+
exit 0
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
exit 5
|
|
282
|
+
)
|
|
283
|
+
local rc=$?
|
|
284
|
+
case "$rc" in
|
|
285
|
+
0) return 0 ;;
|
|
286
|
+
4) log "Git baseline fast-forwarded to origin/main before S3 sync"; return 0 ;;
|
|
287
|
+
2) log "WARNING: Could not fetch origin/main before S3 sync; continuing with existing refs"; return 0 ;;
|
|
288
|
+
3) log "WARNING: Snapshot worktree dirty before S3 sync; skipping baseline fast-forward"; return 0 ;;
|
|
289
|
+
5) log "WARNING: Snapshot worktree is not an ancestor of origin/main; repair step will handle it after S3 sync"; return 0 ;;
|
|
290
|
+
*) log "WARNING: Git baseline refresh failed rc=$rc; continuing"; return 0 ;;
|
|
291
|
+
esac
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
log "=== Wiki Snapshot: $DATE ==="
|
|
295
|
+
|
|
296
|
+
# Check disk space (need at least 100MB free)
|
|
297
|
+
AVAILABLE_KB=$(df -k "$SNAPSHOT_WORKTREE" | awk 'NR==2 {print $4}')
|
|
298
|
+
if [ "$AVAILABLE_KB" -lt 102400 ]; then
|
|
299
|
+
log "ERROR: Insufficient disk space. Only ${AVAILABLE_KB}KB available, need 100MB."
|
|
300
|
+
exit 1
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# Check if git directory exists
|
|
304
|
+
if [ ! -d "$SNAPSHOT_WORKTREE/.git" ]; then
|
|
305
|
+
log "ERROR: Git worktree not found or not a git repo: $SNAPSHOT_WORKTREE"
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
refresh_git_baseline
|
|
310
|
+
|
|
311
|
+
# Single-authority root projections before FUSE/S3 pull promotion.
|
|
312
|
+
if command -v skillwiki >/dev/null 2>&1; then
|
|
313
|
+
if ! skillwiki projections materialize "$WIKI_DIR" --write >>"$LOG_FILE" 2>&1; then
|
|
314
|
+
log "FAIL root projection materialization; snapshot promotion refused"
|
|
315
|
+
exit 1
|
|
316
|
+
fi
|
|
317
|
+
log "OK projections materialize before snapshot sync"
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
# Common rclone options
|
|
321
|
+
# NOTE: rclone sync already deletes by default; exclusions prevent .git deletion
|
|
322
|
+
RCLONE_OPTS=(
|
|
323
|
+
--exclude ".snapshots/**"
|
|
324
|
+
--exclude ".git/**"
|
|
325
|
+
--exclude ".obsidian/**"
|
|
326
|
+
--exclude ".skillwiki/**"
|
|
327
|
+
--exclude ".claude/**"
|
|
328
|
+
--exclude ".antigravitycli/**"
|
|
329
|
+
--exclude ".playwright-cli/**"
|
|
330
|
+
--exclude "._*"
|
|
331
|
+
--exclude ".conflict*"
|
|
332
|
+
--exclude "*.conflict-*"
|
|
333
|
+
--checksum
|
|
334
|
+
--transfers 8
|
|
335
|
+
--checkers 16
|
|
336
|
+
--fast-list
|
|
337
|
+
--timeout 5m
|
|
338
|
+
--contimeout 1m
|
|
339
|
+
--max-delete 10
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
if ! handle_direct_s3_preflight_before_sync; then
|
|
343
|
+
exit 1
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# Sync from cloud directly to git dir using rclone
|
|
347
|
+
echo "Syncing from cloud to git repo (via rclone)..."
|
|
348
|
+
|
|
349
|
+
if ! rclone sync "$CLOUD_REMOTE" "$SNAPSHOT_WORKTREE" "${RCLONE_OPTS[@]}" --stats 10s 2>&1 | tee "$RCLONE_LOG"; then
|
|
350
|
+
RCLONE_EXIT=$?
|
|
351
|
+
log "ERROR: Rclone sync failed with exit code $RCLONE_EXIT"
|
|
352
|
+
log "Last 50 lines of output:"
|
|
353
|
+
tail -50 "$RCLONE_LOG" >> "$LOG_FILE" 2>/dev/null || true
|
|
354
|
+
rm -f "$RCLONE_LOG"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
rm -f "$RCLONE_LOG"
|
|
359
|
+
|
|
360
|
+
# Verify sync actually happened
|
|
361
|
+
if [ ! -f "$SNAPSHOT_WORKTREE/index.md" ]; then
|
|
362
|
+
log "ERROR: Sync verification failed - index.md not found in git dir"
|
|
363
|
+
exit 1
|
|
364
|
+
fi
|
|
365
|
+
|
|
366
|
+
# --- Delete-intent no-resurrect ---
|
|
367
|
+
# Git is SSOT for intentional absences. After S3→worktree sync, strip any path
|
|
368
|
+
# that has an active tombstone on origin/main and optionally prune S3.
|
|
369
|
+
snapshot_apply_delete_intents() {
|
|
370
|
+
if ! command -v delete_intent_list_active_paths_from_git >/dev/null 2>&1 \
|
|
371
|
+
&& ! command -v delete_intent_list_active_paths >/dev/null 2>&1; then
|
|
372
|
+
log "WARNING: delete-intent helpers unavailable; skipping tombstone no-resurrect"
|
|
373
|
+
return 0
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
# Re-materialize ledger from git so rclone sync cannot drop git-only intents,
|
|
377
|
+
# then list from the worktree (single parse — avoid re-git-show of every blob).
|
|
378
|
+
(
|
|
379
|
+
cd "$SNAPSHOT_WORKTREE" || exit 0
|
|
380
|
+
fetch_origin_main_ref 2>/dev/null || true
|
|
381
|
+
if git rev-parse --verify --quiet origin/main >/dev/null 2>&1; then
|
|
382
|
+
git checkout origin/main -- meta/delete-intents/ 2>/dev/null || true
|
|
383
|
+
fi
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
local paths_file pruned=0
|
|
387
|
+
paths_file="$(mktemp)"
|
|
388
|
+
if command -v delete_intent_list_active_paths >/dev/null 2>&1; then
|
|
389
|
+
delete_intent_list_active_paths "$SNAPSHOT_WORKTREE" > "$paths_file" 2>/dev/null || true
|
|
390
|
+
elif command -v delete_intent_list_active_paths_from_git >/dev/null 2>&1; then
|
|
391
|
+
delete_intent_list_active_paths_from_git "$SNAPSHOT_WORKTREE" origin/main > "$paths_file" 2>/dev/null || true
|
|
392
|
+
fi
|
|
393
|
+
|
|
394
|
+
if [ ! -s "$paths_file" ]; then
|
|
395
|
+
rm -f "$paths_file"
|
|
396
|
+
log "delete-intent: no active tombstones"
|
|
397
|
+
return 0
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
local rel_path
|
|
401
|
+
while IFS= read -r rel_path; do
|
|
402
|
+
[ -z "$rel_path" ] && continue
|
|
403
|
+
if [ -e "$SNAPSHOT_WORKTREE/$rel_path" ]; then
|
|
404
|
+
rm -f "$SNAPSHOT_WORKTREE/$rel_path"
|
|
405
|
+
log "delete-intent: stripped resurrected path from worktree: $rel_path"
|
|
406
|
+
fi
|
|
407
|
+
# Bounded S3 prune for tombstoned paths still present on remote.
|
|
408
|
+
if [ "$pruned" -lt 10 ]; then
|
|
409
|
+
if rclone deletefile "${CLOUD_REMOTE%/}/$rel_path" >>"$LOG_FILE" 2>&1; then
|
|
410
|
+
log "delete-intent: pruned S3 object $rel_path"
|
|
411
|
+
pruned=$((pruned + 1))
|
|
412
|
+
fi
|
|
413
|
+
fi
|
|
414
|
+
done < "$paths_file"
|
|
415
|
+
|
|
416
|
+
rm -f "$paths_file"
|
|
417
|
+
log "delete-intent: no-resurrect pass complete (s3_pruned=$pruned)"
|
|
418
|
+
return 0
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
snapshot_apply_delete_intents
|
|
422
|
+
|
|
423
|
+
snapshot_direct_s3_preflight || true
|
|
424
|
+
|
|
425
|
+
# Change to git dir for operations
|
|
426
|
+
cd "$SNAPSHOT_WORKTREE" || { log "ERROR: Failed to cd to $SNAPSHOT_WORKTREE"; exit 1; }
|
|
427
|
+
|
|
428
|
+
# Configure git if not already set
|
|
429
|
+
git config user.email "snapshot@vault-sync.local" 2>/dev/null || true
|
|
430
|
+
git config user.name "Vault Sync Snapshot" 2>/dev/null || true
|
|
431
|
+
|
|
432
|
+
if command -v git_case_assert_clean >/dev/null 2>&1; then
|
|
433
|
+
if ! CASE_CONFLICTS=$(git_case_conflicts); then
|
|
434
|
+
log "ERROR: Case-only path collision detected; refusing to commit snapshot"
|
|
435
|
+
printf '%s\n' "$CASE_CONFLICTS" | tee -a "$LOG_FILE"
|
|
436
|
+
exit 1
|
|
437
|
+
fi
|
|
438
|
+
else
|
|
439
|
+
log "ERROR: git-case helper unavailable; refusing to commit snapshot"
|
|
440
|
+
exit 1
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
# Check if git is in broken state (rebase in progress, merge conflicts, diverged)
|
|
444
|
+
needs_repair=false
|
|
445
|
+
|
|
446
|
+
# Check for rebase/merge in progress
|
|
447
|
+
if [ -d ".git/rebase-merge" ] || [ -d ".git/rebase-apply" ] || [ -f ".git/MERGE_HEAD" ]; then
|
|
448
|
+
log "WARNING: Git rebase/merge in progress - needs repair"
|
|
449
|
+
needs_repair=true
|
|
450
|
+
fi
|
|
451
|
+
|
|
452
|
+
# Check for unmerged files
|
|
453
|
+
if git diff --name-only --diff-filter=U | grep -q . 2>/dev/null; then
|
|
454
|
+
log "WARNING: Merge conflicts detected - needs repair"
|
|
455
|
+
needs_repair=true
|
|
456
|
+
fi
|
|
457
|
+
|
|
458
|
+
# Check if we're in detached HEAD state
|
|
459
|
+
if ! git symbolic-ref -q HEAD >/dev/null 2>&1; then
|
|
460
|
+
log "WARNING: Detached HEAD state - needs repair"
|
|
461
|
+
needs_repair=true
|
|
462
|
+
fi
|
|
463
|
+
|
|
464
|
+
# Check if we can fast-forward to origin
|
|
465
|
+
DEFAULT_BRANCH=$(git remote show origin 2>/dev/null | grep 'HEAD branch' | awk '{print $3}')
|
|
466
|
+
DEFAULT_BRANCH=${DEFAULT_BRANCH:-main}
|
|
467
|
+
|
|
468
|
+
if ! git merge-base --is-ancestor HEAD "origin/$DEFAULT_BRANCH" 2>/dev/null; then
|
|
469
|
+
# Local HEAD is not an ancestor of origin - diverged
|
|
470
|
+
log "WARNING: Diverged from origin/$DEFAULT_BRANCH - needs repair"
|
|
471
|
+
needs_repair=true
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
# Repair if needed
|
|
475
|
+
if [ "$needs_repair" = true ]; then
|
|
476
|
+
log "Running git repair..."
|
|
477
|
+
if ! bash "$REPAIR_SCRIPT" 2>&1 | tee -a "$LOG_FILE"; then
|
|
478
|
+
log "ERROR: Git repair failed"
|
|
479
|
+
exit 1
|
|
480
|
+
fi
|
|
481
|
+
cd "$SNAPSHOT_WORKTREE" || { log "ERROR: Failed to cd to $SNAPSHOT_WORKTREE after repair"; exit 1; }
|
|
482
|
+
|
|
483
|
+
# Re-run rclone sync after repair to ensure we have latest
|
|
484
|
+
if ! rclone sync "$CLOUD_REMOTE" "$SNAPSHOT_WORKTREE" "${RCLONE_OPTS[@]}" --stats 10s 2>&1 | tee "$RCLONE_LOG"; then
|
|
485
|
+
log "ERROR: Post-repair rclone sync failed"
|
|
486
|
+
tail -50 "$RCLONE_LOG" >> "$LOG_FILE" 2>/dev/null || true
|
|
487
|
+
rm -f "$RCLONE_LOG"
|
|
488
|
+
exit 1
|
|
489
|
+
fi
|
|
490
|
+
rm -f "$RCLONE_LOG"
|
|
491
|
+
snapshot_apply_delete_intents
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
if ! raw_dedup_guard; then
|
|
495
|
+
exit 1
|
|
496
|
+
fi
|
|
497
|
+
|
|
498
|
+
if ! conflict_marker_guard; then
|
|
499
|
+
exit 1
|
|
500
|
+
fi
|
|
501
|
+
|
|
502
|
+
# Check for changes
|
|
503
|
+
if [ -z "$(git status --porcelain)" ]; then
|
|
504
|
+
log "No changes to commit"
|
|
505
|
+
exit 0
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
# Re-check immediately before staging because rclone/repair may have changed the
|
|
509
|
+
# tree after the first guard.
|
|
510
|
+
if command -v git_case_assert_clean >/dev/null 2>&1; then
|
|
511
|
+
if ! CASE_CONFLICTS=$(git_case_conflicts); then
|
|
512
|
+
log "ERROR: Case-only path collision detected before git add; refusing to commit snapshot"
|
|
513
|
+
printf '%s\n' "$CASE_CONFLICTS" | tee -a "$LOG_FILE"
|
|
514
|
+
exit 1
|
|
515
|
+
fi
|
|
516
|
+
fi
|
|
517
|
+
|
|
518
|
+
# Commit
|
|
519
|
+
echo "Committing changes..."
|
|
520
|
+
if ! git add -A; then
|
|
521
|
+
log "ERROR: git add failed"
|
|
522
|
+
exit 1
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
if ! git commit -m "Snapshot $DATE"; then
|
|
526
|
+
log "ERROR: git commit failed"
|
|
527
|
+
exit 1
|
|
528
|
+
fi
|
|
529
|
+
|
|
530
|
+
# Pull with rebase to get any remote changes (with retry)
|
|
531
|
+
echo "Pulling from origin..."
|
|
532
|
+
PULL_RETRIES=3
|
|
533
|
+
PULL_SUCCESS=false
|
|
534
|
+
|
|
535
|
+
for i in $(seq 1 $PULL_RETRIES); do
|
|
536
|
+
if git pull --rebase origin "$DEFAULT_BRANCH" 2>&1 | tee -a "$LOG_FILE"; then
|
|
537
|
+
PULL_SUCCESS=true
|
|
538
|
+
break
|
|
539
|
+
fi
|
|
540
|
+
log "WARNING: Pull attempt $i failed, retrying..."
|
|
541
|
+
sleep 5
|
|
542
|
+
done
|
|
543
|
+
|
|
544
|
+
if [ "$PULL_SUCCESS" = false ]; then
|
|
545
|
+
log "ERROR: Pull failed after $PULL_RETRIES attempts - attempting repair"
|
|
546
|
+
if bash "$REPAIR_SCRIPT" 2>&1 | tee -a "$LOG_FILE"; then
|
|
547
|
+
# After repair, commit timestamp will be slightly different
|
|
548
|
+
DATE=$(date +%Y%m%d_%H%M%S)
|
|
549
|
+
cd "$SNAPSHOT_WORKTREE" || { log "ERROR: Failed to cd after repair"; exit 1; }
|
|
550
|
+
|
|
551
|
+
# Re-sync after repair
|
|
552
|
+
rclone sync "$CLOUD_REMOTE" "$SNAPSHOT_WORKTREE" "${RCLONE_OPTS[@]}" 2>&1 | tee -a "$LOG_FILE" || true
|
|
553
|
+
if ! raw_dedup_guard || ! conflict_marker_guard; then
|
|
554
|
+
exit 1
|
|
555
|
+
fi
|
|
556
|
+
git add -A || true
|
|
557
|
+
git commit -m "Snapshot $DATE (post-repair)" || true
|
|
558
|
+
else
|
|
559
|
+
log "ERROR: Repair failed after pull failure"
|
|
560
|
+
exit 1
|
|
561
|
+
fi
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
# Push (with retry)
|
|
565
|
+
echo "Pushing to origin..."
|
|
566
|
+
PUSH_RETRIES=3
|
|
567
|
+
PUSH_SUCCESS=false
|
|
568
|
+
|
|
569
|
+
for i in $(seq 1 $PUSH_RETRIES); do
|
|
570
|
+
if git push origin "$DEFAULT_BRANCH" 2>&1 | tee -a "$LOG_FILE"; then
|
|
571
|
+
PUSH_SUCCESS=true
|
|
572
|
+
break
|
|
573
|
+
fi
|
|
574
|
+
log "WARNING: Push attempt $i failed, retrying..."
|
|
575
|
+
sleep 5
|
|
576
|
+
done
|
|
577
|
+
|
|
578
|
+
if [ "$PUSH_SUCCESS" = true ]; then
|
|
579
|
+
log "Push successful"
|
|
580
|
+
log "Status: complete"
|
|
581
|
+
exit 0
|
|
582
|
+
else
|
|
583
|
+
log "ERROR: Push failed after $PUSH_RETRIES attempts"
|
|
584
|
+
# Run repair for next attempt
|
|
585
|
+
bash "$REPAIR_SCRIPT" 2>&1 | tee -a "$LOG_FILE" || true
|
|
586
|
+
exit 1
|
|
587
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skillwiki": "dist/cli.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tsup &&
|
|
16
|
+
"build": "tsup && node ./scripts/copy-build-assets.mjs",
|
|
17
17
|
"test": "vitest run",
|
|
18
18
|
"test:watch": "vitest",
|
|
19
19
|
"typecheck": "tsc --noEmit",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|
package/skills/README.md
CHANGED
|
@@ -40,3 +40,16 @@ canonical skill, agent, or hook assets. Run
|
|
|
40
40
|
|
|
41
41
|
The sibling `vault-sync` plugin ships six additional operational skills under
|
|
42
42
|
`packages/vault-sync/skills/` and is packaged separately from this skill set.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## Managed Vault Mutation Contract
|
|
46
|
+
|
|
47
|
+
Before a managed vault mutation, invoke the managed SkillWiki command while the draft remains outside the authoritative target path. The command resolves fleet authority, refuses existing unmerged/review-required state, converges an authorized Git writer, freezes the base OID, and only then applies the write. Do not run `git pull --rebase --autostash` after placing the authoritative change in the live worktree. Do not edit root `index.md` or root `log.md` directly; projection and log commands own those compatibility files.
|
|
48
|
+
|
|
49
|
+
- typed pages: `skillwiki page publish <draft> <vault> --target <path>` then the same command with `--write`
|
|
50
|
+
- archive: `skillwiki archive <path> <vault>`
|
|
51
|
+
- ad-hoc structural log: `skillwiki log-append <vault> --content '<entry>'` (Release A dual-write) or event materialization (Release B)
|
|
52
|
+
- project/root index: `skillwiki project-index <slug> <vault> --apply` and `skillwiki index rebuild <vault> --write` only through managed commands
|
|
53
|
+
- log projection: `skillwiki log materialize <vault> [--write]`
|
|
54
|
+
- paired projections: `skillwiki projections materialize <vault> [--write]`
|
|
55
|
+
|
package/skills/package.json
CHANGED
|
@@ -67,3 +67,6 @@ Rules:
|
|
|
67
67
|
- Writing spec/plan files outside the work folder.
|
|
68
68
|
- Marking `status: completed` without a `completed:` date.
|
|
69
69
|
- Accepting tasks.md status labels without independent disk verification.
|
|
70
|
+
## Managed writes
|
|
71
|
+
|
|
72
|
+
Use managed SkillWiki commands (`skillwiki page publish`, `skillwiki archive`) rather than editing root index.md/log.md directly.
|
|
@@ -67,3 +67,6 @@ Rules:
|
|
|
67
67
|
- Writing spec/plan files outside the work folder.
|
|
68
68
|
- Marking `status: completed` without a `completed:` date.
|
|
69
69
|
- Accepting tasks.md status labels without independent disk verification.
|
|
70
|
+
## Managed writes
|
|
71
|
+
|
|
72
|
+
Use managed SkillWiki commands (`skillwiki page publish`, `skillwiki archive`) rather than editing root index.md/log.md directly.
|
|
@@ -75,6 +75,19 @@ path, run publisher dry-run, then run the same command with `--write`.
|
|
|
75
75
|
- Non-typed project work items and immutable raw sources keep their existing
|
|
76
76
|
workflows.
|
|
77
77
|
|
|
78
|
+
|
|
79
|
+
## Managed Vault Mutation Contract
|
|
80
|
+
|
|
81
|
+
Before a managed vault mutation, invoke the managed SkillWiki command while the draft remains outside the authoritative target path. The command resolves fleet authority, refuses existing unmerged/review-required state, converges an authorized Git writer, freezes the base OID, and only then applies the write. Do not run `git pull --rebase --autostash` after placing the authoritative change in the live worktree. Do not edit root `index.md` or root `log.md` directly; projection and log commands own those compatibility files.
|
|
82
|
+
|
|
83
|
+
- typed pages: `skillwiki page publish <draft> <vault> --target <path>` then the same command with `--write`
|
|
84
|
+
- archive: `skillwiki archive <path> <vault>`
|
|
85
|
+
- ad-hoc structural log: `skillwiki log-append <vault> --content '<entry>'` (Release A dual-write) or event materialization (Release B)
|
|
86
|
+
- project/root index: `skillwiki project-index <slug> <vault> --apply` and `skillwiki index rebuild <vault> --write` only through managed commands
|
|
87
|
+
- log projection: `skillwiki log materialize <vault> [--write]`
|
|
88
|
+
- paired projections: `skillwiki projections materialize <vault> [--write]`
|
|
89
|
+
|
|
90
|
+
|
|
78
91
|
## CLI probe and failsafe (vault-mutating skills)
|
|
79
92
|
|
|
80
93
|
Vault fleets that combine S3 + GitHub need an explicit delete-intent path. Prefer the skillwiki CLI; when it is missing, agents that still have **git/gh access to the private vault remote** use **FAILSAFE-GIT**.
|