@codyswann/lisa 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh +31 -0
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +24 -2
- package/dist/codex/scripts/block-no-verify.sh +53 -4
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +9 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/lisa/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa/rules/reference/tracked-work.md +4 -2
- package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-agy/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa-copilot/rules/eager/tracked-work.md +1 -1
- package/plugins/lisa-copilot/rules/reference/tracked-work.md +4 -2
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-no-verify.sh +31 -0
- package/plugins/lisa-cursor/rules/tracked-work-reference.mdc +4 -2
- package/plugins/lisa-cursor/rules/tracked-work.mdc +1 -1
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-track/SKILL.md +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-no-verify.agy.sh +53 -4
- package/plugins/src/base/hooks/block-no-verify.sh +31 -0
- package/plugins/src/base/rules/eager/tracked-work.md +1 -1
- package/plugins/src/base/rules/reference/tracked-work.md +4 -2
- package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-track/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"name": "@codyswann/lisa",
|
|
123
|
-
"version": "3.4.
|
|
123
|
+
"version": "3.4.1",
|
|
124
124
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
125
125
|
"main": "dist/index.js",
|
|
126
126
|
"exports": {
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
@@ -102,16 +102,65 @@ def disables_verification(token):
|
|
|
102
102
|
and NO_VERIFY.startswith(token)
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
# The only relocations that keep a repo's own hooks in play. Anything else —
|
|
106
|
+
# including "" and /dev/null, which are simply the two most obvious members of
|
|
107
|
+
# the blocked set rather than special cases — is refused.
|
|
108
|
+
PERMITTED_HOOKS_PATHS = {".husky", ".githooks"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_permitted_hooks_path(value):
|
|
112
|
+
"""Whether a core.hooksPath value relocates hooks rather than disabling them.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value: The raw core.hooksPath value as it appeared on the command line.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if the path is an established in-repo hooks directory.
|
|
119
|
+
"""
|
|
120
|
+
cleaned = value.strip().strip("'\"")
|
|
121
|
+
if cleaned.startswith("./"):
|
|
122
|
+
cleaned = cleaned[2:]
|
|
123
|
+
return cleaned.rstrip("/") in PERMITTED_HOOKS_PATHS
|
|
124
|
+
|
|
125
|
+
|
|
105
126
|
for i, token in enumerate(normalized_tokens):
|
|
106
127
|
if disables_verification(token):
|
|
107
128
|
sys.exit(1)
|
|
108
129
|
if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
|
|
109
130
|
sys.exit(1)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
# Allowlist the destinations, do not denylist the disabling ones: hooks are
|
|
132
|
+
# disabled just as completely by any directory that happens to contain none
|
|
133
|
+
# (`-c core.hooksPath=/tmp/empty`), so the set that DISABLES hooks is
|
|
134
|
+
# unbounded while the set that legitimately relocates them is tiny. Matched
|
|
135
|
+
# case-insensitively because git config names are.
|
|
136
|
+
lowered = token.lower()
|
|
137
|
+
if lowered.startswith("core.hookspath="):
|
|
138
|
+
if not is_permitted_hooks_path(token.split("=", 1)[1]):
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
if lowered == "core.hookspath" and i + 1 < len(normalized_tokens):
|
|
141
|
+
if not is_permitted_hooks_path(normalized_tokens[i + 1]):
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
# `--config-env=core.hooksPath=SOMEVAR` reads the path out of the named env
|
|
144
|
+
# var, so it is not in the command at all and cannot be allowlisted.
|
|
145
|
+
if lowered.startswith("--config-env="):
|
|
146
|
+
spec = token.split("=", 1)[1]
|
|
147
|
+
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
# git also accepts `--config-env <name>=<envvar>` as two tokens. Guarding
|
|
150
|
+
# only the `=` spelling let the trailing `core.hooksPath=.husky` fall
|
|
151
|
+
# through to the allowlist above, which reads `.husky` as a path — but here
|
|
152
|
+
# it names an ENVIRONMENT VARIABLE, which can hold /dev/null. Checked at the
|
|
153
|
+
# `--config-env` token, which the loop reaches first.
|
|
154
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
155
|
+
spec = normalized_tokens[i + 1]
|
|
156
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
113
157
|
sys.exit(1)
|
|
114
|
-
|
|
158
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
159
|
+
# GIT_CONFIG_VALUE_0=/dev/null git ...` sets the same command-scope config
|
|
160
|
+
# via env-var-style assignments. The index is arbitrary below
|
|
161
|
+
# GIT_CONFIG_COUNT, so it is matched as `\d+` rather than pinned to 0.
|
|
162
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
163
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
115
164
|
sys.exit(1)
|
|
116
165
|
|
|
117
166
|
sys.exit(0)
|
|
@@ -186,6 +186,37 @@ for i, token in enumerate(normalized_tokens):
|
|
|
186
186
|
spec = token.split("=", 1)[1]
|
|
187
187
|
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
188
188
|
sys.exit(1)
|
|
189
|
+
# git accepts `--config-env <name>=<envvar>` as TWO tokens as well as one,
|
|
190
|
+
# and guarding only the `=` spelling was worse than missing the separate
|
|
191
|
+
# form outright: the trailing `core.hooksPath=.husky` then fell through to
|
|
192
|
+
# the allowlist above, which reads `.husky` as a PATH and permits it. But
|
|
193
|
+
# here it is an ENVIRONMENT VARIABLE NAME, and `env '.husky=/dev/null' git
|
|
194
|
+
# --config-env core.hooksPath=.husky` really does resolve hooksPath to
|
|
195
|
+
# /dev/null. The allowlist was being used as the bypass.
|
|
196
|
+
#
|
|
197
|
+
# Checked at the `--config-env` token, which the loop reaches first, so the
|
|
198
|
+
# refusal happens before the value token can be mistaken for a path.
|
|
199
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
200
|
+
spec = normalized_tokens[i + 1]
|
|
201
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
204
|
+
# GIT_CONFIG_VALUE_0=/dev/null git commit` sets command-scope config the
|
|
205
|
+
# same way `-c core.hooksPath=...` does — env-var-style assignments ahead of
|
|
206
|
+
# the invocation instead of a flag — so it disables every hook just as
|
|
207
|
+
# completely while matching none of the token shapes above. Upstream missed
|
|
208
|
+
# this until a downstream fork hardened its own copy against it, which is
|
|
209
|
+
# the one direction a guard must never be caught in.
|
|
210
|
+
#
|
|
211
|
+
# The index is matched as `\d+` rather than pinned to 0: git accepts any
|
|
212
|
+
# index below GIT_CONFIG_COUNT, so a single-index check is evaded by typing
|
|
213
|
+
# a 1. Refused outright, like --config-env=, because the path lives in a
|
|
214
|
+
# separate GIT_CONFIG_VALUE_<n> token that can be exported earlier,
|
|
215
|
+
# reordered, or left out entirely — there is nothing here to allowlist
|
|
216
|
+
# against.
|
|
217
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
218
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
219
|
+
sys.exit(1)
|
|
189
220
|
|
|
190
221
|
sys.exit(0)
|
|
191
222
|
PY
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Before the first durable project mutation (code, tests, config, docs, committed research/plans/findings, commits, or PRs), establish exactly one live tracker leaf through `lisa-track`. Read-only discussion and orientation are exempt only while they produce no durable artifact.
|
|
4
4
|
|
|
5
|
-
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs
|
|
5
|
+
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs link <ref>`. A tracker that answers no, and any claim or binding failure, blocks durable work; a tracker that cannot be reached does not block a commit — the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges.
|
|
6
6
|
|
|
7
7
|
Carry that canonical ref through the branch, every ordinary commit's `Work-Item:` trailer, the PR, usage/evidence, and `lisa-tracker-sync`. Hooks and CI never create tickets. Keep the binding through interruptions or blocked outcomes; run `node scripts/lisa-work-item.mjs clear` only after merge/deploy/verification, two-way linkage/evidence, and the tracker item have all reached true terminal completion.
|
|
@@ -9,9 +9,11 @@ Use `lisa-track` as the single entry point:
|
|
|
9
9
|
1. An explicit ticket is live-read through `lisa-tracker-read` and rejected if it is missing, inaccessible, terminal, a container, outside the configured project, or outside the current repository.
|
|
10
10
|
2. A plain-text request or specification file is searched conservatively within the configured project. Reuse only one uniquely high-confidence matching live leaf. If no unique match exists, create exactly one complete single-repository leaf through `lisa-tracker-write`; never create a thin placeholder or a container.
|
|
11
11
|
3. Idempotently claim the resolved leaf through `lisa-tracker-claim`, which reuses the vendor build-intake claim semantics and post-read verifies the claimed-or-later state.
|
|
12
|
-
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs
|
|
12
|
+
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs link <ref>` and verify the worktree-local binding.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`link` is the spelling to use. `bind` is accepted as a permanent alias for the identical operation, but some agent harnesses — Claude Code's worktree isolation among them — refuse any command line containing the bare token `bind`, because it names a shell builtin that evaluates a string. Inside an isolated worktree that refusal makes the `bind` spelling unrunnable, so reach for `link` and never work around a blocked binding by hand-writing `Work-Item:` trailers.
|
|
15
|
+
|
|
16
|
+
The sequence is strict: **live validate/create -> claim -> link -> durable work**. A tracker answer of no, and any claim or binding failure, blocks the work. A tracker that cannot be reached — `gh` absent, or its credential refused — does not block a commit: the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges. Any other tracker failure still blocks. Tool presence or stale session text is not access.
|
|
15
17
|
|
|
16
18
|
## One canonical identity
|
|
17
19
|
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
@@ -102,16 +102,65 @@ def disables_verification(token):
|
|
|
102
102
|
and NO_VERIFY.startswith(token)
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
# The only relocations that keep a repo's own hooks in play. Anything else —
|
|
106
|
+
# including "" and /dev/null, which are simply the two most obvious members of
|
|
107
|
+
# the blocked set rather than special cases — is refused.
|
|
108
|
+
PERMITTED_HOOKS_PATHS = {".husky", ".githooks"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_permitted_hooks_path(value):
|
|
112
|
+
"""Whether a core.hooksPath value relocates hooks rather than disabling them.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value: The raw core.hooksPath value as it appeared on the command line.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if the path is an established in-repo hooks directory.
|
|
119
|
+
"""
|
|
120
|
+
cleaned = value.strip().strip("'\"")
|
|
121
|
+
if cleaned.startswith("./"):
|
|
122
|
+
cleaned = cleaned[2:]
|
|
123
|
+
return cleaned.rstrip("/") in PERMITTED_HOOKS_PATHS
|
|
124
|
+
|
|
125
|
+
|
|
105
126
|
for i, token in enumerate(normalized_tokens):
|
|
106
127
|
if disables_verification(token):
|
|
107
128
|
sys.exit(1)
|
|
108
129
|
if token == "HUSKY=0" or token.startswith("HUSKY_SKIP_HOOKS="):
|
|
109
130
|
sys.exit(1)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
# Allowlist the destinations, do not denylist the disabling ones: hooks are
|
|
132
|
+
# disabled just as completely by any directory that happens to contain none
|
|
133
|
+
# (`-c core.hooksPath=/tmp/empty`), so the set that DISABLES hooks is
|
|
134
|
+
# unbounded while the set that legitimately relocates them is tiny. Matched
|
|
135
|
+
# case-insensitively because git config names are.
|
|
136
|
+
lowered = token.lower()
|
|
137
|
+
if lowered.startswith("core.hookspath="):
|
|
138
|
+
if not is_permitted_hooks_path(token.split("=", 1)[1]):
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
if lowered == "core.hookspath" and i + 1 < len(normalized_tokens):
|
|
141
|
+
if not is_permitted_hooks_path(normalized_tokens[i + 1]):
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
# `--config-env=core.hooksPath=SOMEVAR` reads the path out of the named env
|
|
144
|
+
# var, so it is not in the command at all and cannot be allowlisted.
|
|
145
|
+
if lowered.startswith("--config-env="):
|
|
146
|
+
spec = token.split("=", 1)[1]
|
|
147
|
+
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
# git also accepts `--config-env <name>=<envvar>` as two tokens. Guarding
|
|
150
|
+
# only the `=` spelling let the trailing `core.hooksPath=.husky` fall
|
|
151
|
+
# through to the allowlist above, which reads `.husky` as a path — but here
|
|
152
|
+
# it names an ENVIRONMENT VARIABLE, which can hold /dev/null. Checked at the
|
|
153
|
+
# `--config-env` token, which the loop reaches first.
|
|
154
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
155
|
+
spec = normalized_tokens[i + 1]
|
|
156
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
113
157
|
sys.exit(1)
|
|
114
|
-
|
|
158
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
159
|
+
# GIT_CONFIG_VALUE_0=/dev/null git ...` sets the same command-scope config
|
|
160
|
+
# via env-var-style assignments. The index is arbitrary below
|
|
161
|
+
# GIT_CONFIG_COUNT, so it is matched as `\d+` rather than pinned to 0.
|
|
162
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
163
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
115
164
|
sys.exit(1)
|
|
116
165
|
|
|
117
166
|
sys.exit(0)
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
@@ -186,6 +186,37 @@ for i, token in enumerate(normalized_tokens):
|
|
|
186
186
|
spec = token.split("=", 1)[1]
|
|
187
187
|
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
188
188
|
sys.exit(1)
|
|
189
|
+
# git accepts `--config-env <name>=<envvar>` as TWO tokens as well as one,
|
|
190
|
+
# and guarding only the `=` spelling was worse than missing the separate
|
|
191
|
+
# form outright: the trailing `core.hooksPath=.husky` then fell through to
|
|
192
|
+
# the allowlist above, which reads `.husky` as a PATH and permits it. But
|
|
193
|
+
# here it is an ENVIRONMENT VARIABLE NAME, and `env '.husky=/dev/null' git
|
|
194
|
+
# --config-env core.hooksPath=.husky` really does resolve hooksPath to
|
|
195
|
+
# /dev/null. The allowlist was being used as the bypass.
|
|
196
|
+
#
|
|
197
|
+
# Checked at the `--config-env` token, which the loop reaches first, so the
|
|
198
|
+
# refusal happens before the value token can be mistaken for a path.
|
|
199
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
200
|
+
spec = normalized_tokens[i + 1]
|
|
201
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
204
|
+
# GIT_CONFIG_VALUE_0=/dev/null git commit` sets command-scope config the
|
|
205
|
+
# same way `-c core.hooksPath=...` does — env-var-style assignments ahead of
|
|
206
|
+
# the invocation instead of a flag — so it disables every hook just as
|
|
207
|
+
# completely while matching none of the token shapes above. Upstream missed
|
|
208
|
+
# this until a downstream fork hardened its own copy against it, which is
|
|
209
|
+
# the one direction a guard must never be caught in.
|
|
210
|
+
#
|
|
211
|
+
# The index is matched as `\d+` rather than pinned to 0: git accepts any
|
|
212
|
+
# index below GIT_CONFIG_COUNT, so a single-index check is evaded by typing
|
|
213
|
+
# a 1. Refused outright, like --config-env=, because the path lives in a
|
|
214
|
+
# separate GIT_CONFIG_VALUE_<n> token that can be exported earlier,
|
|
215
|
+
# reordered, or left out entirely — there is nothing here to allowlist
|
|
216
|
+
# against.
|
|
217
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
218
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
219
|
+
sys.exit(1)
|
|
189
220
|
|
|
190
221
|
sys.exit(0)
|
|
191
222
|
PY
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Before the first durable project mutation (code, tests, config, docs, committed research/plans/findings, commits, or PRs), establish exactly one live tracker leaf through `lisa-track`. Read-only discussion and orientation are exempt only while they produce no durable artifact.
|
|
4
4
|
|
|
5
|
-
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs
|
|
5
|
+
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs link <ref>`. A tracker that answers no, and any claim or binding failure, blocks durable work; a tracker that cannot be reached does not block a commit — the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges.
|
|
6
6
|
|
|
7
7
|
Carry that canonical ref through the branch, every ordinary commit's `Work-Item:` trailer, the PR, usage/evidence, and `lisa-tracker-sync`. Hooks and CI never create tickets. Keep the binding through interruptions or blocked outcomes; run `node scripts/lisa-work-item.mjs clear` only after merge/deploy/verification, two-way linkage/evidence, and the tracker item have all reached true terminal completion.
|
|
@@ -9,9 +9,11 @@ Use `lisa-track` as the single entry point:
|
|
|
9
9
|
1. An explicit ticket is live-read through `lisa-tracker-read` and rejected if it is missing, inaccessible, terminal, a container, outside the configured project, or outside the current repository.
|
|
10
10
|
2. A plain-text request or specification file is searched conservatively within the configured project. Reuse only one uniquely high-confidence matching live leaf. If no unique match exists, create exactly one complete single-repository leaf through `lisa-tracker-write`; never create a thin placeholder or a container.
|
|
11
11
|
3. Idempotently claim the resolved leaf through `lisa-tracker-claim`, which reuses the vendor build-intake claim semantics and post-read verifies the claimed-or-later state.
|
|
12
|
-
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs
|
|
12
|
+
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs link <ref>` and verify the worktree-local binding.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
`link` is the spelling to use. `bind` is accepted as a permanent alias for the identical operation, but some agent harnesses — Claude Code's worktree isolation among them — refuse any command line containing the bare token `bind`, because it names a shell builtin that evaluates a string. Inside an isolated worktree that refusal makes the `bind` spelling unrunnable, so reach for `link` and never work around a blocked binding by hand-writing `Work-Item:` trailers.
|
|
15
|
+
|
|
16
|
+
The sequence is strict: **live validate/create -> claim -> link -> durable work**. A tracker answer of no, and any claim or binding failure, blocks the work. A tracker that cannot be reached — `gh` absent, or its credential refused — does not block a commit: the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges. Any other tracker failure still blocks. Tool presence or stale session text is not access.
|
|
15
17
|
|
|
16
18
|
## One canonical identity
|
|
17
19
|
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|
|
@@ -186,6 +186,37 @@ for i, token in enumerate(normalized_tokens):
|
|
|
186
186
|
spec = token.split("=", 1)[1]
|
|
187
187
|
if spec.split("=", 1)[0].strip().lower() == "core.hookspath":
|
|
188
188
|
sys.exit(1)
|
|
189
|
+
# git accepts `--config-env <name>=<envvar>` as TWO tokens as well as one,
|
|
190
|
+
# and guarding only the `=` spelling was worse than missing the separate
|
|
191
|
+
# form outright: the trailing `core.hooksPath=.husky` then fell through to
|
|
192
|
+
# the allowlist above, which reads `.husky` as a PATH and permits it. But
|
|
193
|
+
# here it is an ENVIRONMENT VARIABLE NAME, and `env '.husky=/dev/null' git
|
|
194
|
+
# --config-env core.hooksPath=.husky` really does resolve hooksPath to
|
|
195
|
+
# /dev/null. The allowlist was being used as the bypass.
|
|
196
|
+
#
|
|
197
|
+
# Checked at the `--config-env` token, which the loop reaches first, so the
|
|
198
|
+
# refusal happens before the value token can be mistaken for a path.
|
|
199
|
+
if lowered == "--config-env" and i + 1 < len(normalized_tokens):
|
|
200
|
+
spec = normalized_tokens[i + 1]
|
|
201
|
+
if spec.split("=", 1)[0].strip().strip("'\"").lower() == "core.hookspath":
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
# `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath
|
|
204
|
+
# GIT_CONFIG_VALUE_0=/dev/null git commit` sets command-scope config the
|
|
205
|
+
# same way `-c core.hooksPath=...` does — env-var-style assignments ahead of
|
|
206
|
+
# the invocation instead of a flag — so it disables every hook just as
|
|
207
|
+
# completely while matching none of the token shapes above. Upstream missed
|
|
208
|
+
# this until a downstream fork hardened its own copy against it, which is
|
|
209
|
+
# the one direction a guard must never be caught in.
|
|
210
|
+
#
|
|
211
|
+
# The index is matched as `\d+` rather than pinned to 0: git accepts any
|
|
212
|
+
# index below GIT_CONFIG_COUNT, so a single-index check is evaded by typing
|
|
213
|
+
# a 1. Refused outright, like --config-env=, because the path lives in a
|
|
214
|
+
# separate GIT_CONFIG_VALUE_<n> token that can be exported earlier,
|
|
215
|
+
# reordered, or left out entirely — there is nothing here to allowlist
|
|
216
|
+
# against.
|
|
217
|
+
key_match = re.match(r"git_config_key_\d+=(.*)$", lowered, re.DOTALL)
|
|
218
|
+
if key_match and key_match.group(1).strip().strip("'\"") == "core.hookspath":
|
|
219
|
+
sys.exit(1)
|
|
189
220
|
|
|
190
221
|
sys.exit(0)
|
|
191
222
|
PY
|
|
@@ -14,9 +14,11 @@ Use `lisa-track` as the single entry point:
|
|
|
14
14
|
1. An explicit ticket is live-read through `lisa-tracker-read` and rejected if it is missing, inaccessible, terminal, a container, outside the configured project, or outside the current repository.
|
|
15
15
|
2. A plain-text request or specification file is searched conservatively within the configured project. Reuse only one uniquely high-confidence matching live leaf. If no unique match exists, create exactly one complete single-repository leaf through `lisa-tracker-write`; never create a thin placeholder or a container.
|
|
16
16
|
3. Idempotently claim the resolved leaf through `lisa-tracker-claim`, which reuses the vendor build-intake claim semantics and post-read verifies the claimed-or-later state.
|
|
17
|
-
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs
|
|
17
|
+
4. Before any durable repository work, persist the canonical reference with `node scripts/lisa-work-item.mjs link <ref>` and verify the worktree-local binding.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`link` is the spelling to use. `bind` is accepted as a permanent alias for the identical operation, but some agent harnesses — Claude Code's worktree isolation among them — refuse any command line containing the bare token `bind`, because it names a shell builtin that evaluates a string. Inside an isolated worktree that refusal makes the `bind` spelling unrunnable, so reach for `link` and never work around a blocked binding by hand-writing `Work-Item:` trailers.
|
|
20
|
+
|
|
21
|
+
The sequence is strict: **live validate/create -> claim -> link -> durable work**. A tracker answer of no, and any claim or binding failure, blocks the work. A tracker that cannot be reached — `gh` absent, or its credential refused — does not block a commit: the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges. Any other tracker failure still blocks. Tool presence or stale session text is not access.
|
|
20
22
|
|
|
21
23
|
## One canonical identity
|
|
22
24
|
|
|
@@ -7,6 +7,6 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
Before the first durable project mutation (code, tests, config, docs, committed research/plans/findings, commits, or PRs), establish exactly one live tracker leaf through `lisa-track`. Read-only discussion and orientation are exempt only while they produce no durable artifact.
|
|
9
9
|
|
|
10
|
-
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs
|
|
10
|
+
The mandatory order is: live-validate an explicit ref, or conservatively search and create exactly one valid leaf through `lisa-tracker-write` when no unique match exists; idempotently claim it through `lisa-tracker-claim`; then persist and verify the worktree-local binding with `node scripts/lisa-work-item.mjs link <ref>`. A tracker that answers no, and any claim or binding failure, blocks durable work; a tracker that cannot be reached does not block a commit — the offline checks still run, the skip is loud on stderr, and the required `Work-Item Traceability` check re-runs the live checks with credentials before anything merges.
|
|
11
11
|
|
|
12
12
|
Carry that canonical ref through the branch, every ordinary commit's `Work-Item:` trailer, the PR, usage/evidence, and `lisa-tracker-sync`. Hooks and CI never create tickets. Keep the binding through interruptions or blocked outcomes; run `node scripts/lisa-work-item.mjs clear` only after merge/deploy/verification, two-way linkage/evidence, and the tracker item have all reached true terminal completion.
|
|
@@ -64,7 +64,7 @@ The input-resolver invokes `lisa-track $ARGUMENTS` and owns its complete resolve
|
|
|
64
64
|
- **Bind before durable work:** only after the verified claim, run:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
node scripts/lisa-work-item.mjs
|
|
67
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Require a successful readback of that worktree-local binding. On detached HEAD, `branch: null` is the expected pending binding; after branch creation the mandatory `attach-branch` step below must replace it before any commit. Tracker or binding failure stops the flow; never continue untracked.
|
|
@@ -47,7 +47,7 @@ This is intentionally conservative: ambiguity creates one explicit work item ins
|
|
|
47
47
|
2. Persist only the canonical reference in worktree-local machine state:
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
node scripts/lisa-work-item.mjs
|
|
50
|
+
node scripts/lisa-work-item.mjs link <canonical-ref>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
3. Read the binding back through `node scripts/lisa-work-item.mjs current` and require it to equal the canonical reference. If binding fails, stop before durable project work.
|