sentinelayer-cli 0.1.1 → 0.3.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/README.md +996 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +62 -54
- package/src/agents/jules/config/definition.js +209 -209
- package/src/agents/jules/config/system-prompt.js +175 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +377 -377
- package/src/agents/jules/loop.js +367 -367
- package/src/agents/jules/pulse.js +319 -319
- package/src/agents/jules/stream.js +186 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +308 -308
- package/src/agents/jules/tools/auth-audit.js +226 -222
- package/src/agents/jules/tools/dispatch.js +327 -327
- package/src/agents/jules/tools/file-edit.js +180 -180
- package/src/agents/jules/tools/file-read.js +100 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +168 -168
- package/src/agents/jules/tools/grep.js +228 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +161 -161
- package/src/agents/jules/tools/runtime-audit.js +493 -493
- package/src/agents/jules/tools/shell.js +383 -383
- package/src/ai/aidenid.js +972 -945
- package/src/ai/client.js +508 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/http.js +113 -113
- package/src/auth/service.js +891 -848
- package/src/auth/session-store.js +359 -345
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1337
- package/src/commands/ai/provision-governance.js +1272 -1246
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1166 -1147
- package/src/commands/auth.js +375 -366
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -404
- package/src/commands/omargate.js +15 -15
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +866 -788
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +510 -510
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +626 -626
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +195 -0
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/action-menu.js +132 -0
- package/src/interactive/auto-ingest.js +111 -0
- package/src/interactive/index.js +95 -0
- package/src/interactive/workspace.js +92 -0
- package/src/legacy-cli.js +2548 -2435
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -106
- package/src/review/ai-review.js +669 -669
- package/src/review/local-review.js +1284 -1284
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -0
- package/src/scaffold/templates.js +150 -0
- package/src/scan/generator.js +418 -351
- package/src/scan/gh-secrets.js +107 -0
- package/src/spec/generator.js +519 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +118 -0
- package/src/telemetry/sync.js +190 -0
- package/src/ui/markdown.js +220 -220
|
@@ -1,168 +1,168 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
const MAX_RESULTS = 200;
|
|
5
|
-
const IGNORE_DIRS = new Set([
|
|
6
|
-
".git", "node_modules", ".next", "dist", "build", "coverage",
|
|
7
|
-
".turbo", ".idea", ".vscode", "__pycache__", ".venv", ".cache",
|
|
8
|
-
".parcel-cache", ".svelte-kit", ".nuxt", ".output", ".vercel",
|
|
9
|
-
]);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Fast file pattern matching sorted by modification time (newest first).
|
|
13
|
-
*
|
|
14
|
-
* @param {object} input
|
|
15
|
-
* @param {string} input.pattern - Glob pattern (e.g., "**\/*.tsx", "src/**\/*.js").
|
|
16
|
-
* @param {string} [input.path] - Directory to search (default: cwd).
|
|
17
|
-
* @param {number} [input.limit] - Max results (default: 200).
|
|
18
|
-
* @returns {{ filenames, numFiles, truncated, durationMs }}
|
|
19
|
-
*/
|
|
20
|
-
export function glob(input) {
|
|
21
|
-
if (!input.pattern || typeof input.pattern !== "string") {
|
|
22
|
-
throw new GlobError("pattern is required and must be a non-empty string.");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const searchPath = input.path ? path.resolve(input.path) : process.cwd();
|
|
26
|
-
const limit = input.limit ?? MAX_RESULTS;
|
|
27
|
-
const startMs = Date.now();
|
|
28
|
-
|
|
29
|
-
if (!fs.existsSync(searchPath)) {
|
|
30
|
-
throw new GlobError(`Directory not found: ${searchPath}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const stat = fs.statSync(searchPath);
|
|
34
|
-
if (!stat.isDirectory()) {
|
|
35
|
-
throw new GlobError(`Path is not a directory: ${searchPath}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const matcher = buildMatcher(input.pattern);
|
|
39
|
-
const ignorePatterns = loadIgnorePatterns(searchPath);
|
|
40
|
-
const results = [];
|
|
41
|
-
|
|
42
|
-
walk(searchPath, searchPath, matcher, ignorePatterns, results, limit);
|
|
43
|
-
|
|
44
|
-
// Sort by mtime descending (newest first)
|
|
45
|
-
results.sort((a, b) => b.mtime - a.mtime);
|
|
46
|
-
|
|
47
|
-
const truncated = results.length >= limit;
|
|
48
|
-
const filenames = results.map((r) => r.relativePath);
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
filenames,
|
|
52
|
-
numFiles: filenames.length,
|
|
53
|
-
truncated,
|
|
54
|
-
durationMs: Date.now() - startMs,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function walk(rootPath, currentPath, matcher, ignorePatterns, results, limit) {
|
|
59
|
-
let entries;
|
|
60
|
-
try {
|
|
61
|
-
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
62
|
-
} catch {
|
|
63
|
-
return; // skip unreadable directories
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const entry of entries) {
|
|
67
|
-
if (results.length >= limit) return;
|
|
68
|
-
|
|
69
|
-
const name = entry.name;
|
|
70
|
-
if (IGNORE_DIRS.has(name)) continue;
|
|
71
|
-
|
|
72
|
-
const fullPath = path.join(currentPath, name);
|
|
73
|
-
const relativePath = path.relative(rootPath, fullPath);
|
|
74
|
-
|
|
75
|
-
if (ignorePatterns.some((p) => p(relativePath))) continue;
|
|
76
|
-
|
|
77
|
-
if (entry.isDirectory()) {
|
|
78
|
-
walk(rootPath, fullPath, matcher, ignorePatterns, results, limit);
|
|
79
|
-
} else if (entry.isFile() && matcher(relativePath)) {
|
|
80
|
-
let mtime = 0;
|
|
81
|
-
try {
|
|
82
|
-
mtime = fs.statSync(fullPath).mtimeMs;
|
|
83
|
-
} catch { /* use 0 if stat fails */ }
|
|
84
|
-
results.push({ relativePath, mtime });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Build a filename matcher from a glob pattern.
|
|
91
|
-
* Supports: *.ext, **\/*.ext, *.{ext1,ext2}, prefix*, *suffix
|
|
92
|
-
*/
|
|
93
|
-
function buildMatcher(pattern) {
|
|
94
|
-
// Handle brace expansion: *.{ts,tsx} → ["*.ts", "*.tsx"]
|
|
95
|
-
const expanded = expandBraces(pattern);
|
|
96
|
-
|
|
97
|
-
const matchers = expanded.map((p) => {
|
|
98
|
-
// ** recursive match
|
|
99
|
-
if (p.includes("**/")) {
|
|
100
|
-
const suffix = p.split("**/").pop();
|
|
101
|
-
const suffixMatcher = buildSimpleMatcher(suffix);
|
|
102
|
-
return (filepath) => {
|
|
103
|
-
const basename = path.basename(filepath);
|
|
104
|
-
const segments = filepath.split(path.sep);
|
|
105
|
-
return segments.some((_, i) =>
|
|
106
|
-
suffixMatcher(segments.slice(i).join(path.sep)),
|
|
107
|
-
) || suffixMatcher(basename);
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return buildSimpleMatcher(p);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return (filepath) => matchers.some((m) => m(filepath));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function buildSimpleMatcher(pattern) {
|
|
117
|
-
if (pattern.startsWith("*.")) {
|
|
118
|
-
const ext = pattern.slice(1);
|
|
119
|
-
return (filepath) => filepath.endsWith(ext) || path.basename(filepath).endsWith(ext);
|
|
120
|
-
}
|
|
121
|
-
if (pattern.endsWith("*")) {
|
|
122
|
-
const prefix = pattern.slice(0, -1);
|
|
123
|
-
return (filepath) => filepath.startsWith(prefix) || path.basename(filepath).startsWith(prefix);
|
|
124
|
-
}
|
|
125
|
-
if (pattern.includes("*")) {
|
|
126
|
-
const regex = new RegExp(
|
|
127
|
-
"^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$",
|
|
128
|
-
);
|
|
129
|
-
return (filepath) => regex.test(filepath) || regex.test(path.basename(filepath));
|
|
130
|
-
}
|
|
131
|
-
return (filepath) => filepath === pattern || path.basename(filepath) === pattern;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function expandBraces(pattern) {
|
|
135
|
-
const braceMatch = pattern.match(/\{([^}]+)\}/);
|
|
136
|
-
if (!braceMatch) return [pattern];
|
|
137
|
-
const alternatives = braceMatch[1].split(",");
|
|
138
|
-
return alternatives.map((alt) =>
|
|
139
|
-
pattern.replace(braceMatch[0], alt.trim()),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function loadIgnorePatterns(rootPath) {
|
|
144
|
-
const patterns = [];
|
|
145
|
-
const gitignorePath = path.join(rootPath, ".gitignore");
|
|
146
|
-
const slignorePath = path.join(rootPath, ".sentinelayerignore");
|
|
147
|
-
|
|
148
|
-
for (const ignorePath of [gitignorePath, slignorePath]) {
|
|
149
|
-
try {
|
|
150
|
-
const content = fs.readFileSync(ignorePath, "utf-8");
|
|
151
|
-
for (const line of content.split("\n")) {
|
|
152
|
-
const trimmed = line.trim();
|
|
153
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
154
|
-
const matcher = buildSimpleMatcher(trimmed);
|
|
155
|
-
patterns.push(matcher);
|
|
156
|
-
}
|
|
157
|
-
} catch { /* ignore missing files */ }
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return patterns;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export class GlobError extends Error {
|
|
164
|
-
constructor(message) {
|
|
165
|
-
super(message);
|
|
166
|
-
this.name = "GlobError";
|
|
167
|
-
}
|
|
168
|
-
}
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const MAX_RESULTS = 200;
|
|
5
|
+
const IGNORE_DIRS = new Set([
|
|
6
|
+
".git", "node_modules", ".next", "dist", "build", "coverage",
|
|
7
|
+
".turbo", ".idea", ".vscode", "__pycache__", ".venv", ".cache",
|
|
8
|
+
".parcel-cache", ".svelte-kit", ".nuxt", ".output", ".vercel",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Fast file pattern matching sorted by modification time (newest first).
|
|
13
|
+
*
|
|
14
|
+
* @param {object} input
|
|
15
|
+
* @param {string} input.pattern - Glob pattern (e.g., "**\/*.tsx", "src/**\/*.js").
|
|
16
|
+
* @param {string} [input.path] - Directory to search (default: cwd).
|
|
17
|
+
* @param {number} [input.limit] - Max results (default: 200).
|
|
18
|
+
* @returns {{ filenames, numFiles, truncated, durationMs }}
|
|
19
|
+
*/
|
|
20
|
+
export function glob(input) {
|
|
21
|
+
if (!input.pattern || typeof input.pattern !== "string") {
|
|
22
|
+
throw new GlobError("pattern is required and must be a non-empty string.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const searchPath = input.path ? path.resolve(input.path) : process.cwd();
|
|
26
|
+
const limit = input.limit ?? MAX_RESULTS;
|
|
27
|
+
const startMs = Date.now();
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(searchPath)) {
|
|
30
|
+
throw new GlobError(`Directory not found: ${searchPath}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const stat = fs.statSync(searchPath);
|
|
34
|
+
if (!stat.isDirectory()) {
|
|
35
|
+
throw new GlobError(`Path is not a directory: ${searchPath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const matcher = buildMatcher(input.pattern);
|
|
39
|
+
const ignorePatterns = loadIgnorePatterns(searchPath);
|
|
40
|
+
const results = [];
|
|
41
|
+
|
|
42
|
+
walk(searchPath, searchPath, matcher, ignorePatterns, results, limit);
|
|
43
|
+
|
|
44
|
+
// Sort by mtime descending (newest first)
|
|
45
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
46
|
+
|
|
47
|
+
const truncated = results.length >= limit;
|
|
48
|
+
const filenames = results.map((r) => r.relativePath);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
filenames,
|
|
52
|
+
numFiles: filenames.length,
|
|
53
|
+
truncated,
|
|
54
|
+
durationMs: Date.now() - startMs,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function walk(rootPath, currentPath, matcher, ignorePatterns, results, limit) {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
62
|
+
} catch {
|
|
63
|
+
return; // skip unreadable directories
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (results.length >= limit) return;
|
|
68
|
+
|
|
69
|
+
const name = entry.name;
|
|
70
|
+
if (IGNORE_DIRS.has(name)) continue;
|
|
71
|
+
|
|
72
|
+
const fullPath = path.join(currentPath, name);
|
|
73
|
+
const relativePath = path.relative(rootPath, fullPath);
|
|
74
|
+
|
|
75
|
+
if (ignorePatterns.some((p) => p(relativePath))) continue;
|
|
76
|
+
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
walk(rootPath, fullPath, matcher, ignorePatterns, results, limit);
|
|
79
|
+
} else if (entry.isFile() && matcher(relativePath)) {
|
|
80
|
+
let mtime = 0;
|
|
81
|
+
try {
|
|
82
|
+
mtime = fs.statSync(fullPath).mtimeMs;
|
|
83
|
+
} catch { /* use 0 if stat fails */ }
|
|
84
|
+
results.push({ relativePath, mtime });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build a filename matcher from a glob pattern.
|
|
91
|
+
* Supports: *.ext, **\/*.ext, *.{ext1,ext2}, prefix*, *suffix
|
|
92
|
+
*/
|
|
93
|
+
function buildMatcher(pattern) {
|
|
94
|
+
// Handle brace expansion: *.{ts,tsx} → ["*.ts", "*.tsx"]
|
|
95
|
+
const expanded = expandBraces(pattern);
|
|
96
|
+
|
|
97
|
+
const matchers = expanded.map((p) => {
|
|
98
|
+
// ** recursive match
|
|
99
|
+
if (p.includes("**/")) {
|
|
100
|
+
const suffix = p.split("**/").pop();
|
|
101
|
+
const suffixMatcher = buildSimpleMatcher(suffix);
|
|
102
|
+
return (filepath) => {
|
|
103
|
+
const basename = path.basename(filepath);
|
|
104
|
+
const segments = filepath.split(path.sep);
|
|
105
|
+
return segments.some((_, i) =>
|
|
106
|
+
suffixMatcher(segments.slice(i).join(path.sep)),
|
|
107
|
+
) || suffixMatcher(basename);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return buildSimpleMatcher(p);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return (filepath) => matchers.some((m) => m(filepath));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function buildSimpleMatcher(pattern) {
|
|
117
|
+
if (pattern.startsWith("*.")) {
|
|
118
|
+
const ext = pattern.slice(1);
|
|
119
|
+
return (filepath) => filepath.endsWith(ext) || path.basename(filepath).endsWith(ext);
|
|
120
|
+
}
|
|
121
|
+
if (pattern.endsWith("*")) {
|
|
122
|
+
const prefix = pattern.slice(0, -1);
|
|
123
|
+
return (filepath) => filepath.startsWith(prefix) || path.basename(filepath).startsWith(prefix);
|
|
124
|
+
}
|
|
125
|
+
if (pattern.includes("*")) {
|
|
126
|
+
const regex = new RegExp(
|
|
127
|
+
"^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$",
|
|
128
|
+
);
|
|
129
|
+
return (filepath) => regex.test(filepath) || regex.test(path.basename(filepath));
|
|
130
|
+
}
|
|
131
|
+
return (filepath) => filepath === pattern || path.basename(filepath) === pattern;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function expandBraces(pattern) {
|
|
135
|
+
const braceMatch = pattern.match(/\{([^}]+)\}/);
|
|
136
|
+
if (!braceMatch) return [pattern];
|
|
137
|
+
const alternatives = braceMatch[1].split(",");
|
|
138
|
+
return alternatives.map((alt) =>
|
|
139
|
+
pattern.replace(braceMatch[0], alt.trim()),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function loadIgnorePatterns(rootPath) {
|
|
144
|
+
const patterns = [];
|
|
145
|
+
const gitignorePath = path.join(rootPath, ".gitignore");
|
|
146
|
+
const slignorePath = path.join(rootPath, ".sentinelayerignore");
|
|
147
|
+
|
|
148
|
+
for (const ignorePath of [gitignorePath, slignorePath]) {
|
|
149
|
+
try {
|
|
150
|
+
const content = fs.readFileSync(ignorePath, "utf-8");
|
|
151
|
+
for (const line of content.split("\n")) {
|
|
152
|
+
const trimmed = line.trim();
|
|
153
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
154
|
+
const matcher = buildSimpleMatcher(trimmed);
|
|
155
|
+
patterns.push(matcher);
|
|
156
|
+
}
|
|
157
|
+
} catch { /* ignore missing files */ }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return patterns;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class GlobError extends Error {
|
|
164
|
+
constructor(message) {
|
|
165
|
+
super(message);
|
|
166
|
+
this.name = "GlobError";
|
|
167
|
+
}
|
|
168
|
+
}
|