@codyswann/lisa 4.71.3 → 4.71.5

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.
Files changed (86) hide show
  1. package/all/copy-overwrite/scripts/lib/npm-update-classifier-cache.mjs +94 -0
  2. package/all/copy-overwrite/scripts/lib/npm-update-helper-inventory.mjs +1 -0
  3. package/all/copy-overwrite/scripts/lib/npm-update-helper.mjs +9 -2
  4. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +122 -8
  5. package/all/copy-overwrite/scripts/npm-updater-helper-graph.json +12 -3
  6. package/dist/cli/hook-refresh-files.d.ts +86 -0
  7. package/dist/cli/hook-refresh-files.d.ts.map +1 -0
  8. package/dist/cli/hook-refresh-files.js +209 -0
  9. package/dist/cli/hook-refresh-files.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -1
  12. package/dist/cli/index.js +3 -0
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/refresh-hooks-cmd.d.ts +27 -0
  15. package/dist/cli/refresh-hooks-cmd.d.ts.map +1 -0
  16. package/dist/cli/refresh-hooks-cmd.js +156 -0
  17. package/dist/cli/refresh-hooks-cmd.js.map +1 -0
  18. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  19. package/dist/core/lisa-owned-hash-ledger.js +20 -0
  20. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  21. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  22. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  23. package/dist/core/upstream-evidence-manifest.js +21 -9
  24. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  25. package/dist/opencode/plugin-templates/parity-safety-net.sh +122 -8
  26. package/package.json +4 -4
  27. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa/hooks/parity-safety-net.sh +122 -8
  30. package/plugins/lisa-agy/hooks/parity-safety-net.sh +122 -8
  31. package/plugins/lisa-agy/plugin.json +1 -1
  32. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  35. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +122 -8
  39. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +122 -8
  41. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  43. package/plugins/lisa-expo-agy/plugin.json +1 -1
  44. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  53. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  54. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  64. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  68. package/plugins/lisa-rails-agy/plugin.json +1 -1
  69. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  74. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  79. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/src/base/hooks/parity-safety-net.sh +122 -8
  82. package/rails/copy-overwrite/scripts/lisa-scratch-run.sh +54 -9
  83. package/scripts/build-plugins.sh +3 -0
  84. package/scripts/generate-scratch-supervisor-profile.mjs +75 -0
  85. package/scripts/two-channel-couplings.json +1 -1
  86. package/typescript/copy-contents/.husky/commit-msg +28 -31
@@ -0,0 +1,94 @@
1
+ // This file is managed by Lisa and IS replaced on each `lisa` run.
2
+ // Do not edit directly — durable changes belong upstream in Lisa.
3
+
4
+ /** One invocation reuses authenticated code bytes, never mutable provider authorization. */
5
+ import { required, OBJECT } from "./npm-update-contract.mjs";
6
+
7
+ /** Fresh owner selection and regular-file reads precede every cached use. */
8
+ function currentBinding(config, owner, read, members) {
9
+ const selected = owner();
10
+ return {
11
+ root: selected.root,
12
+ source: selected.source,
13
+ version: selected.metadata.version,
14
+ signerDigest: config.automationProvenance?.signerDigest,
15
+ bytes: read(selected.root, members),
16
+ };
17
+ }
18
+
19
+ /** A cached import URL cannot authorize changed files or a replacement owner. */
20
+ function assertBinding(expected, actual) {
21
+ required(
22
+ ["root", "source", "version", "signerDigest"].every(
23
+ field => expected[field] === actual[field]
24
+ ) &&
25
+ expected.bytes.length === actual.bytes.length &&
26
+ expected.bytes.every((bytes, index) => bytes.equals(actual.bytes[index])),
27
+ "authenticated classifier identity or bytes changed"
28
+ );
29
+ }
30
+
31
+ /** A sibling awaiting the same import may have invalidated its URL meanwhile. */
32
+ function assertUsable(poisoned, ...roots) {
33
+ required(
34
+ roots.every(root => !poisoned.has(root)),
35
+ "authenticated classifier import lifetime is uncertain"
36
+ );
37
+ }
38
+
39
+ /** Each factory owns its inaccessible cache; another caller cannot preseed this instance. */
40
+ export function classifierMemo(owner, read, members) {
41
+ const entries = new WeakMap();
42
+ const poisoned = new Set();
43
+ const fixed = [...members];
44
+ return async (config, authenticate) => {
45
+ const current = currentBinding(config, owner, read, fixed);
46
+ assertUsable(poisoned, current.root);
47
+ // Without a source pin, the original qualifier must observe current HEAD each time.
48
+ if (
49
+ current.source &&
50
+ (typeof current.signerDigest !== "string" ||
51
+ !OBJECT.test(current.signerDigest))
52
+ ) {
53
+ const imported = await authenticate();
54
+ const observed = currentBinding(config, owner, read, fixed);
55
+ assertUsable(poisoned, current.root, imported.root, observed.root);
56
+ return imported.verdict;
57
+ }
58
+ let entry = entries.get(config);
59
+ if (entry) assertBinding(entry.binding, current);
60
+ else {
61
+ entry = {
62
+ binding: {
63
+ ...current,
64
+ bytes: current.bytes.map(bytes => Buffer.from(bytes)),
65
+ },
66
+ promise: Promise.resolve().then(authenticate),
67
+ };
68
+ entries.set(config, entry);
69
+ const pending = entry;
70
+ pending.promise.catch(() => {
71
+ if (entries.get(config) === pending) entries.delete(config);
72
+ });
73
+ }
74
+ const imported = await entry.promise;
75
+ let observed;
76
+ try {
77
+ // Authentication/import may yield; detect changes during that interval too.
78
+ observed = currentBinding(config, owner, read, fixed);
79
+ assertBinding(entry.binding, observed);
80
+ required(
81
+ imported.root === entry.binding.root,
82
+ "classifier import owner changed"
83
+ );
84
+ assertUsable(poisoned, entry.binding.root, imported.root, observed.root);
85
+ } catch (error) {
86
+ // ESM caches this URL: restoring files or another config cannot requalify it.
87
+ poisoned.add(entry.binding.root);
88
+ poisoned.add(imported.root);
89
+ if (observed) poisoned.add(observed.root);
90
+ throw error;
91
+ }
92
+ return imported.verdict;
93
+ };
94
+ }
@@ -46,6 +46,7 @@ const PRODUCER = [
46
46
  "cancellation",
47
47
  "cancellation-proof",
48
48
  "checkpoint",
49
+ "classifier-cache",
49
50
  "contract",
50
51
  "controller-broker",
51
52
  "controller-factory",
@@ -8,6 +8,7 @@ import { join } from "node:path";
8
8
  import { fileURLToPath, pathToFileURL } from "node:url";
9
9
  import { existsSync, lstatSync, realpathSync } from "node:fs";
10
10
  import { required, VERSION, OBJECT } from "./npm-update-contract.mjs";
11
+ import { classifierMemo } from "./npm-update-classifier-cache.mjs";
11
12
  import {
12
13
  readBytes,
13
14
  runProcess,
@@ -33,6 +34,7 @@ import {
33
34
  } from "./npm-update-helper-graph.mjs";
34
35
 
35
36
  const PACKAGE = "@codyswann/lisa";
37
+ const classifier = classifierMemo(helperOwner, helperBytes, MEMBERS);
36
38
 
37
39
  /** Native package resolution supports both upstream and actual emitted managed layouts. */
38
40
  export function helperOwner() {
@@ -277,7 +279,7 @@ export async function qualifiedControllerGraph(cwd, config, entries) {
277
279
  }
278
280
 
279
281
  /** No canonical package code is imported until independent authority qualifies its fixed graph. */
280
- export async function canonicalClassifier(config) {
282
+ async function authenticatedClassifier(config) {
281
283
  const owner = await qualifiedHelperFiles(config, MEMBERS);
282
284
  required(
283
285
  classifierGraphMatches(MEMBERS.map(member => owner.bytes.get(member))),
@@ -288,5 +290,10 @@ export async function canonicalClassifier(config) {
288
290
  typeof module.humanGateVerdict === "function",
289
291
  "canonical hold classifier is unavailable"
290
292
  );
291
- return module.humanGateVerdict;
293
+ return { root: owner.root, verdict: module.humanGateVerdict };
294
+ }
295
+
296
+ /** Current bytes must still match the independently authenticated imported closure. */
297
+ export function canonicalClassifier(config) {
298
+ return classifier(config, () => authenticatedClassifier(config));
292
299
  }
@@ -527,6 +527,94 @@ follow_names=""
527
527
  follow_count=0
528
528
  follow_bytes=0
529
529
  followed_text=""
530
+ followed_rm_text=""
531
+ # Generated from the canonical reviewed supervisor, never from a host manifest.
532
+ # Regenerate through scripts/generate-scratch-supervisor-profile.mjs.
533
+ readonly SCRATCH_SUPERVISOR_SHA256='6fdec1ad0441d7636dc6749c44a23167baf7b75004f156b8a7d4ce1c18c790b7'
534
+ readonly SCRATCH_SUPERVISOR_CLEANUP_COUNT='7'
535
+
536
+ # An authenticated public supervisor owns its fixed cleanup sites. Its original
537
+ # source remains visible to every other policy; its actual payload is followed.
538
+ follow_supervisor() {
539
+ local token="$1" depth="$2" statement="$3" path="" profile="" payload="" rm_source="" original=""
540
+ case "$token" in *lisa-scratch-run.sh) ;; *) return 1 ;; esac
541
+ follow_locate "$token" || follow_refuse "$token" "supervisor source could not be resolved"
542
+ path="$follow_located"
543
+ profile="$(python3 - "$path" "$SCRATCH_SUPERVISOR_SHA256" "$statement" "$token" "$SCRATCH_SUPERVISOR_CLEANUP_COUNT" <<'PY'
544
+ import hashlib
545
+ import json
546
+ import os
547
+ import re
548
+ import shlex
549
+ import stat
550
+ import sys
551
+
552
+ path, digest, statement, operand, expected_count = sys.argv[1:]
553
+ def refuse():
554
+ sys.exit(2)
555
+
556
+ try:
557
+ info = os.lstat(path)
558
+ if not stat.S_ISREG(info.st_mode) or info.st_size > 262144:
559
+ refuse()
560
+ raw = open(path, 'rb').read(262145)
561
+ if len(raw) != info.st_size or not re.fullmatch(r'[0-9a-f]{64}', digest):
562
+ refuse()
563
+ if hashlib.sha256(raw).hexdigest() != digest:
564
+ refuse()
565
+ tokens = shlex.split(statement)
566
+ indices = [index for index, token in enumerate(tokens) if token == operand]
567
+ if len(indices) != 1 or '$' in operand or '`' in operand:
568
+ refuse()
569
+ index = indices[0]
570
+ if index < 1 or tokens[index - 1] not in ('sh', 'bash', '/bin/sh', '/bin/bash'):
571
+ refuse()
572
+ prefix = tokens[:index - 1]
573
+ if prefix[:1] == ['env']:
574
+ prefix = prefix[1:]
575
+ assignments = {}
576
+ for token in prefix:
577
+ if '=' not in token or re.search(r'[$`\r\n]', token):
578
+ refuse()
579
+ key, value = token.split('=', 1)
580
+ if key not in ('LISA_SCRATCH_BASE', 'LISA_SCRATCH_TRACE', 'TMPDIR') or key in assignments:
581
+ refuse()
582
+ assignments[key] = value
583
+ # Match actual shell :- precedence, including inherited and
584
+ # explicitly empty values. TMPDIR cannot override a nonempty inherited base.
585
+ effective = dict(os.environ, **assignments)
586
+ base = effective.get('LISA_SCRATCH_BASE') or effective.get('TMPDIR') or '/tmp'
587
+ if not base or not os.path.isabs(base) or re.search(r'[$`\r\n]', base):
588
+ refuse()
589
+ real_base = os.path.realpath(base)
590
+ roots = ['/tmp', '/var/tmp', os.environ.get('TMPDIR', '/tmp')]
591
+ if not any(os.path.commonpath([real_base, os.path.realpath(root)]) == os.path.realpath(root) for root in roots if root and os.path.isabs(root)):
592
+ refuse()
593
+ args = tokens[index + 1:]
594
+ if len(args) < 4 or args[0] != '--suite' or args[2] != '--':
595
+ refuse()
596
+ if not re.fullmatch(r'[A-Za-z0-9._-]{1,64}', args[1]):
597
+ refuse()
598
+ payload = args[3:]
599
+ if not payload[0] or payload[0].startswith('-') or any(re.search(r'[$`\x00\r\n]', value) for value in payload):
600
+ refuse()
601
+ source = raw.decode('utf8')
602
+ cleaned, count = re.subn(r'(?m)^(\s*)rm -rf "(\$_quarantine|\$_root|\$_entry|\$LISA_SCRATCH_ROOT)"', r'\1:', source)
603
+ if not re.fullmatch(r'[1-9][0-9]*', expected_count) or count != int(expected_count):
604
+ refuse()
605
+ print(json.dumps({'source': cleaned, 'original': source, 'payload': shlex.join(payload)}))
606
+ except (OSError, UnicodeError, ValueError):
607
+ refuse()
608
+ PY
609
+ )" || follow_refuse "$path" "supervisor source or public entry authority could not be authenticated"
610
+ rm_source="$(printf '%s' "$profile" | jq -er '.source')" || follow_refuse "$path" "invalid supervisor profile"
611
+ original="$(printf '%s' "$profile" | jq -er '.original')" || follow_refuse "$path" "invalid supervisor source"
612
+ payload="$(printf '%s' "$profile" | jq -er '.payload')" || follow_refuse "$path" "invalid supervisor payload"
613
+ follow_take "$path" "$depth" "$rm_source" "$original"
614
+ [ "$depth" -lt "$FOLLOW_MAX_DEPTH" ] || follow_refuse "$path" "supervisor payload exceeds inspection depth"
615
+ follow_scan "$payload" "$((depth + 1))"
616
+ return 0
617
+ }
530
618
 
531
619
  FOLLOW_GUIDANCE="This command EXECUTES a file. These guards classify shell text, so the script an
532
620
  invocation runs is read and scanned as part of the command — a destructive line
@@ -813,7 +901,7 @@ follow_locate() {
813
901
  # executes, to a bounded depth. Full-line comments are dropped: a commented line
814
902
  # is never executed, so scanning it can only manufacture a false refusal.
815
903
  follow_take() {
816
- local path="$1" depth="$2" size="" content=""
904
+ local path="$1" depth="$2" size="" content="" rm_content="${3-}"
817
905
  case "$follow_paths" in
818
906
  *$'\n'"$path"$'\n'*) return 0 ;;
819
907
  esac
@@ -833,10 +921,22 @@ follow_take() {
833
921
  if [ "$follow_bytes" -gt "$FOLLOW_MAX_TOTAL_BYTES" ]; then
834
922
  follow_refuse "$path" "the files this command executes total more than the ${FOLLOW_MAX_TOTAL_BYTES}-byte inspection budget"
835
923
  fi
836
- content="$(LC_ALL=C tr -d '\000' <"$path" | sed 's/^[[:space:]]*#.*$//')" \
837
- || follow_refuse "$path" "it could not be read"
924
+ if [ "$#" -eq 4 ]; then
925
+ # Scan the same complete bytes whose digest was authenticated, not a
926
+ # second file read that could pair changed source with the cleanup view.
927
+ content="$(printf '%s' "$4" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "authenticated source could not be read"
928
+ else
929
+ content="$(LC_ALL=C tr -d '\000' <"$path" | sed 's/^[[:space:]]*#.*$//')" \
930
+ || follow_refuse "$path" "it could not be read"
931
+ fi
838
932
  follow_names="$follow_names $path"
839
933
  followed_text="$followed_text"$'\n'"$content"
934
+ if [ "$#" -lt 3 ]; then
935
+ rm_content="$content"
936
+ else
937
+ rm_content="$(printf '%s' "$rm_content" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "supervisor cleanup view could not be read"
938
+ fi
939
+ followed_rm_text="$followed_rm_text"$'\n'"$rm_content"
840
940
  if [ "$depth" -lt "$FOLLOW_MAX_DEPTH" ]; then
841
941
  # A `cd` inside the followed script governs what THAT script goes on to
842
942
  # execute, and nothing after it in the command that ran the script — the
@@ -855,6 +955,11 @@ follow_take() {
855
955
  follow_target() {
856
956
  local token="$1" depth="$2" path=""
857
957
  [ -n "$token" ] || return 0
958
+ # Entry authority belongs to each invocation, not the file-read cache. Only
959
+ # follow_supervisor may admit the exact public interpreter/argv profile.
960
+ case "$token" in
961
+ *lisa-scratch-run.sh) follow_refuse "$token" "supervisor requires its authenticated public interpreter entry" ;;
962
+ esac
858
963
  if ! follow_locate "$token"; then
859
964
  if [ "$depth" -eq 0 ]; then
860
965
  follow_refuse "$token" "no readable file could be resolved for it"
@@ -888,6 +993,9 @@ follow_direct() {
888
993
  return 0
889
994
  fi
890
995
  path="$follow_located"
996
+ case "$path" in
997
+ *lisa-scratch-run.sh) follow_refuse "$path" "supervisor requires its authenticated public interpreter entry" ;;
998
+ esac
891
999
  IFS= read -r -n 256 raw <"$path" 2>/dev/null || true
892
1000
  while [ "$k" -lt "${#raw}" ]; do
893
1001
  char="${raw:k:1}"
@@ -1219,7 +1327,9 @@ follow_scan() {
1219
1327
  fi
1220
1328
  follow_unwrap "${toks[j]}"
1221
1329
  tj="$follow_unwrapped"
1222
- follow_target "$tj" "$depth"
1330
+ if ! follow_supervisor "$tj" "$depth" "$stmt"; then
1331
+ follow_target "$tj" "$depth"
1332
+ fi
1223
1333
  cmd_pos=0
1224
1334
  i=$((j + 1))
1225
1335
  continue
@@ -1248,6 +1358,7 @@ follow_scan() {
1248
1358
  }
1249
1359
 
1250
1360
  follow_scan "$command_for_guards" 0
1361
+ rm_command_for_guards="$command_for_guards"$'\n'"$followed_rm_text"
1251
1362
  if [ -n "$follow_names" ]; then
1252
1363
  command_for_guards="$command_for_guards"$'\n'"$followed_text"
1253
1364
  # A refusal must say WHERE the match is. Without this the operator reads
@@ -1266,6 +1377,8 @@ fi
1266
1377
  # `sed ':a;N;$!ba;…'`, which errors on BSD sed (macOS) and there silently no-ops.
1267
1378
  normalized_command_str="$(printf '%s' "$command_for_guards" \
1268
1379
  | awk '{ if (sub(/\\$/, "")) printf "%s ", $0; else print }')"
1380
+ rm_normalized_command_str="$(printf '%s' "$rm_command_for_guards" \
1381
+ | awk '{ if (sub(/\\$/, "")) printf "%s ", $0; else print }')"
1269
1382
 
1270
1383
  # matches / matches_cs run an ERE against the guarded command text. matches is
1271
1384
  # case-insensitive (the default for these guards); matches_cs is case-SENSITIVE
@@ -1570,7 +1683,7 @@ rm_assignment_value() {
1570
1683
  # small lexer below preserves quoted right-hand sides such as V="/tmp/x"
1571
1684
  # while excluding an argument such as echo "V=/tmp/x". Any ambiguity fails
1572
1685
  # closed.
1573
- RM_ASSIGNMENT_NAME="$name" RM_ASSIGNMENT_COMMAND="$normalized_command_str" \
1686
+ RM_ASSIGNMENT_NAME="$name" RM_ASSIGNMENT_COMMAND="$rm_normalized_command_str" \
1574
1687
  RM_ASSIGNMENT_STATEMENT="$rm_assignment_stmt" \
1575
1688
  python3 - <<'PY'
1576
1689
  import os
@@ -1769,7 +1882,7 @@ classify_rm_target() {
1769
1882
  # process substitution cannot deny anything either — it ends that subshell, not
1770
1883
  # this hook.
1771
1884
  rm_segments_status=0
1772
- rm_segments="$(printf '%s' "$normalized_command_str" | tr '&|;' '\n' \
1885
+ rm_segments="$(printf '%s' "$rm_normalized_command_str" | tr '&|;' '\n' \
1773
1886
  | grep -Ei -- "$RM_CMD"'([[:space:]]|$)')" || rm_segments_status=$?
1774
1887
  case "$rm_segments_status" in
1775
1888
  0 | 1) ;;
@@ -2281,9 +2394,10 @@ git_segments="$(printf '%s' "$normalized_command_str" \
2281
2394
  -e 's/[0-9]*>&[0-9-]*/>/g' \
2282
2395
  -e 's/[0-9]*<&[0-9-]*/</g' \
2283
2396
  -e 's/||/;/g' \
2284
- | tr '&;' '\n')" || git_segments_status=$?
2397
+ | tr '&;' '\n' \
2398
+ | grep -E -- "$RM_RF_CLUSTER|$RM_RF_SPLIT")" || git_segments_status=$?
2285
2399
  case "$git_segments_status" in
2286
- 0) ;;
2400
+ 0 | 1) ;;
2287
2401
  *) scan_failed "$git_segments_status" ;;
2288
2402
  esac
2289
2403
  while IFS= read -r git_stmt; do
@@ -280,6 +280,14 @@
280
280
  "dynamicImports": [],
281
281
  "children": []
282
282
  },
283
+ "lib/npm-update-classifier-cache.mjs": {
284
+ "sha256": "92272e40617ca9d620ff47e3ffe07a0e5bc1a3949d139752aa2675398ab49f9b",
285
+ "staticImports": [
286
+ "lib/npm-update-contract.mjs"
287
+ ],
288
+ "dynamicImports": [],
289
+ "children": []
290
+ },
283
291
  "lib/npm-update-contract.mjs": {
284
292
  "sha256": "6905dd9d5747702ab502b66bcb38b6d4dcb30127f02466d29b3905f53c27981d",
285
293
  "staticImports": [
@@ -472,14 +480,15 @@
472
480
  "children": []
473
481
  },
474
482
  "lib/npm-update-helper-inventory.mjs": {
475
- "sha256": "33ab2fcba24552a378a75314fe021b25ae6862a6e3c7c4f6b83250f9749264bb",
483
+ "sha256": "3561c20458d74eff34650d6b4366f4f03ed4e7825ae886ea78394f63f98a18bb",
476
484
  "staticImports": [],
477
485
  "dynamicImports": [],
478
486
  "children": []
479
487
  },
480
488
  "lib/npm-update-helper.mjs": {
481
- "sha256": "f4012383184237aa0a7ca21847864fdcc2241f94fd3d2cd6d82a6345763e5824",
489
+ "sha256": "41e5a37769ec3737829ed1948d0a1442792b25d120f0fb4f4b5e424a50f34705",
482
490
  "staticImports": [
491
+ "lib/npm-update-classifier-cache.mjs",
483
492
  "lib/npm-update-contract.mjs",
484
493
  "lib/npm-update-helper-graph.mjs",
485
494
  "lib/npm-update-helper-inventory.mjs",
@@ -1076,7 +1085,7 @@
1076
1085
  ]
1077
1086
  },
1078
1087
  "lisa-scratch-run.sh": {
1079
- "sha256": "ef3c38f8d1d2f48ba265e21f841310d25142228dac57b0a5742c7d902113ca9a",
1088
+ "sha256": "6fdec1ad0441d7636dc6749c44a23167baf7b75004f156b8a7d4ce1c18c790b7",
1080
1089
  "staticImports": [],
1081
1090
  "dynamicImports": [],
1082
1091
  "children": []
@@ -0,0 +1,86 @@
1
+ /** Confine refresh IO and rollback to held native file identities. */
2
+ import { type BigIntStats } from "node:fs";
3
+ import { type FileHandle } from "node:fs/promises";
4
+ /** Retained object; pathname replacement never supplies write authority. */
5
+ export interface Pin {
6
+ readonly filename: string;
7
+ readonly handle: FileHandle;
8
+ readonly identity: BigIntStats;
9
+ }
10
+ /**
11
+ * Require the pathname still selects its retained object.
12
+ * @param pin - Original native object.
13
+ */
14
+ export declare function checkPath(pin: Pin): Promise<void>;
15
+ /**
16
+ * Read a bounded regular descriptor, detecting growth or torn observations.
17
+ * @param pin - Retained file.
18
+ * @returns Complete bytes.
19
+ */
20
+ export declare function bytes(pin: Pin): Promise<Buffer>;
21
+ /** Bounded original bytes and acknowledged writes on one retained destination. */
22
+ export declare class HookPair {
23
+ readonly source: Pin;
24
+ readonly host: Pin;
25
+ readonly sourceBytes: Buffer;
26
+ readonly hostBytes: Buffer;
27
+ private expected;
28
+ private expectedMode;
29
+ /**
30
+ * Keep source and destination authority for one operation.
31
+ * @param source - Read-only package descriptor.
32
+ * @param host - Original writable host descriptor.
33
+ * @param sourceBytes - Authenticated release bytes.
34
+ * @param hostBytes - Bounded original backup.
35
+ */
36
+ constructor(source: Pin, host: Pin, sourceBytes: Buffer, hostBytes: Buffer);
37
+ /**
38
+ * Write only the held inode; verify each acknowledged write before truncation.
39
+ * @param content - Complete replacement or backup bytes.
40
+ */
41
+ write(content: Buffer): Promise<void>;
42
+ /** Reject shared inodes or concurrent bytes before any further mutation. */
43
+ private assertOwnWrites;
44
+ /**
45
+ * Change permissions only on the retained inode containing our writes.
46
+ * @param mode - Original authenticated source or backup permissions.
47
+ */
48
+ chmod(mode: number): Promise<void>;
49
+ /** Restore only this original inode while it contains our acknowledged writes. */
50
+ restore(): Promise<void>;
51
+ }
52
+ /** Operation-local native resources and error accumulation, always finally-closed. */
53
+ export declare class HookFiles {
54
+ private readonly pins;
55
+ private readonly changed;
56
+ private readonly failures;
57
+ /**
58
+ * Retain a no-follow object before subsequent validation can fail.
59
+ * @param filename - Confined absolute path.
60
+ * @param flags - Native access flags.
61
+ * @returns Original object.
62
+ */
63
+ retain(filename: string, flags: number): Promise<Pin>;
64
+ /**
65
+ * Hold every closed directory component including the resolved root.
66
+ * @param root - Canonical package or host root.
67
+ * @param relative - Closed directory suffix.
68
+ * @returns Pinned ancestors.
69
+ */
70
+ directories(root: string, relative: string): Promise<Pin[]>;
71
+ /**
72
+ * Register a backup immediately before the first possible write.
73
+ * @param pair - Original descriptor and bytes.
74
+ */
75
+ mark(pair: HookPair): void;
76
+ /**
77
+ * Preserve the original failure and attempt every descriptor-bound rollback.
78
+ * @param original - Original refresh failure.
79
+ */
80
+ rollback(original: unknown): Promise<void>;
81
+ /** Close every retained handle even after preflight, write or rollback errors. */
82
+ close(): Promise<void>;
83
+ /** Surface all native failures, preserving the original error object first. */
84
+ assertSuccess(): void;
85
+ }
86
+ //# sourceMappingURL=hook-refresh-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-refresh-files.d.ts","sourceRoot":"","sources":["../../src/cli/hook-refresh-files.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKhE,4EAA4E;AAC5E,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;CAChC;AAYD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvD;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BrD;AAED,kFAAkF;AAClF,qBAAa,QAAQ;IAYjB,QAAQ,CAAC,MAAM,EAAE,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM;IAd5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAE7B;;;;;;OAMG;gBAEQ,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,GAAG,EACT,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM;IAM5B;;;OAGG;IACG,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B3C,4EAA4E;YAC9D,eAAe;IAY7B;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxC,kFAAkF;IAC5E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAO/B;AAED,sFAAsF;AACtF,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiB;IAE1C;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAe3D;;;;;OAKG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqBjE;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI1B;;;OAGG;IACG,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,kFAAkF;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAU5B,+EAA+E;IAC/E,aAAa,IAAI,IAAI;CAQtB"}