@specferret/core 0.1.0 → 0.1.2

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/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @specferret/core
2
+
3
+ Core engine for SpecFerret contract extraction, validation, graph reconciliation, and drift analysis.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@specferret/core)](https://www.npmjs.com/package/@specferret/core)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
7
+
8
+ ## What this package is for
9
+
10
+ `@specferret/core` is the runtime library behind the CLI. Use it when you want to embed SpecFerret behavior in scripts, custom tooling, or internal workflows.
11
+
12
+ It includes:
13
+
14
+ - Store implementations (`sqlite`, `postgres`)
15
+ - Frontmatter extraction and schema validation
16
+ - Reconciler logic for direct/transitive impact
17
+ - Context graph generation utilities
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm i @specferret/core
23
+ ```
24
+
25
+ ## Runtime requirements
26
+
27
+ - Bun 1.0+
28
+
29
+ SpecFerret core is Bun-first and uses Bun-compatible runtime behavior.
30
+
31
+ ## Typical consumers
32
+
33
+ - Internal platform tooling that needs drift checks in-process
34
+ - CI wrappers that need machine-readable drift metadata
35
+ - Custom developer workflows on top of SpecFerret reconciliation data
36
+
37
+ ## Validation status
38
+
39
+ Core behavior is validated through the Sprint 5 external branch-matrix harness (spec-kit + BMAD), including explicit assertions for:
40
+
41
+ - S40 transitive depth thresholds
42
+ - S41 review proof artifacts
43
+ - S42 extract determinism
44
+
45
+ Latest green runs:
46
+
47
+ - spec-kit: https://github.com/BenGardiner123/spec-ferret-validation-spec-kit/actions/runs/23962649755
48
+ - BMAD: https://github.com/BenGardiner123/specferret-validation-bmad/actions/runs/23962652352
49
+
50
+ ## Looking for the CLI?
51
+
52
+ Install `@specferret/cli` if you want the end-user command interface:
53
+
54
+ ```bash
55
+ bun install -g @specferret/cli
56
+ ```
57
+
58
+ ## Links
59
+
60
+ - Source: https://github.com/BenGardiner123/spec-ferret
61
+ - Docs: https://specferret.dev
62
+ - CLI package: https://www.npmjs.com/package/@specferret/cli
@@ -33,48 +33,48 @@ export class SqliteStore {
33
33
  this.db = new Database(this.dbPath);
34
34
  this.db.exec("PRAGMA journal_mode = WAL;");
35
35
  this.db.exec("PRAGMA foreign_keys = ON;");
36
- this.db.exec(`
37
- CREATE TABLE IF NOT EXISTS ferret_nodes (
38
- id TEXT PRIMARY KEY,
39
- file_path TEXT NOT NULL,
40
- hash TEXT NOT NULL,
41
- status TEXT NOT NULL,
42
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
43
- );
44
-
45
- CREATE TABLE IF NOT EXISTS ferret_contracts (
46
- id TEXT PRIMARY KEY,
47
- node_id TEXT NOT NULL,
48
- shape_hash TEXT NOT NULL,
49
- shape_schema TEXT NOT NULL DEFAULT '{}',
50
- type TEXT NOT NULL,
51
- status TEXT NOT NULL,
52
- FOREIGN KEY (node_id) REFERENCES ferret_nodes(id)
53
- );
54
-
55
- CREATE TABLE IF NOT EXISTS ferret_dependencies (
56
- id TEXT PRIMARY KEY,
57
- source_node_id TEXT NOT NULL,
58
- target_contract_id TEXT NOT NULL,
59
- FOREIGN KEY (source_node_id) REFERENCES ferret_nodes(id)
60
- );
61
-
62
- CREATE TABLE IF NOT EXISTS ferret_reconciliation_log (
63
- id TEXT PRIMARY KEY,
64
- node_id TEXT NOT NULL,
65
- triggered_by TEXT NOT NULL,
66
- resolved_by TEXT,
67
- resolution_notes TEXT,
68
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
69
- );
70
-
71
- CREATE TABLE IF NOT EXISTS ferret_placement_decisions (
72
- id TEXT PRIMARY KEY,
73
- node_id TEXT NOT NULL,
74
- placed_by TEXT NOT NULL,
75
- reasoning TEXT,
76
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
77
- );
36
+ this.db.exec(`
37
+ CREATE TABLE IF NOT EXISTS ferret_nodes (
38
+ id TEXT PRIMARY KEY,
39
+ file_path TEXT NOT NULL,
40
+ hash TEXT NOT NULL,
41
+ status TEXT NOT NULL,
42
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS ferret_contracts (
46
+ id TEXT PRIMARY KEY,
47
+ node_id TEXT NOT NULL,
48
+ shape_hash TEXT NOT NULL,
49
+ shape_schema TEXT NOT NULL DEFAULT '{}',
50
+ type TEXT NOT NULL,
51
+ status TEXT NOT NULL,
52
+ FOREIGN KEY (node_id) REFERENCES ferret_nodes(id)
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS ferret_dependencies (
56
+ id TEXT PRIMARY KEY,
57
+ source_node_id TEXT NOT NULL,
58
+ target_contract_id TEXT NOT NULL,
59
+ FOREIGN KEY (source_node_id) REFERENCES ferret_nodes(id)
60
+ );
61
+
62
+ CREATE TABLE IF NOT EXISTS ferret_reconciliation_log (
63
+ id TEXT PRIMARY KEY,
64
+ node_id TEXT NOT NULL,
65
+ triggered_by TEXT NOT NULL,
66
+ resolved_by TEXT,
67
+ resolution_notes TEXT,
68
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS ferret_placement_decisions (
72
+ id TEXT PRIMARY KEY,
73
+ node_id TEXT NOT NULL,
74
+ placed_by TEXT NOT NULL,
75
+ reasoning TEXT,
76
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
77
+ );
78
78
  `);
79
79
  try {
80
80
  this.db.exec(`ALTER TABLE ferret_contracts ADD COLUMN shape_schema TEXT NOT NULL DEFAULT '{}';`);
@@ -92,44 +92,44 @@ export class SqliteStore {
92
92
  return row ?? null;
93
93
  }
94
94
  async upsertNode(node) {
95
- this.db.prepare(`
96
- INSERT INTO ferret_nodes (id, file_path, hash, status)
97
- VALUES (?, ?, ?, ?)
98
- ON CONFLICT(id) DO UPDATE SET
99
- file_path = excluded.file_path,
100
- hash = excluded.hash,
101
- status = excluded.status,
102
- updated_at = CURRENT_TIMESTAMP
95
+ this.db.prepare(`
96
+ INSERT INTO ferret_nodes (id, file_path, hash, status)
97
+ VALUES (?, ?, ?, ?)
98
+ ON CONFLICT(id) DO UPDATE SET
99
+ file_path = excluded.file_path,
100
+ hash = excluded.hash,
101
+ status = excluded.status,
102
+ updated_at = CURRENT_TIMESTAMP
103
103
  `).run(node.id, node.file_path, node.hash, node.status);
104
104
  }
105
105
  async getAllContractIds() {
106
106
  return this.db.prepare("SELECT id FROM ferret_contracts").all().map((r) => r.id);
107
107
  }
108
108
  async upsertContract(contract) {
109
- this.db.prepare(`
110
- INSERT INTO ferret_contracts (id, node_id, shape_hash, shape_schema, type, status)
111
- VALUES (?, ?, ?, ?, ?, ?)
112
- ON CONFLICT(id) DO UPDATE SET
113
- node_id = excluded.node_id,
114
- shape_hash = excluded.shape_hash,
115
- shape_schema = excluded.shape_schema,
116
- type = excluded.type,
117
- status = excluded.status
109
+ this.db.prepare(`
110
+ INSERT INTO ferret_contracts (id, node_id, shape_hash, shape_schema, type, status)
111
+ VALUES (?, ?, ?, ?, ?, ?)
112
+ ON CONFLICT(id) DO UPDATE SET
113
+ node_id = excluded.node_id,
114
+ shape_hash = excluded.shape_hash,
115
+ shape_schema = excluded.shape_schema,
116
+ type = excluded.type,
117
+ status = excluded.status
118
118
  `).run(contract.id, contract.node_id, contract.shape_hash, contract.shape_schema, contract.type, contract.status);
119
119
  }
120
120
  async upsertDependency(dependency) {
121
- this.db.prepare(`
122
- INSERT INTO ferret_dependencies (id, source_node_id, target_contract_id)
123
- VALUES (?, ?, ?)
124
- ON CONFLICT(id) DO NOTHING
121
+ this.db.prepare(`
122
+ INSERT INTO ferret_dependencies (id, source_node_id, target_contract_id)
123
+ VALUES (?, ?, ?)
124
+ ON CONFLICT(id) DO NOTHING
125
125
  `).run(dependency.id, dependency.source_node_id, dependency.target_contract_id);
126
126
  }
127
127
  async replaceDependenciesForSourceNode(sourceNodeId, targetContractIds) {
128
128
  const uniqueTargets = [...new Set(targetContractIds)].sort();
129
129
  const deleteStatement = this.db.prepare("DELETE FROM ferret_dependencies WHERE source_node_id = ?");
130
- const insertStatement = this.db.prepare(`
131
- INSERT INTO ferret_dependencies (id, source_node_id, target_contract_id)
132
- VALUES (?, ?, ?)
130
+ const insertStatement = this.db.prepare(`
131
+ INSERT INTO ferret_dependencies (id, source_node_id, target_contract_id)
132
+ VALUES (?, ?, ?)
133
133
  `);
134
134
  const transaction = this.db.transaction((sourceId) => {
135
135
  deleteStatement.run(sourceId);
@@ -159,18 +159,18 @@ export class SqliteStore {
159
159
  this.db.prepare("UPDATE ferret_nodes SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, nodeId);
160
160
  }
161
161
  async insertReconciliationLog(log) {
162
- this.db.prepare(`
163
- INSERT INTO ferret_reconciliation_log (id, node_id, triggered_by, resolved_by, resolution_notes)
164
- VALUES (?, ?, ?, ?, ?)
162
+ this.db.prepare(`
163
+ INSERT INTO ferret_reconciliation_log (id, node_id, triggered_by, resolved_by, resolution_notes)
164
+ VALUES (?, ?, ?, ?, ?)
165
165
  `).run(log.id, log.node_id, log.triggered_by, log.resolved_by ?? null, log.resolution_notes ?? null);
166
166
  }
167
167
  async getReconciliationLogs() {
168
168
  return this.db.prepare("SELECT id, node_id, triggered_by, resolved_by, resolution_notes FROM ferret_reconciliation_log").all();
169
169
  }
170
170
  async insertPlacementDecision(decision) {
171
- this.db.prepare(`
172
- INSERT INTO ferret_placement_decisions (id, node_id, placed_by, reasoning)
173
- VALUES (?, ?, ?, ?)
171
+ this.db.prepare(`
172
+ INSERT INTO ferret_placement_decisions (id, node_id, placed_by, reasoning)
173
+ VALUES (?, ?, ?, ?)
174
174
  `).run(decision.id, decision.node_id, decision.placed_by, decision.reasoning ?? null);
175
175
  }
176
176
  async getPlacementDecisions() {
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "@specferret/core",
3
- "version": "0.1.0",
4
- "description": "SpecFerret core engine — spec drift detection.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "bun": "./src/index.ts",
11
- "import": "./dist/index.js",
12
- "types": "./dist/index.d.ts"
13
- }
14
- },
15
- "files": [
16
- "dist/",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "scripts": {
21
- "build": "bun ../../scripts/clean-dist.ts dist && tsc --project tsconfig.build.json",
22
- "test": "bun test"
23
- },
24
- "dependencies": {
25
- "ajv": "^8.12.0",
26
- "ajv-formats": "^2.1.1",
27
- "glob": "^13.0.6",
28
- "gray-matter": "^4.0.3",
29
- "zod": "^3.22.4"
30
- },
31
- "devDependencies": {
32
- "@types/bun": "^1.3.11",
33
- "typescript": "^6.0.2"
34
- },
35
- "engines": {
36
- "bun": ">=1.0.0"
37
- },
38
- "license": "MIT",
39
- "repository": {
40
- "type": "git",
41
- "url": "git+https://github.com/BenGardiner123/spec-ferret.git"
42
- },
43
- "homepage": "https://specferret.dev"
44
- }
1
+ {
2
+ "name": "@specferret/core",
3
+ "version": "0.1.2",
4
+ "description": "SpecFerret core engine — spec drift detection.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "bun": "./src/index.ts",
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist/",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "bun ../../scripts/clean-dist.ts dist && tsc --project tsconfig.build.json",
22
+ "test": "bun test"
23
+ },
24
+ "dependencies": {
25
+ "ajv": "^8.12.0",
26
+ "ajv-formats": "^2.1.1",
27
+ "glob": "^13.0.6",
28
+ "gray-matter": "^4.0.3",
29
+ "zod": "^3.22.4"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "^1.3.11",
33
+ "typescript": "^6.0.2"
34
+ },
35
+ "engines": {
36
+ "bun": ">=1.0.0"
37
+ },
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/BenGardiner123/spec-ferret.git"
42
+ },
43
+ "homepage": "https://specferret.dev"
44
+ }