@exero1/claudecontext 0.1.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 +286 -0
- package/dist/installer/install.d.ts +12 -0
- package/dist/installer/install.d.ts.map +1 -0
- package/dist/installer/install.js +714 -0
- package/dist/installer/install.js.map +1 -0
- package/dist/src/cache/budget.d.ts +48 -0
- package/dist/src/cache/budget.d.ts.map +1 -0
- package/dist/src/cache/budget.js +55 -0
- package/dist/src/cache/budget.js.map +1 -0
- package/dist/src/cache/compressor.d.ts +21 -0
- package/dist/src/cache/compressor.d.ts.map +1 -0
- package/dist/src/cache/compressor.js +89 -0
- package/dist/src/cache/compressor.js.map +1 -0
- package/dist/src/cache/levels.d.ts +16 -0
- package/dist/src/cache/levels.d.ts.map +1 -0
- package/dist/src/cache/levels.js +41 -0
- package/dist/src/cache/levels.js.map +1 -0
- package/dist/src/cache/manager.d.ts +38 -0
- package/dist/src/cache/manager.d.ts.map +1 -0
- package/dist/src/cache/manager.js +196 -0
- package/dist/src/cache/manager.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +279 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/detection/areas.d.ts +13 -0
- package/dist/src/detection/areas.d.ts.map +1 -0
- package/dist/src/detection/areas.js +96 -0
- package/dist/src/detection/areas.js.map +1 -0
- package/dist/src/detection/task.d.ts +28 -0
- package/dist/src/detection/task.d.ts.map +1 -0
- package/dist/src/detection/task.js +77 -0
- package/dist/src/detection/task.js.map +1 -0
- package/dist/src/gating/gate.d.ts +38 -0
- package/dist/src/gating/gate.d.ts.map +1 -0
- package/dist/src/gating/gate.js +74 -0
- package/dist/src/gating/gate.js.map +1 -0
- package/dist/src/graph/edges.d.ts +41 -0
- package/dist/src/graph/edges.d.ts.map +1 -0
- package/dist/src/graph/edges.js +115 -0
- package/dist/src/graph/edges.js.map +1 -0
- package/dist/src/graph/indexer.d.ts +38 -0
- package/dist/src/graph/indexer.d.ts.map +1 -0
- package/dist/src/graph/indexer.js +228 -0
- package/dist/src/graph/indexer.js.map +1 -0
- package/dist/src/graph/traversal.d.ts +25 -0
- package/dist/src/graph/traversal.d.ts.map +1 -0
- package/dist/src/graph/traversal.js +173 -0
- package/dist/src/graph/traversal.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +82 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/indexing/codebase.d.ts +30 -0
- package/dist/src/indexing/codebase.d.ts.map +1 -0
- package/dist/src/indexing/codebase.js +127 -0
- package/dist/src/indexing/codebase.js.map +1 -0
- package/dist/src/markdown/writer.d.ts +34 -0
- package/dist/src/markdown/writer.d.ts.map +1 -0
- package/dist/src/markdown/writer.js +96 -0
- package/dist/src/markdown/writer.js.map +1 -0
- package/dist/src/server.d.ts +15 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +520 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/storage/db.d.ts +123 -0
- package/dist/src/storage/db.d.ts.map +1 -0
- package/dist/src/storage/db.js +318 -0
- package/dist/src/storage/db.js.map +1 -0
- package/dist/src/utils/glob.d.ts +11 -0
- package/dist/src/utils/glob.d.ts.map +1 -0
- package/dist/src/utils/glob.js +20 -0
- package/dist/src/utils/glob.js.map +1 -0
- package/hooks/post-write.mjs +57 -0
- package/hooks/pre-compact.mjs +44 -0
- package/hooks/pre-tool-use.mjs +87 -0
- package/hooks/session-start.mjs +54 -0
- package/package.json +51 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ClaudeContext CLI — short-lived helper for hooks.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* gate-check <input> Check input against gate rules. Exit 0=allow, 1=warn, 2=deny.
|
|
7
|
+
* ingest-diff <file> [file...] Record file touches in DB + L0.
|
|
8
|
+
* create-edges --type <type> --files <f1,f2,...> Create edge(s) between files.
|
|
9
|
+
* summarize-session Write deterministic session summary to L4 archive.
|
|
10
|
+
* status Print current context status.
|
|
11
|
+
* graph-inspect <file> Show all edges for a file.
|
|
12
|
+
* hydrate Output additionalContext JSON for session-start hook.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, readFileSync, mkdirSync } from 'node:fs';
|
|
15
|
+
import { join, resolve, relative } from 'node:path';
|
|
16
|
+
import { Db } from './storage/db.js';
|
|
17
|
+
import { check, loadRulesFromConfig, prependRules } from './gating/gate.js';
|
|
18
|
+
import { upsertBidirectionalEdge, applyDecayToAll } from './graph/edges.js';
|
|
19
|
+
import { indexFileImports, indexFileSubsystem } from './graph/indexer.js';
|
|
20
|
+
import { appendLog, readLastLines, patchSection, safeRead } from './markdown/writer.js';
|
|
21
|
+
import { buildBundle } from './cache/manager.js';
|
|
22
|
+
import { detectTask } from './detection/task.js';
|
|
23
|
+
import { scoreFile } from './indexing/codebase.js';
|
|
24
|
+
import { L0_MAX_LINES } from './cache/levels.js';
|
|
25
|
+
import { compressL1 } from './cache/compressor.js';
|
|
26
|
+
// CLAUDE_SESSION_ID is provided in hook JSON payload (not env var) in Claude Code v2+
|
|
27
|
+
// so we no longer gate on it here — hooks are only invoked by Claude Code via settings.json
|
|
28
|
+
const projectRoot = resolve(process.env['CLAUDECONTEXT_PROJECT_ROOT'] ?? process.cwd());
|
|
29
|
+
const stateDir = join(projectRoot, '.claudecontext');
|
|
30
|
+
if (!existsSync(stateDir)) {
|
|
31
|
+
mkdirSync(stateDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const dbPath = join(stateDir, 'state.db');
|
|
34
|
+
const l0LogPath = join(stateDir, 'l0.log');
|
|
35
|
+
const areasPath = join(stateDir, 'areas.json');
|
|
36
|
+
let areas = [];
|
|
37
|
+
if (existsSync(areasPath)) {
|
|
38
|
+
try {
|
|
39
|
+
areas = JSON.parse(readFileSync(areasPath, 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
catch { /* ignore */ }
|
|
42
|
+
}
|
|
43
|
+
const db = new Db(dbPath);
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const command = args[0];
|
|
46
|
+
switch (command) {
|
|
47
|
+
// ── gate-check ─────────────────────────────────────────────────────────────
|
|
48
|
+
case 'gate-check': {
|
|
49
|
+
// Extract --rules-file flag
|
|
50
|
+
const rfIdx = args.indexOf('--rules-file');
|
|
51
|
+
const rulesFile = rfIdx !== -1 ? args[rfIdx + 1] : null;
|
|
52
|
+
// Remaining args (skip command name, --rules-file flag, and its value)
|
|
53
|
+
const filteredArgs = args.filter((_, i) => i !== 0 && i !== rfIdx && i !== rfIdx + 1);
|
|
54
|
+
const input = filteredArgs.join(' ');
|
|
55
|
+
if (rulesFile) {
|
|
56
|
+
try {
|
|
57
|
+
const config = JSON.parse(readFileSync(rulesFile, 'utf8'));
|
|
58
|
+
prependRules(loadRulesFromConfig(config));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
process.stderr.write(`ERROR: Could not load rules from ${rulesFile}: ${e}\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const result = check(input);
|
|
66
|
+
if (result.action === 'deny') {
|
|
67
|
+
process.stderr.write(`DENY: ${result.reason}\n`);
|
|
68
|
+
process.exit(2);
|
|
69
|
+
}
|
|
70
|
+
if (result.action === 'warn') {
|
|
71
|
+
process.stderr.write(`WARN: ${result.reason}\n`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
process.exit(0);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
// ── ingest-diff ────────────────────────────────────────────────────────────
|
|
78
|
+
case 'ingest-diff': {
|
|
79
|
+
const files = args.slice(1).map(f => f.startsWith('/') ? f : join(projectRoot, f));
|
|
80
|
+
if (files.length === 0) {
|
|
81
|
+
process.stderr.write('Usage: ingest-diff <file1> [file2...]\n');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const timestamp = new Date().toISOString();
|
|
85
|
+
for (const file of files) {
|
|
86
|
+
db.touchFile(file);
|
|
87
|
+
const fi = db.getFile(file);
|
|
88
|
+
const score = scoreFile(fi?.last_touched ?? Date.now(), fi?.touch_count ?? 1, false);
|
|
89
|
+
db.updateRelevanceScore(file, score);
|
|
90
|
+
indexFileImports(db, file, projectRoot);
|
|
91
|
+
indexFileSubsystem(db, file, areas, projectRoot);
|
|
92
|
+
appendLog(l0LogPath, `[${timestamp}] WRITE ${file} (touch_count=${fi?.touch_count ?? 1})`, L0_MAX_LINES);
|
|
93
|
+
}
|
|
94
|
+
// Create co_modified edges:
|
|
95
|
+
// 1. Between all pairs within this batch (if multiple files passed at once)
|
|
96
|
+
// 2. Between each new file and other files touched recently (same session window = 30 min)
|
|
97
|
+
db.runInTransaction(() => {
|
|
98
|
+
for (const file of files) {
|
|
99
|
+
// Batch pairs
|
|
100
|
+
for (const other of files) {
|
|
101
|
+
if (other !== file) {
|
|
102
|
+
upsertBidirectionalEdge(db, {
|
|
103
|
+
fromNode: file, fromType: 'file',
|
|
104
|
+
toNode: other, toType: 'file',
|
|
105
|
+
edgeType: 'co_modified',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Cross-session pairs: this file ↔ other recently-touched files
|
|
110
|
+
const recentFiles = db.getRecentlyTouchedFiles(30 * 60 * 1000, file);
|
|
111
|
+
for (const recent of recentFiles) {
|
|
112
|
+
upsertBidirectionalEdge(db, {
|
|
113
|
+
fromNode: file, fromType: 'file',
|
|
114
|
+
toNode: recent, toType: 'file',
|
|
115
|
+
edgeType: 'co_modified',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Auto-update L1 "Files touched" — so it stays current without user discipline
|
|
121
|
+
try {
|
|
122
|
+
const info = await detectTask(projectRoot);
|
|
123
|
+
const taskFilePath = join(projectRoot, 'tasks', `${info.id}.md`);
|
|
124
|
+
const taskContent = safeRead(taskFilePath);
|
|
125
|
+
// Bug 4: ensure task exists in DB so context_status shows Tasks > 0
|
|
126
|
+
db.ensureTask(info.id, info.id);
|
|
127
|
+
if (taskContent) {
|
|
128
|
+
// Normalize all paths to relative so absolute and relative forms don't duplicate
|
|
129
|
+
const toRel = (p) => p.startsWith('/') ? relative(projectRoot, p) : p;
|
|
130
|
+
const existing = new Set(taskContent.split('\n')
|
|
131
|
+
.filter(l => l.startsWith('- '))
|
|
132
|
+
.map(l => toRel(l.slice(2).trim())));
|
|
133
|
+
const toAdd = files.map(toRel).filter(f => !existing.has(f));
|
|
134
|
+
if (toAdd.length > 0) {
|
|
135
|
+
const allFiles = [...existing, ...toAdd];
|
|
136
|
+
patchSection(taskFilePath, 'Files touched', allFiles.map(f => `- ${f}`).join('\n'));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Non-fatal — best-effort L1 update
|
|
142
|
+
}
|
|
143
|
+
db.close();
|
|
144
|
+
process.exit(0);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
// ── create-edges ───────────────────────────────────────────────────────────
|
|
148
|
+
case 'create-edges': {
|
|
149
|
+
const typeIdx = args.indexOf('--type');
|
|
150
|
+
const filesIdx = args.indexOf('--files');
|
|
151
|
+
if (typeIdx === -1 || filesIdx === -1) {
|
|
152
|
+
process.stderr.write('Usage: create-edges --type <type> --files <f1,f2,...>\n');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
const edgeType = args[typeIdx + 1];
|
|
156
|
+
const fileList = (args[filesIdx + 1] ?? '').split(',').filter(Boolean);
|
|
157
|
+
if (!edgeType || fileList.length < 2) {
|
|
158
|
+
process.stderr.write('Need --type and at least 2 --files\n');
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
162
|
+
for (let j = i + 1; j < fileList.length; j++) {
|
|
163
|
+
const f1 = fileList[i];
|
|
164
|
+
const f2 = fileList[j];
|
|
165
|
+
if (f1 && f2) {
|
|
166
|
+
upsertBidirectionalEdge(db, {
|
|
167
|
+
fromNode: f1, fromType: 'file',
|
|
168
|
+
toNode: f2, toType: 'file',
|
|
169
|
+
edgeType,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
db.close();
|
|
175
|
+
process.exit(0);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
// ── summarize-session ──────────────────────────────────────────────────────
|
|
179
|
+
case 'summarize-session': {
|
|
180
|
+
const info = await detectTask(projectRoot);
|
|
181
|
+
const l0Lines = readLastLines(l0LogPath, 200);
|
|
182
|
+
const writes = l0Lines.filter(l => / WRITE /.test(l));
|
|
183
|
+
const notes = l0Lines.filter(l => / NOTE /.test(l) || / DECISION /.test(l) || / OBSERVATION /.test(l));
|
|
184
|
+
const errors = l0Lines.filter(l => / ERROR /.test(l));
|
|
185
|
+
const tests = l0Lines.filter(l => / TEST /.test(l));
|
|
186
|
+
const lastTest = tests[tests.length - 1] ?? '';
|
|
187
|
+
const summary = [
|
|
188
|
+
`Session on branch ${info.branch}`,
|
|
189
|
+
`Files touched: ${writes.length}`,
|
|
190
|
+
notes.length > 0 ? `Notes: ${notes.length}` : '',
|
|
191
|
+
tests.length > 0 ? `Tests: ${lastTest.replace(/.*TEST /, '')}` : '',
|
|
192
|
+
errors.length > 0 ? `Errors: ${errors.length}` : '',
|
|
193
|
+
].filter(Boolean).join('. ');
|
|
194
|
+
const archiveId = `${info.id}-${Date.now()}`;
|
|
195
|
+
db.insertArchive({
|
|
196
|
+
id: archiveId,
|
|
197
|
+
task_id: info.id,
|
|
198
|
+
summary,
|
|
199
|
+
created_at: Date.now(),
|
|
200
|
+
source_level: 'L4',
|
|
201
|
+
});
|
|
202
|
+
// Compress L1 task file (trim Files touched + archive old decisions)
|
|
203
|
+
const taskFilePath = join(projectRoot, 'tasks', `${info.id}.md`);
|
|
204
|
+
const compressResult = compressL1(taskFilePath, db);
|
|
205
|
+
if (compressResult.filesCompressed > 0 || compressResult.decisionsArchived > 0) {
|
|
206
|
+
appendLog(l0LogPath, `[${new Date().toISOString()}] L1 COMPRESS files=${compressResult.filesCompressed} decisions=${compressResult.decisionsArchived}`);
|
|
207
|
+
}
|
|
208
|
+
// Apply lazy weight decay + prune stale edges on each pre-compact sweep
|
|
209
|
+
const decayResult = applyDecayToAll(db);
|
|
210
|
+
appendLog(l0LogPath, `[${new Date().toISOString()}] SESSION SUMMARY written to archive: ${archiveId} | decay: ${decayResult.updated} edges updated, ${decayResult.pruned} pruned`);
|
|
211
|
+
db.close();
|
|
212
|
+
process.exit(0);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
// ── status ─────────────────────────────────────────────────────────────────
|
|
216
|
+
case 'status': {
|
|
217
|
+
const info = await detectTask(projectRoot);
|
|
218
|
+
const stats = db.getStats();
|
|
219
|
+
const edgeCounts = db.countEdgesByType();
|
|
220
|
+
const edgeStr = Object.entries(edgeCounts).map(([t, c]) => `${t}=${c}`).join(' ');
|
|
221
|
+
process.stdout.write([
|
|
222
|
+
`Active task: ${info.id} (branch: ${info.branch})`,
|
|
223
|
+
`Files indexed: ${stats.fileCount} | Tasks: ${stats.taskCount} | Archive: ${stats.archiveCount}`,
|
|
224
|
+
`Edges: ${edgeStr || 'none'}`,
|
|
225
|
+
].join('\n') + '\n');
|
|
226
|
+
db.close();
|
|
227
|
+
process.exit(0);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
// ── graph-inspect ──────────────────────────────────────────────────────────
|
|
231
|
+
case 'graph-inspect': {
|
|
232
|
+
const fileArg = args[1];
|
|
233
|
+
if (!fileArg) {
|
|
234
|
+
process.stderr.write('Usage: graph-inspect <file>\n');
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
// Resolve relative paths against projectRoot so both forms work
|
|
238
|
+
const file = fileArg.startsWith('/') ? fileArg : join(projectRoot, fileArg);
|
|
239
|
+
const outgoing = db.getEdgesFrom(file, 50);
|
|
240
|
+
const incoming = db.getEdgesTo(file, 50);
|
|
241
|
+
process.stdout.write(`\nEdges for: ${file}\n`);
|
|
242
|
+
process.stdout.write(`\nOutgoing (${outgoing.length}):\n`);
|
|
243
|
+
for (const e of outgoing) {
|
|
244
|
+
process.stdout.write(` → ${e.to_node} [${e.edge_type}] weight=${e.weight.toFixed(3)} sessions=${e.session_count}\n`);
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`\nIncoming (${incoming.length}):\n`);
|
|
247
|
+
for (const e of incoming) {
|
|
248
|
+
process.stdout.write(` ← ${e.from_node} [${e.edge_type}] weight=${e.weight.toFixed(3)} sessions=${e.session_count}\n`);
|
|
249
|
+
}
|
|
250
|
+
db.close();
|
|
251
|
+
process.exit(0);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
// ── hydrate ────────────────────────────────────────────────────────────────
|
|
255
|
+
case 'hydrate': {
|
|
256
|
+
const info = await detectTask(projectRoot);
|
|
257
|
+
const bundle = buildBundle(db, {
|
|
258
|
+
projectRoot,
|
|
259
|
+
stateDir,
|
|
260
|
+
taskId: info.id,
|
|
261
|
+
taskBranch: info.branch,
|
|
262
|
+
areas,
|
|
263
|
+
depth: 1,
|
|
264
|
+
});
|
|
265
|
+
// Output additionalContext JSON for session-start hook
|
|
266
|
+
const output = JSON.stringify({ additionalContext: bundle.markdown });
|
|
267
|
+
process.stdout.write(output + '\n');
|
|
268
|
+
db.close();
|
|
269
|
+
process.exit(0);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
default: {
|
|
273
|
+
process.stderr.write(`Unknown command: ${command ?? '(none)'}\n`);
|
|
274
|
+
process.stderr.write('Commands: gate-check, ingest-diff, create-edges, summarize-session, status, graph-inspect, hydrate\n');
|
|
275
|
+
db.close();
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AreaDef } from '../graph/indexer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detect subsystem areas by scanning the project source tree.
|
|
4
|
+
*
|
|
5
|
+
* Strategy: for each of src/, lib/, app/ (first one that has results), scan
|
|
6
|
+
* immediate subdirectories. Any subdirectory with ≥3 source files becomes
|
|
7
|
+
* a candidate area with glob `<root>/<dir>/**`.
|
|
8
|
+
*
|
|
9
|
+
* @param projectRoot - absolute path to project root
|
|
10
|
+
* @param files - optional pre-collected relative file list (drives detection without filesystem access, for testing)
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectAreas(projectRoot: string, files?: string[]): AreaDef[];
|
|
13
|
+
//# sourceMappingURL=areas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"areas.d.ts","sourceRoot":"","sources":["../../../src/detection/areas.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAYnD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,CAkE5E"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-detect subsystem areas from directory structure.
|
|
3
|
+
*
|
|
4
|
+
* Scans top-level source directories (src/, lib/, app/) and proposes an AreaDef
|
|
5
|
+
* for each subdirectory that contains ≥3 source files.
|
|
6
|
+
*/
|
|
7
|
+
import { readdirSync, statSync, existsSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { collectSourceFiles } from '../graph/indexer.js';
|
|
10
|
+
const SOURCE_ROOTS = ['src', 'lib', 'app'];
|
|
11
|
+
const SKIP_DIRS = new Set([
|
|
12
|
+
'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',
|
|
13
|
+
'coverage', '.cache', '__pycache__', '.claudecontext',
|
|
14
|
+
'__tests__', 'test', 'tests', 'spec', 'specs',
|
|
15
|
+
]);
|
|
16
|
+
const MIN_FILES = 3;
|
|
17
|
+
/**
|
|
18
|
+
* Detect subsystem areas by scanning the project source tree.
|
|
19
|
+
*
|
|
20
|
+
* Strategy: for each of src/, lib/, app/ (first one that has results), scan
|
|
21
|
+
* immediate subdirectories. Any subdirectory with ≥3 source files becomes
|
|
22
|
+
* a candidate area with glob `<root>/<dir>/**`.
|
|
23
|
+
*
|
|
24
|
+
* @param projectRoot - absolute path to project root
|
|
25
|
+
* @param files - optional pre-collected relative file list (drives detection without filesystem access, for testing)
|
|
26
|
+
*/
|
|
27
|
+
export function detectAreas(projectRoot, files) {
|
|
28
|
+
const areas = [];
|
|
29
|
+
for (const root of SOURCE_ROOTS) {
|
|
30
|
+
if (files !== undefined) {
|
|
31
|
+
// Derive subdirectory names from the provided file list — no filesystem access.
|
|
32
|
+
// Only count files that are in a subdirectory (rest contains '/').
|
|
33
|
+
const subdirFileCounts = new Map();
|
|
34
|
+
for (const f of files) {
|
|
35
|
+
if (!f.startsWith(`${root}/`))
|
|
36
|
+
continue;
|
|
37
|
+
const rest = f.slice(root.length + 1);
|
|
38
|
+
if (!rest.includes('/'))
|
|
39
|
+
continue; // flat file at root level
|
|
40
|
+
const subdir = rest.split('/')[0];
|
|
41
|
+
if (SKIP_DIRS.has(subdir))
|
|
42
|
+
continue;
|
|
43
|
+
subdirFileCounts.set(subdir, (subdirFileCounts.get(subdir) ?? 0) + 1);
|
|
44
|
+
}
|
|
45
|
+
for (const [subdir, count] of subdirFileCounts) {
|
|
46
|
+
if (count >= MIN_FILES) {
|
|
47
|
+
areas.push({
|
|
48
|
+
name: subdir,
|
|
49
|
+
globs: [`${root}/${subdir}/**`],
|
|
50
|
+
docPath: `docs/context/${subdir}.md`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Scan filesystem
|
|
57
|
+
const rootPath = join(projectRoot, root);
|
|
58
|
+
if (!existsSync(rootPath))
|
|
59
|
+
continue;
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = readdirSync(rootPath);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
if (SKIP_DIRS.has(entry))
|
|
69
|
+
continue;
|
|
70
|
+
const fullPath = join(rootPath, entry);
|
|
71
|
+
let stat;
|
|
72
|
+
try {
|
|
73
|
+
stat = statSync(fullPath);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!stat.isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
const dirFiles = collectSourceFiles(fullPath, projectRoot);
|
|
81
|
+
if (dirFiles.length >= MIN_FILES) {
|
|
82
|
+
areas.push({
|
|
83
|
+
name: entry,
|
|
84
|
+
globs: [`${root}/${entry}/**`],
|
|
85
|
+
docPath: `docs/context/${entry}.md`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Stop at the first source root that yields results
|
|
91
|
+
if (areas.length > 0)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return areas;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=areas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"areas.js","sourceRoot":"","sources":["../../../src/detection/areas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO;IACzD,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB;IACrD,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO;CAC9C,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,WAAmB,EAAE,KAAgB;IAC/D,MAAM,KAAK,GAAc,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,gFAAgF;YAChF,mEAAmE;YACnE,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;YACnD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;gBACtB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC;oBAAE,SAAS;gBACxC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBACtC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;oBAAE,SAAS,CAAC,0BAA0B;gBAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;gBACnC,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC;oBAAE,SAAS;gBACpC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACxE,CAAC;YAED,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,gBAAgB,EAAE,CAAC;gBAC/C,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;oBACvB,KAAK,CAAC,IAAI,CAAC;wBACT,IAAI,EAAE,MAAM;wBACZ,KAAK,EAAE,CAAC,GAAG,IAAI,IAAI,MAAM,KAAK,CAAC;wBAC/B,OAAO,EAAE,gBAAgB,MAAM,KAAK;qBACrC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,kBAAkB;YAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACzC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAEpC,IAAI,OAAiB,CAAC;YACtB,IAAI,CAAC;gBACH,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAEnC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACvC,IAAI,IAAI,CAAC;gBACT,IAAI,CAAC;oBACH,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAC5B,CAAC;gBAAC,MAAM,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;oBAAE,SAAS;gBAElC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;gBAC3D,IAAI,QAAQ,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;oBACjC,KAAK,CAAC,IAAI,CAAC;wBACT,IAAI,EAAE,KAAK;wBACX,KAAK,EAAE,CAAC,GAAG,IAAI,IAAI,KAAK,KAAK,CAAC;wBAC9B,OAAO,EAAE,gBAAgB,KAAK,KAAK;qBACpC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,oDAAoD;QACpD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM;IAC9B,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface TaskInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
branch: string;
|
|
4
|
+
isGit: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Convert a git branch name to a filesystem-safe slug.
|
|
8
|
+
* e.g. "feature/add-auth" → "feature-add-auth"
|
|
9
|
+
*/
|
|
10
|
+
export declare function branchToSlug(branch: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Detect the active task from the git branch.
|
|
13
|
+
* Falls back to 'default' for non-git repos or detached HEAD.
|
|
14
|
+
*/
|
|
15
|
+
export declare function detectTask(projectRoot: string): Promise<TaskInfo>;
|
|
16
|
+
/**
|
|
17
|
+
* Get the path to a task's markdown file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function taskFilePath(projectRoot: string, taskId: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Check if a task file exists.
|
|
22
|
+
*/
|
|
23
|
+
export declare function taskFileExists(projectRoot: string, taskId: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Generate default task file content for a new task.
|
|
26
|
+
*/
|
|
27
|
+
export declare function defaultTaskContent(taskId: string, branch: string): string;
|
|
28
|
+
//# sourceMappingURL=task.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../../src/detection/task.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoBvE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAsBzE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { simpleGit } from 'simple-git';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Convert a git branch name to a filesystem-safe slug.
|
|
6
|
+
* e.g. "feature/add-auth" → "feature-add-auth"
|
|
7
|
+
*/
|
|
8
|
+
export function branchToSlug(branch) {
|
|
9
|
+
return branch
|
|
10
|
+
.replace(/\//g, '-')
|
|
11
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
12
|
+
.replace(/-+/g, '-')
|
|
13
|
+
.replace(/^-|-$/g, '');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Detect the active task from the git branch.
|
|
17
|
+
* Falls back to 'default' for non-git repos or detached HEAD.
|
|
18
|
+
*/
|
|
19
|
+
export async function detectTask(projectRoot) {
|
|
20
|
+
try {
|
|
21
|
+
const git = simpleGit(projectRoot);
|
|
22
|
+
const isRepo = await git.checkIsRepo();
|
|
23
|
+
if (!isRepo)
|
|
24
|
+
return { id: 'default', branch: 'default', isGit: false };
|
|
25
|
+
const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
26
|
+
const trimmed = branch.trim();
|
|
27
|
+
// Detached HEAD
|
|
28
|
+
if (trimmed === 'HEAD' || trimmed === '') {
|
|
29
|
+
const sha = await git.revparse(['HEAD']);
|
|
30
|
+
const shortSha = sha.trim().slice(0, 7);
|
|
31
|
+
return { id: `detached-${shortSha}`, branch: `HEAD@${shortSha}`, isGit: true };
|
|
32
|
+
}
|
|
33
|
+
return { id: branchToSlug(trimmed), branch: trimmed, isGit: true };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { id: 'default', branch: 'default', isGit: false };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the path to a task's markdown file.
|
|
41
|
+
*/
|
|
42
|
+
export function taskFilePath(projectRoot, taskId) {
|
|
43
|
+
return join(projectRoot, 'tasks', `${taskId}.md`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Check if a task file exists.
|
|
47
|
+
*/
|
|
48
|
+
export function taskFileExists(projectRoot, taskId) {
|
|
49
|
+
return existsSync(taskFilePath(projectRoot, taskId));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate default task file content for a new task.
|
|
53
|
+
*/
|
|
54
|
+
export function defaultTaskContent(taskId, branch) {
|
|
55
|
+
const now = new Date().toISOString();
|
|
56
|
+
return `# Task: ${taskId}
|
|
57
|
+
**Branch:** ${branch}
|
|
58
|
+
**Created:** ${now}
|
|
59
|
+
**Status:** active
|
|
60
|
+
|
|
61
|
+
## Goal
|
|
62
|
+
[Describe what you're trying to accomplish in this session]
|
|
63
|
+
|
|
64
|
+
## Files touched
|
|
65
|
+
[Files modified in this task]
|
|
66
|
+
|
|
67
|
+
## Open questions
|
|
68
|
+
[Questions or uncertainties to resolve]
|
|
69
|
+
|
|
70
|
+
## Decisions
|
|
71
|
+
[Key decisions made during this task]
|
|
72
|
+
|
|
73
|
+
## Notes
|
|
74
|
+
[Anything else worth remembering]
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../../src/detection/task.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,MAAM;SACV,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC;SAC/B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,WAAmB;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAEvE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAE9B,gBAAgB;QAChB,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACzC,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACxC,OAAO,EAAE,EAAE,EAAE,YAAY,QAAQ,EAAE,EAAE,MAAM,EAAE,QAAQ,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjF,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC5D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,WAAmB,EAAE,MAAc;IAC9D,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,MAAc;IAChE,OAAO,UAAU,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,MAAc;IAC/D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,WAAW,MAAM;cACZ,MAAM;eACL,GAAG;;;;;;;;;;;;;;;;;CAiBjB,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate engine — synchronous regex-rule-based allow/deny/warn decisions.
|
|
3
|
+
* Target: <5ms per check. No async, no I/O.
|
|
4
|
+
*/
|
|
5
|
+
export type GateAction = 'allow' | 'warn' | 'deny';
|
|
6
|
+
export interface GateRule {
|
|
7
|
+
action: GateAction;
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GateResult {
|
|
12
|
+
action: GateAction;
|
|
13
|
+
reason: string;
|
|
14
|
+
matched: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Replace the active rule set (e.g., loaded from areas.json config).
|
|
18
|
+
*/
|
|
19
|
+
export declare function setRules(newRules: GateRule[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* Add rules at the front (highest priority).
|
|
22
|
+
*/
|
|
23
|
+
export declare function prependRules(newRules: GateRule[]): void;
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate a command/content string against the active rules.
|
|
26
|
+
* Returns the first matching rule's action and reason.
|
|
27
|
+
*/
|
|
28
|
+
export declare function check(input: string): GateResult;
|
|
29
|
+
/**
|
|
30
|
+
* Load gate rules from a JSON config (subset of areas.json).
|
|
31
|
+
* Expected format: Array of { action, pattern, reason }
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadRulesFromConfig(config: Array<{
|
|
34
|
+
action: string;
|
|
35
|
+
pattern: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}>): GateRule[];
|
|
38
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/gating/gate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAwCD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAEnD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAEvD;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAO/C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,QAAQ,EAAE,CAMlH"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gate engine — synchronous regex-rule-based allow/deny/warn decisions.
|
|
3
|
+
* Target: <5ms per check. No async, no I/O.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default rules. Rules are evaluated in order; first match wins.
|
|
7
|
+
* deny > warn > allow (by convention, deny rules come first).
|
|
8
|
+
*
|
|
9
|
+
* NOTE: These are best-effort safeguards against obvious mistakes, not a
|
|
10
|
+
* complete security boundary. Determined users or malicious prompts can
|
|
11
|
+
* always bypass regex patterns. Project teams should tune rules via
|
|
12
|
+
* areas.json to match their own risk tolerance.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_RULES = [
|
|
15
|
+
// Hard denies — destructive/irreversible system operations
|
|
16
|
+
{ action: 'deny', pattern: /rm\s+-rf\s+\//, reason: 'Refuses to delete root filesystem' },
|
|
17
|
+
{ action: 'deny', pattern: /rm\s+-rf\s+\*/, reason: 'Refuses to delete everything in current directory' },
|
|
18
|
+
{ action: 'deny', pattern: />\s*\/etc\//, reason: 'Refuses to overwrite system config files' },
|
|
19
|
+
{ action: 'deny', pattern: /^\/etc\//, reason: 'Refuses to write system config files' },
|
|
20
|
+
{ action: 'deny', pattern: /dd\s+if=.*of=\/dev\//, reason: 'Refuses raw disk writes' },
|
|
21
|
+
{ action: 'deny', pattern: /mkfs\./, reason: 'Refuses filesystem formatting' },
|
|
22
|
+
// Fork bomb: classic form and common variants (f(){ f|f& }; f or :(){ :|:& };:)
|
|
23
|
+
{ action: 'deny', pattern: /\w+\s*\(\s*\)\s*\{[^}]*\|\s*\w+\s*&/, reason: 'Fork bomb pattern detected' },
|
|
24
|
+
// Warn — potentially destructive, require user attention
|
|
25
|
+
{ action: 'warn', pattern: /rm\s+-rf/, reason: 'Recursive deletion — confirm this is intentional' },
|
|
26
|
+
{ action: 'warn', pattern: /git\s+reset\s+--hard/, reason: 'Hard reset will discard uncommitted changes' },
|
|
27
|
+
{ action: 'warn', pattern: /git\s+push\s+.*--force/, reason: 'Force push can overwrite remote history' },
|
|
28
|
+
{ action: 'warn', pattern: /DROP\s+TABLE/i, reason: 'SQL DROP TABLE detected' },
|
|
29
|
+
{ action: 'warn', pattern: /DROP\s+DATABASE/i, reason: 'SQL DROP DATABASE detected' },
|
|
30
|
+
{ action: 'warn', pattern: /TRUNCATE\s+TABLE/i, reason: 'SQL TRUNCATE detected' },
|
|
31
|
+
{ action: 'warn', pattern: /chmod\s+777/, reason: 'chmod 777 creates world-writable files' },
|
|
32
|
+
{ action: 'warn', pattern: /sudo\s+rm/, reason: 'Privileged deletion detected' },
|
|
33
|
+
{ action: 'warn', pattern: /\beval\s+["'`$]/, reason: 'eval with dynamic input — confirm this is intentional' },
|
|
34
|
+
{ action: 'warn', pattern: /\bexec\s+[^(]/, reason: 'exec replaces current process — confirm this is intentional' },
|
|
35
|
+
// Allow everything else
|
|
36
|
+
{ action: 'allow', pattern: /.*/, reason: 'No gate rules matched' },
|
|
37
|
+
];
|
|
38
|
+
let rules = [...DEFAULT_RULES];
|
|
39
|
+
/**
|
|
40
|
+
* Replace the active rule set (e.g., loaded from areas.json config).
|
|
41
|
+
*/
|
|
42
|
+
export function setRules(newRules) {
|
|
43
|
+
rules = newRules;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Add rules at the front (highest priority).
|
|
47
|
+
*/
|
|
48
|
+
export function prependRules(newRules) {
|
|
49
|
+
rules = [...newRules, ...rules];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate a command/content string against the active rules.
|
|
53
|
+
* Returns the first matching rule's action and reason.
|
|
54
|
+
*/
|
|
55
|
+
export function check(input) {
|
|
56
|
+
for (const rule of rules) {
|
|
57
|
+
if (rule.pattern.test(input)) {
|
|
58
|
+
return { action: rule.action, reason: rule.reason, matched: true };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { action: 'allow', reason: 'No rules matched', matched: false };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load gate rules from a JSON config (subset of areas.json).
|
|
65
|
+
* Expected format: Array of { action, pattern, reason }
|
|
66
|
+
*/
|
|
67
|
+
export function loadRulesFromConfig(config) {
|
|
68
|
+
return config.map(r => ({
|
|
69
|
+
action: r.action,
|
|
70
|
+
pattern: new RegExp(r.pattern),
|
|
71
|
+
reason: r.reason,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=gate.js.map
|