sentinelayer-cli 0.39.1 → 0.40.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/package.json +1 -1
- package/src/agents/ai-governance/tools/base.js +19 -166
- package/src/agents/backend/tools/base.js +33 -186
- package/src/agents/code-quality/tools/base.js +27 -154
- package/src/agents/data-layer/tools/base.js +35 -180
- package/src/agents/documentation/tools/base.js +18 -146
- package/src/agents/infrastructure/tools/base.js +17 -164
- package/src/agents/observability/tools/base.js +35 -180
- package/src/agents/performance/tools/base.js +35 -205
- package/src/agents/release/tools/base.js +35 -180
- package/src/agents/reliability/tools/base.js +35 -180
- package/src/agents/security/tools/base.js +33 -187
- package/src/agents/shared-tools/domain-base.js +246 -0
- package/src/agents/supply-chain/tools/base.js +15 -144
- package/src/agents/testing/tools/base.js +33 -184
- package/src/cli.js +5 -0
- package/src/commands/mcp.js +1 -1
- package/src/commands/ring-owner.js +58 -0
- package/src/commands/session.js +92 -31
- package/src/legacy-cli.js +2 -2
- package/src/mcp/smoke.js +9 -8
- package/src/pocket/ring-owner.js +97 -0
- package/src/review/local-review.js +9 -5
- package/src/review/omargate-orchestrator.js +111 -14
- package/src/review/spec-binding.js +16 -1
- package/src/scan/generator.js +1 -1
- package/src/session/checkpoints.js +194 -0
- package/src/session/pricing-ledger.js +2 -0
- package/src/session/recap.js +13 -1
package/package.json
CHANGED
|
@@ -1,171 +1,24 @@
|
|
|
1
1
|
// Shared helpers for Amina's (ai-governance) domain tools (#A24).
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
".idea",
|
|
21
|
-
".vscode",
|
|
22
|
-
"__pycache__",
|
|
23
|
-
".cache",
|
|
24
|
-
]);
|
|
25
|
-
const MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
26
|
-
const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
|
|
27
|
-
|
|
28
|
-
export function toPosix(value) {
|
|
29
|
-
return String(value || "").replace(/\\/g, "/");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function createFinding({
|
|
33
|
-
severity,
|
|
34
|
-
kind,
|
|
35
|
-
file,
|
|
36
|
-
line = 0,
|
|
37
|
-
evidence = "",
|
|
38
|
-
rootCause = "",
|
|
39
|
-
recommendedFix = "",
|
|
40
|
-
confidence = null,
|
|
41
|
-
tool = "",
|
|
42
|
-
persona = "ai-governance",
|
|
43
|
-
} = {}) {
|
|
44
|
-
const normalizedSeverity = SEVERITIES.includes(String(severity || "").toUpperCase())
|
|
45
|
-
? String(severity).toUpperCase()
|
|
46
|
-
: "P2";
|
|
47
|
-
return {
|
|
48
|
-
persona,
|
|
49
|
-
tool: String(tool || "").trim(),
|
|
50
|
-
kind: String(kind || "").trim() || "ai-governance",
|
|
51
|
-
severity: normalizedSeverity,
|
|
52
|
-
file: toPosix(file || ""),
|
|
53
|
-
line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
|
|
54
|
-
evidence: String(evidence || "").trim().slice(0, 400),
|
|
55
|
-
rootCause: String(rootCause || "").trim(),
|
|
56
|
-
recommendedFix: String(recommendedFix || "").trim(),
|
|
57
|
-
confidence:
|
|
58
|
-
confidence === null || confidence === undefined
|
|
59
|
-
? null
|
|
60
|
-
: Math.max(0, Math.min(1, Number(confidence) || 0)),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function readIgnorePatterns(filePath) {
|
|
65
|
-
try {
|
|
66
|
-
const raw = await fsp.readFile(filePath, "utf-8");
|
|
67
|
-
return String(raw || "")
|
|
68
|
-
.split(/\r?\n/)
|
|
69
|
-
.map((line) => line.trim())
|
|
70
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function createIgnoreMatcher(rootPath) {
|
|
80
|
-
const matcher = ignore();
|
|
81
|
-
const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
|
|
82
|
-
const sentinel = await readIgnorePatterns(
|
|
83
|
-
path.join(rootPath, ".sentinelayerignore")
|
|
84
|
-
);
|
|
85
|
-
matcher.add([...gitignore, ...sentinel]);
|
|
86
|
-
return (relativePath, isDirectory) => {
|
|
87
|
-
const normalized = toPosix(relativePath);
|
|
88
|
-
if (!normalized) {
|
|
89
|
-
return false;
|
|
90
|
-
}
|
|
91
|
-
const candidate = isDirectory ? `${normalized}/` : normalized;
|
|
92
|
-
return matcher.ignores(candidate);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function findLineMatches(content, pattern) {
|
|
97
|
-
const text = String(content || "");
|
|
98
|
-
const global = new RegExp(
|
|
99
|
-
pattern.source,
|
|
100
|
-
pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
|
|
101
|
-
);
|
|
102
|
-
const matches = [];
|
|
103
|
-
let match;
|
|
104
|
-
while ((match = global.exec(text)) !== null) {
|
|
105
|
-
const lineIndex = text.slice(0, match.index).split(/\r?\n/).length;
|
|
106
|
-
matches.push({ index: match.index, line: lineIndex, match: match[0] });
|
|
107
|
-
}
|
|
108
|
-
return matches;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function getLineContent(content, line) {
|
|
112
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
113
|
-
return (lines[Math.max(0, (Number(line) || 1) - 1)] || "").trim();
|
|
3
|
+
import {
|
|
4
|
+
createFinding as createDomainFinding,
|
|
5
|
+
findLineMatches,
|
|
6
|
+
getLineContent,
|
|
7
|
+
toPosix,
|
|
8
|
+
walkRepoFiles as walkDomainRepoFiles,
|
|
9
|
+
} from "../../shared-tools/domain-base.js";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PERSONA = "ai-governance";
|
|
12
|
+
|
|
13
|
+
export { findLineMatches, getLineContent, toPosix };
|
|
14
|
+
|
|
15
|
+
export function createFinding(options = {}) {
|
|
16
|
+
return createDomainFinding(options, {
|
|
17
|
+
defaultPersona: DEFAULT_PERSONA,
|
|
18
|
+
defaultKind: DEFAULT_PERSONA,
|
|
19
|
+
});
|
|
114
20
|
}
|
|
115
21
|
|
|
116
|
-
export async function* walkRepoFiles({
|
|
117
|
-
|
|
118
|
-
extensions = new Set(),
|
|
119
|
-
maxFileSize = MAX_FILE_SIZE_BYTES,
|
|
120
|
-
} = {}) {
|
|
121
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
122
|
-
const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
|
|
123
|
-
const wantedExtensions =
|
|
124
|
-
extensions instanceof Set
|
|
125
|
-
? extensions
|
|
126
|
-
: new Set(Array.isArray(extensions) ? extensions : []);
|
|
127
|
-
const stack = [resolvedRoot];
|
|
128
|
-
while (stack.length > 0) {
|
|
129
|
-
const current = stack.pop();
|
|
130
|
-
let entries = [];
|
|
131
|
-
try {
|
|
132
|
-
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
133
|
-
} catch {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
for (const entry of entries) {
|
|
137
|
-
const fullPath = path.join(current, entry.name);
|
|
138
|
-
const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
|
|
139
|
-
if (entry.isDirectory()) {
|
|
140
|
-
if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
if (ignoreMatcher(relativePath, true)) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
stack.push(fullPath);
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (!entry.isFile()) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (ignoreMatcher(relativePath, false)) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
156
|
-
if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
let stat = null;
|
|
160
|
-
try {
|
|
161
|
-
stat = await fsp.stat(fullPath);
|
|
162
|
-
} catch {
|
|
163
|
-
stat = null;
|
|
164
|
-
}
|
|
165
|
-
if (!stat || stat.size > maxFileSize) {
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
yield { fullPath, relativePath, stat };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
22
|
+
export async function* walkRepoFiles(options = {}) {
|
|
23
|
+
yield* walkDomainRepoFiles(options, { entryMetadata: "stat" });
|
|
171
24
|
}
|
|
@@ -1,189 +1,36 @@
|
|
|
1
1
|
// Shared helpers for Maya's (backend) domain tools (#A14).
|
|
2
|
-
// Mirrors src/agents/security/tools/base.js but with persona defaulting to
|
|
3
|
-
// "backend". Intentional per-persona copy — a later refactor (#A25+) can
|
|
4
|
-
// hoist this to src/agents/shared-tools/ once the 12 persona modules are
|
|
5
|
-
// shipped and the common surface is stable.
|
|
6
2
|
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
return "P2";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createFinding({
|
|
45
|
-
severity,
|
|
46
|
-
kind,
|
|
47
|
-
file,
|
|
48
|
-
line = 0,
|
|
49
|
-
evidence = "",
|
|
50
|
-
rootCause = "",
|
|
51
|
-
recommendedFix = "",
|
|
52
|
-
confidence = null,
|
|
53
|
-
tool = "",
|
|
54
|
-
persona = "backend",
|
|
55
|
-
} = {}) {
|
|
56
|
-
return {
|
|
57
|
-
persona,
|
|
58
|
-
tool: String(tool || "").trim(),
|
|
59
|
-
kind: String(kind || "").trim() || "backend",
|
|
60
|
-
severity: normalizeSeverity(severity),
|
|
61
|
-
file: toPosix(file || ""),
|
|
62
|
-
line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
|
|
63
|
-
evidence: String(evidence || "").trim().slice(0, 400),
|
|
64
|
-
rootCause: String(rootCause || "").trim(),
|
|
65
|
-
recommendedFix: String(recommendedFix || "").trim(),
|
|
66
|
-
confidence:
|
|
67
|
-
confidence === null || confidence === undefined
|
|
68
|
-
? null
|
|
69
|
-
: Math.max(0, Math.min(1, Number(confidence) || 0)),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function readIgnorePatterns(filePath) {
|
|
74
|
-
try {
|
|
75
|
-
const raw = await fsp.readFile(filePath, "utf-8");
|
|
76
|
-
return String(raw || "")
|
|
77
|
-
.split(/\r?\n/)
|
|
78
|
-
.map((line) => line.trim())
|
|
79
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
80
|
-
} catch (err) {
|
|
81
|
-
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
throw err;
|
|
85
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_IGNORED_DIRS,
|
|
5
|
+
MAX_FILE_SIZE_BYTES,
|
|
6
|
+
SEVERITIES,
|
|
7
|
+
createFinding as createDomainFinding,
|
|
8
|
+
findLineMatches,
|
|
9
|
+
getLineContent,
|
|
10
|
+
normalizeSeverity,
|
|
11
|
+
toPosix,
|
|
12
|
+
walkRepoFiles as walkDomainRepoFiles,
|
|
13
|
+
} from "../../shared-tools/domain-base.js";
|
|
14
|
+
|
|
15
|
+
const DEFAULT_PERSONA = "backend";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
DEFAULT_IGNORED_DIRS,
|
|
19
|
+
MAX_FILE_SIZE_BYTES,
|
|
20
|
+
SEVERITIES,
|
|
21
|
+
findLineMatches,
|
|
22
|
+
getLineContent,
|
|
23
|
+
normalizeSeverity,
|
|
24
|
+
toPosix,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function createFinding(options = {}) {
|
|
28
|
+
return createDomainFinding(options, {
|
|
29
|
+
defaultPersona: DEFAULT_PERSONA,
|
|
30
|
+
defaultKind: DEFAULT_PERSONA,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function* walkRepoFiles(options = {}) {
|
|
35
|
+
yield* walkDomainRepoFiles(options);
|
|
86
36
|
}
|
|
87
|
-
|
|
88
|
-
async function createIgnoreMatcher(rootPath) {
|
|
89
|
-
const matcher = ignore();
|
|
90
|
-
const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
|
|
91
|
-
const sentinel = await readIgnorePatterns(
|
|
92
|
-
path.join(rootPath, ".sentinelayerignore")
|
|
93
|
-
);
|
|
94
|
-
matcher.add([...gitignore, ...sentinel]);
|
|
95
|
-
return (relativePath, isDirectory) => {
|
|
96
|
-
const normalized = toPosix(relativePath);
|
|
97
|
-
if (!normalized) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const candidate = isDirectory ? `${normalized}/` : normalized;
|
|
101
|
-
return matcher.ignores(candidate);
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function* walkRepoFiles({
|
|
106
|
-
rootPath = process.cwd(),
|
|
107
|
-
extensions = new Set(),
|
|
108
|
-
maxFileSize = MAX_FILE_SIZE_BYTES,
|
|
109
|
-
} = {}) {
|
|
110
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
111
|
-
const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
|
|
112
|
-
const wantedExtensions =
|
|
113
|
-
extensions instanceof Set
|
|
114
|
-
? extensions
|
|
115
|
-
: new Set(Array.isArray(extensions) ? extensions : []);
|
|
116
|
-
const stack = [resolvedRoot];
|
|
117
|
-
while (stack.length > 0) {
|
|
118
|
-
const current = stack.pop();
|
|
119
|
-
let entries = [];
|
|
120
|
-
try {
|
|
121
|
-
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
122
|
-
} catch {
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
for (const entry of entries) {
|
|
126
|
-
const fullPath = path.join(current, entry.name);
|
|
127
|
-
const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
|
|
128
|
-
if (entry.isDirectory()) {
|
|
129
|
-
if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
if (ignoreMatcher(relativePath, true)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
stack.push(fullPath);
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (!entry.isFile()) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (ignoreMatcher(relativePath, false)) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
145
|
-
if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
let stat = null;
|
|
149
|
-
try {
|
|
150
|
-
stat = await fsp.stat(fullPath);
|
|
151
|
-
} catch {
|
|
152
|
-
stat = null;
|
|
153
|
-
}
|
|
154
|
-
if (!stat || stat.size > maxFileSize) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
yield { fullPath, relativePath };
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function findLineMatches(content, pattern) {
|
|
163
|
-
const text = String(content || "");
|
|
164
|
-
if (!pattern) {
|
|
165
|
-
return [];
|
|
166
|
-
}
|
|
167
|
-
const global = new RegExp(
|
|
168
|
-
pattern.source,
|
|
169
|
-
pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
|
|
170
|
-
);
|
|
171
|
-
const matches = [];
|
|
172
|
-
let match;
|
|
173
|
-
while ((match = global.exec(text)) !== null) {
|
|
174
|
-
const lineIndex = text.slice(0, match.index).split(/\r?\n/).length;
|
|
175
|
-
matches.push({
|
|
176
|
-
index: match.index,
|
|
177
|
-
line: lineIndex,
|
|
178
|
-
match: match[0],
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
return matches;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function getLineContent(content, line) {
|
|
185
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
186
|
-
return (lines[Math.max(0, (Number(line) || 1) - 1)] || "").trim();
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export { DEFAULT_IGNORED_DIRS, MAX_FILE_SIZE_BYTES, SEVERITIES };
|
|
@@ -1,159 +1,32 @@
|
|
|
1
1
|
// Shared helpers for Ethan's (code-quality) domain tools (#A16).
|
|
2
|
-
// Same per-persona base pattern as security / backend / testing.
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export function toPosix(value) {
|
|
30
|
-
return String(value || "").replace(/\\/g, "/");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function normalizeSeverity(value) {
|
|
34
|
-
const normalized = String(value || "").trim().toUpperCase();
|
|
35
|
-
if (SEVERITIES.includes(normalized)) {
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
return "P2";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function createFinding({
|
|
42
|
-
severity,
|
|
43
|
-
kind,
|
|
44
|
-
file,
|
|
45
|
-
line = 0,
|
|
46
|
-
evidence = "",
|
|
47
|
-
rootCause = "",
|
|
48
|
-
recommendedFix = "",
|
|
49
|
-
confidence = null,
|
|
50
|
-
tool = "",
|
|
51
|
-
persona = "code-quality",
|
|
52
|
-
} = {}) {
|
|
53
|
-
return {
|
|
54
|
-
persona,
|
|
55
|
-
tool: String(tool || "").trim(),
|
|
56
|
-
kind: String(kind || "").trim() || "code-quality",
|
|
57
|
-
severity: normalizeSeverity(severity),
|
|
58
|
-
file: toPosix(file || ""),
|
|
59
|
-
line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
|
|
60
|
-
evidence: String(evidence || "").trim().slice(0, 400),
|
|
61
|
-
rootCause: String(rootCause || "").trim(),
|
|
62
|
-
recommendedFix: String(recommendedFix || "").trim(),
|
|
63
|
-
confidence:
|
|
64
|
-
confidence === null || confidence === undefined
|
|
65
|
-
? null
|
|
66
|
-
: Math.max(0, Math.min(1, Number(confidence) || 0)),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function readIgnorePatterns(filePath) {
|
|
71
|
-
try {
|
|
72
|
-
const raw = await fsp.readFile(filePath, "utf-8");
|
|
73
|
-
return String(raw || "")
|
|
74
|
-
.split(/\r?\n/)
|
|
75
|
-
.map((line) => line.trim())
|
|
76
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
77
|
-
} catch (err) {
|
|
78
|
-
if (err && typeof err === "object" && err.code === "ENOENT") {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
throw err;
|
|
82
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_IGNORED_DIRS,
|
|
5
|
+
MAX_FILE_SIZE_BYTES,
|
|
6
|
+
SEVERITIES,
|
|
7
|
+
createFinding as createDomainFinding,
|
|
8
|
+
normalizeSeverity,
|
|
9
|
+
toPosix,
|
|
10
|
+
walkRepoFiles as walkDomainRepoFiles,
|
|
11
|
+
} from "../../shared-tools/domain-base.js";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PERSONA = "code-quality";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
DEFAULT_IGNORED_DIRS,
|
|
17
|
+
MAX_FILE_SIZE_BYTES,
|
|
18
|
+
SEVERITIES,
|
|
19
|
+
normalizeSeverity,
|
|
20
|
+
toPosix,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createFinding(options = {}) {
|
|
24
|
+
return createDomainFinding(options, {
|
|
25
|
+
defaultPersona: DEFAULT_PERSONA,
|
|
26
|
+
defaultKind: DEFAULT_PERSONA,
|
|
27
|
+
});
|
|
83
28
|
}
|
|
84
29
|
|
|
85
|
-
async function
|
|
86
|
-
|
|
87
|
-
const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
|
|
88
|
-
const sentinel = await readIgnorePatterns(
|
|
89
|
-
path.join(rootPath, ".sentinelayerignore")
|
|
90
|
-
);
|
|
91
|
-
matcher.add([...gitignore, ...sentinel]);
|
|
92
|
-
return (relativePath, isDirectory) => {
|
|
93
|
-
const normalized = toPosix(relativePath);
|
|
94
|
-
if (!normalized) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
const candidate = isDirectory ? `${normalized}/` : normalized;
|
|
98
|
-
return matcher.ignores(candidate);
|
|
99
|
-
};
|
|
30
|
+
export async function* walkRepoFiles(options = {}) {
|
|
31
|
+
yield* walkDomainRepoFiles(options);
|
|
100
32
|
}
|
|
101
|
-
|
|
102
|
-
export async function* walkRepoFiles({
|
|
103
|
-
rootPath = process.cwd(),
|
|
104
|
-
extensions = new Set(),
|
|
105
|
-
maxFileSize = MAX_FILE_SIZE_BYTES,
|
|
106
|
-
} = {}) {
|
|
107
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
108
|
-
const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
|
|
109
|
-
const wantedExtensions =
|
|
110
|
-
extensions instanceof Set
|
|
111
|
-
? extensions
|
|
112
|
-
: new Set(Array.isArray(extensions) ? extensions : []);
|
|
113
|
-
const stack = [resolvedRoot];
|
|
114
|
-
while (stack.length > 0) {
|
|
115
|
-
const current = stack.pop();
|
|
116
|
-
let entries = [];
|
|
117
|
-
try {
|
|
118
|
-
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
119
|
-
} catch {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
for (const entry of entries) {
|
|
123
|
-
const fullPath = path.join(current, entry.name);
|
|
124
|
-
const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
|
|
125
|
-
if (entry.isDirectory()) {
|
|
126
|
-
if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (ignoreMatcher(relativePath, true)) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
stack.push(fullPath);
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
if (!entry.isFile()) {
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (ignoreMatcher(relativePath, false)) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
142
|
-
if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
let stat = null;
|
|
146
|
-
try {
|
|
147
|
-
stat = await fsp.stat(fullPath);
|
|
148
|
-
} catch {
|
|
149
|
-
stat = null;
|
|
150
|
-
}
|
|
151
|
-
if (!stat || stat.size > maxFileSize) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
yield { fullPath, relativePath };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export { DEFAULT_IGNORED_DIRS, MAX_FILE_SIZE_BYTES, SEVERITIES };
|