@codyswann/lisa 4.33.12 → 4.33.14

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 (89) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +6 -2
  2. package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +53 -4
  3. package/dist/configs/vitest/scratch-setup.d.ts +34 -0
  4. package/dist/configs/vitest/scratch-setup.d.ts.map +1 -1
  5. package/dist/configs/vitest/scratch-setup.js +89 -10
  6. package/dist/configs/vitest/scratch-setup.js.map +1 -1
  7. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  8. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  9. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  10. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  11. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  12. package/dist/core/upstream-evidence-manifest.js +12 -8
  13. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  14. package/dist/opencode/plugin-templates/parity-safety-net.sh +53 -4
  15. package/package.json +4 -4
  16. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  19. package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +17 -6
  20. package/plugins/lisa/hooks/block-direct-issue-create.sh +6 -2
  21. package/plugins/lisa/hooks/parity-safety-net.sh +53 -4
  22. package/plugins/lisa/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  23. package/plugins/lisa/skills/lisa-track/SKILL.md +18 -7
  24. package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +6 -2
  25. package/plugins/lisa-agy/hooks/parity-safety-net.sh +53 -4
  26. package/plugins/lisa-agy/plugin.json +1 -1
  27. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  28. package/plugins/lisa-agy/skills/lisa-track/SKILL.md +18 -7
  29. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  31. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  32. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-copilot/hooks/block-direct-issue-create.sh +6 -2
  36. package/plugins/lisa-copilot/hooks/parity-safety-net.sh +53 -4
  37. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  38. package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +18 -7
  39. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +6 -2
  41. package/plugins/lisa-cursor/hooks/parity-safety-net.sh +53 -4
  42. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  43. package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +18 -7
  44. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-expo-agy/plugin.json +1 -1
  47. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  51. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  52. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  56. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  57. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  61. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  62. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  67. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  71. package/plugins/lisa-rails-agy/plugin.json +1 -1
  72. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  77. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  82. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  84. package/plugins/src/base/hooks/block-direct-issue-create.sh +6 -2
  85. package/plugins/src/base/hooks/parity-safety-net.sh +53 -4
  86. package/plugins/src/base/skills/lisa-setup-workstation/scripts/catalogue.mjs +29 -0
  87. package/plugins/src/base/skills/lisa-track/SKILL.md +18 -7
  88. package/typescript/copy-contents/.husky/pre-commit +10 -6
  89. package/typescript/copy-contents/.husky/pre-push +46 -16
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-track
3
- description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
3
+ description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Also accepts human_gate: <why a human must judge this first>, which files the leaf held — outside the build-ready lane, never claimed — instead of build-ready. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -17,13 +17,14 @@ This flow must return exactly one canonical `(tracker_provider, work_item_ref)`
17
17
  - an explicit reference matching the configured tracker (`KEY-123`, `org/repo#123` or issue URL, Linear team identifier);
18
18
  - an existing file path containing a specification (read the entire file, without offset/limit);
19
19
  - plain-text work description.
20
- 3. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
20
+ 3. Detect the caller's readiness declaration, which is orthogonal to the classification above. Absent any declaration this is the default build-ready path. A caller deliberately holding the work for a pending human product call passes `human_gate: "<why a human must judge this first>"`; the two declarations are mutually exclusive and a `human_gate` present but empty is a blocking error, because an unexplained hold is indistinguishable from an accident.
21
+ 4. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
21
22
 
22
23
  ## Phase 2 — Resolve one live work item
23
24
 
24
25
  ### Explicit reference
25
26
 
26
- Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. This live read is mandatory even if caller context already includes ticket text.
27
+ Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. Read the returned body for the hold marker: an item carrying `[lisa-human-gate]` is held and Phase 3 refuses to claim it, whatever role or labels it carries. This live read is mandatory even if caller context already includes ticket text.
27
28
 
28
29
  ### File or plain text
29
30
 
@@ -34,15 +35,21 @@ Search conservatively before creating:
34
35
  - GitHub: `gh issue list --repo <org>/<repo> --state open --search "<keywords> in:title,body"`.
35
36
  - Jira: `lisa-atlassian-access operation: search-issues` with project-scoped JQL.
36
37
  - Linear: `lisa-linear-access operation: list-issues` scoped to the configured team/workspace.
37
- 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, cross-repo, and materially different outcomes.
38
+ 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, held-by-gate, cross-repo, and materially different outcomes. A held candidate is never reused: attaching new work to an item a human is holding is how the hold gets spent.
38
39
  4. Reuse only when **exactly one** live leaf is a high-confidence semantic match for the same requested outcome and repository. A shared keyword or similar title is not enough. Record the search queries, candidates, and rejection reasons in the returned resolution evidence.
39
- 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true`. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
+ 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true` unless the caller declared a `human_gate`, in which case pass that `human_gate: "<why>"` reason instead of `build_ready: true` and never alongside it, so the writer leaves the leaf in the tracker's default backlog role — out of the lane build-intake claims from — and stamps the hold on the body as a `[lisa-human-gate]` marker. Discarding a declared gate and filing build-ready anyway is the one outcome this step must never produce. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
41
  6. Live-read the writer's canonical returned reference with `lisa-tracker-read`. A create response without a verified live leaf is failure.
41
42
 
42
43
  This is intentionally conservative: ambiguity creates one explicit work item instead of silently attaching work to the wrong existing item. Across the entire invocation, at most one new work item may be created.
43
44
 
44
45
  ## Phase 3 — Claim and bind
45
46
 
47
+ The work item is **held** when either of two independent signals is present: the caller declared a `human_gate`, or the resolved item's body contains the literal `[lisa-human-gate]` marker. Test the marker as a plain substring and never key the parse on a `reason=` value — markers are written both with and without one, and a parser that requires the key misses the keyless ones while appearing to work.
48
+
49
+ A held item stops the flow here, whatever the resolution outcome and whatever roles or labels the item carries. The marker is the authority: an item can be held while sitting in the build-ready role with no blocked or human-needed label, because a human stamping a hold on live work is the most likely way a gate is ever applied, and the vendor claim contracts reject a closed item or an active blocker but not a marked one. Do not invoke `lisa-tracker-claim`, do not write the worktree binding, and do not begin durable project work. Claiming a held leaf binds a live lane to it and makes it look already-attended, which suppresses exactly the human attention it was filed to attract. Return the structured result below with `claim_outcome: held-by-gate` and `binding_outcome: skipped-human-gate`, name which signal held it in `held_by` and carry the reason verbatim in `gate_reason` alongside the canonical reference, then stop. A marker written without a `reason=` key yields `gate_reason: null` — a missing reason narrows what the result can say, never whether the item is held.
50
+
51
+ Otherwise:
52
+
46
53
  1. Invoke `lisa-tracker-claim <canonical-ref>`. Require its post-read verified `claim_outcome: claimed|reused`; no binding may be written after a failed/unverified claim.
47
54
  2. Persist only the canonical reference in worktree-local machine state:
48
55
 
@@ -58,8 +65,11 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
58
65
  tracker_provider: jira|github|linear
59
66
  work_item_ref: <canonical-ref>
60
67
  resolution_outcome: explicit|reused|created
61
- claim_outcome: claimed|reused
62
- binding_outcome: verified
68
+ readiness_declaration: build-ready|human-gate
69
+ held_by: none|caller-declaration|item-marker
70
+ gate_reason: <why a human must judge this first>|null
71
+ claim_outcome: claimed|reused|held-by-gate
72
+ binding_outcome: verified|skipped-human-gate
63
73
  ```
64
74
 
65
75
  ## Lifecycle
@@ -68,4 +78,5 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
68
78
  - Branch setup may call `node scripts/lisa-work-item.mjs attach-branch` after the feature branch exists.
69
79
  - Keep the binding across ordinary interruptions and blocked outcomes so resumed work remains attributable.
70
80
  - Clear it only after true terminal completion — merged, deployed/verified where required, tracker evidence/backlink complete, and the work item terminal — by running `node scripts/lisa-work-item.mjs clear` and verifying no current binding remains.
81
+ - A held filing writes no binding at all, so there is nothing to clear. The gate is released by a human, not by this skill: a human removes the `[lisa-human-gate]` marker and flips the leaf into the build-ready role, and a later invocation then resolves it on the ordinary path.
71
82
  - A tracker outage, invalid item, failed claim, or failed binding blocks durable work. Never continue untracked and never ask a Git hook to create the item.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -269,8 +269,12 @@ FILE IT THE SANCTIONED WAY — one of these two, always explicit:
269
269
  bar — reproduction, observed-versus-expected, Gherkin acceptance criteria.
270
270
 
271
271
  2. A human product call is genuinely pending. Route the same way but pass
272
- \`human_gate: "<why a human must judge this first>"\`, which stamps the hold
273
- so it is auditable rather than indistinguishable from an accident:
272
+ \`human_gate: "<why a human must judge this first>"\` in place of
273
+ \`build_ready: true\` — the two are mutually exclusive. The leaf is filed
274
+ into the default backlog role, outside the lane build-intake claims from,
275
+ and is deliberately NOT claimed, so it keeps attracting the human attention
276
+ it was filed for. The hold is stamped so it is auditable rather than
277
+ indistinguishable from an accident:
274
278
 
275
279
  Held for a human product call: <reason>.
276
280
  <!-- [lisa-human-gate] reason=<short-slug> -->
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -121,6 +121,20 @@ export const PROVIDERS = [
121
121
  sha256:
122
122
  "18253757286e119d450133a87eb463bf8c1ce418ce24c834f4f250d60cba6f9e",
123
123
  },
124
+ // Bitwarden publishes macOS archives for the same tag, and omitting them
125
+ // made the one genuinely pinned credential CLI the one a macOS operator
126
+ // had to install by hand — the unverified path, for the tool where an
127
+ // unverified download matters most.
128
+ "darwin-arm64": {
129
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-aarch64-apple-darwin-2.1.0.zip",
130
+ sha256:
131
+ "9cb1c1c6e6164d83b2e339883ba02b4cbb37188ce9a484b1ce8249443163e066",
132
+ },
133
+ "darwin-x64": {
134
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-x86_64-apple-darwin-2.1.0.zip",
135
+ sha256:
136
+ "6f626b3971368902af1b9847c02791a1b4666969d7561e2047681cded7997537",
137
+ },
124
138
  },
125
139
  },
126
140
  {
@@ -197,11 +211,26 @@ export const TOOLS = [
197
211
  "12311e320d4cfdb54d7fa2d58cd1e3a2ccb4c12e1c3abb32b0a2e48bd0f991bf",
198
212
  binary: "gh_2.83.0_linux_arm64/bin/gh",
199
213
  },
214
+ // `install` is per-block for gh and only for gh: the vendor ships a
215
+ // .tar.gz for Linux and a .zip for macOS, so the entry-level `release-tar`
216
+ // is wrong for exactly these two. Without the override the resolved entry
217
+ // keeps `release-tar` and the installer runs `tar -xzf` over a ZIP, which
218
+ // fails during extraction — a macOS pin that resolves and cannot install.
200
219
  "darwin-arm64": {
201
220
  url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_macOS_arm64.zip",
202
221
  sha256:
203
222
  "fecba907bc361d5e33620dbf1145f11432c39fb2b388a839463cfbb89a84820b",
204
223
  binary: "gh_2.83.0_macOS_arm64/bin/gh",
224
+ install: "release-zip",
225
+ },
226
+ // Intel Macs are still in service, and an Apple Silicon-only macOS block
227
+ // reads as "macOS is covered" while leaving half of it uncovered.
228
+ "darwin-x64": {
229
+ url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_macOS_amd64.zip",
230
+ sha256:
231
+ "0c0de650752bb92d7283e386cafd03d9ac5f47028c648c4ab821ef08a75c0716",
232
+ binary: "gh_2.83.0_macOS_amd64/bin/gh",
233
+ install: "release-zip",
205
234
  },
206
235
  },
207
236
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-track
3
- description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
3
+ description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Also accepts human_gate: <why a human must judge this first>, which files the leaf held — outside the build-ready lane, never claimed — instead of build-ready. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -17,13 +17,14 @@ This flow must return exactly one canonical `(tracker_provider, work_item_ref)`
17
17
  - an explicit reference matching the configured tracker (`KEY-123`, `org/repo#123` or issue URL, Linear team identifier);
18
18
  - an existing file path containing a specification (read the entire file, without offset/limit);
19
19
  - plain-text work description.
20
- 3. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
20
+ 3. Detect the caller's readiness declaration, which is orthogonal to the classification above. Absent any declaration this is the default build-ready path. A caller deliberately holding the work for a pending human product call passes `human_gate: "<why a human must judge this first>"`; the two declarations are mutually exclusive and a `human_gate` present but empty is a blocking error, because an unexplained hold is indistinguishable from an accident.
21
+ 4. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
21
22
 
22
23
  ## Phase 2 — Resolve one live work item
23
24
 
24
25
  ### Explicit reference
25
26
 
26
- Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. This live read is mandatory even if caller context already includes ticket text.
27
+ Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. Read the returned body for the hold marker: an item carrying `[lisa-human-gate]` is held and Phase 3 refuses to claim it, whatever role or labels it carries. This live read is mandatory even if caller context already includes ticket text.
27
28
 
28
29
  ### File or plain text
29
30
 
@@ -34,15 +35,21 @@ Search conservatively before creating:
34
35
  - GitHub: `gh issue list --repo <org>/<repo> --state open --search "<keywords> in:title,body"`.
35
36
  - Jira: `lisa-atlassian-access operation: search-issues` with project-scoped JQL.
36
37
  - Linear: `lisa-linear-access operation: list-issues` scoped to the configured team/workspace.
37
- 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, cross-repo, and materially different outcomes.
38
+ 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, held-by-gate, cross-repo, and materially different outcomes. A held candidate is never reused: attaching new work to an item a human is holding is how the hold gets spent.
38
39
  4. Reuse only when **exactly one** live leaf is a high-confidence semantic match for the same requested outcome and repository. A shared keyword or similar title is not enough. Record the search queries, candidates, and rejection reasons in the returned resolution evidence.
39
- 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true`. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
+ 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true` unless the caller declared a `human_gate`, in which case pass that `human_gate: "<why>"` reason instead of `build_ready: true` and never alongside it, so the writer leaves the leaf in the tracker's default backlog role — out of the lane build-intake claims from — and stamps the hold on the body as a `[lisa-human-gate]` marker. Discarding a declared gate and filing build-ready anyway is the one outcome this step must never produce. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
41
  6. Live-read the writer's canonical returned reference with `lisa-tracker-read`. A create response without a verified live leaf is failure.
41
42
 
42
43
  This is intentionally conservative: ambiguity creates one explicit work item instead of silently attaching work to the wrong existing item. Across the entire invocation, at most one new work item may be created.
43
44
 
44
45
  ## Phase 3 — Claim and bind
45
46
 
47
+ The work item is **held** when either of two independent signals is present: the caller declared a `human_gate`, or the resolved item's body contains the literal `[lisa-human-gate]` marker. Test the marker as a plain substring and never key the parse on a `reason=` value — markers are written both with and without one, and a parser that requires the key misses the keyless ones while appearing to work.
48
+
49
+ A held item stops the flow here, whatever the resolution outcome and whatever roles or labels the item carries. The marker is the authority: an item can be held while sitting in the build-ready role with no blocked or human-needed label, because a human stamping a hold on live work is the most likely way a gate is ever applied, and the vendor claim contracts reject a closed item or an active blocker but not a marked one. Do not invoke `lisa-tracker-claim`, do not write the worktree binding, and do not begin durable project work. Claiming a held leaf binds a live lane to it and makes it look already-attended, which suppresses exactly the human attention it was filed to attract. Return the structured result below with `claim_outcome: held-by-gate` and `binding_outcome: skipped-human-gate`, name which signal held it in `held_by` and carry the reason verbatim in `gate_reason` alongside the canonical reference, then stop. A marker written without a `reason=` key yields `gate_reason: null` — a missing reason narrows what the result can say, never whether the item is held.
50
+
51
+ Otherwise:
52
+
46
53
  1. Invoke `lisa-tracker-claim <canonical-ref>`. Require its post-read verified `claim_outcome: claimed|reused`; no binding may be written after a failed/unverified claim.
47
54
  2. Persist only the canonical reference in worktree-local machine state:
48
55
 
@@ -58,8 +65,11 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
58
65
  tracker_provider: jira|github|linear
59
66
  work_item_ref: <canonical-ref>
60
67
  resolution_outcome: explicit|reused|created
61
- claim_outcome: claimed|reused
62
- binding_outcome: verified
68
+ readiness_declaration: build-ready|human-gate
69
+ held_by: none|caller-declaration|item-marker
70
+ gate_reason: <why a human must judge this first>|null
71
+ claim_outcome: claimed|reused|held-by-gate
72
+ binding_outcome: verified|skipped-human-gate
63
73
  ```
64
74
 
65
75
  ## Lifecycle
@@ -68,4 +78,5 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
68
78
  - Branch setup may call `node scripts/lisa-work-item.mjs attach-branch` after the feature branch exists.
69
79
  - Keep the binding across ordinary interruptions and blocked outcomes so resumed work remains attributable.
70
80
  - Clear it only after true terminal completion — merged, deployed/verified where required, tracker evidence/backlink complete, and the work item terminal — by running `node scripts/lisa-work-item.mjs clear` and verifying no current binding remains.
81
+ - A held filing writes no binding at all, so there is nothing to clear. The gate is released by a human, not by this skill: a human removes the `[lisa-human-gate]` marker and flips the leaf into the build-ready role, and a later invocation then resolves it on the ordinary path.
71
82
  - A tracker outage, invalid item, failed claim, or failed binding blocks durable work. Never continue untracked and never ask a Git hook to create the item.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.33.12",
3
+ "version": "4.33.14",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -269,8 +269,12 @@ FILE IT THE SANCTIONED WAY — one of these two, always explicit:
269
269
  bar — reproduction, observed-versus-expected, Gherkin acceptance criteria.
270
270
 
271
271
  2. A human product call is genuinely pending. Route the same way but pass
272
- \`human_gate: "<why a human must judge this first>"\`, which stamps the hold
273
- so it is auditable rather than indistinguishable from an accident:
272
+ \`human_gate: "<why a human must judge this first>"\` in place of
273
+ \`build_ready: true\` — the two are mutually exclusive. The leaf is filed
274
+ into the default backlog role, outside the lane build-intake claims from,
275
+ and is deliberately NOT claimed, so it keeps attracting the human attention
276
+ it was filed for. The hold is stamped so it is auditable rather than
277
+ indistinguishable from an accident:
274
278
 
275
279
  Held for a human product call: <reason>.
276
280
  <!-- [lisa-human-gate] reason=<short-slug> -->
@@ -1019,14 +1019,40 @@ readonly GIT_CMD='(^|[^[:alnum:]_-])git[[:space:]]+'"$GIT_GLOBAL_OPTS"
1019
1019
  # match. The preceding-char class still excludes `.`, `/`, and `-` to keep
1020
1020
  # `--rm`-style flags and `foo.rm` names out.
1021
1021
  readonly RM_CMD='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?rm'
1022
+ # Where a recursive-forced flag cluster is allowed to END.
1023
+ #
1024
+ # It used to be `([[:space:]]|$)` — whitespace or end of segment — and a closing
1025
+ # parenthesis is neither, so a SUBSHELL-wrapped pipeline fell out of every guard
1026
+ # built on these two patterns while its brace-wrapped twin was caught: `{ find
1027
+ # .git -type d | parallel rm -rf; }` matched because the `;` supplied the
1028
+ # boundary, and `(find .git -type d | parallel rm -rf)` did not (issue #3494).
1029
+ #
1030
+ # So the two STATEMENT-GROUPING terminators join whitespace here, and nothing
1031
+ # else. The general spelling — "any character that cannot be part of a flag",
1032
+ # `([^[:alnum:]-]|$)` — is the more principled statement of the intent, and is
1033
+ # deliberately NOT used: these patterns feed the rm TARGET WALK, the guard with
1034
+ # this file's longest false-positive history, and it would newly match ordinary
1035
+ # prose punctuation (`echo "rm -rf, then …"` on the comma). Paren and semicolon
1036
+ # buy the brace/paren parity the defect is about and buy nothing else.
1037
+ #
1038
+ # The cluster must still END at the boundary, so `-rfoo` is not read as `-rf`.
1039
+ readonly RM_RF_END='([[:space:];)]|$)'
1022
1040
  # rm invoked with BOTH a recursive and a force flag — clustered (-rf/-fr, any
1023
1041
  # extra letters) or split, in either order. The split gate pairs ANY recursive
1024
1042
  # form (short cluster containing r, or --recursive) with ANY force form (short
1025
1043
  # cluster containing f, or --force), so mixed spellings like `rm -r --force /`
1026
1044
  # and `rm --recursive -f /` cannot slip between the short/short and long/long
1027
1045
  # alternations (PR #1976 review).
1028
- readonly RM_RF_CLUSTER="$RM_CMD"'([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'
1029
- readonly RM_RF_SPLIT="$RM_CMD"'(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]]|$)|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]]|$))'
1046
+ #
1047
+ # The FLAG TAILS are named separately from the `rm`-anchored patterns because
1048
+ # the find/xargs family below needs the identical flag vocabulary against a
1049
+ # different verb anchor. Before #3494 it carried its own short-cluster-only copy
1050
+ # and therefore missed `rm --recursive --force` — a divergence that could only
1051
+ # happen because the spelling set was written twice.
1052
+ readonly RM_RF_TAIL_CLUSTER='([[:space:]]+-[[:alnum:]-]+)*[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)'"$RM_RF_END"
1053
+ readonly RM_RF_TAIL_SPLIT='(([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)'"$RM_RF_END"'|([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*f[[:alnum:]]*|--force)([[:space:]][^;&|]*)?[[:space:]](-[[:alnum:]]*r[[:alnum:]]*|--recursive)'"$RM_RF_END"')'
1054
+ readonly RM_RF_CLUSTER="$RM_CMD$RM_RF_TAIL_CLUSTER"
1055
+ readonly RM_RF_SPLIT="$RM_CMD$RM_RF_TAIL_SPLIT"
1030
1056
 
1031
1057
  # 1. Recursive forced delete (`rm -rf`) of a filesystem root, home, or top-level
1032
1058
  # wildcard. Two gates ANDed: the statement must invoke `rm` with BOTH a
@@ -1667,8 +1693,31 @@ fi
1667
1693
  if matches '(^|[^[:alnum:]_./-])find[[:space:]][^;&|]*-exec[[:space:]]+rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1668
1694
  block "find -exec rm -rf performs a recursive forced delete on unreviewed paths"
1669
1695
  fi
1670
- if matches '(^|[^[:alnum:]_./-])xargs[[:space:]]([^;&|]*[[:space:]])?rm[[:space:]]+(-[[:alnum:]]*r[[:alnum:]]*f|-[[:alnum:]]*f[[:alnum:]]*r)([[:space:]]|$)'; then
1671
- block "xargs rm -rf performs a recursive forced delete on dynamic stdin input"
1696
+ # A command that reads its operands from STDIN and runs a program on them. The
1697
+ # targets are therefore whatever the upstream stage emitted, which is exactly
1698
+ # what makes a recursive forced delete behind one unauditable.
1699
+ #
1700
+ # Three fail-opens closed here (issue #3494), all of them the same mistake —
1701
+ # this rule was written standalone instead of inheriting vocabulary the file
1702
+ # already had:
1703
+ #
1704
+ # 1. The old prefix was `(^|[^[:alnum:]_./-])xargs`, whose excluded class
1705
+ # contains `/`, so the character before `xargs` in `/usr/bin/xargs` was
1706
+ # itself disqualifying and a PATH-SPELLED invocation never matched.
1707
+ # `gxargs` — the GNU findutils spelling shipped by macOS package managers —
1708
+ # failed the same way on the alphanumeric side. RM_CMD above solved this
1709
+ # exact problem for `rm` by putting the boundary before the WHOLE path and
1710
+ # making the directory part optional; that shape is reused verbatim.
1711
+ # 2. `parallel` was absent. It is a drop-in `xargs` substitute reaching the
1712
+ # same delete on the same unaudited stdin, and guard 14 below already had
1713
+ # to name it in its own evidence — it was missing only here.
1714
+ # 3. The flag set was a short-cluster-only copy, so `rm --recursive --force`
1715
+ # was allowed where `rm -rf` was refused. It now uses the SAME tails as the
1716
+ # rm walk, which is what makes that class of divergence unrepeatable.
1717
+ readonly STDIN_DELETER='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(g?xargs|parallel)[[:space:]]([^;&|]*[[:space:]])?rm'
1718
+ if matches "$STDIN_DELETER$RM_RF_TAIL_CLUSTER" \
1719
+ || matches "$STDIN_DELETER$RM_RF_TAIL_SPLIT"; then
1720
+ block "xargs/parallel rm -rf performs a recursive forced delete on dynamic stdin input"
1672
1721
  fi
1673
1722
 
1674
1723
  # 12. Disk destroyers, always on (exceeds upstream, which only scans these
@@ -121,6 +121,20 @@ export const PROVIDERS = [
121
121
  sha256:
122
122
  "18253757286e119d450133a87eb463bf8c1ce418ce24c834f4f250d60cba6f9e",
123
123
  },
124
+ // Bitwarden publishes macOS archives for the same tag, and omitting them
125
+ // made the one genuinely pinned credential CLI the one a macOS operator
126
+ // had to install by hand — the unverified path, for the tool where an
127
+ // unverified download matters most.
128
+ "darwin-arm64": {
129
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-aarch64-apple-darwin-2.1.0.zip",
130
+ sha256:
131
+ "9cb1c1c6e6164d83b2e339883ba02b4cbb37188ce9a484b1ce8249443163e066",
132
+ },
133
+ "darwin-x64": {
134
+ url: "https://github.com/bitwarden/sdk-sm/releases/download/bws-v2.1.0/bws-x86_64-apple-darwin-2.1.0.zip",
135
+ sha256:
136
+ "6f626b3971368902af1b9847c02791a1b4666969d7561e2047681cded7997537",
137
+ },
124
138
  },
125
139
  },
126
140
  {
@@ -197,11 +211,26 @@ export const TOOLS = [
197
211
  "12311e320d4cfdb54d7fa2d58cd1e3a2ccb4c12e1c3abb32b0a2e48bd0f991bf",
198
212
  binary: "gh_2.83.0_linux_arm64/bin/gh",
199
213
  },
214
+ // `install` is per-block for gh and only for gh: the vendor ships a
215
+ // .tar.gz for Linux and a .zip for macOS, so the entry-level `release-tar`
216
+ // is wrong for exactly these two. Without the override the resolved entry
217
+ // keeps `release-tar` and the installer runs `tar -xzf` over a ZIP, which
218
+ // fails during extraction — a macOS pin that resolves and cannot install.
200
219
  "darwin-arm64": {
201
220
  url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_macOS_arm64.zip",
202
221
  sha256:
203
222
  "fecba907bc361d5e33620dbf1145f11432c39fb2b388a839463cfbb89a84820b",
204
223
  binary: "gh_2.83.0_macOS_arm64/bin/gh",
224
+ install: "release-zip",
225
+ },
226
+ // Intel Macs are still in service, and an Apple Silicon-only macOS block
227
+ // reads as "macOS is covered" while leaving half of it uncovered.
228
+ "darwin-x64": {
229
+ url: "https://github.com/cli/cli/releases/download/v2.83.0/gh_2.83.0_macOS_amd64.zip",
230
+ sha256:
231
+ "0c0de650752bb92d7283e386cafd03d9ac5f47028c648c4ab821ef08a75c0716",
232
+ binary: "gh_2.83.0_macOS_amd64/bin/gh",
233
+ install: "release-zip",
205
234
  },
206
235
  },
207
236
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lisa-track
3
- description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
3
+ description: "Resolves exactly one live configured-tracker leaf for durable project work, claims it idempotently, and persists its canonical reference in worktree-local state. Accepts an existing Jira/GitHub/Linear ref, a spec file, or plain text. Also accepts human_gate: <why a human must judge this first>, which files the leaf held — outside the build-ready lane, never claimed — instead of build-ready. Use directly to mention/create the related ticket, and as lisa-implement's mandatory input gate."
4
4
  allowed-tools: ["Skill", "Bash", "Read"]
5
5
  ---
6
6
 
@@ -17,13 +17,14 @@ This flow must return exactly one canonical `(tracker_provider, work_item_ref)`
17
17
  - an explicit reference matching the configured tracker (`KEY-123`, `org/repo#123` or issue URL, Linear team identifier);
18
18
  - an existing file path containing a specification (read the entire file, without offset/limit);
19
19
  - plain-text work description.
20
- 3. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
20
+ 3. Detect the caller's readiness declaration, which is orthogonal to the classification above. Absent any declaration this is the default build-ready path. A caller deliberately holding the work for a pending human product call passes `human_gate: "<why a human must judge this first>"`; the two declarations are mutually exclusive and a `human_gate` present but empty is a blocking error, because an unexplained hold is indistinguishable from an accident.
21
+ 4. Preserve the full resolved specification for the caller. Do not treat a ticket-like token for a different provider/project as plain text; report the mismatch.
21
22
 
22
23
  ## Phase 2 — Resolve one live work item
23
24
 
24
25
  ### Explicit reference
25
26
 
26
- Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. This live read is mandatory even if caller context already includes ticket text.
27
+ Invoke `lisa-tracker-read <ref>` and require a live result from the configured project. Reject nonexistent, inaccessible, closed/resolved/terminal, wrong-project, wrong-repository, or container items. Read the returned body for the hold marker: an item carrying `[lisa-human-gate]` is held and Phase 3 refuses to claim it, whatever role or labels it carries. This live read is mandatory even if caller context already includes ticket text.
27
28
 
28
29
  ### File or plain text
29
30
 
@@ -34,15 +35,21 @@ Search conservatively before creating:
34
35
  - GitHub: `gh issue list --repo <org>/<repo> --state open --search "<keywords> in:title,body"`.
35
36
  - Jira: `lisa-atlassian-access operation: search-issues` with project-scoped JQL.
36
37
  - Linear: `lisa-linear-access operation: list-issues` scoped to the configured team/workspace.
37
- 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, cross-repo, and materially different outcomes.
38
+ 3. Treat search results as candidates, never proof. Live-read each plausible candidate through `lisa-tracker-read`, and discard terminal, container, blocked, held-by-gate, cross-repo, and materially different outcomes. A held candidate is never reused: attaching new work to an item a human is holding is how the hold gets spent.
38
39
  4. Reuse only when **exactly one** live leaf is a high-confidence semantic match for the same requested outcome and repository. A shared keyword or similar title is not enough. Record the search queries, candidates, and rejection reasons in the returned resolution evidence.
39
- 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true`. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
+ 5. If there is no unique high-confidence match (zero or ambiguous candidates), create **exactly one** item by invoking `lisa-tracker-write` once. Synthesize one complete single-repository leaf (`Bug`, `Task`, `Sub-task`, or `Improvement`, never Epic/container) with the writer's required three-audience body, Gherkin acceptance criteria, repository, target environment, relationship search, and executable Validation Journey. Pass `build_ready: true` unless the caller declared a `human_gate`, in which case pass that `human_gate: "<why>"` reason instead of `build_ready: true` and never alongside it, so the writer leaves the leaf in the tracker's default backlog role — out of the lane build-intake claims from — and stamps the hold on the body as a `[lisa-human-gate]` marker. Discarding a declared gate and filing build-ready anyway is the one outcome this step must never produce. Do not create placeholder/thin tickets, do not create a hierarchy, and do not retry creation by making another item if validation fails; repair the proposed spec and retry the same writer operation only if the vendor contract supports idempotent reuse.
40
41
  6. Live-read the writer's canonical returned reference with `lisa-tracker-read`. A create response without a verified live leaf is failure.
41
42
 
42
43
  This is intentionally conservative: ambiguity creates one explicit work item instead of silently attaching work to the wrong existing item. Across the entire invocation, at most one new work item may be created.
43
44
 
44
45
  ## Phase 3 — Claim and bind
45
46
 
47
+ The work item is **held** when either of two independent signals is present: the caller declared a `human_gate`, or the resolved item's body contains the literal `[lisa-human-gate]` marker. Test the marker as a plain substring and never key the parse on a `reason=` value — markers are written both with and without one, and a parser that requires the key misses the keyless ones while appearing to work.
48
+
49
+ A held item stops the flow here, whatever the resolution outcome and whatever roles or labels the item carries. The marker is the authority: an item can be held while sitting in the build-ready role with no blocked or human-needed label, because a human stamping a hold on live work is the most likely way a gate is ever applied, and the vendor claim contracts reject a closed item or an active blocker but not a marked one. Do not invoke `lisa-tracker-claim`, do not write the worktree binding, and do not begin durable project work. Claiming a held leaf binds a live lane to it and makes it look already-attended, which suppresses exactly the human attention it was filed to attract. Return the structured result below with `claim_outcome: held-by-gate` and `binding_outcome: skipped-human-gate`, name which signal held it in `held_by` and carry the reason verbatim in `gate_reason` alongside the canonical reference, then stop. A marker written without a `reason=` key yields `gate_reason: null` — a missing reason narrows what the result can say, never whether the item is held.
50
+
51
+ Otherwise:
52
+
46
53
  1. Invoke `lisa-tracker-claim <canonical-ref>`. Require its post-read verified `claim_outcome: claimed|reused`; no binding may be written after a failed/unverified claim.
47
54
  2. Persist only the canonical reference in worktree-local machine state:
48
55
 
@@ -58,8 +65,11 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
58
65
  tracker_provider: jira|github|linear
59
66
  work_item_ref: <canonical-ref>
60
67
  resolution_outcome: explicit|reused|created
61
- claim_outcome: claimed|reused
62
- binding_outcome: verified
68
+ readiness_declaration: build-ready|human-gate
69
+ held_by: none|caller-declaration|item-marker
70
+ gate_reason: <why a human must judge this first>|null
71
+ claim_outcome: claimed|reused|held-by-gate
72
+ binding_outcome: verified|skipped-human-gate
63
73
  ```
64
74
 
65
75
  ## Lifecycle
@@ -68,4 +78,5 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
68
78
  - Branch setup may call `node scripts/lisa-work-item.mjs attach-branch` after the feature branch exists.
69
79
  - Keep the binding across ordinary interruptions and blocked outcomes so resumed work remains attributable.
70
80
  - Clear it only after true terminal completion — merged, deployed/verified where required, tracker evidence/backlink complete, and the work item terminal — by running `node scripts/lisa-work-item.mjs clear` and verifying no current binding remains.
81
+ - A held filing writes no binding at all, so there is nothing to clear. The gate is released by a human, not by this skill: a human removes the `[lisa-human-gate]` marker and flips the leaf into the build-ready role, and a later invocation then resolves it on the ordinary path.
71
82
  - A tracker outage, invalid item, failed claim, or failed binding blocks durable work. Never continue untracked and never ask a Git hook to create the item.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
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.33.12",
3
+ "version": "4.33.14",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"