@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
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Retention Management (KINDLING-016)
3
+ *
4
+ * Handles pruning of old observations and storage statistics.
5
+ * Works against the abstract IKindlingStore interface.
6
+ *
7
+ * Since the abstract store does not expose a direct "delete older than" method,
8
+ * retention is implemented via a dedicated IRetentionCapableStore interface
9
+ * that concrete stores can optionally implement.
10
+ */
11
+ import type { KindlingConfig } from './config.js';
12
+ /**
13
+ * Extended store interface for stores that support retention operations.
14
+ *
15
+ * Not all stores need to implement this. The NoOpKindlingStore does not.
16
+ * Concrete SQLite or database-backed stores should implement this interface.
17
+ */
18
+ export interface IRetentionCapableStore {
19
+ /**
20
+ * Delete all observations with timestamps older than the given ISO8601 date.
21
+ *
22
+ * @param olderThan - ISO8601 datetime cutoff
23
+ * @returns Number of observations deleted
24
+ */
25
+ deleteObservationsOlderThan(olderThan: string): Promise<number>;
26
+ /**
27
+ * Get storage statistics.
28
+ *
29
+ * @returns Observation count and estimated storage size in bytes
30
+ */
31
+ getStats(): Promise<StorageStats>;
32
+ }
33
+ /**
34
+ * Storage statistics
35
+ */
36
+ export interface StorageStats {
37
+ /** Total number of observations in the store */
38
+ observation_count: number;
39
+ /** Estimated storage size in bytes */
40
+ estimated_size_bytes: number;
41
+ }
42
+ /**
43
+ * Check if a store supports retention operations.
44
+ */
45
+ export declare function isRetentionCapable(store: unknown): store is IRetentionCapableStore;
46
+ /**
47
+ * Result of a pruning operation
48
+ */
49
+ export interface PruneResult {
50
+ /** Whether pruning was actually performed */
51
+ pruned: boolean;
52
+ /** Number of observations deleted (0 if not pruned) */
53
+ deleted_count: number;
54
+ /** The cutoff date used */
55
+ cutoff_date: string;
56
+ /** Reason if pruning was skipped */
57
+ skip_reason?: string;
58
+ }
59
+ /**
60
+ * Prune observations older than the configured retention period.
61
+ *
62
+ * If the store does not implement IRetentionCapableStore, this is a no-op
63
+ * that returns a skip result.
64
+ *
65
+ * @param store - The store to prune (must implement IRetentionCapableStore)
66
+ * @param config - Kindling configuration with retention settings
67
+ * @returns Prune result
68
+ */
69
+ export declare function pruneOldObservations(store: unknown, config: KindlingConfig): Promise<PruneResult>;
70
+ /**
71
+ * Get storage statistics from the store.
72
+ *
73
+ * If the store does not implement IRetentionCapableStore, returns zero values.
74
+ *
75
+ * @param store - The store to query
76
+ * @returns Storage statistics
77
+ */
78
+ export declare function getStorageStats(store: unknown): Promise<StorageStats>;
79
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../src/retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;OAKG;IACH,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEhE;;;;OAIG;IACH,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAMD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CASlF;AAMD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,WAAW,CAAC,CA8BtB;AAMD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAS3E"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Retention Management (KINDLING-016)
3
+ *
4
+ * Handles pruning of old observations and storage statistics.
5
+ * Works against the abstract IKindlingStore interface.
6
+ *
7
+ * Since the abstract store does not expose a direct "delete older than" method,
8
+ * retention is implemented via a dedicated IRetentionCapableStore interface
9
+ * that concrete stores can optionally implement.
10
+ */
11
+ // =============================================================================
12
+ // Type Guard
13
+ // =============================================================================
14
+ /**
15
+ * Check if a store supports retention operations.
16
+ */
17
+ export function isRetentionCapable(store) {
18
+ if (store === null || typeof store !== 'object') {
19
+ return false;
20
+ }
21
+ const candidate = store;
22
+ return (typeof candidate['deleteObservationsOlderThan'] === 'function' &&
23
+ typeof candidate['getStats'] === 'function');
24
+ }
25
+ /**
26
+ * Prune observations older than the configured retention period.
27
+ *
28
+ * If the store does not implement IRetentionCapableStore, this is a no-op
29
+ * that returns a skip result.
30
+ *
31
+ * @param store - The store to prune (must implement IRetentionCapableStore)
32
+ * @param config - Kindling configuration with retention settings
33
+ * @returns Prune result
34
+ */
35
+ export async function pruneOldObservations(store, config) {
36
+ if (!config.enabled) {
37
+ return {
38
+ pruned: false,
39
+ deleted_count: 0,
40
+ cutoff_date: '',
41
+ skip_reason: 'Kindling is disabled',
42
+ };
43
+ }
44
+ if (!isRetentionCapable(store)) {
45
+ return {
46
+ pruned: false,
47
+ deleted_count: 0,
48
+ cutoff_date: '',
49
+ skip_reason: 'Store does not support retention operations',
50
+ };
51
+ }
52
+ const cutoffDate = new Date();
53
+ cutoffDate.setDate(cutoffDate.getDate() - config.retention.days);
54
+ const cutoffIso = cutoffDate.toISOString();
55
+ const deletedCount = await store.deleteObservationsOlderThan(cutoffIso);
56
+ return {
57
+ pruned: true,
58
+ deleted_count: deletedCount,
59
+ cutoff_date: cutoffIso,
60
+ };
61
+ }
62
+ // =============================================================================
63
+ // Statistics
64
+ // =============================================================================
65
+ /**
66
+ * Get storage statistics from the store.
67
+ *
68
+ * If the store does not implement IRetentionCapableStore, returns zero values.
69
+ *
70
+ * @param store - The store to query
71
+ * @returns Storage statistics
72
+ */
73
+ export async function getStorageStats(store) {
74
+ if (!isRetentionCapable(store)) {
75
+ return {
76
+ observation_count: 0,
77
+ estimated_size_bytes: 0,
78
+ };
79
+ }
80
+ return store.getStats();
81
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Sensitive Data Validation (KINDLING-015)
3
+ *
4
+ * Validates and redacts sensitive data from observations before they
5
+ * are persisted to Kindling. This is a defense-in-depth layer on top of
6
+ * the `containsSensitiveData` check in observation-contract.ts.
7
+ *
8
+ * Patterns detected:
9
+ * - API keys (sk-*, ghp_*, AKIA*)
10
+ * - Long hex tokens (40+ characters)
11
+ * - Email addresses
12
+ * - Password-like values
13
+ *
14
+ * @see observation-contract.ts for the base containsSensitiveData utility
15
+ */
16
+ import { type Observation } from './observation-contract.js';
17
+ /**
18
+ * Result of sensitive data validation
19
+ */
20
+ export interface SensitiveDataValidationResult {
21
+ /** Whether any sensitive data was detected */
22
+ hasSensitiveData: boolean;
23
+ /** Detailed issues found */
24
+ issues: string[];
25
+ }
26
+ /**
27
+ * Validate that an observation does not contain sensitive data.
28
+ *
29
+ * Uses the contract-level `containsSensitiveData` check plus additional
30
+ * pattern matching for known credential formats.
31
+ *
32
+ * @param observation - The observation to validate
33
+ * @returns Validation result with issues if sensitive data found
34
+ */
35
+ export declare function validateNoSensitiveData(observation: Observation): SensitiveDataValidationResult;
36
+ /**
37
+ * Deep-clone an observation and redact all known sensitive patterns from
38
+ * string values. This operates on the JSON serialization to catch values
39
+ * regardless of nesting depth.
40
+ *
41
+ * The returned observation is safe to persist.
42
+ *
43
+ * @param observation - The observation to redact
44
+ * @returns A new observation with sensitive values replaced
45
+ */
46
+ export declare function redactSensitiveFields(observation: Observation): Observation;
47
+ //# sourceMappingURL=sensitive-data-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensitive-data-validator.d.ts","sourceRoot":"","sources":["../src/sensitive-data-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAyB,KAAK,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAyEpF;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C,8CAA8C;IAC9C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,6BAA6B,CA+B/F;AAMD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,WAAW,GAAG,WAAW,CAW3E"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Sensitive Data Validation (KINDLING-015)
3
+ *
4
+ * Validates and redacts sensitive data from observations before they
5
+ * are persisted to Kindling. This is a defense-in-depth layer on top of
6
+ * the `containsSensitiveData` check in observation-contract.ts.
7
+ *
8
+ * Patterns detected:
9
+ * - API keys (sk-*, ghp_*, AKIA*)
10
+ * - Long hex tokens (40+ characters)
11
+ * - Email addresses
12
+ * - Password-like values
13
+ *
14
+ * @see observation-contract.ts for the base containsSensitiveData utility
15
+ */
16
+ import { containsSensitiveData } from './observation-contract.js';
17
+ import { createDebugger } from './utils/debug.js';
18
+ const debug = createDebugger('kindling');
19
+ // =============================================================================
20
+ // Sensitive Patterns
21
+ // =============================================================================
22
+ /**
23
+ * Known sensitive value patterns and their replacement strings
24
+ */
25
+ const SENSITIVE_PATTERNS = [
26
+ {
27
+ name: 'openai-api-key',
28
+ pattern: /sk-[a-zA-Z0-9]{20,}/g,
29
+ replacement: '[REDACTED:api-key]',
30
+ },
31
+ {
32
+ name: 'github-pat',
33
+ pattern: /ghp_[a-zA-Z0-9]{36,}/g,
34
+ replacement: '[REDACTED:github-token]',
35
+ },
36
+ {
37
+ name: 'github-fine-grained-pat',
38
+ pattern: /github_pat_[a-zA-Z0-9_]{36,}/g,
39
+ replacement: '[REDACTED:github-token]',
40
+ },
41
+ {
42
+ name: 'aws-access-key',
43
+ pattern: /AKIA[0-9A-Z]{16}/g,
44
+ replacement: '[REDACTED:aws-key]',
45
+ },
46
+ {
47
+ name: 'aws-secret-key',
48
+ pattern: /(?<=aws_secret_access_key\s*[=:]\s*)[^\s"',]+/gi,
49
+ replacement: '[REDACTED:aws-secret]',
50
+ },
51
+ {
52
+ name: 'hex-token',
53
+ pattern: /\b[0-9a-fA-F]{40,}\b/g,
54
+ replacement: '[REDACTED:token]',
55
+ },
56
+ {
57
+ name: 'email',
58
+ pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
59
+ replacement: '[REDACTED:email]',
60
+ },
61
+ {
62
+ name: 'password-value',
63
+ pattern: /(?<=(?:password|passwd|pwd)\s*[=:]\s*["']?)[^\s"',]+/gi,
64
+ replacement: '[REDACTED:password]',
65
+ },
66
+ {
67
+ name: 'bearer-token',
68
+ pattern: /(?<=Bearer\s+)[a-zA-Z0-9._~+/=-]{20,}/gi,
69
+ replacement: '[REDACTED:bearer-token]',
70
+ },
71
+ {
72
+ name: 'npm-token',
73
+ pattern: /npm_[a-zA-Z0-9]{36,}/g,
74
+ replacement: '[REDACTED:npm-token]',
75
+ },
76
+ ];
77
+ /**
78
+ * Validate that an observation does not contain sensitive data.
79
+ *
80
+ * Uses the contract-level `containsSensitiveData` check plus additional
81
+ * pattern matching for known credential formats.
82
+ *
83
+ * @param observation - The observation to validate
84
+ * @returns Validation result with issues if sensitive data found
85
+ */
86
+ export function validateNoSensitiveData(observation) {
87
+ const issues = [];
88
+ // Run the contract-level check first
89
+ const contractCheck = containsSensitiveData(observation);
90
+ if (contractCheck.hasSensitiveData) {
91
+ issues.push(...contractCheck.issues);
92
+ }
93
+ // Run additional pattern checks against the serialized payload
94
+ const serialized = JSON.stringify(observation);
95
+ for (const { name, pattern } of SENSITIVE_PATTERNS) {
96
+ // Reset lastIndex for global regex patterns
97
+ pattern.lastIndex = 0;
98
+ if (pattern.test(serialized)) {
99
+ issues.push(`Sensitive pattern detected: ${name}`);
100
+ }
101
+ }
102
+ if (issues.length > 0) {
103
+ debug('sensitive data detected in observation', {
104
+ issueCount: issues.length,
105
+ patterns: issues,
106
+ });
107
+ }
108
+ return {
109
+ hasSensitiveData: issues.length > 0,
110
+ issues,
111
+ };
112
+ }
113
+ // =============================================================================
114
+ // Redaction
115
+ // =============================================================================
116
+ /**
117
+ * Deep-clone an observation and redact all known sensitive patterns from
118
+ * string values. This operates on the JSON serialization to catch values
119
+ * regardless of nesting depth.
120
+ *
121
+ * The returned observation is safe to persist.
122
+ *
123
+ * @param observation - The observation to redact
124
+ * @returns A new observation with sensitive values replaced
125
+ */
126
+ export function redactSensitiveFields(observation) {
127
+ debug('redacting sensitive fields from observation', { kind: observation.kind });
128
+ let serialized = JSON.stringify(observation);
129
+ for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
130
+ // Reset lastIndex for global regex patterns
131
+ pattern.lastIndex = 0;
132
+ serialized = serialized.replace(pattern, replacement);
133
+ }
134
+ return JSON.parse(serialized);
135
+ }
@@ -0,0 +1,104 @@
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
+ * Kindling status summary returned by getKindlingStatus()
14
+ */
15
+ export interface KindlingStatus {
16
+ /** Whether Kindling is enabled in config */
17
+ enabled: boolean;
18
+ /** Total number of observations stored (undefined if disabled) */
19
+ observationCount?: number;
20
+ /** Database file size in bytes (undefined if disabled or no DB) */
21
+ dbSizeBytes?: number;
22
+ /** Configured retention period in days (undefined if disabled) */
23
+ retentionDays?: number;
24
+ /** ISO8601 timestamp of the most recent observation (undefined if none) */
25
+ lastObservationAt?: string;
26
+ }
27
+ /**
28
+ * Minimal config shape needed for status checks.
29
+ * This avoids importing from config.ts (built by other agent).
30
+ */
31
+ export interface KindlingStatusConfig {
32
+ enabled: boolean;
33
+ database?: string;
34
+ retention?: {
35
+ days?: number;
36
+ };
37
+ }
38
+ /**
39
+ * Minimal store interface for status queries.
40
+ * Implementations can be KindlingService, a direct SQLite handle, or a mock.
41
+ */
42
+ export interface KindlingStatusStore {
43
+ /**
44
+ * Count all observations in the store.
45
+ * Returns 0 if the store is empty.
46
+ */
47
+ countObservations(): Promise<number>;
48
+ /**
49
+ * Get the database file size in bytes.
50
+ * Returns 0 if the database does not exist or cannot be read.
51
+ */
52
+ getDatabaseSizeBytes(): Promise<number>;
53
+ /**
54
+ * Get the timestamp of the most recent observation.
55
+ * Returns undefined if no observations exist.
56
+ */
57
+ getLastObservationTimestamp(): Promise<string | undefined>;
58
+ }
59
+ /**
60
+ * Get Kindling integration status.
61
+ *
62
+ * If store is not provided or config is disabled, returns a minimal
63
+ * disabled status. This function never throws -- it handles errors
64
+ * gracefully and returns partial information.
65
+ *
66
+ * @param config - Kindling configuration (at minimum, { enabled: boolean })
67
+ * @param store - Optional store interface for querying observation data
68
+ * @returns KindlingStatus summary
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { getKindlingStatus } from './status.js';
73
+ *
74
+ * // Disabled (no store needed)
75
+ * const status = await getKindlingStatus({ enabled: false });
76
+ * // => { enabled: false }
77
+ *
78
+ * // Enabled with store
79
+ * const status = await getKindlingStatus(
80
+ * { enabled: true, retention: { days: 90 } },
81
+ * myStore,
82
+ * );
83
+ * // => { enabled: true, observationCount: 42, dbSizeBytes: 81920,
84
+ * // retentionDays: 90, lastObservationAt: '2026-02-15T...' }
85
+ * ```
86
+ */
87
+ export declare function getKindlingStatus(config: KindlingStatusConfig, store?: KindlingStatusStore): Promise<KindlingStatus>;
88
+ /**
89
+ * Format a KindlingStatus as a human-readable string.
90
+ * Useful for CLI output without coupling to a specific formatter.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const status = await getKindlingStatus(config, store);
95
+ * console.log(formatKindlingStatus(status));
96
+ * // Kindling: enabled
97
+ * // Observations: 42
98
+ * // Database size: 80 KB
99
+ * // Retention: 90 days
100
+ * // Last observation: 2026-02-15T10:30:00.000Z
101
+ * ```
102
+ */
103
+ export declare function formatKindlingStatus(status: KindlingStatus): string;
104
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IAEjB,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAMD;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAErC;;;OAGG;IACH,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAExC;;;OAGG;IACH,2BAA2B,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5D;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,KAAK,CAAC,EAAE,mBAAmB,GAC1B,OAAO,CAAC,cAAc,CAAC,CA4BzB;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA4BnE"}
package/dist/status.js ADDED
@@ -0,0 +1,136 @@
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
+ // Status Function
14
+ // =============================================================================
15
+ /**
16
+ * Get Kindling integration status.
17
+ *
18
+ * If store is not provided or config is disabled, returns a minimal
19
+ * disabled status. This function never throws -- it handles errors
20
+ * gracefully and returns partial information.
21
+ *
22
+ * @param config - Kindling configuration (at minimum, { enabled: boolean })
23
+ * @param store - Optional store interface for querying observation data
24
+ * @returns KindlingStatus summary
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { getKindlingStatus } from './status.js';
29
+ *
30
+ * // Disabled (no store needed)
31
+ * const status = await getKindlingStatus({ enabled: false });
32
+ * // => { enabled: false }
33
+ *
34
+ * // Enabled with store
35
+ * const status = await getKindlingStatus(
36
+ * { enabled: true, retention: { days: 90 } },
37
+ * myStore,
38
+ * );
39
+ * // => { enabled: true, observationCount: 42, dbSizeBytes: 81920,
40
+ * // retentionDays: 90, lastObservationAt: '2026-02-15T...' }
41
+ * ```
42
+ */
43
+ export async function getKindlingStatus(config, store) {
44
+ // Fast path: disabled
45
+ if (!config.enabled) {
46
+ return { enabled: false };
47
+ }
48
+ // Enabled but no store available
49
+ if (!store) {
50
+ return {
51
+ enabled: true,
52
+ retentionDays: config.retention?.days,
53
+ };
54
+ }
55
+ // Enabled with store -- query for status data
56
+ const [observationCount, dbSizeBytes, lastObservationAt] = await Promise.all([
57
+ safeCall(() => store.countObservations(), 0),
58
+ safeCall(() => store.getDatabaseSizeBytes(), 0),
59
+ safeCall(() => store.getLastObservationTimestamp(), undefined),
60
+ ]);
61
+ return {
62
+ enabled: true,
63
+ observationCount,
64
+ dbSizeBytes,
65
+ retentionDays: config.retention?.days,
66
+ lastObservationAt,
67
+ };
68
+ }
69
+ // =============================================================================
70
+ // Formatting Utilities
71
+ // =============================================================================
72
+ /**
73
+ * Format a KindlingStatus as a human-readable string.
74
+ * Useful for CLI output without coupling to a specific formatter.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const status = await getKindlingStatus(config, store);
79
+ * console.log(formatKindlingStatus(status));
80
+ * // Kindling: enabled
81
+ * // Observations: 42
82
+ * // Database size: 80 KB
83
+ * // Retention: 90 days
84
+ * // Last observation: 2026-02-15T10:30:00.000Z
85
+ * ```
86
+ */
87
+ export function formatKindlingStatus(status) {
88
+ const lines = [];
89
+ lines.push(`Kindling: ${status.enabled ? 'enabled' : 'disabled'}`);
90
+ if (!status.enabled) {
91
+ return lines.join('\n');
92
+ }
93
+ if (status.observationCount !== undefined) {
94
+ lines.push(`Observations: ${status.observationCount.toLocaleString()}`);
95
+ }
96
+ if (status.dbSizeBytes !== undefined) {
97
+ lines.push(`Database size: ${formatBytes(status.dbSizeBytes)}`);
98
+ }
99
+ if (status.retentionDays !== undefined) {
100
+ lines.push(`Retention: ${status.retentionDays} days`);
101
+ }
102
+ if (status.lastObservationAt) {
103
+ lines.push(`Last observation: ${status.lastObservationAt}`);
104
+ }
105
+ else if (status.observationCount === 0) {
106
+ lines.push('Last observation: none');
107
+ }
108
+ return lines.join('\n');
109
+ }
110
+ // =============================================================================
111
+ // Internal Helpers
112
+ // =============================================================================
113
+ /**
114
+ * Call an async function and return a fallback value on error.
115
+ * Ensures getKindlingStatus never throws.
116
+ */
117
+ async function safeCall(fn, fallback) {
118
+ try {
119
+ return await fn();
120
+ }
121
+ catch {
122
+ return fallback;
123
+ }
124
+ }
125
+ /**
126
+ * Format bytes into a human-readable string.
127
+ */
128
+ function formatBytes(bytes) {
129
+ if (bytes === 0)
130
+ return '0 B';
131
+ const units = ['B', 'KB', 'MB', 'GB'];
132
+ const k = 1024;
133
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), units.length - 1);
134
+ const value = bytes / Math.pow(k, i);
135
+ return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[i]}`;
136
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Minimal debug logging utility for kindling-integration package
3
+ *
4
+ * Self-contained to avoid dependency on @eddacraft/anvil-core
5
+ */
6
+ type DebugNamespace = 'kindling';
7
+ export declare function createDebugger(_namespace: DebugNamespace): (message: string, data?: unknown) => void;
8
+ export {};
9
+ //# sourceMappingURL=debug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,KAAK,cAAc,GAAG,UAAU,CAAC;AAsDjC,wBAAgB,cAAc,CAC5B,UAAU,EAAE,cAAc,GACzB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAE3C"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Minimal debug logging utility for kindling-integration package
3
+ *
4
+ * Self-contained to avoid dependency on @eddacraft/anvil-core
5
+ */
6
+ function isDebugEnabled() {
7
+ const anvilDebug = process.env.ANVIL_DEBUG;
8
+ const debug = process.env.DEBUG;
9
+ if (anvilDebug === '1' || anvilDebug === 'true') {
10
+ return true;
11
+ }
12
+ if (debug) {
13
+ if (debug.includes('anvil:*') || debug.includes('anvil:kindling')) {
14
+ return true;
15
+ }
16
+ }
17
+ return false;
18
+ }
19
+ function sanitizeForLog(value) {
20
+ let sanitized = value.replace(/\b(sk-|ghp_|ghu_)[A-Za-z0-9_-]+/g, '[REDACTED]');
21
+ sanitized = sanitized.replace(/Bearer\s+[A-Za-z0-9_.+/=-]+/g, 'Bearer [REDACTED]');
22
+ sanitized = sanitized.replace(/\b[0-9a-fA-F]{40,}\b/g, '[REDACTED]');
23
+ sanitized = sanitized.replace(/\b[A-Za-z0-9+/]{20,}={0,3}\b/g, '[REDACTED]');
24
+ return sanitized;
25
+ }
26
+ function debugLog(namespace, message, data) {
27
+ if (!isDebugEnabled()) {
28
+ return;
29
+ }
30
+ const timestamp = new Date().toISOString();
31
+ const prefix = `[${timestamp}] [anvil:${namespace}]`;
32
+ const sanitizedMessage = sanitizeForLog(message);
33
+ /* eslint-disable no-console -- debug utility */
34
+ if (data !== undefined) {
35
+ if (data instanceof Error) {
36
+ console.debug(`${prefix} ${sanitizedMessage}:`, sanitizeForLog(data.message));
37
+ if (data.stack) {
38
+ console.debug(`${prefix} Stack:`, sanitizeForLog(data.stack));
39
+ }
40
+ }
41
+ else if (typeof data === 'string') {
42
+ console.debug(`${prefix} ${sanitizedMessage}:`, sanitizeForLog(data));
43
+ }
44
+ else {
45
+ console.debug(`${prefix} ${sanitizedMessage}:`, data);
46
+ }
47
+ }
48
+ else {
49
+ console.debug(`${prefix} ${sanitizedMessage}`);
50
+ }
51
+ /* eslint-enable no-console */
52
+ }
53
+ export function createDebugger(_namespace) {
54
+ return (message, data) => debugLog('kindling', message, data);
55
+ }