@codyswann/lisa 3.33.7 → 3.34.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/all/copy-overwrite/scripts/lisa-gates.mjs +35 -3
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +6 -0
- package/dist/configs/eslint/base.d.ts.map +1 -1
- package/dist/configs/eslint/base.js +8 -0
- package/dist/configs/eslint/base.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +2 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +31 -12
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/eslint-plugin-component-structure/rules/enforce-component-structure.js +63 -43
- package/package.json +2 -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-github-build-intake/SKILL.md +53 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +24 -0
- package/plugins/lisa/rules/eager/automation-runbook-contract.md +2 -0
- package/plugins/lisa/rules/reference/automation-runbook-contract.md +19 -0
- package/plugins/lisa/scripts/automation-run-record.mjs +146 -33
- package/plugins/lisa/scripts/intake-blocker-reprobe.mjs +190 -0
- package/plugins/lisa/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +53 -3
- package/plugins/lisa/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/lisa/skills/lisa-tracker-build-intake/SKILL.md +24 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/automation-run-record.mjs +146 -33
- package/plugins/lisa-agy/scripts/intake-blocker-reprobe.mjs +190 -0
- package/plugins/lisa-agy/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +53 -3
- package/plugins/lisa-agy/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/lisa-agy/skills/lisa-tracker-build-intake/SKILL.md +24 -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/rules/eager/automation-runbook-contract.md +2 -0
- package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +19 -0
- package/plugins/lisa-copilot/scripts/automation-run-record.mjs +146 -33
- package/plugins/lisa-copilot/scripts/intake-blocker-reprobe.mjs +190 -0
- package/plugins/lisa-copilot/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +53 -3
- package/plugins/lisa-copilot/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/lisa-copilot/skills/lisa-tracker-build-intake/SKILL.md +24 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +19 -0
- package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +2 -0
- package/plugins/lisa-cursor/scripts/automation-run-record.mjs +146 -33
- package/plugins/lisa-cursor/scripts/intake-blocker-reprobe.mjs +190 -0
- package/plugins/lisa-cursor/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +53 -3
- package/plugins/lisa-cursor/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/lisa-cursor/skills/lisa-tracker-build-intake/SKILL.md +24 -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/rules/eager/automation-runbook-contract.md +2 -0
- package/plugins/src/base/rules/reference/automation-runbook-contract.md +19 -0
- package/plugins/src/base/scripts/automation-run-record.mjs +146 -33
- package/plugins/src/base/scripts/intake-blocker-reprobe.mjs +190 -0
- package/plugins/src/base/scripts/intake-prework-denominator.mjs +249 -0
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +53 -3
- package/plugins/src/base/skills/lisa-intake/SKILL.md +17 -1
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +53 -5
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +48 -7
- package/plugins/src/base/skills/lisa-tracker-build-intake/SKILL.md +24 -0
- package/typescript/copy-contents/.husky/pre-push +88 -1
|
@@ -99,7 +99,7 @@ The only legitimate reasons to stop early:
|
|
|
99
99
|
|
|
100
100
|
- Missing team key or required configuration. Surface and exit.
|
|
101
101
|
- Workflow states not yet adopted (the `ready` state does not exist on the team). Surface and exit with an Adoption hint pointing at `/lisa:setup:linear`.
|
|
102
|
-
- Empty
|
|
102
|
+
- Empty pre-work set. Exit cleanly on the denominator-stated summary from `summarizeDryLane` — which names every lane swept, its count, and the open total. A bare "nothing to do" is not an acceptable exit: it is indistinguishable from a wrong denominator (#2657).
|
|
103
103
|
|
|
104
104
|
## Lifecycle assumed
|
|
105
105
|
|
|
@@ -112,7 +112,7 @@ ready → claimed → review → done(env-keyed) (downstream)
|
|
|
112
112
|
|
|
113
113
|
(Defaults: `Ready` / `In Progress` / `In Review` / `On Dev`/`On Stg`/`Done`.)
|
|
114
114
|
|
|
115
|
-
This skill ONLY transitions `$READY → $CLAIMED` on claim, and `$CLAIMED → $DONE` on completion. It never touches the terminal production `done
|
|
115
|
+
This skill ONLY transitions `$READY → $CLAIMED` on claim, and `$CLAIMED → $DONE` on completion. It never touches the terminal production `done` or `$REVIEW` (owned by the lifecycle / `lisa-linear-evidence`). It never *sets* `$BLOCKED` either — that stays owned by the pre-flight gate — but Phase 2.5 does move an Issue **out** of a pre-work blocked lane back to `$READY` when it re-probes the Issue's own stated discharge condition and finds it no longer holds, recording the discharging evidence on the Issue.
|
|
116
116
|
|
|
117
117
|
**Pre-flight check**: at start of each cycle, confirm `$READY`, `$CLAIMED`, and the relevant `$DONE` variants exist on the team via `lisa-linear-access operation: list-workflow-states`. If `$READY` is missing, stop and report adoption needed. **Unlike labels, a missing state cannot be created on demand here** — a workflow state is team configuration with a `type` and a board position, and guessing either would put an Issue somewhere a human did not sanction. Any missing state is a setup defect: report it, name the role and the expected state, and point at `/lisa:setup:linear`.
|
|
118
118
|
|
|
@@ -125,15 +125,56 @@ This skill ONLY transitions `$READY → $CLAIMED` on claim, and `$CLAIMED → $D
|
|
|
125
125
|
- Literal `linear` → fall back to `linear.teamKey` from config.
|
|
126
126
|
2. Resolve team ID via `lisa-linear-access operation: list-teams({query: <teamKey>})`.
|
|
127
127
|
|
|
128
|
-
### Phase 2 —
|
|
128
|
+
### Phase 2 — Sweep every pre-work lane by state TYPE
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
**Sweep by `type`, never by a roster of state names.** Linear state types are `backlog | unstarted | started | completed | canceled`, and there is no `blocked` type — a team that wants a `Blocked` lane models it as **`unstarted`**, i.e. work that was *never started*. Selecting candidates from a hardcoded `Backlog / Todo / Ready` name list therefore omits an entire pre-work lane and reports an empty queue over a full one. Measured on one team: the name sweep saw **39 of 343 open rows**; the type sweep sees **100**, and the 61-row difference produced 31 consecutive false "dry lane" cycles (#2657).
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
1. List the team's workflow states via `lisa-linear-access operation: list-workflow-states`.
|
|
133
|
+
2. Keep every state whose `type` is `backlog` or `unstarted` — that is the pre-work set. `$READY` is one member of it, not the whole of it.
|
|
134
|
+
3. Query each pre-work state: `lisa-linear-access operation: list-issues({team: <teamId>, state: "<state>"})`, paging to `hasNextPage=false`. Linear's GraphQL complexity ceiling silently truncates at `first: 250`, so a single unpaged call is not a count.
|
|
135
|
+
4. Also read the **total open** count for the team (every state whose `type` is not `completed` / `canceled`). This number is what makes an omitted lane arithmetically visible.
|
|
136
|
+
|
|
137
|
+
Capture each Issue's: identifier, title, type label, priority, assignee, project, state (with its `type`), labels, description summary.
|
|
138
|
+
|
|
139
|
+
Build the denominator with the shared helper, which owns the type vocabulary so no two scanners can disagree about what counts as pre-work:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
node -e '
|
|
143
|
+
import("'"${CLAUDE_PLUGIN_ROOT:-plugins/src/base}"'/scripts/intake-prework-denominator.mjs").then(m => {
|
|
144
|
+
const d = m.buildIntakeDenominator({ lanes: JSON.parse(process.argv[1]), totalOpen: Number(process.argv[2]) });
|
|
145
|
+
console.log(JSON.stringify(d));
|
|
146
|
+
console.log(m.summarizeDryLane(d, { queue: process.argv[3] }));
|
|
147
|
+
});' "$LANES_JSON" "$TOTAL_OPEN" "team $TEAM_KEY"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`$LANES_JSON` is `[{"name":"<state>","type":"<state.type>","position":<state.position>,"count":<open rows>}, …]` for **every** state on the team, pre-work and not — the helper does the selecting.
|
|
151
|
+
|
|
152
|
+
**Candidate order.** Work `$READY` first (it is the human-flipped signal), then the remaining pre-work lanes oldest-first. Every candidate outside `$READY` must clear Phase 2.5 before it is treated as a candidate at all.
|
|
133
153
|
|
|
134
154
|
> **No query-time repo pre-filter here (by design).** Unlike `lisa-jira-build-intake`, which narrows its JQL with `AND (labels = "repo:<current>" OR labels IS EMPTY)` (the query-time arm of `repo-scope-split`), the Linear `list_issues` label filter is an AND-of-labels and cannot express "current-repo **or** unlabeled" in one query. Adding `repo:<current>` to this query would strand unlabeled Issues the determine + stamp path must see. So the Linear scanner keeps this query broad and relies on the per-candidate 3a.0 gate below for repo scoping. (The `state` filter above is orthogonal to that — it narrows the lifecycle lane, not the repo, and is a single-valued equality so it has none of the AND-of-labels problem.)
|
|
135
155
|
|
|
136
|
-
If empty,
|
|
156
|
+
If every pre-work lane is empty, or nothing survives Phase 2.5, exit on the **denominator-stated** summary from `summarizeDryLane` — never a bare "nothing to do". See "Run outcome" below; the run recorder rejects a dry build-intake run that does not name what it swept.
|
|
157
|
+
|
|
158
|
+
### Phase 2.5 — Re-probe the blockers instead of inheriting them
|
|
159
|
+
|
|
160
|
+
A blocker is a **claim with a timestamp, not a fact**. It is written once and goes stale the moment its condition comes true — a dependency lands on trunk, an advisory gets patched, a package publishes. Nothing re-read one before this phase, so a discharged blocker held its Issue out of the queue indefinitely. Measured: one Issue's stated condition went true ~15 hours before anything noticed.
|
|
161
|
+
|
|
162
|
+
For each pre-work candidate that is **not** in `$READY`:
|
|
163
|
+
|
|
164
|
+
1. **Human gate first, and it is absolute.** An Issue carrying the configured human-needed label (`linear.labels.build.human_needed`, default `human-needed`) or a `[lisa-human-gate]` marker in its description is **never** auto-selected, whatever any probe says. Skip it and move on.
|
|
165
|
+
2. **Extract the stated discharge condition** from the description or the most recent blocking comment — the sentence naming what has to become true.
|
|
166
|
+
3. **Probe it.** Machine-testable conditions are the ones that rot fastest and are cheapest to check: a version on trunk (`git show origin/<trunk>:<manifest>`), a published package, a run history (`gh run list`), an advisory's patched status. A condition that is a human decision is not machine-testable — leave it and move on.
|
|
167
|
+
4. **Classify with the shared helper** so the ordering and the evidence requirement cannot drift per vendor:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
classifyPreWorkCandidate({ laneType, labels, body, humanNeededLabel, statedBlocker, probe })
|
|
171
|
+
→ { selectable, reason, humanGated, evidence }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
A discharge with **no recorded evidence is not a discharge** — the helper refuses it. So is a candidate nothing probed this cycle.
|
|
175
|
+
|
|
176
|
+
5. **Record the result on the Issue either way**, via `lisa-linear-access operation: save-comment` using `formatReprobeNote(...)`, so the next cycle reads the answer rather than re-deriving it. Keep it idempotent — skip the post when an identical note already exists.
|
|
177
|
+
6. **On `selectable: true`**, move the Issue to `$READY` (recording the discharging evidence in the same comment) and treat it as an ordinary candidate from Phase 3 onward. On anything else, leave the Issue exactly where it is.
|
|
137
178
|
|
|
138
179
|
### Phase 3 — Process the first eligible ready Issue
|
|
139
180
|
|
|
@@ -148,7 +189,7 @@ A Linear team can oversee multiple repos (`frontend` / `backend` / `infrastructu
|
|
|
148
189
|
- **Unlabeled** → determine the target repo(s) from the Issue + code surfaces, then **stamp** `repo:<name>` via `lisa-linear-access operation: save-issue` (resolve/create the label via `list_issue_labels`/`create_issue_label`) so later cycles filter cheaply; re-apply with the now-known repo.
|
|
149
190
|
- **Multi-repo leaf → split, never claim.** Run the `repo-scope-split` work-time procedure into single-repo siblings, each created **build-ready** (`build_ready: true`) and stamped with its own `repo:<name>`; the current repo's sibling becomes a normal candidate.
|
|
150
191
|
- **Single-repo leaf for the current repo** → fall through to 3a (leaf-only gate) and 3b (claim).
|
|
151
|
-
4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the
|
|
192
|
+
4. Continue until a claimable current-repo leaf is found (claim it; one per cycle) or the candidate set is exhausted — exit cleanly on the denominator-stated summary, naming the current repo alongside the swept lanes.
|
|
152
193
|
|
|
153
194
|
#### 3a. Leaf-only claim gate (skip / safe-block containers)
|
|
154
195
|
|
|
@@ -42,6 +42,30 @@ This is the claim-time arm of the rule. Its siblings are the write-time labeling
|
|
|
42
42
|
|
|
43
43
|
The shim never needs to inspect the item itself — it forwards `$ARGUMENTS` verbatim and the resolved vendor scanner runs its Phase 3a gate before any claim.
|
|
44
44
|
|
|
45
|
+
## Pre-work denominator contract (forwarded to every vendor)
|
|
46
|
+
|
|
47
|
+
Also part of the build-intake API, and the reason a dry queue can be believed. Two obligations,
|
|
48
|
+
uniform across `jira`, `github`, and `linear`:
|
|
49
|
+
|
|
50
|
+
1. **Sweep every pre-work lane by its machine-readable category, never by a roster of lane names.**
|
|
51
|
+
No tracker has a `blocked` category: Linear models a `Blocked` state as `unstarted`, JIRA models
|
|
52
|
+
a `Blocked` status under `To Do`, and GitHub has no category at all, so its lanes come from
|
|
53
|
+
configured roles. In every case a `Blocked` lane is *pre-work* — items that were never started,
|
|
54
|
+
each carrying a written blocker. Keying selection on names omits it silently.
|
|
55
|
+
2. **A `nothing-needed` run states its denominator** — which lanes were swept, how many rows each
|
|
56
|
+
held, and the total open. This is enforced, not requested: `automation-run-record.mjs` refuses a
|
|
57
|
+
`nothing-needed` row for `intake-tickets` without a `--denominator`.
|
|
58
|
+
|
|
59
|
+
Shared helpers own both, so the vocabulary cannot drift per vendor:
|
|
60
|
+
`scripts/intake-prework-denominator.mjs` (`buildIntakeDenominator`, `summarizeDryLane`) and
|
|
61
|
+
`scripts/intake-blocker-reprobe.mjs` (`classifyPreWorkCandidate`, `formatReprobeNote`). The
|
|
62
|
+
blocker re-probe carries an absolute human gate: an item carrying the configured human-needed label
|
|
63
|
+
or a `[lisa-human-gate]` marker is never auto-selected, whatever a probe returns.
|
|
64
|
+
|
|
65
|
+
Measured: sweeping one team by lane name saw 39 of 343 open rows; by category it sees 100. The
|
|
66
|
+
61-row gap produced 31 consecutive false "dry lane" cycles, every record honest and every
|
|
67
|
+
conclusion wrong (#2657).
|
|
68
|
+
|
|
45
69
|
## Repo-scope claim contract (forwarded to every vendor)
|
|
46
70
|
|
|
47
71
|
Equally part of the build-intake API, and forwarded identically: when the tracker oversees multiple repos, each vendor scanner claims only tickets for the repo it is running in. Per the `repo-scope-split` rule's "Claim-time repo scoping" section, before the leaf-only gate each scanner (Phase 3a.0) resolves the current repo (`config-resolution` "Repo scoping": `repo` → `github.repo` → git remote basename), then for each ready candidate: skips a ticket labeled `repo:<other>`, determines + stamps `repo:<name>` on an unlabeled one, splits a multi-repo leaf into single-repo build-ready siblings, and claims only a single-repo leaf for the current repo. This shim does not re-implement the gate — it relies on the vendor scanner's Phase 3a.0 — but the contract is uniform across `jira`, `github`, and `linear` so behavior never drifts by tracker. It is the claim-time complement to the write-time S10 scope gate (`lisa-tracker-validate`) and `task-decomposition` step 1.5; all cite `repo-scope-split`.
|
|
@@ -11,7 +11,74 @@ if ! command -v node >/dev/null 2>&1; then
|
|
|
11
11
|
echo "❌ Push blocked: Node.js is required to validate tracker work items."
|
|
12
12
|
exit 1
|
|
13
13
|
fi
|
|
14
|
-
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# Work-item traceability, at the push moment.
|
|
16
|
+
#
|
|
17
|
+
# This call used to be unconditional, and it was the last check in either hook
|
|
18
|
+
# that ran outside the gate facade: `gates.traceability` could say `off` and it
|
|
19
|
+
# still ran, could say `required` and nothing changed. That is the defect #2680
|
|
20
|
+
# measured in CI, one moment earlier. The declaration decides now, and this is
|
|
21
|
+
# the FALLBACK:
|
|
22
|
+
#
|
|
23
|
+
# declared at push, at any level -> the registry owns the property. The gate
|
|
24
|
+
# runner below runs what the project
|
|
25
|
+
# declared, and this step stands down.
|
|
26
|
+
# not declared, or the registry
|
|
27
|
+
# cannot be read at all -> this runs, exactly as it always has. A
|
|
28
|
+
# project that has declared nothing loses
|
|
29
|
+
# no protection whatsoever.
|
|
30
|
+
#
|
|
31
|
+
# `off` needs no third branch, for the same reason it needed none in CI: an off
|
|
32
|
+
# declaration is still a declaration, the runner records it as covered, and the
|
|
33
|
+
# built-in step stands down having correctly done nothing. What is refused is
|
|
34
|
+
# the shape where this step runs and its exit code is discarded — a check that
|
|
35
|
+
# reports green having proved nothing is the defect the facade exists to remove.
|
|
36
|
+
#
|
|
37
|
+
# WHY THE DECISION IS RESOLVED HERE rather than read from the coverage file the
|
|
38
|
+
# other built-in steps consult. This step must run BEFORE the GIT_* set is
|
|
39
|
+
# unset below, and before anything else reads the hook's stdin, because Git
|
|
40
|
+
# feeds the refs being pushed in on stdin and that is what the validator parses.
|
|
41
|
+
# No coverage file exists this early, so the declaration is resolved directly.
|
|
42
|
+
# That makes deferring only half a decision; the other half is further down,
|
|
43
|
+
# where this runs after all if the registry never actually proved it.
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
GATE_REGISTRY=""
|
|
46
|
+
for LISA_GATE_REGISTRY_CANDIDATE in \
|
|
47
|
+
"node_modules/@codyswann/lisa/all/copy-overwrite/scripts/lisa-gates.mjs" \
|
|
48
|
+
"scripts/lisa-gates.mjs" \
|
|
49
|
+
"all/copy-overwrite/scripts/lisa-gates.mjs"
|
|
50
|
+
do
|
|
51
|
+
if [ -f "$LISA_GATE_REGISTRY_CANDIDATE" ]; then
|
|
52
|
+
GATE_REGISTRY="$LISA_GATE_REGISTRY_CANDIDATE"
|
|
53
|
+
break
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
unset LISA_GATE_REGISTRY_CANDIDATE
|
|
57
|
+
|
|
58
|
+
# Nothing here discards stderr. A resolver that failed and a project that
|
|
59
|
+
# declared nothing produce the same empty answer, and both send this hook to the
|
|
60
|
+
# built-in step — which is the safe direction — but only one of them is a
|
|
61
|
+
# problem somebody needs to see.
|
|
62
|
+
TRACEABILITY_DECLARED=""
|
|
63
|
+
if [ -n "$GATE_REGISTRY" ]; then
|
|
64
|
+
TRACEABILITY_DECLARED=$(node "$GATE_REGISTRY" list --moment=push --json --include-off | node -e '
|
|
65
|
+
let raw = "";
|
|
66
|
+
process.stdin.on("data", chunk => { raw += chunk }).on("end", () => {
|
|
67
|
+
try {
|
|
68
|
+
const declared = JSON.parse(raw || "[]").some(gate => gate.id === "traceability");
|
|
69
|
+
process.stdout.write(declared ? "declared" : "");
|
|
70
|
+
} catch (error) {
|
|
71
|
+
process.stderr.write(`\u26a0\ufe0f Could not read the gate registry: ${error.message}\n`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
')
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [ "$TRACEABILITY_DECLARED" = "declared" ]; then
|
|
78
|
+
echo "ℹ️ gates.traceability is declared at push; the gate registry decides what runs."
|
|
79
|
+
else
|
|
80
|
+
node "$WORK_ITEM_SCRIPT" validate-push "${1:-origin}" || exit 1
|
|
81
|
+
fi
|
|
15
82
|
|
|
16
83
|
# Git supplies repository-local GIT_* variables to hooks. Keep them for push
|
|
17
84
|
# validation above, then remove exactly Git's documented local set so nested
|
|
@@ -117,6 +184,26 @@ lisa_gate_covers() {
|
|
|
117
184
|
# BEGIN: built-in checks — the pre-registry path, kept step for step so a
|
|
118
185
|
# project without a `gates` block behaves exactly as it did before.
|
|
119
186
|
|
|
187
|
+
# Work-item traceability, when the registry took the property and then proved
|
|
188
|
+
# nothing with it — no runner on disk, a runner that could not run, a coverage
|
|
189
|
+
# file that could not be written. Each of those means nothing was proved, and a
|
|
190
|
+
# declared gate must never be a quieter way of turning a check off than
|
|
191
|
+
# declaring it `off`.
|
|
192
|
+
#
|
|
193
|
+
# A late run is not identical to the early one: the repository-local GIT_*
|
|
194
|
+
# variables are gone by here, and a declared gate that read stdin has already
|
|
195
|
+
# consumed the pushed refs. `validate-push` survives both — with empty stdin it
|
|
196
|
+
# falls back to `git rev-list HEAD --not --remotes=<remote>` — which is what
|
|
197
|
+
# makes a degraded run worth having, and why the early position stays the
|
|
198
|
+
# normal one.
|
|
199
|
+
if lisa_gate_covers traceability; then
|
|
200
|
+
echo "ℹ️ Covered by the traceability gate; the built-in work-item validation stands down."
|
|
201
|
+
elif [ "$TRACEABILITY_DECLARED" = "declared" ]; then
|
|
202
|
+
echo "⚠️ gates.traceability is declared at push, but the gate registry proved nothing here."
|
|
203
|
+
echo " Running the built-in work-item validation so the property is not lost."
|
|
204
|
+
node "$WORK_ITEM_SCRIPT" validate-push "${1:-origin}" || exit 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
120
207
|
# Run the whole-project type check once before code leaves the machine. It is
|
|
121
208
|
# intentionally not a pre-commit gate because TypeScript cannot check only the
|
|
122
209
|
# staged files and rebuilding the full program makes small commits too slow.
|