@codyswann/lisa 2.265.0 → 2.267.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/core/lisa-plugin-selection.d.ts +11 -0
- package/dist/core/lisa-plugin-selection.d.ts.map +1 -1
- package/dist/core/lisa-plugin-selection.js +11 -1
- package/dist/core/lisa-plugin-selection.js.map +1 -1
- package/dist/core/lisa.d.ts +0 -41
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +23 -104
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/template-ownership.d.ts +46 -0
- package/dist/core/template-ownership.d.ts.map +1 -0
- package/dist/core/template-ownership.js +123 -0
- package/dist/core/template-ownership.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +38 -7
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/deadline.d.ts +25 -0
- package/dist/health/deadline.d.ts.map +1 -0
- package/dist/health/deadline.js +50 -0
- package/dist/health/deadline.js.map +1 -0
- package/dist/health/deterministic.d.ts +34 -0
- package/dist/health/deterministic.d.ts.map +1 -0
- package/dist/health/deterministic.js +206 -0
- package/dist/health/deterministic.js.map +1 -0
- package/dist/health/finding-utils.d.ts +17 -0
- package/dist/health/finding-utils.d.ts.map +1 -0
- package/dist/health/finding-utils.js +25 -0
- package/dist/health/finding-utils.js.map +1 -0
- package/dist/health/governance-probes.d.ts +51 -0
- package/dist/health/governance-probes.d.ts.map +1 -0
- package/dist/health/governance-probes.js +94 -0
- package/dist/health/governance-probes.js.map +1 -0
- package/dist/health/hook-inspection.d.ts +35 -0
- package/dist/health/hook-inspection.d.ts.map +1 -0
- package/dist/health/hook-inspection.js +123 -0
- package/dist/health/hook-inspection.js.map +1 -0
- package/dist/health/index.d.ts +1 -0
- package/dist/health/index.d.ts.map +1 -1
- package/dist/health/index.js +1 -0
- package/dist/health/index.js.map +1 -1
- package/dist/health/plugin-inspection.d.ts +37 -0
- package/dist/health/plugin-inspection.d.ts.map +1 -0
- package/dist/health/plugin-inspection.js +128 -0
- package/dist/health/plugin-inspection.js.map +1 -0
- package/dist/health/project-probes.d.ts +34 -0
- package/dist/health/project-probes.d.ts.map +1 -0
- package/dist/health/project-probes.js +104 -0
- package/dist/health/project-probes.js.map +1 -0
- package/dist/health/read-only-fs.d.ts +54 -0
- package/dist/health/read-only-fs.d.ts.map +1 -0
- package/dist/health/read-only-fs.js +165 -0
- package/dist/health/read-only-fs.js.map +1 -0
- package/dist/health/ruleset-inspection.d.ts +57 -0
- package/dist/health/ruleset-inspection.d.ts.map +1 -0
- package/dist/health/ruleset-inspection.js +315 -0
- package/dist/health/ruleset-inspection.js.map +1 -0
- package/dist/health/template-inspection.d.ts +67 -0
- package/dist/health/template-inspection.d.ts.map +1 -0
- package/dist/health/template-inspection.js +469 -0
- package/dist/health/template-inspection.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +7 -25
- package/dist/strategies/copy-contents.d.ts.map +1 -1
- package/dist/strategies/copy-contents.js +32 -63
- package/dist/strategies/copy-contents.js.map +1 -1
- package/dist/strategies/merge.d.ts +7 -0
- package/dist/strategies/merge.d.ts.map +1 -1
- package/dist/strategies/merge.js +10 -1
- package/dist/strategies/merge.js.map +1 -1
- package/dist/strategies/package-lisa.d.ts +10 -1
- package/dist/strategies/package-lisa.d.ts.map +1 -1
- package/dist/strategies/package-lisa.js +18 -20
- package/dist/strategies/package-lisa.js.map +1 -1
- package/dist/strategies/tagged-merge.d.ts +7 -0
- package/dist/strategies/tagged-merge.d.ts.map +1 -1
- package/dist/strategies/tagged-merge.js +10 -1
- package/dist/strategies/tagged-merge.js.map +1 -1
- package/dist/sync/config-sync.d.ts +7 -0
- package/dist/sync/config-sync.d.ts.map +1 -1
- package/dist/sync/config-sync.js +23 -16
- package/dist/sync/config-sync.js.map +1 -1
- package/dist/utils/ignore-patterns.d.ts +18 -0
- package/dist/utils/ignore-patterns.d.ts.map +1 -1
- package/dist/utils/ignore-patterns.js +2 -2
- package/dist/utils/ignore-patterns.js.map +1 -1
- package/package.json +3 -2
- 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-implement/SKILL.md +22 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-spec-conformance/SKILL.md +43 -11
- package/plugins/lisa/agents/spec-conformance-specialist.md +5 -2
- package/plugins/lisa/agents/verification-specialist.md +1 -1
- package/plugins/lisa/hooks/enforce-verification-gate.sh +83 -9
- package/plugins/lisa/rules/reference/verification.md +2 -0
- package/plugins/lisa/skills/lisa-implement/SKILL.md +22 -1
- package/plugins/lisa/skills/lisa-spec-conformance/SKILL.md +43 -11
- package/plugins/lisa-agy/agents/spec-conformance-specialist.md +5 -2
- package/plugins/lisa-agy/agents/verification-specialist.md +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +22 -1
- package/plugins/lisa-agy/skills/lisa-spec-conformance/SKILL.md +43 -11
- 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/agents/spec-conformance-specialist.agent.md +5 -2
- package/plugins/lisa-copilot/agents/verification-specialist.agent.md +1 -1
- package/plugins/lisa-copilot/hooks/enforce-verification-gate.sh +83 -9
- package/plugins/lisa-copilot/rules/reference/verification.md +2 -0
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +22 -1
- package/plugins/lisa-copilot/skills/lisa-spec-conformance/SKILL.md +43 -11
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/agents/spec-conformance-specialist.md +5 -2
- package/plugins/lisa-cursor/agents/verification-specialist.md +1 -1
- package/plugins/lisa-cursor/hooks/enforce-verification-gate.sh +83 -9
- package/plugins/lisa-cursor/rules/verification-reference.mdc +2 -0
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +22 -1
- package/plugins/lisa-cursor/skills/lisa-spec-conformance/SKILL.md +43 -11
- 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/agents/spec-conformance-specialist.md +5 -2
- package/plugins/src/base/agents/verification-specialist.md +1 -1
- package/plugins/src/base/hooks/enforce-verification-gate.sh +83 -9
- package/plugins/src/base/rules/reference/verification.md +2 -0
- package/plugins/src/base/skills/lisa-implement/SKILL.md +22 -1
- package/plugins/src/base/skills/lisa-spec-conformance/SKILL.md +43 -11
- package/scripts/detect-stale-workflow-inputs.mjs +21 -148
- package/scripts/lib/reusable-workflow-contract.d.mts +24 -0
- package/scripts/lib/reusable-workflow-contract.mjs +123 -0
- package/scripts/verify-health-deterministic-built.mjs +499 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Confined, bounded filesystem reads for deterministic health probes. */
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const DEFAULT_MAX_BYTES = 512 * 1024;
|
|
6
|
+
const READ_CHUNK_BYTES = 64 * 1024;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a project-relative path without permitting traversal.
|
|
9
|
+
* @param root - Canonical project root
|
|
10
|
+
* @param relativePath - Project-relative path
|
|
11
|
+
* @returns Confined absolute path
|
|
12
|
+
*/
|
|
13
|
+
export function resolveProjectPath(root, relativePath) {
|
|
14
|
+
if (path.isAbsolute(relativePath)) {
|
|
15
|
+
throw new Error("Health path must be project-relative");
|
|
16
|
+
}
|
|
17
|
+
const target = path.resolve(root, relativePath);
|
|
18
|
+
if (target !== root && !target.startsWith(`${root}${path.sep}`)) {
|
|
19
|
+
throw new Error("Health path escapes project root");
|
|
20
|
+
}
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Assert that one lstat result is a bounded regular file.
|
|
25
|
+
* @param stat - File metadata
|
|
26
|
+
* @param maximumBytes - Maximum allowed size
|
|
27
|
+
*/
|
|
28
|
+
function assertBoundedFile(stat, maximumBytes) {
|
|
29
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
30
|
+
throw new Error("Unsafe health project file");
|
|
31
|
+
}
|
|
32
|
+
if (stat.size > maximumBytes) {
|
|
33
|
+
throw new Error("Health project file exceeds size limit");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read at most maximum plus one bytes without unbounded stream behavior.
|
|
38
|
+
* @param handle - Open nonblocking regular-file handle
|
|
39
|
+
* @param maximumBytes - Maximum allowed payload size
|
|
40
|
+
* @param offset - Current read offset
|
|
41
|
+
* @returns Bounded bytes
|
|
42
|
+
*/
|
|
43
|
+
async function readBounded(handle, maximumBytes, offset = 0) {
|
|
44
|
+
const remaining = maximumBytes + 1 - offset;
|
|
45
|
+
if (remaining <= 0)
|
|
46
|
+
return Buffer.alloc(0);
|
|
47
|
+
const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, remaining));
|
|
48
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
49
|
+
if (bytesRead === 0)
|
|
50
|
+
return Buffer.alloc(0);
|
|
51
|
+
const current = chunk.subarray(0, bytesRead);
|
|
52
|
+
const tail = await readBounded(handle, maximumBytes, offset + bytesRead);
|
|
53
|
+
return Buffer.concat([current, tail], bytesRead + tail.length);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read one stable regular file without following a target outside the project.
|
|
57
|
+
* @param root - Canonical project root
|
|
58
|
+
* @param relativePath - Project-relative path
|
|
59
|
+
* @param maximumBytes - Maximum permitted file size
|
|
60
|
+
* @returns File bytes, or undefined when absent
|
|
61
|
+
*/
|
|
62
|
+
export async function readProjectFile(root, relativePath, maximumBytes = DEFAULT_MAX_BYTES) {
|
|
63
|
+
const target = resolveProjectPath(root, relativePath);
|
|
64
|
+
try {
|
|
65
|
+
const before = await lstat(target);
|
|
66
|
+
assertBoundedFile(before, maximumBytes);
|
|
67
|
+
const actual = await realpath(target);
|
|
68
|
+
if (!actual.startsWith(`${root}${path.sep}`)) {
|
|
69
|
+
throw new Error("Unsafe health project file escapes project root");
|
|
70
|
+
}
|
|
71
|
+
const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
72
|
+
try {
|
|
73
|
+
const opened = await handle.stat();
|
|
74
|
+
assertBoundedFile(opened, maximumBytes);
|
|
75
|
+
if (opened.dev !== before.dev || opened.ino !== before.ino) {
|
|
76
|
+
throw new Error("Health project file changed during read");
|
|
77
|
+
}
|
|
78
|
+
const bytes = await readBounded(handle, maximumBytes);
|
|
79
|
+
if (bytes.length > maximumBytes) {
|
|
80
|
+
throw new Error("Health project file exceeds size limit");
|
|
81
|
+
}
|
|
82
|
+
const after = await handle.stat();
|
|
83
|
+
if (after.size !== opened.size || bytes.length !== after.size) {
|
|
84
|
+
throw new Error("Health project file changed during read");
|
|
85
|
+
}
|
|
86
|
+
return { bytes, mode: opened.mode };
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await handle.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error.code === "ENOENT")
|
|
94
|
+
return undefined;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read just the bytes from one confined stable regular file.
|
|
100
|
+
* @param root - Canonical project root
|
|
101
|
+
* @param relativePath - Project-relative path
|
|
102
|
+
* @param maximumBytes - Maximum permitted file size
|
|
103
|
+
* @returns File bytes, or undefined when absent
|
|
104
|
+
*/
|
|
105
|
+
export async function readProjectBytes(root, relativePath, maximumBytes = DEFAULT_MAX_BYTES) {
|
|
106
|
+
return (await readProjectFile(root, relativePath, maximumBytes))?.bytes;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Classify a confined path without following symbolic links or accepting
|
|
110
|
+
* special files such as FIFOs and devices.
|
|
111
|
+
* @param root - Canonical project root
|
|
112
|
+
* @param relativePath - Project-relative path
|
|
113
|
+
* @returns Safe path kind
|
|
114
|
+
*/
|
|
115
|
+
export async function projectPathKind(root, relativePath) {
|
|
116
|
+
const target = resolveProjectPath(root, relativePath);
|
|
117
|
+
try {
|
|
118
|
+
const stat = await lstat(target);
|
|
119
|
+
if (stat.isSymbolicLink())
|
|
120
|
+
throw new Error("Unsafe health project path");
|
|
121
|
+
if (!stat.isFile() && !stat.isDirectory()) {
|
|
122
|
+
throw new Error("Unsafe health project path");
|
|
123
|
+
}
|
|
124
|
+
const actual = await realpath(target);
|
|
125
|
+
if (actual !== root && !actual.startsWith(`${root}${path.sep}`)) {
|
|
126
|
+
throw new Error("Unsafe health project path escapes project root");
|
|
127
|
+
}
|
|
128
|
+
return stat.isDirectory() ? "directory" : "file";
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error.code === "ENOENT")
|
|
132
|
+
return "missing";
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Read strict UTF-8 text from a confined project file.
|
|
138
|
+
* @param root - Canonical project root
|
|
139
|
+
* @param relativePath - Project-relative path
|
|
140
|
+
* @param maximumBytes - Maximum permitted file size
|
|
141
|
+
* @returns Text, or undefined when absent
|
|
142
|
+
*/
|
|
143
|
+
export async function readProjectText(root, relativePath, maximumBytes = DEFAULT_MAX_BYTES) {
|
|
144
|
+
const bytes = await readProjectBytes(root, relativePath, maximumBytes);
|
|
145
|
+
return bytes === undefined
|
|
146
|
+
? undefined
|
|
147
|
+
: new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Read a JSON object without exposing its values in failures.
|
|
151
|
+
* @param root - Canonical project root
|
|
152
|
+
* @param relativePath - Project-relative JSON path
|
|
153
|
+
* @returns Parsed object, or undefined when absent
|
|
154
|
+
*/
|
|
155
|
+
export async function readProjectJsonObject(root, relativePath) {
|
|
156
|
+
const text = await readProjectText(root, relativePath);
|
|
157
|
+
if (text === undefined)
|
|
158
|
+
return undefined;
|
|
159
|
+
const parsed = JSON.parse(text);
|
|
160
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
161
|
+
throw new Error("Health project JSON must be an object");
|
|
162
|
+
}
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=read-only-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-only-fs.js","sourceRoot":"","sources":["../../src/health/read-only-fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ProjectType } from "../core/config.js";
|
|
2
|
+
import type { HealthFinding } from "./contract.js";
|
|
3
|
+
/** Material ruleset fields health compares. */
|
|
4
|
+
export interface HealthRuleset {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly target: unknown;
|
|
7
|
+
readonly enforcement: unknown;
|
|
8
|
+
readonly conditions: unknown;
|
|
9
|
+
readonly rules: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Injectable structured ruleset reader. Implementations must honor `signal`
|
|
13
|
+
* and release any owned handles before settling.
|
|
14
|
+
*/
|
|
15
|
+
export type RulesetReader = (owner: string, repo: string, projectRoot: string, timeoutMs: number, signal: AbortSignal) => Promise<readonly HealthRuleset[]>;
|
|
16
|
+
/** Ruleset comparison result containing names only. */
|
|
17
|
+
export interface RulesetDrift {
|
|
18
|
+
readonly missing: readonly string[];
|
|
19
|
+
readonly drifted: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Default fixed-argv detailed GitHub ruleset reader.
|
|
23
|
+
* @param owner
|
|
24
|
+
* @param repo
|
|
25
|
+
* @param projectRoot
|
|
26
|
+
* @param timeoutMs
|
|
27
|
+
* @param signal
|
|
28
|
+
*/
|
|
29
|
+
export declare const readGithubRulesets: RulesetReader;
|
|
30
|
+
/**
|
|
31
|
+
* Read expected material rulesets after the same per-project normalization as apply.
|
|
32
|
+
* @param lisaRoot - Lisa package root
|
|
33
|
+
* @param projectRoot - Canonical host root
|
|
34
|
+
* @param types - Canonically ordered project types
|
|
35
|
+
* @param config - Safe project config
|
|
36
|
+
* @returns Expected rulesets sorted by name
|
|
37
|
+
*/
|
|
38
|
+
export declare function expectedRulesets(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config: Readonly<Record<string, unknown>>): Promise<readonly HealthRuleset[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Compare expected and observed material ruleset documents.
|
|
41
|
+
* @param expected
|
|
42
|
+
* @param actual
|
|
43
|
+
*/
|
|
44
|
+
export declare function compareRulesets(expected: readonly HealthRuleset[], actual: readonly HealthRuleset[]): RulesetDrift;
|
|
45
|
+
/**
|
|
46
|
+
* Collect and compare detailed material GitHub ruleset state.
|
|
47
|
+
* @param lisaRoot - Lisa package root
|
|
48
|
+
* @param projectRoot - Canonical host root
|
|
49
|
+
* @param types - Safely detected project types
|
|
50
|
+
* @param config - Safe project config
|
|
51
|
+
* @param reader - Structured remote ruleset reader
|
|
52
|
+
* @param timeoutMs - Remaining shared deadline
|
|
53
|
+
* @param signal - Shared cancellation signal
|
|
54
|
+
* @returns GitHub ruleset finding
|
|
55
|
+
*/
|
|
56
|
+
export declare function rulesetFinding(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config: Readonly<Record<string, unknown>>, reader: RulesetReader, timeoutMs: number, signal: AbortSignal): Promise<HealthFinding>;
|
|
57
|
+
//# sourceMappingURL=ruleset-inspection.d.ts.map
|
|
@@ -0,0 +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;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA0BnD,+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;AAgED;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAsChC,CAAC;AAsEF;;;;;;;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,CA4BnC;AA4CD;;;;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,CAwCxB"}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/** Structured, bounded GitHub ruleset collection and canonical comparison. */
|
|
2
|
+
/* eslint-disable jsdoc/require-param-description, jsdoc/require-returns, max-lines -- structured collection and normalization stay colocated */
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { listFilesRecursive } from "../utils/file-operations.js";
|
|
7
|
+
import { deterministicFinding, namedReason } from "./finding-utils.js";
|
|
8
|
+
import { projectPathKind } from "./read-only-fs.js";
|
|
9
|
+
const GH_TIMEOUT_MS = 15_000;
|
|
10
|
+
const MAX_GH_OUTPUT_BYTES = 256 * 1024;
|
|
11
|
+
const MAX_RULESETS = 32;
|
|
12
|
+
const ACTIONS_INTEGRATION_ID = 15_368;
|
|
13
|
+
const REPOSITORY_PART = /^[A-Za-z0-9_.-]{1,100}$/u;
|
|
14
|
+
const RULESET_CHECK = "github.rulesets";
|
|
15
|
+
const WARN = "warn";
|
|
16
|
+
/**
|
|
17
|
+
* Return a sorted copy without mutating caller-owned input.
|
|
18
|
+
* @param items - Caller-owned items
|
|
19
|
+
* @param compare - Stable comparator
|
|
20
|
+
*/
|
|
21
|
+
function sortedCopy(items, compare) {
|
|
22
|
+
const copy = [...items];
|
|
23
|
+
// eslint-disable-next-line functional/immutable-data -- only the detached copy is sorted
|
|
24
|
+
return copy.sort(compare);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run one fixed-argv bounded gh JSON read.
|
|
28
|
+
* @param argv
|
|
29
|
+
* @param cwd
|
|
30
|
+
* @param timeoutMs
|
|
31
|
+
* @param signal
|
|
32
|
+
*/
|
|
33
|
+
function readGhJson(argv, cwd, timeoutMs, signal) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
execFile(
|
|
36
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- fixed user-installed gh executable
|
|
37
|
+
"gh", [...argv], {
|
|
38
|
+
cwd,
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
maxBuffer: MAX_GH_OUTPUT_BYTES,
|
|
41
|
+
signal,
|
|
42
|
+
killSignal: "SIGKILL",
|
|
43
|
+
timeout: Math.max(1, Math.min(timeoutMs, GH_TIMEOUT_MS)),
|
|
44
|
+
}, (error, stdout) => {
|
|
45
|
+
if (error !== null) {
|
|
46
|
+
reject(error);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
resolve(JSON.parse(stdout));
|
|
51
|
+
}
|
|
52
|
+
catch (parseError) {
|
|
53
|
+
reject(parseError);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validate and project one detailed GitHub ruleset response.
|
|
60
|
+
* @param candidate
|
|
61
|
+
*/
|
|
62
|
+
function projectRuleset(candidate) {
|
|
63
|
+
if (candidate === null || typeof candidate !== "object") {
|
|
64
|
+
throw new Error("Ruleset detail was not an object");
|
|
65
|
+
}
|
|
66
|
+
const name = Reflect.get(candidate, "name");
|
|
67
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
68
|
+
throw new Error("Ruleset detail omitted its name");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
name,
|
|
72
|
+
target: Reflect.get(candidate, "target"),
|
|
73
|
+
enforcement: Reflect.get(candidate, "enforcement"),
|
|
74
|
+
conditions: Reflect.get(candidate, "conditions"),
|
|
75
|
+
rules: Reflect.get(candidate, "rules"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Default fixed-argv detailed GitHub ruleset reader.
|
|
80
|
+
* @param owner
|
|
81
|
+
* @param repo
|
|
82
|
+
* @param projectRoot
|
|
83
|
+
* @param timeoutMs
|
|
84
|
+
* @param signal
|
|
85
|
+
*/
|
|
86
|
+
export const readGithubRulesets = async (owner, repo, projectRoot, timeoutMs, signal) => {
|
|
87
|
+
const listed = await readGhJson(["api", "-X", "GET", `repos/${owner}/${repo}/rulesets`], projectRoot, timeoutMs, signal);
|
|
88
|
+
if (!Array.isArray(listed) || listed.length > MAX_RULESETS) {
|
|
89
|
+
throw new Error("Ruleset list exceeded its bounded contract");
|
|
90
|
+
}
|
|
91
|
+
const details = await Promise.all(listed.map(async (entry) => {
|
|
92
|
+
if (entry === null || typeof entry !== "object") {
|
|
93
|
+
throw new Error("Ruleset list entry was not an object");
|
|
94
|
+
}
|
|
95
|
+
const id = Reflect.get(entry, "id");
|
|
96
|
+
if (!Number.isSafeInteger(id)) {
|
|
97
|
+
throw new Error("Ruleset list entry omitted its id");
|
|
98
|
+
}
|
|
99
|
+
return projectRuleset(await readGhJson(["api", "-X", "GET", `repos/${owner}/${repo}/rulesets/${String(id)}`], projectRoot, timeoutMs, signal));
|
|
100
|
+
}));
|
|
101
|
+
return sortedCopy(details, (left, right) => left.name.localeCompare(right.name));
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Read configured required-check opt-outs without exposing their values.
|
|
105
|
+
* @param config
|
|
106
|
+
*/
|
|
107
|
+
function droppedChecks(config) {
|
|
108
|
+
const github = config.github;
|
|
109
|
+
const rulesets = github !== null && typeof github === "object" && !Array.isArray(github)
|
|
110
|
+
? Reflect.get(github, "rulesets")
|
|
111
|
+
: undefined;
|
|
112
|
+
const dropped = rulesets !== null &&
|
|
113
|
+
typeof rulesets === "object" &&
|
|
114
|
+
!Array.isArray(rulesets)
|
|
115
|
+
? Reflect.get(rulesets, "dropRequiredChecks")
|
|
116
|
+
: undefined;
|
|
117
|
+
return new Set(Array.isArray(dropped)
|
|
118
|
+
? dropped.filter(item => typeof item === "string")
|
|
119
|
+
: []);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Apply workflow and configured required-check normalization used by apply.
|
|
123
|
+
* @param rules
|
|
124
|
+
* @param hasWorkflows
|
|
125
|
+
* @param dropped
|
|
126
|
+
*/
|
|
127
|
+
function normalizeExpectedRules(rules, hasWorkflows, dropped) {
|
|
128
|
+
if (!Array.isArray(rules))
|
|
129
|
+
return rules;
|
|
130
|
+
return rules.flatMap(rule => {
|
|
131
|
+
if (rule === null ||
|
|
132
|
+
typeof rule !== "object" ||
|
|
133
|
+
Reflect.get(rule, "type") !== "required_status_checks") {
|
|
134
|
+
return [rule];
|
|
135
|
+
}
|
|
136
|
+
const parameters = Reflect.get(rule, "parameters");
|
|
137
|
+
if (parameters === null || typeof parameters !== "object")
|
|
138
|
+
return [rule];
|
|
139
|
+
const checks = Reflect.get(parameters, "required_status_checks");
|
|
140
|
+
if (!Array.isArray(checks))
|
|
141
|
+
return [rule];
|
|
142
|
+
const retained = checks.filter(check => {
|
|
143
|
+
if (check === null || typeof check !== "object")
|
|
144
|
+
return true;
|
|
145
|
+
const integration = Reflect.get(check, "integration_id");
|
|
146
|
+
const context = Reflect.get(check, "context");
|
|
147
|
+
return ((hasWorkflows || integration !== ACTIONS_INTEGRATION_ID) &&
|
|
148
|
+
(typeof context !== "string" || !dropped.has(context)));
|
|
149
|
+
});
|
|
150
|
+
if (retained.length === 0)
|
|
151
|
+
return [];
|
|
152
|
+
return [
|
|
153
|
+
{
|
|
154
|
+
...rule,
|
|
155
|
+
parameters: { ...parameters, required_status_checks: retained },
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Read expected material rulesets after the same per-project normalization as apply.
|
|
162
|
+
* @param lisaRoot - Lisa package root
|
|
163
|
+
* @param projectRoot - Canonical host root
|
|
164
|
+
* @param types - Canonically ordered project types
|
|
165
|
+
* @param config - Safe project config
|
|
166
|
+
* @returns Expected rulesets sorted by name
|
|
167
|
+
*/
|
|
168
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- normalization mirrors the apply contract explicitly
|
|
169
|
+
export async function expectedRulesets(lisaRoot, projectRoot, types, config) {
|
|
170
|
+
const hasWorkflows = (await projectPathKind(projectRoot, path.join(".github", "workflows"))) ===
|
|
171
|
+
"directory";
|
|
172
|
+
const dropped = droppedChecks(config);
|
|
173
|
+
const byName = new Map();
|
|
174
|
+
for (const type of ["all", ...types]) {
|
|
175
|
+
const directory = path.join(lisaRoot, type, "github-rulesets");
|
|
176
|
+
try {
|
|
177
|
+
for (const file of await listFilesRecursive(directory)) {
|
|
178
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
179
|
+
const projected = projectRuleset(parsed);
|
|
180
|
+
const normalized = {
|
|
181
|
+
...projected,
|
|
182
|
+
rules: normalizeExpectedRules(projected.rules, hasWorkflows, dropped),
|
|
183
|
+
};
|
|
184
|
+
if (!Array.isArray(normalized.rules) || normalized.rules.length > 0) {
|
|
185
|
+
// eslint-disable-next-line functional/immutable-data -- most-specific stack wins in the bounded plan
|
|
186
|
+
byName.set(normalized.name, normalized);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (error.code !== "ENOENT")
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return [...byName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Canonicalize semantically unordered JSON objects and arrays.
|
|
199
|
+
* @param value
|
|
200
|
+
*/
|
|
201
|
+
function canonical(value) {
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
return value
|
|
204
|
+
.map(canonical)
|
|
205
|
+
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
206
|
+
}
|
|
207
|
+
if (value !== null && typeof value === "object") {
|
|
208
|
+
return Object.fromEntries(Object.entries(value)
|
|
209
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
210
|
+
.map(([key, item]) => [key, canonical(item)]));
|
|
211
|
+
}
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Remove read-only defaults GitHub may add to otherwise equivalent rules.
|
|
216
|
+
* This projection is applied symmetrically so authored non-default values
|
|
217
|
+
* remain material.
|
|
218
|
+
* @param value
|
|
219
|
+
*/
|
|
220
|
+
function withoutGithubDefaults(value) {
|
|
221
|
+
if (Array.isArray(value))
|
|
222
|
+
return value.map(withoutGithubDefaults);
|
|
223
|
+
if (value !== null && typeof value === "object") {
|
|
224
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => key === "required_reviewers" && Array.isArray(item) && item.length === 0
|
|
225
|
+
? []
|
|
226
|
+
: [[key, withoutGithubDefaults(item)]]));
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Compare expected and observed material ruleset documents.
|
|
232
|
+
* @param expected
|
|
233
|
+
* @param actual
|
|
234
|
+
*/
|
|
235
|
+
export function compareRulesets(expected, actual) {
|
|
236
|
+
const observed = new Map(actual.map(item => [item.name, item]));
|
|
237
|
+
const missing = expected
|
|
238
|
+
.filter(item => !observed.has(item.name))
|
|
239
|
+
.map(item => item.name);
|
|
240
|
+
const drifted = expected.flatMap(item => {
|
|
241
|
+
const present = observed.get(item.name);
|
|
242
|
+
if (present === undefined)
|
|
243
|
+
return [];
|
|
244
|
+
const expectedMaterial = canonical(withoutGithubDefaults({
|
|
245
|
+
target: item.target,
|
|
246
|
+
enforcement: item.enforcement,
|
|
247
|
+
conditions: item.conditions,
|
|
248
|
+
rules: item.rules,
|
|
249
|
+
}));
|
|
250
|
+
const actualMaterial = canonical(withoutGithubDefaults({
|
|
251
|
+
target: present.target,
|
|
252
|
+
enforcement: present.enforcement,
|
|
253
|
+
conditions: present.conditions,
|
|
254
|
+
rules: present.rules,
|
|
255
|
+
}));
|
|
256
|
+
if (JSON.stringify(expectedMaterial) !== JSON.stringify(actualMaterial)) {
|
|
257
|
+
return [item.name];
|
|
258
|
+
}
|
|
259
|
+
return [];
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
missing: sortedCopy(missing, (left, right) => left.localeCompare(right)),
|
|
263
|
+
drifted: sortedCopy(drifted, (left, right) => left.localeCompare(right)),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Extract a safe GitHub target without reporting configured values.
|
|
268
|
+
* @param config
|
|
269
|
+
*/
|
|
270
|
+
function githubTarget(config) {
|
|
271
|
+
const github = config.github;
|
|
272
|
+
if (github === null || typeof github !== "object" || Array.isArray(github)) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
const owner = Reflect.get(github, "org");
|
|
276
|
+
const repo = Reflect.get(github, "repo");
|
|
277
|
+
return typeof owner === "string" &&
|
|
278
|
+
typeof repo === "string" &&
|
|
279
|
+
REPOSITORY_PART.test(owner) &&
|
|
280
|
+
REPOSITORY_PART.test(repo)
|
|
281
|
+
? { owner, repo }
|
|
282
|
+
: undefined;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Collect and compare detailed material GitHub ruleset state.
|
|
286
|
+
* @param lisaRoot - Lisa package root
|
|
287
|
+
* @param projectRoot - Canonical host root
|
|
288
|
+
* @param types - Safely detected project types
|
|
289
|
+
* @param config - Safe project config
|
|
290
|
+
* @param reader - Structured remote ruleset reader
|
|
291
|
+
* @param timeoutMs - Remaining shared deadline
|
|
292
|
+
* @param signal - Shared cancellation signal
|
|
293
|
+
* @returns GitHub ruleset finding
|
|
294
|
+
*/
|
|
295
|
+
export async function rulesetFinding(lisaRoot, projectRoot, types, config, reader, timeoutMs, signal) {
|
|
296
|
+
const target = githubTarget(config);
|
|
297
|
+
if (target === undefined) {
|
|
298
|
+
return deterministicFinding("github.rulesets", "warn", "GitHub rulesets are unavailable without a configured repository.");
|
|
299
|
+
}
|
|
300
|
+
const actual = await reader(target.owner, target.repo, projectRoot, timeoutMs, signal).catch(() => undefined);
|
|
301
|
+
if (actual === undefined) {
|
|
302
|
+
return deterministicFinding(RULESET_CHECK, WARN, "GitHub rulesets could not be observed within the deterministic deadline.");
|
|
303
|
+
}
|
|
304
|
+
const expected = await expectedRulesets(lisaRoot, projectRoot, types, config);
|
|
305
|
+
const drift = compareRulesets(expected, actual);
|
|
306
|
+
const names = [
|
|
307
|
+
...drift.missing.map(name => `${name} missing`),
|
|
308
|
+
...drift.drifted.map(name => `${name} drifted`),
|
|
309
|
+
];
|
|
310
|
+
return names.length === 0
|
|
311
|
+
? deterministicFinding(RULESET_CHECK, "pass", "Required GitHub rulesets are active and materially current.")
|
|
312
|
+
: deterministicFinding(RULESET_CHECK, "fail", namedReason("GitHub ruleset drift", names));
|
|
313
|
+
}
|
|
314
|
+
/* eslint-enable jsdoc/require-param-description, jsdoc/require-returns, max-lines -- restore repository defaults */
|
|
315
|
+
//# sourceMappingURL=ruleset-inspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ruleset-inspection.js","sourceRoot":"","sources":["../../src/health/ruleset-inspection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ProjectType } from "../core/config.js";
|
|
2
|
+
import { DEFAULT_PROJECT_LEARNINGS_FILE } from "../core/project-config.js";
|
|
3
|
+
declare const MANAGED = "managed";
|
|
4
|
+
declare const HOOKS = "hooks";
|
|
5
|
+
declare const WORKFLOWS = "workflows";
|
|
6
|
+
/** Template categories reported by separate deterministic findings. */
|
|
7
|
+
export type ManagedTemplateCategory = typeof MANAGED | typeof HOOKS | typeof WORKFLOWS;
|
|
8
|
+
/** Stale or unresolvable reusable-workflow inputs. */
|
|
9
|
+
export interface WorkflowInputInspection {
|
|
10
|
+
readonly stale: readonly string[];
|
|
11
|
+
readonly unknown: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
/** Safely-detected project shape reused by health probes. */
|
|
14
|
+
export interface HealthProjectShape {
|
|
15
|
+
readonly types: readonly ProjectType[];
|
|
16
|
+
readonly packageJson: Readonly<Record<string, unknown>> | undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Detect project types using only confined, bounded project reads.
|
|
20
|
+
* @param projectRoot - Canonical host project root
|
|
21
|
+
* @returns Safe project shape
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectHealthProjectShape(projectRoot: string): Promise<HealthProjectShape>;
|
|
24
|
+
/**
|
|
25
|
+
* Backward-compatible type-only detector surface.
|
|
26
|
+
* @param projectRoot
|
|
27
|
+
*/
|
|
28
|
+
export declare function detectHealthProjectTypes(projectRoot: string): Promise<readonly ProjectType[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Inspect one category using Lisa's shared ownership and pure comparators.
|
|
31
|
+
* @param lisaRoot - Lisa package root
|
|
32
|
+
* @param projectRoot - Canonical host root
|
|
33
|
+
* @param types - Canonically ordered types
|
|
34
|
+
* @param category - Health category
|
|
35
|
+
* @param config - Safe project config
|
|
36
|
+
* @returns Sorted project-relative drift paths
|
|
37
|
+
*/
|
|
38
|
+
export declare function inspectManagedTemplates(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], category: ManagedTemplateCategory, config?: Readonly<Record<string, unknown>>): Promise<readonly string[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Require applicable create-only workflow presence while preserving bytes.
|
|
41
|
+
* @param lisaRoot - Lisa package root
|
|
42
|
+
* @param projectRoot - Canonical host root
|
|
43
|
+
* @param types - Canonically ordered types
|
|
44
|
+
* @param config - Safe project config
|
|
45
|
+
* @returns Missing workflow paths
|
|
46
|
+
*/
|
|
47
|
+
export declare function inspectCreateOnlyWorkflows(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config?: Readonly<Record<string, unknown>>): Promise<readonly string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Inspect applicable create-only workflow caller inputs against shipped contracts.
|
|
50
|
+
* @param lisaRoot - Lisa package root
|
|
51
|
+
* @param projectRoot - Canonical host root
|
|
52
|
+
* @param types - Canonically ordered types
|
|
53
|
+
* @param config - Safe project config
|
|
54
|
+
* @returns Stale and unknown caller descriptions
|
|
55
|
+
*/
|
|
56
|
+
export declare function inspectWorkflowInputs(lisaRoot: string, projectRoot: string, types: readonly ProjectType[], config?: Readonly<Record<string, unknown>>): Promise<WorkflowInputInspection>;
|
|
57
|
+
/**
|
|
58
|
+
* Inspect package.json through the exact pure package-lisa planner.
|
|
59
|
+
* @param lisaRoot - Lisa package root
|
|
60
|
+
* @param projectRoot - Canonical host root
|
|
61
|
+
* @param types - Safely detected project types
|
|
62
|
+
* @param packageJson - Safely captured package document
|
|
63
|
+
* @returns Whether package.json conforms
|
|
64
|
+
*/
|
|
65
|
+
export declare function packageJsonConforms(lisaRoot: string, projectRoot: string, types?: readonly ProjectType[], packageJson?: Readonly<Record<string, unknown>>): Promise<boolean>;
|
|
66
|
+
export { DEFAULT_PROJECT_LEARNINGS_FILE };
|
|
67
|
+
//# sourceMappingURL=template-inspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-inspection.d.ts","sourceRoot":"","sources":["../../src/health/template-inspection.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,EACL,8BAA8B,EAI/B,MAAM,2BAA2B,CAAC;AAyBnC,QAAA,MAAM,OAAO,YAAY,CAAC;AAC1B,QAAA,MAAM,KAAK,UAAU,CAAC;AACtB,QAAA,MAAM,SAAS,cAAc,CAAC;AAc9B,uEAAuE;AACvE,MAAM,MAAM,uBAAuB,GAC/B,OAAO,OAAO,GACd,OAAO,KAAK,GACZ,OAAO,SAAS,CAAC;AAiBrB,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE;AAiFD;;;;GAIG;AAEH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAuD7B;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,SAAS,WAAW,EAAE,CAAC,CAEjC;AA4RD;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,QAAQ,EAAE,uBAAuB,EACjC,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAyB5B;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAkB5B;AAED;;;;;;;GAOG;AAEH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC7C,OAAO,CAAC,uBAAuB,CAAC,CAuDlC;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,EAC9B,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC9C,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,OAAO,EAAE,8BAA8B,EAAE,CAAC"}
|