skillwiki 0.9.63 → 0.10.1
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-C5OLZRRM.js +357 -0
- package/dist/chunk-IZABIE44.js +647 -0
- package/dist/chunk-R6BKJWVC.js +890 -0
- package/dist/chunk-SCGC7YNM.js +213 -0
- package/dist/{chunk-TUFQZ5K4.js → chunk-XSTXPA34.js} +834 -1981
- package/dist/cli.js +1780 -697
- package/dist/index-projection-ERFX76U5.js +10 -0
- package/dist/managed-write-preflight-PW4OOOMV.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 +24 -0
- package/skills/skills/wiki-crystallize/SKILL.md +3 -0
- package/skills/using-skillwiki/SKILL.md +24 -0
- package/skills/wiki-crystallize/SKILL.md +3 -0
package/dist/skillwiki-mcp.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runSkillwikiMcpStdio
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XSTXPA34.js";
|
|
5
5
|
import "./chunk-7I2TPIV5.js";
|
|
6
|
+
import "./chunk-IZABIE44.js";
|
|
7
|
+
import "./chunk-R6BKJWVC.js";
|
|
8
|
+
import "./chunk-C5OLZRRM.js";
|
|
6
9
|
|
|
7
10
|
// src/mcp-entry.ts
|
|
8
11
|
runSkillwikiMcpStdio().catch((error) => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# conflict-markers.sh — Detect complete Git conflict-marker blocks in Markdown.
|
|
3
|
+
#
|
|
4
|
+
# Scans *.md under a vault root, ignoring fenced code blocks. Writes findings
|
|
5
|
+
# as path:line: conflict marker block starts here. No ripgrep dependency.
|
|
6
|
+
|
|
7
|
+
vault_sync_scan_conflict_markers() {
|
|
8
|
+
local root="$1"
|
|
9
|
+
local out_file="$2"
|
|
10
|
+
local rel file
|
|
11
|
+
|
|
12
|
+
: >"$out_file" || return 1
|
|
13
|
+
(
|
|
14
|
+
cd "$root" || exit 2
|
|
15
|
+
find . \
|
|
16
|
+
-path './.git' -prune -o \
|
|
17
|
+
-path './.obsidian' -prune -o \
|
|
18
|
+
-path './.skillwiki' -prune -o \
|
|
19
|
+
-path './.claude' -prune -o \
|
|
20
|
+
-path './.antigravitycli' -prune -o \
|
|
21
|
+
-path './.playwright-cli' -prune -o \
|
|
22
|
+
-type f -name '*.md' -print
|
|
23
|
+
) | while IFS= read -r file; do
|
|
24
|
+
rel="${file#./}"
|
|
25
|
+
awk -v path="$rel" '
|
|
26
|
+
BEGIN {
|
|
27
|
+
in_fence = 0
|
|
28
|
+
open_line = 0
|
|
29
|
+
sep_line = 0
|
|
30
|
+
}
|
|
31
|
+
/^```/ || /^~~~/ {
|
|
32
|
+
in_fence = !in_fence
|
|
33
|
+
next
|
|
34
|
+
}
|
|
35
|
+
in_fence {
|
|
36
|
+
next
|
|
37
|
+
}
|
|
38
|
+
/^<<<<<<< / {
|
|
39
|
+
open_line = NR
|
|
40
|
+
sep_line = 0
|
|
41
|
+
next
|
|
42
|
+
}
|
|
43
|
+
/^=======$/ {
|
|
44
|
+
if (open_line > 0) {
|
|
45
|
+
sep_line = NR
|
|
46
|
+
}
|
|
47
|
+
next
|
|
48
|
+
}
|
|
49
|
+
/^>>>>>>> / {
|
|
50
|
+
if (open_line > 0 && sep_line > 0) {
|
|
51
|
+
printf "%s:%d: conflict marker block starts here\n", path, open_line
|
|
52
|
+
}
|
|
53
|
+
open_line = 0
|
|
54
|
+
sep_line = 0
|
|
55
|
+
next
|
|
56
|
+
}
|
|
57
|
+
' "$root/$rel" >>"$out_file"
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
[ ! -s "$out_file" ]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
vault_sync_log_conflict_marker_findings() {
|
|
64
|
+
local findings_file="$1"
|
|
65
|
+
local log_file="$2"
|
|
66
|
+
if [ -s "$findings_file" ]; then
|
|
67
|
+
cat "$findings_file" >>"$log_file"
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# delete-intent.sh — helpers for vault-delete-intent/v1 tombstones under
|
|
3
|
+
# meta/delete-intents/. Used by wiki-push (S3 prune) and wiki-snapshot
|
|
4
|
+
# (no-resurrect). Prefer reading from a vault worktree directory.
|
|
5
|
+
|
|
6
|
+
# Print active delete-intent paths (one per line) from VAULT/meta/delete-intents.
|
|
7
|
+
# Requires python3 for JSON. Silent empty on missing dir.
|
|
8
|
+
delete_intent_list_active_paths() {
|
|
9
|
+
local vault="${1:-}"
|
|
10
|
+
local dir
|
|
11
|
+
[ -n "$vault" ] || return 0
|
|
12
|
+
dir="$vault/meta/delete-intents"
|
|
13
|
+
[ -d "$dir" ] || return 0
|
|
14
|
+
|
|
15
|
+
python3 - "$dir" <<'PY'
|
|
16
|
+
import json, os, sys
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
|
|
19
|
+
d = sys.argv[1]
|
|
20
|
+
now = datetime.now(timezone.utc)
|
|
21
|
+
paths = []
|
|
22
|
+
for name in sorted(os.listdir(d)):
|
|
23
|
+
if not name.endswith(".json"):
|
|
24
|
+
continue
|
|
25
|
+
path = os.path.join(d, name)
|
|
26
|
+
try:
|
|
27
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
28
|
+
data = json.load(f)
|
|
29
|
+
except Exception:
|
|
30
|
+
continue
|
|
31
|
+
if data.get("schema") != "vault-delete-intent/v1":
|
|
32
|
+
continue
|
|
33
|
+
p = data.get("path")
|
|
34
|
+
if not p or not data.get("action"):
|
|
35
|
+
continue
|
|
36
|
+
exp = data.get("expires")
|
|
37
|
+
if exp not in (None, ""):
|
|
38
|
+
try:
|
|
39
|
+
# support Z suffix
|
|
40
|
+
exp_s = str(exp).replace("Z", "+00:00")
|
|
41
|
+
exp_dt = datetime.fromisoformat(exp_s)
|
|
42
|
+
if exp_dt.tzinfo is None:
|
|
43
|
+
exp_dt = exp_dt.replace(tzinfo=timezone.utc)
|
|
44
|
+
if exp_dt <= now:
|
|
45
|
+
continue
|
|
46
|
+
except Exception:
|
|
47
|
+
continue
|
|
48
|
+
paths.append(p)
|
|
49
|
+
for p in sorted(set(paths)):
|
|
50
|
+
print(p)
|
|
51
|
+
PY
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# List active paths by reading JSON blobs from git ref (default origin/main).
|
|
55
|
+
# WORKTREE must be a git checkout; REF defaults to origin/main.
|
|
56
|
+
delete_intent_list_active_paths_from_git() {
|
|
57
|
+
local worktree="${1:-}"
|
|
58
|
+
local ref="${2:-origin/main}"
|
|
59
|
+
[ -n "$worktree" ] || return 0
|
|
60
|
+
[ -d "$worktree/.git" ] || [ -f "$worktree/.git" ] || return 0
|
|
61
|
+
|
|
62
|
+
local tmp
|
|
63
|
+
tmp="$(mktemp -d)" || return 0
|
|
64
|
+
(
|
|
65
|
+
cd "$worktree" || exit 0
|
|
66
|
+
git ls-tree -r --name-only "$ref" -- meta/delete-intents/ 2>/dev/null | while IFS= read -r rel; do
|
|
67
|
+
[ -z "$rel" ] && continue
|
|
68
|
+
mkdir -p "$tmp/$(dirname "$rel")"
|
|
69
|
+
git show "$ref:$rel" > "$tmp/$rel" 2>/dev/null || true
|
|
70
|
+
done
|
|
71
|
+
)
|
|
72
|
+
delete_intent_list_active_paths "$tmp"
|
|
73
|
+
rm -rf "$tmp"
|
|
74
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# fleet.sh — Fleet manifest parsing for vault-sync.
|
|
3
|
+
#
|
|
4
|
+
# Reads fleet.yaml from the vault and provides role enforcement.
|
|
5
|
+
# Uses yq if available, awk fallback for the limited schema.
|
|
6
|
+
|
|
7
|
+
# Populate VS_FLEET_* env from YAML.
|
|
8
|
+
# Uses yq if available, awk fallback otherwise.
|
|
9
|
+
fleet_load() {
|
|
10
|
+
_fleet_file=""
|
|
11
|
+
if command -v skillwiki >/dev/null 2>&1; then
|
|
12
|
+
_vault_path=$(skillwiki path 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['path'])" 2>/dev/null) || true
|
|
13
|
+
if [ -n "${_vault_path:-}" ]; then
|
|
14
|
+
_fleet_file="$_vault_path/projects/llm-wiki/architecture/fleet.yaml"
|
|
15
|
+
fi
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Fallback: guess from WIKI_DIR
|
|
19
|
+
if [ -z "${_fleet_file:-}" ] || [ ! -f "$_fleet_file" ]; then
|
|
20
|
+
_wiki_dir="${WIKI_DIR:-$HOME/wiki}"
|
|
21
|
+
_fleet_file="$_wiki_dir/projects/llm-wiki/architecture/fleet.yaml"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [ ! -f "$_fleet_file" ]; then
|
|
25
|
+
VS_FLEET_LOADED=false
|
|
26
|
+
export VS_FLEET_LOADED
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
VS_FLEET_FILE="$_fleet_file"
|
|
31
|
+
VS_FLEET_LOADED=true
|
|
32
|
+
export VS_FLEET_FILE VS_FLEET_LOADED
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Echo: hostname of current snapshotter, or "" if none.
|
|
36
|
+
fleet_get_snapshotter() {
|
|
37
|
+
if [ "${VS_FLEET_LOADED:-false}" != "true" ]; then
|
|
38
|
+
fleet_load
|
|
39
|
+
fi
|
|
40
|
+
if [ "${VS_FLEET_LOADED:-false}" != "true" ]; then
|
|
41
|
+
echo ""
|
|
42
|
+
return 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if command -v yq >/dev/null 2>&1; then
|
|
46
|
+
yq '.hosts | to_entries[] | select(.value.role == "snapshotter") | .key' "$VS_FLEET_FILE" 2>/dev/null
|
|
47
|
+
else
|
|
48
|
+
# awk fallback: find the host with role: snapshotter
|
|
49
|
+
awk '/^ [a-z0-9_-]+:/{host=$1; gsub(/:/,"",host)} /role: snapshotter/{print host; exit}' "$VS_FLEET_FILE" 2>/dev/null
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Exit 0 if host is marked protected: true, 1 otherwise.
|
|
54
|
+
fleet_is_protected() {
|
|
55
|
+
_host="$1"
|
|
56
|
+
if [ "${VS_FLEET_LOADED:-false}" != "true" ]; then
|
|
57
|
+
fleet_load
|
|
58
|
+
fi
|
|
59
|
+
if [ "${VS_FLEET_LOADED:-false}" != "true" ]; then
|
|
60
|
+
return 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
if command -v yq >/dev/null 2>&1; then
|
|
64
|
+
_prot=$(yq ".hosts.$_host.protected // false" "$VS_FLEET_FILE" 2>/dev/null)
|
|
65
|
+
[ "$_prot" = "true" ]
|
|
66
|
+
else
|
|
67
|
+
# awk fallback: find protected: true under the host key
|
|
68
|
+
awk -v h="$_host" '
|
|
69
|
+
$0 ~ "^ " h ":" { in_host=1; next }
|
|
70
|
+
/^ [a-z]/ { in_host=0 }
|
|
71
|
+
in_host && /protected: true/ { found=1; exit }
|
|
72
|
+
END { exit (found ? 0 : 1) }
|
|
73
|
+
' "$VS_FLEET_FILE" 2>/dev/null
|
|
74
|
+
fi
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Exit 0 if install is safe, non-zero with reason if not.
|
|
78
|
+
fleet_validate_install() {
|
|
79
|
+
_host="$1"
|
|
80
|
+
_role="$2"
|
|
81
|
+
_override="${3:-false}"
|
|
82
|
+
|
|
83
|
+
if [ "${VS_FLEET_LOADED:-false}" != "true" ]; then
|
|
84
|
+
fleet_load
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# If role is not snapshotter, install is always safe
|
|
88
|
+
if [ "$_role" != "snapshotter" ]; then
|
|
89
|
+
return 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# Check if a snapshotter already exists
|
|
93
|
+
_current=$(fleet_get_snapshotter)
|
|
94
|
+
if [ -n "$_current" ] && [ "$_current" != "$_host" ]; then
|
|
95
|
+
if [ "$_override" != "true" ]; then
|
|
96
|
+
echo "FATAL: host '$_current' is already the snapshotter. Use --override-snapshotter to force." >&2
|
|
97
|
+
return 1
|
|
98
|
+
fi
|
|
99
|
+
echo "WARNING: overriding snapshotter from '$_current' to '$_host'" >&2
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
return 0
|
|
103
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# git-case.sh — Detect case-only path collisions before unattended git writes.
|
|
3
|
+
#
|
|
4
|
+
# Case-insensitive filesystems cannot represent two paths that differ only by
|
|
5
|
+
# letter case. If Linux commits such a tree, macOS checkouts can become
|
|
6
|
+
# permanently dirty and unable to rebase. These helpers return nonzero and print
|
|
7
|
+
# conflicting pairs when a repo contains such paths.
|
|
8
|
+
|
|
9
|
+
git_case_index_conflicts() {
|
|
10
|
+
git ls-files 2>/dev/null | awk '
|
|
11
|
+
{
|
|
12
|
+
key = tolower($0)
|
|
13
|
+
if (seen[key] && seen[key] != $0) {
|
|
14
|
+
print seen[key] " <-> " $0
|
|
15
|
+
bad = 1
|
|
16
|
+
} else if (!seen[key]) {
|
|
17
|
+
seen[key] = $0
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
END { exit bad ? 1 : 0 }
|
|
21
|
+
'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
git_case_worktree_conflicts() {
|
|
25
|
+
find . -type f \
|
|
26
|
+
! -path './.git/*' \
|
|
27
|
+
! -path './.snapshots/*' \
|
|
28
|
+
-print 2>/dev/null |
|
|
29
|
+
sed 's#^\./##' |
|
|
30
|
+
awk '
|
|
31
|
+
{
|
|
32
|
+
key = tolower($0)
|
|
33
|
+
if (seen[key] && seen[key] != $0) {
|
|
34
|
+
print seen[key] " <-> " $0
|
|
35
|
+
bad = 1
|
|
36
|
+
} else if (!seen[key]) {
|
|
37
|
+
seen[key] = $0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
END { exit bad ? 1 : 0 }
|
|
41
|
+
'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
git_case_conflicts() {
|
|
45
|
+
_git_case_tmp="${TMPDIR:-/tmp}/vault-sync-case-conflicts.$$"
|
|
46
|
+
: >"$_git_case_tmp"
|
|
47
|
+
|
|
48
|
+
git_case_index_conflicts >>"$_git_case_tmp" || true
|
|
49
|
+
git_case_worktree_conflicts >>"$_git_case_tmp" || true
|
|
50
|
+
|
|
51
|
+
if [ -s "$_git_case_tmp" ]; then
|
|
52
|
+
sort -u "$_git_case_tmp"
|
|
53
|
+
rm -f "$_git_case_tmp"
|
|
54
|
+
return 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
rm -f "$_git_case_tmp"
|
|
58
|
+
return 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
git_case_assert_clean() {
|
|
62
|
+
_git_case_label="${1:-repository}"
|
|
63
|
+
_git_case_conflicts="$(git_case_conflicts)"
|
|
64
|
+
_git_case_rc=$?
|
|
65
|
+
if [ "$_git_case_rc" -ne 0 ]; then
|
|
66
|
+
printf 'FATAL: case-only path collision in %s\n' "$_git_case_label" >&2
|
|
67
|
+
printf '%s\n' "$_git_case_conflicts" >&2
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
return 0
|
|
71
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# git-materialization.sh — prove a local commit's content is already present on a target ref.
|
|
3
|
+
#
|
|
4
|
+
# Bash 3.2 compatible. Source from vault-sync scripts.
|
|
5
|
+
#
|
|
6
|
+
# vault_sync_commit_materialized <commit> <target-ref> [repo]
|
|
7
|
+
# Exit 0 only when every changed path is proven present in the target tree:
|
|
8
|
+
# - ordinary add/modify: target blob == commit blob
|
|
9
|
+
# - delete: target path absent
|
|
10
|
+
# - log.md / */log.md: every added ## section body occurs byte-for-byte in target log
|
|
11
|
+
# Fail closed (exit 1) on rename, binary mismatch, missing target, unsupported status,
|
|
12
|
+
# partial log section match, or any unprovable change.
|
|
13
|
+
|
|
14
|
+
vault_sync_is_log_path() {
|
|
15
|
+
case "$1" in
|
|
16
|
+
log.md|*/log.md) return 0 ;;
|
|
17
|
+
*) return 1 ;;
|
|
18
|
+
esac
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Return 0 if file contains at least one NUL byte (binary). Do NOT use
|
|
22
|
+
# `grep -q $'\0'` — on macOS BSD grep that matches every non-empty file.
|
|
23
|
+
vault_sync_file_has_nul() {
|
|
24
|
+
local f="$1"
|
|
25
|
+
[ -f "$f" ] || return 1
|
|
26
|
+
# Compare original to a NUL-stripped copy; differ ⇒ had NUL.
|
|
27
|
+
if ! tr -d '\0' <"$f" | cmp -s - "$f"; then
|
|
28
|
+
return 0
|
|
29
|
+
fi
|
|
30
|
+
return 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Extract Markdown sections starting at ^## from stdin; print each section
|
|
34
|
+
# separated by a form-feed so callers can compare complete bodies.
|
|
35
|
+
vault_sync_extract_h2_sections() {
|
|
36
|
+
awk '
|
|
37
|
+
BEGIN { sec = ""; insec = 0 }
|
|
38
|
+
/^## / {
|
|
39
|
+
if (insec && sec != "") {
|
|
40
|
+
printf "%s\f", sec
|
|
41
|
+
}
|
|
42
|
+
sec = $0 "\n"
|
|
43
|
+
insec = 1
|
|
44
|
+
next
|
|
45
|
+
}
|
|
46
|
+
insec {
|
|
47
|
+
sec = sec $0 "\n"
|
|
48
|
+
}
|
|
49
|
+
END {
|
|
50
|
+
if (insec && sec != "") {
|
|
51
|
+
printf "%s\f", sec
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Return 0 if every added ## section in new_file appears byte-for-byte in target_file.
|
|
58
|
+
vault_sync_log_sections_materialized() {
|
|
59
|
+
local new_file="$1"
|
|
60
|
+
local target_file="$2"
|
|
61
|
+
local old_file="${3:-}"
|
|
62
|
+
local new_secs target_text sec old_secs sec_file rc
|
|
63
|
+
|
|
64
|
+
if [ ! -f "$new_file" ] || [ ! -f "$target_file" ]; then
|
|
65
|
+
return 1
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
new_secs="$(vault_sync_extract_h2_sections < "$new_file")"
|
|
69
|
+
target_text="$(cat "$target_file")"
|
|
70
|
+
old_secs=""
|
|
71
|
+
if [ -n "$old_file" ] && [ -f "$old_file" ]; then
|
|
72
|
+
old_secs="$(vault_sync_extract_h2_sections < "$old_file")"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Write sections to a temp file and iterate without a pipeline subshell
|
|
76
|
+
# so a missing section can fail the function itself (Bash 3.2-safe).
|
|
77
|
+
sec_file="$(mktemp)" || return 1
|
|
78
|
+
printf '%s' "$new_secs" > "$sec_file"
|
|
79
|
+
rc=0
|
|
80
|
+
while IFS= read -r -d $'\f' sec || [ -n "${sec:-}" ]; do
|
|
81
|
+
[ -n "${sec:-}" ] || continue
|
|
82
|
+
if [ -n "$old_secs" ]; then
|
|
83
|
+
case "$old_secs" in
|
|
84
|
+
*"$sec"*) continue ;;
|
|
85
|
+
esac
|
|
86
|
+
fi
|
|
87
|
+
case "$target_text" in
|
|
88
|
+
*"$sec"*) ;;
|
|
89
|
+
*) rc=1; break ;;
|
|
90
|
+
esac
|
|
91
|
+
done < "$sec_file"
|
|
92
|
+
rm -f "$sec_file"
|
|
93
|
+
return $rc
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Prove commit is fully materialized on target-ref.
|
|
97
|
+
vault_sync_commit_materialized() {
|
|
98
|
+
local commit="$1"
|
|
99
|
+
local target_ref="$2"
|
|
100
|
+
local repo="${3:-.}"
|
|
101
|
+
local parent status path tmpdir old_blob new_blob target_blob
|
|
102
|
+
local commit_sha target_sha
|
|
103
|
+
local changed=0
|
|
104
|
+
|
|
105
|
+
if [ -z "$commit" ] || [ -z "$target_ref" ]; then
|
|
106
|
+
return 1
|
|
107
|
+
fi
|
|
108
|
+
if ! git -C "$repo" rev-parse -q --verify "$commit^{commit}" >/dev/null 2>&1; then
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
if ! git -C "$repo" rev-parse -q --verify "$target_ref^{commit}" >/dev/null 2>&1; then
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
parent="$(git -C "$repo" rev-parse "${commit}^" 2>/dev/null || true)"
|
|
116
|
+
if [ -z "$parent" ]; then
|
|
117
|
+
return 1
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
while IFS= read -r line; do
|
|
121
|
+
[ -n "$line" ] || continue
|
|
122
|
+
changed=1
|
|
123
|
+
status="${line%% *}"
|
|
124
|
+
path="${line#* }"
|
|
125
|
+
case "$status" in
|
|
126
|
+
R*|C*)
|
|
127
|
+
return 1
|
|
128
|
+
;;
|
|
129
|
+
A|M|D|T)
|
|
130
|
+
;;
|
|
131
|
+
*)
|
|
132
|
+
return 1
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
|
|
136
|
+
case "$path" in
|
|
137
|
+
*$'\t'*) return 1 ;;
|
|
138
|
+
esac
|
|
139
|
+
|
|
140
|
+
if vault_sync_is_log_path "$path"; then
|
|
141
|
+
if [ "$status" = "D" ]; then
|
|
142
|
+
if git -C "$repo" cat-file -e "$target_ref:$path" 2>/dev/null; then
|
|
143
|
+
return 1
|
|
144
|
+
fi
|
|
145
|
+
continue
|
|
146
|
+
fi
|
|
147
|
+
tmpdir="$(mktemp -d)" || return 1
|
|
148
|
+
old_blob="$tmpdir/old"
|
|
149
|
+
new_blob="$tmpdir/new"
|
|
150
|
+
target_blob="$tmpdir/target"
|
|
151
|
+
if [ "$status" = "A" ]; then
|
|
152
|
+
: > "$old_blob"
|
|
153
|
+
else
|
|
154
|
+
if ! git -C "$repo" show "$parent:$path" >"$old_blob" 2>/dev/null; then
|
|
155
|
+
rm -rf "$tmpdir"
|
|
156
|
+
return 1
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
if ! git -C "$repo" show "$commit:$path" >"$new_blob" 2>/dev/null; then
|
|
160
|
+
rm -rf "$tmpdir"
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
163
|
+
if ! git -C "$repo" show "$target_ref:$path" >"$target_blob" 2>/dev/null; then
|
|
164
|
+
rm -rf "$tmpdir"
|
|
165
|
+
return 1
|
|
166
|
+
fi
|
|
167
|
+
# Real binary/NUL check (not BSD-grep $'\0', which false-positives on text).
|
|
168
|
+
if vault_sync_file_has_nul "$new_blob" || vault_sync_file_has_nul "$target_blob"; then
|
|
169
|
+
rm -rf "$tmpdir"
|
|
170
|
+
return 1
|
|
171
|
+
fi
|
|
172
|
+
if ! vault_sync_log_sections_materialized "$new_blob" "$target_blob" "$old_blob"; then
|
|
173
|
+
rm -rf "$tmpdir"
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
rm -rf "$tmpdir"
|
|
177
|
+
continue
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
case "$status" in
|
|
181
|
+
D)
|
|
182
|
+
if git -C "$repo" cat-file -e "$target_ref:$path" 2>/dev/null; then
|
|
183
|
+
return 1
|
|
184
|
+
fi
|
|
185
|
+
;;
|
|
186
|
+
A|M|T)
|
|
187
|
+
if ! git -C "$repo" cat-file -e "$commit:$path" 2>/dev/null; then
|
|
188
|
+
return 1
|
|
189
|
+
fi
|
|
190
|
+
if ! git -C "$repo" cat-file -e "$target_ref:$path" 2>/dev/null; then
|
|
191
|
+
return 1
|
|
192
|
+
fi
|
|
193
|
+
commit_sha="$(git -C "$repo" rev-parse "$commit:$path" 2>/dev/null)" || return 1
|
|
194
|
+
target_sha="$(git -C "$repo" rev-parse "$target_ref:$path" 2>/dev/null)" || return 1
|
|
195
|
+
if [ "$commit_sha" != "$target_sha" ]; then
|
|
196
|
+
return 1
|
|
197
|
+
fi
|
|
198
|
+
;;
|
|
199
|
+
esac
|
|
200
|
+
done < <(git -C "$repo" diff-tree --no-commit-id --name-status -r "$parent" "$commit" 2>/dev/null)
|
|
201
|
+
|
|
202
|
+
# Empty commit (no path changes): nothing to replay, treat as materialized.
|
|
203
|
+
return 0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
vault_sync_list_materialized_commits() {
|
|
207
|
+
local base_ref="$1"
|
|
208
|
+
local target_ref="$2"
|
|
209
|
+
local out_file="$3"
|
|
210
|
+
local repo="${4:-.}"
|
|
211
|
+
local sha
|
|
212
|
+
|
|
213
|
+
: > "$out_file"
|
|
214
|
+
while IFS= read -r sha; do
|
|
215
|
+
[ -n "$sha" ] || continue
|
|
216
|
+
if vault_sync_commit_materialized "$sha" "$target_ref" "$repo"; then
|
|
217
|
+
# Store full SHA for robust matching in the sequence editor.
|
|
218
|
+
git -C "$repo" rev-parse "$sha" >> "$out_file" 2>/dev/null || printf '%s\n' "$sha" >> "$out_file"
|
|
219
|
+
fi
|
|
220
|
+
done < <(git -C "$repo" rev-list --reverse "${base_ref}..HEAD" 2>/dev/null)
|
|
221
|
+
return 0
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Drop matching pick lines entirely (portable; avoids relying on `drop` verb).
|
|
225
|
+
# Usage: vault_sync_sequence_editor_drop <drop-list-file> <todo-file>
|
|
226
|
+
vault_sync_sequence_editor_drop() {
|
|
227
|
+
local drop_list="$1"
|
|
228
|
+
local todo_file="$2"
|
|
229
|
+
local tmp line rest sha full
|
|
230
|
+
|
|
231
|
+
tmp="$(mktemp)" || return 1
|
|
232
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
233
|
+
case "$line" in
|
|
234
|
+
"pick "*|"p "*)
|
|
235
|
+
rest="${line#* }"
|
|
236
|
+
sha="${rest%% *}"
|
|
237
|
+
full=""
|
|
238
|
+
# Match if any full SHA in drop list starts with the todo short/long sha,
|
|
239
|
+
# or the todo sha starts with a drop-list entry.
|
|
240
|
+
if [ -s "$drop_list" ]; then
|
|
241
|
+
while IFS= read -r full || [ -n "$full" ]; do
|
|
242
|
+
[ -n "$full" ] || continue
|
|
243
|
+
case "$full" in
|
|
244
|
+
"$sha"*)
|
|
245
|
+
# omit this pick line (drop)
|
|
246
|
+
continue 2
|
|
247
|
+
;;
|
|
248
|
+
esac
|
|
249
|
+
case "$sha" in
|
|
250
|
+
"$full"*)
|
|
251
|
+
continue 2
|
|
252
|
+
;;
|
|
253
|
+
esac
|
|
254
|
+
done < "$drop_list"
|
|
255
|
+
fi
|
|
256
|
+
printf '%s\n' "$line" >> "$tmp"
|
|
257
|
+
;;
|
|
258
|
+
*)
|
|
259
|
+
printf '%s\n' "$line" >> "$tmp"
|
|
260
|
+
;;
|
|
261
|
+
esac
|
|
262
|
+
done < "$todo_file"
|
|
263
|
+
mv "$tmp" "$todo_file"
|
|
264
|
+
}
|