@codyswann/lisa 3.15.1 → 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 +76 -9
- 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
- package/ui/index.html +271 -12
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive the CLIs a project's own configuration already implies.
|
|
3
|
+
*
|
|
4
|
+
* The symmetric half of `routing-floor.mjs`. A credential and the binary that
|
|
5
|
+
* consumes it are the same question asked twice — can this agent do the work —
|
|
6
|
+
* and both were answered only by a hand-maintained list.
|
|
7
|
+
*
|
|
8
|
+
* `detect-tooling` already knows most of these pairings and states them well:
|
|
9
|
+
* "the work-item guardrails shell out to `gh` for every tracker read". But it
|
|
10
|
+
* knows them at *proposal* time, as suggestions a human runs a skill to see and
|
|
11
|
+
* then copies into config. Nothing derives them at runtime, so a project that
|
|
12
|
+
* never ran the skill — or ran it before it adopted Maestro — has a manifest
|
|
13
|
+
* that silently understates what its agents need.
|
|
14
|
+
*
|
|
15
|
+
* This promotes those pairings to a derivation. The floor is unioned into the
|
|
16
|
+
* required set, never written into config, so it cannot drift from the routing
|
|
17
|
+
* that produces it.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately narrow. An entry belongs here only when the configuration that
|
|
20
|
+
* implies it makes the tool unavoidable — not merely likely. A project can have
|
|
21
|
+
* `quality.testCoverage` thresholds and run them through any runner, so no CLI
|
|
22
|
+
* is derived from that; `secrets.provider: "bitwarden"` on the other hand
|
|
23
|
+
* cannot resolve one credential without `bws`.
|
|
24
|
+
* @module tool-floor
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Config predicates that make a CLI unavoidable, with the reason to report.
|
|
29
|
+
*
|
|
30
|
+
* The reason is not decoration. A missing tool has two valid remedies — install
|
|
31
|
+
* it, or change the configuration that demands it — and an operator cannot pick
|
|
32
|
+
* between them without knowing which line of config is responsible.
|
|
33
|
+
*/
|
|
34
|
+
const DERIVATIONS = [
|
|
35
|
+
{
|
|
36
|
+
tool: "gh",
|
|
37
|
+
applies: cfg => cfg.tracker === "github" || cfg.source === "github",
|
|
38
|
+
reason: cfg =>
|
|
39
|
+
`${cfg.tracker === "github" ? "tracker" : "source"} is "github", and ` +
|
|
40
|
+
`the work-item guardrails shell out to gh for every tracker read`,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
tool: "bws",
|
|
44
|
+
applies: cfg => cfg.secrets?.provider === "bitwarden",
|
|
45
|
+
reason: () =>
|
|
46
|
+
`secrets.provider is "bitwarden", and the CLI is how every secret is ` +
|
|
47
|
+
`resolved`,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
tool: "doppler",
|
|
51
|
+
applies: cfg => cfg.secrets?.provider === "doppler",
|
|
52
|
+
reason: () => `secrets.provider is "doppler"`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
tool: "maestro",
|
|
56
|
+
// Keyed on the coverage block rather than a `.maestro` directory: config is
|
|
57
|
+
// the declaration that this project gates on Maestro, where a stray
|
|
58
|
+
// directory could be a leftover. `detect-tooling` uses the directory
|
|
59
|
+
// because it is guessing; here the project has already said so.
|
|
60
|
+
applies: cfg => cfg.quality?.e2eCoverage?.maestro !== undefined,
|
|
61
|
+
reason: () =>
|
|
62
|
+
`quality.e2eCoverage.maestro is configured, and nothing installs the ` +
|
|
63
|
+
`maestro binary`,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The CLIs a project's configuration makes mandatory.
|
|
69
|
+
* @param {object} [config] Parsed `.lisa.config.json` root.
|
|
70
|
+
* @returns {Array<{name: string, reason: string}>} Sorted derived tools.
|
|
71
|
+
*/
|
|
72
|
+
export function toolFloor(config = {}) {
|
|
73
|
+
return DERIVATIONS.filter(entry => safely(() => entry.applies(config)))
|
|
74
|
+
.map(entry => ({ name: entry.tool, reason: entry.reason(config) }))
|
|
75
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Evaluate a predicate, treating a malformed config as "does not apply".
|
|
80
|
+
*
|
|
81
|
+
* A config shaped unexpectedly is a problem for the schema validator to report
|
|
82
|
+
* in its own vocabulary. Throwing here would make a readiness check the place a
|
|
83
|
+
* structural defect first surfaces, with a message about tools that names
|
|
84
|
+
* nothing an operator can act on.
|
|
85
|
+
* @param {Function} predicate Test to run.
|
|
86
|
+
* @returns {boolean} The result, or false when it threw.
|
|
87
|
+
*/
|
|
88
|
+
function safely(predicate) {
|
|
89
|
+
try {
|
|
90
|
+
return Boolean(predicate());
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -25,8 +25,8 @@ import { execFileSync } from "node:child_process";
|
|
|
25
25
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
|
|
28
|
-
import { readRemoteEnvConfig } from "./setup-remote-env.mjs";
|
|
29
|
-
import {
|
|
28
|
+
import { probe, readRemoteEnvConfig } from "./setup-remote-env.mjs";
|
|
29
|
+
import { planToolchain } from "./toolchain.mjs";
|
|
30
30
|
|
|
31
31
|
/** Collected results, so every check runs before anything reports failure. */
|
|
32
32
|
const results = [];
|
|
@@ -84,39 +84,21 @@ function node(args) {
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Assert each declared tool is present at its pinned or minimum version.
|
|
87
|
+
*
|
|
88
|
+
* Delegates every decision to `planToolchain` rather than probing here. This
|
|
89
|
+
* function used to run its own loop, and the copy silently forgot `minVersion`:
|
|
90
|
+
* a `require` entry passed as long as the binary answered `--version` at all,
|
|
91
|
+
* so a container verified clean against a node older than the manifest
|
|
92
|
+
* demanded. The plan-side check rejected exactly what this one accepted.
|
|
93
|
+
*
|
|
94
|
+
* That is the same failure the note validators had before one shared module
|
|
95
|
+
* replaced them, and it recurs for the same reason — two implementations of one
|
|
96
|
+
* question cannot be kept in step by intention. There is now one.
|
|
87
97
|
* @param {object} tools Toolchain manifest.
|
|
88
98
|
*/
|
|
89
99
|
function verifyToolchain(tools) {
|
|
90
|
-
for (const
|
|
91
|
-
|
|
92
|
-
const out = execFileSync(tool.name, ["--version"], {
|
|
93
|
-
encoding: "utf8",
|
|
94
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
95
|
-
});
|
|
96
|
-
check(true, `tool ${tool.name}`, extractVersion(out) ?? "present");
|
|
97
|
-
} catch {
|
|
98
|
-
check(false, `tool ${tool.name}`, "required but not present");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
for (const tool of tools.install ?? []) {
|
|
102
|
-
try {
|
|
103
|
-
const out = execFileSync(tool.name, ["--version"], {
|
|
104
|
-
encoding: "utf8",
|
|
105
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
106
|
-
});
|
|
107
|
-
const found = extractVersion(out);
|
|
108
|
-
check(
|
|
109
|
-
found === tool.version,
|
|
110
|
-
`tool ${tool.name}`,
|
|
111
|
-
`${found} (pin ${tool.version})`
|
|
112
|
-
);
|
|
113
|
-
} catch {
|
|
114
|
-
check(
|
|
115
|
-
false,
|
|
116
|
-
`tool ${tool.name}`,
|
|
117
|
-
`pinned ${tool.version} but not installed`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
100
|
+
for (const step of planToolchain(tools, probe, "remote")) {
|
|
101
|
+
check(step.action === "present", `tool ${step.name}`, step.reason);
|
|
120
102
|
}
|
|
121
103
|
}
|
|
122
104
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -120,6 +120,15 @@
|
|
|
120
120
|
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/setup-jira-cli.sh"
|
|
121
121
|
}
|
|
122
122
|
]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"matcher": "",
|
|
126
|
+
"hooks": [
|
|
127
|
+
{
|
|
128
|
+
"type": "command",
|
|
129
|
+
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/secrets-preflight.sh"
|
|
130
|
+
}
|
|
131
|
+
]
|
|
123
132
|
}
|
|
124
133
|
],
|
|
125
134
|
"sessionEnd": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prove at session start that this agent can actually do the work: that the
|
|
3
|
+
# credentials the project needs resolve, and that the CLIs it needs are on PATH.
|
|
4
|
+
#
|
|
5
|
+
# One hook for both because it is one question — can this agent do the work —
|
|
6
|
+
# and an operator reading two separate reports has to assemble the answer
|
|
7
|
+
# themselves. Both halves also share the same failure mode if split: two hooks
|
|
8
|
+
# means two subprocess spawns on every session start, for a check that is silent
|
|
9
|
+
# almost every time.
|
|
10
|
+
#
|
|
11
|
+
# Injects rather than blocks, and that is deliberate. The right response to a
|
|
12
|
+
# missing credential or tool is for the agent to know before it claims work and
|
|
13
|
+
# to route the item to blocked with a reason a human can act on — not for the
|
|
14
|
+
# session to die. A session that cannot reach the vault can still write code,
|
|
15
|
+
# review a PR, or answer a question; killing it would tax every session for a
|
|
16
|
+
# minority need, and a control people route around enforces nothing.
|
|
17
|
+
#
|
|
18
|
+
# The hard gates live where they can afford to be hard: `lisa doctor` and CI
|
|
19
|
+
# run the same two scripts and exit non-zero on them.
|
|
20
|
+
#
|
|
21
|
+
# Silent on success on purpose. A control that announces itself when everything
|
|
22
|
+
# is fine trains its readers to skim past it, and then it is decoration.
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
26
|
+
if [ -n "$INPUT" ]; then
|
|
27
|
+
HOOK_EVENT=$(printf '%s' "$INPUT" | jq -r '.hook_event_name // "SessionStart"' 2>/dev/null || echo "SessionStart")
|
|
28
|
+
else
|
|
29
|
+
HOOK_EVENT="SessionStart"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
ROOT="${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}}"
|
|
33
|
+
SECRETS="$ROOT/skills/lisa-secrets-access/scripts/preflight-secrets.mjs"
|
|
34
|
+
TOOLS="$ROOT/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs"
|
|
35
|
+
|
|
36
|
+
command -v node >/dev/null 2>&1 || exit 0
|
|
37
|
+
# The hook's whole output is one `jq -n` document. Without jq there is nothing
|
|
38
|
+
# to write, so exiting is the only honest outcome — the alternative is a hook
|
|
39
|
+
# that prints `jq: command not found` and exits 127, which reports a failure
|
|
40
|
+
# from a check designed never to block. The doctor and CI gates run the same
|
|
41
|
+
# two scripts without jq involved.
|
|
42
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
43
|
+
|
|
44
|
+
# stderr carries each report; stdout is reserved for the hook's JSON. A script
|
|
45
|
+
# that is absent (partial or older install) or fails to run at all contributes
|
|
46
|
+
# nothing, because a broken hook must not manufacture a finding.
|
|
47
|
+
REPORT=""
|
|
48
|
+
for script in "$SECRETS" "$TOOLS"; do
|
|
49
|
+
[ -f "$script" ] || continue
|
|
50
|
+
# An escape hatch that is honest about what it costs, and scoped to what it
|
|
51
|
+
# names. Set when a surface has no secrets provider by design and the noise is
|
|
52
|
+
# not worth it; the doctor and CI gates are unaffected, so opting out here
|
|
53
|
+
# does not opt out of the check entirely. It must not also silence the
|
|
54
|
+
# TOOLING half — a variable about secrets that hides a missing `gh` or
|
|
55
|
+
# `maestro` is an opt-out from a check nobody opted out of.
|
|
56
|
+
if [ "$script" = "$SECRETS" ] && [ "${LISA_SKIP_SECRETS_PREFLIGHT:-}" = "1" ]; then
|
|
57
|
+
continue
|
|
58
|
+
fi
|
|
59
|
+
OUT=$(node "$script" 2>&1 >/dev/null) || true
|
|
60
|
+
[ -n "$OUT" ] || continue
|
|
61
|
+
[ -n "$REPORT" ] && REPORT+=$'\n\n'
|
|
62
|
+
REPORT+="$OUT"
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
[ -n "$REPORT" ] || exit 0
|
|
66
|
+
|
|
67
|
+
CONTEXT="## Readiness preflight
|
|
68
|
+
|
|
69
|
+
$REPORT"
|
|
70
|
+
|
|
71
|
+
jq -n --arg event "$HOOK_EVENT" --arg ctx "$CONTEXT" \
|
|
72
|
+
'{"hookSpecificOutput": {"hookEventName": $event, "additionalContext": $ctx}}'
|
|
@@ -422,14 +422,93 @@ The verdict ladder is:
|
|
|
422
422
|
- `READY_WITH_WARNINGS` — no `FAIL`, but one or more `WARN`.
|
|
423
423
|
- `NOT_READY` — one or more `FAIL`.
|
|
424
424
|
|
|
425
|
+
## Gate configuration
|
|
426
|
+
|
|
427
|
+
```sh
|
|
428
|
+
node scripts/lisa-gates.mjs validate # shape + unknown gate ids
|
|
429
|
+
node scripts/lisa-gates.mjs list --moment=pull-request
|
|
430
|
+
node scripts/lisa-gates.mjs contexts # branch-protection contexts
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
A gate is a **property** — *credential leakage* — not a tool. `gitleaks` is one way to prove
|
|
434
|
+
that property, and which way is the project's choice: each gate names a task in the project's own
|
|
435
|
+
runner, so swapping the tool changes one line of project config and nothing in Lisa.
|
|
436
|
+
|
|
437
|
+
`validate` refuses an unknown gate id rather than ignoring it. A misspelled `credential-leakge`
|
|
438
|
+
would otherwise read as an enabled guarantee and run nothing at all — the same silent-hole shape
|
|
439
|
+
as a skipped required check. Gates a project invents carry an `x-` prefix, which Lisa runs without
|
|
440
|
+
pretending to understand.
|
|
441
|
+
|
|
442
|
+
`contexts` is the value that replaces a hand-transcribed branch-protection list. It is scoped to
|
|
443
|
+
one environment: a gate required before a production deploy is **not** thereby a merge blocker on a
|
|
444
|
+
pull request, and collapsing the two would promote every deploy-time gate into branch protection.
|
|
445
|
+
|
|
446
|
+
When reconciling a ruleset against it, pass `--previous=` with any label retired in the last
|
|
447
|
+
release. Downstream repositories call the shared workflow unpinned, so a renamed job reaches every
|
|
448
|
+
repository before any of them has reconciled — and a required context that never reports leaves
|
|
449
|
+
pull requests waiting indefinitely. The fastest way out of that is deleting the requirement, which
|
|
450
|
+
is how a rename ends up removing a guarantee. Emitting both labels for one release avoids it.
|
|
451
|
+
|
|
452
|
+
### Reconciling against the live ruleset
|
|
453
|
+
|
|
454
|
+
`contexts` says what the repository *should* require. Reconciliation asks whether GitHub agrees:
|
|
455
|
+
|
|
456
|
+
```sh
|
|
457
|
+
node scripts/lisa-reconcile-policy.mjs --dry-run # read-only: what would change
|
|
458
|
+
node scripts/lisa-reconcile-policy.mjs --on-drift=report # report, write nothing
|
|
459
|
+
node scripts/lisa-reconcile-policy.mjs --on-drift=block # exit 1 on any drift, for CI
|
|
460
|
+
node scripts/lisa-reconcile-policy.mjs # honors policy.on_drift
|
|
461
|
+
node scripts/lisa-reconcile-policy.mjs --previous="🧽 Lint" # keep a renamed context required
|
|
462
|
+
node scripts/lisa-reconcile-policy.mjs --prune # also remove EXTRA contexts
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
It reads the live ruleset and repository settings through `gh`, compares them against the derived
|
|
466
|
+
contexts and the `policy` block, and reports three sets: **MISSING** (declared, not live), **EXTRA**
|
|
467
|
+
(live, not declared), **MATCHED**. `--dry-run` never writes, whatever `policy.on_drift` says.
|
|
468
|
+
|
|
469
|
+
Three behaviors need an operator to understand them before reading a report.
|
|
470
|
+
|
|
471
|
+
**1. `UNPROVEN` is not a pass.** If `gh` is missing, unauthenticated, or the API errors — a private
|
|
472
|
+
repository on a plan without rulesets answers `403` — the verdict is `UNPROVEN` and the exit code is
|
|
473
|
+
`2`. It is neither of its neighbours, and the distinction is the same one the secrets preflight
|
|
474
|
+
draws with `unreachable`: reported as clean it is a vacuous green, clean precisely because nothing
|
|
475
|
+
was learned; reported as drift it sends someone to fix a ruleset that may be perfectly correct. The
|
|
476
|
+
drift sets come back `null` rather than empty, because empty is what a clean repository looks like.
|
|
477
|
+
`on_drift` does not soften this — not even `report` — because `on_drift` decides what to do about a
|
|
478
|
+
drift that was *measured*, and here nothing was. Map it to doctor's `WARN`/`FAIL` on the same rule
|
|
479
|
+
as any other unavailable check surface: never `PASS`.
|
|
480
|
+
|
|
481
|
+
**2. An `EXTRA` context is reported, never removed.** Lisa does not own the whole required list.
|
|
482
|
+
`SonarCloud Code Analysis`, `GitGuardian Security Checks`, and `CodeRabbit` are posted by external
|
|
483
|
+
apps that no gates block declares, so `contextsFor` cannot derive them and every one of them is
|
|
484
|
+
EXTRA by construction. Under `repair` the script therefore ADDS what is missing and leaves what is
|
|
485
|
+
extra alone, naming each one. Removing them requires `--prune`, and the right way to clear the list
|
|
486
|
+
is one at a time: each EXTRA context is either an app to keep, or a gate that belongs in
|
|
487
|
+
`.lisa.config.json` — decide which before pruning anything.
|
|
488
|
+
|
|
489
|
+
**3. Keep both names during a rename.** `--previous=<old label>` requires the old and the new
|
|
490
|
+
context simultaneously for one release. Without it the reconciliation reports the still-live old
|
|
491
|
+
context as EXTRA (and `--prune` would delete it) while in-flight pull requests wait on a context
|
|
492
|
+
that will never report again.
|
|
493
|
+
|
|
494
|
+
Repair writes exactly two things: required contexts on a ruleset, and repository settings. Policy
|
|
495
|
+
carried by the *shape* of a rule — linear history, signed commits, force-push and deletion
|
|
496
|
+
protection, conversation resolution — is compared here and repaired by
|
|
497
|
+
`scripts/lisa-github-rulesets.sh`, which owns rule construction; the reconciler reports those and
|
|
498
|
+
names that script rather than reshaping rules it did not build. When more than one ruleset requires
|
|
499
|
+
status checks it refuses to guess which owns the derived contexts and asks for `--ruleset=<name>`,
|
|
500
|
+
because writing to the wrong one enforces a context under a different ref-name condition.
|
|
501
|
+
|
|
425
502
|
## Secrets configuration
|
|
426
503
|
|
|
427
504
|
Run the secrets health checks through the skill that owns the contract, rather than reimplementing
|
|
428
505
|
any part of it here:
|
|
429
506
|
|
|
430
507
|
```sh
|
|
431
|
-
node .claude/skills/lisa-secrets-access/scripts/validate-config.mjs
|
|
432
|
-
node .claude/skills/lisa-secrets-access/scripts/
|
|
508
|
+
node .claude/skills/lisa-secrets-access/scripts/validate-config.mjs # shape
|
|
509
|
+
node .claude/skills/lisa-secrets-access/scripts/preflight-secrets.mjs # credential readiness
|
|
510
|
+
node .claude/skills/lisa-setup-remote-env/scripts/preflight-tools.mjs # tooling readiness
|
|
511
|
+
node .claude/skills/lisa-secrets-access/scripts/doctor-secrets.mjs # health
|
|
433
512
|
```
|
|
434
513
|
|
|
435
514
|
Run the validator first. It checks only the *shape* of the `secrets`, `remoteEnv`, and
|
|
@@ -438,6 +517,33 @@ would otherwise surface somewhere unhelpful: a container failing mid-setup, a sc
|
|
|
438
517
|
never fires, a dispatch naming a surface nobody provisioned. The health check then asks whether the
|
|
439
518
|
credentials actually resolve.
|
|
440
519
|
|
|
520
|
+
The preflight in the middle is the same check the SessionStart hook runs, and it is here for the
|
|
521
|
+
half the hook deliberately cannot do. The hook injects its verdict into an agent's context and
|
|
522
|
+
never blocks — killing a session over a credential it may not need would tax every session for a
|
|
523
|
+
minority need, and a control people route around enforces nothing. Doctor is where the same
|
|
524
|
+
verdict is allowed to be a non-zero exit.
|
|
525
|
+
|
|
526
|
+
It reports three outcomes, and the third carries the weight. `ok` and `missing` are self-evident.
|
|
527
|
+
**`unreachable`** means the provider itself could not be asked — no CLI, no bootstrap token, an API
|
|
528
|
+
that errored — and it is neither of its neighbours. Reported as `ok` it would be a vacuous green,
|
|
529
|
+
clean precisely because nothing was learned. Reported as `missing` it would blame the vault for a
|
|
530
|
+
fault in this machine's access and send someone to grant a credential that was never absent. Both
|
|
531
|
+
fail; they differ in what they tell the reader to fix.
|
|
532
|
+
|
|
533
|
+
The tooling preflight asks the same question about CLIs, and it is a *caller* of
|
|
534
|
+
`planToolchain` rather than a second implementation — that distinction is the point.
|
|
535
|
+
`verify-remote-env.mjs` previously ran its own toolchain loop which never consulted
|
|
536
|
+
`minVersion`, so a container verified clean against a `node` older than the manifest
|
|
537
|
+
demanded while the plan-side check rejected exactly that. One function now answers the
|
|
538
|
+
question everywhere.
|
|
539
|
+
|
|
540
|
+
It needs only two verdicts. A local binary cannot fail to be asked the way a vault can,
|
|
541
|
+
and the analogous trap — a tool present at an unparseable version — already fails closed
|
|
542
|
+
upstream, because an unknown version loses every `minVersion` comparison. What it does add
|
|
543
|
+
is a split credentials have no equivalent for: a missing tool with a pinned, checksummed
|
|
544
|
+
`install` entry is something Lisa can place itself, so it reports an action and exits zero,
|
|
545
|
+
while a tool nothing can provision blocks and exits non-zero.
|
|
546
|
+
|
|
441
547
|
It reports without ever printing a value, and compares two copies of the same credential by digest.
|
|
442
548
|
Map its findings into the doctor's own verdicts: `error` → `FAIL`, `warn` → `WARN`.
|
|
443
549
|
|