@devflow-tools/database 0.17.7 → 0.18.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.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/data/LocalDataProvider.d.ts +63 -0
- package/dist/data/LocalDataProvider.js +332 -0
- package/dist/data/default-enforcer-rules.d.ts +3 -0
- package/dist/data/default-enforcer-rules.js +170 -0
- package/dist/data/devflow-schema.d.ts +2 -0
- package/dist/data/devflow-schema.js +80 -0
- package/dist/database.d.ts +45 -7
- package/dist/database.js +583 -125
- package/dist/index.d.ts +8 -4
- package/dist/index.js +11 -5
- package/dist/retrieval-ledger.d.ts +16 -1
- package/dist/retrieval-ledger.js +6 -0
- package/dist/retrieval-runtime.d.ts +39 -0
- package/dist/retrieval-runtime.js +2 -0
- package/dist/task-aggregate.d.ts +49 -0
- package/dist/task-aggregate.js +2 -0
- package/dist/task-semantic-control.d.ts +8 -1
- package/dist/task-semantic-control.js +2 -4
- package/package.json +15 -3
- package/CHANGELOG.md +0 -836
- package/__tests__/database.failure-category.test.ts +0 -44
- package/__tests__/database.host-actions.test.ts +0 -405
- package/__tests__/database.learning-candidates.test.ts +0 -93
- package/__tests__/database.memory-turn-receipts.test.ts +0 -98
- package/__tests__/database.retrieval-ledger.test.ts +0 -68
- package/__tests__/database.retrieval-sessions.test.ts +0 -79
- package/__tests__/database.semantic-resolution.test.ts +0 -87
- package/__tests__/database.skill-executions.test.ts +0 -456
- package/__tests__/database.task-runtime.test.ts +0 -73
- package/__tests__/database.test.ts +0 -177
- package/__tests__/database.work-queue.test.ts +0 -274
- package/__tests__/database.workflow-workers.test.ts +0 -60
- package/__tests__/node-sqlite.test.ts +0 -68
- package/src/database.ts +0 -5853
- package/src/host-actions.ts +0 -211
- package/src/index.ts +0 -153
- package/src/learning-candidates.ts +0 -181
- package/src/node-sqlite.ts +0 -60
- package/src/obligation-ledger.ts +0 -57
- package/src/retrieval-ledger.ts +0 -35
- package/src/retrieval-sessions.ts +0 -196
- package/src/semantic-resolution.ts +0 -181
- package/src/task-runtime.ts +0 -169
- package/src/task-semantic-control.ts +0 -270
- package/src/types.ts +0 -17
- package/src/work-queue.ts +0 -123
- package/src/workflow-workers.ts +0 -198
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -41
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shilongfeicool
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @devflow-tools/database
|
|
2
2
|
|
|
3
|
-
> 基于 better-sqlite3 的本地 SQLite/WAL 持久化层,承载运行、检索、记忆和遥测记录。(v0.17.
|
|
3
|
+
> 基于 better-sqlite3 的本地 SQLite/WAL 持久化层,承载运行、检索、记忆和遥测记录。(v0.17.8)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@devflow-tools/database)
|
|
6
6
|
|
|
@@ -30,7 +30,7 @@ console.log(db);
|
|
|
30
30
|
|
|
31
31
|
## 版本与兼容性
|
|
32
32
|
|
|
33
|
-
- 当前包版本:`0.17.
|
|
33
|
+
- 当前包版本:`0.17.8`
|
|
34
34
|
- Node.js:未在 package manifest 声明更高版本时,使用 Node.js 18+;原生 SQLite/Playwright 能力请按对应依赖安装
|
|
35
35
|
- workspace 内部包应使用同一 DevFlow minor/patch 版本,避免类型和数据库 schema 不一致
|
|
36
36
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { SQLInputValue } from "node:sqlite";
|
|
2
|
+
import type { IDataProvider, VectorResult, VectorPoint, GraphNode, GraphEdge, SessionEntry, SymbolEntry, SymbolKind, FileMeta, ImportEntry, AuditEntry, ContextReceipt, Rule } from "@devflow-tools/sdk";
|
|
3
|
+
export declare class LocalDataProvider implements IDataProvider {
|
|
4
|
+
private db;
|
|
5
|
+
constructor(_root: string, options?: {
|
|
6
|
+
dbPath?: string;
|
|
7
|
+
});
|
|
8
|
+
private initSchema;
|
|
9
|
+
close(): void;
|
|
10
|
+
vectorSearch(_collection: string, _query: number[], _limit: number): Promise<VectorResult[]>;
|
|
11
|
+
vectorUpsert(_collection: string, _points: VectorPoint[]): Promise<void>;
|
|
12
|
+
vectorDelete(_collection: string, _ids: string[]): Promise<void>;
|
|
13
|
+
graphGetAll(): Promise<{
|
|
14
|
+
nodes: GraphNode[];
|
|
15
|
+
edges: GraphEdge[];
|
|
16
|
+
}>;
|
|
17
|
+
graphGetNode(_id: string): Promise<GraphNode | null>;
|
|
18
|
+
graphGetEdges(_nodeId: string, _direction: "in" | "out"): Promise<GraphEdge[]>;
|
|
19
|
+
graphUpsertNodes(_nodes: GraphNode[]): Promise<void>;
|
|
20
|
+
graphUpsertEdges(_edges: GraphEdge[]): Promise<void>;
|
|
21
|
+
graphClear(): Promise<void>;
|
|
22
|
+
kvGet(key: string): Promise<unknown>;
|
|
23
|
+
kvSet(key: string, value: unknown): Promise<void>;
|
|
24
|
+
kvDelete(key: string): Promise<void>;
|
|
25
|
+
kvGetPrefix(prefix: string): Promise<Record<string, unknown>>;
|
|
26
|
+
sessionGet(sessionId: string): Promise<SessionEntry[]>;
|
|
27
|
+
sessionAppend(sessionId: string, entry: SessionEntry): Promise<void>;
|
|
28
|
+
sessionCreate(sessionId: string, task: string): Promise<void>;
|
|
29
|
+
sessionClose(sessionId: string): Promise<void>;
|
|
30
|
+
sqlQuery<T>(sql: string, params?: SQLInputValue[]): Promise<T[]>;
|
|
31
|
+
sqlExecute(sql: string, params?: SQLInputValue[]): Promise<void>;
|
|
32
|
+
recordToolCall(tool: string, args: Record<string, unknown>, durationMs: number, error?: string): Promise<void>;
|
|
33
|
+
getToolCallStats(): Promise<{
|
|
34
|
+
total: number;
|
|
35
|
+
byTool: Record<string, number>;
|
|
36
|
+
recent: Array<{
|
|
37
|
+
tool: string;
|
|
38
|
+
timestamp: number;
|
|
39
|
+
durationMs: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}>;
|
|
43
|
+
private _auditSecret;
|
|
44
|
+
auditGetSecret(): Promise<string>;
|
|
45
|
+
auditAppend(entry: AuditEntry): Promise<void>;
|
|
46
|
+
auditGetAll(): Promise<AuditEntry[]>;
|
|
47
|
+
ruleSave(rule: Rule): Promise<void>;
|
|
48
|
+
ruleGetAll(options?: {
|
|
49
|
+
includeDisabled?: boolean;
|
|
50
|
+
}): Promise<Rule[]>;
|
|
51
|
+
ruleDelete(id: string): Promise<void>;
|
|
52
|
+
contextReceiptPut(receipt: ContextReceipt): Promise<void>;
|
|
53
|
+
contextReceiptGet(projectRoot: string, sessionId: string, executionId: string): Promise<ContextReceipt | null>;
|
|
54
|
+
contextReceiptDelete(projectRoot: string, sessionId: string, executionId?: string): Promise<void>;
|
|
55
|
+
insertSymbols(_symbols: SymbolEntry[]): Promise<void>;
|
|
56
|
+
insertFiles(_files: FileMeta[]): Promise<void>;
|
|
57
|
+
insertImports(_imports: ImportEntry[]): Promise<void>;
|
|
58
|
+
findSymbols(_name: string): Promise<SymbolEntry[]>;
|
|
59
|
+
findSymbolsByKind(_kind: SymbolKind): Promise<SymbolEntry[]>;
|
|
60
|
+
getSymbolsInFile(_file: string): Promise<SymbolEntry[]>;
|
|
61
|
+
getFileCount(): Promise<number>;
|
|
62
|
+
getSymbolCount(): Promise<number>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalDataProvider = void 0;
|
|
4
|
+
const node_sqlite_1 = require("node:sqlite");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const database_js_1 = require("../database.js");
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const devflow_schema_js_1 = require("./devflow-schema.js");
|
|
10
|
+
function parseStringArray(value) {
|
|
11
|
+
if (typeof value !== 'string')
|
|
12
|
+
return [];
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(value);
|
|
15
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string') : [];
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function parseJsonObject(value) {
|
|
22
|
+
if (typeof value !== 'string' || !value)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(value);
|
|
26
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function parseJsonArray(value) {
|
|
33
|
+
if (typeof value !== 'string' || !value)
|
|
34
|
+
return [];
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(value);
|
|
37
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
class LocalDataProvider {
|
|
44
|
+
constructor(_root, options) {
|
|
45
|
+
// --- Audit chain ---
|
|
46
|
+
this._auditSecret = null;
|
|
47
|
+
const dbPath = options?.dbPath ?? (0, database_js_1.getGlobalDevFlowDbPath)();
|
|
48
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dbPath), { recursive: true });
|
|
49
|
+
this.db = new node_sqlite_1.DatabaseSync(dbPath);
|
|
50
|
+
this.db.exec("PRAGMA journal_mode = WAL");
|
|
51
|
+
this.initSchema();
|
|
52
|
+
}
|
|
53
|
+
initSchema() {
|
|
54
|
+
this.db.exec(devflow_schema_js_1.DEVFLOW_SCHEMA_SQL);
|
|
55
|
+
try {
|
|
56
|
+
this.db.exec("ALTER TABLE devflow_workflow_runs ADD COLUMN failure_category TEXT");
|
|
57
|
+
}
|
|
58
|
+
catch { }
|
|
59
|
+
try {
|
|
60
|
+
this.db.exec("ALTER TABLE devflow_workflow_runs ADD COLUMN failure_reason TEXT");
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
try {
|
|
64
|
+
this.db.exec("ALTER TABLE devflow_workflow_runs ADD COLUMN failure_evidence TEXT");
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
try {
|
|
68
|
+
this.db.exec("ALTER TABLE devflow_rules ADD COLUMN gate INTEGER NOT NULL DEFAULT 1");
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
try {
|
|
72
|
+
this.db.exec("ALTER TABLE devflow_rules ADD COLUMN updated_at INTEGER");
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
try {
|
|
76
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN selected_files TEXT NOT NULL DEFAULT '[]'");
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
try {
|
|
80
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN memory_ids TEXT NOT NULL DEFAULT '[]'");
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
try {
|
|
84
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN canonical_next_action TEXT");
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
try {
|
|
88
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN canonical_action_json TEXT");
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
try {
|
|
92
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN action_attempts TEXT NOT NULL DEFAULT '[]'");
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
try {
|
|
96
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN action_satisfied_at INTEGER");
|
|
97
|
+
}
|
|
98
|
+
catch { }
|
|
99
|
+
try {
|
|
100
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN action_degradation_json TEXT");
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
try {
|
|
104
|
+
this.db.exec("ALTER TABLE devflow_context_receipts ADD COLUMN request_id TEXT");
|
|
105
|
+
}
|
|
106
|
+
catch { }
|
|
107
|
+
this.db.exec("UPDATE devflow_rules SET updated_at = created_at WHERE updated_at IS NULL");
|
|
108
|
+
}
|
|
109
|
+
close() {
|
|
110
|
+
if (this.db.isOpen)
|
|
111
|
+
this.db.close();
|
|
112
|
+
}
|
|
113
|
+
// --- Vector (P0 stubs) ---
|
|
114
|
+
async vectorSearch(_collection, _query, _limit) {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
async vectorUpsert(_collection, _points) { }
|
|
118
|
+
async vectorDelete(_collection, _ids) { }
|
|
119
|
+
// --- Graph (stubs — CodeGraph owns this now) ---
|
|
120
|
+
async graphGetAll() { return { nodes: [], edges: [] }; }
|
|
121
|
+
async graphGetNode(_id) { return null; }
|
|
122
|
+
async graphGetEdges(_nodeId, _direction) { return []; }
|
|
123
|
+
async graphUpsertNodes(_nodes) { }
|
|
124
|
+
async graphUpsertEdges(_edges) { }
|
|
125
|
+
async graphClear() { }
|
|
126
|
+
// --- KV ---
|
|
127
|
+
async kvGet(key) {
|
|
128
|
+
const row = this.db.prepare("SELECT value FROM devflow_kv WHERE key = ?").get(key);
|
|
129
|
+
if (!row)
|
|
130
|
+
return null;
|
|
131
|
+
return JSON.parse(row.value);
|
|
132
|
+
}
|
|
133
|
+
async kvSet(key, value) {
|
|
134
|
+
this.db.prepare("INSERT OR REPLACE INTO devflow_kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
135
|
+
}
|
|
136
|
+
async kvDelete(key) {
|
|
137
|
+
this.db.prepare("DELETE FROM devflow_kv WHERE key = ?").run(key);
|
|
138
|
+
}
|
|
139
|
+
async kvGetPrefix(prefix) {
|
|
140
|
+
const rows = this.db.prepare("SELECT key, value FROM devflow_kv WHERE key LIKE ?").all(prefix + "%");
|
|
141
|
+
const result = {};
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
result[row.key] = JSON.parse(row.value);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
// --- Session ---
|
|
148
|
+
async sessionGet(sessionId) {
|
|
149
|
+
const rows = this.db.prepare("SELECT timestamp, event, data FROM devflow_sessions WHERE session_id = ? ORDER BY timestamp").all(sessionId);
|
|
150
|
+
return rows.map((r) => ({
|
|
151
|
+
timestamp: r.timestamp,
|
|
152
|
+
action: r.event,
|
|
153
|
+
summary: r.event,
|
|
154
|
+
...(r.data ? JSON.parse(r.data) : {}),
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
async sessionAppend(sessionId, entry) {
|
|
158
|
+
this.db.prepare("INSERT INTO devflow_sessions (session_id, timestamp, event, data) VALUES (?, ?, ?, ?)").run(sessionId, entry.timestamp, entry.action, JSON.stringify({ summary: entry.summary, metadata: entry.metadata }));
|
|
159
|
+
}
|
|
160
|
+
async sessionCreate(sessionId, task) {
|
|
161
|
+
await this.sessionAppend(sessionId, {
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
action: "session:create",
|
|
164
|
+
summary: task,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async sessionClose(sessionId) {
|
|
168
|
+
await this.sessionAppend(sessionId, {
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
action: "session:close",
|
|
171
|
+
summary: "Session closed",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// --- SQL ---
|
|
175
|
+
async sqlQuery(sql, params) {
|
|
176
|
+
const stmt = this.db.prepare(sql);
|
|
177
|
+
return (params ? stmt.all(...params) : stmt.all());
|
|
178
|
+
}
|
|
179
|
+
async sqlExecute(sql, params) {
|
|
180
|
+
const stmt = this.db.prepare(sql);
|
|
181
|
+
params ? stmt.run(...params) : stmt.run();
|
|
182
|
+
}
|
|
183
|
+
// --- Tool call telemetry (Layer 4: local mode) ---
|
|
184
|
+
async recordToolCall(tool, args, durationMs, error) {
|
|
185
|
+
this.db.prepare("INSERT INTO devflow_tool_calls (tool, args, timestamp, duration_ms, error) VALUES (?, ?, ?, ?, ?)").run(tool, JSON.stringify(args), Date.now(), durationMs, error ?? null);
|
|
186
|
+
}
|
|
187
|
+
async getToolCallStats() {
|
|
188
|
+
const total = this.db.prepare("SELECT COUNT(*) as c FROM devflow_tool_calls").get().c;
|
|
189
|
+
const byToolRows = this.db.prepare("SELECT tool, COUNT(*) as c FROM devflow_tool_calls GROUP BY tool").all();
|
|
190
|
+
const byTool = {};
|
|
191
|
+
for (const r of byToolRows)
|
|
192
|
+
byTool[r.tool] = r.c;
|
|
193
|
+
const recent = this.db.prepare("SELECT tool, timestamp, duration_ms, error FROM devflow_tool_calls ORDER BY timestamp DESC LIMIT 20").all();
|
|
194
|
+
return {
|
|
195
|
+
total,
|
|
196
|
+
byTool,
|
|
197
|
+
recent: recent.map((r) => ({ tool: r.tool, timestamp: r.timestamp, durationMs: r.duration_ms, ...(r.error ? { error: r.error } : {}) })),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
async auditGetSecret() {
|
|
201
|
+
if (this._auditSecret)
|
|
202
|
+
return this._auditSecret;
|
|
203
|
+
const row = this.db.prepare("SELECT value FROM devflow_kv WHERE key = ?").get("audit_secret");
|
|
204
|
+
if (row) {
|
|
205
|
+
this._auditSecret = row.value;
|
|
206
|
+
return this._auditSecret;
|
|
207
|
+
}
|
|
208
|
+
const secret = (0, node_crypto_1.randomBytes)(32).toString("hex");
|
|
209
|
+
this.db.prepare("INSERT OR REPLACE INTO devflow_kv (key, value) VALUES ('audit_secret', ?)").run(secret);
|
|
210
|
+
this._auditSecret = secret;
|
|
211
|
+
return secret;
|
|
212
|
+
}
|
|
213
|
+
async auditAppend(entry) {
|
|
214
|
+
this.db.prepare("INSERT INTO devflow_audit_chain (ts, tool, args_hash, decision, rule_id, session_id, prev_hash, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").run(entry.ts, entry.tool, entry.argsHash, entry.decision, entry.ruleId, entry.sessionId, entry.prevHash, entry.signature);
|
|
215
|
+
}
|
|
216
|
+
async auditGetAll() {
|
|
217
|
+
const rows = this.db.prepare("SELECT seq, ts, tool, args_hash, decision, rule_id, session_id, prev_hash, signature FROM devflow_audit_chain ORDER BY seq").all();
|
|
218
|
+
return rows.map((r) => ({
|
|
219
|
+
seq: r.seq,
|
|
220
|
+
ts: r.ts,
|
|
221
|
+
tool: r.tool,
|
|
222
|
+
argsHash: r.args_hash,
|
|
223
|
+
decision: r.decision,
|
|
224
|
+
ruleId: r.rule_id,
|
|
225
|
+
sessionId: r.session_id,
|
|
226
|
+
prevHash: r.prev_hash,
|
|
227
|
+
signature: r.signature,
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
// --- Rules ---
|
|
231
|
+
async ruleSave(rule) {
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
this.db.prepare(`INSERT INTO devflow_rules
|
|
234
|
+
(id, name, gate, level, condition_json, action, message, enabled, created_at, updated_at)
|
|
235
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
236
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
237
|
+
name = excluded.name, gate = excluded.gate, level = excluded.level,
|
|
238
|
+
condition_json = excluded.condition_json, action = excluded.action,
|
|
239
|
+
message = excluded.message, enabled = excluded.enabled,
|
|
240
|
+
updated_at = excluded.updated_at`).run(rule.id, rule.name, rule.gate, rule.level, JSON.stringify(rule.condition), rule.action, rule.message, rule.enabled ? 1 : 0, rule.createdAt ?? now, now);
|
|
241
|
+
}
|
|
242
|
+
async ruleGetAll(options) {
|
|
243
|
+
const where = options?.includeDisabled ? "" : "WHERE enabled = 1";
|
|
244
|
+
const rows = this.db.prepare(`SELECT * FROM devflow_rules ${where} ORDER BY gate, id`).all();
|
|
245
|
+
return rows.map((r) => ({
|
|
246
|
+
id: r.id,
|
|
247
|
+
name: r.name,
|
|
248
|
+
gate: r.gate,
|
|
249
|
+
level: r.level,
|
|
250
|
+
condition: JSON.parse(r.condition_json),
|
|
251
|
+
action: r.action,
|
|
252
|
+
message: r.message,
|
|
253
|
+
enabled: r.enabled === 1,
|
|
254
|
+
createdAt: r.created_at,
|
|
255
|
+
updatedAt: r.updated_at,
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
async ruleDelete(id) {
|
|
259
|
+
this.db.prepare("DELETE FROM devflow_rules WHERE id = ?").run(id);
|
|
260
|
+
}
|
|
261
|
+
async contextReceiptPut(receipt) {
|
|
262
|
+
this.db.prepare(`
|
|
263
|
+
INSERT INTO devflow_context_receipts
|
|
264
|
+
(project_root, session_id, execution_id, context_hash, issued_at, expires_at,
|
|
265
|
+
selected_files, memory_ids, canonical_next_action, canonical_action_json,
|
|
266
|
+
action_attempts, action_satisfied_at, action_degradation_json, request_id)
|
|
267
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
268
|
+
ON CONFLICT(project_root, session_id, execution_id) DO UPDATE SET
|
|
269
|
+
context_hash = excluded.context_hash,
|
|
270
|
+
issued_at = excluded.issued_at,
|
|
271
|
+
expires_at = excluded.expires_at,
|
|
272
|
+
selected_files = excluded.selected_files,
|
|
273
|
+
memory_ids = excluded.memory_ids,
|
|
274
|
+
canonical_next_action = excluded.canonical_next_action,
|
|
275
|
+
canonical_action_json = excluded.canonical_action_json,
|
|
276
|
+
action_attempts = excluded.action_attempts,
|
|
277
|
+
action_satisfied_at = excluded.action_satisfied_at,
|
|
278
|
+
action_degradation_json = excluded.action_degradation_json,
|
|
279
|
+
request_id = excluded.request_id
|
|
280
|
+
`).run(receipt.projectRoot, receipt.sessionId, receipt.executionId, receipt.contextHash, receipt.issuedAt, receipt.expiresAt, JSON.stringify(receipt.selectedFiles ?? []), JSON.stringify(receipt.memoryIds ?? []), receipt.canonicalNextAction ?? null, receipt.canonicalAction ? JSON.stringify(receipt.canonicalAction) : null, JSON.stringify(receipt.actionAttempts ?? []), receipt.actionSatisfiedAt ?? null, receipt.actionDegradation ? JSON.stringify(receipt.actionDegradation) : null, receipt.requestId ?? null);
|
|
281
|
+
}
|
|
282
|
+
async contextReceiptGet(projectRoot, sessionId, executionId) {
|
|
283
|
+
const row = this.db.prepare(`
|
|
284
|
+
SELECT project_root, session_id, execution_id, context_hash, issued_at, expires_at,
|
|
285
|
+
selected_files, memory_ids, canonical_next_action, canonical_action_json,
|
|
286
|
+
action_attempts, action_satisfied_at, action_degradation_json, request_id
|
|
287
|
+
FROM devflow_context_receipts
|
|
288
|
+
WHERE project_root = ? AND session_id = ? AND execution_id = ?
|
|
289
|
+
`).get(projectRoot, sessionId, executionId);
|
|
290
|
+
if (!row)
|
|
291
|
+
return null;
|
|
292
|
+
return {
|
|
293
|
+
projectRoot: row.project_root,
|
|
294
|
+
sessionId: row.session_id,
|
|
295
|
+
executionId: row.execution_id,
|
|
296
|
+
contextHash: row.context_hash,
|
|
297
|
+
issuedAt: row.issued_at,
|
|
298
|
+
expiresAt: row.expires_at,
|
|
299
|
+
selectedFiles: parseStringArray(row.selected_files),
|
|
300
|
+
memoryIds: parseStringArray(row.memory_ids),
|
|
301
|
+
canonicalNextAction: typeof row.canonical_next_action === 'string'
|
|
302
|
+
? row.canonical_next_action
|
|
303
|
+
: undefined,
|
|
304
|
+
canonicalAction: parseJsonObject(row.canonical_action_json),
|
|
305
|
+
actionAttempts: parseJsonArray(row.action_attempts),
|
|
306
|
+
actionSatisfiedAt: typeof row.action_satisfied_at === 'number' ? row.action_satisfied_at : undefined,
|
|
307
|
+
actionDegradation: parseJsonObject(row.action_degradation_json),
|
|
308
|
+
requestId: typeof row.request_id === 'string' ? row.request_id : undefined,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async contextReceiptDelete(projectRoot, sessionId, executionId) {
|
|
312
|
+
if (executionId) {
|
|
313
|
+
this.db.prepare(`DELETE FROM devflow_context_receipts
|
|
314
|
+
WHERE project_root = ? AND session_id = ? AND execution_id = ?`)
|
|
315
|
+
.run(projectRoot, sessionId, executionId);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this.db.prepare(`DELETE FROM devflow_context_receipts
|
|
319
|
+
WHERE project_root = ? AND session_id = ?`).run(projectRoot, sessionId);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// --- File index (stubs — CodeGraph owns this now) ---
|
|
323
|
+
async insertSymbols(_symbols) { }
|
|
324
|
+
async insertFiles(_files) { }
|
|
325
|
+
async insertImports(_imports) { }
|
|
326
|
+
async findSymbols(_name) { return []; }
|
|
327
|
+
async findSymbolsByKind(_kind) { return []; }
|
|
328
|
+
async getSymbolsInFile(_file) { return []; }
|
|
329
|
+
async getFileCount() { return 0; }
|
|
330
|
+
async getSymbolCount() { return 0; }
|
|
331
|
+
}
|
|
332
|
+
exports.LocalDataProvider = LocalDataProvider;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_ENFORCER_RULES = void 0;
|
|
4
|
+
exports.seedDefaultEnforcerRules = seedDefaultEnforcerRules;
|
|
5
|
+
const constitutional = (rule) => ({
|
|
6
|
+
...rule,
|
|
7
|
+
gate: 1,
|
|
8
|
+
level: "constitutional",
|
|
9
|
+
enabled: true,
|
|
10
|
+
});
|
|
11
|
+
exports.DEFAULT_ENFORCER_RULES = [
|
|
12
|
+
constitutional({
|
|
13
|
+
id: "C001",
|
|
14
|
+
name: "Session Token 缺失",
|
|
15
|
+
condition: { requireFields: ["_devflow_token"] },
|
|
16
|
+
action: "warn",
|
|
17
|
+
message: "缺少 Session Token。当前调用处于降级认证模式。",
|
|
18
|
+
}),
|
|
19
|
+
constitutional({
|
|
20
|
+
id: "C002",
|
|
21
|
+
name: "Session Token 过期",
|
|
22
|
+
condition: { paramPatterns: { "_devflow_token.expired": "^true$" } },
|
|
23
|
+
action: "warn",
|
|
24
|
+
message: "Session Token 已过期,DevFlow 将自动续期。",
|
|
25
|
+
}),
|
|
26
|
+
constitutional({
|
|
27
|
+
id: "C003",
|
|
28
|
+
name: "调用速率限制",
|
|
29
|
+
condition: { maxCallsPerMinute: 60 },
|
|
30
|
+
action: "warn",
|
|
31
|
+
message: "调用频率超过每分钟 60 次,请降低请求速率。",
|
|
32
|
+
}),
|
|
33
|
+
constitutional({
|
|
34
|
+
id: "C004",
|
|
35
|
+
name: "危险 Shell 命令",
|
|
36
|
+
condition: {
|
|
37
|
+
toolPatterns: ["^(bash|shell|run_command|execute_command)$"],
|
|
38
|
+
forbidPatterns: ["(^|\\s)(rm\\s+-rf|mkfs(\\.|\\s)|dd\\s+if=|shutdown|reboot)(\\s|$)"],
|
|
39
|
+
},
|
|
40
|
+
action: "deny",
|
|
41
|
+
message: "检测到高风险系统命令,执行已阻止。",
|
|
42
|
+
}),
|
|
43
|
+
constitutional({
|
|
44
|
+
id: "C005",
|
|
45
|
+
name: "禁止强制推送",
|
|
46
|
+
condition: { forbidPatterns: ["git\\s+push(?:\\s+[^\\n]*)?\\s(?:--force|-f)(?:\\s|$)"] },
|
|
47
|
+
action: "deny",
|
|
48
|
+
message: "禁止未经显式治理规则批准的 Git 强制推送。",
|
|
49
|
+
}),
|
|
50
|
+
constitutional({
|
|
51
|
+
id: "C006",
|
|
52
|
+
name: "保护敏感文件",
|
|
53
|
+
condition: {
|
|
54
|
+
toolPatterns: ["(write|edit|patch|create|delete|remove|move|rename)"],
|
|
55
|
+
forbidPatterns: ["(^|/)(\\.env(?:\\.[^/]*)?|id_rsa|id_ed25519|credentials(?:\\.json)?|secrets?\\.(?:json|ya?ml))$"],
|
|
56
|
+
},
|
|
57
|
+
action: "deny",
|
|
58
|
+
message: "目标包含敏感凭据文件,写入已阻止。",
|
|
59
|
+
}),
|
|
60
|
+
constitutional({
|
|
61
|
+
id: "C007",
|
|
62
|
+
name: "阻止明文凭据",
|
|
63
|
+
condition: {
|
|
64
|
+
toolPatterns: ["(write|edit|patch|create|save|update)"],
|
|
65
|
+
forbidPatterns: ["(?:sk-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN (?:RSA |OPENSSH )?PRIVATE KEY-----)"],
|
|
66
|
+
},
|
|
67
|
+
action: "deny",
|
|
68
|
+
message: "检测到疑似明文凭据或私钥,写入已阻止。",
|
|
69
|
+
}),
|
|
70
|
+
constitutional({
|
|
71
|
+
id: "C008",
|
|
72
|
+
name: "Token 预算上限",
|
|
73
|
+
condition: { maxNumericFields: { maxTokens: 200000, max_tokens: 200000, tokenBudget: 200000, token_budget: 200000 } },
|
|
74
|
+
action: "warn",
|
|
75
|
+
message: "请求的 Token 预算超过 200000,请拆分任务或降低预算。",
|
|
76
|
+
}),
|
|
77
|
+
constitutional({
|
|
78
|
+
id: "C009",
|
|
79
|
+
name: "项目根目录必填",
|
|
80
|
+
condition: {
|
|
81
|
+
toolPatterns: ["^(get_project_context|search_symbol|get_dependency_graph|react_|vue_|nest_|next_|express_)"],
|
|
82
|
+
requireFields: ["projectRoot"],
|
|
83
|
+
},
|
|
84
|
+
action: "deny",
|
|
85
|
+
message: "该工具必须显式提供 projectRoot,禁止使用进程 cwd 推断。",
|
|
86
|
+
}),
|
|
87
|
+
constitutional({
|
|
88
|
+
id: "C010",
|
|
89
|
+
name: "破坏性 SQL",
|
|
90
|
+
condition: {
|
|
91
|
+
toolPatterns: ["(sql|query|database|db)"],
|
|
92
|
+
forbidPatterns: ["\\b(?:DROP\\s+(?:DATABASE|TABLE)|TRUNCATE\\s+TABLE|DELETE\\s+FROM\\s+[^;]+(?:;|$))"],
|
|
93
|
+
},
|
|
94
|
+
action: "deny",
|
|
95
|
+
message: "检测到破坏性 SQL,执行已阻止。",
|
|
96
|
+
}),
|
|
97
|
+
{
|
|
98
|
+
id: "S001",
|
|
99
|
+
name: "领域工具先获取项目上下文",
|
|
100
|
+
gate: 2,
|
|
101
|
+
level: "project",
|
|
102
|
+
condition: {
|
|
103
|
+
toolPatterns: ["^(react|vue|nest|next|nextjs|graphql|express|typescript|testing|security|database)_"],
|
|
104
|
+
requirePriorTools: ["get_project_context"],
|
|
105
|
+
},
|
|
106
|
+
action: "deny",
|
|
107
|
+
message: "Call get_project_context successfully before this domain tool.",
|
|
108
|
+
enabled: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "S002",
|
|
112
|
+
name: "代码生成先获取项目上下文",
|
|
113
|
+
gate: 2,
|
|
114
|
+
level: "project",
|
|
115
|
+
condition: {
|
|
116
|
+
toolPatterns: ["(generate|scaffold|refactor|code_write)"],
|
|
117
|
+
requirePriorTools: ["get_project_context"],
|
|
118
|
+
},
|
|
119
|
+
action: "warn",
|
|
120
|
+
message: "Gate 2:生成或重构代码前必须先获取项目上下文。",
|
|
121
|
+
enabled: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "I001",
|
|
125
|
+
name: "上下文告警拦截写入",
|
|
126
|
+
gate: 3,
|
|
127
|
+
level: "session",
|
|
128
|
+
condition: {
|
|
129
|
+
toolPatterns: ["(write|edit|patch|create|generate|refactor|code_write)"],
|
|
130
|
+
requiresContextWarning: true,
|
|
131
|
+
},
|
|
132
|
+
action: "warn",
|
|
133
|
+
message: "Gate 3:最近的项目上下文包含告警或低置信度,请先补充检索再写代码。",
|
|
134
|
+
enabled: true,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "I002",
|
|
138
|
+
name: "低置信度上下文拦截工作流",
|
|
139
|
+
gate: 3,
|
|
140
|
+
level: "session",
|
|
141
|
+
condition: {
|
|
142
|
+
toolPatterns: ["(workflow|execute|run)_"],
|
|
143
|
+
requiresContextWarning: true,
|
|
144
|
+
},
|
|
145
|
+
action: "warn",
|
|
146
|
+
message: "Gate 3:上下文质量不足,执行工作流前应确认告警并缩小范围。",
|
|
147
|
+
enabled: true,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "R001",
|
|
151
|
+
name: "失败后自动纠正",
|
|
152
|
+
gate: 4,
|
|
153
|
+
level: "session",
|
|
154
|
+
condition: { tools: ["*"], requiresPreviousFailure: true },
|
|
155
|
+
action: "warn",
|
|
156
|
+
message: "Gate 4:上一工具调用失败。请根据失败信息调整参数、缩小操作范围后重试。",
|
|
157
|
+
enabled: true,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
async function seedDefaultEnforcerRules(provider) {
|
|
161
|
+
const existing = new Map((await provider.ruleGetAll({ includeDisabled: true })).map((rule) => [rule.id, rule]));
|
|
162
|
+
for (const rule of exports.DEFAULT_ENFORCER_RULES) {
|
|
163
|
+
const stored = existing.get(rule.id);
|
|
164
|
+
const staleS001 = rule.id === "S001" && stored && (stored.action !== rule.action
|
|
165
|
+
|| stored.enabled !== rule.enabled
|
|
166
|
+
|| JSON.stringify(stored.condition) !== JSON.stringify(rule.condition));
|
|
167
|
+
if (!stored || staleS001)
|
|
168
|
+
await provider.ruleSave(rule);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DEVFLOW_TABLE_NAMES: readonly ["devflow_kv", "devflow_sessions", "devflow_telemetry_runs", "devflow_benchmark_reports", "devflow_workflow_runs", "devflow_knowledge_entries", "devflow_tool_calls", "devflow_rules", "devflow_audit_chain", "devflow_context_receipts"];
|
|
2
|
+
export declare const DEVFLOW_SCHEMA_SQL = "\n CREATE TABLE IF NOT EXISTS devflow_kv (\n key TEXT PRIMARY KEY, value TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_sessions (\n session_id TEXT, timestamp INTEGER, event TEXT, data TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_telemetry_runs (\n id TEXT PRIMARY KEY, agent TEXT, mode TEXT, status TEXT,\n started_at INTEGER, finished_at INTEGER, token_used INTEGER, data TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_benchmark_reports (\n id TEXT PRIMARY KEY, task TEXT, score REAL, started_at INTEGER,\n finished_at INTEGER, data TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_workflow_runs (\n run_id TEXT PRIMARY KEY, type TEXT, status TEXT, input TEXT,\n steps TEXT, started_at INTEGER, finished_at INTEGER,\n failure_category TEXT, failure_reason TEXT, failure_evidence TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_knowledge_entries (\n id TEXT PRIMARY KEY, source TEXT, title TEXT, content TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_tool_calls (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n tool TEXT NOT NULL,\n args TEXT,\n timestamp INTEGER NOT NULL,\n duration_ms INTEGER NOT NULL,\n error TEXT\n );\n CREATE TABLE IF NOT EXISTS devflow_rules (\n id TEXT PRIMARY KEY, name TEXT NOT NULL, level TEXT NOT NULL DEFAULT 'project',\n gate INTEGER NOT NULL DEFAULT 1,\n condition_json TEXT NOT NULL, action TEXT NOT NULL DEFAULT 'warn',\n message TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1,\n created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL\n );\n CREATE TABLE IF NOT EXISTS devflow_audit_chain (\n seq INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL,\n tool TEXT NOT NULL, args_hash TEXT NOT NULL,\n decision TEXT NOT NULL, rule_id TEXT,\n session_id TEXT NOT NULL, prev_hash TEXT NOT NULL,\n signature TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS devflow_context_receipts (\n project_root TEXT NOT NULL,\n session_id TEXT NOT NULL,\n execution_id TEXT NOT NULL,\n context_hash TEXT NOT NULL,\n issued_at INTEGER NOT NULL,\n expires_at INTEGER NOT NULL,\n selected_files TEXT NOT NULL DEFAULT '[]',\n memory_ids TEXT NOT NULL DEFAULT '[]',\n canonical_next_action TEXT,\n canonical_action_json TEXT,\n action_attempts TEXT NOT NULL DEFAULT '[]',\n action_satisfied_at INTEGER,\n action_degradation_json TEXT,\n request_id TEXT,\n PRIMARY KEY (project_root, session_id, execution_id)\n );\n CREATE INDEX IF NOT EXISTS idx_context_receipts_expiry\n ON devflow_context_receipts(expires_at);\n";
|