@codyswann/lisa 4.34.3 → 4.34.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lisa-gates.mjs +40 -1
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +14 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +10 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +5 -4
- 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-github-build-intake/SKILL.md +35 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +35 -0
- package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +134 -0
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +35 -0
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +35 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +134 -0
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +35 -0
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +35 -0
- 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/scripts/intake-blocker-reprobe.mjs +134 -0
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +35 -0
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +35 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +134 -0
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +35 -0
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +35 -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/scripts/intake-blocker-reprobe.mjs +134 -0
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +35 -0
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +35 -0
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +35 -0
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-overwrite/.claude/hooks/worktree-create.sh +174 -12
package/package.json
CHANGED
|
@@ -127,6 +127,7 @@
|
|
|
127
127
|
"resolutions": {
|
|
128
128
|
"@isaacs/brace-expansion": "^5.0.1",
|
|
129
129
|
"axios": ">=1.15.2",
|
|
130
|
+
"browserslist": ">=4.28.8",
|
|
130
131
|
"deepmerge-ts": "^8.0.1",
|
|
131
132
|
"esbuild": ">=0.28.1",
|
|
132
133
|
"eslint-plugin-react-hooks": "^7.0.0",
|
|
@@ -165,7 +166,7 @@
|
|
|
165
166
|
"zod-validation-error": "^4.0.0"
|
|
166
167
|
},
|
|
167
168
|
"name": "@codyswann/lisa",
|
|
168
|
-
"version": "4.34.
|
|
169
|
+
"version": "4.34.5",
|
|
169
170
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
170
171
|
"main": "dist/index.js",
|
|
171
172
|
"exports": {
|
|
@@ -313,7 +314,7 @@
|
|
|
313
314
|
"test": "tests"
|
|
314
315
|
},
|
|
315
316
|
"types": "./dist/index.d.ts",
|
|
316
|
-
"lisaReleaseCommit": "
|
|
317
|
-
"gitHead": "
|
|
318
|
-
"lisaReleaseTag": "v4.34.
|
|
317
|
+
"lisaReleaseCommit": "f53b310d12af3c0d1e81173e95d9db7c5de23a5c",
|
|
318
|
+
"gitHead": "f53b310d12af3c0d1e81173e95d9db7c5de23a5c",
|
|
319
|
+
"lisaReleaseTag": "v4.34.5"
|
|
319
320
|
}
|
|
@@ -300,6 +300,41 @@ Lifecycle membership is decided by the **`status:` prefix**, never by a pinned m
|
|
|
300
300
|
|
|
301
301
|
### Phase 3 — Process the first eligible ready issue
|
|
302
302
|
|
|
303
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
304
|
+
|
|
305
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
306
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
307
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
308
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
309
|
+
matters.
|
|
310
|
+
|
|
311
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
312
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
313
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
314
|
+
|
|
315
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
316
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
317
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
318
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
319
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
320
|
+
on every item that happens to have one.
|
|
321
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
322
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
323
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
324
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
325
|
+
outside its filter twice over. Call
|
|
326
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
327
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
328
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
329
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
330
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
331
|
+
dispatched.
|
|
332
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
333
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
334
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
335
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
336
|
+
|
|
337
|
+
|
|
303
338
|
#### 3a.0 Repo-scope gate (claim only current-repo issues)
|
|
304
339
|
|
|
305
340
|
GitHub Issues live in one repo by definition, so the scanned repo's issues are usually inherently current-repo. But a planning/umbrella repo's issues can target sibling repos, so this skill still claims only issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -205,6 +205,41 @@ true, and nothing re-read one before this phase. For each pre-work candidate out
|
|
|
205
205
|
|
|
206
206
|
### Phase 3 — Process the first eligible ready ticket
|
|
207
207
|
|
|
208
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
209
|
+
|
|
210
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
211
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
212
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
213
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
214
|
+
matters.
|
|
215
|
+
|
|
216
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
217
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
218
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
219
|
+
|
|
220
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
221
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
222
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
223
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
224
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
225
|
+
on every item that happens to have one.
|
|
226
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
227
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
228
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
229
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
230
|
+
outside its filter twice over. Call
|
|
231
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
232
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
233
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
234
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
235
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
236
|
+
dispatched.
|
|
237
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
238
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
239
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
240
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
241
|
+
|
|
242
|
+
|
|
208
243
|
#### 3a.0 Repo-scope gate (claim only current-repo tickets)
|
|
209
244
|
|
|
210
245
|
A JIRA project can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only tickets for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -199,6 +199,41 @@ classifyPreWorkCandidate({ laneType, labels, body, humanNeededLabel, statedBlock
|
|
|
199
199
|
|
|
200
200
|
### Phase 3 — Process the first eligible ready Issue
|
|
201
201
|
|
|
202
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
203
|
+
|
|
204
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
205
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
206
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
207
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
208
|
+
matters.
|
|
209
|
+
|
|
210
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
211
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
212
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
213
|
+
|
|
214
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
215
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
216
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
217
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
218
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
219
|
+
on every item that happens to have one.
|
|
220
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
221
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
222
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
223
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
224
|
+
outside its filter twice over. Call
|
|
225
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
226
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
227
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
228
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
229
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
230
|
+
dispatched.
|
|
231
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
232
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
233
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
234
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
235
|
+
|
|
236
|
+
|
|
202
237
|
#### 3a.0 Repo-scope gate (claim only current-repo Issues)
|
|
203
238
|
|
|
204
239
|
A Linear team can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only Issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -188,3 +188,137 @@ export function formatReprobeNote(candidate = {}, verdict) {
|
|
|
188
188
|
|
|
189
189
|
return lines.join("\n");
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Marker on the note this module leaves, so a later cycle recognises its own
|
|
194
|
+
* work instead of commenting again.
|
|
195
|
+
*/
|
|
196
|
+
export const HUMAN_GATE_NOTE_MARKER = "<!-- [lisa-human-gate-reconciled] -->";
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Decide whether a candidate ALREADY IN THE READY LANE may be claimed.
|
|
200
|
+
*
|
|
201
|
+
* `classifyPreWorkCandidate` was the only caller of the human-gate test, and it
|
|
202
|
+
* runs on candidates outside the ready lane. An item that reached the ready
|
|
203
|
+
* lane was therefore claimed with the gate never consulted — the check was
|
|
204
|
+
* correct and simply unreachable from the path that matters.
|
|
205
|
+
*
|
|
206
|
+
* This shares `isHumanGated` with the pre-work classifier deliberately. Two
|
|
207
|
+
* copies of a substring test drift, and a drifted gate fails silently: it stops
|
|
208
|
+
* matching, and nothing reports that it stopped.
|
|
209
|
+
*
|
|
210
|
+
* The test stays a plain literal-substring match and is NOT keyed on `reason=`.
|
|
211
|
+
* Markers in the wild carry no `reason=` at all and sit anywhere in the body, so
|
|
212
|
+
* a parser keyed on the structured field would miss those while appearing to
|
|
213
|
+
* work on every item that happens to have one — reproducing this defect one
|
|
214
|
+
* layer down.
|
|
215
|
+
* @param {{ labels?: unknown, body?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
|
|
216
|
+
* @returns {{ claimable: boolean, reason: string, humanGated: boolean }} Claim verdict
|
|
217
|
+
*/
|
|
218
|
+
export function classifyReadyCandidate(input = {}) {
|
|
219
|
+
if (isHumanGated(input)) {
|
|
220
|
+
return { claimable: false, reason: "human-gate", humanGated: true };
|
|
221
|
+
}
|
|
222
|
+
return { claimable: true, reason: "ready-eligible", humanGated: false };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Plan the state repair for a ready-lane item that is held for a person.
|
|
227
|
+
*
|
|
228
|
+
* Skipping such an item is not enough. `lisa-repair-intake` sweeps items that
|
|
229
|
+
* are NOT in the ready role and excludes gated ones outright, so a ready AND
|
|
230
|
+
* gated item falls outside its filter twice over. Skipping alone would leave it
|
|
231
|
+
* re-evaluated and re-rejected every cycle forever, seen by nothing — trading a
|
|
232
|
+
* loud failure for a silent one.
|
|
233
|
+
*
|
|
234
|
+
* So the lane is reconciled to match the hold: the item leaves the pickup queue
|
|
235
|
+
* and gains the human-needed marker. That is the same repair the leaf-only gate
|
|
236
|
+
* already performs for a ready item that must not be dispatched.
|
|
237
|
+
*
|
|
238
|
+
* Idempotent by state rather than by memory: an item already out of the queue
|
|
239
|
+
* and already marked needs nothing, so asking twice yields no second mutation.
|
|
240
|
+
* @param {{
|
|
241
|
+
* labels?: unknown
|
|
242
|
+
* body?: unknown
|
|
243
|
+
* humanNeededLabel?: unknown
|
|
244
|
+
* readyLabel?: unknown
|
|
245
|
+
* alreadyNotified?: unknown
|
|
246
|
+
* }} input - Candidate surfaces plus the configured lane and marker labels
|
|
247
|
+
* @returns {{ gated: boolean, reason: string, actions: { removeReadyRole: boolean, addHumanNeededLabel: boolean, comment: boolean } }} Repair plan
|
|
248
|
+
*/
|
|
249
|
+
export function planHumanGateReconciliation(input = {}) {
|
|
250
|
+
const idle = Object.freeze({
|
|
251
|
+
removeReadyRole: false,
|
|
252
|
+
addHumanNeededLabel: false,
|
|
253
|
+
comment: false,
|
|
254
|
+
});
|
|
255
|
+
if (!isHumanGated(input)) {
|
|
256
|
+
return { gated: false, reason: "not-gated", actions: idle };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const labels = normalizeLabels(input.labels);
|
|
260
|
+
const needed = String(input.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL)
|
|
261
|
+
.trim()
|
|
262
|
+
.toLowerCase();
|
|
263
|
+
const ready = String(input.readyLabel ?? "")
|
|
264
|
+
.trim()
|
|
265
|
+
.toLowerCase();
|
|
266
|
+
const inReadyLane = ready.length > 0 && labels.includes(ready);
|
|
267
|
+
const marked = needed.length > 0 && labels.includes(needed);
|
|
268
|
+
const actions = {
|
|
269
|
+
removeReadyRole: inReadyLane,
|
|
270
|
+
addHumanNeededLabel: needed.length > 0 && !marked,
|
|
271
|
+
comment:
|
|
272
|
+
input.alreadyNotified !== true &&
|
|
273
|
+
(inReadyLane || (needed.length > 0 && !marked)),
|
|
274
|
+
};
|
|
275
|
+
const changed =
|
|
276
|
+
actions.removeReadyRole || actions.addHumanNeededLabel || actions.comment;
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
gated: true,
|
|
280
|
+
reason: changed ? "reconcile" : "already-reconciled",
|
|
281
|
+
actions,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Render the hold notice for a reader who does not know this subsystem.
|
|
287
|
+
*
|
|
288
|
+
* Written for a non-technical operator on purpose — that is who stands at this
|
|
289
|
+
* gate. It names what was found, what changed, and how to undo it, and uses no
|
|
290
|
+
* skill name, label syntax, or phase number, none of which a reader can act on.
|
|
291
|
+
* @returns {string} Comment body, carrying the marker that keeps a re-run quiet
|
|
292
|
+
*/
|
|
293
|
+
export function formatHumanGateNote() {
|
|
294
|
+
return [
|
|
295
|
+
"**On hold for a person**",
|
|
296
|
+
"",
|
|
297
|
+
"- What was found: this item is marked as held for a person, but it was " +
|
|
298
|
+
"also sitting in the queue that agents build from — so it would have " +
|
|
299
|
+
"been picked up and built anyway.",
|
|
300
|
+
"- What changed: it has been taken out of that queue and flagged as " +
|
|
301
|
+
"needing a person, so nothing will build it automatically.",
|
|
302
|
+
"- To resume it: remove the hold note from the description and put it " +
|
|
303
|
+
"back in the build queue.",
|
|
304
|
+
"",
|
|
305
|
+
HUMAN_GATE_NOTE_MARKER,
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Render the cycle-summary line naming what was held.
|
|
311
|
+
*
|
|
312
|
+
* A lane mutation nobody can see afterwards is the same class of problem as the
|
|
313
|
+
* one this fixes, so the run says what it moved — and it keeps "nothing was
|
|
314
|
+
* eligible" distinguishable from "something eligible was held".
|
|
315
|
+
* @param {readonly unknown[]} held - Item references held this cycle
|
|
316
|
+
* @returns {string} One summary line
|
|
317
|
+
*/
|
|
318
|
+
export function summarizeHumanGateHolds(held = []) {
|
|
319
|
+
const names = (Array.isArray(held) ? held : [])
|
|
320
|
+
.map(item => trimmedString(item))
|
|
321
|
+
.filter(name => name.length > 0);
|
|
322
|
+
if (names.length === 0) return "Held for a person: none.";
|
|
323
|
+
return `Held for a person (${String(names.length)}): ${names.join(", ")}.`;
|
|
324
|
+
}
|
|
@@ -300,6 +300,41 @@ Lifecycle membership is decided by the **`status:` prefix**, never by a pinned m
|
|
|
300
300
|
|
|
301
301
|
### Phase 3 — Process the first eligible ready issue
|
|
302
302
|
|
|
303
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
304
|
+
|
|
305
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
306
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
307
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
308
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
309
|
+
matters.
|
|
310
|
+
|
|
311
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
312
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
313
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
314
|
+
|
|
315
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
316
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
317
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
318
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
319
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
320
|
+
on every item that happens to have one.
|
|
321
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
322
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
323
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
324
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
325
|
+
outside its filter twice over. Call
|
|
326
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
327
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
328
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
329
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
330
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
331
|
+
dispatched.
|
|
332
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
333
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
334
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
335
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
336
|
+
|
|
337
|
+
|
|
303
338
|
#### 3a.0 Repo-scope gate (claim only current-repo issues)
|
|
304
339
|
|
|
305
340
|
GitHub Issues live in one repo by definition, so the scanned repo's issues are usually inherently current-repo. But a planning/umbrella repo's issues can target sibling repos, so this skill still claims only issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -205,6 +205,41 @@ true, and nothing re-read one before this phase. For each pre-work candidate out
|
|
|
205
205
|
|
|
206
206
|
### Phase 3 — Process the first eligible ready ticket
|
|
207
207
|
|
|
208
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
209
|
+
|
|
210
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
211
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
212
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
213
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
214
|
+
matters.
|
|
215
|
+
|
|
216
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
217
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
218
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
219
|
+
|
|
220
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
221
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
222
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
223
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
224
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
225
|
+
on every item that happens to have one.
|
|
226
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
227
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
228
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
229
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
230
|
+
outside its filter twice over. Call
|
|
231
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
232
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
233
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
234
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
235
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
236
|
+
dispatched.
|
|
237
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
238
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
239
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
240
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
241
|
+
|
|
242
|
+
|
|
208
243
|
#### 3a.0 Repo-scope gate (claim only current-repo tickets)
|
|
209
244
|
|
|
210
245
|
A JIRA project can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only tickets for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -199,6 +199,41 @@ classifyPreWorkCandidate({ laneType, labels, body, humanNeededLabel, statedBlock
|
|
|
199
199
|
|
|
200
200
|
### Phase 3 — Process the first eligible ready Issue
|
|
201
201
|
|
|
202
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
203
|
+
|
|
204
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
205
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
206
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
207
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
208
|
+
matters.
|
|
209
|
+
|
|
210
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
211
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
212
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
213
|
+
|
|
214
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
215
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
216
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
217
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
218
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
219
|
+
on every item that happens to have one.
|
|
220
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
221
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
222
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
223
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
224
|
+
outside its filter twice over. Call
|
|
225
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
226
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
227
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
228
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
229
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
230
|
+
dispatched.
|
|
231
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
232
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
233
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
234
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
235
|
+
|
|
236
|
+
|
|
202
237
|
#### 3a.0 Repo-scope gate (claim only current-repo Issues)
|
|
203
238
|
|
|
204
239
|
A Linear team can oversee multiple repos (`frontend` / `backend` / `infrastructure`). This skill claims only Issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|
|
@@ -188,3 +188,137 @@ export function formatReprobeNote(candidate = {}, verdict) {
|
|
|
188
188
|
|
|
189
189
|
return lines.join("\n");
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Marker on the note this module leaves, so a later cycle recognises its own
|
|
194
|
+
* work instead of commenting again.
|
|
195
|
+
*/
|
|
196
|
+
export const HUMAN_GATE_NOTE_MARKER = "<!-- [lisa-human-gate-reconciled] -->";
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Decide whether a candidate ALREADY IN THE READY LANE may be claimed.
|
|
200
|
+
*
|
|
201
|
+
* `classifyPreWorkCandidate` was the only caller of the human-gate test, and it
|
|
202
|
+
* runs on candidates outside the ready lane. An item that reached the ready
|
|
203
|
+
* lane was therefore claimed with the gate never consulted — the check was
|
|
204
|
+
* correct and simply unreachable from the path that matters.
|
|
205
|
+
*
|
|
206
|
+
* This shares `isHumanGated` with the pre-work classifier deliberately. Two
|
|
207
|
+
* copies of a substring test drift, and a drifted gate fails silently: it stops
|
|
208
|
+
* matching, and nothing reports that it stopped.
|
|
209
|
+
*
|
|
210
|
+
* The test stays a plain literal-substring match and is NOT keyed on `reason=`.
|
|
211
|
+
* Markers in the wild carry no `reason=` at all and sit anywhere in the body, so
|
|
212
|
+
* a parser keyed on the structured field would miss those while appearing to
|
|
213
|
+
* work on every item that happens to have one — reproducing this defect one
|
|
214
|
+
* layer down.
|
|
215
|
+
* @param {{ labels?: unknown, body?: unknown, humanNeededLabel?: unknown }} input - Candidate surfaces
|
|
216
|
+
* @returns {{ claimable: boolean, reason: string, humanGated: boolean }} Claim verdict
|
|
217
|
+
*/
|
|
218
|
+
export function classifyReadyCandidate(input = {}) {
|
|
219
|
+
if (isHumanGated(input)) {
|
|
220
|
+
return { claimable: false, reason: "human-gate", humanGated: true };
|
|
221
|
+
}
|
|
222
|
+
return { claimable: true, reason: "ready-eligible", humanGated: false };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Plan the state repair for a ready-lane item that is held for a person.
|
|
227
|
+
*
|
|
228
|
+
* Skipping such an item is not enough. `lisa-repair-intake` sweeps items that
|
|
229
|
+
* are NOT in the ready role and excludes gated ones outright, so a ready AND
|
|
230
|
+
* gated item falls outside its filter twice over. Skipping alone would leave it
|
|
231
|
+
* re-evaluated and re-rejected every cycle forever, seen by nothing — trading a
|
|
232
|
+
* loud failure for a silent one.
|
|
233
|
+
*
|
|
234
|
+
* So the lane is reconciled to match the hold: the item leaves the pickup queue
|
|
235
|
+
* and gains the human-needed marker. That is the same repair the leaf-only gate
|
|
236
|
+
* already performs for a ready item that must not be dispatched.
|
|
237
|
+
*
|
|
238
|
+
* Idempotent by state rather than by memory: an item already out of the queue
|
|
239
|
+
* and already marked needs nothing, so asking twice yields no second mutation.
|
|
240
|
+
* @param {{
|
|
241
|
+
* labels?: unknown
|
|
242
|
+
* body?: unknown
|
|
243
|
+
* humanNeededLabel?: unknown
|
|
244
|
+
* readyLabel?: unknown
|
|
245
|
+
* alreadyNotified?: unknown
|
|
246
|
+
* }} input - Candidate surfaces plus the configured lane and marker labels
|
|
247
|
+
* @returns {{ gated: boolean, reason: string, actions: { removeReadyRole: boolean, addHumanNeededLabel: boolean, comment: boolean } }} Repair plan
|
|
248
|
+
*/
|
|
249
|
+
export function planHumanGateReconciliation(input = {}) {
|
|
250
|
+
const idle = Object.freeze({
|
|
251
|
+
removeReadyRole: false,
|
|
252
|
+
addHumanNeededLabel: false,
|
|
253
|
+
comment: false,
|
|
254
|
+
});
|
|
255
|
+
if (!isHumanGated(input)) {
|
|
256
|
+
return { gated: false, reason: "not-gated", actions: idle };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const labels = normalizeLabels(input.labels);
|
|
260
|
+
const needed = String(input.humanNeededLabel ?? DEFAULT_HUMAN_NEEDED_LABEL)
|
|
261
|
+
.trim()
|
|
262
|
+
.toLowerCase();
|
|
263
|
+
const ready = String(input.readyLabel ?? "")
|
|
264
|
+
.trim()
|
|
265
|
+
.toLowerCase();
|
|
266
|
+
const inReadyLane = ready.length > 0 && labels.includes(ready);
|
|
267
|
+
const marked = needed.length > 0 && labels.includes(needed);
|
|
268
|
+
const actions = {
|
|
269
|
+
removeReadyRole: inReadyLane,
|
|
270
|
+
addHumanNeededLabel: needed.length > 0 && !marked,
|
|
271
|
+
comment:
|
|
272
|
+
input.alreadyNotified !== true &&
|
|
273
|
+
(inReadyLane || (needed.length > 0 && !marked)),
|
|
274
|
+
};
|
|
275
|
+
const changed =
|
|
276
|
+
actions.removeReadyRole || actions.addHumanNeededLabel || actions.comment;
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
gated: true,
|
|
280
|
+
reason: changed ? "reconcile" : "already-reconciled",
|
|
281
|
+
actions,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Render the hold notice for a reader who does not know this subsystem.
|
|
287
|
+
*
|
|
288
|
+
* Written for a non-technical operator on purpose — that is who stands at this
|
|
289
|
+
* gate. It names what was found, what changed, and how to undo it, and uses no
|
|
290
|
+
* skill name, label syntax, or phase number, none of which a reader can act on.
|
|
291
|
+
* @returns {string} Comment body, carrying the marker that keeps a re-run quiet
|
|
292
|
+
*/
|
|
293
|
+
export function formatHumanGateNote() {
|
|
294
|
+
return [
|
|
295
|
+
"**On hold for a person**",
|
|
296
|
+
"",
|
|
297
|
+
"- What was found: this item is marked as held for a person, but it was " +
|
|
298
|
+
"also sitting in the queue that agents build from — so it would have " +
|
|
299
|
+
"been picked up and built anyway.",
|
|
300
|
+
"- What changed: it has been taken out of that queue and flagged as " +
|
|
301
|
+
"needing a person, so nothing will build it automatically.",
|
|
302
|
+
"- To resume it: remove the hold note from the description and put it " +
|
|
303
|
+
"back in the build queue.",
|
|
304
|
+
"",
|
|
305
|
+
HUMAN_GATE_NOTE_MARKER,
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Render the cycle-summary line naming what was held.
|
|
311
|
+
*
|
|
312
|
+
* A lane mutation nobody can see afterwards is the same class of problem as the
|
|
313
|
+
* one this fixes, so the run says what it moved — and it keeps "nothing was
|
|
314
|
+
* eligible" distinguishable from "something eligible was held".
|
|
315
|
+
* @param {readonly unknown[]} held - Item references held this cycle
|
|
316
|
+
* @returns {string} One summary line
|
|
317
|
+
*/
|
|
318
|
+
export function summarizeHumanGateHolds(held = []) {
|
|
319
|
+
const names = (Array.isArray(held) ? held : [])
|
|
320
|
+
.map(item => trimmedString(item))
|
|
321
|
+
.filter(name => name.length > 0);
|
|
322
|
+
if (names.length === 0) return "Held for a person: none.";
|
|
323
|
+
return `Held for a person (${String(names.length)}): ${names.join(", ")}.`;
|
|
324
|
+
}
|
|
@@ -300,6 +300,41 @@ Lifecycle membership is decided by the **`status:` prefix**, never by a pinned m
|
|
|
300
300
|
|
|
301
301
|
### Phase 3 — Process the first eligible ready issue
|
|
302
302
|
|
|
303
|
+
#### 3.0 Human-hold gate (absolute, and it runs before every other gate)
|
|
304
|
+
|
|
305
|
+
A person parks an item by putting `[lisa-human-gate]` in its description. That marker used to be
|
|
306
|
+
read only for candidates **outside** `$READY` (Phase 2's blocker re-probe), so an item already in
|
|
307
|
+
the ready lane was claimed with the hold never consulted — one was dispatched and fully implemented
|
|
308
|
+
before a human vetoed the merge. The check was correct; it was unreachable from the path that
|
|
309
|
+
matters.
|
|
310
|
+
|
|
311
|
+
Run this **first**, ahead of the repo-scope gate (3a.0) and the leaf-only gate (3a), for every ready
|
|
312
|
+
candidate. Ordering is load-bearing for the same reason it is inside `classifyPreWorkCandidate`: no
|
|
313
|
+
other gate's verdict — however conclusive — may promote an item a person parked.
|
|
314
|
+
|
|
315
|
+
1. **Classify with `classifyReadyCandidate(...)`** from `scripts/intake-blocker-reprobe.mjs`. It
|
|
316
|
+
shares the gate test with the pre-work classifier deliberately — two copies of a substring test
|
|
317
|
+
drift, and a drifted gate fails *silently*, by quietly ceasing to match. Do **not** re-implement
|
|
318
|
+
the test here, and do **not** key it on `reason=`: markers in the wild carry no `reason=` key at
|
|
319
|
+
all and sit anywhere in the body, so a structured parse would miss them while appearing to work
|
|
320
|
+
on every item that happens to have one.
|
|
321
|
+
2. **On `claimable: false` with reason `human-gate`, do not claim and do not dispatch.**
|
|
322
|
+
3. **Reconcile the lane; do not merely skip.** Skipping alone leaves the item in `$READY`, re-judged
|
|
323
|
+
and re-rejected every cycle forever and seen by nothing — `lisa-repair-intake` sweeps items that
|
|
324
|
+
are **not** in the ready role and excludes gated ones outright, so a ready-and-gated item falls
|
|
325
|
+
outside its filter twice over. Call
|
|
326
|
+
`planHumanGateReconciliation({ labels, body, humanNeededLabel, readyLabel, alreadyNotified })`
|
|
327
|
+
and apply exactly the actions it returns: remove `$READY`, add the configured human-needed
|
|
328
|
+
marker, and post `formatHumanGateNote()` once. The planner is idempotent by state, so an item
|
|
329
|
+
already out of the lane and already marked yields no second mutation and no second comment. This
|
|
330
|
+
is the same repair the leaf-only gate already performs for a ready item that must not be
|
|
331
|
+
dispatched.
|
|
332
|
+
4. **Name it in the cycle summary** via `summarizeHumanGateHolds([...])`, so the record
|
|
333
|
+
distinguishes "nothing was eligible" from "something eligible was held for a person". A lane
|
|
334
|
+
mutation nobody can see afterwards is the same class of problem this gate exists to fix.
|
|
335
|
+
5. **Continue to the next candidate.** A held item does not end the cycle.
|
|
336
|
+
|
|
337
|
+
|
|
303
338
|
#### 3a.0 Repo-scope gate (claim only current-repo issues)
|
|
304
339
|
|
|
305
340
|
GitHub Issues live in one repo by definition, so the scanned repo's issues are usually inherently current-repo. But a planning/umbrella repo's issues can target sibling repos, so this skill still claims only issues for the repo it is running in. Run this gate **before** the leaf-only gate (3a) and the claim (3b), per the `repo-scope-split` rule's "Claim-time repo scoping" section (cite it by slug; do not restate its decision table).
|