@codyswann/lisa 3.49.0 → 3.51.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.
Files changed (130) hide show
  1. package/dist/cli/gate-report-agent-hooks.d.ts +40 -0
  2. package/dist/cli/gate-report-agent-hooks.d.ts.map +1 -0
  3. package/dist/cli/gate-report-agent-hooks.js +226 -0
  4. package/dist/cli/gate-report-agent-hooks.js.map +1 -0
  5. package/dist/cli/gate-report-assemble.d.ts +37 -0
  6. package/dist/cli/gate-report-assemble.d.ts.map +1 -0
  7. package/dist/cli/gate-report-assemble.js +69 -0
  8. package/dist/cli/gate-report-assemble.js.map +1 -0
  9. package/dist/cli/gate-report-cells.d.ts +27 -2
  10. package/dist/cli/gate-report-cells.d.ts.map +1 -1
  11. package/dist/cli/gate-report-cells.js +59 -14
  12. package/dist/cli/gate-report-cells.js.map +1 -1
  13. package/dist/cli/gate-report-contexts.d.ts +70 -0
  14. package/dist/cli/gate-report-contexts.d.ts.map +1 -0
  15. package/dist/cli/gate-report-contexts.js +72 -0
  16. package/dist/cli/gate-report-contexts.js.map +1 -0
  17. package/dist/cli/gate-report-facade.d.ts +62 -0
  18. package/dist/cli/gate-report-facade.d.ts.map +1 -0
  19. package/dist/cli/gate-report-facade.js +224 -0
  20. package/dist/cli/gate-report-facade.js.map +1 -0
  21. package/dist/cli/gate-report-fragment.d.ts +11 -0
  22. package/dist/cli/gate-report-fragment.d.ts.map +1 -0
  23. package/dist/cli/gate-report-fragment.js +100 -0
  24. package/dist/cli/gate-report-fragment.js.map +1 -0
  25. package/dist/cli/gate-report-html.d.ts +47 -0
  26. package/dist/cli/gate-report-html.d.ts.map +1 -0
  27. package/dist/cli/gate-report-html.js +49 -0
  28. package/dist/cli/gate-report-html.js.map +1 -0
  29. package/dist/cli/gate-report-joins.d.ts +37 -0
  30. package/dist/cli/gate-report-joins.d.ts.map +1 -0
  31. package/dist/cli/gate-report-joins.js +104 -0
  32. package/dist/cli/gate-report-joins.js.map +1 -0
  33. package/dist/cli/gate-report-merge.d.ts +22 -0
  34. package/dist/cli/gate-report-merge.d.ts.map +1 -0
  35. package/dist/cli/gate-report-merge.js +79 -0
  36. package/dist/cli/gate-report-merge.js.map +1 -0
  37. package/dist/cli/gate-report-preview.d.ts +0 -18
  38. package/dist/cli/gate-report-preview.d.ts.map +1 -1
  39. package/dist/cli/gate-report-preview.js +35 -266
  40. package/dist/cli/gate-report-preview.js.map +1 -1
  41. package/dist/cli/gate-report-rows.d.ts +39 -0
  42. package/dist/cli/gate-report-rows.d.ts.map +1 -0
  43. package/dist/cli/gate-report-rows.js +209 -0
  44. package/dist/cli/gate-report-rows.js.map +1 -0
  45. package/dist/cli/gate-report-sections.d.ts +32 -0
  46. package/dist/cli/gate-report-sections.d.ts.map +1 -0
  47. package/dist/cli/gate-report-sections.js +164 -0
  48. package/dist/cli/gate-report-sections.js.map +1 -0
  49. package/dist/cli/gate-report-style.d.ts +19 -0
  50. package/dist/cli/gate-report-style.d.ts.map +1 -0
  51. package/dist/cli/gate-report-style.js +97 -0
  52. package/dist/cli/gate-report-style.js.map +1 -0
  53. package/dist/cli/gate-report-summary.d.ts.map +1 -1
  54. package/dist/cli/gate-report-summary.js +3 -0
  55. package/dist/cli/gate-report-summary.js.map +1 -1
  56. package/dist/cli/gate-report-types.d.ts +132 -1
  57. package/dist/cli/gate-report-types.d.ts.map +1 -1
  58. package/dist/cli/gate-report-types.js +1 -1
  59. package/dist/cli/gate-report-upstream.d.ts +70 -0
  60. package/dist/cli/gate-report-upstream.d.ts.map +1 -0
  61. package/dist/cli/gate-report-upstream.js +90 -0
  62. package/dist/cli/gate-report-upstream.js.map +1 -0
  63. package/dist/cli/gate-report.d.ts +2 -0
  64. package/dist/cli/gate-report.d.ts.map +1 -1
  65. package/dist/cli/gate-report.js +98 -140
  66. package/dist/cli/gate-report.js.map +1 -1
  67. package/dist/cli/ui-cmd.d.ts +4 -0
  68. package/dist/cli/ui-cmd.d.ts.map +1 -1
  69. package/dist/cli/ui-cmd.js +10 -2
  70. package/dist/cli/ui-cmd.js.map +1 -1
  71. package/dist/cli/ui-gate-report.d.ts +57 -0
  72. package/dist/cli/ui-gate-report.d.ts.map +1 -0
  73. package/dist/cli/ui-gate-report.js +95 -0
  74. package/dist/cli/ui-gate-report.js.map +1 -0
  75. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  76. package/dist/core/upstream-evidence-manifest.js +23 -2
  77. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  78. package/package.json +1 -1
  79. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  80. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  81. package/plugins/lisa-agy/plugin.json +1 -1
  82. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  84. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  85. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  86. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  90. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  91. package/plugins/lisa-expo-agy/plugin.json +1 -1
  92. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  96. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  97. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  101. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  102. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  106. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  107. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  111. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  112. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  115. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  116. package/plugins/lisa-rails-agy/plugin.json +1 -1
  117. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  120. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  121. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  122. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  125. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  126. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  127. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  129. package/ui/README.md +56 -3
  130. package/ui/index.html +52 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * What else gates my merges, and where does it come from?
3
+ *
4
+ * The rest of this report describes Lisa's own jobs. A reader can reasonably
5
+ * take that for the whole picture of what stands between their work and a
6
+ * merge, and it is not: a project's CI almost always carries jobs Lisa neither
7
+ * ships nor governs — a third-party app, or a workflow the project wrote — and
8
+ * some of them are required.
9
+ *
10
+ * Four origins, because collapsing them loses the attribution. A context Lisa
11
+ * ships AND a declaration governs is working as intended. A context Lisa ships
12
+ * that nothing declares is the project's own settings gap. A context from the
13
+ * project's own workflow is the project's, and Lisa has no opinion about it. A
14
+ * context from something else entirely is neither the project's
15
+ * misconfiguration nor Lisa's defect, and reporting it as a gap in either is
16
+ * how a report manufactures a problem that does not exist.
17
+ * @module cli/gate-report-contexts
18
+ */
19
+ import type { ContextOrigin, Finding, RequiredContextRow } from "./gate-report-types.js";
20
+ /** Everything the classification needs. */
21
+ export interface ContextOriginInputs {
22
+ /** Contexts the settings file's `required` declarations imply. */
23
+ readonly declared: readonly string[];
24
+ /** Every context Lisa could produce if every gate were declared required. */
25
+ readonly lisaUniverse: readonly string[];
26
+ /** Contexts the project's own workflows could post. */
27
+ readonly projectContexts: ReadonlySet<string>;
28
+ }
29
+ /**
30
+ * Which of the four origins a required context has.
31
+ * @param context - The context string
32
+ * @param inputs - Everything the classification needs
33
+ * @returns The origin
34
+ */
35
+ export declare function originOf(context: string, inputs: ContextOriginInputs): ContextOrigin;
36
+ /**
37
+ * Classify every context a merge is actually blocked on.
38
+ *
39
+ * Carries the unknown through unchanged. A run that could not read branch
40
+ * protection has no list to classify, and inventing an empty one would report
41
+ * "nothing else gates your merges" on the strength of not having looked.
42
+ * @param required - The live required contexts, or an unknown
43
+ * @param inputs - Everything the classification needs
44
+ * @returns One row per required context, sorted, or the same unknown
45
+ */
46
+ export declare function classifyRequiredContexts(required: Finding<readonly string[]>, inputs: ContextOriginInputs): Finding<readonly RequiredContextRow[]>;
47
+ /**
48
+ * Every context Lisa could produce, if every gate were declared required.
49
+ *
50
+ * Derived by asking the registry rather than by rebuilding its format string:
51
+ * an `await` gate posts under the awaited signal's own name and a `run` gate
52
+ * under the calling workflow's, and a second transcription of that rule is a
53
+ * second thing to keep true.
54
+ * @param options - Derivation inputs
55
+ * @param options.gateIds - Gate ids legal at the merge moment
56
+ * @param options.moment - The merge moment
57
+ * @param options.workflowName - The calling workflow's name
58
+ * @param options.contextsFor - The registry's own context derivation
59
+ * @returns Every context Lisa ships a job for, sorted
60
+ */
61
+ export declare function lisaContextUniverse(options: {
62
+ gateIds: readonly string[];
63
+ moment: string;
64
+ workflowName: string;
65
+ contextsFor: (gates: Record<string, unknown>, options?: {
66
+ moment?: string;
67
+ workflowName?: string;
68
+ }) => string[];
69
+ }): string[];
70
+ //# sourceMappingURL=gate-report-contexts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-contexts.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-contexts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EACV,aAAa,EACb,OAAO,EACP,kBAAkB,EACnB,MAAM,wBAAwB,CAAC;AAEhC,2CAA2C;AAC3C,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,uDAAuD;IACvD,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC/C;AAcD;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,mBAAmB,GAC1B,aAAa,CAMf;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EACpC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAWxC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,CACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,KACjD,MAAM,EAAE,CAAC;CACf,GAAG,MAAM,EAAE,CAYX"}
@@ -0,0 +1,72 @@
1
+ /** What each origin means, in one operator-readable sentence. */
2
+ const DETAILS = {
3
+ "lisa-governed": "Lisa ships the job and a declaration in this project's settings governs it. Changing the declaration changes what runs.",
4
+ "lisa-undeclared": "Lisa ships the job and it blocks merges, but nothing in this project's settings governs it — changing the settings file cannot turn it on, off, or into something else.",
5
+ "project-workflow": "This project's own workflow posts this context. Lisa neither ships nor governs it, and nothing here is a finding against it.",
6
+ "third-party": "Neither Lisa nor a workflow in this repository posts this context — it comes from an app installed on the repository. It gates merges and is outside both.",
7
+ };
8
+ /**
9
+ * Which of the four origins a required context has.
10
+ * @param context - The context string
11
+ * @param inputs - Everything the classification needs
12
+ * @returns The origin
13
+ */
14
+ export function originOf(context, inputs) {
15
+ if (inputs.declared.includes(context))
16
+ return "lisa-governed";
17
+ if (inputs.lisaUniverse.includes(context))
18
+ return "lisa-undeclared";
19
+ return inputs.projectContexts.has(context)
20
+ ? "project-workflow"
21
+ : "third-party";
22
+ }
23
+ /**
24
+ * Classify every context a merge is actually blocked on.
25
+ *
26
+ * Carries the unknown through unchanged. A run that could not read branch
27
+ * protection has no list to classify, and inventing an empty one would report
28
+ * "nothing else gates your merges" on the strength of not having looked.
29
+ * @param required - The live required contexts, or an unknown
30
+ * @param inputs - Everything the classification needs
31
+ * @returns One row per required context, sorted, or the same unknown
32
+ */
33
+ export function classifyRequiredContexts(required, inputs) {
34
+ if (required.state !== "verified")
35
+ return required;
36
+ return {
37
+ state: "verified",
38
+ value: [...required.value]
39
+ .sort((left, right) => left.localeCompare(right))
40
+ .map(context => {
41
+ const origin = originOf(context, inputs);
42
+ return { context, origin, detail: DETAILS[origin] };
43
+ }),
44
+ };
45
+ }
46
+ /**
47
+ * Every context Lisa could produce, if every gate were declared required.
48
+ *
49
+ * Derived by asking the registry rather than by rebuilding its format string:
50
+ * an `await` gate posts under the awaited signal's own name and a `run` gate
51
+ * under the calling workflow's, and a second transcription of that rule is a
52
+ * second thing to keep true.
53
+ * @param options - Derivation inputs
54
+ * @param options.gateIds - Gate ids legal at the merge moment
55
+ * @param options.moment - The merge moment
56
+ * @param options.workflowName - The calling workflow's name
57
+ * @param options.contextsFor - The registry's own context derivation
58
+ * @returns Every context Lisa ships a job for, sorted
59
+ */
60
+ export function lisaContextUniverse(options) {
61
+ const everything = Object.fromEntries(options.gateIds.map(id => [id, { [options.moment]: "required" }]));
62
+ try {
63
+ return options.contextsFor(everything, {
64
+ moment: options.moment,
65
+ workflowName: options.workflowName,
66
+ });
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ //# sourceMappingURL=gate-report-contexts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-contexts.js","sourceRoot":"","sources":["../../src/cli/gate-report-contexts.ts"],"names":[],"mappings":"AAkCA,iEAAiE;AACjE,MAAM,OAAO,GAA4C;IACvD,eAAe,EACb,yHAAyH;IAC3H,iBAAiB,EACf,yKAAyK;IAC3K,kBAAkB,EAChB,8HAA8H;IAChI,aAAa,EACX,4JAA4J;CAC/J,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CACtB,OAAe,EACf,MAA2B;IAE3B,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,eAAe,CAAC;IAC9D,IAAI,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACpE,OAAO,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC;QACxC,CAAC,CAAC,kBAAkB;QACpB,CAAC,CAAC,aAAa,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAoC,EACpC,MAA2B;IAE3B,IAAI,QAAQ,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC;IACnD,OAAO;QACL,KAAK,EAAE,UAAU;QACjB,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;aACvB,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aAChD,GAAG,CAAC,OAAO,CAAC,EAAE;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACzC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAQnC;IACC,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CACnC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAClE,CAAC;IACF,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE;YACrC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { FacadeSource, Finding } from "./gate-report-types.js";
2
+ /** Everything the local workflows say about façade wiring. */
3
+ export interface FacadeFacts {
4
+ /** Whether `quality.yml` itself was found and parsed. */
5
+ readonly qualityYmlPresent: boolean;
6
+ /** Workflow files parsed, project-relative and sorted. */
7
+ readonly files: readonly string[];
8
+ /** Job id -> the gate its façade resolves. */
9
+ readonly gatesByJob: ReadonlyMap<string, string>;
10
+ /** Every job id declared across the parsed workflows. */
11
+ readonly jobs: ReadonlySet<string>;
12
+ /**
13
+ * Every status context the project's OWN workflows could post.
14
+ *
15
+ * Both the bare job name and the `workflow / job` form, because GitHub
16
+ * spells a context differently depending on how the job was reached and a
17
+ * report that matched only one form would attribute a project's own job to a
18
+ * third party.
19
+ */
20
+ readonly projectContexts: ReadonlySet<string>;
21
+ /** Every job, with the shell it runs and the contexts it could post. */
22
+ readonly jobSites: readonly JobSite[];
23
+ }
24
+ /** One CI job, as the merge-verdict join reads it. */
25
+ export interface JobSite {
26
+ /** The job's id. */
27
+ readonly id: string;
28
+ /** The job's `name:`, which is what a ruleset matches by string. */
29
+ readonly name: string;
30
+ /** The contexts it could post, in both spellings GitHub uses. */
31
+ readonly contexts: readonly string[];
32
+ /** Every `run:` block in the job, concatenated. */
33
+ readonly shell: string;
34
+ }
35
+ /** The refusal a project with no copy of the workflow gets. */
36
+ export declare const TIER_THREE_UNKNOWABLE: Finding<never>;
37
+ /**
38
+ * Read what the project's own workflows say about façade wiring.
39
+ * @param projectRoot - Project root
40
+ * @returns The facts, empty when the project holds no workflows
41
+ */
42
+ export declare function readFacadeFacts(projectRoot: string): Promise<FacadeFacts>;
43
+ /**
44
+ * How the report names the workflows it read.
45
+ * @param facts - The parsed workflow facts
46
+ * @returns The source block
47
+ */
48
+ export declare function facadeSourceOf(facts: FacadeFacts): FacadeSource;
49
+ /**
50
+ * Whether the CI job for one gate reads the declaration.
51
+ *
52
+ * Three answers, and the third is not a fourth state smuggled in: a job the
53
+ * workflow does not declare at all is a different fact from a job that runs a
54
+ * hardcoded command, and calling the first one `false` would report a gate as
55
+ * unwired on the strength of never having found it.
56
+ * @param facts - The parsed workflow facts
57
+ * @param gateId - The gate being reported
58
+ * @param qualityJob - The CI job the static table pairs with it
59
+ * @returns Whether that job's façade resolves this gate
60
+ */
61
+ export declare function facadeFinding(facts: FacadeFacts, gateId: string, qualityJob: string | null): Finding<boolean>;
62
+ //# sourceMappingURL=gate-report-facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-facade.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-facade.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAWpE,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,8CAA8C;IAC9C,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC;AAED,sDAAsD;AACtD,MAAM,WAAW,OAAO;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,+DAA+D;AAC/D,eAAO,MAAM,qBAAqB,EAAE,OAAO,CAAC,KAAK,CAKhD,CAAC;AA+GF;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CA2CtB;AAYD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAE/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,OAAO,CAAC,CAoBlB"}
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Tier 3 — whether a CI job reads the declaration or runs a hardcoded command.
3
+ *
4
+ * That answer is written in `.github/workflows/quality.yml`, and a consumer
5
+ * holds no copy of it: it calls the reusable workflow by ref. So for most
6
+ * projects the honest answer is that the question cannot be answered here.
7
+ *
8
+ * It was, however, being refused UNCONDITIONALLY — including in the one
9
+ * repository where the file is present, which is Lisa's own. A refusal that
10
+ * does not look is indistinguishable from a refusal that looked and failed,
11
+ * and only one of those is true. So: when the workflow is on disk, read it and
12
+ * answer.
13
+ *
14
+ * The read is a deterministic YAML parse and nothing else. No agent, no
15
+ * judgement, no network. A job's façade is wired when its steps carry the
16
+ * resolve step — `id: gate` with a `GATE_ID` in its environment — which is the
17
+ * same signal `tests/integration/quality-gate-skip-jobs-mapping.test.ts`
18
+ * derives the shipped job table from, so the two cannot answer differently.
19
+ *
20
+ * Sibling workflows are scanned too, because a job that moved to a workflow of
21
+ * its own took its façade with it. Reading `quality.yml` alone would report a
22
+ * moved job as unwired on the grounds that it is no longer next door.
23
+ * @module cli/gate-report-facade
24
+ */
25
+ import { readdir, readFile } from "node:fs/promises";
26
+ import * as path from "node:path";
27
+ import { loadYaml } from "../utils/yaml.js";
28
+ /** The workflow Tier 3 is written in. */
29
+ const QUALITY_YML = "quality.yml";
30
+ /** Where a repository keeps its workflows. */
31
+ const WORKFLOW_DIR = path.join(".github", "workflows");
32
+ /** The step id every façade resolve step carries. */
33
+ const RESOLVE_STEP_ID = "gate";
34
+ /** The refusal a project with no copy of the workflow gets. */
35
+ export const TIER_THREE_UNKNOWABLE = {
36
+ state: "unknown",
37
+ reason: "determined-by-quality-yml",
38
+ message: "Whether the CI job reads this declaration or runs a hardcoded command is written in quality.yml, which this project does not have — it calls the reusable workflow by ref.",
39
+ };
40
+ /**
41
+ * The gate one job's façade resolves, if it has one.
42
+ * @param job - One parsed job definition
43
+ * @returns The declared `GATE_ID`, or null
44
+ */
45
+ function gateOfJob(job) {
46
+ if (job === null || typeof job !== "object")
47
+ return null;
48
+ const steps = Reflect.get(job, "steps");
49
+ if (!Array.isArray(steps))
50
+ return null;
51
+ const resolve = steps.find(step => step !== null &&
52
+ typeof step === "object" &&
53
+ Reflect.get(step, "id") === RESOLVE_STEP_ID);
54
+ if (resolve === undefined)
55
+ return null;
56
+ const environment = Reflect.get(resolve, "env");
57
+ if (environment === null || typeof environment !== "object")
58
+ return null;
59
+ const id = Reflect.get(environment, "GATE_ID");
60
+ return typeof id === "string" ? id : null;
61
+ }
62
+ /**
63
+ * Parse one workflow file into its name and its jobs.
64
+ * @param file - Absolute path
65
+ * @returns The parsed workflow, or null when it could not be read
66
+ */
67
+ async function parseWorkflow(file) {
68
+ const source = await readFile(file, "utf8").catch(() => undefined);
69
+ if (source === undefined)
70
+ return null;
71
+ try {
72
+ const parsed = loadYaml(source);
73
+ if (parsed === null || typeof parsed !== "object")
74
+ return null;
75
+ const jobs = Reflect.get(parsed, "jobs");
76
+ if (jobs === null || typeof jobs !== "object")
77
+ return null;
78
+ const workflowName = Reflect.get(parsed, "name");
79
+ return {
80
+ workflowName: typeof workflowName === "string" ? workflowName : null,
81
+ jobs: jobs,
82
+ };
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ /**
89
+ * The contexts one job could post, in both spellings GitHub uses.
90
+ * @param workflowName - The declaring workflow's name
91
+ * @param job - The job definition
92
+ * @param jobId - The job's id, used when it declares no name
93
+ * @returns Candidate context strings
94
+ */
95
+ function contextsOfJob(workflowName, job, jobId) {
96
+ const declared = job !== null && typeof job === "object" ? Reflect.get(job, "name") : null;
97
+ const name = typeof declared === "string" ? declared : jobId;
98
+ return workflowName === null ? [name] : [name, `${workflowName} / ${name}`];
99
+ }
100
+ /**
101
+ * Every `run:` block one job declares, concatenated.
102
+ * @param job - The job definition
103
+ * @returns The shell the job runs
104
+ */
105
+ function shellOf(job) {
106
+ if (job === null || typeof job !== "object")
107
+ return "";
108
+ const steps = Reflect.get(job, "steps");
109
+ if (!Array.isArray(steps))
110
+ return "";
111
+ return steps
112
+ .map(step => {
113
+ if (step === null || typeof step !== "object")
114
+ return "";
115
+ const run = Reflect.get(step, "run");
116
+ return typeof run === "string" ? run : "";
117
+ })
118
+ .join("\n");
119
+ }
120
+ /**
121
+ * Every workflow file in the project, sorted so the report is stable.
122
+ * @param projectRoot - Project root
123
+ * @returns File names, sorted
124
+ */
125
+ async function listWorkflowFiles(projectRoot) {
126
+ const entries = await readdir(path.join(projectRoot, WORKFLOW_DIR), {
127
+ withFileTypes: true,
128
+ }).catch(() => undefined);
129
+ if (entries === undefined)
130
+ return [];
131
+ return entries
132
+ .filter(entry => entry.isFile() &&
133
+ (entry.name.endsWith(".yml") || entry.name.endsWith(".yaml")))
134
+ .map(entry => entry.name)
135
+ .sort((left, right) => left.localeCompare(right));
136
+ }
137
+ /**
138
+ * Read what the project's own workflows say about façade wiring.
139
+ * @param projectRoot - Project root
140
+ * @returns The facts, empty when the project holds no workflows
141
+ */
142
+ export async function readFacadeFacts(projectRoot) {
143
+ const names = await listWorkflowFiles(projectRoot);
144
+ const parsed = await Promise.all(names.map(async (name) => ({
145
+ name,
146
+ parsed: await parseWorkflow(path.join(projectRoot, WORKFLOW_DIR, name)),
147
+ })));
148
+ const readable = parsed.filter((entry) => entry.parsed !== null);
149
+ const sites = readable.flatMap(entry => Object.entries(entry.parsed.jobs).map(([job, definition]) => {
150
+ const contexts = contextsOfJob(entry.parsed.workflowName, definition, job);
151
+ return {
152
+ id: job,
153
+ name: contexts[0] ?? job,
154
+ contexts,
155
+ shell: shellOf(definition),
156
+ gate: gateOfJob(definition),
157
+ };
158
+ }));
159
+ const jobSites = [...sites].sort(byJobId);
160
+ const gatePairs = jobSites.flatMap((site) => site.gate === null ? [] : [[site.id, site.gate]]);
161
+ return {
162
+ qualityYmlPresent: readable.some(entry => entry.name === QUALITY_YML),
163
+ files: readable.map(entry => `${WORKFLOW_DIR}/${entry.name}`),
164
+ // Declaration order wins for a duplicated job id, because that is the job
165
+ // GitHub itself would run: a Map built from pairs keeps the LAST, so the
166
+ // list is reversed before it is built.
167
+ gatesByJob: new Map([...gatePairs].reverse()),
168
+ jobs: new Set(jobSites.map(site => site.id)),
169
+ projectContexts: new Set(jobSites.flatMap(site => site.contexts)),
170
+ jobSites,
171
+ };
172
+ }
173
+ /**
174
+ * Order two jobs by id, so the report is stable across machines.
175
+ * @param left - One job
176
+ * @param right - Another job
177
+ * @returns A comparison result
178
+ */
179
+ function byJobId(left, right) {
180
+ return left.id.localeCompare(right.id);
181
+ }
182
+ /**
183
+ * How the report names the workflows it read.
184
+ * @param facts - The parsed workflow facts
185
+ * @returns The source block
186
+ */
187
+ export function facadeSourceOf(facts) {
188
+ return { present: facts.qualityYmlPresent, files: facts.files };
189
+ }
190
+ /**
191
+ * Whether the CI job for one gate reads the declaration.
192
+ *
193
+ * Three answers, and the third is not a fourth state smuggled in: a job the
194
+ * workflow does not declare at all is a different fact from a job that runs a
195
+ * hardcoded command, and calling the first one `false` would report a gate as
196
+ * unwired on the strength of never having found it.
197
+ * @param facts - The parsed workflow facts
198
+ * @param gateId - The gate being reported
199
+ * @param qualityJob - The CI job the static table pairs with it
200
+ * @returns Whether that job's façade resolves this gate
201
+ */
202
+ export function facadeFinding(facts, gateId, qualityJob) {
203
+ if (qualityJob === null) {
204
+ return {
205
+ state: "not-applicable",
206
+ reason: "no-mapped-job",
207
+ message: "Lisa's static job table pairs no CI job with this gate, so there is no job whose wiring could be read.",
208
+ };
209
+ }
210
+ if (!facts.qualityYmlPresent)
211
+ return TIER_THREE_UNKNOWABLE;
212
+ const resolved = facts.gatesByJob.get(qualityJob);
213
+ if (resolved !== undefined) {
214
+ return { state: "verified", value: resolved === gateId };
215
+ }
216
+ if (facts.jobs.has(qualityJob))
217
+ return { state: "verified", value: false };
218
+ return {
219
+ state: "unknown",
220
+ reason: "job-absent-from-this-workflow",
221
+ message: `quality.yml is present here but declares no job called ${qualityJob}, so whether that job reads this declaration cannot be read from this checkout.`,
222
+ };
223
+ }
224
+ //# sourceMappingURL=gate-report-facade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-facade.js","sourceRoot":"","sources":["../../src/cli/gate-report-facade.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { GateReport } from "./gate-report-types.js";
2
+ /** The class every rule in the report's stylesheet is scoped to. */
3
+ export declare const GATE_REPORT_ROOT_CLASS = "lisa-gate-report";
4
+ /**
5
+ * Render the report as a fragment for the console tab.
6
+ * @param report - The emitter's output
7
+ * @param projectLabel - How to name the project being reported on
8
+ * @returns HTML with no document wrapper, safe to inject into the console
9
+ */
10
+ export declare function renderGateReportFragment(report: GateReport, projectLabel: string): string;
11
+ //# sourceMappingURL=gate-report-fragment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-fragment.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-fragment.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AAwCzD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,GACnB,MAAM,CA8BR"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The gate report as an HTML fragment, for the Doctor tab in `lisa ui`.
3
+ *
4
+ * A fragment rather than a document because the ruling is that this is one
5
+ * surface: a tab alongside the console's other tabs, not a second page on a
6
+ * second port served by a second server.
7
+ *
8
+ * It is composed HERE and injected by the server at request time, and that is
9
+ * deliberate. `ui/index.html` is a single 13,000-line zero-build file with a
10
+ * known concurrent-edit hazard where union merges lose braces; appending a
11
+ * section of this size to it would make a bad merge materially worse and would
12
+ * put a second copy of this markup somewhere no test renders. The console
13
+ * gains a small block that asks the server for this string, so the file grows
14
+ * by tens of lines rather than hundreds, and opening `ui/index.html` straight
15
+ * from disk still works — the tab then says, in the report's own vocabulary,
16
+ * that it could not be derived without a server rather than showing an empty
17
+ * panel.
18
+ *
19
+ * Pure and total: report in, string out, no I/O, so the same emitter output
20
+ * always produces the same bytes.
21
+ * @module cli/gate-report-fragment
22
+ */
23
+ import { chip, escapeHtml } from "./gate-report-html.js";
24
+ import { gateRow } from "./gate-report-rows.js";
25
+ import { agentHooksSection, requiredContextsSection, rulesetSection, summarySection, upstreamSection, } from "./gate-report-sections.js";
26
+ import { GATE_REPORT_STYLE } from "./gate-report-style.js";
27
+ /** The class every rule in the report's stylesheet is scoped to. */
28
+ export const GATE_REPORT_ROOT_CLASS = "lisa-gate-report";
29
+ /**
30
+ * The legend, which is where the three states are named for the reader.
31
+ * @returns A block of HTML
32
+ */
33
+ function legend() {
34
+ return [
35
+ '<div class="lgr-legend">',
36
+ `<span><span class="lgr-state lgr-required">must</span> declared required</span>`,
37
+ `<span><span class="lgr-state lgr-optional">tells</span> declared optional</span>`,
38
+ `<span><span class="lgr-state lgr-off">off</span> declared off — different from never declared</span>`,
39
+ `<span><span class="lgr-state lgr-undeclared">not declared</span> the settings file never mentions it here</span>`,
40
+ `<span>${chip("b-A", "A")} declared, and something reads the declaration</span>`,
41
+ `<span>${chip("b-B", "B")} something runs it anyway; the settings file has no say</span>`,
42
+ `<span>${chip("b-C", "C")} declared, but nothing can run it</span>`,
43
+ `<span>${chip("b-D", "D")} nothing declares it and nothing runs it</span>`,
44
+ `<span>${chip("unknown", "not checked here")} this run could not determine it — NOT a pass</span>`,
45
+ "</div>",
46
+ ].join("");
47
+ }
48
+ /**
49
+ * The footer, which names what the report was derived from.
50
+ * @param report - The whole report
51
+ * @returns A block of HTML
52
+ */
53
+ function footer(report) {
54
+ const runner = report.runner.state === "verified" ? report.runner.value : "unknown";
55
+ const problems = report.declarationProblems.length === 0
56
+ ? ""
57
+ : ` · <strong>${report.declarationProblems.length} declaration problem(s)</strong>: ${escapeHtml(report.declarationProblems.join("; "))}`;
58
+ const facade = report.facadeSource.present
59
+ ? `the workflow file was read from this checkout (${report.facadeSource.files.length} workflow file(s))`
60
+ : "this project holds no copy of the workflow, so what each cloud job does with a declaration could not be read";
61
+ return `<div class="lgr-foot"><p>Report format v${report.version} · registry read from the running Lisa package · runner <code>${escapeHtml(runner)}</code> · ${escapeHtml(facade)}${problems}</p></div>`;
62
+ }
63
+ /**
64
+ * Render the report as a fragment for the console tab.
65
+ * @param report - The emitter's output
66
+ * @param projectLabel - How to name the project being reported on
67
+ * @returns HTML with no document wrapper, safe to inject into the console
68
+ */
69
+ export function renderGateReportFragment(report, projectLabel) {
70
+ const axis = report.momentAxis;
71
+ const head = axis
72
+ .map(moment => `<th class="lgr-m">${escapeHtml(moment)}</th>`)
73
+ .join("");
74
+ return [
75
+ `<div class="${GATE_REPORT_ROOT_CLASS}">`,
76
+ `<style>${GATE_REPORT_STYLE}</style>`,
77
+ `<p class="lgr-sub">Derived, not written. Every number below comes from ${escapeHtml(projectLabel)}'s own settings file, package scripts, git hooks and workflows, joined against Lisa's check registry. Nothing here is a language model's summary of those files.</p>`,
78
+ summarySection(report),
79
+ "<h2>Every check, and what governs it</h2>",
80
+ '<p class="lgr-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>',
81
+ legend(),
82
+ '<div class="lgr-scroll"><table>',
83
+ `<thead><tr><th>Check</th><th>What a pass proves</th><th>Command</th>${head}<th class="lgr-m">Blocks a merge</th><th>Cloud job</th></tr></thead>`,
84
+ `<tbody>${report.gates.map(row => gateRow(row, axis)).join("")}</tbody>`,
85
+ "</table></div>",
86
+ "<h2>What your settings imply, against what the repository requires</h2>",
87
+ '<p class="lgr-lede">The contexts the settings file implies, beside the ones the repository\'s branch-protection rules really require. Nothing compares these two automatically, so they are only ever equal by hand.</p>',
88
+ rulesetSection(report),
89
+ "<h2>What else gates my merges, and where does it come from</h2>",
90
+ '<p class="lgr-lede">Everything above describes Lisa\'s own jobs. This is every required check standing between a change and a merge, whoever put it there — including the ones Lisa neither ships nor governs.</p>',
91
+ requiredContextsSection(report),
92
+ "<h2>What runs on every edit</h2>",
93
+ agentHooksSection(report),
94
+ `<h2>${report.projectIsUpstream ? "Lisa's own limitations — and this project is Lisa" : "Not this project's — Lisa's"}</h2>`,
95
+ upstreamSection(report),
96
+ footer(report),
97
+ "</div>",
98
+ ].join("");
99
+ }
100
+ //# sourceMappingURL=gate-report-fragment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-fragment.js","sourceRoot":"","sources":["../../src/cli/gate-report-fragment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The report's HTML primitives.
3
+ *
4
+ * One escaper and one chip vocabulary, shared by every renderer, so a state
5
+ * cannot acquire a second spelling. The rule the chips exist to hold: an
6
+ * `unknown` must never look like a pass, and — the gap that shipped in the
7
+ * first cut — it must never look like nothing either. A caller that omits a
8
+ * chip because it had nothing to show has invented a fourth outcome the
9
+ * three-state design never sanctioned, and silence is the one outcome a reader
10
+ * cannot tell from "fine".
11
+ * @module cli/gate-report-html
12
+ */
13
+ import type { Finding } from "./gate-report-types.js";
14
+ /**
15
+ * Escape a string for interpolation into HTML.
16
+ * @param value - Raw text
17
+ * @returns Escaped text
18
+ */
19
+ export declare function escapeHtml(value: string): string;
20
+ /**
21
+ * One chip.
22
+ * @param variant - The chip's state class
23
+ * @param text - Its label
24
+ * @param title - Hover text explaining it
25
+ * @returns One chip of HTML
26
+ */
27
+ export declare function chip(variant: string, text: string, title?: string): string;
28
+ /**
29
+ * The chip for a finding this run could not determine.
30
+ *
31
+ * Carries the human message as hover text rather than dropping it: a report
32
+ * that says "not checked here" without saying why is indistinguishable from a
33
+ * bug in the report.
34
+ * @param finding - An unknown or not-applicable finding
35
+ * @param label - What to call it
36
+ * @returns One chip of HTML
37
+ */
38
+ export declare function unknownChip(finding: Extract<Finding<unknown>, {
39
+ reason: string;
40
+ }>, label?: string): string;
41
+ /**
42
+ * One monospaced fragment.
43
+ * @param value - Raw text
44
+ * @returns The text inside a `code` element, escaped
45
+ */
46
+ export declare function code(value: string): string;
47
+ //# sourceMappingURL=gate-report-html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-report-html.d.ts","sourceRoot":"","sources":["../../src/cli/gate-report-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAWtD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAGtE;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EACtD,KAAK,SAAqB,GACzB,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1C"}