@codyswann/lisa 4.65.8 → 4.66.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.
Files changed (64) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs +34 -10
  2. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  3. package/dist/core/lisa-owned-hash-ledger.js +8 -0
  4. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  5. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  6. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  7. package/dist/core/upstream-evidence-manifest.js +5 -3
  8. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  9. package/expo/create-only/.github/workflows/maestro-e2e.yml +6 -0
  10. package/package.json +4 -4
  11. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  13. package/plugins/lisa/hooks/worktree-binding-guard.mjs +34 -10
  14. package/plugins/lisa-agy/plugin.json +1 -1
  15. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  18. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +34 -10
  22. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +34 -10
  24. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  26. package/plugins/lisa-expo-agy/plugin.json +1 -1
  27. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  36. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  37. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  42. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  47. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-rails-agy/plugin.json +1 -1
  52. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  57. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  62. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/src/base/hooks/worktree-binding-guard.mjs +34 -10
@@ -112,6 +112,12 @@ jobs:
112
112
  # MAESTRO_TEST_PHONE=0000000000
113
113
  # MAESTRO_TEST_OTP=555555
114
114
  #
115
+ # MAESTRO_RUN_ID namespaces the throwaway accounts and uploads a run
116
+ # mints (convention: tester+<runId><ordinal>) so a sweep can delete
117
+ # exactly one run's state. It defaults to the run's own id; uncomment to
118
+ # pin a shared namespace across invocations you will sweep together.
119
+ # run_id: 'my-sweep-namespace'
120
+ #
115
121
  # Once per run, before either platform starts — resets, sweeps, seeding.
116
122
  # pre_suite_command: |
117
123
  # node scripts/maestro/prune-e2e-fixtures.mjs
package/package.json CHANGED
@@ -187,7 +187,7 @@
187
187
  "zod-validation-error": "^4.0.0"
188
188
  },
189
189
  "name": "@codyswann/lisa",
190
- "version": "4.65.8",
190
+ "version": "4.66.0",
191
191
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
192
192
  "main": "dist/index.js",
193
193
  "exports": {
@@ -336,7 +336,7 @@
336
336
  "test": "tests"
337
337
  },
338
338
  "types": "./dist/index.d.ts",
339
- "lisaReleaseCommit": "dd0cd9d7119aa0d6ed10d1695cfdb9da488ffd7e",
340
- "gitHead": "dd0cd9d7119aa0d6ed10d1695cfdb9da488ffd7e",
341
- "lisaReleaseTag": "v4.65.8"
339
+ "lisaReleaseCommit": "719eb2d8b19f05e8f6dab62790533cfba50c1f32",
340
+ "gitHead": "719eb2d8b19f05e8f6dab62790533cfba50c1f32",
341
+ "lisaReleaseTag": "v4.66.0"
342
342
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -506,9 +506,32 @@ function reachesOut(script, foreign, bound) {
506
506
  ]);
507
507
  }
508
508
 
509
- function stateFile(sessionId) {
509
+ function stateFile(bindingKey) {
510
510
  const home = process.env.LISA_STATE_HOME || join(homedir(), ".lisa");
511
- return join(home, "worktree-binding", `${sessionId}.json`);
511
+ return join(home, "worktree-binding", `${bindingKey}.json`);
512
+ }
513
+
514
+ /**
515
+ * The identity a binding belongs to.
516
+ *
517
+ * Parallel subagents share the session's `session_id`, so keying the binding
518
+ * on it alone made each subagent's guarded call read the binding a sibling
519
+ * recorded — the sibling's worktree then read as "changed underneath it" and
520
+ * its writes were refused, or an acknowledgement rebound the file out from
521
+ * under the sibling (CodySwannGT/lisa#4277). The payload's `agent_id` is what
522
+ * distinguishes a subagent from the main agent, so a caller carrying one gets
523
+ * its own state file. The main agent carries no `agent_id` and keeps the
524
+ * session-scoped key it always had, which is also the fallback for runtimes
525
+ * that never send the field.
526
+ *
527
+ * The key lands in a filename, so the composite is folded into a set that is
528
+ * legal on every filesystem — `:` is not, which rules it out as a separator
529
+ * and as tolerated session_id content on Windows hosts.
530
+ */
531
+ function bindingKey(payload) {
532
+ const agent = payload?.agent_id;
533
+ if (typeof agent !== "string" || !agent) return payload.session_id;
534
+ return `${payload.session_id}--${agent}`.replaceAll(/[^A-Za-z0-9._-]/g, "_");
512
535
  }
513
536
 
514
537
  function readState(sessionId) {
@@ -640,8 +663,8 @@ function recordBaseline(sessionId, observed) {
640
663
  function recordClaim(payload, observed) {
641
664
  const claimed = claimedRoot(payload.tool_input, payload.cwd);
642
665
  if (!claimed || claimed === observed) return;
643
- const previous = readState(payload.session_id);
644
- writeState(payload.session_id, {
666
+ const previous = readState(bindingKey(payload));
667
+ writeState(bindingKey(payload), {
645
668
  boundRoot: previous?.boundRoot ?? observed,
646
669
  claimedRoot: claimed,
647
670
  updatedAt: new Date().toISOString(),
@@ -681,7 +704,7 @@ function handleAcceptance(payload, observed) {
681
704
  acceptanceLine(observed),
682
705
  ]);
683
706
  }
684
- writeState(payload.session_id, {
707
+ writeState(bindingKey(payload), {
685
708
  boundRoot: observed,
686
709
  claimedRoot: null,
687
710
  updatedAt: new Date().toISOString(),
@@ -883,15 +906,16 @@ function evaluate(payload) {
883
906
  say("payload carries no session id or cwd; binding is NOT enforced");
884
907
  return 0;
885
908
  }
909
+ const binding = bindingKey(payload);
886
910
  const observed = worktreeRoot(cwd);
887
911
  if (!observed) return 0;
888
912
 
889
913
  if (payload.hook_event_name === "SessionStart") {
890
- recordBaseline(sessionId, observed);
914
+ recordBaseline(binding, observed);
891
915
  // After the baseline, never before: `recordBaseline` writes a fresh state
892
916
  // for a session it has not seen, and the notice's record has to go on top
893
917
  // of that write rather than under it.
894
- noticeRuntimeDrift(sessionId, process.env);
918
+ noticeRuntimeDrift(binding, process.env);
895
919
  return 0;
896
920
  }
897
921
  if (payload.tool_name === "EnterWorktree") {
@@ -903,7 +927,7 @@ function evaluate(payload) {
903
927
  const accepted = handleAcceptance(payload, observed);
904
928
  if (accepted !== null) return accepted;
905
929
 
906
- const state = readState(sessionId);
930
+ const state = readState(binding);
907
931
  if (!state?.boundRoot) {
908
932
  // No baseline means SessionStart did not run for this session — an older
909
933
  // install, a runtime that fires no such event, a harness that skips it.
@@ -912,7 +936,7 @@ function evaluate(payload) {
912
936
  // refusing. Treating it as a refusal would turn a floor into a wall on
913
937
  // every surface where the event does not fire, and a wall gets switched
914
938
  // off, which costs the whole guard.
915
- writeState(sessionId, {
939
+ writeState(binding, {
916
940
  boundRoot: observed,
917
941
  claimedRoot: null,
918
942
  updatedAt: new Date().toISOString(),
@@ -923,7 +947,7 @@ function evaluate(payload) {
923
947
  return unconfirmedSwitch(state.claimedRoot, observed);
924
948
  }
925
949
  if (state.claimedRoot === observed) {
926
- writeState(sessionId, {
950
+ writeState(binding, {
927
951
  boundRoot: observed,
928
952
  claimedRoot: null,
929
953
  updatedAt: new Date().toISOString(),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -506,9 +506,32 @@ function reachesOut(script, foreign, bound) {
506
506
  ]);
507
507
  }
508
508
 
509
- function stateFile(sessionId) {
509
+ function stateFile(bindingKey) {
510
510
  const home = process.env.LISA_STATE_HOME || join(homedir(), ".lisa");
511
- return join(home, "worktree-binding", `${sessionId}.json`);
511
+ return join(home, "worktree-binding", `${bindingKey}.json`);
512
+ }
513
+
514
+ /**
515
+ * The identity a binding belongs to.
516
+ *
517
+ * Parallel subagents share the session's `session_id`, so keying the binding
518
+ * on it alone made each subagent's guarded call read the binding a sibling
519
+ * recorded — the sibling's worktree then read as "changed underneath it" and
520
+ * its writes were refused, or an acknowledgement rebound the file out from
521
+ * under the sibling (CodySwannGT/lisa#4277). The payload's `agent_id` is what
522
+ * distinguishes a subagent from the main agent, so a caller carrying one gets
523
+ * its own state file. The main agent carries no `agent_id` and keeps the
524
+ * session-scoped key it always had, which is also the fallback for runtimes
525
+ * that never send the field.
526
+ *
527
+ * The key lands in a filename, so the composite is folded into a set that is
528
+ * legal on every filesystem — `:` is not, which rules it out as a separator
529
+ * and as tolerated session_id content on Windows hosts.
530
+ */
531
+ function bindingKey(payload) {
532
+ const agent = payload?.agent_id;
533
+ if (typeof agent !== "string" || !agent) return payload.session_id;
534
+ return `${payload.session_id}--${agent}`.replaceAll(/[^A-Za-z0-9._-]/g, "_");
512
535
  }
513
536
 
514
537
  function readState(sessionId) {
@@ -640,8 +663,8 @@ function recordBaseline(sessionId, observed) {
640
663
  function recordClaim(payload, observed) {
641
664
  const claimed = claimedRoot(payload.tool_input, payload.cwd);
642
665
  if (!claimed || claimed === observed) return;
643
- const previous = readState(payload.session_id);
644
- writeState(payload.session_id, {
666
+ const previous = readState(bindingKey(payload));
667
+ writeState(bindingKey(payload), {
645
668
  boundRoot: previous?.boundRoot ?? observed,
646
669
  claimedRoot: claimed,
647
670
  updatedAt: new Date().toISOString(),
@@ -681,7 +704,7 @@ function handleAcceptance(payload, observed) {
681
704
  acceptanceLine(observed),
682
705
  ]);
683
706
  }
684
- writeState(payload.session_id, {
707
+ writeState(bindingKey(payload), {
685
708
  boundRoot: observed,
686
709
  claimedRoot: null,
687
710
  updatedAt: new Date().toISOString(),
@@ -883,15 +906,16 @@ function evaluate(payload) {
883
906
  say("payload carries no session id or cwd; binding is NOT enforced");
884
907
  return 0;
885
908
  }
909
+ const binding = bindingKey(payload);
886
910
  const observed = worktreeRoot(cwd);
887
911
  if (!observed) return 0;
888
912
 
889
913
  if (payload.hook_event_name === "SessionStart") {
890
- recordBaseline(sessionId, observed);
914
+ recordBaseline(binding, observed);
891
915
  // After the baseline, never before: `recordBaseline` writes a fresh state
892
916
  // for a session it has not seen, and the notice's record has to go on top
893
917
  // of that write rather than under it.
894
- noticeRuntimeDrift(sessionId, process.env);
918
+ noticeRuntimeDrift(binding, process.env);
895
919
  return 0;
896
920
  }
897
921
  if (payload.tool_name === "EnterWorktree") {
@@ -903,7 +927,7 @@ function evaluate(payload) {
903
927
  const accepted = handleAcceptance(payload, observed);
904
928
  if (accepted !== null) return accepted;
905
929
 
906
- const state = readState(sessionId);
930
+ const state = readState(binding);
907
931
  if (!state?.boundRoot) {
908
932
  // No baseline means SessionStart did not run for this session — an older
909
933
  // install, a runtime that fires no such event, a harness that skips it.
@@ -912,7 +936,7 @@ function evaluate(payload) {
912
936
  // refusing. Treating it as a refusal would turn a floor into a wall on
913
937
  // every surface where the event does not fire, and a wall gets switched
914
938
  // off, which costs the whole guard.
915
- writeState(sessionId, {
939
+ writeState(binding, {
916
940
  boundRoot: observed,
917
941
  claimedRoot: null,
918
942
  updatedAt: new Date().toISOString(),
@@ -923,7 +947,7 @@ function evaluate(payload) {
923
947
  return unconfirmedSwitch(state.claimedRoot, observed);
924
948
  }
925
949
  if (state.claimedRoot === observed) {
926
- writeState(sessionId, {
950
+ writeState(binding, {
927
951
  boundRoot: observed,
928
952
  claimedRoot: null,
929
953
  updatedAt: new Date().toISOString(),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -506,9 +506,32 @@ function reachesOut(script, foreign, bound) {
506
506
  ]);
507
507
  }
508
508
 
509
- function stateFile(sessionId) {
509
+ function stateFile(bindingKey) {
510
510
  const home = process.env.LISA_STATE_HOME || join(homedir(), ".lisa");
511
- return join(home, "worktree-binding", `${sessionId}.json`);
511
+ return join(home, "worktree-binding", `${bindingKey}.json`);
512
+ }
513
+
514
+ /**
515
+ * The identity a binding belongs to.
516
+ *
517
+ * Parallel subagents share the session's `session_id`, so keying the binding
518
+ * on it alone made each subagent's guarded call read the binding a sibling
519
+ * recorded — the sibling's worktree then read as "changed underneath it" and
520
+ * its writes were refused, or an acknowledgement rebound the file out from
521
+ * under the sibling (CodySwannGT/lisa#4277). The payload's `agent_id` is what
522
+ * distinguishes a subagent from the main agent, so a caller carrying one gets
523
+ * its own state file. The main agent carries no `agent_id` and keeps the
524
+ * session-scoped key it always had, which is also the fallback for runtimes
525
+ * that never send the field.
526
+ *
527
+ * The key lands in a filename, so the composite is folded into a set that is
528
+ * legal on every filesystem — `:` is not, which rules it out as a separator
529
+ * and as tolerated session_id content on Windows hosts.
530
+ */
531
+ function bindingKey(payload) {
532
+ const agent = payload?.agent_id;
533
+ if (typeof agent !== "string" || !agent) return payload.session_id;
534
+ return `${payload.session_id}--${agent}`.replaceAll(/[^A-Za-z0-9._-]/g, "_");
512
535
  }
513
536
 
514
537
  function readState(sessionId) {
@@ -640,8 +663,8 @@ function recordBaseline(sessionId, observed) {
640
663
  function recordClaim(payload, observed) {
641
664
  const claimed = claimedRoot(payload.tool_input, payload.cwd);
642
665
  if (!claimed || claimed === observed) return;
643
- const previous = readState(payload.session_id);
644
- writeState(payload.session_id, {
666
+ const previous = readState(bindingKey(payload));
667
+ writeState(bindingKey(payload), {
645
668
  boundRoot: previous?.boundRoot ?? observed,
646
669
  claimedRoot: claimed,
647
670
  updatedAt: new Date().toISOString(),
@@ -681,7 +704,7 @@ function handleAcceptance(payload, observed) {
681
704
  acceptanceLine(observed),
682
705
  ]);
683
706
  }
684
- writeState(payload.session_id, {
707
+ writeState(bindingKey(payload), {
685
708
  boundRoot: observed,
686
709
  claimedRoot: null,
687
710
  updatedAt: new Date().toISOString(),
@@ -883,15 +906,16 @@ function evaluate(payload) {
883
906
  say("payload carries no session id or cwd; binding is NOT enforced");
884
907
  return 0;
885
908
  }
909
+ const binding = bindingKey(payload);
886
910
  const observed = worktreeRoot(cwd);
887
911
  if (!observed) return 0;
888
912
 
889
913
  if (payload.hook_event_name === "SessionStart") {
890
- recordBaseline(sessionId, observed);
914
+ recordBaseline(binding, observed);
891
915
  // After the baseline, never before: `recordBaseline` writes a fresh state
892
916
  // for a session it has not seen, and the notice's record has to go on top
893
917
  // of that write rather than under it.
894
- noticeRuntimeDrift(sessionId, process.env);
918
+ noticeRuntimeDrift(binding, process.env);
895
919
  return 0;
896
920
  }
897
921
  if (payload.tool_name === "EnterWorktree") {
@@ -903,7 +927,7 @@ function evaluate(payload) {
903
927
  const accepted = handleAcceptance(payload, observed);
904
928
  if (accepted !== null) return accepted;
905
929
 
906
- const state = readState(sessionId);
930
+ const state = readState(binding);
907
931
  if (!state?.boundRoot) {
908
932
  // No baseline means SessionStart did not run for this session — an older
909
933
  // install, a runtime that fires no such event, a harness that skips it.
@@ -912,7 +936,7 @@ function evaluate(payload) {
912
936
  // refusing. Treating it as a refusal would turn a floor into a wall on
913
937
  // every surface where the event does not fire, and a wall gets switched
914
938
  // off, which costs the whole guard.
915
- writeState(sessionId, {
939
+ writeState(binding, {
916
940
  boundRoot: observed,
917
941
  claimedRoot: null,
918
942
  updatedAt: new Date().toISOString(),
@@ -923,7 +947,7 @@ function evaluate(payload) {
923
947
  return unconfirmedSwitch(state.claimedRoot, observed);
924
948
  }
925
949
  if (state.claimedRoot === observed) {
926
- writeState(sessionId, {
950
+ writeState(binding, {
927
951
  boundRoot: observed,
928
952
  claimedRoot: null,
929
953
  updatedAt: new Date().toISOString(),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.65.8",
3
+ "version": "4.66.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"