sentinelayer-cli 0.39.2 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -3
  3. package/src/agents/ai-governance/tools/base.js +19 -166
  4. package/src/agents/backend/tools/base.js +33 -186
  5. package/src/agents/code-quality/tools/base.js +27 -154
  6. package/src/agents/data-layer/tools/base.js +35 -180
  7. package/src/agents/documentation/tools/base.js +18 -146
  8. package/src/agents/infrastructure/tools/base.js +17 -164
  9. package/src/agents/observability/tools/base.js +35 -180
  10. package/src/agents/performance/tools/base.js +35 -205
  11. package/src/agents/release/tools/base.js +35 -180
  12. package/src/agents/reliability/tools/base.js +35 -180
  13. package/src/agents/security/tools/base.js +33 -187
  14. package/src/agents/shared-tools/domain-base.js +246 -0
  15. package/src/agents/supply-chain/tools/base.js +15 -144
  16. package/src/agents/testing/tools/base.js +33 -184
  17. package/src/cli.js +5 -0
  18. package/src/commands/mcp.js +1 -1
  19. package/src/commands/ring-owner.js +58 -0
  20. package/src/commands/session.js +703 -263
  21. package/src/engram/governance.js +67 -0
  22. package/src/engram/index.js +64 -0
  23. package/src/engram/namespace.js +89 -0
  24. package/src/engram/observations.js +34 -0
  25. package/src/engram/sla.js +104 -0
  26. package/src/engram/store.js +115 -0
  27. package/src/engram/tools.js +146 -0
  28. package/src/engram/trust.js +105 -0
  29. package/src/legacy-cli.js +6 -3
  30. package/src/mcp/registry.js +27 -10
  31. package/src/mcp/session-stdio-server.js +209 -18
  32. package/src/mcp/smoke.js +9 -8
  33. package/src/pocket/ring-owner.js +97 -0
  34. package/src/review/local-review.js +9 -5
  35. package/src/review/omargate-orchestrator.js +111 -14
  36. package/src/review/spec-binding.js +16 -1
  37. package/src/scan/generator.js +1 -1
  38. package/src/session/agent-registry.js +22 -63
  39. package/src/session/checkpoints.js +194 -0
  40. package/src/session/coordination-guidance.js +6 -6
  41. package/src/session/daemon.js +19 -124
  42. package/src/session/file-lease-integrations.js +1300 -0
  43. package/src/session/file-locks.js +967 -464
  44. package/src/session/listener.js +243 -20
  45. package/src/session/listeners.js +71 -80
  46. package/src/session/paths.js +2 -0
  47. package/src/session/recall/embedder.js +181 -0
  48. package/src/session/recall/entities.js +0 -0
  49. package/src/session/recall/index-build.js +38 -0
  50. package/src/session/recall/index-core.js +108 -0
  51. package/src/session/recall/index.js +131 -0
  52. package/src/session/recall/needle-eval.js +279 -0
  53. package/src/session/recall/observation-core.js +183 -0
  54. package/src/session/recall/observations.js +21 -0
  55. package/src/session/recall/provenance.js +153 -0
  56. package/src/session/recall/retrieve.js +374 -0
  57. package/src/session/recall/text.js +0 -0
  58. package/src/session/recall/token-stats.js +81 -0
  59. package/src/session/recap.js +29 -23
  60. package/src/session/remote-hydrate.js +5 -1
  61. package/src/session/sync.js +661 -11
  62. package/src/session/wake/pump.js +8 -3
@@ -1,181 +1,36 @@
1
- // Shared helpers for Linh's (data-layer) domain tools (#A17).
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 normalizeSeverity(value) {
33
- const normalized = String(value || "").trim().toUpperCase();
34
- if (SEVERITIES.includes(normalized)) {
35
- return normalized;
36
- }
37
- return "P2";
38
- }
39
-
40
- export function createFinding({
41
- severity,
42
- kind,
43
- file,
44
- line = 0,
45
- evidence = "",
46
- rootCause = "",
47
- recommendedFix = "",
48
- confidence = null,
49
- tool = "",
50
- persona = "data-layer",
51
- } = {}) {
52
- return {
53
- persona,
54
- tool: String(tool || "").trim(),
55
- kind: String(kind || "").trim() || "data-layer",
56
- severity: normalizeSeverity(severity),
57
- file: toPosix(file || ""),
58
- line: Number.isFinite(Number(line)) ? Math.max(0, Math.floor(Number(line))) : 0,
59
- evidence: String(evidence || "").trim().slice(0, 400),
60
- rootCause: String(rootCause || "").trim(),
61
- recommendedFix: String(recommendedFix || "").trim(),
62
- confidence:
63
- confidence === null || confidence === undefined
64
- ? null
65
- : Math.max(0, Math.min(1, Number(confidence) || 0)),
66
- };
67
- }
68
-
69
- async function readIgnorePatterns(filePath) {
70
- try {
71
- const raw = await fsp.readFile(filePath, "utf-8");
72
- return String(raw || "")
73
- .split(/\r?\n/)
74
- .map((line) => line.trim())
75
- .filter((line) => line && !line.startsWith("#"));
76
- } catch (err) {
77
- if (err && typeof err === "object" && err.code === "ENOENT") {
78
- return [];
79
- }
80
- throw err;
81
- }
1
+ // Shared helpers for Imani's (data-layer) domain tools (#A17).
2
+
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 = "data-layer";
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);
82
36
  }
83
-
84
- async function createIgnoreMatcher(rootPath) {
85
- const matcher = ignore();
86
- const gitignore = await readIgnorePatterns(path.join(rootPath, ".gitignore"));
87
- const sentinel = await readIgnorePatterns(
88
- path.join(rootPath, ".sentinelayerignore")
89
- );
90
- matcher.add([...gitignore, ...sentinel]);
91
- return (relativePath, isDirectory) => {
92
- const normalized = toPosix(relativePath);
93
- if (!normalized) {
94
- return false;
95
- }
96
- const candidate = isDirectory ? `${normalized}/` : normalized;
97
- return matcher.ignores(candidate);
98
- };
99
- }
100
-
101
- export async function* walkRepoFiles({
102
- rootPath = process.cwd(),
103
- extensions = new Set(),
104
- maxFileSize = MAX_FILE_SIZE_BYTES,
105
- } = {}) {
106
- const resolvedRoot = path.resolve(rootPath);
107
- const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
108
- const wantedExtensions =
109
- extensions instanceof Set
110
- ? extensions
111
- : new Set(Array.isArray(extensions) ? extensions : []);
112
- const stack = [resolvedRoot];
113
- while (stack.length > 0) {
114
- const current = stack.pop();
115
- let entries = [];
116
- try {
117
- entries = await fsp.readdir(current, { withFileTypes: true });
118
- } catch {
119
- continue;
120
- }
121
- for (const entry of entries) {
122
- const fullPath = path.join(current, entry.name);
123
- const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
124
- if (entry.isDirectory()) {
125
- if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
126
- continue;
127
- }
128
- if (ignoreMatcher(relativePath, true)) {
129
- continue;
130
- }
131
- stack.push(fullPath);
132
- continue;
133
- }
134
- if (!entry.isFile()) {
135
- continue;
136
- }
137
- if (ignoreMatcher(relativePath, false)) {
138
- continue;
139
- }
140
- const ext = path.extname(entry.name).toLowerCase();
141
- if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
142
- continue;
143
- }
144
- let stat = null;
145
- try {
146
- stat = await fsp.stat(fullPath);
147
- } catch {
148
- stat = null;
149
- }
150
- if (!stat || stat.size > maxFileSize) {
151
- continue;
152
- }
153
- yield { fullPath, relativePath };
154
- }
155
- }
156
- }
157
-
158
- export function findLineMatches(content, pattern) {
159
- const text = String(content || "");
160
- if (!pattern) {
161
- return [];
162
- }
163
- const global = new RegExp(
164
- pattern.source,
165
- pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
166
- );
167
- const matches = [];
168
- let match;
169
- while ((match = global.exec(text)) !== null) {
170
- const lineIndex = text.slice(0, match.index).split(/\r?\n/).length;
171
- matches.push({ index: match.index, line: lineIndex, match: match[0] });
172
- }
173
- return matches;
174
- }
175
-
176
- export function getLineContent(content, line) {
177
- const lines = String(content || "").split(/\r?\n/);
178
- return (lines[Math.max(0, (Number(line) || 1) - 1)] || "").trim();
179
- }
180
-
181
- export { DEFAULT_IGNORED_DIRS, MAX_FILE_SIZE_BYTES, SEVERITIES };
@@ -1,151 +1,23 @@
1
1
  // Shared helpers for Samir's (documentation) domain tools (#A23).
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 = "documentation",
43
- } = {}) {
44
- const normalizedSeverity = SEVERITIES.includes(String(severity || "").toUpperCase())
45
- ? String(severity).toUpperCase()
46
- : "P3";
47
- return {
48
- persona,
49
- tool: String(tool || "").trim(),
50
- kind: String(kind || "").trim() || "documentation",
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
- };
3
+ import {
4
+ createFinding as createDomainFinding,
5
+ toPosix,
6
+ walkRepoFiles as walkDomainRepoFiles,
7
+ } from "../../shared-tools/domain-base.js";
8
+
9
+ const DEFAULT_PERSONA = "documentation";
10
+
11
+ export { toPosix };
12
+
13
+ export function createFinding(options = {}) {
14
+ return createDomainFinding(options, {
15
+ defaultPersona: DEFAULT_PERSONA,
16
+ defaultKind: DEFAULT_PERSONA,
17
+ severityFallback: "P3",
18
+ });
94
19
  }
95
20
 
96
- export async function* walkRepoFiles({
97
- rootPath = process.cwd(),
98
- extensions = new Set(),
99
- maxFileSize = MAX_FILE_SIZE_BYTES,
100
- } = {}) {
101
- const resolvedRoot = path.resolve(rootPath);
102
- const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
103
- const wantedExtensions =
104
- extensions instanceof Set
105
- ? extensions
106
- : new Set(Array.isArray(extensions) ? extensions : []);
107
- const stack = [resolvedRoot];
108
- while (stack.length > 0) {
109
- const current = stack.pop();
110
- let entries = [];
111
- try {
112
- entries = await fsp.readdir(current, { withFileTypes: true });
113
- } catch {
114
- continue;
115
- }
116
- for (const entry of entries) {
117
- const fullPath = path.join(current, entry.name);
118
- const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
119
- if (entry.isDirectory()) {
120
- if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
121
- continue;
122
- }
123
- if (ignoreMatcher(relativePath, true)) {
124
- continue;
125
- }
126
- stack.push(fullPath);
127
- continue;
128
- }
129
- if (!entry.isFile()) {
130
- continue;
131
- }
132
- if (ignoreMatcher(relativePath, false)) {
133
- continue;
134
- }
135
- const ext = path.extname(entry.name).toLowerCase();
136
- if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
137
- continue;
138
- }
139
- let stat = null;
140
- try {
141
- stat = await fsp.stat(fullPath);
142
- } catch {
143
- stat = null;
144
- }
145
- if (!stat || stat.size > maxFileSize) {
146
- continue;
147
- }
148
- yield { fullPath, relativePath, stat };
149
- }
150
- }
21
+ export async function* walkRepoFiles(options = {}) {
22
+ yield* walkDomainRepoFiles(options, { entryMetadata: "stat" });
151
23
  }
@@ -1,171 +1,24 @@
1
- // Shared helpers for Kat's (infrastructure) domain tools (#A21).
1
+ // Shared helpers for Noam's (infrastructure) domain tools (#A19).
2
2
 
3
- import fsp from "node:fs/promises";
4
- import path from "node:path";
5
- import process from "node:process";
3
+ import {
4
+ createFinding as createDomainFinding,
5
+ findLineMatches,
6
+ getLineContent,
7
+ toPosix,
8
+ walkRepoFiles as walkDomainRepoFiles,
9
+ } from "../../shared-tools/domain-base.js";
6
10
 
7
- import ignore from "ignore";
11
+ const DEFAULT_PERSONA = "infrastructure";
8
12
 
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"]);
13
+ export { findLineMatches, getLineContent, toPosix };
27
14
 
28
- export function toPosix(value) {
29
- return String(value || "").replace(/\\/g, "/");
15
+ export function createFinding(options = {}) {
16
+ return createDomainFinding(options, {
17
+ defaultPersona: DEFAULT_PERSONA,
18
+ defaultKind: DEFAULT_PERSONA,
19
+ });
30
20
  }
31
21
 
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 = "infrastructure",
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() || "infrastructure",
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 async function* walkRepoFiles({
97
- rootPath = process.cwd(),
98
- extensions = new Set(),
99
- maxFileSize = MAX_FILE_SIZE_BYTES,
100
- } = {}) {
101
- const resolvedRoot = path.resolve(rootPath);
102
- const ignoreMatcher = await createIgnoreMatcher(resolvedRoot);
103
- const wantedExtensions =
104
- extensions instanceof Set
105
- ? extensions
106
- : new Set(Array.isArray(extensions) ? extensions : []);
107
- const stack = [resolvedRoot];
108
- while (stack.length > 0) {
109
- const current = stack.pop();
110
- let entries = [];
111
- try {
112
- entries = await fsp.readdir(current, { withFileTypes: true });
113
- } catch {
114
- continue;
115
- }
116
- for (const entry of entries) {
117
- const fullPath = path.join(current, entry.name);
118
- const relativePath = toPosix(path.relative(resolvedRoot, fullPath));
119
- if (entry.isDirectory()) {
120
- if (!relativePath || DEFAULT_IGNORED_DIRS.has(entry.name)) {
121
- continue;
122
- }
123
- if (ignoreMatcher(relativePath, true)) {
124
- continue;
125
- }
126
- stack.push(fullPath);
127
- continue;
128
- }
129
- if (!entry.isFile()) {
130
- continue;
131
- }
132
- if (ignoreMatcher(relativePath, false)) {
133
- continue;
134
- }
135
- const ext = path.extname(entry.name).toLowerCase();
136
- if (wantedExtensions.size > 0 && !wantedExtensions.has(ext) && !wantedExtensions.has("")) {
137
- continue;
138
- }
139
- let stat = null;
140
- try {
141
- stat = await fsp.stat(fullPath);
142
- } catch {
143
- stat = null;
144
- }
145
- if (!stat || stat.size > maxFileSize) {
146
- continue;
147
- }
148
- yield { fullPath, relativePath };
149
- }
150
- }
151
- }
152
-
153
- export function findLineMatches(content, pattern) {
154
- const text = String(content || "");
155
- const global = new RegExp(
156
- pattern.source,
157
- pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`
158
- );
159
- const matches = [];
160
- let match;
161
- while ((match = global.exec(text)) !== null) {
162
- const lineIndex = text.slice(0, match.index).split(/\r?\n/).length;
163
- matches.push({ index: match.index, line: lineIndex, match: match[0] });
164
- }
165
- return matches;
166
- }
167
-
168
- export function getLineContent(content, line) {
169
- const lines = String(content || "").split(/\r?\n/);
170
- return (lines[Math.max(0, (Number(line) || 1) - 1)] || "").trim();
22
+ export async function* walkRepoFiles(options = {}) {
23
+ yield* walkDomainRepoFiles(options);
171
24
  }