skillwiki 0.10.16 → 0.10.18
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-OWPQJOFG.js → chunk-GNS2ZV5P.js} +44 -3
- package/dist/{chunk-24MVNAFJ.js → chunk-IAPD6YTC.js} +24 -55
- package/dist/{chunk-NMUYMNNB.js → chunk-IGCW3DR2.js} +1 -1
- package/dist/{chunk-UJLSUJB6.js → chunk-QJPCCW4N.js} +141 -80
- package/dist/{chunk-C5OLZRRM.js → chunk-Y6KRDGI2.js} +70 -2
- package/dist/cli.js +441 -467
- package/dist/{index-projection-MHXG7QO2.js → index-projection-DENEYZTK.js} +2 -2
- package/dist/{managed-write-preflight-M2HL6DDY.js → managed-write-preflight-LO4LZ6OQ.js} +3 -3
- package/dist/skillwiki-mcp.js +4 -4
- package/dist/vault-sync/scripts/generate-systemd-property-catalog.mjs +73 -0
- package/dist/vault-sync/scripts/wiki-snapshot.sh +280 -4
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runManagedWritePreflight,
|
|
4
4
|
runManagedWriteTransaction
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-QJPCCW4N.js";
|
|
6
|
+
import "./chunk-GNS2ZV5P.js";
|
|
7
|
+
import "./chunk-Y6KRDGI2.js";
|
|
8
8
|
export {
|
|
9
9
|
runManagedWritePreflight,
|
|
10
10
|
runManagedWriteTransaction
|
package/dist/skillwiki-mcp.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runSkillwikiMcpStdio
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IAPD6YTC.js";
|
|
5
5
|
import "./chunk-7I2TPIV5.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-IGCW3DR2.js";
|
|
7
|
+
import "./chunk-GNS2ZV5P.js";
|
|
8
|
+
import "./chunk-Y6KRDGI2.js";
|
|
9
9
|
|
|
10
10
|
// src/mcp-entry.ts
|
|
11
11
|
runSkillwikiMcpStdio().catch((error) => {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const repoRoot = join(scriptDir, "..", "..", "..");
|
|
9
|
+
const catalogPath = join(
|
|
10
|
+
repoRoot,
|
|
11
|
+
"packages",
|
|
12
|
+
"shared",
|
|
13
|
+
"src",
|
|
14
|
+
"systemd-property-catalog.json",
|
|
15
|
+
);
|
|
16
|
+
const outputPath = join(
|
|
17
|
+
repoRoot,
|
|
18
|
+
"packages",
|
|
19
|
+
"vault-sync",
|
|
20
|
+
"skills",
|
|
21
|
+
"vault-sync-status",
|
|
22
|
+
"systemd-property-catalog.sh",
|
|
23
|
+
);
|
|
24
|
+
const checkOnly = process.argv.includes("--check");
|
|
25
|
+
|
|
26
|
+
const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
|
|
27
|
+
const entries = Object.entries(catalog);
|
|
28
|
+
const invalid = entries.find(([semantic, property]) =>
|
|
29
|
+
!/^[a-z][a-z0-9_]*$/.test(semantic)
|
|
30
|
+
|| typeof property !== "string"
|
|
31
|
+
|| !/^[A-Z][A-Za-z0-9]*$/.test(property)
|
|
32
|
+
);
|
|
33
|
+
if (invalid) {
|
|
34
|
+
throw new Error(`invalid systemd property catalog entry: ${JSON.stringify(invalid)}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lines = [
|
|
38
|
+
"#!/usr/bin/env bash",
|
|
39
|
+
"# AUTO-GENERATED by packages/vault-sync/scripts/generate-systemd-property-catalog.mjs.",
|
|
40
|
+
"# Source: packages/shared/src/systemd-property-catalog.json",
|
|
41
|
+
"# Do not edit this file directly.",
|
|
42
|
+
"",
|
|
43
|
+
"snapshot_semantic_to_systemd_prop() {",
|
|
44
|
+
' case "$1" in',
|
|
45
|
+
...entries.map(([semantic, property]) =>
|
|
46
|
+
` ${semantic}) printf '${property}\\n' ;;`
|
|
47
|
+
),
|
|
48
|
+
" # Closed map: never pass unknown/snake_case keys through to systemctl.",
|
|
49
|
+
" *) return 1 ;;",
|
|
50
|
+
" esac",
|
|
51
|
+
"}",
|
|
52
|
+
"",
|
|
53
|
+
].join("\n");
|
|
54
|
+
|
|
55
|
+
if (checkOnly) {
|
|
56
|
+
let current = "";
|
|
57
|
+
try {
|
|
58
|
+
current = readFileSync(outputPath, "utf8");
|
|
59
|
+
} catch {
|
|
60
|
+
// Report the same actionable drift error for a missing generated file.
|
|
61
|
+
}
|
|
62
|
+
if (current !== lines) {
|
|
63
|
+
console.error(
|
|
64
|
+
"systemd property catalog shell adapter is stale; run "
|
|
65
|
+
+ "node packages/vault-sync/scripts/generate-systemd-property-catalog.mjs",
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
console.log("systemd property catalog shell adapter is current");
|
|
70
|
+
} else {
|
|
71
|
+
writeFileSync(outputPath, lines);
|
|
72
|
+
console.log(`wrote ${outputPath}`);
|
|
73
|
+
}
|
|
@@ -61,10 +61,22 @@ elif [ -f "$SCRIPT_DIR/../../scripts/lib/delete-intent.sh" ]; then
|
|
|
61
61
|
source "$SCRIPT_DIR/../../scripts/lib/delete-intent.sh"
|
|
62
62
|
fi
|
|
63
63
|
|
|
64
|
+
if [ -f "$SCRIPT_DIR/lib/runtime-manifest.sh" ]; then
|
|
65
|
+
source "$SCRIPT_DIR/lib/runtime-manifest.sh"
|
|
66
|
+
elif [ -f "$SCRIPT_DIR/scripts/lib/runtime-manifest.sh" ]; then
|
|
67
|
+
source "$SCRIPT_DIR/scripts/lib/runtime-manifest.sh"
|
|
68
|
+
elif [ -f "$SCRIPT_DIR/../../scripts/lib/runtime-manifest.sh" ]; then
|
|
69
|
+
source "$SCRIPT_DIR/../../scripts/lib/runtime-manifest.sh"
|
|
70
|
+
fi
|
|
71
|
+
|
|
64
72
|
if ! command -v vault_sync_scan_conflict_markers >/dev/null 2>&1; then
|
|
65
73
|
echo "[wiki-snapshot] ERROR: conflict-marker helper unavailable; refusing to run." >&2
|
|
66
74
|
exit 1
|
|
67
75
|
fi
|
|
76
|
+
if ! command -v vault_sync_sha256 >/dev/null 2>&1; then
|
|
77
|
+
echo "[wiki-snapshot] ERROR: SHA-256 helper unavailable; refusing to run." >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
68
80
|
|
|
69
81
|
# ── Guard: Linux-only operation ────────────────────────────
|
|
70
82
|
platform_require linux
|
|
@@ -79,10 +91,26 @@ CLOUD_REMOTE="${CLOUD_REMOTE:-cloud:cloud/wiki}"
|
|
|
79
91
|
REPAIR_SCRIPT="${WIKI_GIT_REPAIR_SCRIPT:-$SCRIPT_DIR/wiki-git-repair-v3.sh}"
|
|
80
92
|
MAX_S3_ONLY_NOTES="${WIKI_SNAPSHOT_MAX_S3_ONLY_NOTES:-200}"
|
|
81
93
|
MAX_TOMBSTONE_PRUNES="${WIKI_SNAPSHOT_MAX_TOMBSTONE_PRUNES:-10}"
|
|
94
|
+
PROJECTION_PARITY_TIMEOUT_SECONDS="${WIKI_SNAPSHOT_PROJECTION_PARITY_TIMEOUT_SECONDS:-120}"
|
|
95
|
+
PROJECTION_PARITY_POLL_SECONDS="${WIKI_SNAPSHOT_PROJECTION_PARITY_POLL_SECONDS:-2}"
|
|
96
|
+
PROJECTION_READ_TIMEOUT_SECONDS="${WIKI_SNAPSHOT_PROJECTION_READ_TIMEOUT_SECONDS:-15}"
|
|
82
97
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
83
98
|
RCLONE_LOG="/tmp/rclone-${DATE}.log"
|
|
84
99
|
SNAPSHOT_DIRECT_S3_NOT_GIT_COUNT=0
|
|
85
100
|
SNAPSHOT_REMOTE_INVENTORY_READY=0
|
|
101
|
+
PROJECTION_STATE_DIR=""
|
|
102
|
+
PROJECTION_EXPECTED_INDEX_SHA256=""
|
|
103
|
+
PROJECTION_EXPECTED_LOG_SHA256=""
|
|
104
|
+
SNAPSHOT_BASE_OID=""
|
|
105
|
+
SNAPSHOT_GIT_DIR=""
|
|
106
|
+
SNAPSHOT_GIT_COMMON_DIR=""
|
|
107
|
+
|
|
108
|
+
cleanup_projection_state() {
|
|
109
|
+
if [ -n "$PROJECTION_STATE_DIR" ] && [ -d "$PROJECTION_STATE_DIR" ]; then
|
|
110
|
+
rm -rf "$PROJECTION_STATE_DIR"
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
trap cleanup_projection_state EXIT
|
|
86
114
|
|
|
87
115
|
fetch_origin_main_ref() {
|
|
88
116
|
# The snapshot guards read origin/main directly. Use an explicit destination
|
|
@@ -309,6 +337,174 @@ log() {
|
|
|
309
337
|
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
|
|
310
338
|
}
|
|
311
339
|
|
|
340
|
+
snapshot_projection_hash_file() {
|
|
341
|
+
local path="${1:-}"
|
|
342
|
+
[ -f "$path" ] || return 1
|
|
343
|
+
local digest
|
|
344
|
+
digest="$(vault_sync_sha256 "$path")"
|
|
345
|
+
[ -n "$digest" ] || return 1
|
|
346
|
+
printf '%s\n' "$digest"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
validate_projection_parity_config() {
|
|
350
|
+
case "$PROJECTION_PARITY_TIMEOUT_SECONDS" in
|
|
351
|
+
''|*[!0-9]*)
|
|
352
|
+
log "ERROR: invalid WIKI_SNAPSHOT_PROJECTION_PARITY_TIMEOUT_SECONDS=$PROJECTION_PARITY_TIMEOUT_SECONDS; expected a non-negative integer"
|
|
353
|
+
return 1
|
|
354
|
+
;;
|
|
355
|
+
esac
|
|
356
|
+
case "$PROJECTION_PARITY_POLL_SECONDS" in
|
|
357
|
+
''|*[!0-9]*)
|
|
358
|
+
log "ERROR: invalid WIKI_SNAPSHOT_PROJECTION_PARITY_POLL_SECONDS=$PROJECTION_PARITY_POLL_SECONDS; expected a non-negative integer"
|
|
359
|
+
return 1
|
|
360
|
+
;;
|
|
361
|
+
esac
|
|
362
|
+
case "$PROJECTION_READ_TIMEOUT_SECONDS" in
|
|
363
|
+
''|*[!0-9]*|0)
|
|
364
|
+
log "ERROR: invalid WIKI_SNAPSHOT_PROJECTION_READ_TIMEOUT_SECONDS=$PROJECTION_READ_TIMEOUT_SECONDS; expected a positive integer"
|
|
365
|
+
return 1
|
|
366
|
+
;;
|
|
367
|
+
esac
|
|
368
|
+
return 0
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
snapshot_freeze_projection_expectations() {
|
|
372
|
+
if [ "${WIKI_SNAPSHOT_TEST_SKIP_PROJECTION_PARITY:-0}" = "1" ] \
|
|
373
|
+
&& [ -n "${SNAPSHOT_TEST_ROOT:-}" ]; then
|
|
374
|
+
log "projection parity guard skipped by test fixture"
|
|
375
|
+
return 0
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if [ ! -f "$WIKI_DIR/index.md" ] || [ ! -f "$WIKI_DIR/log.md" ]; then
|
|
379
|
+
log "ERROR: live projection files missing after materialization"
|
|
380
|
+
return 1
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
PROJECTION_STATE_DIR="$(mktemp -d)" || {
|
|
384
|
+
log "ERROR: could not create private projection parity state"
|
|
385
|
+
return 1
|
|
386
|
+
}
|
|
387
|
+
if ! cp "$WIKI_DIR/index.md" "$PROJECTION_STATE_DIR/expected-index.md" \
|
|
388
|
+
|| ! cp "$WIKI_DIR/log.md" "$PROJECTION_STATE_DIR/expected-log.md"; then
|
|
389
|
+
log "ERROR: could not freeze live projection bytes"
|
|
390
|
+
return 1
|
|
391
|
+
fi
|
|
392
|
+
|
|
393
|
+
PROJECTION_EXPECTED_INDEX_SHA256="$(snapshot_projection_hash_file "$PROJECTION_STATE_DIR/expected-index.md")" || return 1
|
|
394
|
+
PROJECTION_EXPECTED_LOG_SHA256="$(snapshot_projection_hash_file "$PROJECTION_STATE_DIR/expected-log.md")" || return 1
|
|
395
|
+
log "projection expected bytes frozen index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 log_sha256=$PROJECTION_EXPECTED_LOG_SHA256"
|
|
396
|
+
return 0
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
snapshot_live_projection_matches_frozen() {
|
|
400
|
+
local live_index_sha live_log_sha
|
|
401
|
+
if ! cmp -s "$PROJECTION_STATE_DIR/expected-index.md" "$WIKI_DIR/index.md" \
|
|
402
|
+
|| ! cmp -s "$PROJECTION_STATE_DIR/expected-log.md" "$WIKI_DIR/log.md"; then
|
|
403
|
+
live_index_sha="$(snapshot_projection_hash_file "$WIKI_DIR/index.md" 2>/dev/null || echo unavailable)"
|
|
404
|
+
live_log_sha="$(snapshot_projection_hash_file "$WIKI_DIR/log.md" 2>/dev/null || echo unavailable)"
|
|
405
|
+
log "ERROR: live projection changed after freeze expected_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 live_index_sha256=$live_index_sha expected_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 live_log_sha256=$live_log_sha"
|
|
406
|
+
return 1
|
|
407
|
+
fi
|
|
408
|
+
return 0
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
snapshot_wait_for_direct_projection_parity() {
|
|
412
|
+
if [ -z "$PROJECTION_STATE_DIR" ]; then
|
|
413
|
+
# Test-only skip path above. Production always has frozen state.
|
|
414
|
+
return 0
|
|
415
|
+
fi
|
|
416
|
+
|
|
417
|
+
local started deadline now attempts
|
|
418
|
+
local remote_index="$PROJECTION_STATE_DIR/remote-index.md"
|
|
419
|
+
local remote_log="$PROJECTION_STATE_DIR/remote-log.md"
|
|
420
|
+
local remote_index_sha="unavailable"
|
|
421
|
+
local remote_log_sha="unavailable"
|
|
422
|
+
local index_pid log_pid index_rc log_rc
|
|
423
|
+
started="$(date +%s)"
|
|
424
|
+
deadline=$((started + PROJECTION_PARITY_TIMEOUT_SECONDS))
|
|
425
|
+
attempts=0
|
|
426
|
+
|
|
427
|
+
while :; do
|
|
428
|
+
attempts=$((attempts + 1))
|
|
429
|
+
: > "$remote_index"
|
|
430
|
+
: > "$remote_log"
|
|
431
|
+
timeout "$PROJECTION_READ_TIMEOUT_SECONDS" \
|
|
432
|
+
rclone cat "${CLOUD_REMOTE%/}/index.md" --retries 1 >"$remote_index" 2>>"$LOG_FILE" &
|
|
433
|
+
index_pid=$!
|
|
434
|
+
timeout "$PROJECTION_READ_TIMEOUT_SECONDS" \
|
|
435
|
+
rclone cat "${CLOUD_REMOTE%/}/log.md" --retries 1 >"$remote_log" 2>>"$LOG_FILE" &
|
|
436
|
+
log_pid=$!
|
|
437
|
+
wait "$index_pid"
|
|
438
|
+
index_rc=$?
|
|
439
|
+
wait "$log_pid"
|
|
440
|
+
log_rc=$?
|
|
441
|
+
if [ "$index_rc" -eq 0 ] && [ "$log_rc" -eq 0 ]; then
|
|
442
|
+
if cmp -s "$PROJECTION_STATE_DIR/expected-index.md" "$remote_index" \
|
|
443
|
+
&& cmp -s "$PROJECTION_STATE_DIR/expected-log.md" "$remote_log"; then
|
|
444
|
+
if ! snapshot_live_projection_matches_frozen; then
|
|
445
|
+
return 1
|
|
446
|
+
fi
|
|
447
|
+
remote_index_sha="$(snapshot_projection_hash_file "$remote_index" 2>/dev/null || echo unavailable)"
|
|
448
|
+
remote_log_sha="$(snapshot_projection_hash_file "$remote_log" 2>/dev/null || echo unavailable)"
|
|
449
|
+
log "projection direct-store parity confirmed attempts=$attempts expected_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 live_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 remote_index_sha256=$remote_index_sha expected_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 live_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 remote_log_sha256=$remote_log_sha"
|
|
450
|
+
return 0
|
|
451
|
+
fi
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
now="$(date +%s)"
|
|
455
|
+
if [ "$now" -ge "$deadline" ]; then
|
|
456
|
+
if [ "$index_rc" -eq 0 ]; then
|
|
457
|
+
remote_index_sha="$(snapshot_projection_hash_file "$remote_index" 2>/dev/null || echo unavailable)"
|
|
458
|
+
fi
|
|
459
|
+
if [ "$log_rc" -eq 0 ]; then
|
|
460
|
+
remote_log_sha="$(snapshot_projection_hash_file "$remote_log" 2>/dev/null || echo unavailable)"
|
|
461
|
+
fi
|
|
462
|
+
log "ERROR: projection direct-store parity timeout attempts=$attempts expected_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 remote_index_sha256=$remote_index_sha expected_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 remote_log_sha256=$remote_log_sha"
|
|
463
|
+
return 1
|
|
464
|
+
fi
|
|
465
|
+
sleep "$PROJECTION_PARITY_POLL_SECONDS"
|
|
466
|
+
done
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
snapshot_verify_worktree_projection_parity() {
|
|
470
|
+
if [ -z "$PROJECTION_STATE_DIR" ]; then
|
|
471
|
+
return 0
|
|
472
|
+
fi
|
|
473
|
+
local worktree_index_sha worktree_log_sha
|
|
474
|
+
worktree_index_sha="$(snapshot_projection_hash_file "$SNAPSHOT_WORKTREE/index.md" 2>/dev/null || echo unavailable)"
|
|
475
|
+
worktree_log_sha="$(snapshot_projection_hash_file "$SNAPSHOT_WORKTREE/log.md" 2>/dev/null || echo unavailable)"
|
|
476
|
+
if ! cmp -s "$PROJECTION_STATE_DIR/expected-index.md" "$SNAPSHOT_WORKTREE/index.md" \
|
|
477
|
+
|| ! cmp -s "$PROJECTION_STATE_DIR/expected-log.md" "$SNAPSHOT_WORKTREE/log.md"; then
|
|
478
|
+
log "ERROR: projection worktree parity mismatch expected_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 worktree_index_sha256=$worktree_index_sha expected_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 worktree_log_sha256=$worktree_log_sha"
|
|
479
|
+
return 1
|
|
480
|
+
fi
|
|
481
|
+
log "projection worktree parity confirmed expected_index_sha256=$PROJECTION_EXPECTED_INDEX_SHA256 worktree_index_sha256=$worktree_index_sha expected_log_sha256=$PROJECTION_EXPECTED_LOG_SHA256 worktree_log_sha256=$worktree_log_sha"
|
|
482
|
+
return 0
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
snapshot_verify_semantic_projection_preview() {
|
|
486
|
+
if [ -z "$PROJECTION_STATE_DIR" ]; then
|
|
487
|
+
return 0
|
|
488
|
+
fi
|
|
489
|
+
local preview="$PROJECTION_STATE_DIR/worktree-preview.json"
|
|
490
|
+
if ! "$SKILLWIKI_BIN" projections materialize "$SNAPSHOT_WORKTREE" >"$preview" 2>>"$LOG_FILE"; then
|
|
491
|
+
log "ERROR: projection semantic preview failed"
|
|
492
|
+
return 1
|
|
493
|
+
fi
|
|
494
|
+
if ! grep -Eq '"index_drift"[[:space:]]*:[[:space:]]*false' "$preview" \
|
|
495
|
+
|| ! grep -Eq '"log_drift"[[:space:]]*:[[:space:]]*false' "$preview"; then
|
|
496
|
+
log "ERROR: projection semantic preview mismatch index_or_log_drift=true"
|
|
497
|
+
return 1
|
|
498
|
+
fi
|
|
499
|
+
log "projection semantic preview confirmed index_drift=false log_drift=false"
|
|
500
|
+
return 0
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
snapshot_verify_projection_candidate() {
|
|
504
|
+
snapshot_verify_worktree_projection_parity \
|
|
505
|
+
&& snapshot_verify_semantic_projection_preview
|
|
506
|
+
}
|
|
507
|
+
|
|
312
508
|
# Emit the canonical snapshot-completion terminal record (v0.10.14).
|
|
313
509
|
# One stable machine-parseable line written for both pushed and no-change
|
|
314
510
|
# success outcomes. Failure paths must never call this.
|
|
@@ -316,9 +512,43 @@ log() {
|
|
|
316
512
|
emit_snapshot_complete() {
|
|
317
513
|
local outcome="$1"
|
|
318
514
|
local head_oid origin_oid
|
|
515
|
+
if ! snapshot_refresh_origin_main; then
|
|
516
|
+
log "ERROR: could not refresh origin/main for final snapshot proof"
|
|
517
|
+
return 1
|
|
518
|
+
fi
|
|
319
519
|
head_oid="$(git -C "$SNAPSHOT_WORKTREE" rev-parse HEAD 2>/dev/null || echo unknown)"
|
|
320
520
|
origin_oid="$(git -C "$SNAPSHOT_WORKTREE" rev-parse --verify -q origin/main 2>/dev/null || echo unknown)"
|
|
521
|
+
if [ "$head_oid" = "unknown" ] || [ "$origin_oid" = "unknown" ] || [ "$head_oid" != "$origin_oid" ]; then
|
|
522
|
+
log "ERROR: final snapshot proof failed head=$head_oid origin=$origin_oid"
|
|
523
|
+
return 1
|
|
524
|
+
fi
|
|
321
525
|
log "SNAPSHOT_COMPLETE schema=v1 outcome=${outcome} result=success ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) head=${head_oid} origin=${origin_oid}"
|
|
526
|
+
return 0
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
snapshot_freeze_git_receipt() {
|
|
530
|
+
local mutation_root convergence_root
|
|
531
|
+
mutation_root="$(cd "$WIKI_DIR" 2>/dev/null && pwd -P)" || return 1
|
|
532
|
+
convergence_root="$(cd "$SNAPSHOT_WORKTREE" 2>/dev/null && pwd -P)" || return 1
|
|
533
|
+
SNAPSHOT_GIT_DIR="$(git -C "$SNAPSHOT_WORKTREE" rev-parse --absolute-git-dir 2>/dev/null)" || return 1
|
|
534
|
+
SNAPSHOT_GIT_COMMON_DIR="$(git -C "$SNAPSHOT_WORKTREE" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || return 1
|
|
535
|
+
SNAPSHOT_BASE_OID="$(git -C "$SNAPSHOT_WORKTREE" rev-parse HEAD 2>/dev/null)" || return 1
|
|
536
|
+
log "snapshot transaction receipt mutation_root=$mutation_root convergence_root=$convergence_root git_dir=$SNAPSHOT_GIT_DIR git_common_dir=$SNAPSHOT_GIT_COMMON_DIR base_oid=$SNAPSHOT_BASE_OID write_mode=dual-path-snapshot"
|
|
537
|
+
return 0
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
snapshot_verify_git_receipt() {
|
|
541
|
+
local current_git_dir current_common_dir current_head
|
|
542
|
+
current_git_dir="$(git -C "$SNAPSHOT_WORKTREE" rev-parse --absolute-git-dir 2>/dev/null)" || return 1
|
|
543
|
+
current_common_dir="$(git -C "$SNAPSHOT_WORKTREE" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" || return 1
|
|
544
|
+
current_head="$(git -C "$SNAPSHOT_WORKTREE" rev-parse HEAD 2>/dev/null)" || return 1
|
|
545
|
+
if [ "$current_git_dir" != "$SNAPSHOT_GIT_DIR" ] \
|
|
546
|
+
|| [ "$current_common_dir" != "$SNAPSHOT_GIT_COMMON_DIR" ] \
|
|
547
|
+
|| [ "$current_head" != "$SNAPSHOT_BASE_OID" ]; then
|
|
548
|
+
log "ERROR: snapshot transaction receipt drift expected_base_oid=$SNAPSHOT_BASE_OID actual_base_oid=$current_head"
|
|
549
|
+
return 1
|
|
550
|
+
fi
|
|
551
|
+
return 0
|
|
322
552
|
}
|
|
323
553
|
|
|
324
554
|
validate_tombstone_prune_cap() {
|
|
@@ -410,6 +640,9 @@ log "=== Wiki Snapshot: $DATE ==="
|
|
|
410
640
|
if ! validate_tombstone_prune_cap; then
|
|
411
641
|
exit 1
|
|
412
642
|
fi
|
|
643
|
+
if ! validate_projection_parity_config; then
|
|
644
|
+
exit 1
|
|
645
|
+
fi
|
|
413
646
|
|
|
414
647
|
# Check disk space (need at least 100MB free)
|
|
415
648
|
AVAILABLE_KB=$(df -k "$SNAPSHOT_WORKTREE" | awk 'NR==2 {print $4}')
|
|
@@ -418,8 +651,9 @@ if [ "$AVAILABLE_KB" -lt 102400 ]; then
|
|
|
418
651
|
exit 1
|
|
419
652
|
fi
|
|
420
653
|
|
|
421
|
-
# Check
|
|
422
|
-
|
|
654
|
+
# Check repository validity through Git so linked worktrees (.git text files)
|
|
655
|
+
# are accepted and Git-internal paths are resolved from the convergence root.
|
|
656
|
+
if [ "$(git -C "$SNAPSHOT_WORKTREE" rev-parse --is-inside-work-tree 2>/dev/null)" != "true" ]; then
|
|
423
657
|
log "ERROR: Git worktree not found or not a git repo: $SNAPSHOT_WORKTREE"
|
|
424
658
|
exit 1
|
|
425
659
|
fi
|
|
@@ -432,6 +666,10 @@ if [ "$(cd "$WIKI_DIR" 2>/dev/null && pwd -P)" = "$(cd "$SNAPSHOT_WORKTREE" 2>/d
|
|
|
432
666
|
log "ERROR: WIKI_DIR and WIKI_GIT_WORKTREE must be distinct paths for dual-path projection (got: $WIKI_DIR)"
|
|
433
667
|
exit 1
|
|
434
668
|
fi
|
|
669
|
+
if ! snapshot_freeze_git_receipt; then
|
|
670
|
+
log "ERROR: could not freeze snapshot transaction receipt"
|
|
671
|
+
exit 1
|
|
672
|
+
fi
|
|
435
673
|
|
|
436
674
|
# Single-authority root projections before FUSE/S3 pull promotion.
|
|
437
675
|
# Mutation target is the live vault ($WIKI_DIR); Git pull/base-OID use
|
|
@@ -459,6 +697,17 @@ if command -v "$SKILLWIKI_BIN" >/dev/null 2>&1; then
|
|
|
459
697
|
exit 1
|
|
460
698
|
fi
|
|
461
699
|
log "OK projections materialize before snapshot sync"
|
|
700
|
+
if ! snapshot_freeze_projection_expectations; then
|
|
701
|
+
log "FAIL projection expectation freeze; snapshot promotion refused"
|
|
702
|
+
exit 1
|
|
703
|
+
fi
|
|
704
|
+
if ! snapshot_wait_for_direct_projection_parity; then
|
|
705
|
+
log "FAIL projection direct-store parity; snapshot promotion refused"
|
|
706
|
+
exit 1
|
|
707
|
+
fi
|
|
708
|
+
else
|
|
709
|
+
log "ERROR: skillwiki CLI unavailable; refusing snapshot without projection materialization and parity proof"
|
|
710
|
+
exit 1
|
|
462
711
|
fi
|
|
463
712
|
|
|
464
713
|
# Common rclone options
|
|
@@ -660,6 +909,10 @@ snapshot_reconcile_delete_intents() {
|
|
|
660
909
|
if ! snapshot_reconcile_delete_intents; then
|
|
661
910
|
exit 1
|
|
662
911
|
fi
|
|
912
|
+
if ! snapshot_verify_projection_candidate; then
|
|
913
|
+
log "FAIL projection candidate verification; snapshot promotion refused"
|
|
914
|
+
exit 1
|
|
915
|
+
fi
|
|
663
916
|
|
|
664
917
|
# Change to git dir for operations
|
|
665
918
|
cd "$SNAPSHOT_WORKTREE" || { log "ERROR: Failed to cd to $SNAPSHOT_WORKTREE"; exit 1; }
|
|
@@ -730,6 +983,14 @@ if [ "$needs_repair" = true ]; then
|
|
|
730
983
|
if ! snapshot_reconcile_delete_intents; then
|
|
731
984
|
exit 1
|
|
732
985
|
fi
|
|
986
|
+
if ! snapshot_verify_projection_candidate; then
|
|
987
|
+
log "FAIL post-repair projection candidate verification; snapshot promotion refused"
|
|
988
|
+
exit 1
|
|
989
|
+
fi
|
|
990
|
+
if ! snapshot_freeze_git_receipt; then
|
|
991
|
+
log "ERROR: could not refresh snapshot transaction receipt after repair"
|
|
992
|
+
exit 1
|
|
993
|
+
fi
|
|
733
994
|
fi
|
|
734
995
|
|
|
735
996
|
if ! raw_dedup_guard; then
|
|
@@ -743,7 +1004,13 @@ fi
|
|
|
743
1004
|
# Check for changes
|
|
744
1005
|
if [ -z "$(git status --porcelain)" ]; then
|
|
745
1006
|
log "No changes to commit"
|
|
746
|
-
|
|
1007
|
+
if ! snapshot_verify_git_receipt; then
|
|
1008
|
+
log "ERROR: snapshot transaction changed before no-change proof"
|
|
1009
|
+
exit 1
|
|
1010
|
+
fi
|
|
1011
|
+
if ! emit_snapshot_complete "no-change"; then
|
|
1012
|
+
exit 1
|
|
1013
|
+
fi
|
|
747
1014
|
exit 0
|
|
748
1015
|
fi
|
|
749
1016
|
|
|
@@ -757,6 +1024,13 @@ if command -v git_case_assert_clean >/dev/null 2>&1; then
|
|
|
757
1024
|
fi
|
|
758
1025
|
fi
|
|
759
1026
|
|
|
1027
|
+
# The commit candidate may modify worktree bytes, but it must not silently move
|
|
1028
|
+
# the convergence repository or HEAD away from the frozen transaction base.
|
|
1029
|
+
if ! snapshot_verify_git_receipt; then
|
|
1030
|
+
log "ERROR: snapshot transaction changed before staging"
|
|
1031
|
+
exit 1
|
|
1032
|
+
fi
|
|
1033
|
+
|
|
760
1034
|
# Commit
|
|
761
1035
|
echo "Committing changes..."
|
|
762
1036
|
if ! git add -A; then
|
|
@@ -820,7 +1094,9 @@ done
|
|
|
820
1094
|
if [ "$PUSH_SUCCESS" = true ]; then
|
|
821
1095
|
log "Push successful"
|
|
822
1096
|
log "Status: complete"
|
|
823
|
-
emit_snapshot_complete "pushed"
|
|
1097
|
+
if ! emit_snapshot_complete "pushed"; then
|
|
1098
|
+
exit 1
|
|
1099
|
+
fi
|
|
824
1100
|
exit 0
|
|
825
1101
|
else
|
|
826
1102
|
log "ERROR: Push failed after $PUSH_RETRIES attempts"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.18",
|
|
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": {
|