@codyswann/lisa 3.33.1 → 3.33.3
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/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +3 -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 +3 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/check-e2e-coverage.mjs +286 -20
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- 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-cursor/.claude-plugin/plugin.json +1 -1
- 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
|
@@ -47,6 +47,10 @@ const FLOW_FILE_PATTERN = /\.(?:yaml|yml)$/;
|
|
|
47
47
|
// `e2e-route: /path` inside any comment declares a route as covered even when
|
|
48
48
|
// the spec reaches it by tapping/navigating rather than by URL or deep link.
|
|
49
49
|
const ROUTE_ANNOTATION_PATTERN = /e2e-route:\s*(\/[^\s'"`,)]*)/g;
|
|
50
|
+
// `e2e-route-exempt: /path` is the counterpart to `e2e-route:` — it declares a
|
|
51
|
+
// route deliberately uncovered, removing it from the denominator rather than
|
|
52
|
+
// leaving a team to reach for a mechanism this gate cannot see.
|
|
53
|
+
const EXEMPT_ANNOTATION_PATTERN = /e2e-route-exempt:\s*(\/[^\s'"`,)]*)/g;
|
|
50
54
|
const GOTO_PATTERN = /\.goto\(\s*(["'`])([^"'`]+)\1/g;
|
|
51
55
|
const OPEN_LINK_PATTERN = /openLink:\s*["']?([^\s"']+)/g;
|
|
52
56
|
|
|
@@ -218,6 +222,190 @@ export function mergeThresholds(defaults, overrides) {
|
|
|
218
222
|
};
|
|
219
223
|
}
|
|
220
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Route exemptions a project has deliberately declared.
|
|
227
|
+
*
|
|
228
|
+
* The script could already ADD a coverage claim (`e2e-route:`) but had no way
|
|
229
|
+
* to remove one. A team exempting a route therefore reached for a mechanism the
|
|
230
|
+
* gate cannot see — `testIgnore`, a disabled project, a Maestro tag — and the
|
|
231
|
+
* number silently overstated. Detecting those mechanisms is only half a fix: if
|
|
232
|
+
* there is no supported way to say "this route is deliberately uncovered", the
|
|
233
|
+
* workaround stays the only option.
|
|
234
|
+
*
|
|
235
|
+
* Exemptions leave the denominator, and are always PRINTED. A silent exemption
|
|
236
|
+
* would be the same defect wearing a sanctioned label.
|
|
237
|
+
* @param {object} input - Exemption input
|
|
238
|
+
* @param {string[]} input.routes - Enumerated app routes
|
|
239
|
+
* @param {string[]} [input.declared] - Routes exempted in e2e.thresholds.json
|
|
240
|
+
* @param {string[]} [input.annotated] - Routes exempted by `e2e-route-exempt:`
|
|
241
|
+
* @returns {{kept: string[], exempt: string[], unknown: string[]}} Partition
|
|
242
|
+
*/
|
|
243
|
+
export function applyExemptions({ routes, declared, annotated }) {
|
|
244
|
+
const asked = [...new Set([...(declared ?? []), ...(annotated ?? [])])];
|
|
245
|
+
const exempt = asked.filter(route => routes.includes(route));
|
|
246
|
+
// An exemption naming a route that does not exist is stale — a renamed or
|
|
247
|
+
// deleted screen whose waiver outlived it. Reported, never silently dropped,
|
|
248
|
+
// because a stale exemption is how a real gap gets permanently excused.
|
|
249
|
+
const unknown = asked.filter(route => !routes.includes(route));
|
|
250
|
+
return {
|
|
251
|
+
kept: routes.filter(route => !exempt.includes(route)),
|
|
252
|
+
exempt,
|
|
253
|
+
unknown,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Routes exempted by an `e2e-route-exempt: /path` comment in a spec or flow.
|
|
259
|
+
*
|
|
260
|
+
* The comment form exists so an exemption lives next to the thing that would
|
|
261
|
+
* otherwise cover the route, where a reviewer of that file sees it. The
|
|
262
|
+
* thresholds-file form exists for a route no file mentions at all. Both are
|
|
263
|
+
* read; neither is silent.
|
|
264
|
+
* @param {string} root - Project root
|
|
265
|
+
* @returns {string[]} Exempted routes
|
|
266
|
+
*/
|
|
267
|
+
export function collectExemptAnnotations(root) {
|
|
268
|
+
const scan = (directories, filePattern) =>
|
|
269
|
+
directories.flatMap(directory => {
|
|
270
|
+
const absolute = path.join(root, directory);
|
|
271
|
+
return listFiles(absolute)
|
|
272
|
+
.filter(file => filePattern.test(file))
|
|
273
|
+
.flatMap(file => [
|
|
274
|
+
...fs
|
|
275
|
+
.readFileSync(path.join(absolute, file), "utf8")
|
|
276
|
+
.matchAll(EXEMPT_ANNOTATION_PATTERN),
|
|
277
|
+
])
|
|
278
|
+
.map(match => match[1]);
|
|
279
|
+
});
|
|
280
|
+
return [
|
|
281
|
+
...scan(["e2e", "tests/e2e"], SPEC_FILE_PATTERN),
|
|
282
|
+
...scan([".maestro"], FLOW_FILE_PATTERN),
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Glob-ish patterns Playwright is configured to ignore.
|
|
288
|
+
*
|
|
289
|
+
* Read textually rather than by importing the config: it is TypeScript, may
|
|
290
|
+
* import project code, and executing it to compute a coverage number would be a
|
|
291
|
+
* far larger blast radius than reading it. A pattern this misses costs a false
|
|
292
|
+
* INCLUSION — the old behaviour — so the failure mode of imprecision here is
|
|
293
|
+
* the status quo rather than a new one.
|
|
294
|
+
* @param {string} root - Project root
|
|
295
|
+
* @returns {string[]} Ignore patterns, empty when no config is found
|
|
296
|
+
*/
|
|
297
|
+
export function readPlaywrightIgnores(root) {
|
|
298
|
+
const config = [
|
|
299
|
+
"playwright.config.ts",
|
|
300
|
+
"playwright.config.js",
|
|
301
|
+
"playwright.config.mjs",
|
|
302
|
+
]
|
|
303
|
+
.map(name => path.join(root, name))
|
|
304
|
+
.find(candidate => fs.existsSync(candidate));
|
|
305
|
+
if (!config) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
const source = fs.readFileSync(config, "utf8");
|
|
309
|
+
const block = source.match(
|
|
310
|
+
/testIgnore\s*:\s*(\[[^\]]*\]|["'`][^"'`]*["'`])/u
|
|
311
|
+
);
|
|
312
|
+
if (!block) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
return [...block[1].matchAll(/["'`]([^"'`]+)["'`]/gu)].map(match => match[1]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Whether a spec path is excluded by one of Playwright's ignore patterns.
|
|
320
|
+
* @param {string} relativePath - Spec path relative to its scan directory
|
|
321
|
+
* @param {string[]} patterns - Ignore patterns
|
|
322
|
+
* @returns {boolean} True when the spec will not run
|
|
323
|
+
*/
|
|
324
|
+
export function isIgnoredSpec(relativePath, patterns) {
|
|
325
|
+
return patterns.some(pattern => globToRegExp(pattern).test(relativePath));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Translate one glob to an anchored expression.
|
|
330
|
+
*
|
|
331
|
+
* `**` spans directories, `*` does not, and `**\/` must also match nothing at
|
|
332
|
+
* all — `**\/x.spec.ts` names a file at the scan root as well as a nested one.
|
|
333
|
+
* Requiring the slash silently un-ignored every top-level spec, which is the
|
|
334
|
+
* bug this whole function exists to close, so it is the case the tests pin.
|
|
335
|
+
* @param {string} pattern - A glob from Playwright's testIgnore
|
|
336
|
+
* @returns {RegExp} Anchored matcher
|
|
337
|
+
*/
|
|
338
|
+
function globToRegExp(pattern) {
|
|
339
|
+
const DOUBLE_SLASH = "\u0000";
|
|
340
|
+
const DOUBLE = "\u0001";
|
|
341
|
+
const SINGLE = "\u0002";
|
|
342
|
+
const marked = pattern
|
|
343
|
+
.replaceAll("**/", DOUBLE_SLASH)
|
|
344
|
+
.replaceAll("**", DOUBLE)
|
|
345
|
+
.replaceAll("*", SINGLE);
|
|
346
|
+
const escaped = marked.replace(/[.+^${}()|[\]\\?]/gu, "\\$&");
|
|
347
|
+
const expression = escaped
|
|
348
|
+
.replaceAll(DOUBLE_SLASH, "(?:.*/)?")
|
|
349
|
+
.replaceAll(DOUBLE, ".*")
|
|
350
|
+
.replaceAll(SINGLE, "[^/]*");
|
|
351
|
+
return new RegExp(`(^|/)${expression}$`, "u");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Whether a Maestro flow carries a tag the running suite excludes.
|
|
356
|
+
*
|
|
357
|
+
* The exclusion is supplied by the caller (`MAESTRO_EXCLUDE_TAGS`) because the
|
|
358
|
+
* suite decides it at invocation time — the flow file cannot know, and neither
|
|
359
|
+
* can this script by reading the repository.
|
|
360
|
+
* @param {string} source - Flow file contents
|
|
361
|
+
* @param {string[]} excluded - Tags the suite excludes
|
|
362
|
+
* @returns {boolean} True when the flow will not run
|
|
363
|
+
*/
|
|
364
|
+
export function isExcludedFlow(source, excluded) {
|
|
365
|
+
if (excluded.length === 0) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
return flowTags(source).some(tag => excluded.includes(tag));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The tags a Maestro flow declares, and nothing else.
|
|
373
|
+
*
|
|
374
|
+
* Read line-wise because YAML block structure is indentation, and the previous
|
|
375
|
+
* regex form could not express "until this block ends": it allowed zero
|
|
376
|
+
* indentation before `-`, so it ran past the `---` document separator and
|
|
377
|
+
* lifted `openLink` and `tapOn` out of the flow's COMMANDS. A false tag causes
|
|
378
|
+
* a false EXCLUSION, dropping a flow that really runs — the opposite error from
|
|
379
|
+
* the one this gate exists to fix, and just as wrong.
|
|
380
|
+
* @param {string} source - Flow file contents
|
|
381
|
+
* @returns {string[]} Declared tags
|
|
382
|
+
*/
|
|
383
|
+
function flowTags(source) {
|
|
384
|
+
const lines = source.split("\n");
|
|
385
|
+
const start = lines.findIndex(line => /^tags:/u.test(line));
|
|
386
|
+
if (start === -1) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
const inline = lines[start].match(/^tags:\s*\[([^\]]*)\]/u);
|
|
390
|
+
if (inline) {
|
|
391
|
+
return inline[1]
|
|
392
|
+
.split(",")
|
|
393
|
+
.map(tag => tag.trim().replace(/^["']|["']$/gu, ""))
|
|
394
|
+
.filter(Boolean);
|
|
395
|
+
}
|
|
396
|
+
const tags = [];
|
|
397
|
+
for (const line of lines.slice(start + 1)) {
|
|
398
|
+
// The block ends at the document separator, at a non-indented line, or at
|
|
399
|
+
// anything that is not a list item. Each of those is what the regex missed.
|
|
400
|
+
const item = line.match(/^\s+-\s*(.+?)\s*$/u);
|
|
401
|
+
if (!item || line.startsWith("---")) {
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
tags.push(item[1].replace(/^["']|["']$/gu, ""));
|
|
405
|
+
}
|
|
406
|
+
return tags;
|
|
407
|
+
}
|
|
408
|
+
|
|
221
409
|
/**
|
|
222
410
|
* Pure decision: does each runner's route coverage meet its threshold?
|
|
223
411
|
* @param {object} input - Evaluation input
|
|
@@ -304,15 +492,32 @@ function listFiles(directory) {
|
|
|
304
492
|
* @param {(source: string) => string[]} input.extract - Path extractor
|
|
305
493
|
* @returns {string[]} Every visited path found
|
|
306
494
|
*/
|
|
307
|
-
function collectVisitedPaths({
|
|
308
|
-
|
|
495
|
+
function collectVisitedPaths({
|
|
496
|
+
root,
|
|
497
|
+
directories,
|
|
498
|
+
filePattern,
|
|
499
|
+
extract,
|
|
500
|
+
skip,
|
|
501
|
+
}) {
|
|
502
|
+
const read = [];
|
|
503
|
+
const skipped = [];
|
|
504
|
+
const visited = directories.flatMap(directory => {
|
|
309
505
|
const absolute = path.join(root, directory);
|
|
310
506
|
return listFiles(absolute)
|
|
311
507
|
.filter(file => filePattern.test(file))
|
|
312
|
-
.flatMap(file =>
|
|
313
|
-
|
|
314
|
-
|
|
508
|
+
.flatMap(file => {
|
|
509
|
+
const source = fs.readFileSync(path.join(absolute, file), "utf8");
|
|
510
|
+
// A file that will not run contributes no coverage, however many
|
|
511
|
+
// routes it mentions. Presence on disk was the whole bug.
|
|
512
|
+
if (skip?.({ file, source })) {
|
|
513
|
+
skipped.push(`${directory}/${file}`);
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
read.push(`${directory}/${file}`);
|
|
517
|
+
return extract(source);
|
|
518
|
+
});
|
|
315
519
|
});
|
|
520
|
+
return { visited, read, skipped };
|
|
316
521
|
}
|
|
317
522
|
|
|
318
523
|
/**
|
|
@@ -330,9 +535,16 @@ function main() {
|
|
|
330
535
|
);
|
|
331
536
|
return;
|
|
332
537
|
}
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
335
|
-
|
|
538
|
+
const allRoutes = enumerateRoutes(listFiles(appDir));
|
|
539
|
+
if (allRoutes.length === 0) {
|
|
540
|
+
// An app directory that enumerates ZERO navigable routes is a discovery
|
|
541
|
+
// bug, not an empty app — a renamed tree or a changed file pattern. The
|
|
542
|
+
// old behaviour reported "nothing to gate" and exited 0, so the gate
|
|
543
|
+
// passed loudest exactly when it had measured nothing.
|
|
544
|
+
console.error(
|
|
545
|
+
`[e2e-coverage] FAIL: found an app directory (${path.relative(root, appDir) || "."}) but enumerated no navigable routes. That is a discovery failure, not an empty app — check the route file patterns before trusting any coverage number.`
|
|
546
|
+
);
|
|
547
|
+
process.exit(1);
|
|
336
548
|
return;
|
|
337
549
|
}
|
|
338
550
|
|
|
@@ -347,20 +559,74 @@ function main() {
|
|
|
347
559
|
}
|
|
348
560
|
const thresholds = mergeThresholds(defaultThresholds, overrides);
|
|
349
561
|
|
|
562
|
+
const ignores = readPlaywrightIgnores(root);
|
|
563
|
+
const excludedTags = (process.env.MAESTRO_EXCLUDE_TAGS ?? "")
|
|
564
|
+
.split(",")
|
|
565
|
+
.map(tag => tag.trim())
|
|
566
|
+
.filter(Boolean);
|
|
567
|
+
|
|
568
|
+
const playwright = collectVisitedPaths({
|
|
569
|
+
root,
|
|
570
|
+
directories: ["e2e", "tests/e2e"],
|
|
571
|
+
filePattern: SPEC_FILE_PATTERN,
|
|
572
|
+
extract: extractPlaywrightPaths,
|
|
573
|
+
skip: ({ file }) => isIgnoredSpec(file, ignores),
|
|
574
|
+
});
|
|
575
|
+
const maestro = collectVisitedPaths({
|
|
576
|
+
root,
|
|
577
|
+
directories: [".maestro"],
|
|
578
|
+
filePattern: FLOW_FILE_PATTERN,
|
|
579
|
+
extract: extractMaestroPaths,
|
|
580
|
+
skip: ({ source }) => isExcludedFlow(source, excludedTags),
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const {
|
|
584
|
+
kept: routes,
|
|
585
|
+
exempt,
|
|
586
|
+
unknown,
|
|
587
|
+
} = applyExemptions({
|
|
588
|
+
routes: allRoutes,
|
|
589
|
+
declared: overrides?.exempt,
|
|
590
|
+
annotated: collectExemptAnnotations(root),
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
for (const [runner, collected] of [
|
|
594
|
+
["playwright", playwright],
|
|
595
|
+
["maestro", maestro],
|
|
596
|
+
]) {
|
|
597
|
+
if (collected.skipped.length > 0) {
|
|
598
|
+
console.log(
|
|
599
|
+
`[e2e-coverage] ${runner}: ${collected.skipped.length} file(s) excluded from the run and NOT counted as coverage:\n` +
|
|
600
|
+
collected.skipped.map(file => ` - ${file}`).join("\n")
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (exempt.length > 0) {
|
|
605
|
+
console.log(
|
|
606
|
+
`[e2e-coverage] ${exempt.length} route(s) deliberately exempt (removed from the denominator):\n` +
|
|
607
|
+
exempt.map(route => ` - ${route}`).join("\n")
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
if (unknown.length > 0) {
|
|
611
|
+
console.error(
|
|
612
|
+
`[e2e-coverage] FAIL: ${unknown.length} exemption(s) name a route that does not exist. A waiver that outlived its screen permanently excuses whatever replaces it:\n` +
|
|
613
|
+
unknown.map(route => ` - ${route}`).join("\n")
|
|
614
|
+
);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (routes.length === 0) {
|
|
619
|
+
console.error(
|
|
620
|
+
"[e2e-coverage] FAIL: every route is exempt, so this gate measures nothing. Remove exemptions or disable the gate explicitly with a threshold of 0."
|
|
621
|
+
);
|
|
622
|
+
process.exit(1);
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
350
626
|
const result = evaluateE2eCoverage({
|
|
351
627
|
routes,
|
|
352
|
-
playwrightVisited:
|
|
353
|
-
|
|
354
|
-
directories: ["e2e", "tests/e2e"],
|
|
355
|
-
filePattern: SPEC_FILE_PATTERN,
|
|
356
|
-
extract: extractPlaywrightPaths,
|
|
357
|
-
}),
|
|
358
|
-
maestroVisited: collectVisitedPaths({
|
|
359
|
-
root,
|
|
360
|
-
directories: [".maestro"],
|
|
361
|
-
filePattern: FLOW_FILE_PATTERN,
|
|
362
|
-
extract: extractMaestroPaths,
|
|
363
|
-
}),
|
|
628
|
+
playwrightVisited: playwright.visited,
|
|
629
|
+
maestroVisited: maestro.visited,
|
|
364
630
|
thresholds,
|
|
365
631
|
});
|
|
366
632
|
|
package/package.json
CHANGED
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"ws": ">=8.21.0"
|
|
132
132
|
},
|
|
133
133
|
"name": "@codyswann/lisa",
|
|
134
|
-
"version": "3.33.
|
|
134
|
+
"version": "3.33.3",
|
|
135
135
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
136
136
|
"main": "dist/index.js",
|
|
137
137
|
"exports": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.3",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.3",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.3",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.3",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "3.33.
|
|
3
|
+
"version": "3.33.3",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|