@eddacraft/anvil-kindling-integration 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/LICENSE +14 -0
- package/README.md +542 -0
- package/dist/adapter.d.ts +49 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +100 -0
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +173 -0
- package/dist/emitters/action-emitter.d.ts +40 -0
- package/dist/emitters/action-emitter.d.ts.map +1 -0
- package/dist/emitters/action-emitter.js +52 -0
- package/dist/emitters/constraint-emitter.d.ts +32 -0
- package/dist/emitters/constraint-emitter.d.ts.map +1 -0
- package/dist/emitters/constraint-emitter.js +41 -0
- package/dist/emitters/error-emitter.d.ts +33 -0
- package/dist/emitters/error-emitter.d.ts.map +1 -0
- package/dist/emitters/error-emitter.js +50 -0
- package/dist/emitters/gate-emitter.d.ts +37 -0
- package/dist/emitters/gate-emitter.d.ts.map +1 -0
- package/dist/emitters/gate-emitter.js +53 -0
- package/dist/emitters/human-input-emitter.d.ts +30 -0
- package/dist/emitters/human-input-emitter.d.ts.map +1 -0
- package/dist/emitters/human-input-emitter.js +38 -0
- package/dist/emitters/index.d.ts +13 -0
- package/dist/emitters/index.d.ts.map +1 -0
- package/dist/emitters/index.js +19 -0
- package/dist/emitters/plan-emitter.d.ts +75 -0
- package/dist/emitters/plan-emitter.d.ts.map +1 -0
- package/dist/emitters/plan-emitter.js +116 -0
- package/dist/emitters/session-emitter.d.ts +57 -0
- package/dist/emitters/session-emitter.d.ts.map +1 -0
- package/dist/emitters/session-emitter.js +80 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/kindling-service.d.ts +122 -0
- package/dist/kindling-service.d.ts.map +1 -0
- package/dist/kindling-service.js +203 -0
- package/dist/observation-contract.d.ts +561 -0
- package/dist/observation-contract.d.ts.map +1 -0
- package/dist/observation-contract.js +391 -0
- package/dist/query-contract.d.ts +463 -0
- package/dist/query-contract.d.ts.map +1 -0
- package/dist/query-contract.js +314 -0
- package/dist/query-limits.d.ts +40 -0
- package/dist/query-limits.d.ts.map +1 -0
- package/dist/query-limits.js +79 -0
- package/dist/query-service.d.ts +109 -0
- package/dist/query-service.d.ts.map +1 -0
- package/dist/query-service.js +140 -0
- package/dist/retention.d.ts +79 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +81 -0
- package/dist/sensitive-data-validator.d.ts +47 -0
- package/dist/sensitive-data-validator.d.ts.map +1 -0
- package/dist/sensitive-data-validator.js +135 -0
- package/dist/status.d.ts +104 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +136 -0
- package/dist/utils/debug.d.ts +9 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +55 -0
- package/package.json +114 -0
- package/src/adapter.ts +117 -0
- package/src/config.ts +202 -0
- package/src/emitters/action-emitter.ts +90 -0
- package/src/emitters/constraint-emitter.ts +73 -0
- package/src/emitters/error-emitter.ts +86 -0
- package/src/emitters/gate-emitter.ts +87 -0
- package/src/emitters/human-input-emitter.ts +71 -0
- package/src/emitters/index.ts +40 -0
- package/src/emitters/plan-emitter.ts +183 -0
- package/src/emitters/session-emitter.ts +131 -0
- package/src/index.ts +254 -0
- package/src/kindling-service.ts +272 -0
- package/src/malicious-ai.test.ts +949 -0
- package/src/observation-contract.ts +500 -0
- package/src/query-contract.ts +389 -0
- package/src/query-limits.ts +106 -0
- package/src/query-service.ts +217 -0
- package/src/retention.ts +153 -0
- package/src/sensitive-data-validator.ts +167 -0
- package/src/status.ts +221 -0
- package/src/utils/debug.ts +65 -0
package/src/status.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kindling Status Utility (KINDLING-014)
|
|
3
|
+
*
|
|
4
|
+
* Provides a decoupled status summary for Kindling integration.
|
|
5
|
+
* Returns observation counts, database size, and retention config
|
|
6
|
+
* without coupling to any specific CLI framework.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const status = await getKindlingStatus(config, store);
|
|
10
|
+
* // => { enabled: true, observationCount: 42, dbSizeBytes: 81920, ... }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Status Types
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Kindling status summary returned by getKindlingStatus()
|
|
19
|
+
*/
|
|
20
|
+
export interface KindlingStatus {
|
|
21
|
+
/** Whether Kindling is enabled in config */
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
|
|
24
|
+
/** Total number of observations stored (undefined if disabled) */
|
|
25
|
+
observationCount?: number;
|
|
26
|
+
|
|
27
|
+
/** Database file size in bytes (undefined if disabled or no DB) */
|
|
28
|
+
dbSizeBytes?: number;
|
|
29
|
+
|
|
30
|
+
/** Configured retention period in days (undefined if disabled) */
|
|
31
|
+
retentionDays?: number;
|
|
32
|
+
|
|
33
|
+
/** ISO8601 timestamp of the most recent observation (undefined if none) */
|
|
34
|
+
lastObservationAt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Configuration Interface (minimal, no coupling to config.ts)
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Minimal config shape needed for status checks.
|
|
43
|
+
* This avoids importing from config.ts (built by other agent).
|
|
44
|
+
*/
|
|
45
|
+
export interface KindlingStatusConfig {
|
|
46
|
+
enabled: boolean;
|
|
47
|
+
database?: string;
|
|
48
|
+
retention?: {
|
|
49
|
+
days?: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// Store Interface (minimal, no coupling to kindling-service.ts)
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Minimal store interface for status queries.
|
|
59
|
+
* Implementations can be KindlingService, a direct SQLite handle, or a mock.
|
|
60
|
+
*/
|
|
61
|
+
export interface KindlingStatusStore {
|
|
62
|
+
/**
|
|
63
|
+
* Count all observations in the store.
|
|
64
|
+
* Returns 0 if the store is empty.
|
|
65
|
+
*/
|
|
66
|
+
countObservations(): Promise<number>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the database file size in bytes.
|
|
70
|
+
* Returns 0 if the database does not exist or cannot be read.
|
|
71
|
+
*/
|
|
72
|
+
getDatabaseSizeBytes(): Promise<number>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the timestamp of the most recent observation.
|
|
76
|
+
* Returns undefined if no observations exist.
|
|
77
|
+
*/
|
|
78
|
+
getLastObservationTimestamp(): Promise<string | undefined>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Status Function
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get Kindling integration status.
|
|
87
|
+
*
|
|
88
|
+
* If store is not provided or config is disabled, returns a minimal
|
|
89
|
+
* disabled status. This function never throws -- it handles errors
|
|
90
|
+
* gracefully and returns partial information.
|
|
91
|
+
*
|
|
92
|
+
* @param config - Kindling configuration (at minimum, { enabled: boolean })
|
|
93
|
+
* @param store - Optional store interface for querying observation data
|
|
94
|
+
* @returns KindlingStatus summary
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* import { getKindlingStatus } from './status.js';
|
|
99
|
+
*
|
|
100
|
+
* // Disabled (no store needed)
|
|
101
|
+
* const status = await getKindlingStatus({ enabled: false });
|
|
102
|
+
* // => { enabled: false }
|
|
103
|
+
*
|
|
104
|
+
* // Enabled with store
|
|
105
|
+
* const status = await getKindlingStatus(
|
|
106
|
+
* { enabled: true, retention: { days: 90 } },
|
|
107
|
+
* myStore,
|
|
108
|
+
* );
|
|
109
|
+
* // => { enabled: true, observationCount: 42, dbSizeBytes: 81920,
|
|
110
|
+
* // retentionDays: 90, lastObservationAt: '2026-02-15T...' }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export async function getKindlingStatus(
|
|
114
|
+
config: KindlingStatusConfig,
|
|
115
|
+
store?: KindlingStatusStore
|
|
116
|
+
): Promise<KindlingStatus> {
|
|
117
|
+
// Fast path: disabled
|
|
118
|
+
if (!config.enabled) {
|
|
119
|
+
return { enabled: false };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Enabled but no store available
|
|
123
|
+
if (!store) {
|
|
124
|
+
return {
|
|
125
|
+
enabled: true,
|
|
126
|
+
retentionDays: config.retention?.days,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Enabled with store -- query for status data
|
|
131
|
+
const [observationCount, dbSizeBytes, lastObservationAt] = await Promise.all([
|
|
132
|
+
safeCall(() => store.countObservations(), 0),
|
|
133
|
+
safeCall(() => store.getDatabaseSizeBytes(), 0),
|
|
134
|
+
safeCall(() => store.getLastObservationTimestamp(), undefined),
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
enabled: true,
|
|
139
|
+
observationCount,
|
|
140
|
+
dbSizeBytes,
|
|
141
|
+
retentionDays: config.retention?.days,
|
|
142
|
+
lastObservationAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// =============================================================================
|
|
147
|
+
// Formatting Utilities
|
|
148
|
+
// =============================================================================
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Format a KindlingStatus as a human-readable string.
|
|
152
|
+
* Useful for CLI output without coupling to a specific formatter.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```typescript
|
|
156
|
+
* const status = await getKindlingStatus(config, store);
|
|
157
|
+
* console.log(formatKindlingStatus(status));
|
|
158
|
+
* // Kindling: enabled
|
|
159
|
+
* // Observations: 42
|
|
160
|
+
* // Database size: 80 KB
|
|
161
|
+
* // Retention: 90 days
|
|
162
|
+
* // Last observation: 2026-02-15T10:30:00.000Z
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function formatKindlingStatus(status: KindlingStatus): string {
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
|
|
168
|
+
lines.push(`Kindling: ${status.enabled ? 'enabled' : 'disabled'}`);
|
|
169
|
+
|
|
170
|
+
if (!status.enabled) {
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (status.observationCount !== undefined) {
|
|
175
|
+
lines.push(`Observations: ${status.observationCount.toLocaleString()}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (status.dbSizeBytes !== undefined) {
|
|
179
|
+
lines.push(`Database size: ${formatBytes(status.dbSizeBytes)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (status.retentionDays !== undefined) {
|
|
183
|
+
lines.push(`Retention: ${status.retentionDays} days`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (status.lastObservationAt) {
|
|
187
|
+
lines.push(`Last observation: ${status.lastObservationAt}`);
|
|
188
|
+
} else if (status.observationCount === 0) {
|
|
189
|
+
lines.push('Last observation: none');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Internal Helpers
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Call an async function and return a fallback value on error.
|
|
201
|
+
* Ensures getKindlingStatus never throws.
|
|
202
|
+
*/
|
|
203
|
+
async function safeCall<T>(fn: () => Promise<T>, fallback: T): Promise<T> {
|
|
204
|
+
try {
|
|
205
|
+
return await fn();
|
|
206
|
+
} catch {
|
|
207
|
+
return fallback;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format bytes into a human-readable string.
|
|
213
|
+
*/
|
|
214
|
+
function formatBytes(bytes: number): string {
|
|
215
|
+
if (bytes === 0) return '0 B';
|
|
216
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
217
|
+
const k = 1024;
|
|
218
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
|
|
219
|
+
const value = bytes / Math.pow(k, i);
|
|
220
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[i]}`;
|
|
221
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal debug logging utility for kindling-integration package
|
|
3
|
+
*
|
|
4
|
+
* Self-contained to avoid dependency on @eddacraft/anvil-core
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type DebugNamespace = 'kindling';
|
|
8
|
+
|
|
9
|
+
function isDebugEnabled(): boolean {
|
|
10
|
+
const anvilDebug = process.env.ANVIL_DEBUG;
|
|
11
|
+
const debug = process.env.DEBUG;
|
|
12
|
+
|
|
13
|
+
if (anvilDebug === '1' || anvilDebug === 'true') {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (debug) {
|
|
18
|
+
if (debug.includes('anvil:*') || debug.includes('anvil:kindling')) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sanitizeForLog(value: string): string {
|
|
27
|
+
let sanitized = value.replace(/\b(sk-|ghp_|ghu_)[A-Za-z0-9_-]+/g, '[REDACTED]');
|
|
28
|
+
sanitized = sanitized.replace(/Bearer\s+[A-Za-z0-9_.+/=-]+/g, 'Bearer [REDACTED]');
|
|
29
|
+
sanitized = sanitized.replace(/\b[0-9a-fA-F]{40,}\b/g, '[REDACTED]');
|
|
30
|
+
sanitized = sanitized.replace(/\b[A-Za-z0-9+/]{20,}={0,3}\b/g, '[REDACTED]');
|
|
31
|
+
return sanitized;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function debugLog(namespace: DebugNamespace, message: string, data?: unknown): void {
|
|
35
|
+
if (!isDebugEnabled()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
const prefix = `[${timestamp}] [anvil:${namespace}]`;
|
|
41
|
+
const sanitizedMessage = sanitizeForLog(message);
|
|
42
|
+
|
|
43
|
+
/* eslint-disable no-console -- debug utility */
|
|
44
|
+
if (data !== undefined) {
|
|
45
|
+
if (data instanceof Error) {
|
|
46
|
+
console.debug(`${prefix} ${sanitizedMessage}:`, sanitizeForLog(data.message));
|
|
47
|
+
if (data.stack) {
|
|
48
|
+
console.debug(`${prefix} Stack:`, sanitizeForLog(data.stack));
|
|
49
|
+
}
|
|
50
|
+
} else if (typeof data === 'string') {
|
|
51
|
+
console.debug(`${prefix} ${sanitizedMessage}:`, sanitizeForLog(data));
|
|
52
|
+
} else {
|
|
53
|
+
console.debug(`${prefix} ${sanitizedMessage}:`, data);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
console.debug(`${prefix} ${sanitizedMessage}`);
|
|
57
|
+
}
|
|
58
|
+
/* eslint-enable no-console */
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createDebugger(
|
|
62
|
+
_namespace: DebugNamespace
|
|
63
|
+
): (message: string, data?: unknown) => void {
|
|
64
|
+
return (message: string, data?: unknown) => debugLog('kindling', message, data);
|
|
65
|
+
}
|