@codyswann/lisa 3.47.10 → 3.48.1

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.
Files changed (110) hide show
  1. package/all/copy-overwrite/scripts/lisa-work-item.mjs +19 -6
  2. package/dist/cli/doctor-render.d.ts +22 -0
  3. package/dist/cli/doctor-render.d.ts.map +1 -0
  4. package/dist/cli/doctor-render.js +27 -0
  5. package/dist/cli/doctor-render.js.map +1 -0
  6. package/dist/cli/doctor.d.ts +15 -0
  7. package/dist/cli/doctor.d.ts.map +1 -1
  8. package/dist/cli/doctor.js +2 -9
  9. package/dist/cli/doctor.js.map +1 -1
  10. package/dist/cli/gate-report-cells.d.ts +101 -0
  11. package/dist/cli/gate-report-cells.d.ts.map +1 -0
  12. package/dist/cli/gate-report-cells.js +269 -0
  13. package/dist/cli/gate-report-cells.js.map +1 -0
  14. package/dist/cli/gate-report-config.d.ts +28 -0
  15. package/dist/cli/gate-report-config.d.ts.map +1 -0
  16. package/dist/cli/gate-report-config.js +33 -0
  17. package/dist/cli/gate-report-config.js.map +1 -0
  18. package/dist/cli/gate-report-executors.d.ts +62 -0
  19. package/dist/cli/gate-report-executors.d.ts.map +1 -0
  20. package/dist/cli/gate-report-executors.js +218 -0
  21. package/dist/cli/gate-report-executors.js.map +1 -0
  22. package/dist/cli/gate-report-preview.d.ts +27 -0
  23. package/dist/cli/gate-report-preview.d.ts.map +1 -0
  24. package/dist/cli/gate-report-preview.js +286 -0
  25. package/dist/cli/gate-report-preview.js.map +1 -0
  26. package/dist/cli/gate-report-registry.d.ts +68 -0
  27. package/dist/cli/gate-report-registry.d.ts.map +1 -0
  28. package/dist/cli/gate-report-registry.js +52 -0
  29. package/dist/cli/gate-report-registry.js.map +1 -0
  30. package/dist/cli/gate-report-ruleset.d.ts +60 -0
  31. package/dist/cli/gate-report-ruleset.d.ts.map +1 -0
  32. package/dist/cli/gate-report-ruleset.js +132 -0
  33. package/dist/cli/gate-report-ruleset.js.map +1 -0
  34. package/dist/cli/gate-report-skip-jobs.d.ts +34 -0
  35. package/dist/cli/gate-report-skip-jobs.d.ts.map +1 -0
  36. package/dist/cli/gate-report-skip-jobs.js +42 -0
  37. package/dist/cli/gate-report-skip-jobs.js.map +1 -0
  38. package/dist/cli/gate-report-summary.d.ts +18 -0
  39. package/dist/cli/gate-report-summary.d.ts.map +1 -0
  40. package/dist/cli/gate-report-summary.js +80 -0
  41. package/dist/cli/gate-report-summary.js.map +1 -0
  42. package/dist/cli/gate-report-types.d.ts +206 -0
  43. package/dist/cli/gate-report-types.d.ts.map +1 -0
  44. package/dist/cli/gate-report-types.js +19 -0
  45. package/dist/cli/gate-report-types.js.map +1 -0
  46. package/dist/cli/gate-report.d.ts +35 -0
  47. package/dist/cli/gate-report.d.ts.map +1 -0
  48. package/dist/cli/gate-report.js +317 -0
  49. package/dist/cli/gate-report.js.map +1 -0
  50. package/dist/cli/ui-github-repo-gh.d.ts +14 -0
  51. package/dist/cli/ui-github-repo-gh.d.ts.map +1 -1
  52. package/dist/cli/ui-github-repo-gh.js +6 -1
  53. package/dist/cli/ui-github-repo-gh.js.map +1 -1
  54. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  55. package/dist/core/lisa-owned-hash-ledger.js +1 -0
  56. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  57. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  58. package/dist/core/upstream-evidence-manifest.js +19 -1
  59. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  60. package/package.json +1 -1
  61. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-agy/plugin.json +1 -1
  64. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  67. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  70. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-expo-agy/plugin.json +1 -1
  74. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  78. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  79. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  81. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  84. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  88. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  89. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  93. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  94. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  98. package/plugins/lisa-rails-agy/plugin.json +1 -1
  99. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  103. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  104. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  108. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  109. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Read `.lisa.config.json`, degrading a refusal into a stated problem.
3
+ * @param registry - The shipped registry
4
+ * @param projectRoot - Project root
5
+ * @returns The parsed config
6
+ */
7
+ export function readConfig(registry, projectRoot) {
8
+ try {
9
+ const { runner, gates } = registry.readGates(projectRoot);
10
+ return {
11
+ gates,
12
+ runner,
13
+ runnerFinding: { state: "verified", value: runner },
14
+ runnerSource: runner === registry.DEFAULT_RUNNER ? "default" : "declared",
15
+ problems: registry.validateGates(gates),
16
+ };
17
+ }
18
+ catch (error) {
19
+ const message = error instanceof Error ? error.message : String(error);
20
+ return {
21
+ gates: {},
22
+ runner: registry.DEFAULT_RUNNER,
23
+ runnerFinding: {
24
+ state: "unknown",
25
+ reason: "config-unreadable",
26
+ message,
27
+ },
28
+ runnerSource: "unknown",
29
+ problems: [message],
30
+ };
31
+ }
32
+ }
33
+ //# sourceMappingURL=gate-report-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-config.js","sourceRoot":"","sources":["../../src/cli/gate-report-config.ts"],"names":[],"mappings":"AAsBA;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,QAA4B,EAC5B,WAAmB;IAEnB,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC1D,OAAO;YACL,KAAK;YACL,MAAM;YACN,aAAa,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE;YACnD,YAAY,EAAE,MAAM,KAAK,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU;YACzE,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC;SACxC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO;YACL,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,QAAQ,CAAC,cAAc;YAC/B,aAAa,EAAE;gBACb,KAAK,EAAE,SAAS;gBAChB,MAAM,EAAE,mBAAmB;gBAC3B,OAAO;aACR;YACD,YAAY,EAAE,SAAS;YACvB,QAAQ,EAAE,CAAC,OAAO,CAAC;SACpB,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { ExecutorEvidence } from "./gate-report-types.js";
2
+ /** One project hook file, read and classified. */
3
+ export interface HookFile {
4
+ /** Project-relative path, e.g. `.husky/pre-push`. */
5
+ readonly file: string;
6
+ /** Its contents, with comment lines blanked. */
7
+ readonly body: string;
8
+ /** The moments this hook fires at, sorted. */
9
+ readonly moments: readonly string[];
10
+ /** Moments it hands to the gate runner, sorted. Empty when it runs none. */
11
+ readonly gateRunnerMoments: readonly string[];
12
+ /** Gates it carries a `lisa_gate_covers`-guarded built-in step for. */
13
+ readonly builtinGates: readonly string[];
14
+ }
15
+ /** Everything the project's hooks were found to do. */
16
+ export interface HookEvidence {
17
+ /** Hook files, sorted by name, so the report is stable across machines. */
18
+ readonly files: readonly HookFile[];
19
+ }
20
+ /** Inputs for one (gate, moment) executor lookup. */
21
+ export interface ExecutorQuery {
22
+ /** The moment being reported. */
23
+ readonly moment: string;
24
+ /** The gate being reported. */
25
+ readonly gateId: string;
26
+ /** The task this gate resolves to, or null. */
27
+ readonly task: string | null;
28
+ /** Whether the settings file puts this pair into service. */
29
+ readonly declared: boolean;
30
+ }
31
+ /**
32
+ * Classify one hook file.
33
+ * @param file - Project-relative path
34
+ * @param raw - Raw file contents
35
+ * @returns The classified hook
36
+ */
37
+ export declare function classifyHook(file: string, raw: string): HookFile;
38
+ /**
39
+ * Read and classify every hook in `.husky`.
40
+ * @param projectRoot - Project root
41
+ * @returns Hook evidence, empty when the directory is absent
42
+ */
43
+ export declare function collectHookEvidence(projectRoot: string): Promise<HookEvidence>;
44
+ /**
45
+ * Whether a hook body invokes one task literally, outside the gate runner.
46
+ *
47
+ * Bounded on both sides on purpose. `test:cov` must not match `test:cov:unit`:
48
+ * they are different scripts, and reporting the wrong one as proof would
49
+ * manufacture exactly the green this report exists to withhold.
50
+ * @param body - Comment-stripped hook body
51
+ * @param task - The task to look for
52
+ * @returns True when the hook runs that exact task
53
+ */
54
+ export declare function hookInvokesTask(body: string, task: string): boolean;
55
+ /**
56
+ * Every executor proved for one gate at one moment.
57
+ * @param evidence - Hook evidence for the project
58
+ * @param query - The pair being reported
59
+ * @returns Executors, sorted by file then kind
60
+ */
61
+ export declare function executorsFor(evidence: HookEvidence, query: ExecutorQuery): ExecutorEvidence[];
62
+ //# sourceMappingURL=gate-report-executors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-executors.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-executors.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AA4C/D,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;CACrC;AAED,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AA2CD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAgBhE;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CAoBvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnE;AAqED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,aAAa,GACnB,gBAAgB,EAAE,CAMpB"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * What, in this project, would actually run a gate's task.
3
+ *
4
+ * Three kinds, and the difference between them is the report's central
5
+ * question. A `gate-runner` site reads the declaration — the hook shells
6
+ * `lisa-run-gates.mjs --moment=<moment>`, so changing the settings file changes
7
+ * what runs. A `hook-builtin` site does not: the shipped hooks carry a
8
+ * fallback step for a named gate, guarded by `lisa_gate_covers <gate>`, which
9
+ * stands down only when the declaration covered it and runs unconditionally
10
+ * otherwise. A `hook-literal` site is the same story without the marker — the
11
+ * command is simply written into the script.
12
+ *
13
+ * The last two are bucket B, the one an operator most needs and can least
14
+ * discover: the check IS running, so the repository looks healthy, and the
15
+ * configuration surface is a decoration.
16
+ *
17
+ * Everything here is read from files the project holds, so both answers are
18
+ * verified rather than inferred — including the negative. "No hook runs this"
19
+ * is a fact about `.husky/`, not a guess about CI.
20
+ * @module cli/gate-report-executors
21
+ */
22
+ import { readdir, readFile } from "node:fs/promises";
23
+ import * as path from "node:path";
24
+ /** A hook that mentions this is shelling Lisa's gate runner. */
25
+ const GATE_RUNNER_REFERENCE = "lisa-run-gates.mjs";
26
+ /**
27
+ * The moments a hook hands to the gate runner.
28
+ *
29
+ * Scanned file-wide rather than line-wide, because the shipped hooks assign the
30
+ * runner to a shell variable several lines above the call site, then invoke
31
+ * `node "$GATE_RUNNER" --moment=push`. A same-line pattern matches neither
32
+ * half, and a report that found no gate runner in a repository whose hooks run
33
+ * one on every commit would be the exact "not present, therefore does not run"
34
+ * error this report exists to remove.
35
+ */
36
+ const MOMENT_FLAG = /--moment=([A-Za-z0-9:_-]{1,64})/g;
37
+ /**
38
+ * The handover marker guarding every built-in step in a shipped hook.
39
+ *
40
+ * `lisa_gate_covers <gate>` is the hook asking whether the gate runner already
41
+ * proved this property. Its presence is therefore proof of the opposite thing
42
+ * from what it reads like: a built-in step exists here, and it runs whenever
43
+ * the declaration does not cover it.
44
+ */
45
+ const BUILTIN_MARKER = /lisa_gate_covers[ \t]{1,8}([a-z][a-z0-9-]{0,63})/g;
46
+ /** Package-manager prefixes a hook may invoke a task through. */
47
+ const RUNNER_PREFIX = String.raw `(?:\$RUNNER|\$\{RUNNER\}|npm run|pnpm run|yarn run|bun run|npm|yarn|bun|pnpm)`;
48
+ /**
49
+ * The moment a git hook fires at, for hooks that name no moment themselves.
50
+ *
51
+ * `commit-msg` runs commitlint with no gate marker and no `--moment` flag, so
52
+ * without this it would contribute evidence to no moment at all. The mapping is
53
+ * git's own semantics, not a Lisa convention.
54
+ */
55
+ const HOOK_NAME_MOMENTS = {
56
+ "pre-commit": "commit",
57
+ "commit-msg": "commit",
58
+ "prepare-commit-msg": "commit",
59
+ "pre-push": "push",
60
+ };
61
+ /**
62
+ * Order strings without mutating the caller's collection.
63
+ * @param values - Values to order
64
+ * @returns A new, sorted array
65
+ */
66
+ function sorted(values) {
67
+ return [...values].sort((left, right) => left.localeCompare(right));
68
+ }
69
+ /**
70
+ * Drop shell comments so a commented-out step is never read as an executor.
71
+ *
72
+ * `.husky/pre-push` carries a worked example of a Lighthouse step inside a
73
+ * comment block. Counting that as proof the gate runs is exactly the class of
74
+ * error this report exists to refuse.
75
+ * @param body - Raw file contents
76
+ * @returns The same text with comment lines blanked
77
+ */
78
+ function stripComments(body) {
79
+ return body
80
+ .split("\n")
81
+ .map(line => (/^\s{0,64}#/.test(line) ? "" : line))
82
+ .join("\n");
83
+ }
84
+ /**
85
+ * Every distinct capture of one global pattern, sorted.
86
+ * @param body - Text to scan
87
+ * @param pattern - A global regular expression with one capture group
88
+ * @returns Sorted, de-duplicated captures
89
+ */
90
+ function captures(body, pattern) {
91
+ return sorted(new Set([...body.matchAll(pattern)]
92
+ .map(match => match[1])
93
+ .filter((value) => value !== undefined)));
94
+ }
95
+ /**
96
+ * Classify one hook file.
97
+ * @param file - Project-relative path
98
+ * @param raw - Raw file contents
99
+ * @returns The classified hook
100
+ */
101
+ export function classifyHook(file, raw) {
102
+ const body = stripComments(raw);
103
+ const name = file.slice(file.lastIndexOf("/") + 1);
104
+ const gateRunnerMoments = body.includes(GATE_RUNNER_REFERENCE)
105
+ ? captures(body, MOMENT_FLAG)
106
+ : [];
107
+ const byName = HOOK_NAME_MOMENTS[name];
108
+ const allMoments = byName === undefined ? gateRunnerMoments : [...gateRunnerMoments, byName];
109
+ return {
110
+ file,
111
+ body,
112
+ moments: sorted(new Set(allMoments)),
113
+ gateRunnerMoments,
114
+ builtinGates: captures(body, BUILTIN_MARKER),
115
+ };
116
+ }
117
+ /**
118
+ * Read and classify every hook in `.husky`.
119
+ * @param projectRoot - Project root
120
+ * @returns Hook evidence, empty when the directory is absent
121
+ */
122
+ export async function collectHookEvidence(projectRoot) {
123
+ const dir = path.join(projectRoot, ".husky");
124
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => undefined);
125
+ if (entries === undefined)
126
+ return { files: [] };
127
+ const names = sorted(entries
128
+ .filter(entry => entry.isFile() && !entry.name.startsWith("."))
129
+ .map(entry => entry.name));
130
+ const read = await Promise.all(names.map(async (name) => {
131
+ const raw = await readFile(path.join(dir, name), "utf8").catch(() => undefined);
132
+ return raw === undefined ? null : classifyHook(`.husky/${name}`, raw);
133
+ }));
134
+ return { files: read.filter((hook) => hook !== null) };
135
+ }
136
+ /**
137
+ * Whether a hook body invokes one task literally, outside the gate runner.
138
+ *
139
+ * Bounded on both sides on purpose. `test:cov` must not match `test:cov:unit`:
140
+ * they are different scripts, and reporting the wrong one as proof would
141
+ * manufacture exactly the green this report exists to withhold.
142
+ * @param body - Comment-stripped hook body
143
+ * @param task - The task to look for
144
+ * @returns True when the hook runs that exact task
145
+ */
146
+ export function hookInvokesTask(body, task) {
147
+ const escaped = task.replace(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
148
+ const pattern = new RegExp(String.raw `(?:^|[\s;&|(])${RUNNER_PREFIX}\s+["']?${escaped}["']?(?=$|[\s"';|&)])`, "m");
149
+ return pattern.test(body);
150
+ }
151
+ /**
152
+ * The evidence a hook contributes for a gate no marker of its names.
153
+ * @param hook - The hook file
154
+ * @param task - The task this gate resolves to, or null
155
+ * @returns Zero or one piece of evidence
156
+ */
157
+ function unmarkedEvidence(hook, task) {
158
+ if (task === null || !hookInvokesTask(hook.body, task))
159
+ return [];
160
+ return [
161
+ {
162
+ kind: "hook-literal",
163
+ file: hook.file,
164
+ detail: `${hook.file} runs \`${task}\` as a written-in step, whatever the settings file says`,
165
+ },
166
+ ];
167
+ }
168
+ /**
169
+ * The gate-runner evidence a hook contributes, when the pair is declared.
170
+ * @param hook - The hook file
171
+ * @param query - The pair being reported
172
+ * @returns Zero or one piece of evidence
173
+ */
174
+ function runnerEvidence(hook, query) {
175
+ if (!query.declared || !hook.gateRunnerMoments.includes(query.moment)) {
176
+ return [];
177
+ }
178
+ return [
179
+ {
180
+ kind: "gate-runner",
181
+ file: hook.file,
182
+ detail: `${hook.file} runs the gate runner at ${query.moment}, which reads this declaration`,
183
+ },
184
+ ];
185
+ }
186
+ /**
187
+ * One hook's contribution to one (gate, moment) pair.
188
+ * @param hook - The hook file
189
+ * @param query - The pair being reported
190
+ * @returns Evidence from this hook alone
191
+ */
192
+ function evidenceFrom(hook, query) {
193
+ const runner = runnerEvidence(hook, query);
194
+ if (!hook.moments.includes(query.moment))
195
+ return runner;
196
+ if (!hook.builtinGates.includes(query.gateId)) {
197
+ return [...runner, ...unmarkedEvidence(hook, query.task)];
198
+ }
199
+ return [
200
+ ...runner,
201
+ {
202
+ kind: "hook-builtin",
203
+ file: hook.file,
204
+ detail: `${hook.file} carries a built-in ${query.gateId} step that runs unless the declaration covers it`,
205
+ },
206
+ ];
207
+ }
208
+ /**
209
+ * Every executor proved for one gate at one moment.
210
+ * @param evidence - Hook evidence for the project
211
+ * @param query - The pair being reported
212
+ * @returns Executors, sorted by file then kind
213
+ */
214
+ export function executorsFor(evidence, query) {
215
+ const found = evidence.files.flatMap(hook => evidenceFrom(hook, query));
216
+ return [...found].sort((left, right) => left.file.localeCompare(right.file) || left.kind.localeCompare(right.kind));
217
+ }
218
+ //# sourceMappingURL=gate-report-executors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-executors.js","sourceRoot":"","sources":["../../src/cli/gate-report-executors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Render the gate report as a standalone HTML page.
3
+ *
4
+ * Deliberately NOT part of `ui/index.html`. That file is one 13,000-line
5
+ * document with no build step and a known hazard where concurrent edits merge
6
+ * badly, and where the report should finally live is an open question nobody
7
+ * has ruled on. A separate page sidesteps both: it is a viewing surface for a
8
+ * payload that already exists, not a decision about the console's structure.
9
+ *
10
+ * Pure and total — report in, string out, no I/O — so the same emitter output
11
+ * always produces the same bytes.
12
+ *
13
+ * The rendering rule that matters: `unknown` must never look like a pass. A
14
+ * cell this run could not determine is drawn in its own hatched, muted style
15
+ * with its own label, and the header counts it in a band of its own rather
16
+ * than folding it into a green total.
17
+ * @module cli/gate-report-preview
18
+ */
19
+ import type { GateReport } from "./gate-report-types.js";
20
+ /**
21
+ * Render the whole page.
22
+ * @param report - The emitter's output
23
+ * @param projectLabel - How to name the project being reported on
24
+ * @returns A complete, self-contained HTML document
25
+ */
26
+ export declare function renderGateReportPreview(report: GateReport, projectLabel: string): string;
27
+ //# sourceMappingURL=gate-report-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-preview.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAGV,UAAU,EAEX,MAAM,wBAAwB,CAAC;AA+LhC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,GACnB,MAAM,CAgDR"}
@@ -0,0 +1,286 @@
1
+ /** How each declaration state is written and coloured. */
2
+ const DECLARATION_LABELS = {
3
+ required: "must",
4
+ optional: "tells",
5
+ off: "off",
6
+ "not-declared": "not declared",
7
+ };
8
+ /** Escape text for HTML text nodes and attribute values. */
9
+ const ESCAPES = {
10
+ "&": "&amp;",
11
+ "<": "&lt;",
12
+ ">": "&gt;",
13
+ '"': "&quot;",
14
+ "'": "&#39;",
15
+ };
16
+ /**
17
+ * Escape a string for interpolation into HTML.
18
+ * @param value - Raw text
19
+ * @returns Escaped text
20
+ */
21
+ function escapeHtml(value) {
22
+ return value.replace(/[&<>"']/g, character => ESCAPES[character] ?? character);
23
+ }
24
+ /**
25
+ * The bucket chip for one cell, or the honest refusal to classify it.
26
+ * @param bucket - The cell's bucket finding
27
+ * @returns One chip of HTML
28
+ */
29
+ function bucketChip(bucket) {
30
+ if (bucket.state === "not-applicable")
31
+ return "";
32
+ if (bucket.state === "unknown") {
33
+ return `<span class="chip unknown" title="${escapeHtml(bucket.message)}">not checked here</span>`;
34
+ }
35
+ const titles = {
36
+ A: "declared, and an executor resolves the declaration",
37
+ B: "an executor exists, but no declaration reaches it",
38
+ C: "a declaration exists that nothing can execute",
39
+ D: "neither declared nor executed",
40
+ };
41
+ return `<span class="chip b-${bucket.value}" title="${escapeHtml(titles[bucket.value] ?? "")}">${bucket.value}</span>`;
42
+ }
43
+ /**
44
+ * One matrix cell.
45
+ * @param cell - The (gate, moment) pair
46
+ * @returns A table cell
47
+ */
48
+ function momentCell(cell) {
49
+ if (!cell.legal) {
50
+ return '<td class="m illegal" title="the registry does not permit declaring this gate here"></td>';
51
+ }
52
+ const label = DECLARATION_LABELS[cell.declaration] ?? cell.declaration;
53
+ const state = cell.declaration === "not-declared" ? "undeclared" : cell.declaration;
54
+ return `<td class="m"><span class="state ${state}">${escapeHtml(label)}</span>${bucketChip(cell.bucket)}</td>`;
55
+ }
56
+ /**
57
+ * Whether `package.json` has the command, said in three states.
58
+ * @param cell - The cell carrying the command
59
+ * @returns One chip of HTML
60
+ */
61
+ function commandChip(cell) {
62
+ if (cell.commandExists.state === "unknown") {
63
+ return '<span class="chip unknown">command not checked</span>';
64
+ }
65
+ if (cell.commandExists.state === "not-applicable")
66
+ return "";
67
+ return cell.commandExists.value
68
+ ? '<span class="chip ok">script exists</span>'
69
+ : '<span class="chip bad">no such script</span>';
70
+ }
71
+ /**
72
+ * The first cell of a row that carries a resolved command, for the task column.
73
+ * @param row - One gate's row
74
+ * @returns The most informative cell, or undefined
75
+ */
76
+ function representativeCell(row) {
77
+ return (row.moments.find(cell => cell.legal && cell.declaration !== "not-declared") ?? row.moments.find(cell => cell.legal));
78
+ }
79
+ /**
80
+ * Legal moments the project's own axis has no column for.
81
+ *
82
+ * There is no bare `deploy` moment in Lisa, and the deploy families only get a
83
+ * column when a project declares an environment. Naming them here keeps the
84
+ * information without inventing a column the project does not have.
85
+ * @param row - One gate's row
86
+ * @param axis - The report's moment axis
87
+ * @returns Human text, or an empty string
88
+ */
89
+ function offAxisMoments(row, axis) {
90
+ const families = row.legalMoments.filter(moment => !axis.includes(moment) && !axis.some(one => one.startsWith(`${moment}:`)));
91
+ return families.length === 0
92
+ ? ""
93
+ : `<div class="offaxis">also legal at ${escapeHtml(families.join(", "))}</div>`;
94
+ }
95
+ /**
96
+ * The cloud job a gate maps to, from the static table.
97
+ * @param job - The job id, or null when the table has no pair for this gate
98
+ * @returns One cell fragment
99
+ */
100
+ function jobCell(job) {
101
+ return job === null
102
+ ? '<span class="chip none">no job</span>'
103
+ : `<code>${escapeHtml(job)}</code>`;
104
+ }
105
+ /**
106
+ * One gate row.
107
+ * @param row - The gate
108
+ * @param axis - The report's moment axis
109
+ * @returns A table row
110
+ */
111
+ function gateRow(row, axis) {
112
+ const cell = representativeCell(row);
113
+ const task = cell?.task ?? row.defaultTask ?? "—";
114
+ return [
115
+ "<tr>",
116
+ `<td><span class="gid">${escapeHtml(row.id)}</span><span class="glabel">${escapeHtml(row.label)}</span>`,
117
+ offAxisMoments(row, axis),
118
+ "</td>",
119
+ `<td class="proves">${escapeHtml(row.summary)}</td>`,
120
+ `<td><code>${escapeHtml(task)}</code>${cell === undefined ? "" : commandChip(cell)}</td>`,
121
+ row.moments.map(momentCell).join(""),
122
+ `<td>${jobCell(row.qualityJob)}<div class="tier3">reads the declaration: not checkable here</div></td>`,
123
+ "</tr>",
124
+ ].join("");
125
+ }
126
+ /**
127
+ * The ruleset section — including the case where it was not read.
128
+ * @param report - The whole report
129
+ * @returns A section of HTML
130
+ */
131
+ function rulesetSection(report) {
132
+ if (report.ruleset.state !== "verified") {
133
+ return `<p class="unknown-note">Branch protection was not read this run — <strong>${escapeHtml(report.ruleset.reason)}</strong>. ${escapeHtml(report.ruleset.message)} Nothing below claims a gate does or does not block a merge.</p>`;
134
+ }
135
+ const { matched, declaredNotRequired, requiredNotDeclared } = report.ruleset.value;
136
+ const list = (values) => values.length === 0
137
+ ? "<li><em>none</em></li>"
138
+ : values
139
+ .map(value => `<li><code>${escapeHtml(value)}</code></li>`)
140
+ .join("");
141
+ return [
142
+ '<div class="cols">',
143
+ `<div><h3>Matched <span class="count">${matched.length}</span></h3><p>Declared <code>required</code>, and the ruleset requires it.</p><ul>${list(matched)}</ul></div>`,
144
+ `<div><h3>Declared, not required <span class="count">${declaredNotRequired.length}</span></h3><p>The settings file asks for it; the ruleset does not enforce it, so it blocks nothing.</p><ul>${list(declaredNotRequired)}</ul></div>`,
145
+ `<div class="sharp"><h3>Required, not declared <span class="count">${requiredNotDeclared.length}</span></h3><p>A merge is blocked on these, and <strong>no declaration governs them</strong>. Changing the settings file cannot turn them on, off, or into something else.</p><ul>${list(requiredNotDeclared)}</ul></div>`,
146
+ "</div>",
147
+ ].join("");
148
+ }
149
+ /**
150
+ * The header counts, each with its denominator and its unknown band.
151
+ * @param report - The whole report
152
+ * @returns A section of HTML
153
+ */
154
+ function summarySection(report) {
155
+ const s = report.summary;
156
+ const verified = s.buckets.A + s.buckets.B + s.buckets.C + s.buckets.D;
157
+ return [
158
+ '<div class="stats">',
159
+ `<div class="stat"><b>${s.governedBySettings}</b><span>of ${s.gateCount} properties governed by the settings file</span></div>`,
160
+ `<div class="stat"><b>${s.provedAnyway}</b><span>proved anyway by a command written into a script</span></div>`,
161
+ `<div class="stat"><b>${s.declaredOffOnly}</b><span>mentioned only to be switched off</span></div>`,
162
+ `<div class="stat"><b>${s.notDeclared}</b><span>never mentioned at all</span></div>`,
163
+ "</div>",
164
+ `<p class="denominator"><strong>${verified} of ${s.legalCells}</strong> gate-and-moment pairs were classified in this run — A ${s.buckets.A} · B ${s.buckets.B} · C ${s.buckets.C} · D ${s.buckets.D}. The other <strong>${s.bucketUnknown}</strong> are <span class="chip unknown">not checked here</span>: this run could not determine them, and they are counted separately rather than added to anything green.</p>`,
165
+ ].join("");
166
+ }
167
+ /**
168
+ * Render the whole page.
169
+ * @param report - The emitter's output
170
+ * @param projectLabel - How to name the project being reported on
171
+ * @returns A complete, self-contained HTML document
172
+ */
173
+ export function renderGateReportPreview(report, projectLabel) {
174
+ const axis = report.momentAxis;
175
+ const head = axis
176
+ .map(moment => `<th class="m">${escapeHtml(moment)}</th>`)
177
+ .join("");
178
+ return `<!doctype html>
179
+ <html lang="en"><head><meta charset="utf-8">
180
+ <meta name="viewport" content="width=device-width,initial-scale=1">
181
+ <title>Gate report — ${escapeHtml(projectLabel)}</title>
182
+ <style>${STYLE}</style></head><body>
183
+ <div class="wrap">
184
+ <header>
185
+ <h1>What this project's checks actually prove</h1>
186
+ <p class="sub">Derived, not written. Every number below comes from ${escapeHtml(projectLabel)}'s own settings file, package scripts and git hooks, joined against Lisa's check registry. Nothing here is a language model's summary of those files.</p>
187
+ ${summarySection(report)}
188
+ </header>
189
+
190
+ <section>
191
+ <h2>Every check, and what governs it</h2>
192
+ <p class="lede">One row per check. One column per moment this project can declare a check at — there is no bare "deploy" column, because Lisa has no such moment: the deploy families take an environment suffix and only appear once a project names one.</p>
193
+ <div class="legend">
194
+ <span><span class="state required">must</span> declared required</span>
195
+ <span><span class="state optional">tells</span> declared optional</span>
196
+ <span><span class="state off">off</span> declared off — different from never declared</span>
197
+ <span><span class="state undeclared">not declared</span> the settings file never mentions it here</span>
198
+ <span><span class="chip b-A">A</span> declared, and something reads the declaration</span>
199
+ <span><span class="chip b-B">B</span> something runs it anyway; the settings file has no say</span>
200
+ <span><span class="chip b-C">C</span> declared, but nothing can run it</span>
201
+ <span><span class="chip unknown">not checked here</span> this run could not determine it — NOT a pass</span>
202
+ </div>
203
+ <div class="scroll"><table>
204
+ <thead><tr><th>Check</th><th>What a pass proves</th><th>Command</th>${head}<th>Cloud job</th></tr></thead>
205
+ <tbody>${report.gates.map(row => gateRow(row, axis)).join("")}</tbody>
206
+ </table></div>
207
+ </section>
208
+
209
+ <section>
210
+ <h2>What a merge is actually blocked on</h2>
211
+ <p class="lede">The contexts the settings file implies, beside the ones the repository's branch-protection rules really require. Nothing compares these two today, so they are only ever equal by hand.</p>
212
+ ${rulesetSection(report)}
213
+ </section>
214
+
215
+ <footer>
216
+ <p>Report format v${String(report.version)} · registry read from the running Lisa package · runner <code>${escapeHtml(report.runner.state === "verified" ? report.runner.value : "unknown")}</code>${report.declarationProblems.length === 0 ? "" : ` · <strong>${String(report.declarationProblems.length)} declaration problem(s)</strong>: ${escapeHtml(report.declarationProblems.join("; "))}`}</p>
217
+ <p>Not rendered yet, and deliberately: the agent on-edit scripts, the table of cloud jobs that ignore the settings file, and any filtering.</p>
218
+ </footer>
219
+ </div>
220
+ </body></html>`;
221
+ }
222
+ /** All styling, inlined so the file opens from disk with no server. */
223
+ const STYLE = `
224
+ :root{--bg:#0f1115;--panel:#161923;--line:#252a38;--ink:#e6e9f2;--dim:#98a0b5;
225
+ --ok:#3fb950;--warn:#d29922;--bad:#f85149;--unk:#6b7280;--acc:#58a6ff}
226
+ *{box-sizing:border-box}
227
+ body{margin:0;background:var(--bg);color:var(--ink);
228
+ font:15px/1.55 ui-sans-serif,-apple-system,"Segoe UI",Roboto,sans-serif}
229
+ .wrap{max-width:1500px;margin:0 auto;padding:32px 24px 72px}
230
+ h1{font-size:26px;margin:0 0 8px}
231
+ h2{font-size:19px;margin:40px 0 6px;padding-top:24px;border-top:1px solid var(--line)}
232
+ h3{font-size:14px;margin:0 0 4px;text-transform:uppercase;letter-spacing:.06em;color:var(--dim)}
233
+ .sub,.lede{color:var(--dim);margin:0 0 16px;max-width:80ch}
234
+ .stats{display:flex;flex-wrap:wrap;gap:12px;margin:20px 0 12px}
235
+ .stat{background:var(--panel);border:1px solid var(--line);border-radius:10px;
236
+ padding:12px 16px;min-width:170px}
237
+ .stat b{display:block;font-size:28px;line-height:1.1}
238
+ .stat span{color:var(--dim);font-size:12.5px}
239
+ .denominator{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acc);
240
+ border-radius:8px;padding:12px 16px;margin:0;max-width:100ch}
241
+ .legend{display:flex;flex-wrap:wrap;gap:8px 18px;margin:14px 0;color:var(--dim);font-size:12.5px}
242
+ .legend>span{display:flex;align-items:center;gap:6px}
243
+ .scroll{overflow-x:auto;border:1px solid var(--line);border-radius:10px}
244
+ table{border-collapse:collapse;width:100%;font-size:13px;background:var(--panel)}
245
+ th,td{text-align:left;padding:9px 11px;border-bottom:1px solid var(--line);vertical-align:top}
246
+ th{position:sticky;top:0;background:#1b2030;font-size:11.5px;text-transform:uppercase;
247
+ letter-spacing:.05em;color:var(--dim);z-index:1}
248
+ th.m,td.m{text-align:center;white-space:nowrap}
249
+ td.illegal{background:repeating-linear-gradient(45deg,transparent,transparent 5px,#1b2030 5px,#1b2030 10px)}
250
+ tr:hover td{background:#1a1f2e}
251
+ .gid{display:block;font-family:ui-monospace,monospace;font-size:12px;color:var(--acc)}
252
+ .glabel{display:block;color:var(--dim);font-size:12px}
253
+ .offaxis{margin-top:4px;color:var(--unk);font-size:11px;font-style:italic}
254
+ .proves{color:var(--dim);max-width:34ch}
255
+ code{font-family:ui-monospace,monospace;font-size:12px;background:#10131c;
256
+ border:1px solid var(--line);border-radius:4px;padding:1px 5px}
257
+ .state{display:inline-block;font-size:11px;border-radius:5px;padding:2px 7px;font-weight:600}
258
+ .required{background:#1d3a24;color:#7ee787;border:1px solid #2ea04326}
259
+ .optional{background:#3a3117;color:#e3b341;border:1px solid #d2992226}
260
+ .off{background:#3a1d1d;color:#ff9492;border:1px solid #f8514926}
261
+ .undeclared{background:#1b2030;color:var(--unk);border:1px dashed #3a4157}
262
+ .chip{display:inline-block;margin-left:5px;font-size:10.5px;border-radius:4px;
263
+ padding:1px 5px;font-weight:600;vertical-align:1px}
264
+ .b-A{background:#123021;color:#7ee787}
265
+ .b-B{background:#3a2c12;color:#e3b341}
266
+ .b-C{background:#3a1616;color:#ff9492}
267
+ .b-D{background:#2a2a2a;color:#bbb}
268
+ .ok{background:#123021;color:#7ee787}
269
+ .bad{background:#3a1616;color:#ff9492}
270
+ .none{background:#1b2030;color:var(--unk)}
271
+ .unknown{background:repeating-linear-gradient(45deg,#22262f,#22262f 3px,#191d25 3px,#191d25 6px);
272
+ color:#9aa3b8;border:1px dashed #3f465c}
273
+ .tier3{margin-top:5px;font-size:11px;color:var(--unk);font-style:italic}
274
+ .unknown-note{background:repeating-linear-gradient(45deg,#22262f,#22262f 4px,#191d25 4px,#191d25 8px);
275
+ border:1px dashed #3f465c;border-radius:8px;padding:14px 16px;color:#c2c9db}
276
+ .cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:14px}
277
+ .cols>div{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 16px}
278
+ .cols .sharp{border-color:#7d2b2b;background:#1e1618}
279
+ .cols p{color:var(--dim);font-size:12.5px;margin:0 0 8px}
280
+ .cols ul{margin:0;padding-left:18px}
281
+ .cols li{margin:3px 0;font-size:12.5px}
282
+ .count{float:right;color:var(--ink);font-size:13px}
283
+ footer{margin-top:44px;padding-top:18px;border-top:1px solid var(--line);
284
+ color:var(--unk);font-size:12.5px}
285
+ `;
286
+ //# sourceMappingURL=gate-report-preview.js.map