sentinelayer-cli 0.39.2 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.39.2",
3
+ "version": "0.40.0",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,171 +1,24 @@
1
1
  // Shared helpers for Amina's (ai-governance) domain tools (#A24).
2
2
 
3
- import fsp from "node:fs/promises";
4
- import path from "node:path";
5
- import process from "node:process";
6
-
7
- import ignore from "ignore";
8
-
9
- const DEFAULT_IGNORED_DIRS = new Set([
10
- ".git",
11
- "node_modules",
12
- ".venv",
13
- ".next",
14
- "dist",
15
- "build",
16
- "coverage",
17
- ".sentinelayer",
18
- ".sentinel",
19
- ".turbo",
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
- rootPath = process.cwd(),
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 fsp from "node:fs/promises";
8
- import path from "node:path";
9
- import process from "node:process";
10
-
11
- import ignore from "ignore";
12
-
13
- const DEFAULT_IGNORED_DIRS = new Set([
14
- ".git",
15
- "node_modules",
16
- ".venv",
17
- ".next",
18
- "dist",
19
- "build",
20
- "coverage",
21
- ".sentinelayer",
22
- ".sentinel",
23
- ".turbo",
24
- ".idea",
25
- ".vscode",
26
- "__pycache__",
27
- ".cache",
28
- ]);
29
- const MAX_FILE_SIZE_BYTES = 1024 * 1024;
30
- const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
31
-
32
- export function toPosix(value) {
33
- return String(value || "").replace(/\\/g, "/");
34
- }
35
-
36
- export function normalizeSeverity(value) {
37
- const normalized = String(value || "").trim().toUpperCase();
38
- if (SEVERITIES.includes(normalized)) {
39
- return normalized;
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 fsp from "node:fs/promises";
5
- import path from "node:path";
6
- import process from "node:process";
7
-
8
- import ignore from "ignore";
9
-
10
- const DEFAULT_IGNORED_DIRS = new Set([
11
- ".git",
12
- "node_modules",
13
- ".venv",
14
- ".next",
15
- "dist",
16
- "build",
17
- "coverage",
18
- ".sentinelayer",
19
- ".sentinel",
20
- ".turbo",
21
- ".idea",
22
- ".vscode",
23
- "__pycache__",
24
- ".cache",
25
- ]);
26
- const MAX_FILE_SIZE_BYTES = 1024 * 1024;
27
- const SEVERITIES = Object.freeze(["P0", "P1", "P2", "P3"]);
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 createIgnoreMatcher(rootPath) {
86
- const matcher = ignore();
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 };