@specferret/core 0.1.4 → 0.2.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.
@@ -1,17 +1,9 @@
1
- import { Database } from "bun:sqlite";
2
- import * as path from "node:path";
3
- import * as fs from "node:fs";
4
- import { randomUUID } from "node:crypto";
5
- import { findProjectRoot } from "../utils/paths.js";
6
- import type {
7
- DBStore,
8
- FerretNode,
9
- FerretContract,
10
- FerretDependency,
11
- FerretReconciliationLog,
12
- FerretPlacementDecision,
13
- NodeStatus,
14
- } from "./types.js";
1
+ import { Database } from 'bun:sqlite';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { findProjectRoot } from '../utils/paths.js';
6
+ import type { DBStore, FerretNode, FerretContract, FerretDependency, FerretReconciliationLog, FerretPlacementDecision, NodeStatus } from './types.js';
15
7
 
16
8
  export class SqliteStore implements DBStore {
17
9
  private db: Database | null = null;
@@ -19,36 +11,27 @@ export class SqliteStore implements DBStore {
19
11
 
20
12
  constructor(customPath?: string) {
21
13
  const root = findProjectRoot();
22
- const defaultPath = path.join(root, ".ferret", "graph.db");
23
- this.dbPath =
24
- customPath === ":memory:"
25
- ? ":memory:"
26
- : customPath
27
- ? path.resolve(root, customPath)
28
- : defaultPath;
14
+ const defaultPath = path.join(root, '.ferret', 'graph.db');
15
+ this.dbPath = customPath === ':memory:' ? ':memory:' : customPath ? path.resolve(root, customPath) : defaultPath;
29
16
  }
30
17
 
31
18
  async init(): Promise<void> {
32
19
  if (this.db) return;
33
20
 
34
- if (this.dbPath !== ":memory:") {
21
+ if (this.dbPath !== ':memory:') {
35
22
  const ferretDir = path.dirname(this.dbPath);
36
23
  if (!fs.existsSync(ferretDir)) {
37
24
  fs.mkdirSync(ferretDir, { recursive: true });
38
25
  }
39
- const gitignorePath = path.join(ferretDir, ".gitignore");
26
+ const gitignorePath = path.join(ferretDir, '.gitignore');
40
27
  try {
41
- fs.writeFileSync(
42
- gitignorePath,
43
- "*\n!.gitignore\n!context.json\n",
44
- "utf-8",
45
- );
28
+ fs.writeFileSync(gitignorePath, '*\n!.gitignore\n!context.json\n', 'utf-8');
46
29
  } catch {}
47
30
  }
48
31
 
49
32
  this.db = new Database(this.dbPath);
50
- this.db.exec("PRAGMA journal_mode = WAL;");
51
- this.db.exec("PRAGMA foreign_keys = ON;");
33
+ this.db.exec('PRAGMA journal_mode = WAL;');
34
+ this.db.exec('PRAGMA foreign_keys = ON;');
52
35
 
53
36
  this.db.exec(`
54
37
  CREATE TABLE IF NOT EXISTS ferret_nodes (
@@ -95,9 +78,15 @@ export class SqliteStore implements DBStore {
95
78
  `);
96
79
 
97
80
  try {
98
- this.db.exec(
99
- `ALTER TABLE ferret_contracts ADD COLUMN shape_schema TEXT NOT NULL DEFAULT '{}';`,
100
- );
81
+ this.db.exec(`ALTER TABLE ferret_contracts ADD COLUMN shape_schema TEXT NOT NULL DEFAULT '{}';`);
82
+ } catch {}
83
+
84
+ try {
85
+ this.db.exec(`ALTER TABLE ferret_contracts ADD COLUMN code_source_file TEXT;`);
86
+ } catch {}
87
+
88
+ try {
89
+ this.db.exec(`ALTER TABLE ferret_contracts ADD COLUMN code_source_symbol TEXT;`);
101
90
  } catch {}
102
91
  }
103
92
 
@@ -109,9 +98,7 @@ export class SqliteStore implements DBStore {
109
98
  }
110
99
 
111
100
  async getNodeByFilePath(filePath: string): Promise<FerretNode | null> {
112
- const row = this.db!.prepare(
113
- "SELECT * FROM ferret_nodes WHERE file_path = ?",
114
- ).get(filePath) as any;
101
+ const row = this.db!.prepare('SELECT * FROM ferret_nodes WHERE file_path = ?').get(filePath) as any;
115
102
  return row ?? null;
116
103
  }
117
104
 
@@ -130,22 +117,22 @@ export class SqliteStore implements DBStore {
130
117
  }
131
118
 
132
119
  async getAllContractIds(): Promise<string[]> {
133
- return (
134
- this.db!.prepare("SELECT id FROM ferret_contracts").all() as any[]
135
- ).map((r) => r.id);
120
+ return (this.db!.prepare('SELECT id FROM ferret_contracts').all() as any[]).map((r) => r.id);
136
121
  }
137
122
 
138
123
  async upsertContract(contract: FerretContract): Promise<void> {
139
124
  this.db!.prepare(
140
125
  `
141
- INSERT INTO ferret_contracts (id, node_id, shape_hash, shape_schema, type, status)
142
- VALUES (?, ?, ?, ?, ?, ?)
126
+ INSERT INTO ferret_contracts (id, node_id, shape_hash, shape_schema, type, status, code_source_file, code_source_symbol)
127
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
143
128
  ON CONFLICT(id) DO UPDATE SET
144
129
  node_id = excluded.node_id,
145
130
  shape_hash = excluded.shape_hash,
146
131
  shape_schema = excluded.shape_schema,
147
132
  type = excluded.type,
148
- status = excluded.status
133
+ status = excluded.status,
134
+ code_source_file = excluded.code_source_file,
135
+ code_source_symbol = excluded.code_source_symbol
149
136
  `,
150
137
  ).run(
151
138
  contract.id,
@@ -154,6 +141,8 @@ export class SqliteStore implements DBStore {
154
141
  contract.shape_schema,
155
142
  contract.type,
156
143
  contract.status,
144
+ contract.code_source_file ?? null,
145
+ contract.code_source_symbol ?? null,
157
146
  );
158
147
  }
159
148
 
@@ -164,21 +153,12 @@ export class SqliteStore implements DBStore {
164
153
  VALUES (?, ?, ?)
165
154
  ON CONFLICT(id) DO NOTHING
166
155
  `,
167
- ).run(
168
- dependency.id,
169
- dependency.source_node_id,
170
- dependency.target_contract_id,
171
- );
156
+ ).run(dependency.id, dependency.source_node_id, dependency.target_contract_id);
172
157
  }
173
158
 
174
- async replaceDependenciesForSourceNode(
175
- sourceNodeId: string,
176
- targetContractIds: string[],
177
- ): Promise<void> {
159
+ async replaceDependenciesForSourceNode(sourceNodeId: string, targetContractIds: string[]): Promise<void> {
178
160
  const uniqueTargets = [...new Set(targetContractIds)].sort();
179
- const deleteStatement = this.db!.prepare(
180
- "DELETE FROM ferret_dependencies WHERE source_node_id = ?",
181
- );
161
+ const deleteStatement = this.db!.prepare('DELETE FROM ferret_dependencies WHERE source_node_id = ?');
182
162
  const insertStatement = this.db!.prepare(
183
163
  `
184
164
  INSERT INTO ferret_dependencies (id, source_node_id, target_contract_id)
@@ -197,40 +177,28 @@ export class SqliteStore implements DBStore {
197
177
  }
198
178
 
199
179
  async getNodes(): Promise<FerretNode[]> {
200
- return this.db!.prepare(
201
- "SELECT * FROM ferret_nodes",
202
- ).all() as unknown as FerretNode[];
180
+ return this.db!.prepare('SELECT * FROM ferret_nodes').all() as unknown as FerretNode[];
203
181
  }
204
182
 
205
183
  async getNodesByStatus(status: NodeStatus): Promise<FerretNode[]> {
206
- return this.db!.prepare("SELECT * FROM ferret_nodes WHERE status = ?").all(
207
- status,
208
- ) as unknown as FerretNode[];
184
+ return this.db!.prepare('SELECT * FROM ferret_nodes WHERE status = ?').all(status) as unknown as FerretNode[];
209
185
  }
210
186
 
211
187
  async getContracts(): Promise<FerretContract[]> {
212
- return this.db!.prepare(
213
- "SELECT * FROM ferret_contracts",
214
- ).all() as unknown as FerretContract[];
188
+ return this.db!.prepare('SELECT * FROM ferret_contracts').all() as unknown as FerretContract[];
215
189
  }
216
190
 
217
191
  async getContract(id: string): Promise<FerretContract | null> {
218
- const row = this.db!.prepare(
219
- "SELECT * FROM ferret_contracts WHERE id = ?",
220
- ).get(id) as any;
192
+ const row = this.db!.prepare('SELECT * FROM ferret_contracts WHERE id = ?').get(id) as any;
221
193
  return row ?? null;
222
194
  }
223
195
 
224
196
  async getDependencies(): Promise<FerretDependency[]> {
225
- return this.db!.prepare(
226
- "SELECT * FROM ferret_dependencies",
227
- ).all() as unknown as FerretDependency[];
197
+ return this.db!.prepare('SELECT * FROM ferret_dependencies').all() as unknown as FerretDependency[];
228
198
  }
229
199
 
230
200
  async updateNodeStatus(nodeId: string, status: NodeStatus): Promise<void> {
231
- this.db!.prepare(
232
- "UPDATE ferret_nodes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
233
- ).run(status, nodeId);
201
+ this.db!.prepare('UPDATE ferret_nodes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(status, nodeId);
234
202
  }
235
203
 
236
204
  async insertReconciliationLog(log: FerretReconciliationLog): Promise<void> {
@@ -239,40 +207,25 @@ export class SqliteStore implements DBStore {
239
207
  INSERT INTO ferret_reconciliation_log (id, node_id, triggered_by, resolved_by, resolution_notes)
240
208
  VALUES (?, ?, ?, ?, ?)
241
209
  `,
242
- ).run(
243
- log.id,
244
- log.node_id,
245
- log.triggered_by,
246
- log.resolved_by ?? null,
247
- log.resolution_notes ?? null,
248
- );
210
+ ).run(log.id, log.node_id, log.triggered_by, log.resolved_by ?? null, log.resolution_notes ?? null);
249
211
  }
250
212
 
251
213
  async getReconciliationLogs(): Promise<FerretReconciliationLog[]> {
252
214
  return this.db!.prepare(
253
- "SELECT id, node_id, triggered_by, resolved_by, resolution_notes FROM ferret_reconciliation_log",
215
+ 'SELECT id, node_id, triggered_by, resolved_by, resolution_notes FROM ferret_reconciliation_log',
254
216
  ).all() as unknown as FerretReconciliationLog[];
255
217
  }
256
218
 
257
- async insertPlacementDecision(
258
- decision: FerretPlacementDecision,
259
- ): Promise<void> {
219
+ async insertPlacementDecision(decision: FerretPlacementDecision): Promise<void> {
260
220
  this.db!.prepare(
261
221
  `
262
222
  INSERT INTO ferret_placement_decisions (id, node_id, placed_by, reasoning)
263
223
  VALUES (?, ?, ?, ?)
264
224
  `,
265
- ).run(
266
- decision.id,
267
- decision.node_id,
268
- decision.placed_by,
269
- decision.reasoning ?? null,
270
- );
225
+ ).run(decision.id, decision.node_id, decision.placed_by, decision.reasoning ?? null);
271
226
  }
272
227
 
273
228
  async getPlacementDecisions(): Promise<FerretPlacementDecision[]> {
274
- return this.db!.prepare(
275
- "SELECT id, node_id, placed_by, reasoning FROM ferret_placement_decisions",
276
- ).all() as unknown as FerretPlacementDecision[];
229
+ return this.db!.prepare('SELECT id, node_id, placed_by, reasoning FROM ferret_placement_decisions').all() as unknown as FerretPlacementDecision[];
277
230
  }
278
231
  }
@@ -1,5 +1,5 @@
1
- export type NodeStatus = "stable" | "needs-review" | "roadmap" | "blocked";
2
- export type ContractStatus = "stable" | "roadmap" | "needs-review";
1
+ export type NodeStatus = 'stable' | 'needs-review' | 'roadmap' | 'blocked';
2
+ export type ContractStatus = 'stable' | 'roadmap' | 'needs-review';
3
3
 
4
4
  export interface FerretNode {
5
5
  id: string;
@@ -16,6 +16,10 @@ export interface FerretContract {
16
16
  shape_schema: string;
17
17
  type: string;
18
18
  status: ContractStatus;
19
+ /** Path to the TypeScript file this contract was generated from (code-first contracts only). */
20
+ code_source_file?: string;
21
+ /** TypeScript symbol name this contract was generated from (code-first contracts only). */
22
+ code_source_symbol?: string;
19
23
  }
20
24
 
21
25
  export interface FerretDependency {
@@ -73,10 +77,7 @@ export interface DBStore {
73
77
  upsertDependency(dependency: FerretDependency): Promise<void>;
74
78
 
75
79
  /** Replaces all dependency edges for a source node with the provided targets */
76
- replaceDependenciesForSourceNode(
77
- sourceNodeId: string,
78
- targetContractIds: string[],
79
- ): Promise<void>;
80
+ replaceDependenciesForSourceNode(sourceNodeId: string, targetContractIds: string[]): Promise<void>;
80
81
 
81
82
  /** Full graph retrieval methods for the Reconciler engine */
82
83
  getNodes(): Promise<FerretNode[]>;