@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.
Files changed (84) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +542 -0
  3. package/dist/adapter.d.ts +49 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +100 -0
  6. package/dist/config.d.ts +89 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +173 -0
  9. package/dist/emitters/action-emitter.d.ts +40 -0
  10. package/dist/emitters/action-emitter.d.ts.map +1 -0
  11. package/dist/emitters/action-emitter.js +52 -0
  12. package/dist/emitters/constraint-emitter.d.ts +32 -0
  13. package/dist/emitters/constraint-emitter.d.ts.map +1 -0
  14. package/dist/emitters/constraint-emitter.js +41 -0
  15. package/dist/emitters/error-emitter.d.ts +33 -0
  16. package/dist/emitters/error-emitter.d.ts.map +1 -0
  17. package/dist/emitters/error-emitter.js +50 -0
  18. package/dist/emitters/gate-emitter.d.ts +37 -0
  19. package/dist/emitters/gate-emitter.d.ts.map +1 -0
  20. package/dist/emitters/gate-emitter.js +53 -0
  21. package/dist/emitters/human-input-emitter.d.ts +30 -0
  22. package/dist/emitters/human-input-emitter.d.ts.map +1 -0
  23. package/dist/emitters/human-input-emitter.js +38 -0
  24. package/dist/emitters/index.d.ts +13 -0
  25. package/dist/emitters/index.d.ts.map +1 -0
  26. package/dist/emitters/index.js +19 -0
  27. package/dist/emitters/plan-emitter.d.ts +75 -0
  28. package/dist/emitters/plan-emitter.d.ts.map +1 -0
  29. package/dist/emitters/plan-emitter.js +116 -0
  30. package/dist/emitters/session-emitter.d.ts +57 -0
  31. package/dist/emitters/session-emitter.d.ts.map +1 -0
  32. package/dist/emitters/session-emitter.js +80 -0
  33. package/dist/index.d.ts +40 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +111 -0
  36. package/dist/kindling-service.d.ts +122 -0
  37. package/dist/kindling-service.d.ts.map +1 -0
  38. package/dist/kindling-service.js +203 -0
  39. package/dist/observation-contract.d.ts +561 -0
  40. package/dist/observation-contract.d.ts.map +1 -0
  41. package/dist/observation-contract.js +391 -0
  42. package/dist/query-contract.d.ts +463 -0
  43. package/dist/query-contract.d.ts.map +1 -0
  44. package/dist/query-contract.js +314 -0
  45. package/dist/query-limits.d.ts +40 -0
  46. package/dist/query-limits.d.ts.map +1 -0
  47. package/dist/query-limits.js +79 -0
  48. package/dist/query-service.d.ts +109 -0
  49. package/dist/query-service.d.ts.map +1 -0
  50. package/dist/query-service.js +140 -0
  51. package/dist/retention.d.ts +79 -0
  52. package/dist/retention.d.ts.map +1 -0
  53. package/dist/retention.js +81 -0
  54. package/dist/sensitive-data-validator.d.ts +47 -0
  55. package/dist/sensitive-data-validator.d.ts.map +1 -0
  56. package/dist/sensitive-data-validator.js +135 -0
  57. package/dist/status.d.ts +104 -0
  58. package/dist/status.d.ts.map +1 -0
  59. package/dist/status.js +136 -0
  60. package/dist/utils/debug.d.ts +9 -0
  61. package/dist/utils/debug.d.ts.map +1 -0
  62. package/dist/utils/debug.js +55 -0
  63. package/package.json +114 -0
  64. package/src/adapter.ts +117 -0
  65. package/src/config.ts +202 -0
  66. package/src/emitters/action-emitter.ts +90 -0
  67. package/src/emitters/constraint-emitter.ts +73 -0
  68. package/src/emitters/error-emitter.ts +86 -0
  69. package/src/emitters/gate-emitter.ts +87 -0
  70. package/src/emitters/human-input-emitter.ts +71 -0
  71. package/src/emitters/index.ts +40 -0
  72. package/src/emitters/plan-emitter.ts +183 -0
  73. package/src/emitters/session-emitter.ts +131 -0
  74. package/src/index.ts +254 -0
  75. package/src/kindling-service.ts +272 -0
  76. package/src/malicious-ai.test.ts +949 -0
  77. package/src/observation-contract.ts +500 -0
  78. package/src/query-contract.ts +389 -0
  79. package/src/query-limits.ts +106 -0
  80. package/src/query-service.ts +217 -0
  81. package/src/retention.ts +153 -0
  82. package/src/sensitive-data-validator.ts +167 -0
  83. package/src/status.ts +221 -0
  84. 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
+ }