skillwiki 0.10.39 → 0.10.40
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.
|
@@ -32,6 +32,21 @@ HANDOFF_NOTIFY_AFTER_SECONDS="${WIKI_FETCH_HANDOFF_NOTIFY_AFTER_SECONDS:-3600}"
|
|
|
32
32
|
case "$HANDOFF_NOTIFY_AFTER_SECONDS" in
|
|
33
33
|
''|*[!0-9]*) HANDOFF_NOTIFY_AFTER_SECONDS=3600 ;;
|
|
34
34
|
esac
|
|
35
|
+
# P2 handoff hard-pause (2026-08-12 design, see projects/llm-wiki/architecture/2026-08-12-wiki-push-fail-dedup-design.md)
|
|
36
|
+
# WIKI_FETCH_HANDOFF_HARD_PAUSE=1 enables the hard pause; default is 0 (opt-in
|
|
37
|
+
# per design Q3) so the legacy reminder-backoff behavior is preserved.
|
|
38
|
+
# WIKI_FETCH_HANDOFF_HARD_PAUSE_CYCLES controls the threshold of consecutive
|
|
39
|
+
# 5-min cycles of the same handoff identity before pausing the fetch job
|
|
40
|
+
# (default 13 cycles — one past the 1h HANDOFF_NOTIFY_AFTER_SECONDS mark at
|
|
41
|
+
# StartInterval=300, so the counter can reach the threshold before the
|
|
42
|
+
# notify resets it; the design's "1-hour hard pause" intent is preserved).
|
|
43
|
+
FETCH_HARD_PAUSE="${WIKI_FETCH_HANDOFF_HARD_PAUSE:-0}"
|
|
44
|
+
FETCH_HARD_PAUSE_CYCLES="${WIKI_FETCH_HANDOFF_HARD_PAUSE_CYCLES:-13}"
|
|
45
|
+
case "$FETCH_HARD_PAUSE_CYCLES" in
|
|
46
|
+
''|*[!0-9]*) FETCH_HARD_PAUSE_CYCLES=13 ;;
|
|
47
|
+
esac
|
|
48
|
+
FETCH_HARD_PAUSE_COUNTER_FILE="$STATE_DIR/handoff-cycle-counter"
|
|
49
|
+
FETCH_HARD_PAUSE_MARKER="$STATE_DIR/wiki-fetch.paused"
|
|
35
50
|
# Opt-in: when enabled, a positive delta triggers `git pull --rebase` so the
|
|
36
51
|
# local working tree consumes sg01 Snapshot commits automatically. This
|
|
37
52
|
# replaces the git pull that was previously bundled inside wiki-push.sh's
|
|
@@ -43,6 +58,15 @@ LOG_FILE="$(platform_log_dir)/wiki-fetch.log"
|
|
|
43
58
|
|
|
44
59
|
mkdir -p "$STATE_DIR" "$(dirname "$LOG_FILE")"
|
|
45
60
|
|
|
61
|
+
# Terminal-state check: if the pause marker exists from a prior incident,
|
|
62
|
+
# the launchd job is already unloaded. Honor the pause — log once and exit
|
|
63
|
+
# without re-fetching or re-notifying. The user clears this by removing
|
|
64
|
+
# the marker and re-loading the plist (per design Q1, B).
|
|
65
|
+
if [ -f "$FETCH_HARD_PAUSE_MARKER" ]; then
|
|
66
|
+
log "PAUSED fetch job is paused; marker present at $FETCH_HARD_PAUSE_MARKER — exiting"
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
46
70
|
log() {
|
|
47
71
|
printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >>"$LOG_FILE"
|
|
48
72
|
}
|
|
@@ -50,7 +74,7 @@ log() {
|
|
|
50
74
|
handle_existing_handoff() {
|
|
51
75
|
local blocker reason op identity previous_identity notified_at now
|
|
52
76
|
blocker="$(vault_sync_op_preflight_blocker "$WIKI_DIR" 2>/dev/null || true)"
|
|
53
|
-
[ -n "$blocker" ] || { rm -f "$HANDOFF_STATE_FILE" 2>/dev/null || true; return 1; }
|
|
77
|
+
[ -n "$blocker" ] || { rm -f "$HANDOFF_STATE_FILE" "$FETCH_HARD_PAUSE_COUNTER_FILE" 2>/dev/null || true; return 1; }
|
|
54
78
|
|
|
55
79
|
reason="${blocker%% *}"
|
|
56
80
|
op="${blocker#* }"
|
|
@@ -69,8 +93,34 @@ handle_existing_handoff() {
|
|
|
69
93
|
platform_notify "wiki" "review-required handoff ${op:-none} still blocks sync"
|
|
70
94
|
printf 'identity=%s\nnotified_at=%s\n' "$identity" "$now" > "$HANDOFF_STATE_FILE"
|
|
71
95
|
log "NOTIFY handoff identity=$identity"
|
|
96
|
+
# P2: a new handoff identity (or a fresh notify) resets the cycle counter.
|
|
97
|
+
if [ "$FETCH_HARD_PAUSE" = "1" ]; then
|
|
98
|
+
printf '0\n' > "$FETCH_HARD_PAUSE_COUNTER_FILE" 2>/dev/null || true
|
|
99
|
+
fi
|
|
72
100
|
else
|
|
73
101
|
log "SKIP PULL handoff identity=$identity reminder-backoff"
|
|
102
|
+
# P2: increment the cycle counter for this identity. When the counter
|
|
103
|
+
# reaches FETCH_HARD_PAUSE_CYCLES, write the persistent-handoff log line
|
|
104
|
+
# and unload the launchd job (with marker-file fallback for non-macOS).
|
|
105
|
+
if [ "$FETCH_HARD_PAUSE" = "1" ]; then
|
|
106
|
+
local cycle_count
|
|
107
|
+
cycle_count=0
|
|
108
|
+
if [ -f "$FETCH_HARD_PAUSE_COUNTER_FILE" ]; then
|
|
109
|
+
cycle_count="$(cat "$FETCH_HARD_PAUSE_COUNTER_FILE" 2>/dev/null || echo 0)"
|
|
110
|
+
case "$cycle_count" in ''|*[!0-9]*) cycle_count=0 ;; esac
|
|
111
|
+
fi
|
|
112
|
+
cycle_count=$((cycle_count + 1))
|
|
113
|
+
printf '%s\n' "$cycle_count" > "$FETCH_HARD_PAUSE_COUNTER_FILE" 2>/dev/null || true
|
|
114
|
+
if [ "$cycle_count" -ge "$FETCH_HARD_PAUSE_CYCLES" ]; then
|
|
115
|
+
log "handoff persistent — pausing fetch (identity=$identity cycles=$cycle_count threshold=$FETCH_HARD_PAUSE_CYCLES)"
|
|
116
|
+
printf 'paused_at=%s\nidentity=%s\ncycles=%s\nthreshold=%s\nreason=handoff-persistent-past-threshold\n' \
|
|
117
|
+
"$(date -u +%FT%TZ)" "$identity" "$cycle_count" "$FETCH_HARD_PAUSE_CYCLES" \
|
|
118
|
+
> "$FETCH_HARD_PAUSE_MARKER" 2>/dev/null || true
|
|
119
|
+
if [ "$(platform_scheduler)" = "launchd" ]; then
|
|
120
|
+
launchctl unload "$HOME/Library/LaunchAgents/com.karlchow.wiki-fetch.plist" 2>/dev/null || true
|
|
121
|
+
fi
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
74
124
|
fi
|
|
75
125
|
return 0
|
|
76
126
|
}
|
|
@@ -37,20 +37,122 @@ LOCK_FILE="$(platform_cache_dir)/wiki-push.lock"
|
|
|
37
37
|
LOG_FILE="$(platform_log_dir)/wiki-push.log"
|
|
38
38
|
LOG_MAX_SIZE=1048576 # 1 MB
|
|
39
39
|
LOG_KEEP=5
|
|
40
|
+
# P1 conflict-marker dedup (2026-08-12 design, see projects/llm-wiki/architecture/2026-08-12-wiki-push-fail-dedup-design.md)
|
|
41
|
+
# WIKI_PUSH_FAIL_DEDUP_DISABLE=1 disables the dedup entirely (rollback to legacy behavior).
|
|
42
|
+
# WIKI_PUSH_FAIL_DEDUP_COOLDOWN_SECONDS controls the per-incident cooldown window (default 900s = 15min).
|
|
43
|
+
PUSH_DEDUP_DISABLE="${WIKI_PUSH_FAIL_DEDUP_DISABLE:-0}"
|
|
44
|
+
PUSH_DEDUP_COOLDOWN_SECONDS="${WIKI_PUSH_FAIL_DEDUP_COOLDOWN_SECONDS:-900}"
|
|
45
|
+
PUSH_DEDUP_STATE_FILE="$(platform_cache_dir)/wiki-push-fail-dedup.state"
|
|
46
|
+
PUSH_DEDUP_PAUSE_MARKER="$(platform_cache_dir)/wiki-push.paused"
|
|
40
47
|
|
|
41
48
|
mkdir -p "$(dirname "$LOCK_FILE")" "$(dirname "$LOG_FILE")"
|
|
42
49
|
|
|
43
50
|
log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >>"$LOG_FILE"; }
|
|
44
51
|
|
|
52
|
+
# Terminal-state check: if the pause marker exists from a prior incident,
|
|
53
|
+
# the launchd job is already unloaded. Honor the pause — log once and exit
|
|
54
|
+
# without re-scanning or re-attempting the push. The user clears this by
|
|
55
|
+
# removing the marker and re-loading the plist (per design Q1, B).
|
|
56
|
+
if [ "$PUSH_DEDUP_DISABLE" != "1" ] && [ -f "$PUSH_DEDUP_PAUSE_MARKER" ]; then
|
|
57
|
+
log "PAUSED push job is paused; marker present at $PUSH_DEDUP_PAUSE_MARKER — exiting"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# P1 dedup state check: read the dedup state file's mtime and compare against
|
|
62
|
+
# the cooldown window. Echoes one of: "disabled" (dedup turned off, caller
|
|
63
|
+
# must not touch state), "first" (no prior incident, standard guard should
|
|
64
|
+
# run), "cooldown" (within window, suppress duplicate FAIL line + skip the
|
|
65
|
+
# new FAIL log), "expired" (past window, escalate to pause).
|
|
66
|
+
push_dedup_cooldown_check() {
|
|
67
|
+
if [ "$PUSH_DEDUP_DISABLE" = "1" ]; then
|
|
68
|
+
printf '%s\n' "disabled"
|
|
69
|
+
return 0
|
|
70
|
+
fi
|
|
71
|
+
if [ ! -f "$PUSH_DEDUP_STATE_FILE" ]; then
|
|
72
|
+
printf '%s\n' "first"
|
|
73
|
+
return 0
|
|
74
|
+
fi
|
|
75
|
+
local state_ctime now elapsed
|
|
76
|
+
state_ctime="$(platform_stat_ctime "$PUSH_DEDUP_STATE_FILE" 2>/dev/null || echo 0)"
|
|
77
|
+
now="$(date +%s)"
|
|
78
|
+
elapsed=$((now - state_ctime))
|
|
79
|
+
if [ "$elapsed" -lt "$PUSH_DEDUP_COOLDOWN_SECONDS" ]; then
|
|
80
|
+
printf '%s\n' "cooldown"
|
|
81
|
+
return 0
|
|
82
|
+
fi
|
|
83
|
+
printf '%s\n' "expired"
|
|
84
|
+
return 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Touch (or create) the dedup state file so subsequent invocations see the
|
|
88
|
+
# incident's first-detection timestamp. Uses a single `touch` to avoid races
|
|
89
|
+
# with parallel invocations (which are guarded by the lockfile above).
|
|
90
|
+
push_dedup_touch_state() {
|
|
91
|
+
touch "$PUSH_DEDUP_STATE_FILE" 2>/dev/null || true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Clear the dedup state file (used on a successful push, when markers are gone).
|
|
95
|
+
push_dedup_clear_state() {
|
|
96
|
+
rm -f "$PUSH_DEDUP_STATE_FILE" 2>/dev/null || true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Write a pause marker file as a portable stand-in for `launchctl unload`.
|
|
100
|
+
# On a real macOS host, the launchd plist is the actual unpause target; this
|
|
101
|
+
# marker is a portable test surface and a cross-platform indicator.
|
|
102
|
+
push_dedup_write_pause_marker() {
|
|
103
|
+
local now
|
|
104
|
+
now="$(date -u +%FT%TZ)"
|
|
105
|
+
printf 'paused_at=%s reason=conflict-markers-persist-past-cooldown cooldown_seconds=%s\n' \
|
|
106
|
+
"$now" "$PUSH_DEDUP_COOLDOWN_SECONDS" > "$PUSH_DEDUP_PAUSE_MARKER" 2>/dev/null || true
|
|
107
|
+
if [ "$(platform_scheduler)" = "launchd" ]; then
|
|
108
|
+
# Best-effort launchd unload. Failure is non-fatal — the marker file is
|
|
109
|
+
# the canonical pause signal. This matches the design doc's rollback
|
|
110
|
+
# semantics: the marker is the test surface, launchctl is the production
|
|
111
|
+
# surface, both can be present.
|
|
112
|
+
launchctl unload "$HOME/Library/LaunchAgents/com.karlchow.wiki-push.plist" 2>/dev/null || true
|
|
113
|
+
fi
|
|
114
|
+
}
|
|
115
|
+
|
|
45
116
|
conflict_marker_guard() {
|
|
46
117
|
local findings
|
|
47
118
|
findings="$(mktemp)" || { log "FAIL could not create conflict-marker scan temp file"; return 1; }
|
|
48
119
|
if ! vault_sync_scan_conflict_markers "$WIKI_DIR" "$findings"; then
|
|
49
|
-
|
|
50
|
-
|
|
120
|
+
local dedup_state
|
|
121
|
+
dedup_state="$(push_dedup_cooldown_check)"
|
|
122
|
+
case "$dedup_state" in
|
|
123
|
+
first)
|
|
124
|
+
# First detection: log the FAIL line, mark the state file so
|
|
125
|
+
# subsequent invocations within the cooldown window are silent.
|
|
126
|
+
log "FAIL conflict marker blocks present; refusing S3 push"
|
|
127
|
+
vault_sync_log_conflict_marker_findings "$findings" "$LOG_FILE"
|
|
128
|
+
push_dedup_touch_state
|
|
129
|
+
;;
|
|
130
|
+
expired)
|
|
131
|
+
# Past the cooldown: log the FAIL line + escalation notice,
|
|
132
|
+
# write the pause marker, refresh the state file so the next
|
|
133
|
+
# cooldown starts from this moment.
|
|
134
|
+
log "FAIL conflict marker blocks present; refusing S3 push"
|
|
135
|
+
vault_sync_log_conflict_marker_findings "$findings" "$LOG_FILE"
|
|
136
|
+
log "cooldown expired — pausing push (markers persist past ${PUSH_DEDUP_COOLDOWN_SECONDS}s)"
|
|
137
|
+
push_dedup_write_pause_marker
|
|
138
|
+
push_dedup_touch_state
|
|
139
|
+
;;
|
|
140
|
+
cooldown)
|
|
141
|
+
# Within cooldown: suppress the duplicate FAIL line. The first
|
|
142
|
+
# FAIL line in the incident (state=first) is already logged;
|
|
143
|
+
# subsequent invocations within the cooldown are silent.
|
|
144
|
+
;;
|
|
145
|
+
disabled)
|
|
146
|
+
# Dedup is off: log the FAIL line (legacy behavior), do not
|
|
147
|
+
# touch any state file or pause marker.
|
|
148
|
+
log "FAIL conflict marker blocks present; refusing S3 push"
|
|
149
|
+
vault_sync_log_conflict_marker_findings "$findings" "$LOG_FILE"
|
|
150
|
+
;;
|
|
151
|
+
esac
|
|
51
152
|
rm -f "$findings"
|
|
52
153
|
return 1
|
|
53
154
|
fi
|
|
155
|
+
push_dedup_clear_state
|
|
54
156
|
rm -f "$findings"
|
|
55
157
|
return 0
|
|
56
158
|
}
|
|
@@ -359,6 +461,11 @@ if [ "$RC" -eq 0 ]; then
|
|
|
359
461
|
if ! remote_prune_tombstoned_paths; then
|
|
360
462
|
log "FAIL tombstone prune failed after rclone copy"
|
|
361
463
|
fi
|
|
464
|
+
# P1: a successful push proves markers are gone. conflict_marker_guard
|
|
465
|
+
# already cleared the dedup state on the scan-success path. The pause
|
|
466
|
+
# marker rm is defensive: it covers the case where the user manually
|
|
467
|
+
# unpaused the launchd job without removing the marker file.
|
|
468
|
+
rm -f "$PUSH_DEDUP_PAUSE_MARKER" 2>/dev/null || true
|
|
362
469
|
fi
|
|
363
470
|
|
|
364
471
|
exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.40",
|
|
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": {
|