@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/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2026 EddaCraft. All rights reserved.
2
+
3
+ PROPRIETARY AND CONFIDENTIAL
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ exclusive property of EddaCraft. Unauthorised copying, modification,
7
+ distribution, or use of this Software, via any medium, is strictly prohibited
8
+ without the express written permission of EddaCraft.
9
+
10
+ The Software is provided for evaluation and testing purposes only to authorised
11
+ beta testers. No licence is granted to use, copy, modify, or distribute the
12
+ Software for any other purpose.
13
+
14
+ For licensing enquiries, contact: legal@eddacraft.com
package/README.md ADDED
@@ -0,0 +1,542 @@
1
+ # @eddacraft/anvil-kindling-integration
2
+
3
+ > Mechanical contracts and integration layer for Kindling memory in Anvil v1
4
+
5
+ This package provides the **read-only, queryable memory contract** between Anvil
6
+ and Kindling. It defines what Anvil records (observations), how to retrieve them
7
+ (queries), and enforces that user-supplied AI can read but never mutate the
8
+ system of record.
9
+
10
+ No embedded AI. No magic. Just mechanics.
11
+
12
+ ## Governing Rule
13
+
14
+ > **Kindling is a system of record, not a reasoning engine.** Queries may
15
+ > retrieve facts; interpretation is the caller's responsibility.
16
+
17
+ Anvil enforces this mechanically:
18
+
19
+ - User-supplied AI may **read**, but may not **mutate, infer, or generalise**
20
+ via Kindling
21
+ - All queries are **bounded, explicit, and evidence-preserving**
22
+ - No free-text search. No global scans. No cross-project reads.
23
+
24
+ ## Architecture
25
+
26
+ ```
27
+ +----------------+
28
+ | Kindling |
29
+ | (SQLite DB) |
30
+ +----------------+
31
+ ^ |
32
+ Write | | Read
33
+ Only | | Only
34
+ | v
35
+ +-----------------+-------------------+
36
+ | |
37
+ +----v------+ +-------v--------+
38
+ | Anvil | | User AI |
39
+ | Emits | | (BYO-AI) |
40
+ | Facts | | Queries + |
41
+ +----------+ | Interprets |
42
+ +----------------+
43
+
44
+ Truth flows one way: Anvil -> Kindling -> AI
45
+ AI never writes back to Kindling
46
+ ```
47
+
48
+ ## Package Structure
49
+
50
+ ```
51
+ packages/kindling-integration/
52
+ src/
53
+ observation-contract.ts # Write-only (11 observation kinds)
54
+ query-contract.ts # Read-only (4 query scopes)
55
+ index.ts # Public exports
56
+ kindling-service.ts # Core service (emit + query)
57
+ config.ts # Configuration schema
58
+ query-service.ts # Read-only query API
59
+ query-limits.ts # Anti-vacuum-cleaner enforcement
60
+ sensitive-data-validator.ts # Secret detection
61
+ retention.ts # Auto-pruning
62
+ status.ts # Status utility (decoupled from CLI)
63
+ malicious-ai.test.ts # Read-only enforcement tests
64
+ emitters/
65
+ session-emitter.ts # session_start / session_end
66
+ gate-emitter.ts # gate_evaluated
67
+ action-emitter.ts # action_executed
68
+ plan-emitter.ts # plan_created / edited / approved / rejected
69
+ human-input-emitter.ts # human_input
70
+ constraint-emitter.ts # constraint_applied
71
+ error-emitter.ts # error
72
+ benchmarks/
73
+ emission-overhead.bench.ts # Performance validation
74
+ scripts/
75
+ generate-openapi.ts # OpenAPI 3.1 spec generator
76
+ CONTRACTS.md # Contract summary
77
+ README.md # This file
78
+ openapi.json # Generated OpenAPI spec
79
+ ```
80
+
81
+ ## Contracts Overview
82
+
83
+ ### 1. Observation Contract (Write-Only)
84
+
85
+ **File:** [`src/observation-contract.ts`](./src/observation-contract.ts)
86
+
87
+ Defines the 11 observation kinds Anvil must emit to be "Kindling-complete":
88
+
89
+ | Kind | Purpose | When Emitted |
90
+ | -------------------- | ------------------------- | ------------------------------- |
91
+ | `session_start` | Session recording spine | Every Anvil run starts |
92
+ | `session_end` | Session outcome + summary | Every Anvil run completes |
93
+ | `plan_created` | Plan lifecycle tracking | New plan authored |
94
+ | `plan_edited` | Plan version history | Plan modified |
95
+ | `plan_approved` | Human approval | User approves plan |
96
+ | `plan_rejected` | Human rejection | User rejects plan |
97
+ | `action_executed` | Action provenance | Command/tool/file operation |
98
+ | `gate_evaluated` | Gate check result | Every gate evaluation |
99
+ | `constraint_applied` | Decision constraint | Action prevented by rule/policy |
100
+ | `human_input` | Human decision | Approval/override/rejection |
101
+ | `error` | Failure history | Command/tool/execution error |
102
+
103
+ **All observations are:**
104
+
105
+ - Immutable (write-once)
106
+ - Timestamped (ISO8601)
107
+ - Linked (session_id, plan_id, gate_id, action_id)
108
+ - Sanitised (no secrets, redacted commands)
109
+ - Facts only (no interpretation, no inference)
110
+
111
+ ### 2. Query Contract (Read-Only)
112
+
113
+ **File:** [`src/query-contract.ts`](./src/query-contract.ts)
114
+
115
+ Defines 4 bounded query scopes:
116
+
117
+ | Scope | Question | Required ID |
118
+ | --------- | ------------------------------------- | -------------- |
119
+ | `session` | "What happened in this run?" | `session_id` |
120
+ | `plan` | "What happened because of this plan?" | `plan_id` |
121
+ | `gate` | "Why did this gate pass/fail?" | `gate_eval_id` |
122
+ | `action` | "What exactly did this action do?" | `action_id` |
123
+
124
+ ## Service Setup and Configuration
125
+
126
+ ### Configuration
127
+
128
+ Kindling is opt-in and disabled by default:
129
+
130
+ ```typescript
131
+ // In .anvilrc
132
+ {
133
+ "kindling": {
134
+ "enabled": true,
135
+ "database_path": ".anvil/kindling.db",
136
+ "retention": {
137
+ "days": 90,
138
+ "auto_prune": false
139
+ },
140
+ "capture": {
141
+ "sessions": true,
142
+ "plans": true,
143
+ "gates": true,
144
+ "actions": true,
145
+ "constraints": true,
146
+ "human_inputs": true,
147
+ "errors": true
148
+ },
149
+ "query_limits": {
150
+ "max_results": 100,
151
+ "max_payload_bytes": 1048576
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Service Initialisation
158
+
159
+ ```typescript
160
+ import { KindlingService } from '@eddacraft/anvil-kindling-integration';
161
+
162
+ const kindling = new KindlingService(store, config);
163
+ ```
164
+
165
+ ## Emitter Usage
166
+
167
+ Emitters are specialised helpers for each observation kind. They construct
168
+ properly-typed observations and emit them through the service.
169
+
170
+ ### Session Emitter
171
+
172
+ ```typescript
173
+ import { SessionStartObservation } from '@eddacraft/anvil-kindling-integration/observation';
174
+
175
+ // Emit at command entry
176
+ const obs: SessionStartObservation = {
177
+ kind: 'session_start',
178
+ session_id: '550e8400-e29b-41d4-a716-446655440000',
179
+ timestamp: new Date().toISOString(),
180
+ context: {
181
+ working_directory: '/home/user/project',
182
+ anvil_version: '1.0.0',
183
+ command: 'anvil check',
184
+ args: ['--watch'],
185
+ environment: 'development',
186
+ },
187
+ };
188
+ await kindling.emit(obs);
189
+ ```
190
+
191
+ ### Gate Emitter
192
+
193
+ ```typescript
194
+ import { GateEvaluatedObservation } from '@eddacraft/anvil-kindling-integration/observation';
195
+
196
+ const obs: GateEvaluatedObservation = {
197
+ kind: 'gate_evaluated',
198
+ session_id: sessionId,
199
+ timestamp: new Date().toISOString(),
200
+ gate_eval_id: 'gate-eval-001',
201
+ gate_id: 'architecture',
202
+ inputs: {
203
+ file_count: 12,
204
+ changed_files: ['src/index.ts', 'src/config.ts'],
205
+ },
206
+ outcome: 'pass',
207
+ rules_evaluated: ['no-circular-deps', 'layer-boundaries'],
208
+ enforcement: 'blocking',
209
+ duration_ms: 250,
210
+ };
211
+ await kindling.emit(obs);
212
+ ```
213
+
214
+ ### Validation Before Emission
215
+
216
+ Always validate observations before emitting:
217
+
218
+ ```typescript
219
+ import {
220
+ validateObservation,
221
+ containsSensitiveData,
222
+ } from '@eddacraft/anvil-kindling-integration';
223
+
224
+ const validation = validateObservation(obs);
225
+ if (!validation.success) {
226
+ console.error('Invalid observation:', validation.error);
227
+ return;
228
+ }
229
+
230
+ const sensitiveCheck = containsSensitiveData(validation.data);
231
+ if (sensitiveCheck.hasSensitiveData) {
232
+ console.error('Sensitive data detected:', sensitiveCheck.issues);
233
+ return;
234
+ }
235
+
236
+ await kindling.emit(validation.data);
237
+ ```
238
+
239
+ ## Query API Usage
240
+
241
+ ### Session Query
242
+
243
+ ```typescript
244
+ import {
245
+ SessionQuery,
246
+ validateQueryRequest,
247
+ } from '@eddacraft/anvil-kindling-integration';
248
+
249
+ const query: SessionQuery = {
250
+ scope: 'session',
251
+ session_id: '550e8400-e29b-41d4-a716-446655440000',
252
+ shape: 'timeline',
253
+ format: 'json',
254
+ max_results: 100,
255
+ };
256
+
257
+ const result = validateQueryRequest(query);
258
+ if (result.success) {
259
+ const response = await kindling.query(result.data);
260
+ console.log(`${response.metadata.result_count} observations`);
261
+ console.log(`Truncated: ${response.metadata.truncated}`);
262
+ for (const obs of response.observations) {
263
+ console.log(` [${obs.timestamp}] ${obs.kind}`);
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Plan Query (Cross-Session)
269
+
270
+ ```typescript
271
+ import { PlanQuery } from '@eddacraft/anvil-kindling-integration';
272
+
273
+ // This is the ONLY cross-session read allowed
274
+ const query: PlanQuery = {
275
+ scope: 'plan',
276
+ plan_id: 'plan-001',
277
+ shape: 'entity',
278
+ include_executions: true,
279
+ include_versions: true,
280
+ };
281
+
282
+ const response = await kindling.query(query);
283
+ ```
284
+
285
+ ### Gate Query
286
+
287
+ ```typescript
288
+ import { GateQuery } from '@eddacraft/anvil-kindling-integration';
289
+
290
+ const query: GateQuery = {
291
+ scope: 'gate',
292
+ gate_eval_id: 'gate-eval-456',
293
+ shape: 'entity',
294
+ };
295
+
296
+ const response = await kindling.query(query);
297
+ // Returns: gate evaluation with rule IDs, inputs (sanitised), outcome
298
+ ```
299
+
300
+ ### Action Query
301
+
302
+ ```typescript
303
+ import { ActionQuery } from '@eddacraft/anvil-kindling-integration';
304
+
305
+ const query: ActionQuery = {
306
+ scope: 'action',
307
+ action_id: 'action-789',
308
+ shape: 'entity',
309
+ include_approval_chain: true,
310
+ };
311
+
312
+ const response = await kindling.query(query);
313
+ // Returns: action details, redacted command, governance links
314
+ ```
315
+
316
+ ## CLI Command Mapping
317
+
318
+ All queries have CLI equivalents. The CLI is a **thin wrapper** over the same
319
+ query surface.
320
+
321
+ | CLI Command | Query Scope | Query Type |
322
+ | ------------------------ | ----------- | -------------- |
323
+ | `anvil run show <id>` | session | `SessionQuery` |
324
+ | `anvil plan trace <id>` | plan | `PlanQuery` |
325
+ | `anvil gate show <id>` | gate | `GateQuery` |
326
+ | `anvil action show <id>` | action | `ActionQuery` |
327
+
328
+ **Examples:**
329
+
330
+ ```bash
331
+ # Session timeline (JSON)
332
+ anvil run show 550e8400-e29b-41d4-a716-446655440000 --json
333
+
334
+ # Plan trace with linked executions
335
+ anvil plan trace plan-001 --json
336
+
337
+ # Gate evaluation details
338
+ anvil gate show gate-eval-456 --json
339
+
340
+ # Action with approval chain
341
+ anvil action show action-789 --json
342
+ ```
343
+
344
+ ## Security Model
345
+
346
+ ### Read-Only Enforcement
347
+
348
+ Operations that **MUST NOT** exist in the query API:
349
+
350
+ - `write()` / `update()` / `delete()`
351
+ - `annotate()` / `tag()`
352
+ - `learn()` / `embed()` / `infer()`
353
+
354
+ **If user AI wants memory, it must bring its own store.** The malicious AI test
355
+ suite (`src/malicious-ai.test.ts`) proves these boundaries hold.
356
+
357
+ ### Sensitive Data Detection
358
+
359
+ Observations are validated before emission to catch:
360
+
361
+ - Passwords, tokens, API keys
362
+ - AWS credentials
363
+ - Private keys
364
+ - Email addresses (flagged as potentially sensitive)
365
+
366
+ ```typescript
367
+ const check = containsSensitiveData(obs);
368
+ if (check.hasSensitiveData) {
369
+ // Reject observation, log issues
370
+ console.error(check.issues);
371
+ }
372
+ ```
373
+
374
+ ### Query Limits (Anti-Vacuum-Cleaner)
375
+
376
+ Every query enforces:
377
+
378
+ - `max_results`: Default 100, max 1000
379
+ - `max_payload_bytes`: Default 1MB, max 10MB
380
+ - Mandatory scoping (scope + explicit ID)
381
+ - No free-text search, no global scans
382
+
383
+ ### Output Guarantees
384
+
385
+ Every Kindling response guarantees:
386
+
387
+ 1. **Stable field names** -- no field names change between queries
388
+ 2. **Explicit timestamps** -- every observation has ISO8601 timestamp
389
+ 3. **Explicit links** -- provenance via typed links (caused_by, governed_by,
390
+ approved_by)
391
+ 4. **No hidden inference** -- payload contains only raw facts
392
+ 5. **No reordered history** -- observations returned in recorded order
393
+
394
+ ## Status Utility
395
+
396
+ Check Kindling integration status without coupling to any CLI framework:
397
+
398
+ ```typescript
399
+ import {
400
+ getKindlingStatus,
401
+ formatKindlingStatus,
402
+ } from '@eddacraft/anvil-kindling-integration';
403
+
404
+ // Quick check (no store needed)
405
+ const status = await getKindlingStatus({ enabled: false });
406
+ // => { enabled: false }
407
+
408
+ // Full status with store
409
+ const status = await getKindlingStatus(
410
+ { enabled: true, retention: { days: 90 } },
411
+ myStore
412
+ );
413
+ console.log(formatKindlingStatus(status));
414
+ // Kindling: enabled
415
+ // Observations: 42
416
+ // Database size: 80 KB
417
+ // Retention: 90 days
418
+ // Last observation: 2026-02-15T10:30:00.000Z
419
+ ```
420
+
421
+ ## BYO-AI Integration Example
422
+
423
+ User brings their own AI (e.g., Claude via API):
424
+
425
+ ```typescript
426
+ import {
427
+ QueryRequest,
428
+ QueryResponse,
429
+ } from '@eddacraft/anvil-kindling-integration';
430
+
431
+ async function explainGateFailure(gateEvalId: string): Promise<string> {
432
+ // 1. Query Kindling (read-only, bounded)
433
+ const query: QueryRequest = {
434
+ scope: 'gate',
435
+ gate_eval_id: gateEvalId,
436
+ shape: 'entity',
437
+ format: 'json',
438
+ };
439
+
440
+ const response: QueryResponse = await kindling.query(query);
441
+
442
+ // 2. AI interprets facts (using external AI service)
443
+ const prompt = `
444
+ Explain why this gate failed based on these facts:
445
+ ${JSON.stringify(response.observations, null, 2)}
446
+ `;
447
+
448
+ const explanation = await callExternalAI(prompt);
449
+
450
+ // 3. AI stores interpretation in its own memory (NOT in Kindling)
451
+ await userAI.memory.store({
452
+ type: 'gate_failure_explanation',
453
+ gate_eval_id: gateEvalId,
454
+ explanation,
455
+ generated_at: new Date().toISOString(),
456
+ });
457
+
458
+ return explanation;
459
+ }
460
+ ```
461
+
462
+ **Key points:**
463
+
464
+ - AI reads from Kindling (bounded query)
465
+ - AI interprets facts (external reasoning)
466
+ - AI stores conclusions in **its own memory** (not Kindling)
467
+ - Kindling remains immutable system of record
468
+
469
+ ## OpenAPI Spec Generation
470
+
471
+ Generate a machine-readable OpenAPI 3.1 spec from the query contracts:
472
+
473
+ ```bash
474
+ npx tsx scripts/generate-openapi.ts
475
+ # Outputs: openapi.json
476
+ ```
477
+
478
+ Use the spec for:
479
+
480
+ - **Client library generation** (TypeScript, Python, Go, Rust, etc.)
481
+ - **API documentation** (Swagger UI, Redoc)
482
+ - **Contract testing** (validate implementations against the spec)
483
+
484
+ ```bash
485
+ # Generate TypeScript client
486
+ npx openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ./clients/ts
487
+
488
+ # Generate Python client
489
+ openapi-generator-cli generate -i openapi.json -g python -o ./clients/python
490
+ ```
491
+
492
+ ## Performance
493
+
494
+ Observation emission must add < 50ms overhead (async, non-blocking). The
495
+ benchmark suite validates this:
496
+
497
+ ```bash
498
+ pnpm bench --filter kindling-integration
499
+ ```
500
+
501
+ With a no-op store, emission overhead is typically < 1ms per observation
502
+ (validation + sensitive data check).
503
+
504
+ ## Running Tests
505
+
506
+ ```bash
507
+ # All tests
508
+ pnpm test --filter kindling-integration
509
+
510
+ # Malicious AI test suite only
511
+ pnpm test --filter kindling-integration -- --testNamePattern="malicious-ai"
512
+
513
+ # Benchmarks
514
+ pnpm bench --filter kindling-integration
515
+ ```
516
+
517
+ ## Explicit Non-Goals (v1)
518
+
519
+ The following are **OUT OF SCOPE** for v1:
520
+
521
+ - Semantic search / similarity queries / embeddings
522
+ - Cross-plan discovery (except explicit plan_id lookup)
523
+ - Learned relevance / pattern detection
524
+ - Auto-summaries stored in Kindling
525
+ - AI-generated annotations stored in Kindling
526
+ - Real-time streaming dashboard
527
+ - Team-level memory sharing
528
+
529
+ **These belong to Edda / Ember, not Kindling v1.**
530
+
531
+ ## Licence
532
+
533
+ Copyright (c) 2026 EddaCraft. All rights reserved. See [LICENSE](../../LICENSE)
534
+ for details.
535
+
536
+ ## See Also
537
+
538
+ - [CONTRACTS.md](./CONTRACTS.md) -- One-page contract summary
539
+ - [Kindling Integration Plan](../../plans/modules/kindling-integration.aps.md)
540
+ -- APS module specification
541
+ - [Kindling Repository](https://github.com/EddaCraft/kindling) -- Core Kindling
542
+ implementation
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Anvil-Kindling Adapter
3
+ *
4
+ * Maps Anvil's 11 observation kinds (rich, domain-specific schemas)
5
+ * to Kindling core's generic observation model for storage and retrieval.
6
+ *
7
+ * Anvil observation data is serialized to `content` as JSON.
8
+ * The original Anvil kind is preserved in `provenance.anvil_kind`.
9
+ */
10
+ import type { KindlingService, Capsule, ID } from '@eddacraft/kindling-core';
11
+ import type { Observation as AnvilObservation } from './observation-contract.js';
12
+ export interface AnvilKindlingAdapterConfig {
13
+ service: KindlingService;
14
+ /** Repo path for scope isolation */
15
+ repoId?: string;
16
+ }
17
+ /**
18
+ * Bridges Anvil observation emission to Kindling storage.
19
+ *
20
+ * Usage:
21
+ * ```ts
22
+ * const adapter = new AnvilKindlingAdapter({ service });
23
+ * const capsule = adapter.startSession(sessionId, scopeIds);
24
+ * adapter.emit(observation);
25
+ * adapter.endSession(capsule.id);
26
+ * ```
27
+ */
28
+ export declare class AnvilKindlingAdapter {
29
+ private service;
30
+ private repoId;
31
+ constructor(config: AnvilKindlingAdapterConfig);
32
+ /**
33
+ * Open a Kindling capsule for an Anvil session.
34
+ * Call this when a CLI command starts.
35
+ */
36
+ startSession(sessionId: string, intent: string): Capsule;
37
+ /**
38
+ * Close the capsule for a session.
39
+ * Call this when a CLI command ends.
40
+ */
41
+ endSession(capsuleId: ID, summaryContent?: string): Capsule;
42
+ /**
43
+ * Emit an Anvil observation to Kindling.
44
+ * The rich Anvil schema is serialized to content; the original kind
45
+ * is preserved in provenance for filtering.
46
+ */
47
+ emit(observation: AnvilObservation, capsuleId?: ID): void;
48
+ }
49
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAqBjF,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,eAAe,CAAC;IACzB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAAqB;gBAEvB,MAAM,EAAE,0BAA0B;IAM9C;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAYxD;;;OAGG;IACH,UAAU,CAAC,SAAS,EAAE,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO;IAQ3D;;;;OAIG;IACH,IAAI,CAAC,WAAW,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE,EAAE,GAAG,IAAI;CAuB1D"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Anvil-Kindling Adapter
3
+ *
4
+ * Maps Anvil's 11 observation kinds (rich, domain-specific schemas)
5
+ * to Kindling core's generic observation model for storage and retrieval.
6
+ *
7
+ * Anvil observation data is serialized to `content` as JSON.
8
+ * The original Anvil kind is preserved in `provenance.anvil_kind`.
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ import { OBSERVATION_CONTRACT_VERSION } from './observation-contract.js';
12
+ import { createDebugger } from './utils/debug.js';
13
+ const debug = createDebugger('kindling');
14
+ /** Map Anvil observation kinds to Kindling's generic observation kinds */
15
+ const KIND_MAP = {
16
+ session_start: 'message',
17
+ session_end: 'message',
18
+ plan_created: 'message',
19
+ plan_edited: 'message',
20
+ plan_approved: 'message',
21
+ plan_rejected: 'message',
22
+ action_executed: 'command',
23
+ gate_evaluated: 'command',
24
+ constraint_applied: 'message',
25
+ human_input: 'message',
26
+ error: 'error',
27
+ };
28
+ /**
29
+ * Bridges Anvil observation emission to Kindling storage.
30
+ *
31
+ * Usage:
32
+ * ```ts
33
+ * const adapter = new AnvilKindlingAdapter({ service });
34
+ * const capsule = adapter.startSession(sessionId, scopeIds);
35
+ * adapter.emit(observation);
36
+ * adapter.endSession(capsule.id);
37
+ * ```
38
+ */
39
+ export class AnvilKindlingAdapter {
40
+ service;
41
+ repoId;
42
+ constructor(config) {
43
+ this.service = config.service;
44
+ this.repoId = config.repoId;
45
+ debug('AnvilKindlingAdapter created', { repoId: config.repoId });
46
+ }
47
+ /**
48
+ * Open a Kindling capsule for an Anvil session.
49
+ * Call this when a CLI command starts.
50
+ */
51
+ startSession(sessionId, intent) {
52
+ debug('starting session', { sessionId, intent });
53
+ return this.service.openCapsule({
54
+ type: 'session',
55
+ intent,
56
+ scopeIds: {
57
+ sessionId,
58
+ repoId: this.repoId,
59
+ },
60
+ });
61
+ }
62
+ /**
63
+ * Close the capsule for a session.
64
+ * Call this when a CLI command ends.
65
+ */
66
+ endSession(capsuleId, summaryContent) {
67
+ debug('ending session', { capsuleId });
68
+ return this.service.closeCapsule(capsuleId, {
69
+ generateSummary: !!summaryContent,
70
+ summaryContent,
71
+ });
72
+ }
73
+ /**
74
+ * Emit an Anvil observation to Kindling.
75
+ * The rich Anvil schema is serialized to content; the original kind
76
+ * is preserved in provenance for filtering.
77
+ */
78
+ emit(observation, capsuleId) {
79
+ debug('emitting observation via adapter', { kind: observation.kind, capsuleId });
80
+ const kindlingObs = {
81
+ id: randomUUID(),
82
+ kind: KIND_MAP[observation.kind],
83
+ content: JSON.stringify(observation),
84
+ provenance: {
85
+ anvil_kind: observation.kind,
86
+ anvil_contract_version: OBSERVATION_CONTRACT_VERSION,
87
+ },
88
+ ts: Date.now(),
89
+ scopeIds: {
90
+ sessionId: observation.session_id,
91
+ repoId: this.repoId,
92
+ },
93
+ redacted: false,
94
+ };
95
+ this.service.appendObservation(kindlingObs, {
96
+ capsuleId,
97
+ validate: true,
98
+ });
99
+ }
100
+ }