@nirnex/cli 3.0.0 → 4.0.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/dist/commands/runtime.d.ts +2 -0
- package/dist/commands/runtime.d.ts.map +1 -0
- package/dist/commands/runtime.js +60 -0
- package/dist/commands/runtime.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +99 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +69 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/trace.d.ts.map +1 -1
- package/dist/commands/trace.js +74 -1
- package/dist/commands/trace.js.map +1 -1
- package/dist/index.js +12 -7
- package/dist/index.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts +2 -0
- package/dist/runtime/bootstrap.d.ts.map +1 -0
- package/dist/runtime/bootstrap.js +90 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/entry.d.ts +2 -0
- package/dist/runtime/entry.d.ts.map +1 -0
- package/dist/runtime/entry.js +86 -0
- package/dist/runtime/entry.js.map +1 -0
- package/dist/runtime/envelope.d.ts +4 -0
- package/dist/runtime/envelope.d.ts.map +1 -0
- package/dist/runtime/envelope.js +113 -0
- package/dist/runtime/envelope.js.map +1 -0
- package/dist/runtime/guard.d.ts +2 -0
- package/dist/runtime/guard.d.ts.map +1 -0
- package/dist/runtime/guard.js +114 -0
- package/dist/runtime/guard.js.map +1 -0
- package/dist/runtime/session.d.ts +13 -0
- package/dist/runtime/session.d.ts.map +1 -0
- package/dist/runtime/session.js +111 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/runtime/trace-hook.d.ts +2 -0
- package/dist/runtime/trace-hook.d.ts.map +1 -0
- package/dist/runtime/trace-hook.js +102 -0
- package/dist/runtime/trace-hook.js.map +1 -0
- package/dist/runtime/types.d.ts +108 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/validate.d.ts +2 -0
- package/dist/runtime/validate.d.ts.map +1 -0
- package/dist/runtime/validate.js +91 -0
- package/dist/runtime/validate.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// UserPromptSubmit hook handler.
|
|
2
|
+
// Called before Claude processes each user prompt.
|
|
3
|
+
// Builds ECO from the prompt, creates a task envelope, writes session state,
|
|
4
|
+
// and returns additionalContext so Claude sees the envelope before acting.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { buildEnvelope, formatEnvelopeContext } from './envelope.js';
|
|
8
|
+
import { loadSession, saveSession, saveEnvelope } from './session.js';
|
|
9
|
+
function readStdin() {
|
|
10
|
+
return new Promise(resolve => {
|
|
11
|
+
let buf = '';
|
|
12
|
+
process.stdin.setEncoding('utf8');
|
|
13
|
+
process.stdin.on('data', chunk => { buf += chunk; });
|
|
14
|
+
process.stdin.on('end', () => resolve(buf));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function runEntry() {
|
|
18
|
+
const raw = await readStdin();
|
|
19
|
+
let hookData = {
|
|
20
|
+
session_id: 'unknown',
|
|
21
|
+
hook_event_name: 'UserPromptSubmit',
|
|
22
|
+
prompt: '',
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
hookData = JSON.parse(raw || '{}');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Non-fatal: proceed with empty prompt
|
|
29
|
+
}
|
|
30
|
+
const repoRoot = process.env.NIRNEX_REPO_ROOT ?? process.cwd();
|
|
31
|
+
const sessionId = hookData.session_id ?? process.env.NIRNEX_SESSION_ID ?? `sess_${Date.now().toString(36)}`;
|
|
32
|
+
const prompt = hookData.prompt ?? '';
|
|
33
|
+
// Skip if not a Nirnex project
|
|
34
|
+
if (!fs.existsSync(path.join(repoRoot, 'nirnex.config.json'))) {
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
// Skip very short or empty prompts (e.g. clarifications, single words)
|
|
38
|
+
if (prompt.trim().length < 10) {
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
let eco = {};
|
|
42
|
+
try {
|
|
43
|
+
const { buildECO } = await import('@nirnex/core/dist/eco.js');
|
|
44
|
+
eco = buildECO(null, repoRoot, { query: prompt });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Degraded mode: use a minimal ECO
|
|
48
|
+
eco = {
|
|
49
|
+
recommended_lane: 'A',
|
|
50
|
+
forced_lane_minimum: 'A',
|
|
51
|
+
confidence_score: 50,
|
|
52
|
+
forced_unknown: false,
|
|
53
|
+
blocked: false,
|
|
54
|
+
intent: { primary: 'unknown' },
|
|
55
|
+
modules_touched: [],
|
|
56
|
+
penalties: [],
|
|
57
|
+
escalation_reasons: [],
|
|
58
|
+
boundary_warnings: [],
|
|
59
|
+
eco_dimensions: {},
|
|
60
|
+
evidence_checkpoints: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const envelope = buildEnvelope(eco, prompt, sessionId);
|
|
64
|
+
// Persist envelope and link to session
|
|
65
|
+
saveEnvelope(repoRoot, envelope);
|
|
66
|
+
const session = loadSession(repoRoot, sessionId);
|
|
67
|
+
if (session) {
|
|
68
|
+
session.active_task_id = envelope.task_id;
|
|
69
|
+
session.tasks.push(envelope.task_id);
|
|
70
|
+
saveSession(repoRoot, session);
|
|
71
|
+
}
|
|
72
|
+
// Block if ECO says blocked
|
|
73
|
+
if (eco.blocked) {
|
|
74
|
+
const output = {
|
|
75
|
+
blockMessage: `[Nirnex] Task blocked by ECO. Reasons: ${(eco.escalation_reasons ?? []).join('; ') || 'unknown'}. Please revise the spec or run nirnex plan for details.`,
|
|
76
|
+
};
|
|
77
|
+
process.stdout.write(JSON.stringify(output));
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
// Return envelope context to Claude
|
|
81
|
+
const contextText = formatEnvelopeContext(envelope);
|
|
82
|
+
const output = { additionalContext: contextText };
|
|
83
|
+
process.stdout.write(JSON.stringify(output));
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/runtime/entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { TaskEnvelope } from './types.js';
|
|
2
|
+
export declare function buildEnvelope(eco: Record<string, any>, prompt: string, sessionId: string): TaskEnvelope;
|
|
3
|
+
export declare function formatEnvelopeContext(envelope: TaskEnvelope): string;
|
|
4
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/runtime/envelope.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAQ,MAAM,YAAY,CAAC;AAyBhD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAmEvG;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CA0BpE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Converts an ECO (Execution Context Object) into a TaskEnvelope (TEE).
|
|
2
|
+
// The envelope is the actionable, policy-bearing runtime representation.
|
|
3
|
+
import { generateTaskId } from './session.js';
|
|
4
|
+
import { getConfidenceLabel } from '@nirnex/core/dist/confidence.js';
|
|
5
|
+
const LANE_TOOL_POLICY = {
|
|
6
|
+
A: {
|
|
7
|
+
allowed_tools: ['Read', 'Glob', 'Grep', 'Bash', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'WebFetch', 'WebSearch'],
|
|
8
|
+
requires_guard: [],
|
|
9
|
+
denied_patterns: [],
|
|
10
|
+
},
|
|
11
|
+
B: {
|
|
12
|
+
allowed_tools: ['Read', 'Glob', 'Grep', 'Bash', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'WebFetch', 'WebSearch'],
|
|
13
|
+
requires_guard: ['Edit', 'Write', 'MultiEdit'],
|
|
14
|
+
denied_patterns: ['rm -rf', 'git reset --hard', 'git push --force', 'git push -f'],
|
|
15
|
+
},
|
|
16
|
+
C: {
|
|
17
|
+
allowed_tools: ['Read', 'Glob', 'Grep', 'Bash', 'Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'WebFetch', 'WebSearch'],
|
|
18
|
+
requires_guard: ['Bash', 'Edit', 'Write', 'MultiEdit'],
|
|
19
|
+
denied_patterns: [
|
|
20
|
+
'rm -rf', 'git reset --hard', 'git push --force', 'git push -f',
|
|
21
|
+
'DROP TABLE', 'ALTER TABLE', 'truncate', '> /dev/null 2>&1 &',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export function buildEnvelope(eco, prompt, sessionId) {
|
|
26
|
+
const lane = (eco.recommended_lane ?? eco.forced_lane_minimum ?? 'A');
|
|
27
|
+
const taskId = generateTaskId();
|
|
28
|
+
// Derive scope from ECO
|
|
29
|
+
const modulesTouched = eco.modules_touched ?? [];
|
|
30
|
+
const blockedPaths = [
|
|
31
|
+
...(eco.boundary_warnings ?? []).map((w) => w.split(':')[0]).filter(Boolean),
|
|
32
|
+
];
|
|
33
|
+
// Derive acceptance criteria from evidence checkpoints
|
|
34
|
+
const checkpoints = eco.evidence_checkpoints ?? {};
|
|
35
|
+
const acceptanceCriteria = Object.keys(checkpoints).map(k => `checkpoint:${k}`);
|
|
36
|
+
// Derive constraints from dimension severities
|
|
37
|
+
const constraints = [];
|
|
38
|
+
const dims = eco.eco_dimensions ?? {};
|
|
39
|
+
for (const [dim, val] of Object.entries(dims)) {
|
|
40
|
+
const v = val;
|
|
41
|
+
if (v.severity === 'warn' || v.severity === 'escalate') {
|
|
42
|
+
constraints.push(`dimension:${dim}:${v.severity}${v.detail ? ' — ' + v.detail : ''}`);
|
|
43
|
+
}
|
|
44
|
+
else if (v.severity === 'block') {
|
|
45
|
+
constraints.push(`dimension:${dim}:blocked${v.detail ? ' — ' + v.detail : ''}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const r of (eco.escalation_reasons ?? [])) {
|
|
49
|
+
constraints.push(`escalation:${r}`);
|
|
50
|
+
}
|
|
51
|
+
const penalties = eco.penalties ?? [];
|
|
52
|
+
const confidenceScore = eco.confidence_score ?? 100;
|
|
53
|
+
const envelope = {
|
|
54
|
+
task_id: taskId,
|
|
55
|
+
session_id: sessionId,
|
|
56
|
+
created_at: new Date().toISOString(),
|
|
57
|
+
prompt,
|
|
58
|
+
lane,
|
|
59
|
+
scope: {
|
|
60
|
+
allowed_paths: modulesTouched,
|
|
61
|
+
blocked_paths: blockedPaths,
|
|
62
|
+
modules_expected: modulesTouched,
|
|
63
|
+
},
|
|
64
|
+
constraints,
|
|
65
|
+
acceptance_criteria: acceptanceCriteria,
|
|
66
|
+
tool_policy: LANE_TOOL_POLICY[lane],
|
|
67
|
+
stop_conditions: {
|
|
68
|
+
required_validations: acceptanceCriteria,
|
|
69
|
+
forbidden_files: blockedPaths,
|
|
70
|
+
},
|
|
71
|
+
confidence: {
|
|
72
|
+
score: confidenceScore,
|
|
73
|
+
label: getConfidenceLabel(confidenceScore),
|
|
74
|
+
penalties,
|
|
75
|
+
},
|
|
76
|
+
eco_summary: {
|
|
77
|
+
intent: eco.intent?.primary ?? 'unknown',
|
|
78
|
+
recommended_lane: eco.recommended_lane ?? 'A',
|
|
79
|
+
forced_unknown: eco.forced_unknown ?? false,
|
|
80
|
+
blocked: eco.blocked ?? false,
|
|
81
|
+
escalation_reasons: eco.escalation_reasons ?? [],
|
|
82
|
+
boundary_warnings: eco.boundary_warnings ?? [],
|
|
83
|
+
},
|
|
84
|
+
status: 'active',
|
|
85
|
+
};
|
|
86
|
+
return envelope;
|
|
87
|
+
}
|
|
88
|
+
export function formatEnvelopeContext(envelope) {
|
|
89
|
+
const lines = [
|
|
90
|
+
`[Nirnex] Task ${envelope.task_id} · Lane ${envelope.lane} · Confidence ${envelope.confidence.score}% (${envelope.confidence.label})`,
|
|
91
|
+
`Intent: ${envelope.eco_summary.intent}`,
|
|
92
|
+
];
|
|
93
|
+
if (envelope.eco_summary.forced_unknown) {
|
|
94
|
+
lines.push(`⚠ ECO forced unknown — proceed with caution`);
|
|
95
|
+
}
|
|
96
|
+
if (envelope.eco_summary.blocked) {
|
|
97
|
+
lines.push(`✘ Task blocked by ECO — do not proceed with implementation`);
|
|
98
|
+
}
|
|
99
|
+
if (envelope.scope.allowed_paths.length > 0) {
|
|
100
|
+
lines.push(`Scope: ${envelope.scope.allowed_paths.join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
if (envelope.constraints.length > 0) {
|
|
103
|
+
lines.push(`Constraints: ${envelope.constraints.slice(0, 3).join('; ')}`);
|
|
104
|
+
}
|
|
105
|
+
if (envelope.tool_policy.requires_guard.length > 0) {
|
|
106
|
+
lines.push(`Guarded tools (Lane ${envelope.lane}): ${envelope.tool_policy.requires_guard.join(', ')}`);
|
|
107
|
+
}
|
|
108
|
+
if (envelope.eco_summary.escalation_reasons.length > 0) {
|
|
109
|
+
lines.push(`Escalation: ${envelope.eco_summary.escalation_reasons.join('; ')}`);
|
|
110
|
+
}
|
|
111
|
+
return lines.join('\n');
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../src/runtime/envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/runtime/guard.ts"],"names":[],"mappings":"AA2FA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAsC9C"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// PreToolUse hook handler.
|
|
2
|
+
// Called before Claude executes each tool call.
|
|
3
|
+
// Loads the active task envelope and evaluates the tool against lane policy.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { loadActiveEnvelope } from './session.js';
|
|
7
|
+
function readStdin() {
|
|
8
|
+
return new Promise(resolve => {
|
|
9
|
+
let buf = '';
|
|
10
|
+
process.stdin.setEncoding('utf8');
|
|
11
|
+
process.stdin.on('data', chunk => { buf += chunk; });
|
|
12
|
+
process.stdin.on('end', () => resolve(buf));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function extractAffectedFile(toolName, toolInput) {
|
|
16
|
+
if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Read') {
|
|
17
|
+
return (toolInput.file_path ?? toolInput.path ?? null);
|
|
18
|
+
}
|
|
19
|
+
if (toolName === 'MultiEdit') {
|
|
20
|
+
const edits = toolInput.edits;
|
|
21
|
+
return edits?.[0]?.file_path ?? null;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function matchesDeniedPattern(command, patterns) {
|
|
26
|
+
for (const pattern of patterns) {
|
|
27
|
+
if (command.includes(pattern))
|
|
28
|
+
return pattern;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function evaluateGuard(envelope, toolName, toolInput) {
|
|
33
|
+
const policy = envelope.tool_policy;
|
|
34
|
+
// If tool doesn't require guarding for this lane, allow immediately
|
|
35
|
+
if (!policy.requires_guard.includes(toolName)) {
|
|
36
|
+
return { decision: 'allow' };
|
|
37
|
+
}
|
|
38
|
+
// Bash: check denylist
|
|
39
|
+
if (toolName === 'Bash') {
|
|
40
|
+
const command = (toolInput.command ?? '');
|
|
41
|
+
const matched = matchesDeniedPattern(command, policy.denied_patterns);
|
|
42
|
+
if (matched) {
|
|
43
|
+
return {
|
|
44
|
+
decision: 'deny',
|
|
45
|
+
reason: `[Nirnex Guard] Command contains denied pattern "${matched}" (Lane ${envelope.lane} policy).`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// For Lane C, ask for commands that touch git or system state
|
|
49
|
+
if (envelope.lane === 'C' && /\b(git|rm|mv|chmod|chown)\b/.test(command)) {
|
|
50
|
+
return {
|
|
51
|
+
decision: 'ask',
|
|
52
|
+
message: `[Nirnex Guard] Lane C task — confirm this command is within scope:\n ${command}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { decision: 'allow' };
|
|
56
|
+
}
|
|
57
|
+
// Edit / Write / MultiEdit: check scope
|
|
58
|
+
const filePath = extractAffectedFile(toolName, toolInput);
|
|
59
|
+
if (filePath && envelope.scope.blocked_paths.length > 0) {
|
|
60
|
+
for (const blocked of envelope.scope.blocked_paths) {
|
|
61
|
+
if (filePath.includes(blocked)) {
|
|
62
|
+
return {
|
|
63
|
+
decision: 'deny',
|
|
64
|
+
reason: `[Nirnex Guard] File "${filePath}" is in a blocked path "${blocked}" for this task.`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Lane C: require file to be within expected scope
|
|
70
|
+
if (envelope.lane === 'C' && filePath && envelope.scope.allowed_paths.length > 0) {
|
|
71
|
+
const inScope = envelope.scope.allowed_paths.some(p => filePath.includes(p));
|
|
72
|
+
if (!inScope) {
|
|
73
|
+
return {
|
|
74
|
+
decision: 'ask',
|
|
75
|
+
message: `[Nirnex Guard] Lane C — "${filePath}" is outside the expected scope [${envelope.scope.allowed_paths.join(', ')}]. Proceed?`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { decision: 'allow' };
|
|
80
|
+
}
|
|
81
|
+
export async function runGuard() {
|
|
82
|
+
const raw = await readStdin();
|
|
83
|
+
let hookData = {
|
|
84
|
+
session_id: 'unknown',
|
|
85
|
+
hook_event_name: 'PreToolUse',
|
|
86
|
+
tool_name: '',
|
|
87
|
+
tool_input: {},
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
hookData = JSON.parse(raw || '{}');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Non-fatal: allow on parse failure
|
|
94
|
+
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
const repoRoot = process.env.NIRNEX_REPO_ROOT ?? process.cwd();
|
|
98
|
+
const sessionId = hookData.session_id ?? process.env.NIRNEX_SESSION_ID ?? '';
|
|
99
|
+
// Allow if not in a Nirnex project
|
|
100
|
+
if (!fs.existsSync(path.join(repoRoot, 'nirnex.config.json'))) {
|
|
101
|
+
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
102
|
+
process.exit(0);
|
|
103
|
+
}
|
|
104
|
+
const envelope = loadActiveEnvelope(repoRoot, sessionId);
|
|
105
|
+
// No active envelope → allow (not in a guarded task context)
|
|
106
|
+
if (!envelope) {
|
|
107
|
+
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
const decision = evaluateGuard(envelope, hookData.tool_name, hookData.tool_input);
|
|
111
|
+
process.stdout.write(JSON.stringify(decision));
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/runtime/guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NirnexSession, TaskEnvelope, TraceEvent } from './types.js';
|
|
2
|
+
export declare const RUNTIME_DIR = ".ai-index/runtime";
|
|
3
|
+
export declare function createSession(repoRoot: string, sessionId: string, partial?: Partial<NirnexSession>): NirnexSession;
|
|
4
|
+
export declare function loadSession(repoRoot: string, sessionId: string): NirnexSession | null;
|
|
5
|
+
export declare function saveSession(repoRoot: string, session: NirnexSession): void;
|
|
6
|
+
export declare function saveEnvelope(repoRoot: string, envelope: TaskEnvelope): void;
|
|
7
|
+
export declare function loadEnvelope(repoRoot: string, taskId: string): TaskEnvelope | null;
|
|
8
|
+
export declare function loadActiveEnvelope(repoRoot: string, sessionId: string): TaskEnvelope | null;
|
|
9
|
+
export declare function appendTraceEvent(repoRoot: string, sessionId: string, event: TraceEvent): void;
|
|
10
|
+
export declare function loadTraceEvents(repoRoot: string, sessionId: string): TraceEvent[];
|
|
11
|
+
export declare function generateTaskId(): string;
|
|
12
|
+
export declare function generateEventId(): string;
|
|
13
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/runtime/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAwB/C,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAetH;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAQrF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAO1E;AAID,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAO3E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAQlF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAI3F;AAID,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAK7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,EAAE,CAWjF;AAED,wBAAgB,cAAc,IAAI,MAAM,CAIvC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAIxC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Session and envelope state management.
|
|
2
|
+
// All state is stored under .ai-index/runtime/ as JSON files.
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export const RUNTIME_DIR = '.ai-index/runtime';
|
|
6
|
+
function runtimeDir(repoRoot) {
|
|
7
|
+
return path.join(repoRoot, RUNTIME_DIR);
|
|
8
|
+
}
|
|
9
|
+
function sessionsDir(repoRoot) {
|
|
10
|
+
return path.join(runtimeDir(repoRoot), 'sessions');
|
|
11
|
+
}
|
|
12
|
+
function envelopesDir(repoRoot) {
|
|
13
|
+
return path.join(runtimeDir(repoRoot), 'envelopes');
|
|
14
|
+
}
|
|
15
|
+
function eventsDir(repoRoot, sessionId) {
|
|
16
|
+
return path.join(runtimeDir(repoRoot), 'events', sessionId);
|
|
17
|
+
}
|
|
18
|
+
function ensureDir(p) {
|
|
19
|
+
if (!fs.existsSync(p))
|
|
20
|
+
fs.mkdirSync(p, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
// ─── Session ──────────────────────────────────────────────────────────────
|
|
23
|
+
export function createSession(repoRoot, sessionId, partial = {}) {
|
|
24
|
+
ensureDir(sessionsDir(repoRoot));
|
|
25
|
+
const session = {
|
|
26
|
+
session_id: sessionId,
|
|
27
|
+
repo_root: repoRoot,
|
|
28
|
+
db_path: path.join(repoRoot, '.aidos.db'),
|
|
29
|
+
index_freshness: 'unknown',
|
|
30
|
+
current_head: '',
|
|
31
|
+
policy_mode: 'standard',
|
|
32
|
+
created_at: new Date().toISOString(),
|
|
33
|
+
tasks: [],
|
|
34
|
+
...partial,
|
|
35
|
+
};
|
|
36
|
+
saveSession(repoRoot, session);
|
|
37
|
+
return session;
|
|
38
|
+
}
|
|
39
|
+
export function loadSession(repoRoot, sessionId) {
|
|
40
|
+
const p = path.join(sessionsDir(repoRoot), `${sessionId}.json`);
|
|
41
|
+
if (!fs.existsSync(p))
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function saveSession(repoRoot, session) {
|
|
51
|
+
ensureDir(sessionsDir(repoRoot));
|
|
52
|
+
fs.writeFileSync(path.join(sessionsDir(repoRoot), `${session.session_id}.json`), JSON.stringify(session, null, 2), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
// ─── Envelope ─────────────────────────────────────────────────────────────
|
|
55
|
+
export function saveEnvelope(repoRoot, envelope) {
|
|
56
|
+
ensureDir(envelopesDir(repoRoot));
|
|
57
|
+
fs.writeFileSync(path.join(envelopesDir(repoRoot), `${envelope.task_id}.json`), JSON.stringify(envelope, null, 2), 'utf8');
|
|
58
|
+
}
|
|
59
|
+
export function loadEnvelope(repoRoot, taskId) {
|
|
60
|
+
const p = path.join(envelopesDir(repoRoot), `${taskId}.json`);
|
|
61
|
+
if (!fs.existsSync(p))
|
|
62
|
+
return null;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function loadActiveEnvelope(repoRoot, sessionId) {
|
|
71
|
+
const session = loadSession(repoRoot, sessionId);
|
|
72
|
+
if (!session?.active_task_id)
|
|
73
|
+
return null;
|
|
74
|
+
return loadEnvelope(repoRoot, session.active_task_id);
|
|
75
|
+
}
|
|
76
|
+
// ─── Trace events ─────────────────────────────────────────────────────────
|
|
77
|
+
export function appendTraceEvent(repoRoot, sessionId, event) {
|
|
78
|
+
const dir = eventsDir(repoRoot, sessionId);
|
|
79
|
+
ensureDir(dir);
|
|
80
|
+
const eventsPath = path.join(dir, 'events.jsonl');
|
|
81
|
+
fs.appendFileSync(eventsPath, JSON.stringify(event) + '\n', 'utf8');
|
|
82
|
+
}
|
|
83
|
+
export function loadTraceEvents(repoRoot, sessionId) {
|
|
84
|
+
const eventsPath = path.join(eventsDir(repoRoot, sessionId), 'events.jsonl');
|
|
85
|
+
if (!fs.existsSync(eventsPath))
|
|
86
|
+
return [];
|
|
87
|
+
return fs
|
|
88
|
+
.readFileSync(eventsPath, 'utf8')
|
|
89
|
+
.split('\n')
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.map(line => {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(line);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.filter((e) => e !== null);
|
|
100
|
+
}
|
|
101
|
+
export function generateTaskId() {
|
|
102
|
+
const ts = Date.now().toString(36);
|
|
103
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
104
|
+
return `task_${ts}_${rand}`;
|
|
105
|
+
}
|
|
106
|
+
export function generateEventId() {
|
|
107
|
+
const ts = Date.now().toString(36);
|
|
108
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
109
|
+
return `evt_${ts}_${rand}`;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/runtime/session.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,8DAA8D;AAE9D,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAE/C,SAAS,UAAU,CAAC,QAAgB;IAClC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,UAAU,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB;IACpC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,SAAiB;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,6EAA6E;AAE7E,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,SAAiB,EAAE,UAAkC,EAAE;IACrG,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjC,MAAM,OAAO,GAAkB;QAC7B,UAAU,EAAE,SAAS;QACrB,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC;QACzC,eAAe,EAAE,SAAS;QAC1B,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE,UAAU;QACvB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,KAAK,EAAE,EAAE;QACT,GAAG,OAAO;KACX,CAAC;IACF,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAiB;IAC7D,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,SAAS,OAAO,CAAC,CAAC;IAChE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAkB,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,OAAsB;IAClE,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjC,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,GAAG,OAAO,CAAC,UAAU,OAAO,CAAC,EAC9D,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAChC,MAAM,CACP,CAAC;AACJ,CAAC;AAED,6EAA6E;AAE7E,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,QAAsB;IACnE,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,GAAG,QAAQ,CAAC,OAAO,OAAO,CAAC,EAC7D,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EACjC,MAAM,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,MAAc;IAC3D,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,GAAG,MAAM,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAiB,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,SAAiB;IACpE,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,cAAc;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;AACxD,CAAC;AAED,6EAA6E;AAE7E,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,SAAiB,EAAE,KAAiB;IACrF,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC3C,SAAS,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAClD,EAAE,CAAC,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,SAAiB;IACjE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,cAAc,CAAC,CAAC;IAC7E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,OAAO,EAAE;SACN,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC;SAChC,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,IAAI,CAAC,EAAE;QACV,IAAI,CAAC;YAAC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;IACvE,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAmB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-hook.d.ts","sourceRoot":"","sources":["../../src/runtime/trace-hook.ts"],"names":[],"mappings":"AA6DA,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAiElD"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// PostToolUse hook handler.
|
|
2
|
+
// Called after each tool execution. Appends a trace event, detects deviation,
|
|
3
|
+
// and optionally injects context back to Claude if drift is found.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { loadActiveEnvelope, appendTraceEvent, generateEventId } from './session.js';
|
|
7
|
+
function readStdin() {
|
|
8
|
+
return new Promise(resolve => {
|
|
9
|
+
let buf = '';
|
|
10
|
+
process.stdin.setEncoding('utf8');
|
|
11
|
+
process.stdin.on('data', chunk => { buf += chunk; });
|
|
12
|
+
process.stdin.on('end', () => resolve(buf));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function extractAffectedFiles(toolName, toolInput) {
|
|
16
|
+
const files = [];
|
|
17
|
+
if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Read') {
|
|
18
|
+
const f = toolInput.file_path ?? toolInput.path;
|
|
19
|
+
if (typeof f === 'string')
|
|
20
|
+
files.push(f);
|
|
21
|
+
}
|
|
22
|
+
else if (toolName === 'MultiEdit') {
|
|
23
|
+
const edits = toolInput.edits;
|
|
24
|
+
for (const e of edits ?? []) {
|
|
25
|
+
if (e.file_path)
|
|
26
|
+
files.push(e.file_path);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (toolName === 'Bash') {
|
|
30
|
+
// Cannot reliably extract files from arbitrary commands
|
|
31
|
+
}
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
function detectDeviations(affectedFiles, toolName, toolInput, allowedPaths, blockedPaths, lane) {
|
|
35
|
+
const flags = [];
|
|
36
|
+
for (const f of affectedFiles) {
|
|
37
|
+
for (const blocked of blockedPaths) {
|
|
38
|
+
if (f.includes(blocked)) {
|
|
39
|
+
flags.push(`file_in_blocked_path:${f}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (lane === 'C' && allowedPaths.length > 0) {
|
|
43
|
+
const inScope = allowedPaths.some(p => f.includes(p));
|
|
44
|
+
if (!inScope) {
|
|
45
|
+
flags.push(`file_out_of_scope:${f}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return flags;
|
|
50
|
+
}
|
|
51
|
+
export async function runTraceHook() {
|
|
52
|
+
const raw = await readStdin();
|
|
53
|
+
let hookData = {
|
|
54
|
+
session_id: 'unknown',
|
|
55
|
+
hook_event_name: 'PostToolUse',
|
|
56
|
+
tool_name: '',
|
|
57
|
+
tool_input: {},
|
|
58
|
+
tool_result: {},
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
hookData = JSON.parse(raw || '{}');
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
const repoRoot = process.env.NIRNEX_REPO_ROOT ?? process.cwd();
|
|
67
|
+
const sessionId = hookData.session_id ?? process.env.NIRNEX_SESSION_ID ?? '';
|
|
68
|
+
if (!fs.existsSync(path.join(repoRoot, 'nirnex.config.json'))) {
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
const envelope = loadActiveEnvelope(repoRoot, sessionId);
|
|
72
|
+
const affectedFiles = extractAffectedFiles(hookData.tool_name, hookData.tool_input);
|
|
73
|
+
const deviationFlags = envelope
|
|
74
|
+
? detectDeviations(affectedFiles, hookData.tool_name, hookData.tool_input, envelope.scope.allowed_paths, envelope.scope.blocked_paths, envelope.lane)
|
|
75
|
+
: [];
|
|
76
|
+
const event = {
|
|
77
|
+
event_id: generateEventId(),
|
|
78
|
+
session_id: sessionId,
|
|
79
|
+
task_id: envelope?.task_id ?? 'none',
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
tool: hookData.tool_name,
|
|
82
|
+
tool_input: hookData.tool_input,
|
|
83
|
+
tool_result: hookData.tool_result,
|
|
84
|
+
affected_files: affectedFiles,
|
|
85
|
+
deviation_flags: deviationFlags,
|
|
86
|
+
};
|
|
87
|
+
try {
|
|
88
|
+
appendTraceEvent(repoRoot, sessionId, event);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// Non-fatal
|
|
92
|
+
}
|
|
93
|
+
// If deviation detected, inject context back to Claude
|
|
94
|
+
if (deviationFlags.length > 0) {
|
|
95
|
+
const output = {
|
|
96
|
+
additionalContext: `[Nirnex Trace] Deviation detected in ${hookData.tool_name}: ${deviationFlags.join(', ')}. Verify this is intentional.`,
|
|
97
|
+
};
|
|
98
|
+
process.stdout.write(JSON.stringify(output));
|
|
99
|
+
}
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=trace-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-hook.js","sourceRoot":"","sources":["../../src/runtime/trace-hook.ts"],"names":[],"mappings":"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"}
|