@codyswann/lisa 2.315.0 → 2.316.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +13 -2
- 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/hooks/enforce-verification-gate.sh +52 -2
- package/plugins/lisa/rules/eager/automation-runbook-contract.md +25 -0
- package/plugins/lisa/rules/eager/settled-decisions.md +55 -0
- package/plugins/lisa/rules/reference/settled-decisions.md +29 -0
- package/plugins/lisa-agy/plugin.json +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/enforce-verification-gate.sh +52 -2
- package/plugins/lisa-copilot/rules/eager/automation-runbook-contract.md +25 -0
- package/plugins/lisa-copilot/rules/eager/settled-decisions.md +55 -0
- package/plugins/lisa-copilot/rules/reference/settled-decisions.md +29 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/enforce-verification-gate.sh +52 -2
- package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +25 -0
- package/plugins/lisa-cursor/rules/settled-decisions-reference.mdc +34 -0
- package/plugins/lisa-cursor/rules/settled-decisions.mdc +60 -0
- 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/enforce-verification-gate.sh +52 -2
- package/plugins/src/base/rules/eager/automation-runbook-contract.md +25 -0
- package/plugins/src/base/rules/eager/settled-decisions.md +55 -0
- package/plugins/src/base/rules/reference/settled-decisions.md +29 -0
package/package.json
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"brace-expansion": ">=5.0.8"
|
|
116
116
|
},
|
|
117
117
|
"name": "@codyswann/lisa",
|
|
118
|
-
"version": "2.
|
|
118
|
+
"version": "2.316.0",
|
|
119
119
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
120
120
|
"main": "dist/index.js",
|
|
121
121
|
"exports": {
|
|
@@ -132,6 +132,7 @@ STATE_DIR="${TMPDIR:-/tmp}/lisa-verification-gate"
|
|
|
132
132
|
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
133
133
|
|
|
134
134
|
ARM_FLAG="${STATE_DIR}/${SESSION_ID}.armed"
|
|
135
|
+
ARM_PLAN_FILE="${STATE_DIR}/${SESSION_ID}.plan"
|
|
135
136
|
SUBAGENT_FLAG="${STATE_DIR}/${SESSION_ID}.subagent"
|
|
136
137
|
COUNT_FILE="${STATE_DIR}/${SESSION_ID}.blocks"
|
|
137
138
|
|
|
@@ -149,6 +150,19 @@ arm_once() {
|
|
|
149
150
|
[ -f "$ARM_FLAG" ] || touch "$ARM_FLAG" 2>/dev/null || true
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
record_current_plan() {
|
|
154
|
+
local plan="$1"
|
|
155
|
+
[ -n "$plan" ] || return 0
|
|
156
|
+
[ -f "$ARM_PLAN_FILE" ] && return 0
|
|
157
|
+
printf '%s\n' "$plan" > "$ARM_PLAN_FILE" 2>/dev/null || true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
plan_from_prompt() {
|
|
161
|
+
printf '%s' "$1" |
|
|
162
|
+
sed -nE '1{s/^[[:space:]]*\/(lisa:implement|implement)[[:space:]]+([^[:space:]]+).*$/\2/p;}' |
|
|
163
|
+
tr '[:upper:]' '[:lower:]'
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
case "$HOOK_EVENT" in
|
|
153
167
|
SubagentStart)
|
|
154
168
|
touch "$SUBAGENT_FLAG" 2>/dev/null || true
|
|
@@ -162,6 +176,7 @@ case "$HOOK_EVENT" in
|
|
|
162
176
|
case "$LEADING" in
|
|
163
177
|
/lisa:implement*|/implement*)
|
|
164
178
|
arm_once
|
|
179
|
+
record_current_plan "$(plan_from_prompt "$LEADING")"
|
|
165
180
|
;;
|
|
166
181
|
esac
|
|
167
182
|
fi
|
|
@@ -175,6 +190,8 @@ case "$HOOK_EVENT" in
|
|
|
175
190
|
case "$SKILL_NAME" in
|
|
176
191
|
lisa-implement|implement)
|
|
177
192
|
arm_once
|
|
193
|
+
SKILL_ARGUMENTS=$(printf '%s' "$INPUT" | jq -r '.tool_input.arguments // .tool_input.input // empty' 2>/dev/null || true)
|
|
194
|
+
record_current_plan "$(printf '%s' "$SKILL_ARGUMENTS" | awk '{print tolower($1)}')"
|
|
178
195
|
;;
|
|
179
196
|
esac
|
|
180
197
|
fi
|
|
@@ -205,6 +222,39 @@ fi
|
|
|
205
222
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
206
223
|
VERDICT_FILE="${PROJECT_DIR}/.lisa/verification-status.json"
|
|
207
224
|
|
|
225
|
+
# A completed flow's verdict is a shipped record. The next flow in the same
|
|
226
|
+
# worktree writes the SAME path and destroys it — losing the evidence that
|
|
227
|
+
# proved the earlier work, and gating the new run against a verdict whose
|
|
228
|
+
# `plan` names something else entirely. Preserve any verdict belonging to a
|
|
229
|
+
# different plan before this run can overwrite it.
|
|
230
|
+
#
|
|
231
|
+
# Keyed on `.plan`, so re-running the same plan still overwrites in place and
|
|
232
|
+
# no archive accumulates.
|
|
233
|
+
preserve_foreign_verdict() {
|
|
234
|
+
[ -f "$VERDICT_FILE" ] || return 0
|
|
235
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
236
|
+
|
|
237
|
+
local prior_plan current_plan archive
|
|
238
|
+
prior_plan=$(jq -r '.plan // empty' "$VERDICT_FILE" 2>/dev/null || true)
|
|
239
|
+
[ -n "$prior_plan" ] || return 0
|
|
240
|
+
case "$prior_plan" in
|
|
241
|
+
*[!A-Za-z0-9._-]*)
|
|
242
|
+
return 0
|
|
243
|
+
;;
|
|
244
|
+
esac
|
|
245
|
+
current_plan=$(cat "$ARM_PLAN_FILE" 2>/dev/null || true)
|
|
246
|
+
[ -z "$current_plan" ] || [ "$prior_plan" != "$current_plan" ] || return 0
|
|
247
|
+
|
|
248
|
+
# Only archive a verdict written BEFORE this flow armed — anything newer
|
|
249
|
+
# belongs to the current run.
|
|
250
|
+
[ "$VERDICT_FILE" -ot "$ARM_FLAG" ] || return 0
|
|
251
|
+
|
|
252
|
+
archive="${PROJECT_DIR}/.lisa/verification-status.${prior_plan}.json"
|
|
253
|
+
[ -f "$archive" ] || cp -p "$VERDICT_FILE" "$archive" 2>/dev/null || true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
preserve_foreign_verdict
|
|
257
|
+
|
|
208
258
|
# Set by the v2 path when a claim/evidence violation is what closed the gate,
|
|
209
259
|
# so the block message can state the real reason instead of the v1 fallback.
|
|
210
260
|
V2_BLOCK_REASON=""
|
|
@@ -387,7 +437,7 @@ verdict_is_terminal() {
|
|
|
387
437
|
if verdict_is_terminal; then
|
|
388
438
|
# Gate satisfied — disarm so a follow-up stop in the same session is not
|
|
389
439
|
# re-gated against the now-consumed verdict, and allow the stop.
|
|
390
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
440
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
391
441
|
exit 0
|
|
392
442
|
fi
|
|
393
443
|
|
|
@@ -400,7 +450,7 @@ COUNT=$((COUNT + 1))
|
|
|
400
450
|
echo "$COUNT" > "$COUNT_FILE" 2>/dev/null || true
|
|
401
451
|
|
|
402
452
|
if [ "$COUNT" -gt "$MAX_BLOCKS" ]; then
|
|
403
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
453
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
404
454
|
cat >&2 <<EOF
|
|
405
455
|
Verification gate: still no passing verdict after ${MAX_BLOCKS} attempts.
|
|
406
456
|
Releasing the stop gate to avoid an infinite loop. The /lisa:implement Verify
|
|
@@ -15,6 +15,31 @@ Membership is **registration, not skill-existence**: a loop is under this contra
|
|
|
15
15
|
registered as a scheduled automation, and registering a new one pulls it in automatically. There is
|
|
16
16
|
no hardcoded roster of loops anywhere.
|
|
17
17
|
|
|
18
|
+
### Interactive flows are members too
|
|
19
|
+
|
|
20
|
+
The outcome vocabulary is **not cron-specific** — it answers "did this need me?", which an operator
|
|
21
|
+
asks of an interactive run exactly as often as of a scheduled one. Every Lisa flow that terminates
|
|
22
|
+
is a member: `lisa-implement`, `lisa-verify`, `lisa-plan`, `lisa-git-submit-pr`,
|
|
23
|
+
`lisa-drive-pr-to-merge`, `lisa-research`, and any skill invoked as a slash command.
|
|
24
|
+
|
|
25
|
+
For an interactive flow the required run record is the **final user-facing message**, not a JSONL
|
|
26
|
+
row written by `automation-run-record.mjs`. Interactive flows may also record local JSONL telemetry
|
|
27
|
+
when a specific skill owns that surface, but this contract's mandatory record is the final answer.
|
|
28
|
+
It opens with the outcome and the operator action, before any narrative:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
change-proved — nothing for you. PR #6393 open, auto-merge on, CI running.
|
|
32
|
+
approval-requested — need a decision: ship the 135 Regular→Bold flips, or hold for design sign-off?
|
|
33
|
+
recovery-required — need you: staging E2E gate red for congestion; rerun or admin-merge.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The operator must learn whether they are needed **from the first line**, without reading the report.
|
|
37
|
+
Findings, evidence and caveats follow; they never replace the action line and never precede it.
|
|
38
|
+
|
|
39
|
+
An interactive flow that ends in prose with the action buried — or absent — is the same contract
|
|
40
|
+
violation as a silent cron exit. "I flagged X, I noticed Y, worth knowing Z" is narrative, not an
|
|
41
|
+
outcome. If nothing is needed, say **"nothing for you"** in those words and stop.
|
|
42
|
+
|
|
18
43
|
## The six run outcomes
|
|
19
44
|
|
|
20
45
|
Exactly one per run:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Settled Decisions (load-bearing)
|
|
2
|
+
|
|
3
|
+
**Never ask the operator a question that a standing preference or your own gathered evidence has
|
|
4
|
+
already answered.** Re-asking a settled decision is not caution — it hands back a choice the
|
|
5
|
+
operator already made and makes them make it twice.
|
|
6
|
+
|
|
7
|
+
## The test
|
|
8
|
+
|
|
9
|
+
Before asking anything, check the three sources that may already hold the answer:
|
|
10
|
+
|
|
11
|
+
1. **A standing preference** — something the operator has told you once and expects to hold:
|
|
12
|
+
recorded in project memory, `.lisa.config.json`, a project rule, `CLAUDE.md`/`AGENTS.md`, or
|
|
13
|
+
stated earlier in this conversation. "Every PR gets auto-merge and gets watched to merge" is a
|
|
14
|
+
standing preference; asking "want me to watch this PR?" violates it.
|
|
15
|
+
2. **Evidence you already gathered** — if the research, measurement or code read you just performed
|
|
16
|
+
resolves the question, the question is answered. Report the decision and the evidence for it.
|
|
17
|
+
3. **A convention with an obvious default** — where one option is clearly conventional and the other
|
|
18
|
+
needs a reason, take the conventional one and say so in one line.
|
|
19
|
+
|
|
20
|
+
If any source answers it: **act, and state the decision in a clause.** Do not convert it into a
|
|
21
|
+
question.
|
|
22
|
+
|
|
23
|
+
## When asking IS right
|
|
24
|
+
|
|
25
|
+
Ask when the answer would change the work *and* you genuinely cannot derive it:
|
|
26
|
+
|
|
27
|
+
- The options lead to materially different deliverables and nothing in scope decides between them.
|
|
28
|
+
- Proceeding on a wrong assumption would be unsafe, destructive, or waste substantial work.
|
|
29
|
+
- The answer is a human judgement the artifacts do not contain — a product decision, a design
|
|
30
|
+
vocabulary that does not exist yet, a risk the operator owns.
|
|
31
|
+
|
|
32
|
+
That kind of question is load-bearing and should be asked plainly, once, with a recommendation.
|
|
33
|
+
|
|
34
|
+
## The failure mode this rule exists to stop
|
|
35
|
+
|
|
36
|
+
Asking feels collaborative, so it gets over-applied — especially at the end of a report, where a
|
|
37
|
+
trailing question reads as deference. It is not deference when the answer was already given; it is
|
|
38
|
+
work handed back. Two specific tells:
|
|
39
|
+
|
|
40
|
+
- **Half-applying an instruction.** Doing the first half of a standing preference automatically and
|
|
41
|
+
asking permission for the second half of the same preference.
|
|
42
|
+
- **Asking after the research answered it.** Completing an investigation that points one direction
|
|
43
|
+
unambiguously, then presenting the conclusion as an open question.
|
|
44
|
+
|
|
45
|
+
## Partial application is worse than either extreme
|
|
46
|
+
|
|
47
|
+
If a standing preference covers a multi-step behavior, apply **all** of it. Applying part and asking
|
|
48
|
+
about the rest produces the worst outcome: the operator is interrupted *and* the instruction was not
|
|
49
|
+
honored.
|
|
50
|
+
|
|
51
|
+
## Recording, not asking
|
|
52
|
+
|
|
53
|
+
When you take a settled decision, make it auditable in one clause — "auto-merge on, per your standing
|
|
54
|
+
preference", "scoped app-wide, because the Android finding decides it". That gives the operator the
|
|
55
|
+
chance to correct it without requiring them to answer first.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Settled Decisions
|
|
2
|
+
|
|
3
|
+
Do not ask the operator to decide something that is already settled by standing preference, evidence you just gathered, or a conventional default. A question is appropriate only when the answer materially changes the work and the answer cannot be derived from available artifacts.
|
|
4
|
+
|
|
5
|
+
## Decision Sources
|
|
6
|
+
|
|
7
|
+
Check these sources before asking:
|
|
8
|
+
|
|
9
|
+
1. Standing preferences recorded in memory, project config, project rules, instruction files, or earlier in the same conversation.
|
|
10
|
+
2. Evidence already gathered during the current run. If research or code inspection resolves the choice, report the decision and cite the evidence.
|
|
11
|
+
3. Conventional defaults where one option is clearly standard and the alternative needs a reason.
|
|
12
|
+
|
|
13
|
+
If one of those sources answers the question, act on it and state the basis briefly.
|
|
14
|
+
|
|
15
|
+
## When To Ask
|
|
16
|
+
|
|
17
|
+
Ask only when the answer changes the deliverable and cannot be inferred. That includes product decisions, design vocabulary that does not exist yet, destructive or unsafe assumptions, or choices that would waste substantial work if guessed wrong.
|
|
18
|
+
|
|
19
|
+
When asking, ask once, plainly, with a recommended option.
|
|
20
|
+
|
|
21
|
+
## Common Violations
|
|
22
|
+
|
|
23
|
+
Half-applying a standing instruction is a violation. If a preference covers a multi-step behavior, apply the whole behavior instead of doing one part and asking about the rest.
|
|
24
|
+
|
|
25
|
+
Asking after the research answered the question is also a violation. When gathered evidence points one way unambiguously, treat it as a decision and make the reasoning auditable in the report.
|
|
26
|
+
|
|
27
|
+
## Reporting
|
|
28
|
+
|
|
29
|
+
Record settled decisions in a short clause, such as "auto-merge on, per standing preference" or "scoped app-wide, because the Android finding decides it." This gives the operator a chance to correct the decision without requiring an avoidable question first.
|
|
@@ -132,6 +132,7 @@ STATE_DIR="${TMPDIR:-/tmp}/lisa-verification-gate"
|
|
|
132
132
|
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
133
133
|
|
|
134
134
|
ARM_FLAG="${STATE_DIR}/${SESSION_ID}.armed"
|
|
135
|
+
ARM_PLAN_FILE="${STATE_DIR}/${SESSION_ID}.plan"
|
|
135
136
|
SUBAGENT_FLAG="${STATE_DIR}/${SESSION_ID}.subagent"
|
|
136
137
|
COUNT_FILE="${STATE_DIR}/${SESSION_ID}.blocks"
|
|
137
138
|
|
|
@@ -149,6 +150,19 @@ arm_once() {
|
|
|
149
150
|
[ -f "$ARM_FLAG" ] || touch "$ARM_FLAG" 2>/dev/null || true
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
record_current_plan() {
|
|
154
|
+
local plan="$1"
|
|
155
|
+
[ -n "$plan" ] || return 0
|
|
156
|
+
[ -f "$ARM_PLAN_FILE" ] && return 0
|
|
157
|
+
printf '%s\n' "$plan" > "$ARM_PLAN_FILE" 2>/dev/null || true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
plan_from_prompt() {
|
|
161
|
+
printf '%s' "$1" |
|
|
162
|
+
sed -nE '1{s/^[[:space:]]*\/(lisa:implement|implement)[[:space:]]+([^[:space:]]+).*$/\2/p;}' |
|
|
163
|
+
tr '[:upper:]' '[:lower:]'
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
case "$HOOK_EVENT" in
|
|
153
167
|
SubagentStart)
|
|
154
168
|
touch "$SUBAGENT_FLAG" 2>/dev/null || true
|
|
@@ -162,6 +176,7 @@ case "$HOOK_EVENT" in
|
|
|
162
176
|
case "$LEADING" in
|
|
163
177
|
/lisa:implement*|/implement*)
|
|
164
178
|
arm_once
|
|
179
|
+
record_current_plan "$(plan_from_prompt "$LEADING")"
|
|
165
180
|
;;
|
|
166
181
|
esac
|
|
167
182
|
fi
|
|
@@ -175,6 +190,8 @@ case "$HOOK_EVENT" in
|
|
|
175
190
|
case "$SKILL_NAME" in
|
|
176
191
|
lisa-implement|implement)
|
|
177
192
|
arm_once
|
|
193
|
+
SKILL_ARGUMENTS=$(printf '%s' "$INPUT" | jq -r '.tool_input.arguments // .tool_input.input // empty' 2>/dev/null || true)
|
|
194
|
+
record_current_plan "$(printf '%s' "$SKILL_ARGUMENTS" | awk '{print tolower($1)}')"
|
|
178
195
|
;;
|
|
179
196
|
esac
|
|
180
197
|
fi
|
|
@@ -205,6 +222,39 @@ fi
|
|
|
205
222
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
206
223
|
VERDICT_FILE="${PROJECT_DIR}/.lisa/verification-status.json"
|
|
207
224
|
|
|
225
|
+
# A completed flow's verdict is a shipped record. The next flow in the same
|
|
226
|
+
# worktree writes the SAME path and destroys it — losing the evidence that
|
|
227
|
+
# proved the earlier work, and gating the new run against a verdict whose
|
|
228
|
+
# `plan` names something else entirely. Preserve any verdict belonging to a
|
|
229
|
+
# different plan before this run can overwrite it.
|
|
230
|
+
#
|
|
231
|
+
# Keyed on `.plan`, so re-running the same plan still overwrites in place and
|
|
232
|
+
# no archive accumulates.
|
|
233
|
+
preserve_foreign_verdict() {
|
|
234
|
+
[ -f "$VERDICT_FILE" ] || return 0
|
|
235
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
236
|
+
|
|
237
|
+
local prior_plan current_plan archive
|
|
238
|
+
prior_plan=$(jq -r '.plan // empty' "$VERDICT_FILE" 2>/dev/null || true)
|
|
239
|
+
[ -n "$prior_plan" ] || return 0
|
|
240
|
+
case "$prior_plan" in
|
|
241
|
+
*[!A-Za-z0-9._-]*)
|
|
242
|
+
return 0
|
|
243
|
+
;;
|
|
244
|
+
esac
|
|
245
|
+
current_plan=$(cat "$ARM_PLAN_FILE" 2>/dev/null || true)
|
|
246
|
+
[ -z "$current_plan" ] || [ "$prior_plan" != "$current_plan" ] || return 0
|
|
247
|
+
|
|
248
|
+
# Only archive a verdict written BEFORE this flow armed — anything newer
|
|
249
|
+
# belongs to the current run.
|
|
250
|
+
[ "$VERDICT_FILE" -ot "$ARM_FLAG" ] || return 0
|
|
251
|
+
|
|
252
|
+
archive="${PROJECT_DIR}/.lisa/verification-status.${prior_plan}.json"
|
|
253
|
+
[ -f "$archive" ] || cp -p "$VERDICT_FILE" "$archive" 2>/dev/null || true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
preserve_foreign_verdict
|
|
257
|
+
|
|
208
258
|
# Set by the v2 path when a claim/evidence violation is what closed the gate,
|
|
209
259
|
# so the block message can state the real reason instead of the v1 fallback.
|
|
210
260
|
V2_BLOCK_REASON=""
|
|
@@ -387,7 +437,7 @@ verdict_is_terminal() {
|
|
|
387
437
|
if verdict_is_terminal; then
|
|
388
438
|
# Gate satisfied — disarm so a follow-up stop in the same session is not
|
|
389
439
|
# re-gated against the now-consumed verdict, and allow the stop.
|
|
390
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
440
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
391
441
|
exit 0
|
|
392
442
|
fi
|
|
393
443
|
|
|
@@ -400,7 +450,7 @@ COUNT=$((COUNT + 1))
|
|
|
400
450
|
echo "$COUNT" > "$COUNT_FILE" 2>/dev/null || true
|
|
401
451
|
|
|
402
452
|
if [ "$COUNT" -gt "$MAX_BLOCKS" ]; then
|
|
403
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
453
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
404
454
|
cat >&2 <<EOF
|
|
405
455
|
Verification gate: still no passing verdict after ${MAX_BLOCKS} attempts.
|
|
406
456
|
Releasing the stop gate to avoid an infinite loop. The /lisa:implement Verify
|
|
@@ -15,6 +15,31 @@ Membership is **registration, not skill-existence**: a loop is under this contra
|
|
|
15
15
|
registered as a scheduled automation, and registering a new one pulls it in automatically. There is
|
|
16
16
|
no hardcoded roster of loops anywhere.
|
|
17
17
|
|
|
18
|
+
### Interactive flows are members too
|
|
19
|
+
|
|
20
|
+
The outcome vocabulary is **not cron-specific** — it answers "did this need me?", which an operator
|
|
21
|
+
asks of an interactive run exactly as often as of a scheduled one. Every Lisa flow that terminates
|
|
22
|
+
is a member: `lisa-implement`, `lisa-verify`, `lisa-plan`, `lisa-git-submit-pr`,
|
|
23
|
+
`lisa-drive-pr-to-merge`, `lisa-research`, and any skill invoked as a slash command.
|
|
24
|
+
|
|
25
|
+
For an interactive flow the required run record is the **final user-facing message**, not a JSONL
|
|
26
|
+
row written by `automation-run-record.mjs`. Interactive flows may also record local JSONL telemetry
|
|
27
|
+
when a specific skill owns that surface, but this contract's mandatory record is the final answer.
|
|
28
|
+
It opens with the outcome and the operator action, before any narrative:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
change-proved — nothing for you. PR #6393 open, auto-merge on, CI running.
|
|
32
|
+
approval-requested — need a decision: ship the 135 Regular→Bold flips, or hold for design sign-off?
|
|
33
|
+
recovery-required — need you: staging E2E gate red for congestion; rerun or admin-merge.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The operator must learn whether they are needed **from the first line**, without reading the report.
|
|
37
|
+
Findings, evidence and caveats follow; they never replace the action line and never precede it.
|
|
38
|
+
|
|
39
|
+
An interactive flow that ends in prose with the action buried — or absent — is the same contract
|
|
40
|
+
violation as a silent cron exit. "I flagged X, I noticed Y, worth knowing Z" is narrative, not an
|
|
41
|
+
outcome. If nothing is needed, say **"nothing for you"** in those words and stop.
|
|
42
|
+
|
|
18
43
|
## The six run outcomes
|
|
19
44
|
|
|
20
45
|
Exactly one per run:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Settled Decisions (load-bearing)
|
|
2
|
+
|
|
3
|
+
**Never ask the operator a question that a standing preference or your own gathered evidence has
|
|
4
|
+
already answered.** Re-asking a settled decision is not caution — it hands back a choice the
|
|
5
|
+
operator already made and makes them make it twice.
|
|
6
|
+
|
|
7
|
+
## The test
|
|
8
|
+
|
|
9
|
+
Before asking anything, check the three sources that may already hold the answer:
|
|
10
|
+
|
|
11
|
+
1. **A standing preference** — something the operator has told you once and expects to hold:
|
|
12
|
+
recorded in project memory, `.lisa.config.json`, a project rule, `CLAUDE.md`/`AGENTS.md`, or
|
|
13
|
+
stated earlier in this conversation. "Every PR gets auto-merge and gets watched to merge" is a
|
|
14
|
+
standing preference; asking "want me to watch this PR?" violates it.
|
|
15
|
+
2. **Evidence you already gathered** — if the research, measurement or code read you just performed
|
|
16
|
+
resolves the question, the question is answered. Report the decision and the evidence for it.
|
|
17
|
+
3. **A convention with an obvious default** — where one option is clearly conventional and the other
|
|
18
|
+
needs a reason, take the conventional one and say so in one line.
|
|
19
|
+
|
|
20
|
+
If any source answers it: **act, and state the decision in a clause.** Do not convert it into a
|
|
21
|
+
question.
|
|
22
|
+
|
|
23
|
+
## When asking IS right
|
|
24
|
+
|
|
25
|
+
Ask when the answer would change the work *and* you genuinely cannot derive it:
|
|
26
|
+
|
|
27
|
+
- The options lead to materially different deliverables and nothing in scope decides between them.
|
|
28
|
+
- Proceeding on a wrong assumption would be unsafe, destructive, or waste substantial work.
|
|
29
|
+
- The answer is a human judgement the artifacts do not contain — a product decision, a design
|
|
30
|
+
vocabulary that does not exist yet, a risk the operator owns.
|
|
31
|
+
|
|
32
|
+
That kind of question is load-bearing and should be asked plainly, once, with a recommendation.
|
|
33
|
+
|
|
34
|
+
## The failure mode this rule exists to stop
|
|
35
|
+
|
|
36
|
+
Asking feels collaborative, so it gets over-applied — especially at the end of a report, where a
|
|
37
|
+
trailing question reads as deference. It is not deference when the answer was already given; it is
|
|
38
|
+
work handed back. Two specific tells:
|
|
39
|
+
|
|
40
|
+
- **Half-applying an instruction.** Doing the first half of a standing preference automatically and
|
|
41
|
+
asking permission for the second half of the same preference.
|
|
42
|
+
- **Asking after the research answered it.** Completing an investigation that points one direction
|
|
43
|
+
unambiguously, then presenting the conclusion as an open question.
|
|
44
|
+
|
|
45
|
+
## Partial application is worse than either extreme
|
|
46
|
+
|
|
47
|
+
If a standing preference covers a multi-step behavior, apply **all** of it. Applying part and asking
|
|
48
|
+
about the rest produces the worst outcome: the operator is interrupted *and* the instruction was not
|
|
49
|
+
honored.
|
|
50
|
+
|
|
51
|
+
## Recording, not asking
|
|
52
|
+
|
|
53
|
+
When you take a settled decision, make it auditable in one clause — "auto-merge on, per your standing
|
|
54
|
+
preference", "scoped app-wide, because the Android finding decides it". That gives the operator the
|
|
55
|
+
chance to correct it without requiring them to answer first.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Settled Decisions
|
|
2
|
+
|
|
3
|
+
Do not ask the operator to decide something that is already settled by standing preference, evidence you just gathered, or a conventional default. A question is appropriate only when the answer materially changes the work and the answer cannot be derived from available artifacts.
|
|
4
|
+
|
|
5
|
+
## Decision Sources
|
|
6
|
+
|
|
7
|
+
Check these sources before asking:
|
|
8
|
+
|
|
9
|
+
1. Standing preferences recorded in memory, project config, project rules, instruction files, or earlier in the same conversation.
|
|
10
|
+
2. Evidence already gathered during the current run. If research or code inspection resolves the choice, report the decision and cite the evidence.
|
|
11
|
+
3. Conventional defaults where one option is clearly standard and the alternative needs a reason.
|
|
12
|
+
|
|
13
|
+
If one of those sources answers the question, act on it and state the basis briefly.
|
|
14
|
+
|
|
15
|
+
## When To Ask
|
|
16
|
+
|
|
17
|
+
Ask only when the answer changes the deliverable and cannot be inferred. That includes product decisions, design vocabulary that does not exist yet, destructive or unsafe assumptions, or choices that would waste substantial work if guessed wrong.
|
|
18
|
+
|
|
19
|
+
When asking, ask once, plainly, with a recommended option.
|
|
20
|
+
|
|
21
|
+
## Common Violations
|
|
22
|
+
|
|
23
|
+
Half-applying a standing instruction is a violation. If a preference covers a multi-step behavior, apply the whole behavior instead of doing one part and asking about the rest.
|
|
24
|
+
|
|
25
|
+
Asking after the research answered the question is also a violation. When gathered evidence points one way unambiguously, treat it as a decision and make the reasoning auditable in the report.
|
|
26
|
+
|
|
27
|
+
## Reporting
|
|
28
|
+
|
|
29
|
+
Record settled decisions in a short clause, such as "auto-merge on, per standing preference" or "scoped app-wide, because the Android finding decides it." This gives the operator a chance to correct the decision without requiring an avoidable question first.
|
|
@@ -132,6 +132,7 @@ STATE_DIR="${TMPDIR:-/tmp}/lisa-verification-gate"
|
|
|
132
132
|
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
|
|
133
133
|
|
|
134
134
|
ARM_FLAG="${STATE_DIR}/${SESSION_ID}.armed"
|
|
135
|
+
ARM_PLAN_FILE="${STATE_DIR}/${SESSION_ID}.plan"
|
|
135
136
|
SUBAGENT_FLAG="${STATE_DIR}/${SESSION_ID}.subagent"
|
|
136
137
|
COUNT_FILE="${STATE_DIR}/${SESSION_ID}.blocks"
|
|
137
138
|
|
|
@@ -149,6 +150,19 @@ arm_once() {
|
|
|
149
150
|
[ -f "$ARM_FLAG" ] || touch "$ARM_FLAG" 2>/dev/null || true
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
record_current_plan() {
|
|
154
|
+
local plan="$1"
|
|
155
|
+
[ -n "$plan" ] || return 0
|
|
156
|
+
[ -f "$ARM_PLAN_FILE" ] && return 0
|
|
157
|
+
printf '%s\n' "$plan" > "$ARM_PLAN_FILE" 2>/dev/null || true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
plan_from_prompt() {
|
|
161
|
+
printf '%s' "$1" |
|
|
162
|
+
sed -nE '1{s/^[[:space:]]*\/(lisa:implement|implement)[[:space:]]+([^[:space:]]+).*$/\2/p;}' |
|
|
163
|
+
tr '[:upper:]' '[:lower:]'
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
case "$HOOK_EVENT" in
|
|
153
167
|
SubagentStart)
|
|
154
168
|
touch "$SUBAGENT_FLAG" 2>/dev/null || true
|
|
@@ -162,6 +176,7 @@ case "$HOOK_EVENT" in
|
|
|
162
176
|
case "$LEADING" in
|
|
163
177
|
/lisa:implement*|/implement*)
|
|
164
178
|
arm_once
|
|
179
|
+
record_current_plan "$(plan_from_prompt "$LEADING")"
|
|
165
180
|
;;
|
|
166
181
|
esac
|
|
167
182
|
fi
|
|
@@ -175,6 +190,8 @@ case "$HOOK_EVENT" in
|
|
|
175
190
|
case "$SKILL_NAME" in
|
|
176
191
|
lisa-implement|implement)
|
|
177
192
|
arm_once
|
|
193
|
+
SKILL_ARGUMENTS=$(printf '%s' "$INPUT" | jq -r '.tool_input.arguments // .tool_input.input // empty' 2>/dev/null || true)
|
|
194
|
+
record_current_plan "$(printf '%s' "$SKILL_ARGUMENTS" | awk '{print tolower($1)}')"
|
|
178
195
|
;;
|
|
179
196
|
esac
|
|
180
197
|
fi
|
|
@@ -205,6 +222,39 @@ fi
|
|
|
205
222
|
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-.}"
|
|
206
223
|
VERDICT_FILE="${PROJECT_DIR}/.lisa/verification-status.json"
|
|
207
224
|
|
|
225
|
+
# A completed flow's verdict is a shipped record. The next flow in the same
|
|
226
|
+
# worktree writes the SAME path and destroys it — losing the evidence that
|
|
227
|
+
# proved the earlier work, and gating the new run against a verdict whose
|
|
228
|
+
# `plan` names something else entirely. Preserve any verdict belonging to a
|
|
229
|
+
# different plan before this run can overwrite it.
|
|
230
|
+
#
|
|
231
|
+
# Keyed on `.plan`, so re-running the same plan still overwrites in place and
|
|
232
|
+
# no archive accumulates.
|
|
233
|
+
preserve_foreign_verdict() {
|
|
234
|
+
[ -f "$VERDICT_FILE" ] || return 0
|
|
235
|
+
command -v jq >/dev/null 2>&1 || return 0
|
|
236
|
+
|
|
237
|
+
local prior_plan current_plan archive
|
|
238
|
+
prior_plan=$(jq -r '.plan // empty' "$VERDICT_FILE" 2>/dev/null || true)
|
|
239
|
+
[ -n "$prior_plan" ] || return 0
|
|
240
|
+
case "$prior_plan" in
|
|
241
|
+
*[!A-Za-z0-9._-]*)
|
|
242
|
+
return 0
|
|
243
|
+
;;
|
|
244
|
+
esac
|
|
245
|
+
current_plan=$(cat "$ARM_PLAN_FILE" 2>/dev/null || true)
|
|
246
|
+
[ -z "$current_plan" ] || [ "$prior_plan" != "$current_plan" ] || return 0
|
|
247
|
+
|
|
248
|
+
# Only archive a verdict written BEFORE this flow armed — anything newer
|
|
249
|
+
# belongs to the current run.
|
|
250
|
+
[ "$VERDICT_FILE" -ot "$ARM_FLAG" ] || return 0
|
|
251
|
+
|
|
252
|
+
archive="${PROJECT_DIR}/.lisa/verification-status.${prior_plan}.json"
|
|
253
|
+
[ -f "$archive" ] || cp -p "$VERDICT_FILE" "$archive" 2>/dev/null || true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
preserve_foreign_verdict
|
|
257
|
+
|
|
208
258
|
# Set by the v2 path when a claim/evidence violation is what closed the gate,
|
|
209
259
|
# so the block message can state the real reason instead of the v1 fallback.
|
|
210
260
|
V2_BLOCK_REASON=""
|
|
@@ -387,7 +437,7 @@ verdict_is_terminal() {
|
|
|
387
437
|
if verdict_is_terminal; then
|
|
388
438
|
# Gate satisfied — disarm so a follow-up stop in the same session is not
|
|
389
439
|
# re-gated against the now-consumed verdict, and allow the stop.
|
|
390
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
440
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
391
441
|
exit 0
|
|
392
442
|
fi
|
|
393
443
|
|
|
@@ -400,7 +450,7 @@ COUNT=$((COUNT + 1))
|
|
|
400
450
|
echo "$COUNT" > "$COUNT_FILE" 2>/dev/null || true
|
|
401
451
|
|
|
402
452
|
if [ "$COUNT" -gt "$MAX_BLOCKS" ]; then
|
|
403
|
-
rm -f "$ARM_FLAG" "$COUNT_FILE" 2>/dev/null || true
|
|
453
|
+
rm -f "$ARM_FLAG" "$ARM_PLAN_FILE" "$COUNT_FILE" 2>/dev/null || true
|
|
404
454
|
cat >&2 <<EOF
|
|
405
455
|
Verification gate: still no passing verdict after ${MAX_BLOCKS} attempts.
|
|
406
456
|
Releasing the stop gate to avoid an infinite loop. The /lisa:implement Verify
|