@codyswann/lisa 3.52.0 → 3.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/doctor-declared-contexts.d.ts +32 -0
- package/dist/cli/doctor-declared-contexts.d.ts.map +1 -0
- package/dist/cli/doctor-declared-contexts.js +144 -0
- package/dist/cli/doctor-declared-contexts.js.map +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +6 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/gate-report-drift-section.d.ts +8 -0
- package/dist/cli/gate-report-drift-section.d.ts.map +1 -0
- package/dist/cli/gate-report-drift-section.js +68 -0
- package/dist/cli/gate-report-drift-section.js.map +1 -0
- package/dist/cli/gate-report-fragment.d.ts.map +1 -1
- package/dist/cli/gate-report-fragment.js +4 -0
- package/dist/cli/gate-report-fragment.js.map +1 -1
- package/dist/cli/gate-report-joins.d.ts +45 -0
- package/dist/cli/gate-report-joins.d.ts.map +1 -1
- package/dist/cli/gate-report-joins.js +56 -0
- package/dist/cli/gate-report-joins.js.map +1 -1
- package/dist/cli/gate-report-registry.d.ts +9 -0
- package/dist/cli/gate-report-registry.d.ts.map +1 -1
- package/dist/cli/gate-report-registry.js +15 -0
- package/dist/cli/gate-report-registry.js.map +1 -1
- package/dist/cli/gate-report-style.d.ts +1 -1
- package/dist/cli/gate-report-style.d.ts.map +1 -1
- package/dist/cli/gate-report-style.js +3 -0
- package/dist/cli/gate-report-style.js.map +1 -1
- package/dist/cli/gate-report-templates.d.ts +22 -0
- package/dist/cli/gate-report-templates.d.ts.map +1 -0
- package/dist/cli/gate-report-templates.js +110 -0
- package/dist/cli/gate-report-templates.js.map +1 -0
- package/dist/cli/gate-report-types.d.ts +19 -5
- package/dist/cli/gate-report-types.d.ts.map +1 -1
- package/dist/cli/gate-report-types.js.map +1 -1
- package/dist/cli/gate-report.d.ts +3 -0
- package/dist/cli/gate-report.d.ts.map +1 -1
- package/dist/cli/gate-report.js +26 -2
- package/dist/cli/gate-report.js.map +1 -1
- package/dist/core/gate-declaration-drift.d.ts +190 -0
- package/dist/core/gate-declaration-drift.d.ts.map +1 -0
- package/dist/core/gate-declaration-drift.js +300 -0
- package/dist/core/gate-declaration-drift.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +12 -1
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/declared-checks-inspection.d.ts +48 -0
- package/dist/health/declared-checks-inspection.d.ts.map +1 -0
- package/dist/health/declared-checks-inspection.js +154 -0
- package/dist/health/declared-checks-inspection.js.map +1 -0
- package/dist/health/deterministic-inputs.d.ts +40 -0
- package/dist/health/deterministic-inputs.d.ts.map +1 -0
- package/dist/health/deterministic-inputs.js +71 -0
- package/dist/health/deterministic-inputs.js.map +1 -0
- package/dist/health/deterministic.d.ts.map +1 -1
- package/dist/health/deterministic.js +10 -45
- package/dist/health/deterministic.js.map +1 -1
- package/dist/health/ruleset-inspection.d.ts +27 -0
- package/dist/health/ruleset-inspection.d.ts.map +1 -1
- package/dist/health/ruleset-inspection.js +70 -0
- package/dist/health/ruleset-inspection.js.map +1 -1
- 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/.codex-plugin/skills/lisa-doctor/SKILL.md +33 -1
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +33 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +33 -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-copilot/skills/lisa-doctor/SKILL.md +33 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +33 -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
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +33 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hold the gates declaration against the live ruleset, on a path a machine
|
|
3
|
+
* takes on its own.
|
|
4
|
+
*
|
|
5
|
+
* The comparison itself was not the missing piece. A finished reconciler has
|
|
6
|
+
* existed for some time, unit- and mutation-covered, reporting MISSING, EXTRA,
|
|
7
|
+
* MATCHED and a distinct UNPROVEN — and nothing invoked it. Its only callers
|
|
8
|
+
* were copies of prose in a skill telling a human to type the command, which
|
|
9
|
+
* makes it indistinguishable from a control that was never written. This module
|
|
10
|
+
* is the same comparison sitting on a scheduled, machine-invoked path.
|
|
11
|
+
*
|
|
12
|
+
* Two rules it does not get to bend:
|
|
13
|
+
*
|
|
14
|
+
* - **A source this run could not read is `warn`, never `pass`.** A comparison
|
|
15
|
+
* that silently passes when it could not reach the live ruleset is the exact
|
|
16
|
+
* defect this check exists to catch, sited on the check itself.
|
|
17
|
+
* - **Nothing here proposes removing a live required context.** A context no
|
|
18
|
+
* declaration asks for is reported so it can be told apart from a third-party
|
|
19
|
+
* check, not so it can be pruned. The remedy vocabulary in
|
|
20
|
+
* `core/gate-declaration-drift` structurally contains no removal.
|
|
21
|
+
* @module health/declared-checks-inspection
|
|
22
|
+
*/
|
|
23
|
+
import { type DeclarationDriftReport } from "../core/gate-declaration-drift.js";
|
|
24
|
+
import type { HealthFinding } from "./contract.js";
|
|
25
|
+
import { type RulesetReader } from "./ruleset-inspection.js";
|
|
26
|
+
/**
|
|
27
|
+
* The finding one completed comparison produces.
|
|
28
|
+
*
|
|
29
|
+
* A contradiction fails: the two surfaces state opposite things, and one of
|
|
30
|
+
* them is wrong. A gap warns: the declaration is silent, and silence is not
|
|
31
|
+
* permission to stop requiring the context — the registry still carries gates
|
|
32
|
+
* whose jobs run with no declaration anywhere, so reading silence as a defect
|
|
33
|
+
* of the ruleset would point the operator at the wrong file.
|
|
34
|
+
* @param report - The completed comparison
|
|
35
|
+
* @returns The finding
|
|
36
|
+
*/
|
|
37
|
+
export declare function declaredChecksFinding(report: DeclarationDriftReport): HealthFinding;
|
|
38
|
+
/**
|
|
39
|
+
* Compare the gates declaration with the live ruleset.
|
|
40
|
+
* @param projectRoot - Canonical host root
|
|
41
|
+
* @param config - Safe project config
|
|
42
|
+
* @param reader - Structured remote ruleset reader
|
|
43
|
+
* @param timeoutMs - Remaining shared deadline
|
|
44
|
+
* @param signal - Shared cancellation signal
|
|
45
|
+
* @returns The finding, never a pass on an unread source
|
|
46
|
+
*/
|
|
47
|
+
export declare function declaredChecksDriftFinding(projectRoot: string, config: Readonly<Record<string, unknown>>, reader: RulesetReader, timeoutMs: number, signal: AbortSignal): Promise<HealthFinding>;
|
|
48
|
+
//# sourceMappingURL=declared-checks-inspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declared-checks-inspection.d.ts","sourceRoot":"","sources":["../../src/health/declared-checks-inspection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAiCjC;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,sBAAsB,GAC7B,aAAa,CAmCf;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,aAAa,CAAC,CAkDxB"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hold the gates declaration against the live ruleset, on a path a machine
|
|
3
|
+
* takes on its own.
|
|
4
|
+
*
|
|
5
|
+
* The comparison itself was not the missing piece. A finished reconciler has
|
|
6
|
+
* existed for some time, unit- and mutation-covered, reporting MISSING, EXTRA,
|
|
7
|
+
* MATCHED and a distinct UNPROVEN — and nothing invoked it. Its only callers
|
|
8
|
+
* were copies of prose in a skill telling a human to type the command, which
|
|
9
|
+
* makes it indistinguishable from a control that was never written. This module
|
|
10
|
+
* is the same comparison sitting on a scheduled, machine-invoked path.
|
|
11
|
+
*
|
|
12
|
+
* Two rules it does not get to bend:
|
|
13
|
+
*
|
|
14
|
+
* - **A source this run could not read is `warn`, never `pass`.** A comparison
|
|
15
|
+
* that silently passes when it could not reach the live ruleset is the exact
|
|
16
|
+
* defect this check exists to catch, sited on the check itself.
|
|
17
|
+
* - **Nothing here proposes removing a live required context.** A context no
|
|
18
|
+
* declaration asks for is reported so it can be told apart from a third-party
|
|
19
|
+
* check, not so it can be pruned. The remedy vocabulary in
|
|
20
|
+
* `core/gate-declaration-drift` structurally contains no removal.
|
|
21
|
+
* @module health/declared-checks-inspection
|
|
22
|
+
*/
|
|
23
|
+
import { classifyDeclarationDrift, contextOwners, } from "../core/gate-declaration-drift.js";
|
|
24
|
+
import { loadGateRegistry } from "../cli/gate-report-registry.js";
|
|
25
|
+
import { deterministicFinding, namedReason } from "./finding-utils.js";
|
|
26
|
+
import { liveRequiredContexts, } from "./ruleset-inspection.js";
|
|
27
|
+
/** This check's stable id. */
|
|
28
|
+
const CHECK = "github.declared-checks";
|
|
29
|
+
/** The workflow whose name prefixes a run gate's status context. */
|
|
30
|
+
const QUALITY_WORKFLOW_NAME = "🔍 Quality Checks";
|
|
31
|
+
/** Repository slug parts this check will address. */
|
|
32
|
+
const REPOSITORY_PART = /^[A-Za-z0-9_.-]{1,100}$/u;
|
|
33
|
+
/**
|
|
34
|
+
* The repository this check would read, when the settings file names one.
|
|
35
|
+
* @param config - Safe project config
|
|
36
|
+
* @returns The owner and repository, or undefined
|
|
37
|
+
*/
|
|
38
|
+
function githubTarget(config) {
|
|
39
|
+
const github = config.github;
|
|
40
|
+
if (github === null || typeof github !== "object" || Array.isArray(github)) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const owner = Reflect.get(github, "org");
|
|
44
|
+
const repo = Reflect.get(github, "repo");
|
|
45
|
+
return typeof owner === "string" &&
|
|
46
|
+
typeof repo === "string" &&
|
|
47
|
+
REPOSITORY_PART.test(owner) &&
|
|
48
|
+
REPOSITORY_PART.test(repo)
|
|
49
|
+
? { owner, repo }
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The finding one completed comparison produces.
|
|
54
|
+
*
|
|
55
|
+
* A contradiction fails: the two surfaces state opposite things, and one of
|
|
56
|
+
* them is wrong. A gap warns: the declaration is silent, and silence is not
|
|
57
|
+
* permission to stop requiring the context — the registry still carries gates
|
|
58
|
+
* whose jobs run with no declaration anywhere, so reading silence as a defect
|
|
59
|
+
* of the ruleset would point the operator at the wrong file.
|
|
60
|
+
* @param report - The completed comparison
|
|
61
|
+
* @returns The finding
|
|
62
|
+
*/
|
|
63
|
+
export function declaredChecksFinding(report) {
|
|
64
|
+
const contradictions = report.entries.filter(entry => entry.verdict === "declared-not-enforced" ||
|
|
65
|
+
entry.verdict === "enforced-declared-off");
|
|
66
|
+
if (contradictions.length > 0) {
|
|
67
|
+
return deterministicFinding(CHECK, "fail", namedReason("Gates declarations contradict live branch protection", contradictions.map(entry => `${entry.context} (${entry.verdict})`)));
|
|
68
|
+
}
|
|
69
|
+
const gaps = report.entries.filter(entry => entry.verdict === "enforced-undeclared" ||
|
|
70
|
+
entry.verdict === "enforced-declared-optional");
|
|
71
|
+
return gaps.length === 0
|
|
72
|
+
? deterministicFinding(CHECK, "pass", "Every context live branch protection requires is governed by a gates declaration.")
|
|
73
|
+
: deterministicFinding(CHECK, "warn", namedReason("Live branch protection requires contexts no declaration governs", gaps.map(entry => `${entry.context} (${entry.verdict})`)));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Compare the gates declaration with the live ruleset.
|
|
77
|
+
* @param projectRoot - Canonical host root
|
|
78
|
+
* @param config - Safe project config
|
|
79
|
+
* @param reader - Structured remote ruleset reader
|
|
80
|
+
* @param timeoutMs - Remaining shared deadline
|
|
81
|
+
* @param signal - Shared cancellation signal
|
|
82
|
+
* @returns The finding, never a pass on an unread source
|
|
83
|
+
*/
|
|
84
|
+
export async function declaredChecksDriftFinding(projectRoot, config, reader, timeoutMs, signal) {
|
|
85
|
+
const target = githubTarget(config);
|
|
86
|
+
if (target === undefined) {
|
|
87
|
+
return deterministicFinding(CHECK, "warn", "Unproven: no GitHub repository is configured, so the gates declarations were not held against live branch protection.");
|
|
88
|
+
}
|
|
89
|
+
const registry = await loadGateRegistry();
|
|
90
|
+
if (registry === null) {
|
|
91
|
+
return deterministicFinding(CHECK, "warn", "Unproven: Lisa's shipped gate registry could not be located, so no declaration could be resolved into a status context.");
|
|
92
|
+
}
|
|
93
|
+
const actual = await readLive(reader, {
|
|
94
|
+
owner: target.owner,
|
|
95
|
+
repo: target.repo,
|
|
96
|
+
projectRoot,
|
|
97
|
+
timeoutMs,
|
|
98
|
+
signal,
|
|
99
|
+
});
|
|
100
|
+
if (actual === undefined) {
|
|
101
|
+
return deterministicFinding(CHECK, "warn", "Unproven: live branch protection could not be read within the deterministic deadline, so nothing here claims a declaration does or does not match it.");
|
|
102
|
+
}
|
|
103
|
+
const gates = readGatesSafely(registry, projectRoot);
|
|
104
|
+
if (gates === null) {
|
|
105
|
+
return deterministicFinding(CHECK, "warn", "Unproven: the gates block could not be read, so the contexts it implies could not be compared with live branch protection.");
|
|
106
|
+
}
|
|
107
|
+
return declaredChecksFinding(classifyDeclarationDrift({
|
|
108
|
+
surface: "live-ruleset",
|
|
109
|
+
owners: contextOwners({
|
|
110
|
+
registry,
|
|
111
|
+
gates,
|
|
112
|
+
workflowName: QUALITY_WORKFLOW_NAME,
|
|
113
|
+
}),
|
|
114
|
+
enforced: liveRequiredContexts(actual),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Call the ruleset reader, surviving a reader that throws synchronously.
|
|
119
|
+
*
|
|
120
|
+
* `.catch()` on the returned promise handles a rejection and nothing else; a
|
|
121
|
+
* reader whose failure happens before it returns one would take the entire
|
|
122
|
+
* health run down, turning "this source was unreadable" into no report at all.
|
|
123
|
+
* @param reader - Structured remote ruleset reader
|
|
124
|
+
* @param options - Reader arguments
|
|
125
|
+
* @param options.owner - Repository owner
|
|
126
|
+
* @param options.repo - Repository name
|
|
127
|
+
* @param options.projectRoot - Canonical host root
|
|
128
|
+
* @param options.timeoutMs - Remaining shared deadline
|
|
129
|
+
* @param options.signal - Shared cancellation signal
|
|
130
|
+
* @returns The rulesets, or undefined when they could not be read
|
|
131
|
+
*/
|
|
132
|
+
async function readLive(reader, options) {
|
|
133
|
+
try {
|
|
134
|
+
return await reader(options.owner, options.repo, options.projectRoot, options.timeoutMs, options.signal);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read the gates block, keeping a refusal distinguishable from an empty one.
|
|
142
|
+
* @param registry - The shipped registry
|
|
143
|
+
* @param projectRoot - Canonical host root
|
|
144
|
+
* @returns The gates block, or null when it could not be read
|
|
145
|
+
*/
|
|
146
|
+
function readGatesSafely(registry, projectRoot) {
|
|
147
|
+
try {
|
|
148
|
+
return registry.readGates(projectRoot).gates;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=declared-checks-inspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declared-checks-inspection.js","sourceRoot":"","sources":["../../src/health/declared-checks-inspection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two confined project reads every deterministic probe is built on.
|
|
3
|
+
*
|
|
4
|
+
* Both preserve a distinction the checks above them depend on and cannot
|
|
5
|
+
* recover once lost: a settings file that is MISSING is a different finding
|
|
6
|
+
* from one that is present and unreadable, and a JSON file that will not parse
|
|
7
|
+
* is a different finding from one that is not there. Collapsing either into
|
|
8
|
+
* "absent" is how a health run reports a clean project that it simply could
|
|
9
|
+
* not read.
|
|
10
|
+
* @module health/deterministic-inputs
|
|
11
|
+
*/
|
|
12
|
+
import type { SyncReadDependencies } from "../sync/config-sync.js";
|
|
13
|
+
import type { HealthConfigState } from "./project-probes.js";
|
|
14
|
+
import type { RulesetReader } from "./ruleset-inspection.js";
|
|
15
|
+
/**
|
|
16
|
+
* Read config while preserving missing vs malformed state.
|
|
17
|
+
* @param projectRoot - Canonical host project root
|
|
18
|
+
* @returns Whether the settings file is present, readable, and its contents
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadConfigState(projectRoot: string): Promise<HealthConfigState>;
|
|
21
|
+
/**
|
|
22
|
+
* Confined JSON/path readers for the shared sync planner.
|
|
23
|
+
* @param projectRoot - Canonical host project root
|
|
24
|
+
* @returns Readers that never escape the project root
|
|
25
|
+
*/
|
|
26
|
+
export declare function safeSyncReads(projectRoot: string): SyncReadDependencies;
|
|
27
|
+
/**
|
|
28
|
+
* Share one ruleset read between the probes that both need it.
|
|
29
|
+
*
|
|
30
|
+
* Two checks now compare against the same live rulesets — one against Lisa's
|
|
31
|
+
* template, one against the project's declarations — and reading them twice
|
|
32
|
+
* would double a bounded N+1 of GitHub calls inside one shared deadline for an
|
|
33
|
+
* answer that cannot differ between them. A rejection is shared too, on
|
|
34
|
+
* purpose: both probes should report the same source as unread rather than
|
|
35
|
+
* disagree about whether it could be reached.
|
|
36
|
+
* @param reader - The underlying reader
|
|
37
|
+
* @returns A reader that calls through at most once per repository
|
|
38
|
+
*/
|
|
39
|
+
export declare function shareRulesetReader(reader: RulesetReader): RulesetReader;
|
|
40
|
+
//# sourceMappingURL=deterministic-inputs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deterministic-inputs.d.ts","sourceRoot":"","sources":["../../src/health/deterministic-inputs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAO7D;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAiB5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAevE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAWvE"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { projectPathKind, readProjectJsonObject, readProjectText, } from "./read-only-fs.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read config while preserving missing vs malformed state.
|
|
4
|
+
* @param projectRoot - Canonical host project root
|
|
5
|
+
* @returns Whether the settings file is present, readable, and its contents
|
|
6
|
+
*/
|
|
7
|
+
export async function loadConfigState(projectRoot) {
|
|
8
|
+
const kind = await projectPathKind(projectRoot, ".lisa.config.json");
|
|
9
|
+
if (kind === "missing") {
|
|
10
|
+
return { config: {}, present: false, readable: false };
|
|
11
|
+
}
|
|
12
|
+
if (kind !== "file") {
|
|
13
|
+
return { config: {}, present: true, readable: false };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const config = await readProjectJsonObject(projectRoot, ".lisa.config.json");
|
|
17
|
+
return { config: config ?? {}, present: true, readable: true };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { config: {}, present: true, readable: false };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Confined JSON/path readers for the shared sync planner.
|
|
25
|
+
* @param projectRoot - Canonical host project root
|
|
26
|
+
* @returns Readers that never escape the project root
|
|
27
|
+
*/
|
|
28
|
+
export function safeSyncReads(projectRoot) {
|
|
29
|
+
return {
|
|
30
|
+
readJson: async (relativePath) => {
|
|
31
|
+
const text = await readProjectText(projectRoot, relativePath);
|
|
32
|
+
if (text === undefined)
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof SyntaxError)
|
|
39
|
+
return null;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
pathExists: async (relativePath) => (await projectPathKind(projectRoot, relativePath)) !== "missing",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Share one ruleset read between the probes that both need it.
|
|
48
|
+
*
|
|
49
|
+
* Two checks now compare against the same live rulesets — one against Lisa's
|
|
50
|
+
* template, one against the project's declarations — and reading them twice
|
|
51
|
+
* would double a bounded N+1 of GitHub calls inside one shared deadline for an
|
|
52
|
+
* answer that cannot differ between them. A rejection is shared too, on
|
|
53
|
+
* purpose: both probes should report the same source as unread rather than
|
|
54
|
+
* disagree about whether it could be reached.
|
|
55
|
+
* @param reader - The underlying reader
|
|
56
|
+
* @returns A reader that calls through at most once per repository
|
|
57
|
+
*/
|
|
58
|
+
export function shareRulesetReader(reader) {
|
|
59
|
+
const inFlight = new Map();
|
|
60
|
+
return (owner, repo, projectRoot, timeoutMs, signal) => {
|
|
61
|
+
const key = `${owner}/${repo}`;
|
|
62
|
+
const existing = inFlight.get(key);
|
|
63
|
+
if (existing !== undefined)
|
|
64
|
+
return existing;
|
|
65
|
+
const started = reader(owner, repo, projectRoot, timeoutMs, signal);
|
|
66
|
+
// eslint-disable-next-line functional/immutable-data -- the cache is the point
|
|
67
|
+
inFlight.set(key, started);
|
|
68
|
+
return started;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=deterministic-inputs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deterministic-inputs.js","sourceRoot":"","sources":["../../src/health/deterministic-inputs.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,WAAmB;IAEnB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACrE,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACzD,CAAC;IACD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACxD,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,qBAAqB,CACxC,WAAW,EACX,mBAAmB,CACpB,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACxD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,WAAmB;IAC/C,OAAO;QACL,QAAQ,EAAE,KAAK,EAAE,YAAoB,EAAE,EAAE;YACvC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;YAC9D,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC;YACpC,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;YACrC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,WAAW;oBAAE,OAAO,IAAI,CAAC;gBAC9C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,UAAU,EAAE,KAAK,EAAE,YAAoB,EAAE,EAAE,CACzC,CAAC,MAAM,eAAe,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,KAAK,SAAS;KACnE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAqB;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqC,CAAC;IAC9D,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE;QACrD,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QACpE,+EAA+E;QAC/E,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deterministic.d.ts","sourceRoot":"","sources":["../../src/health/deterministic.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deterministic.d.ts","sourceRoot":"","sources":["../../src/health/deterministic.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,eAAe,CAAC;AAgBvB,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAShC,OAAO,EACL,kBAAkB,EAElB,KAAK,aAAa,EACnB,MAAM,yBAAyB,CAAC;AAQjC;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,qDAAqD;IACrD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC;IACtC,yDAAyD;IACzD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAC;IACtD,uDAAuD;IACvD,QAAQ,CAAC,aAAa,CAAC,EAAE,eAAe,CAAC;CAC1C;AAsMD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAC9B,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
import { lstat, realpath } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { runConfigSync
|
|
6
|
+
import { runConfigSync } from "../sync/config-sync.js";
|
|
7
7
|
import { summarizeHealthFindings, validateHealthResult, } from "./contract.js";
|
|
8
8
|
import { HealthDeadline } from "./deadline.js";
|
|
9
|
+
import { declaredChecksDriftFinding } from "./declared-checks-inspection.js";
|
|
10
|
+
import { loadConfigState, safeSyncReads, shareRulesetReader, } from "./deterministic-inputs.js";
|
|
9
11
|
import { deterministicFinding } from "./finding-utils.js";
|
|
10
12
|
import { hookFinding, managedTemplateFinding, packageFinding, pluginFinding, workflowFinding, } from "./governance-probes.js";
|
|
11
13
|
import { readCoreHooksPath } from "./hook-inspection.js";
|
|
12
14
|
import { readInstalledClaudePlugins, } from "./plugin-inspection.js";
|
|
13
15
|
import { configFindings, instructionFinding, projectStateFinding, starterFinding, wikiFinding, } from "./project-probes.js";
|
|
14
|
-
import { projectPathKind, readProjectJsonObject, readProjectText, } from "./read-only-fs.js";
|
|
15
16
|
import { readGithubRulesets, rulesetFinding, } from "./ruleset-inspection.js";
|
|
16
17
|
import { detectHealthProjectShape, } from "./template-inspection.js";
|
|
17
18
|
const DEFAULT_DEADLINE_MS = 110_000;
|
|
@@ -28,6 +29,7 @@ const CHECKS = [
|
|
|
28
29
|
{ check: "plugins.current", unavailable: "warn" },
|
|
29
30
|
{ check: "ci.workflows", unavailable: "fail" },
|
|
30
31
|
{ check: "github.rulesets", unavailable: "warn" },
|
|
32
|
+
{ check: "github.declared-checks", unavailable: "warn" },
|
|
31
33
|
];
|
|
32
34
|
/**
|
|
33
35
|
* Stable fallback when a check cannot complete inside the shared deadline.
|
|
@@ -37,48 +39,6 @@ const CHECKS = [
|
|
|
37
39
|
function unavailableFinding(check, status) {
|
|
38
40
|
return deterministicFinding(check, status, `${check} could not be safely observed within the deterministic deadline.`);
|
|
39
41
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Read config while preserving missing vs malformed state.
|
|
42
|
-
* @param projectRoot
|
|
43
|
-
*/
|
|
44
|
-
async function loadConfigState(projectRoot) {
|
|
45
|
-
const kind = await projectPathKind(projectRoot, ".lisa.config.json");
|
|
46
|
-
if (kind === "missing") {
|
|
47
|
-
return { config: {}, present: false, readable: false };
|
|
48
|
-
}
|
|
49
|
-
if (kind !== "file") {
|
|
50
|
-
return { config: {}, present: true, readable: false };
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
const config = await readProjectJsonObject(projectRoot, ".lisa.config.json");
|
|
54
|
-
return { config: config ?? {}, present: true, readable: true };
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return { config: {}, present: true, readable: false };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Confined JSON/path readers for the shared sync planner.
|
|
62
|
-
* @param projectRoot
|
|
63
|
-
*/
|
|
64
|
-
function safeSyncReads(projectRoot) {
|
|
65
|
-
return {
|
|
66
|
-
readJson: async (relativePath) => {
|
|
67
|
-
const text = await readProjectText(projectRoot, relativePath);
|
|
68
|
-
if (text === undefined)
|
|
69
|
-
return null;
|
|
70
|
-
try {
|
|
71
|
-
return JSON.parse(text);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (error instanceof SyntaxError)
|
|
75
|
-
return null;
|
|
76
|
-
throw error;
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
pathExists: async (relativePath) => (await projectPathKind(projectRoot, relativePath)) !== "missing",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
42
|
/**
|
|
83
43
|
* Build a validated immutable HealthResult from ordered findings.
|
|
84
44
|
* @param started
|
|
@@ -132,6 +92,7 @@ function probes(roots, configState, shape, deadline, options) {
|
|
|
132
92
|
dryRun: true,
|
|
133
93
|
reads: safeSyncReads(roots.projectRoot),
|
|
134
94
|
});
|
|
95
|
+
const rulesets = shareRulesetReader(options.readRulesets ?? readGithubRulesets);
|
|
135
96
|
return [
|
|
136
97
|
{
|
|
137
98
|
...CHECKS[0],
|
|
@@ -164,7 +125,11 @@ function probes(roots, configState, shape, deadline, options) {
|
|
|
164
125
|
},
|
|
165
126
|
{
|
|
166
127
|
...CHECKS[11],
|
|
167
|
-
run: () => rulesetFinding(roots.lisaRoot, roots.projectRoot, safeShape.types, configState.config,
|
|
128
|
+
run: () => rulesetFinding(roots.lisaRoot, roots.projectRoot, safeShape.types, configState.config, rulesets, deadline.remainingMs(), deadline.signal),
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
...CHECKS[12],
|
|
132
|
+
run: () => declaredChecksDriftFinding(roots.projectRoot, configState.config, rulesets, deadline.remainingMs(), deadline.signal),
|
|
168
133
|
},
|
|
169
134
|
];
|
|
170
135
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deterministic.js","sourceRoot":"","sources":["../../src/health/deterministic.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,+HAA+H;AAC/H,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,
|
|
1
|
+
{"version":3,"file":"deterministic.js","sourceRoot":"","sources":["../../src/health/deterministic.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ProjectType } from "../core/config.js";
|
|
2
|
+
import type { EnforcedContext } from "../core/gate-declaration-drift.js";
|
|
2
3
|
import type { HealthFinding } from "./contract.js";
|
|
3
4
|
/** Material ruleset fields health compares. */
|
|
4
5
|
export interface HealthRuleset {
|
|
@@ -36,6 +37,32 @@ export declare const readGithubRulesets: RulesetReader;
|
|
|
36
37
|
* @returns Expected rulesets sorted by name
|
|
37
38
|
*/
|
|
38
39
|
export declare function expectedRulesets(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config: Readonly<Record<string, unknown>>): Promise<readonly HealthRuleset[]>;
|
|
40
|
+
/**
|
|
41
|
+
* The required status contexts one project's ruleset templates name, with the
|
|
42
|
+
* file each requirement was read from.
|
|
43
|
+
*
|
|
44
|
+
* `expectedRulesets` already normalizes templates the way apply does, but it
|
|
45
|
+
* answers with whole ruleset documents and loses which file said what. The
|
|
46
|
+
* declaration comparison has to name the template file — "this context is
|
|
47
|
+
* required and no declaration asks for it" is not actionable without it — so
|
|
48
|
+
* this walk keeps the attribution instead.
|
|
49
|
+
*
|
|
50
|
+
* Returning an empty list is meaningful and must not be read as "nothing is
|
|
51
|
+
* enforced": callers treat an empty result as a source they could not reach,
|
|
52
|
+
* because a comparison against nothing reports every declaration as unenforced.
|
|
53
|
+
* @param lisaRoot - Lisa package root
|
|
54
|
+
* @param projectRoot - Canonical host root
|
|
55
|
+
* @param types - Canonically ordered project types
|
|
56
|
+
* @param config - Safe project config
|
|
57
|
+
* @returns Required contexts with their ruleset and source file
|
|
58
|
+
*/
|
|
59
|
+
export declare function expectedRequiredContexts(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config: Readonly<Record<string, unknown>>): Promise<readonly EnforcedContext[]>;
|
|
60
|
+
/**
|
|
61
|
+
* The required status contexts a live ruleset payload names.
|
|
62
|
+
* @param actual - Rulesets as the repository actually holds them
|
|
63
|
+
* @returns Required contexts with their ruleset, sorted
|
|
64
|
+
*/
|
|
65
|
+
export declare function liveRequiredContexts(actual: readonly HealthRuleset[]): readonly EnforcedContext[];
|
|
39
66
|
/**
|
|
40
67
|
* Compare expected and observed material ruleset documents.
|
|
41
68
|
* @param expected
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ruleset-inspection.d.ts","sourceRoot":"","sources":["../../src/health/ruleset-inspection.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ruleset-inspection.d.ts","sourceRoot":"","sources":["../../src/health/ruleset-inspection.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA+BnD,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;AAEvC,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAsED;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAsChC,CAAC;AA8LF;;;;;;;GAOG;AAEH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAmCnC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACxC,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CA4CrC;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,aAAa,EAAE,GAC/B,SAAS,eAAe,EAAE,CAW5B;AAwJD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,MAAM,EAAE,SAAS,aAAa,EAAE,GAC/B,YAAY,CAiCd;AAuBD;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACzC,MAAM,EAAE,aAAa,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,aAAa,CAAC,CAsDxB"}
|
|
@@ -13,6 +13,10 @@ const ACTIONS_INTEGRATION_ID = 15_368;
|
|
|
13
13
|
const REPOSITORY_PART = /^[A-Za-z0-9_.-]{1,100}$/u;
|
|
14
14
|
const RULESET_CHECK = "github.rulesets";
|
|
15
15
|
const WARN = "warn";
|
|
16
|
+
/** Where a per-repo required-check opt-in is declared. */
|
|
17
|
+
const CONFIGURED_CHECK_SOURCE = ".lisa.config.json \u2192 github.rulesets.addRequiredChecks";
|
|
18
|
+
/** Where a live requirement was read from. */
|
|
19
|
+
const LIVE_CHECK_SOURCE = "the repository's live rulesets";
|
|
16
20
|
/**
|
|
17
21
|
* Return a sorted copy without mutating caller-owned input.
|
|
18
22
|
* @param items - Caller-owned items
|
|
@@ -299,6 +303,72 @@ export async function expectedRulesets(lisaRoot, projectRoot, types, config) {
|
|
|
299
303
|
}
|
|
300
304
|
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
301
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* The required status contexts one project's ruleset templates name, with the
|
|
308
|
+
* file each requirement was read from.
|
|
309
|
+
*
|
|
310
|
+
* `expectedRulesets` already normalizes templates the way apply does, but it
|
|
311
|
+
* answers with whole ruleset documents and loses which file said what. The
|
|
312
|
+
* declaration comparison has to name the template file — "this context is
|
|
313
|
+
* required and no declaration asks for it" is not actionable without it — so
|
|
314
|
+
* this walk keeps the attribution instead.
|
|
315
|
+
*
|
|
316
|
+
* Returning an empty list is meaningful and must not be read as "nothing is
|
|
317
|
+
* enforced": callers treat an empty result as a source they could not reach,
|
|
318
|
+
* because a comparison against nothing reports every declaration as unenforced.
|
|
319
|
+
* @param lisaRoot - Lisa package root
|
|
320
|
+
* @param projectRoot - Canonical host root
|
|
321
|
+
* @param types - Canonically ordered project types
|
|
322
|
+
* @param config - Safe project config
|
|
323
|
+
* @returns Required contexts with their ruleset and source file
|
|
324
|
+
*/
|
|
325
|
+
export async function expectedRequiredContexts(lisaRoot, projectRoot, types, config) {
|
|
326
|
+
const hasWorkflows = (await projectPathKind(projectRoot, path.join(".github", "workflows"))) ===
|
|
327
|
+
"directory";
|
|
328
|
+
const dropped = droppedChecks(config);
|
|
329
|
+
const byName = new Map();
|
|
330
|
+
for (const type of ["all", ...types]) {
|
|
331
|
+
const directory = path.join(lisaRoot, type, "github-rulesets");
|
|
332
|
+
try {
|
|
333
|
+
for (const file of await listFilesRecursive(directory)) {
|
|
334
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
335
|
+
const projected = projectRuleset(parsed);
|
|
336
|
+
const added = addedChecks(config, projected.name);
|
|
337
|
+
const rules = normalizeExpectedRules(withAddedChecks(projected.rules, added), hasWorkflows, dropped);
|
|
338
|
+
const addedContexts = new Set(added.map(check => String(check.context)));
|
|
339
|
+
const template = path
|
|
340
|
+
.relative(lisaRoot, file)
|
|
341
|
+
.split(path.sep)
|
|
342
|
+
.join(path.posix.sep);
|
|
343
|
+
// eslint-disable-next-line functional/immutable-data -- most-specific stack wins, matching expectedRulesets
|
|
344
|
+
byName.set(projected.name, [...requiredStatusChecksByContext(rules).keys()].map(context => ({
|
|
345
|
+
context,
|
|
346
|
+
ruleset: projected.name,
|
|
347
|
+
source: addedContexts.has(context)
|
|
348
|
+
? CONFIGURED_CHECK_SOURCE
|
|
349
|
+
: template,
|
|
350
|
+
})));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (error.code !== "ENOENT")
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return sortedCopy([...byName.values()].flat(), (left, right) => left.context.localeCompare(right.context));
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* The required status contexts a live ruleset payload names.
|
|
362
|
+
* @param actual - Rulesets as the repository actually holds them
|
|
363
|
+
* @returns Required contexts with their ruleset, sorted
|
|
364
|
+
*/
|
|
365
|
+
export function liveRequiredContexts(actual) {
|
|
366
|
+
return sortedCopy(actual.flatMap(ruleset => [...requiredStatusChecksByContext(ruleset.rules).keys()].map(context => ({
|
|
367
|
+
context,
|
|
368
|
+
ruleset: ruleset.name,
|
|
369
|
+
source: LIVE_CHECK_SOURCE,
|
|
370
|
+
}))), (left, right) => left.context.localeCompare(right.context));
|
|
371
|
+
}
|
|
302
372
|
/**
|
|
303
373
|
* Canonicalize semantically unordered JSON objects and arrays.
|
|
304
374
|
* @param value
|