@codyswann/lisa 4.27.5 → 4.27.7
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-enforcement-fallback.sh +27 -4
- package/all/copy-overwrite/scripts/lisa-hooks/block-direct-issue-create.sh +507 -11
- package/dist/cli/doctor-enforcement-coverage.d.ts +14 -0
- package/dist/cli/doctor-enforcement-coverage.d.ts.map +1 -0
- package/dist/cli/doctor-enforcement-coverage.js +103 -0
- package/dist/cli/doctor-enforcement-coverage.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +9 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/core/enforcement-census-report.d.ts +19 -0
- package/dist/core/enforcement-census-report.d.ts.map +1 -0
- package/dist/core/enforcement-census-report.js +198 -0
- package/dist/core/enforcement-census-report.js.map +1 -0
- package/dist/core/enforcement-census.d.ts +177 -0
- package/dist/core/enforcement-census.d.ts.map +1 -0
- package/dist/core/enforcement-census.js +252 -0
- package/dist/core/enforcement-census.js.map +1 -0
- package/dist/core/enforcement-coverage.d.ts +168 -0
- package/dist/core/enforcement-coverage.d.ts.map +1 -0
- package/dist/core/enforcement-coverage.js +342 -0
- package/dist/core/enforcement-coverage.js.map +1 -0
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +8 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +21 -5
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/lisa-block-direct-issue-create.ts +180 -19
- package/package.json +4 -3
- 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-setup-automations/SKILL.md +23 -0
- package/plugins/lisa/hooks/block-direct-issue-create.sh +507 -11
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-agy/hooks/block-direct-issue-create.sh +507 -11
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +23 -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/hooks/block-direct-issue-create.sh +507 -11
- package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-direct-issue-create.sh +507 -11
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +23 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/block-direct-issue-create.sh +507 -11
- package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +23 -0
- package/scripts/lisa-enforcement-census.mjs +168 -0
- package/scripts/lisa-enforcement-fallback.sh +27 -4
|
@@ -17,13 +17,22 @@
|
|
|
17
17
|
* The port matches on the raw command text rather than tokenising it, which
|
|
18
18
|
* makes it naturally immune to the prefix and tokenisation bypass classes the
|
|
19
19
|
* shell guard had to be restructured to close — an unrecognised wrapper is just
|
|
20
|
-
* more text before the CLI name.
|
|
21
|
-
*
|
|
22
|
-
* guard
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* more text before the CLI name.
|
|
21
|
+
*
|
|
22
|
+
* It reaches past the command text in the two places the canonical guard does:
|
|
23
|
+
* into the contents of any file the command names — which is what closes
|
|
24
|
+
* `bash create.sh`, `node wrapper.mjs` and `curl --data-binary @payload.json`
|
|
25
|
+
* — and into the lifecycle-role declaration a state-based tracker has to use
|
|
26
|
+
* because no flag on `curl` can carry a workflow state.
|
|
27
|
+
*
|
|
28
|
+
* Two deliberate differences remain, both in the permissive direction so this
|
|
29
|
+
* port can never refuse something the canonical guard would allow:
|
|
25
30
|
* - remote execution (`ssh host '…'`) is not intercepted, matching the shell
|
|
26
|
-
* guard's documented limit
|
|
31
|
+
* guard's documented limit;
|
|
32
|
+
* - a declaration found inside a FILE answers for every creation in that
|
|
33
|
+
* file. Matching raw text cannot attribute a label to one create among
|
|
34
|
+
* several, and the alternative — accepting only whole-scope markers there
|
|
35
|
+
* — would refuse an honest single-create script that carries its label.
|
|
27
36
|
*
|
|
28
37
|
* NOTE: This file is a template Lisa copies verbatim into a host project's
|
|
29
38
|
* `.opencode/plugin/`. It is intentionally excluded from this repo's tsconfig
|
|
@@ -53,6 +62,37 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
53
62
|
const ISSUES_ENDPOINT = /repos\/([^/\s]+)\/([^/\s]+)\/issues\b/;
|
|
54
63
|
const DEFAULT_READY_ROLE = "status:ready";
|
|
55
64
|
const DEFAULT_UPSTREAM_REPO = "CodySwannGT/lisa";
|
|
65
|
+
/**
|
|
66
|
+
* The build-ready declaration for a tracker whose ready role is a workflow
|
|
67
|
+
* STATE rather than a label.
|
|
68
|
+
*
|
|
69
|
+
* GitHub's role is a label and labels are argv-native, so `--label` has
|
|
70
|
+
* always been writable there. JIRA's and Linear's are states living in the
|
|
71
|
+
* request payload, and the mandated client is `curl`, which has no flag that
|
|
72
|
+
* carries one — so the only declaration those trackers could satisfy was
|
|
73
|
+
* `[lisa-human-gate]`, a false statement about a build-ready item. A guard
|
|
74
|
+
* that leaves an honest operator no compliant command and one dishonest one
|
|
75
|
+
* fails in the harmful direction.
|
|
76
|
+
*
|
|
77
|
+
* The role is what the access layer already consumes: it refuses a
|
|
78
|
+
* caller-supplied state id, takes `lifecycle_role`, resolves it against the
|
|
79
|
+
* tracker's own catalog and fails closed. So the token decides the lane
|
|
80
|
+
* rather than decorating the command.
|
|
81
|
+
*
|
|
82
|
+
* The optional quote AFTER the key name is load-bearing: the refusal tells
|
|
83
|
+
* the operator to put the declaration in the request payload, and a JSON
|
|
84
|
+
* payload quotes its keys, so demanding `[:=]` immediately after a bare key
|
|
85
|
+
* name refuses the exact spelling it just asked for.
|
|
86
|
+
*/
|
|
87
|
+
const LIFECYCLE_ROLE_READY =
|
|
88
|
+
/(?:^|[^\w.-])(?:lifecycle_role|LIFECYCLE_ROLE)["']?\s*[:=]\s*["']?ready\b|(?:^|[^\w.-])--role[=\s]+["']?ready\b/;
|
|
89
|
+
/** A creation verb, and a tracker endpoint to send it to. */
|
|
90
|
+
const GRAPHQL_CREATE = /createIssue|issueCreate/;
|
|
91
|
+
const TRACKER_ENDPOINT =
|
|
92
|
+
/api\.linear\.app\/graphql|api\.github\.com|atlassian\.net\/rest\/api|repos\/[^/\s?#'"]+\/[^/\s?#'"]+\/issues/i;
|
|
93
|
+
/** Bounds on reading files a command names, so a hook stays a hook. */
|
|
94
|
+
const MAX_FILE_BYTES = 262_144;
|
|
95
|
+
const MAX_FILES = 8;
|
|
56
96
|
const CREATION_SIGNATURES: readonly {
|
|
57
97
|
readonly re: RegExp;
|
|
58
98
|
readonly name: string;
|
|
@@ -117,6 +157,8 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
117
157
|
readonly upstreamReadyRole: string;
|
|
118
158
|
/** Whether the caller's ready role is a GitHub label at all. */
|
|
119
159
|
readonly callerIsGithub: boolean;
|
|
160
|
+
/** Whether the caller's ready role is a workflow state, not a label. */
|
|
161
|
+
readonly stateRoleTracker: boolean;
|
|
120
162
|
}
|
|
121
163
|
|
|
122
164
|
/** Which roles satisfy a filing, and the target a refusal should name. */
|
|
@@ -179,11 +221,62 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
179
221
|
).toLowerCase(),
|
|
180
222
|
upstreamReadyRole: hardening.upstreamReadyRole ?? DEFAULT_READY_ROLE,
|
|
181
223
|
callerIsGithub: tracker === "github",
|
|
224
|
+
stateRoleTracker: tracker === "jira" || tracker === "linear",
|
|
182
225
|
};
|
|
183
226
|
};
|
|
184
227
|
|
|
185
228
|
const policy = await resolvePolicy();
|
|
186
229
|
|
|
230
|
+
/**
|
|
231
|
+
* The readable files a command names, with their contents.
|
|
232
|
+
*
|
|
233
|
+
* The guard used to match the command text and nothing else, so a creation
|
|
234
|
+
* one file away was invisible: `bash create.sh` is two words, and the
|
|
235
|
+
* conjunction it looks for — an endpoint and a creation verb together — never
|
|
236
|
+
* formed. Lisa's own guards push agents into exactly that shape, telling them
|
|
237
|
+
* to write payloads to a file and execute the file, so complying with the
|
|
238
|
+
* guidance produced the bypass.
|
|
239
|
+
*
|
|
240
|
+
* Which programs execute their operands is unbounded and every gap in such a
|
|
241
|
+
* list fails open, so the question is inverted the same way the wrapper
|
|
242
|
+
* question was: which tokens name a file? That is bounded by the command.
|
|
243
|
+
* @param props Helper inputs.
|
|
244
|
+
* @param props.text The command being inspected.
|
|
245
|
+
* @returns Each named file's path and contents, bounded in count and size.
|
|
246
|
+
*/
|
|
247
|
+
const namedFiles = async ({
|
|
248
|
+
text,
|
|
249
|
+
}: Readonly<{ text: string }>): Promise<
|
|
250
|
+
readonly { readonly path: string; readonly text: string }[]
|
|
251
|
+
> => {
|
|
252
|
+
const found: { path: string; text: string }[] = [];
|
|
253
|
+
const seen = new Set<string>();
|
|
254
|
+
const candidates = text
|
|
255
|
+
.split(/[\s'"]+/)
|
|
256
|
+
// `curl -d@payload.json` is one word, and the path is the half after the
|
|
257
|
+
// `@`. Both halves are offered rather than guessing which flag it was.
|
|
258
|
+
.flatMap(raw =>
|
|
259
|
+
raw.includes("@") ? [raw, raw.split("@").pop() ?? ""] : [raw]
|
|
260
|
+
);
|
|
261
|
+
for (const raw of candidates) {
|
|
262
|
+
if (found.length >= MAX_FILES) break;
|
|
263
|
+
const token = raw.replace(/^@/, "");
|
|
264
|
+
if (!token || token === "-" || seen.has(token)) continue;
|
|
265
|
+
seen.add(token);
|
|
266
|
+
try {
|
|
267
|
+
const handle = Bun.file(token);
|
|
268
|
+
// A file too large to inspect is skipped rather than half-read: a
|
|
269
|
+
// truncated scan reports a confident allow about text it never saw.
|
|
270
|
+
if (handle.size === 0 || handle.size > MAX_FILE_BYTES) continue;
|
|
271
|
+
if (!(await handle.exists())) continue;
|
|
272
|
+
found.push({ path: token, text: await handle.text() });
|
|
273
|
+
} catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return found;
|
|
278
|
+
};
|
|
279
|
+
|
|
187
280
|
/**
|
|
188
281
|
* A repository token reduced to a comparable `owner/name`.
|
|
189
282
|
*
|
|
@@ -274,10 +367,7 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
274
367
|
const inlineOverride = /LISA_ALLOW_DIRECT_ISSUE_CREATE=/.test(command);
|
|
275
368
|
if (process.env["LISA_ALLOW_DIRECT_ISSUE_CREATE"] && !inlineOverride)
|
|
276
369
|
return;
|
|
277
|
-
const
|
|
278
|
-
entry.re.test(command)
|
|
279
|
-
);
|
|
280
|
-
if (!signature) return;
|
|
370
|
+
const inline = CREATION_SIGNATURES.find(entry => entry.re.test(command));
|
|
281
371
|
// The build-ready role counts ONLY as the value of a label / state flag.
|
|
282
372
|
// A free-text scan of the command let a bug report's own title declare
|
|
283
373
|
// readiness — `gh issue create --title "status:ready is broken"` — which
|
|
@@ -300,17 +390,71 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
300
390
|
policy,
|
|
301
391
|
targetRepository({ declarable })
|
|
302
392
|
);
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
393
|
+
// The role escape is scoped: accepted only where no argv flag on the
|
|
394
|
+
// mandated client can carry a state, and never on a GitHub target where
|
|
395
|
+
// the label IS writable and a second weaker spelling would be a hole.
|
|
396
|
+
const stateRoleOk = policy.stateRoleTracker && named === undefined;
|
|
397
|
+
const declaresFor = (
|
|
398
|
+
text: string,
|
|
399
|
+
against: readonly string[],
|
|
400
|
+
roleOk: boolean
|
|
401
|
+
): boolean =>
|
|
402
|
+
[...text.matchAll(LABEL_FLAG)].some(match =>
|
|
403
|
+
(match[2] ?? "")
|
|
404
|
+
.split(",")
|
|
405
|
+
.map(part => part.trim())
|
|
406
|
+
.some(candidate => against.includes(candidate))
|
|
407
|
+
) ||
|
|
408
|
+
text.includes(HUMAN_GATE_MARKER) ||
|
|
409
|
+
(roleOk && LIFECYCLE_ROLE_READY.test(text));
|
|
410
|
+
const declares = (text: string): boolean =>
|
|
411
|
+
declaresFor(text, roles, stateRoleOk);
|
|
412
|
+
// A creation the command does not spell out itself, because it lives in
|
|
413
|
+
// a file the command runs or submits. The conjunction may straddle the
|
|
414
|
+
// two: `curl <endpoint> --data-binary @payload.json` keeps the endpoint
|
|
415
|
+
// in argv and the mutation in the file.
|
|
416
|
+
const fromFile = inline
|
|
417
|
+
? undefined
|
|
418
|
+
: (await namedFiles({ text: command })).flatMap(file => {
|
|
419
|
+
const signature =
|
|
420
|
+
CREATION_SIGNATURES.find(entry => entry.re.test(file.text))
|
|
421
|
+
?.name ??
|
|
422
|
+
(GRAPHQL_CREATE.test(file.text) &&
|
|
423
|
+
(TRACKER_ENDPOINT.test(file.text) ||
|
|
424
|
+
TRACKER_ENDPOINT.test(command))
|
|
425
|
+
? "a tracker creation"
|
|
426
|
+
: undefined);
|
|
427
|
+
if (signature === undefined) return [];
|
|
428
|
+
// WHOSE vocabulary answers is decided by where the create inside
|
|
429
|
+
// the FILE is addressed, not by the command that ran it. `bash
|
|
430
|
+
// create.sh` names no repository, so reading the target from the
|
|
431
|
+
// command let a script doing `gh issue create --repo <other>` be
|
|
432
|
+
// waved through by this project's own lifecycle role — a role the
|
|
433
|
+
// other repository's build queue never reads.
|
|
434
|
+
const fileTarget = targetRepository({ declarable: file.text });
|
|
435
|
+
const scoped = rolesFor(policy, fileTarget);
|
|
436
|
+
const fileRoleOk =
|
|
437
|
+
policy.stateRoleTracker && scoped.named === undefined;
|
|
438
|
+
// A label rather than a marker, and the looseness is forced by
|
|
439
|
+
// this port's stated invariant. It matches raw text rather than
|
|
440
|
+
// tokenising, so it cannot attribute a label to ONE create inside
|
|
441
|
+
// a file. Accepting only whole-scope markers would refuse an
|
|
442
|
+
// honest single-create script that carries its label — a false
|
|
443
|
+
// refusal on the honest path, the failure mode this guard exists
|
|
444
|
+
// to remove. The cost is a decoy: a labelled create alongside an
|
|
445
|
+
// undeclared one in the same file passes here. The canonical guard
|
|
446
|
+
// refuses that, and this port is documented as never being
|
|
447
|
+
// STRICTER than the canonical guard, only ever looser.
|
|
448
|
+
if (declaresFor(file.text, scoped.roles, fileRoleOk)) return [];
|
|
449
|
+
return [`${signature} inside ${file.path}`];
|
|
450
|
+
})[0];
|
|
451
|
+
const signatureName = inline?.name ?? fromFile;
|
|
452
|
+
if (signatureName === undefined) return;
|
|
453
|
+
if (declares(declarable)) return;
|
|
310
454
|
if (named !== undefined)
|
|
311
455
|
throw new Error(
|
|
312
456
|
[
|
|
313
|
-
`block-direct-issue-create: refusing ${
|
|
457
|
+
`block-direct-issue-create: refusing ${signatureName} — this filing declares no readiness.`,
|
|
314
458
|
"",
|
|
315
459
|
"WHY: a work item filed without the build-ready role is an incomplete",
|
|
316
460
|
"handoff. Build-intake scans the ready lane and nothing else, so nothing",
|
|
@@ -347,7 +491,7 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
347
491
|
);
|
|
348
492
|
throw new Error(
|
|
349
493
|
[
|
|
350
|
-
`block-direct-issue-create: refusing ${
|
|
494
|
+
`block-direct-issue-create: refusing ${signatureName} — this filing declares no readiness.`,
|
|
351
495
|
"",
|
|
352
496
|
"WHY: a work item filed without the build-ready role is an incomplete",
|
|
353
497
|
"handoff. Build-intake scans the ready lane and nothing else, so nothing",
|
|
@@ -369,6 +513,23 @@ const LisaBlockDirectIssueCreate = async () => {
|
|
|
369
513
|
"If you must run the CLI directly, the command has to carry one of the",
|
|
370
514
|
`two declarations itself: the configured build-ready role "${policy.readyRole}",`,
|
|
371
515
|
`or a ${HUMAN_GATE_MARKER} marker in the body it submits.`,
|
|
516
|
+
...(policy.stateRoleTracker
|
|
517
|
+
? [
|
|
518
|
+
"",
|
|
519
|
+
`Your role "${policy.readyRole}" is a workflow STATE, not a label, and the`,
|
|
520
|
+
"mandated client is curl, which has no flag that carries a state. So",
|
|
521
|
+
"declare the lifecycle role the access layer resolves the state from:",
|
|
522
|
+
"",
|
|
523
|
+
" LIFECYCLE_ROLE=ready curl -sS -X POST <the tracker endpoint> …",
|
|
524
|
+
"",
|
|
525
|
+
'or lifecycle_role:"ready" in the request payload, or a --state /',
|
|
526
|
+
"--status flag where the CLI has one.",
|
|
527
|
+
]
|
|
528
|
+
: []),
|
|
529
|
+
"",
|
|
530
|
+
"WHERE THE DECLARATION IS READ FROM: the command, and the contents of any",
|
|
531
|
+
"file it runs or submits. Moving the create into a script no longer moves",
|
|
532
|
+
"it out of sight, so the declaration can live wherever the create does.",
|
|
372
533
|
"",
|
|
373
534
|
"OPERATOR ESCAPE: a human can export LISA_ALLOW_DIRECT_ISSUE_CREATE=1 in",
|
|
374
535
|
"the environment before starting the session. Setting it inline on this",
|
package/package.json
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"cleanup:github-branches": "bash scripts/cleanup-github-branches.sh",
|
|
35
35
|
"github:status": "bash scripts/github-status-check.sh",
|
|
36
36
|
"setup:deploy-key": "bash scripts/setup-deploy-key.sh",
|
|
37
|
+
"lisa:enforcement-census": "bun run build:dist:in-place && node scripts/lisa-enforcement-census.mjs",
|
|
37
38
|
"lisa:update:local": "bash scripts/lisa-update-local.sh",
|
|
38
39
|
"lisa:commit-and-pr:local": "bash scripts/lisa-commit-and-pr-local.sh",
|
|
39
40
|
"prepublishOnly": "$npm_execpath run build",
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
"zod-validation-error": "^4.0.0"
|
|
162
163
|
},
|
|
163
164
|
"name": "@codyswann/lisa",
|
|
164
|
-
"version": "4.27.
|
|
165
|
+
"version": "4.27.7",
|
|
165
166
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
166
167
|
"main": "dist/index.js",
|
|
167
168
|
"exports": {
|
|
@@ -305,6 +306,6 @@
|
|
|
305
306
|
"test": "tests"
|
|
306
307
|
},
|
|
307
308
|
"types": "./dist/index.d.ts",
|
|
308
|
-
"lisaReleaseCommit": "
|
|
309
|
-
"gitHead": "
|
|
309
|
+
"lisaReleaseCommit": "96e367f6712d61b7951346f042d32dc066ccbfc7",
|
|
310
|
+
"gitHead": "96e367f6712d61b7951346f042d32dc066ccbfc7"
|
|
310
311
|
}
|
|
@@ -207,6 +207,29 @@ project: the per-check marker dedupe converges under a single scheduled runner,
|
|
|
207
207
|
and two concurrent runs can both observe an empty open-ticket set and both
|
|
208
208
|
file. Tear-down removes it with the rest of the `lisa-auto-<project>-*` set.
|
|
209
209
|
|
|
210
|
+
**Optional automation — the fleet enforcement census.** Register
|
|
211
|
+
`lisa-auto-<project>-enforcement-census` running `/lisa:enforcement-census`
|
|
212
|
+
once a **day** only when BOTH are true of the repository being set up: it
|
|
213
|
+
maintains a fleet roster (`.lisa.workspaces.json`, the machine-local list of
|
|
214
|
+
host checkouts), and it provides the `/lisa:enforcement-census` command. Both
|
|
215
|
+
hold in the Lisa monorepo and in no ordinary host project, which is the point —
|
|
216
|
+
the census measures OTHER checkouts on the same machine, so only the workstation
|
|
217
|
+
holding the roster has a fleet to measure. Where either is absent, register
|
|
218
|
+
nothing — a host project has no fleet, and scheduling a command it does not
|
|
219
|
+
have would be a loop that fails every night — and report the census as SKIPPED
|
|
220
|
+
naming which prerequisite was missing, per the report contract. Registering
|
|
221
|
+
nothing is correct; omitting it from the report is not, because a silently
|
|
222
|
+
absent loop is indistinguishable from one that was never asked for.
|
|
223
|
+
|
|
224
|
+
It **reports and never gates**: no finding about the fleet changes an exit
|
|
225
|
+
status, and no run files a ticket. Enforcement resolves from the checkout an
|
|
226
|
+
agent is working in, so a checkout resolving no guard is protected by nothing
|
|
227
|
+
while looking identical, from inside a session, to one protected by an old
|
|
228
|
+
policy — and nothing was re-measuring that (CodySwannGT/lisa#3490). The
|
|
229
|
+
per-checkout half of the same finding is carried by `lisa doctor`, which every
|
|
230
|
+
host does get. Tear-down removes it with the rest of the `lisa-auto-<project>-*`
|
|
231
|
+
set.
|
|
232
|
+
|
|
210
233
|
**Exploratory PRD pressure gate.** `auto-start-prds=true` means "create PRDs in the ready PRD
|
|
211
234
|
lifecycle when the PRD queue has capacity," not "always create a new ready PRD." The
|
|
212
235
|
`exploratory-prds` automation uses the same PRD source queue and pressure roles reported by
|