@codyswann/lisa 3.16.0 → 3.17.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 +1165 -0
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +1188 -0
- package/all/copy-overwrite/scripts/lisa-run-gates.mjs +597 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +24 -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 +72 -8
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +6 -2
- package/plugins/lisa/.claude-plugin/plugin.json +10 -1
- package/plugins/lisa/.codex-plugin/hooks.json +9 -0
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- package/plugins/lisa/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/lisa/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- 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 +10 -1
- package/plugins/lisa-copilot/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/hooks.json +3 -0
- package/plugins/lisa-cursor/hooks/secrets-preflight.sh +72 -0
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- 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/.claude-plugin/plugin.json +9 -0
- package/plugins/src/base/hooks/secrets-preflight.sh +72 -0
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +108 -2
- package/plugins/src/base/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +324 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/routing-floor.mjs +153 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +55 -7
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +89 -17
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs +242 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/tool-floor.mjs +94 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +14 -32
- package/scripts/generate-lisa-owned-hash-ledger.mjs +10 -1
- package/scripts/lib/per-agent-hook-filter.mjs +18 -0
- package/typescript/copy-contents/.husky/pre-commit +130 -21
- package/typescript/copy-contents/.husky/pre-push +202 -22
|
@@ -43,9 +43,99 @@ else
|
|
|
43
43
|
fi
|
|
44
44
|
fi
|
|
45
45
|
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
# Gate-driven checks.
|
|
48
|
+
#
|
|
49
|
+
# A gate is a PROPERTY the project must hold; `.lisa.config.json` names the task
|
|
50
|
+
# that proves it. `gitleaks` is not a gate — credential leakage is, and gitleaks
|
|
51
|
+
# is one way to prove it. When a `gates` block exists, that registry decides
|
|
52
|
+
# what runs at the `commit` moment, not the hardcoded list further down.
|
|
53
|
+
#
|
|
54
|
+
# BACKWARD COMPATIBILITY — read before changing any of this. Most installed
|
|
55
|
+
# projects have NO `gates` block. The exit codes are the switch:
|
|
56
|
+
#
|
|
57
|
+
# exit 0 → the declared gates at this moment ran and none of them blocked.
|
|
58
|
+
# exit 1 → a required gate FAILED. Block the commit.
|
|
59
|
+
# exit 78 → there is no `gates` block at all. Nothing is covered.
|
|
60
|
+
# other → the runner itself could not run (config unreadable, crash).
|
|
61
|
+
# Nothing was proved. Say so loudly and cover nothing.
|
|
62
|
+
#
|
|
63
|
+
# ONE STEP AT A TIME, NOT ALL OR NOTHING. Exit 0 says the declared gates
|
|
64
|
+
# passed. It does not say the registry covers every property the steps below
|
|
65
|
+
# prove — so it cannot be the thing that skips them wholesale. A block that
|
|
66
|
+
# declares `code-style` and says nothing about `credential-leakage` would then
|
|
67
|
+
# delete the secret scan by omission, which is the failure this whole subsystem
|
|
68
|
+
# exists to prevent: a control returning success for an input it never
|
|
69
|
+
# examined.
|
|
70
|
+
#
|
|
71
|
+
# So the runner writes the properties it covers into $LISA_GATE_COVERAGE, one
|
|
72
|
+
# gate id per line, and every built-in step below stands down only against its
|
|
73
|
+
# OWN property. A half-declared block therefore loses nothing: the registry
|
|
74
|
+
# runs what it declares and the built-ins run the rest.
|
|
75
|
+
#
|
|
76
|
+
# Fail-safe by construction: no coverage file, an empty one, a runner that
|
|
77
|
+
# could not run, or no exact match, all mean the built-in step runs.
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
GATE_RUNNER="scripts/lisa-run-gates.mjs"
|
|
80
|
+
if [ ! -f "$GATE_RUNNER" ]; then
|
|
81
|
+
GATE_RUNNER="all/copy-overwrite/scripts/lisa-run-gates.mjs"
|
|
82
|
+
fi
|
|
83
|
+
LISA_GATE_COVERAGE=""
|
|
84
|
+
if [ -f "$GATE_RUNNER" ] && command -v node >/dev/null 2>&1; then
|
|
85
|
+
LISA_GATE_COVERAGE="$(mktemp "${TMPDIR:-/tmp}/lisa-gate-coverage.XXXXXXXX")" || LISA_GATE_COVERAGE=""
|
|
86
|
+
trap 'rm -f "$LISA_GATE_COVERAGE"' EXIT
|
|
87
|
+
if [ -n "$LISA_GATE_COVERAGE" ]; then
|
|
88
|
+
node "$GATE_RUNNER" --moment=commit --coverage="$LISA_GATE_COVERAGE"
|
|
89
|
+
else
|
|
90
|
+
# No temp file, so nothing can be covered and every built-in step runs.
|
|
91
|
+
node "$GATE_RUNNER" --moment=commit
|
|
92
|
+
fi
|
|
93
|
+
GATE_STATUS=$?
|
|
94
|
+
if [ $GATE_STATUS -eq 1 ]; then
|
|
95
|
+
echo ""
|
|
96
|
+
echo "❌ Commit blocked: a required gate failed. See the FAILED line above."
|
|
97
|
+
echo ""
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
if [ $GATE_STATUS -ne 0 ]; then
|
|
101
|
+
# Anything other than a clean run: discard whatever is in the file. A
|
|
102
|
+
# runner that crashed midway may have written coverage before it failed,
|
|
103
|
+
# and coverage from a run that did not finish is not evidence.
|
|
104
|
+
if [ -n "$LISA_GATE_COVERAGE" ]; then
|
|
105
|
+
: > "$LISA_GATE_COVERAGE"
|
|
106
|
+
fi
|
|
107
|
+
if [ $GATE_STATUS -ne 78 ]; then
|
|
108
|
+
echo ""
|
|
109
|
+
echo "⚠️ The gate runner could not run (exit $GATE_STATUS)."
|
|
110
|
+
echo " Nothing was proved by the gate registry — this is NOT a pass."
|
|
111
|
+
echo " Running the built-in checks instead."
|
|
112
|
+
echo ""
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
else
|
|
116
|
+
echo "ℹ️ Gate runner unavailable; running the built-in checks."
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Whether a declared gate covers every property named, so the built-in step
|
|
120
|
+
# that proves them can stand down. Exact whole-line matching: a gate id that is
|
|
121
|
+
# a prefix of another must never satisfy it.
|
|
122
|
+
lisa_gate_covers() {
|
|
123
|
+
[ -n "$LISA_GATE_COVERAGE" ] || return 1
|
|
124
|
+
[ -s "$LISA_GATE_COVERAGE" ] || return 1
|
|
125
|
+
for _lisa_gate in "$@"; do
|
|
126
|
+
grep -Fqx -- "$_lisa_gate" "$LISA_GATE_COVERAGE" || return 1
|
|
127
|
+
done
|
|
128
|
+
return 0
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# BEGIN: built-in checks — the pre-registry path, kept step for step so a
|
|
132
|
+
# project without a `gates` block behaves exactly as it did before.
|
|
133
|
+
|
|
46
134
|
# Check for Gitleaks and run secret detection
|
|
47
135
|
echo "🔐 Checking for secrets with Gitleaks..."
|
|
48
|
-
if
|
|
136
|
+
if lisa_gate_covers credential-leakage; then
|
|
137
|
+
echo "ℹ️ Covered by the credential-leakage gate; the built-in scan stands down."
|
|
138
|
+
elif command -v gitleaks >/dev/null 2>&1; then
|
|
49
139
|
# Run gitleaks on staged files. Lisa keeps the host-owned allowlist in
|
|
50
140
|
# .gitleaksignore and shared managed entries in .gitleaksignore.local.
|
|
51
141
|
GITLEAKS_IGNORE_ARGS=""
|
|
@@ -109,10 +199,49 @@ fi
|
|
|
109
199
|
# exit 1
|
|
110
200
|
# fi
|
|
111
201
|
|
|
202
|
+
# Run lint-staged for incremental lint and format checks. It proves three
|
|
203
|
+
# properties in one pass — oxlint/eslint, prettier, and `ast-grep scan` — so it
|
|
204
|
+
# stands down only when a gate is declared for every one of them.
|
|
205
|
+
if lisa_gate_covers code-style format-conformance structural-rules; then
|
|
206
|
+
echo "ℹ️ Lint, format and structural rules are covered by gates; lint-staged stands down."
|
|
207
|
+
else
|
|
208
|
+
echo "🚀 Running lint-staged..."
|
|
209
|
+
$EXECUTOR lint-staged --config .lintstagedrc.json
|
|
210
|
+
LINT_STAGED_STATUS=$?
|
|
211
|
+
if [ $LINT_STAGED_STATUS -ne 0 ]; then
|
|
212
|
+
exit $LINT_STAGED_STATUS
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Generated artifacts must vouch for the bytes committed beside them. Runs AFTER
|
|
217
|
+
# lint-staged on purpose: reformatting a staged template moves its bytes, so a
|
|
218
|
+
# check placed above would sign off on bytes that no longer exist. Absent in host
|
|
219
|
+
# projects, which have the hook but none of Lisa's generators.
|
|
220
|
+
if lisa_gate_covers artifact-freshness; then
|
|
221
|
+
echo "ℹ️ Covered by the artifact-freshness gate; the built-in check stands down."
|
|
222
|
+
elif [ -f "scripts/check-derived-artifacts.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
223
|
+
echo "🧾 Checking generated artifacts..."
|
|
224
|
+
node scripts/check-derived-artifacts.mjs --staged
|
|
225
|
+
if [ $? -ne 0 ]; then
|
|
226
|
+
exit 1
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
|
|
230
|
+
# END: built-in checks
|
|
231
|
+
|
|
112
232
|
# Threshold ratchet: quality thresholds may tighten, never weaken. The staged
|
|
113
233
|
# change is compared against HEAD by the same deterministic comparator the
|
|
114
234
|
# agent-time hooks and CI run; human-approved exceptions live in
|
|
115
235
|
# .lisa.config.json under thresholdRatchet.allow.
|
|
236
|
+
#
|
|
237
|
+
# OUTSIDE the gate-registry handover, deliberately. Every other built-in step
|
|
238
|
+
# here has a registry gate that can replace it, so the runner refuses to take
|
|
239
|
+
# the moment until the block declares one — see BUILTIN_FLOOR and
|
|
240
|
+
# CONDITIONAL_FLOOR in lisa-run-gates.mjs. This one has no such counterpart:
|
|
241
|
+
# `threshold-monotonicity` is push-onward and compares against HEAD^, while
|
|
242
|
+
# this compares the STAGED change. Leaving it inside the handover would let a
|
|
243
|
+
# complete gates block delete it at commit with nothing put in its place, which
|
|
244
|
+
# is precisely the deletion-by-omission the floor exists to prevent.
|
|
116
245
|
if [ -f "scripts/check-threshold-ratchet.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
117
246
|
echo "📐 Checking threshold ratchet..."
|
|
118
247
|
node scripts/check-threshold-ratchet.mjs --staged
|
|
@@ -125,24 +254,4 @@ if [ -f "scripts/check-threshold-ratchet.mjs" ] && command -v node >/dev/null 2>
|
|
|
125
254
|
exit 1
|
|
126
255
|
fi
|
|
127
256
|
fi
|
|
128
|
-
|
|
129
|
-
# Run lint-staged for incremental lint and format checks
|
|
130
|
-
echo "🚀 Running lint-staged..."
|
|
131
|
-
$EXECUTOR lint-staged --config .lintstagedrc.json
|
|
132
|
-
LINT_STAGED_STATUS=$?
|
|
133
|
-
if [ $LINT_STAGED_STATUS -ne 0 ]; then
|
|
134
|
-
exit $LINT_STAGED_STATUS
|
|
135
|
-
fi
|
|
136
|
-
|
|
137
|
-
# Generated artifacts must vouch for the bytes committed beside them. Runs AFTER
|
|
138
|
-
# lint-staged on purpose: reformatting a staged template moves its bytes, so a
|
|
139
|
-
# check placed above would sign off on bytes that no longer exist. Absent in host
|
|
140
|
-
# projects, which have the hook but none of Lisa's generators.
|
|
141
|
-
if [ -f "scripts/check-derived-artifacts.mjs" ] && command -v node >/dev/null 2>&1; then
|
|
142
|
-
echo "🧾 Checking generated artifacts..."
|
|
143
|
-
node scripts/check-derived-artifacts.mjs --staged
|
|
144
|
-
if [ $? -ne 0 ]; then
|
|
145
|
-
exit 1
|
|
146
|
-
fi
|
|
147
|
-
fi
|
|
148
257
|
# END: AI GUARDRAILS
|
|
@@ -38,21 +38,100 @@ fi
|
|
|
38
38
|
|
|
39
39
|
echo "📦 Using package manager: $PACKAGE_MANAGER"
|
|
40
40
|
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Gate-driven checks. See the matching block in .husky/pre-commit for the full
|
|
43
|
+
# contract; the exit-code routing is identical, at the `push` moment.
|
|
44
|
+
#
|
|
45
|
+
# exit 0 → the declared gates at this moment ran and none of them blocked.
|
|
46
|
+
# exit 1 → a required gate FAILED. Block the push.
|
|
47
|
+
# exit 78 → there is no `gates` block at all. Nothing is covered.
|
|
48
|
+
# other → the runner itself could not run. Nothing was proved. Say so
|
|
49
|
+
# loudly and cover nothing.
|
|
50
|
+
#
|
|
51
|
+
# ONE STEP AT A TIME, NOT ALL OR NOTHING. Exit 0 says the declared gates
|
|
52
|
+
# passed, not that the registry covers every property the steps below prove, so
|
|
53
|
+
# it cannot be the thing that skips them wholesale. The runner writes the
|
|
54
|
+
# properties it covers into $LISA_GATE_COVERAGE and each step stands down only
|
|
55
|
+
# against its own. Fail-safe: no file, an empty file, a runner that could not
|
|
56
|
+
# run, or no exact match all mean the built-in step runs.
|
|
57
|
+
#
|
|
58
|
+
# The project-specific extension slots at the bottom of this file run either
|
|
59
|
+
# way — they are project behaviour, not registry gates.
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
GATE_RUNNER="scripts/lisa-run-gates.mjs"
|
|
62
|
+
if [ ! -f "$GATE_RUNNER" ]; then
|
|
63
|
+
GATE_RUNNER="all/copy-overwrite/scripts/lisa-run-gates.mjs"
|
|
64
|
+
fi
|
|
65
|
+
LISA_GATE_COVERAGE=""
|
|
66
|
+
if [ -f "$GATE_RUNNER" ] && command -v node >/dev/null 2>&1; then
|
|
67
|
+
LISA_GATE_COVERAGE="$(mktemp "${TMPDIR:-/tmp}/lisa-gate-coverage.XXXXXXXX")" || LISA_GATE_COVERAGE=""
|
|
68
|
+
trap 'rm -f "$LISA_GATE_COVERAGE"' EXIT
|
|
69
|
+
if [ -n "$LISA_GATE_COVERAGE" ]; then
|
|
70
|
+
node "$GATE_RUNNER" --moment=push --coverage="$LISA_GATE_COVERAGE"
|
|
71
|
+
else
|
|
72
|
+
# No temp file, so nothing can be covered and every built-in step runs.
|
|
73
|
+
node "$GATE_RUNNER" --moment=push
|
|
74
|
+
fi
|
|
75
|
+
GATE_STATUS=$?
|
|
76
|
+
if [ $GATE_STATUS -eq 1 ]; then
|
|
77
|
+
echo ""
|
|
78
|
+
echo "❌ Push blocked: a required gate failed. See the FAILED line above."
|
|
79
|
+
echo ""
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
if [ $GATE_STATUS -ne 0 ]; then
|
|
83
|
+
# Coverage from a run that did not finish is not evidence.
|
|
84
|
+
if [ -n "$LISA_GATE_COVERAGE" ]; then
|
|
85
|
+
: > "$LISA_GATE_COVERAGE"
|
|
86
|
+
fi
|
|
87
|
+
if [ $GATE_STATUS -ne 78 ]; then
|
|
88
|
+
echo ""
|
|
89
|
+
echo "⚠️ The gate runner could not run (exit $GATE_STATUS)."
|
|
90
|
+
echo " Nothing was proved by the gate registry — this is NOT a pass."
|
|
91
|
+
echo " Running the built-in checks instead."
|
|
92
|
+
echo ""
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
else
|
|
96
|
+
echo "ℹ️ Gate runner unavailable; running the built-in checks."
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Whether a declared gate covers every property named, so the built-in step
|
|
100
|
+
# that proves them can stand down. Exact whole-line matching: a gate id that is
|
|
101
|
+
# a prefix of another must never satisfy it.
|
|
102
|
+
lisa_gate_covers() {
|
|
103
|
+
[ -n "$LISA_GATE_COVERAGE" ] || return 1
|
|
104
|
+
[ -s "$LISA_GATE_COVERAGE" ] || return 1
|
|
105
|
+
for _lisa_gate in "$@"; do
|
|
106
|
+
grep -Fqx -- "$_lisa_gate" "$LISA_GATE_COVERAGE" || return 1
|
|
107
|
+
done
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# BEGIN: built-in checks — the pre-registry path, kept step for step so a
|
|
112
|
+
# project without a `gates` block behaves exactly as it did before.
|
|
113
|
+
|
|
41
114
|
# Run the whole-project type check once before code leaves the machine. It is
|
|
42
115
|
# intentionally not a pre-commit gate because TypeScript cannot check only the
|
|
43
116
|
# staged files and rebuilding the full program makes small commits too slow.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
echo "
|
|
48
|
-
|
|
117
|
+
if lisa_gate_covers type-correctness; then
|
|
118
|
+
echo "ℹ️ Covered by the type-correctness gate; the built-in type check stands down."
|
|
119
|
+
else
|
|
120
|
+
echo "🔍 Running type check..."
|
|
121
|
+
$RUNNER typecheck
|
|
122
|
+
if [ $? -ne 0 ]; then
|
|
123
|
+
echo "❌ Type check failed. Please fix TypeScript errors before pushing."
|
|
124
|
+
exit 1
|
|
125
|
+
fi
|
|
49
126
|
fi
|
|
50
127
|
|
|
51
128
|
# Run security audit
|
|
52
129
|
echo "🔒 Running security audit..."
|
|
53
130
|
|
|
54
131
|
# jq is required for all audit paths (JSON config reading, npm/yarn filtering)
|
|
55
|
-
if
|
|
132
|
+
if lisa_gate_covers dependency-vulnerability; then
|
|
133
|
+
echo "ℹ️ Covered by the dependency-vulnerability gate; the built-in audit stands down."
|
|
134
|
+
elif ! command -v jq >/dev/null 2>&1; then
|
|
56
135
|
echo ""
|
|
57
136
|
echo "⚠️ WARNING: jq is not installed - required for security audit"
|
|
58
137
|
echo ""
|
|
@@ -179,7 +258,79 @@ else
|
|
|
179
258
|
# devDependencies too and the gate fails on dev-only CVEs that never ship —
|
|
180
259
|
# the SE-5221 false positive. bun honours `--production` even though
|
|
181
260
|
# `bun audit --help` omits it from the flag list.
|
|
182
|
-
|
|
261
|
+
# Normalize bun's transport before parsing, or this gate FAILS OPEN.
|
|
262
|
+
#
|
|
263
|
+
# Measured: bun can return gzip-compressed JSON on stdout while reporting a
|
|
264
|
+
# parse error on stderr. The previous single-line form captured that binary
|
|
265
|
+
# into AUDIT_JSON, the jq filter below then failed, UNFIXED_HIGH came back
|
|
266
|
+
# empty, UNFIXED_COUNT resolved to empty, `-gt 0` was false, and the hook
|
|
267
|
+
# printed "No high or critical vulnerabilities found" and allowed the push.
|
|
268
|
+
# A security gate reporting success having parsed nothing.
|
|
269
|
+
#
|
|
270
|
+
# Lisa's own .husky/pre-push was patched for this; this shipped copy was
|
|
271
|
+
# not, so every host project on this template carried the fail-open. Keep
|
|
272
|
+
# the two in step — two copies of one check cannot be kept aligned by
|
|
273
|
+
# intention, which is exactly how this drifted.
|
|
274
|
+
# Every path out of this block either parses advisory data or blocks the
|
|
275
|
+
# push. The gzip branch below handles the transport shape that was measured;
|
|
276
|
+
# it cannot be the only one handled, because "some other unparseable shape"
|
|
277
|
+
# is precisely the case nobody has seen yet. An audit whose payload was
|
|
278
|
+
# never parsed has audited nothing, and printing "no vulnerabilities found"
|
|
279
|
+
# from that state is the fail-open this whole block exists to remove.
|
|
280
|
+
#
|
|
281
|
+
# This hook does not run under `set -e`, so an unguarded mktemp would leave
|
|
282
|
+
# the path empty, the redirect would fail, and `[ ! -s "" ]` would be true —
|
|
283
|
+
# a clean verdict from a file that was never written. Same rule the
|
|
284
|
+
# pre-commit gitleaks scan states: a degraded security check is worse than a
|
|
285
|
+
# blocked push.
|
|
286
|
+
if ! AUDIT_OUTPUT="$(mktemp "${TMPDIR:-/tmp}/bun-audit.XXXXXXXX")" || [ -z "$AUDIT_OUTPUT" ]; then
|
|
287
|
+
echo "❌ Push blocked: could not create a temporary file for the security audit." >&2
|
|
288
|
+
echo " Check that TMPDIR (${TMPDIR:-/tmp}) exists and is writable." >&2
|
|
289
|
+
exit 1
|
|
290
|
+
fi
|
|
291
|
+
if ! AUDIT_STDERR="$(mktemp "${TMPDIR:-/tmp}/bun-audit-err.XXXXXXXX")" || [ -z "$AUDIT_STDERR" ]; then
|
|
292
|
+
rm -f "$AUDIT_OUTPUT"
|
|
293
|
+
echo "❌ Push blocked: could not create a temporary file for the security audit." >&2
|
|
294
|
+
echo " Check that TMPDIR (${TMPDIR:-/tmp}) exists and is writable." >&2
|
|
295
|
+
exit 1
|
|
296
|
+
fi
|
|
297
|
+
# bun's own diagnostics are kept rather than discarded. "Lockfile not found"
|
|
298
|
+
# is the difference between a broken audit and a clean one, and throwing it
|
|
299
|
+
# away is how a failed command comes to read as a measured zero.
|
|
300
|
+
bun audit --production --json > "$AUDIT_OUTPUT" 2>"$AUDIT_STDERR" || true
|
|
301
|
+
if [ ! -s "$AUDIT_OUTPUT" ]; then
|
|
302
|
+
echo "❌ Push blocked: bun audit produced no output, so nothing was audited." >&2
|
|
303
|
+
echo " This is NOT a clean result — a clean audit prints JSON ({} when" >&2
|
|
304
|
+
echo " there are no advisories). Empty output means the audit did not run." >&2
|
|
305
|
+
if [ -s "$AUDIT_STDERR" ]; then
|
|
306
|
+
echo " bun said:" >&2
|
|
307
|
+
sed 's/^/ /' "$AUDIT_STDERR" >&2
|
|
308
|
+
fi
|
|
309
|
+
rm -f "$AUDIT_OUTPUT" "$AUDIT_STDERR"
|
|
310
|
+
exit 1
|
|
311
|
+
elif jq empty "$AUDIT_OUTPUT" >/dev/null 2>&1; then
|
|
312
|
+
AUDIT_JSON=$(cat "$AUDIT_OUTPUT")
|
|
313
|
+
else
|
|
314
|
+
DECOMPRESSED_AUDIT_OUTPUT="$(mktemp "${TMPDIR:-/tmp}/bun-audit-gz.XXXXXXXX")"
|
|
315
|
+
if [ -n "$DECOMPRESSED_AUDIT_OUTPUT" ]; then
|
|
316
|
+
gzip -dc "$AUDIT_OUTPUT" > "$DECOMPRESSED_AUDIT_OUTPUT" 2>/dev/null || true
|
|
317
|
+
fi
|
|
318
|
+
# `-s` before `jq empty`: an EMPTY file passes `jq empty` (no input is
|
|
319
|
+
# not invalid input), so a gunzip that failed and wrote nothing would be
|
|
320
|
+
# accepted as valid JSON and set AUDIT_JSON to the empty string.
|
|
321
|
+
if [ -s "$DECOMPRESSED_AUDIT_OUTPUT" ] && jq empty "$DECOMPRESSED_AUDIT_OUTPUT" >/dev/null 2>&1; then
|
|
322
|
+
AUDIT_JSON=$(cat "$DECOMPRESSED_AUDIT_OUTPUT")
|
|
323
|
+
rm -f "$DECOMPRESSED_AUDIT_OUTPUT"
|
|
324
|
+
else
|
|
325
|
+
rm -f "$DECOMPRESSED_AUDIT_OUTPUT" "$AUDIT_OUTPUT" "$AUDIT_STDERR"
|
|
326
|
+
echo "❌ Push blocked: bun audit output is neither JSON nor gzipped JSON," >&2
|
|
327
|
+
echo " so no advisory data was parsed and nothing was audited." >&2
|
|
328
|
+
echo " This is NOT a clean result. Re-run 'bun audit --production --json'" >&2
|
|
329
|
+
echo " to see what it emitted." >&2
|
|
330
|
+
exit 1
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
rm -f "$AUDIT_OUTPUT" "$AUDIT_STDERR"
|
|
183
334
|
UNFIXED_HIGH=$(echo "$AUDIT_JSON" | jq -r --arg ids "$AUDIT_EXCLUSIONS" '
|
|
184
335
|
($ids | split(" ") | map(select(length > 0))) as $ex
|
|
185
336
|
| [ .[]? | .[]?
|
|
@@ -188,7 +339,18 @@ else
|
|
|
188
339
|
| select(($ex | index($g)) | not)
|
|
189
340
|
| $g ]
|
|
190
341
|
| unique')
|
|
191
|
-
|
|
342
|
+
# A count that could not be computed is not a count of zero. The old `|| echo
|
|
343
|
+
# 0` turned a jq filter that errored — an audit whose JSON shape this hook
|
|
344
|
+
# does not understand — into "no vulnerabilities found", which is the same
|
|
345
|
+
# fail-open as the transport hole above, one step further down.
|
|
346
|
+
UNFIXED_COUNT=$(echo "$UNFIXED_HIGH" | jq -r 'length' 2>/dev/null || echo "")
|
|
347
|
+
if [ -z "$UNFIXED_COUNT" ]; then
|
|
348
|
+
echo "❌ Push blocked: the audit payload parsed, but the advisory filter" >&2
|
|
349
|
+
echo " produced no count, so no advisory was evaluated." >&2
|
|
350
|
+
echo " This is NOT a clean result. Re-run 'bun audit --production --json'" >&2
|
|
351
|
+
echo " to see the shape it returned." >&2
|
|
352
|
+
exit 1
|
|
353
|
+
fi
|
|
192
354
|
if [ "$UNFIXED_COUNT" -gt 0 ]; then
|
|
193
355
|
echo "⚠️ Security audit failed. Unresolved high/critical advisories in production dependencies:"
|
|
194
356
|
echo "$UNFIXED_HIGH" | jq -r '.[]'
|
|
@@ -200,7 +362,9 @@ else
|
|
|
200
362
|
fi
|
|
201
363
|
|
|
202
364
|
# Run slow lint rules - only if script exists
|
|
203
|
-
if
|
|
365
|
+
if lisa_gate_covers code-style-slow; then
|
|
366
|
+
echo "ℹ️ Covered by the code-style-slow gate; the built-in slow lint stands down."
|
|
367
|
+
elif jq -e '.scripts["lint:slow"]' package.json >/dev/null 2>&1; then
|
|
204
368
|
echo "🐢 Running slow lint rules..."
|
|
205
369
|
$RUNNER lint:slow
|
|
206
370
|
if [ $? -ne 0 ]; then
|
|
@@ -223,7 +387,9 @@ else
|
|
|
223
387
|
KNIP_SCRIPT=""
|
|
224
388
|
fi
|
|
225
389
|
|
|
226
|
-
if
|
|
390
|
+
if lisa_gate_covers dead-code; then
|
|
391
|
+
echo "ℹ️ Covered by the dead-code gate; the built-in knip run stands down."
|
|
392
|
+
elif [ -n "$KNIP_SCRIPT" ]; then
|
|
227
393
|
echo "🗑️ Running dead code detection (knip)..."
|
|
228
394
|
$RUNNER "$KNIP_SCRIPT"
|
|
229
395
|
if [ $? -ne 0 ]; then
|
|
@@ -237,20 +403,30 @@ else
|
|
|
237
403
|
echo "ℹ️ Skipping dead code detection (knip not configured)"
|
|
238
404
|
fi
|
|
239
405
|
|
|
240
|
-
# Run unit tests with coverage
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if
|
|
244
|
-
echo "
|
|
245
|
-
|
|
406
|
+
# Run unit tests with coverage. `test:cov` proves two properties in one run —
|
|
407
|
+
# the suite passes AND the coverage thresholds hold — so it stands down only
|
|
408
|
+
# when both have a gate declared.
|
|
409
|
+
if lisa_gate_covers test-correctness coverage-adequacy; then
|
|
410
|
+
echo "ℹ️ Covered by the test-correctness and coverage-adequacy gates; the built-in run stands down."
|
|
411
|
+
else
|
|
412
|
+
echo "🧪 Running unit tests with coverage..."
|
|
413
|
+
$RUNNER test:cov
|
|
414
|
+
if [ $? -ne 0 ]; then
|
|
415
|
+
echo "❌ Unit tests or coverage thresholds failed. Please fix before pushing."
|
|
416
|
+
exit 1
|
|
417
|
+
fi
|
|
246
418
|
fi
|
|
247
419
|
|
|
248
420
|
# Run integration tests
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
echo "
|
|
253
|
-
|
|
421
|
+
if lisa_gate_covers test-integration; then
|
|
422
|
+
echo "ℹ️ Covered by the test-integration gate; the built-in run stands down."
|
|
423
|
+
else
|
|
424
|
+
echo "🧪 Running integration tests..."
|
|
425
|
+
$RUNNER test:integration
|
|
426
|
+
if [ $? -ne 0 ]; then
|
|
427
|
+
echo "❌ Integration tests failed. Please fix failing tests before pushing."
|
|
428
|
+
exit 1
|
|
429
|
+
fi
|
|
254
430
|
fi
|
|
255
431
|
|
|
256
432
|
# Run mutation-testing gate - only if script exists.
|
|
@@ -258,7 +434,9 @@ fi
|
|
|
258
434
|
# (mutation.gate.json: "enabled": false), so this adds no cost to projects that
|
|
259
435
|
# have not opted in. When enabled, it runs Stryker diff-only on changed files
|
|
260
436
|
# and fails the push when the mutation score is below thresholds.break.
|
|
261
|
-
if
|
|
437
|
+
if lisa_gate_covers test-meaningfulness; then
|
|
438
|
+
echo "ℹ️ Covered by the test-meaningfulness gate; the built-in run stands down."
|
|
439
|
+
elif jq -e '.scripts["test:mutation"]' package.json >/dev/null 2>&1; then
|
|
262
440
|
echo "🧬 Running mutation-testing gate..."
|
|
263
441
|
$RUNNER test:mutation
|
|
264
442
|
if [ $? -ne 0 ]; then
|
|
@@ -337,6 +515,8 @@ fi
|
|
|
337
515
|
# fi
|
|
338
516
|
# fi
|
|
339
517
|
|
|
518
|
+
# END: built-in checks
|
|
519
|
+
|
|
340
520
|
# Managed verification (UAT) extension slot (see .husky/pre-push.verify).
|
|
341
521
|
# Opt-in project types (e.g. phaser) ship this snippet via Lisa; it mirrors the
|
|
342
522
|
# CI verification_coverage gate locally. It is absent for types that have not
|