flavor-code 1.2.13 → 1.2.15

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 (136) hide show
  1. package/README.md +9 -1
  2. package/README.zh-CN.md +9 -1
  3. package/dist/agent/planner.d.ts +2 -0
  4. package/dist/{app-Z3YWFIBB.js → app-3KW2FTVJ.js} +2 -2
  5. package/dist/astgraph/db.mjs +237 -237
  6. package/dist/astgraph/extract.mjs +239 -239
  7. package/dist/astgraph/flavor-plugin.json +25 -25
  8. package/dist/astgraph/grammars.mjs +68 -68
  9. package/dist/astgraph/index.js +301 -301
  10. package/dist/astgraph/indexer.mjs +175 -175
  11. package/dist/astgraph/query.mjs +165 -165
  12. package/dist/astgraph/resolve.mjs +93 -93
  13. package/dist/astgraph/vendor/tree-sitter.js +3980 -3980
  14. package/dist/astgraph/vendor/zod/LICENSE +21 -21
  15. package/dist/astgraph/vendor/zod/index.js +4 -4
  16. package/dist/astgraph/vendor/zod/locales/index.js +1 -1
  17. package/dist/astgraph/vendor/zod/locales/package.json +7 -7
  18. package/dist/astgraph/vendor/zod/package.json +135 -135
  19. package/dist/astgraph/vendor/zod/v4/classic/checks.js +1 -1
  20. package/dist/astgraph/vendor/zod/v4/classic/coerce.js +17 -17
  21. package/dist/astgraph/vendor/zod/v4/classic/compat.js +31 -31
  22. package/dist/astgraph/vendor/zod/v4/classic/errors.js +48 -48
  23. package/dist/astgraph/vendor/zod/v4/classic/external.js +20 -20
  24. package/dist/astgraph/vendor/zod/v4/classic/from-json-schema.js +599 -599
  25. package/dist/astgraph/vendor/zod/v4/classic/index.js +4 -4
  26. package/dist/astgraph/vendor/zod/v4/classic/iso.js +30 -30
  27. package/dist/astgraph/vendor/zod/v4/classic/package.json +7 -7
  28. package/dist/astgraph/vendor/zod/v4/classic/parse.js +15 -15
  29. package/dist/astgraph/vendor/zod/v4/classic/schemas.js +1395 -1395
  30. package/dist/astgraph/vendor/zod/v4/core/api.js +1087 -1087
  31. package/dist/astgraph/vendor/zod/v4/core/checks.js +575 -575
  32. package/dist/astgraph/vendor/zod/v4/core/core.js +78 -78
  33. package/dist/astgraph/vendor/zod/v4/core/doc.js +35 -35
  34. package/dist/astgraph/vendor/zod/v4/core/errors.js +185 -185
  35. package/dist/astgraph/vendor/zod/v4/core/index.js +16 -16
  36. package/dist/astgraph/vendor/zod/v4/core/json-schema-generator.js +95 -95
  37. package/dist/astgraph/vendor/zod/v4/core/json-schema-processors.js +601 -601
  38. package/dist/astgraph/vendor/zod/v4/core/json-schema.js +1 -1
  39. package/dist/astgraph/vendor/zod/v4/core/package.json +7 -7
  40. package/dist/astgraph/vendor/zod/v4/core/parse.js +93 -93
  41. package/dist/astgraph/vendor/zod/v4/core/regexes.js +139 -139
  42. package/dist/astgraph/vendor/zod/v4/core/registries.js +51 -51
  43. package/dist/astgraph/vendor/zod/v4/core/schemas.js +2239 -2239
  44. package/dist/astgraph/vendor/zod/v4/core/standard-schema.js +1 -1
  45. package/dist/astgraph/vendor/zod/v4/core/to-json-schema.js +448 -448
  46. package/dist/astgraph/vendor/zod/v4/core/util.js +674 -674
  47. package/dist/astgraph/vendor/zod/v4/core/versions.js +5 -5
  48. package/dist/astgraph/vendor/zod/v4/index.js +3 -3
  49. package/dist/astgraph/vendor/zod/v4/locales/ar.js +106 -106
  50. package/dist/astgraph/vendor/zod/v4/locales/az.js +105 -105
  51. package/dist/astgraph/vendor/zod/v4/locales/be.js +156 -156
  52. package/dist/astgraph/vendor/zod/v4/locales/bg.js +120 -120
  53. package/dist/astgraph/vendor/zod/v4/locales/ca.js +107 -107
  54. package/dist/astgraph/vendor/zod/v4/locales/cs.js +111 -111
  55. package/dist/astgraph/vendor/zod/v4/locales/da.js +115 -115
  56. package/dist/astgraph/vendor/zod/v4/locales/de.js +108 -108
  57. package/dist/astgraph/vendor/zod/v4/locales/el.js +109 -109
  58. package/dist/astgraph/vendor/zod/v4/locales/en.js +113 -113
  59. package/dist/astgraph/vendor/zod/v4/locales/eo.js +109 -109
  60. package/dist/astgraph/vendor/zod/v4/locales/es.js +132 -132
  61. package/dist/astgraph/vendor/zod/v4/locales/fa.js +114 -114
  62. package/dist/astgraph/vendor/zod/v4/locales/fi.js +112 -112
  63. package/dist/astgraph/vendor/zod/v4/locales/fr-CA.js +107 -107
  64. package/dist/astgraph/vendor/zod/v4/locales/fr.js +125 -125
  65. package/dist/astgraph/vendor/zod/v4/locales/he.js +214 -214
  66. package/dist/astgraph/vendor/zod/v4/locales/hr.js +122 -122
  67. package/dist/astgraph/vendor/zod/v4/locales/hu.js +108 -108
  68. package/dist/astgraph/vendor/zod/v4/locales/hy.js +147 -147
  69. package/dist/astgraph/vendor/zod/v4/locales/id.js +106 -106
  70. package/dist/astgraph/vendor/zod/v4/locales/index.js +52 -52
  71. package/dist/astgraph/vendor/zod/v4/locales/is.js +109 -109
  72. package/dist/astgraph/vendor/zod/v4/locales/it.js +108 -108
  73. package/dist/astgraph/vendor/zod/v4/locales/ja.js +107 -107
  74. package/dist/astgraph/vendor/zod/v4/locales/ka.js +112 -112
  75. package/dist/astgraph/vendor/zod/v4/locales/kh.js +5 -5
  76. package/dist/astgraph/vendor/zod/v4/locales/km.js +110 -110
  77. package/dist/astgraph/vendor/zod/v4/locales/ko.js +111 -111
  78. package/dist/astgraph/vendor/zod/v4/locales/lt.js +203 -203
  79. package/dist/astgraph/vendor/zod/v4/locales/mk.js +109 -109
  80. package/dist/astgraph/vendor/zod/v4/locales/ms.js +107 -107
  81. package/dist/astgraph/vendor/zod/v4/locales/nl.js +110 -110
  82. package/dist/astgraph/vendor/zod/v4/locales/no.js +108 -108
  83. package/dist/astgraph/vendor/zod/v4/locales/ota.js +109 -109
  84. package/dist/astgraph/vendor/zod/v4/locales/package.json +7 -7
  85. package/dist/astgraph/vendor/zod/v4/locales/pl.js +109 -109
  86. package/dist/astgraph/vendor/zod/v4/locales/ps.js +114 -114
  87. package/dist/astgraph/vendor/zod/v4/locales/pt.js +108 -108
  88. package/dist/astgraph/vendor/zod/v4/locales/ro.js +119 -119
  89. package/dist/astgraph/vendor/zod/v4/locales/ru.js +156 -156
  90. package/dist/astgraph/vendor/zod/v4/locales/sl.js +109 -109
  91. package/dist/astgraph/vendor/zod/v4/locales/sv.js +110 -110
  92. package/dist/astgraph/vendor/zod/v4/locales/ta.js +110 -110
  93. package/dist/astgraph/vendor/zod/v4/locales/th.js +110 -110
  94. package/dist/astgraph/vendor/zod/v4/locales/tr.js +105 -105
  95. package/dist/astgraph/vendor/zod/v4/locales/ua.js +5 -5
  96. package/dist/astgraph/vendor/zod/v4/locales/uk.js +108 -108
  97. package/dist/astgraph/vendor/zod/v4/locales/ur.js +110 -110
  98. package/dist/astgraph/vendor/zod/v4/locales/uz.js +110 -110
  99. package/dist/astgraph/vendor/zod/v4/locales/vi.js +108 -108
  100. package/dist/astgraph/vendor/zod/v4/locales/yo.js +107 -107
  101. package/dist/astgraph/vendor/zod/v4/locales/zh-CN.js +109 -109
  102. package/dist/astgraph/vendor/zod/v4/locales/zh-TW.js +107 -107
  103. package/dist/astgraph/vendor/zod/v4/mini/checks.js +1 -1
  104. package/dist/astgraph/vendor/zod/v4/mini/coerce.js +22 -22
  105. package/dist/astgraph/vendor/zod/v4/mini/external.js +14 -14
  106. package/dist/astgraph/vendor/zod/v4/mini/index.js +3 -3
  107. package/dist/astgraph/vendor/zod/v4/mini/iso.js +34 -34
  108. package/dist/astgraph/vendor/zod/v4/mini/package.json +7 -7
  109. package/dist/astgraph/vendor/zod/v4/mini/parse.js +1 -1
  110. package/dist/astgraph/vendor/zod/v4/mini/schemas.js +961 -961
  111. package/dist/astgraph/vendor/zod/v4/package.json +7 -7
  112. package/dist/{chunk-Y5MZDG7I.js → chunk-5TG3E2EJ.js} +3253 -1909
  113. package/dist/{chunk-G6Q2Y2A7.js → chunk-BKARQJQZ.js} +1 -1
  114. package/dist/{chunk-XFCJXRJ2.js → chunk-IG4Z23CL.js} +6 -0
  115. package/dist/cli.js +80 -18
  116. package/dist/config/schema.d.ts +6 -0
  117. package/dist/desktop/main.js +6216 -4807
  118. package/dist/desktop-renderer/assets/{index-CCnSpff0.js → index-BCJjMvbX.js} +1 -1
  119. package/dist/desktop-renderer/index.html +1 -1
  120. package/dist/evolve/loader.d.ts +21 -0
  121. package/dist/evolve/service.d.ts +37 -0
  122. package/dist/evolve/store.d.ts +117 -0
  123. package/dist/git/insights.d.ts +52 -0
  124. package/dist/git/service.d.ts +43 -0
  125. package/dist/git/tools.d.ts +9 -0
  126. package/dist/hooks/types.d.ts +5 -1
  127. package/dist/{load-6ZMEBHSG.js → load-GKA7P6RJ.js} +1 -1
  128. package/dist/plugins/host.d.ts +9 -0
  129. package/dist/plugins/types.d.ts +1 -0
  130. package/dist/sdk/index.js +3 -3
  131. package/dist/session/store.d.ts +1 -0
  132. package/dist/tools/shell.d.ts +13 -0
  133. package/dist/ui/commands.d.ts +11 -2
  134. package/dist/ui/session.d.ts +3 -0
  135. package/package.json +1 -1
  136. package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +303 -1
package/README.md CHANGED
@@ -32,11 +32,13 @@ Flavor Code connects to OpenAI, Anthropic, or compatible services and works with
32
32
  | | Capability | What you get |
33
33
  | --- | --- | --- |
34
34
  | 🖥️ | **One runtime, three entry points** | CLI, Electron, and VS Code share model configuration, sessions, and tooling |
35
- | 🧭 | **Controlled progress on complex tasks** | Task plans, sub-agents, steering, follow-ups, `/loop`, and `/goal` |
35
+ | 🧭 | **Controlled progress on complex tasks** | Task plans, sub-agents, steering, follow-ups, `/loop`, `/goal`, and conflict-safe parallel execution (tasks owning overlapping files run serially) |
36
36
  | ⏪ | **Traceable, resumable results** | Full timeline, checkpoints, rewind, traces, diffs, and failure audits |
37
37
  | 🧠 | **Local long-term context** | Memory, Skills, plugins, and project guides stored on your machine |
38
38
  | 🔎 | **Code graph navigation** | A local AST code-graph index (`.flavor/astgraph/`) powers `ast_search`/`ast_callers`/`ast_impact` queries for precise symbol lookup and reachability tracing |
39
+ | 🌿 | **Git-native workflows** | `/commit` drafts a Conventional-Commits message for staged changes and commits after confirmation; `/review` audits uncommitted changes; the read-only `GitHistory` tool explains when and why code changed |
39
40
  | 🎨 | **E2E requirement-to-delivery** | From a rough requirement or a design export to a delivered product: PRD, interactive prototype, visual implementation, API integration, autonomous acceptance, and scored delivery (Electron only) |
41
+ | 🔁 | **Bounded self-improvement** | Repeated tool failures are captured, deduped, and proposed as suggestions; fixes ship as sandbox-verified plugins or as learned guardrail rules injected into future prompts, with run trends and rule management (`/evolve`) |
40
42
  | 🛡️ | **Clear permission boundaries** | Independent control over read, write, Shell, network, and destructive actions; Docker supported |
41
43
 
42
44
  ## Quick Start
@@ -154,11 +156,16 @@ Common commands:
154
156
  | `/mcp` | View and manage MCP servers |
155
157
  | `/loop <goal>` | Run an autonomous loop with verification |
156
158
  | `/goal <objective>` | Run the plan, execute, adversarial-review workflow |
159
+ | `/commit [hint]` | Draft a Conventional-Commits message for staged changes and commit after confirmation |
160
+ | `/review [focus]` | Review uncommitted changes for bugs and risks before committing |
161
+ | `/evolve <signals\|suggest\|improve ...>` | Self-improvement loop: review repeated tool failures, scaffold fix plugins, manage run trends and learned guardrail rules, verify and hot-reload |
157
162
  | `/pals`, `/chat`, `/co-work` | Discover and collaborate with other local CLI instances |
158
163
  | `/audit` | View tool failure audits |
159
164
 
160
165
  You can submit steering or queue follow-ups while a run is in progress; once the current model response finishes, the task picks up new instructions at safe boundaries.
161
166
 
167
+ `/commit` and `/review` use the cheap sub-agent model and degrade gracefully when it is unavailable. Session checkpoints are tagged with the current git state (`branch@sha`), so `/tree` shows what the workspace looked like at each node.
168
+
162
169
  #### CLI pals and cross-project work
163
170
 
164
171
  Interactive CLI instances on the same Windows or macOS user account can collaborate over local-only IPC (Windows named pipes or Unix sockets; no TCP fallback). Give each window a memorable alias:
@@ -261,6 +268,7 @@ Project runtime data lives under `.flavor/`:
261
268
  ├── memory/ # Long-term memory
262
269
  ├── traces/ # Optional execution traces
263
270
  ├── audit.jsonl # Tool failure audits
271
+ ├── evolve/ # Self-improvement signals and run reflections
264
272
  ├── skills/ # Project skills
265
273
  └── plugins/ # Project plugins
266
274
  ```
package/README.zh-CN.md CHANGED
@@ -32,11 +32,13 @@ Flavor Code 接入 OpenAI、Anthropic 或兼容服务,在受控工作区内使
32
32
  | | 能力 | 你得到什么 |
33
33
  | --- | --- | --- |
34
34
  | 🖥️ | **一个运行时,三个入口** | CLI、Electron 与 VS Code 共享模型配置、会话和工具能力 |
35
- | 🧭 | **复杂任务可控推进** | 任务计划、子 Agent、steering、follow-up、`/loop` 和 `/goal` |
35
+ | 🧭 | **复杂任务可控推进** | 任务计划、子 Agent、steering、follow-up、`/loop`、`/goal`,并行任务自动避免写冲突(拥有重叠文件的任务串行执行) |
36
36
  | ⏪ | **结果可追溯、可恢复** | 完整时间线、checkpoint、rewind、trace、Diff 和失败审计 |
37
37
  | 🧠 | **本地长期上下文** | 记忆、Skill、插件和项目指南均保存在本机 |
38
38
  | 🔎 | **代码图导航** | 本地 AST 代码图索引(`.flavor/astgraph/`),通过 `ast_search`/`ast_callers`/`ast_impact` 等查询精确定位符号、追踪可达性 |
39
+ | 🌿 | **Git 原生工作流** | `/commit` 为暂存改动生成 Conventional Commits 提交信息并确认提交;`/review` 审查未提交改动;只读 `GitHistory` 工具回答“这段代码为什么是这样” |
39
40
  | 🎨 | **E2E 需求到交付** | 从粗需求或设计稿到可交付产品:PRD、交互原型、视觉还原、接口联调、自主验收与评分交付(仅 Electron) |
41
+ | 🔁 | **有界自进化** | 重复的工具失败被捕获、去重并形成建议;修复以沙箱验证过的插件形式落地,或沉淀为注入后续提示词的 guardrail 规则,并支持运行趋势与规则管理(`/evolve`) |
40
42
  | 🛡️ | **明确的权限边界** | 分别控制读、写、Shell、网络和破坏性操作,也可使用 Docker |
41
43
 
42
44
  ## 快速开始
@@ -154,11 +156,16 @@ OAuth PKCE 的运行时行为与配置约定见 [PKCE 规范](./docs/specs/pkce-
154
156
  | `/mcp` | 查看和管理 MCP 服务 |
155
157
  | `/loop <goal>` | 运行带验证的自治循环 |
156
158
  | `/goal <objective>` | 运行规划、执行、对抗审查流程 |
159
+ | `/commit [hint]` | 为暂存改动生成 Conventional Commits 提交信息,确认后提交 |
160
+ | `/review [focus]` | 提交前审查未提交改动的缺陷与风险 |
161
+ | `/evolve <signals\|suggest\|improve ...>` | 自进化循环:查看重复工具失败、脚手架修复插件、管理运行趋势与 guardrail 规则、验证并热重载 |
157
162
  | `/pals`、`/chat`、`/co-work` | 发现并协作其他本机 CLI 实例 |
158
163
  | `/audit` | 查看工具失败审计 |
159
164
 
160
165
  运行中可以提交 steering 或排队 follow-up;当前模型响应结束后,任务会在安全边界处接收新指令。
161
166
 
167
+ `/commit` 与 `/review` 使用廉价子 Agent 模型,模型不可用时优雅降级。会话 checkpoint 会标记当前 git 状态(`branch@sha`),`/tree` 可以看到每个节点对应的工作区现场。
168
+
162
169
  #### CLI Pals 与跨项目协作
163
170
 
164
171
  同一 Windows 或 macOS 用户下的交互式 CLI 可以通过纯本地 IPC 协作(Windows named pipe 或 Unix socket,不回退到 TCP)。先给每个窗口一个容易识别的别名:
@@ -261,6 +268,7 @@ Skill 是带有 YAML 头信息的 `SKILL.md`,放在 `.flavor/skills/<name>/`
261
268
  ├── memory/ # 长期记忆
262
269
  ├── traces/ # 可选执行 trace
263
270
  ├── audit.jsonl # 工具失败审计
271
+ ├── evolve/ # 自进化信号与运行评估记录
264
272
  ├── skills/ # 项目 Skill
265
273
  └── plugins/ # 项目插件
266
274
  ```
@@ -6,6 +6,7 @@ export declare const TaskNodeSchema: z.ZodObject<{
6
6
  dependencies: z.ZodArray<z.ZodString>;
7
7
  expectedOutputs: z.ZodArray<z.ZodString>;
8
8
  verification: z.ZodArray<z.ZodString>;
9
+ files: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
10
  }, z.core.$strict>;
10
11
  export declare const TaskGraphSchema: z.ZodObject<{
11
12
  nodes: z.ZodArray<z.ZodObject<{
@@ -14,6 +15,7 @@ export declare const TaskGraphSchema: z.ZodObject<{
14
15
  dependencies: z.ZodArray<z.ZodString>;
15
16
  expectedOutputs: z.ZodArray<z.ZodString>;
16
17
  verification: z.ZodArray<z.ZodString>;
18
+ files: z.ZodOptional<z.ZodArray<z.ZodString>>;
17
19
  }, z.core.$strict>>;
18
20
  }, z.core.$strict>;
19
21
  export type TaskNode = z.infer<typeof TaskNodeSchema>;
@@ -15,8 +15,8 @@ import {
15
15
  packageVersion,
16
16
  redactErrorText,
17
17
  transcriptReducer
18
- } from "./chunk-Y5MZDG7I.js";
19
- import "./chunk-XFCJXRJ2.js";
18
+ } from "./chunk-5TG3E2EJ.js";
19
+ import "./chunk-IG4Z23CL.js";
20
20
  import {
21
21
  Box_default,
22
22
  ClockContext,
@@ -1,237 +1,237 @@
1
- // astgraph graph database — SQLite-backed node/edge graph (codegraph-inspired).
2
- // Uses node:sqlite with WAL mode so concurrent readers never block on a writer.
3
-
4
- import { mkdirSync } from "node:fs";
5
- import { dirname } from "node:path";
6
- import { DatabaseSync } from "node:sqlite";
7
-
8
- const SCHEMA = `
9
- CREATE TABLE IF NOT EXISTS schema_versions (
10
- version INTEGER PRIMARY KEY,
11
- applied_at INTEGER NOT NULL,
12
- description TEXT
13
- );
14
-
15
- CREATE TABLE IF NOT EXISTS files (
16
- path TEXT PRIMARY KEY,
17
- content_hash TEXT NOT NULL,
18
- language TEXT NOT NULL,
19
- size INTEGER NOT NULL,
20
- modified_at INTEGER NOT NULL DEFAULT 0,
21
- indexed_at INTEGER NOT NULL DEFAULT 0,
22
- node_count INTEGER NOT NULL DEFAULT 0
23
- );
24
-
25
- CREATE TABLE IF NOT EXISTS nodes (
26
- id TEXT PRIMARY KEY,
27
- kind TEXT NOT NULL,
28
- name TEXT NOT NULL,
29
- qualified_name TEXT NOT NULL,
30
- file_path TEXT NOT NULL,
31
- language TEXT NOT NULL,
32
- start_line INTEGER NOT NULL,
33
- end_line INTEGER NOT NULL,
34
- signature TEXT,
35
- docstring TEXT,
36
- is_exported INTEGER NOT NULL DEFAULT 0,
37
- is_async INTEGER NOT NULL DEFAULT 0
38
- );
39
-
40
- CREATE TABLE IF NOT EXISTS edges (
41
- id INTEGER PRIMARY KEY AUTOINCREMENT,
42
- source TEXT NOT NULL,
43
- target TEXT NOT NULL,
44
- kind TEXT NOT NULL,
45
- line INTEGER,
46
- col INTEGER,
47
- FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
48
- FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
49
- );
50
-
51
- CREATE TABLE IF NOT EXISTS unresolved_refs (
52
- id INTEGER PRIMARY KEY AUTOINCREMENT,
53
- from_node_id TEXT,
54
- reference_name TEXT NOT NULL,
55
- reference_kind TEXT NOT NULL,
56
- line INTEGER NOT NULL,
57
- col INTEGER NOT NULL,
58
- file_path TEXT NOT NULL DEFAULT '',
59
- module_specifier TEXT,
60
- status TEXT NOT NULL DEFAULT 'pending',
61
- FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
62
- );
63
-
64
- CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
65
- CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name));
66
- CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes(file_path);
67
- CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
68
- CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source, kind);
69
- CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target, kind);
70
- CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_identity
71
- ON edges(source, target, kind, IFNULL(line, -1), IFNULL(col, -1));
72
- CREATE INDEX IF NOT EXISTS idx_unresolved_from ON unresolved_refs(from_node_id);
73
- CREATE INDEX IF NOT EXISTS idx_unresolved_name ON unresolved_refs(reference_name);
74
-
75
- CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
76
- name,
77
- qualified_name,
78
- signature,
79
- docstring,
80
- content='nodes',
81
- content_rowid='rowid'
82
- );
83
-
84
- CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
85
- INSERT INTO nodes_fts(rowid, name, qualified_name, signature, docstring)
86
- VALUES (NEW.rowid, NEW.name, NEW.qualified_name, NEW.signature, NEW.docstring);
87
- END;
88
-
89
- CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
90
- INSERT INTO nodes_fts(nodes_fts, rowid, name, qualified_name, signature, docstring)
91
- VALUES ('delete', OLD.rowid, OLD.name, OLD.qualified_name, OLD.signature, OLD.docstring);
92
- END;
93
-
94
- CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
95
- INSERT INTO nodes_fts(nodes_fts, rowid, name, qualified_name, signature, docstring)
96
- VALUES ('delete', OLD.rowid, OLD.name, OLD.qualified_name, OLD.signature, OLD.docstring);
97
- INSERT INTO nodes_fts(rowid, name, qualified_name, signature, docstring)
98
- VALUES (NEW.rowid, NEW.name, NEW.qualified_name, NEW.signature, NEW.docstring);
99
- END;
100
-
101
- CREATE TABLE IF NOT EXISTS project_metadata (
102
- key TEXT PRIMARY KEY,
103
- value TEXT NOT NULL,
104
- updated_at INTEGER NOT NULL
105
- );
106
- `;
107
-
108
- /** Open (creating when absent) the graph database at `path`. Returns a DatabaseSync. */
109
- export function openDb(path) {
110
- if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
111
- const db = new DatabaseSync(path);
112
- db.exec("PRAGMA journal_mode = WAL");
113
- db.exec("PRAGMA foreign_keys = ON");
114
- db.exec("PRAGMA synchronous = NORMAL");
115
- db.exec(SCHEMA);
116
- const version = db.prepare("SELECT COUNT(*) AS count FROM schema_versions").get();
117
- if (version.count === 0) {
118
- db.prepare("INSERT INTO schema_versions(version, applied_at, description) VALUES (?, ?, ?)")
119
- .run(1, Date.now(), "Initial astgraph schema");
120
- }
121
- return db;
122
- }
123
-
124
- /** Insert or replace the tracking record for one source file. */
125
- export function upsertFileRecord(db, record) {
126
- db.prepare(`
127
- INSERT INTO files(path, content_hash, language, size, modified_at, indexed_at, node_count)
128
- VALUES (?, ?, ?, ?, ?, ?, ?)
129
- ON CONFLICT(path) DO UPDATE SET
130
- content_hash = excluded.content_hash,
131
- language = excluded.language,
132
- size = excluded.size,
133
- modified_at = excluded.modified_at,
134
- indexed_at = excluded.indexed_at,
135
- node_count = excluded.node_count
136
- `).run(
137
- record.path, record.contentHash, record.language, record.size,
138
- record.modifiedAt ?? Date.now(), record.indexedAt ?? Date.now(), record.nodeCount ?? 0,
139
- );
140
- }
141
-
142
- /** Read the tracking record for one source file, or undefined when absent. */
143
- export function getFileRecord(db, path) {
144
- const row = db.prepare("SELECT * FROM files WHERE path = ?").get(path);
145
- if (row === undefined) return undefined;
146
- return {
147
- path: row.path, contentHash: row.content_hash, language: row.language,
148
- size: row.size, modifiedAt: row.modified_at, indexedAt: row.indexed_at,
149
- nodeCount: row.node_count,
150
- };
151
- }
152
-
153
- /** Delete a file record; nodes cascade (and with them edges + refs + FTS rows). */
154
- export function deleteFileRecord(db, path) {
155
- db.prepare("DELETE FROM nodes WHERE file_path = ?").run(path);
156
- db.prepare("DELETE FROM files WHERE path = ?").run(path);
157
- }
158
-
159
- /**
160
- * Replace every node belonging to a file. Runs inside one transaction; the FTS
161
- * triggers keep nodes_fts in sync.
162
- */
163
- export function replaceNodes(db, filePath, nodes) {
164
- const insert = db.prepare(`
165
- INSERT OR REPLACE INTO nodes(
166
- id, kind, name, qualified_name, file_path, language,
167
- start_line, end_line, signature, docstring, is_exported, is_async
168
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
169
- `);
170
- const clear = db.prepare("DELETE FROM nodes WHERE file_path = ?");
171
- db.exec("BEGIN");
172
- try {
173
- clear.run(filePath);
174
- for (const node of nodes) {
175
- insert.run(
176
- node.id, node.kind, node.name, node.qualifiedName, node.filePath, node.language,
177
- node.startLine, node.endLine, node.signature ?? null, node.docstring ?? null,
178
- node.isExported ? 1 : 0, node.isAsync ? 1 : 0,
179
- );
180
- }
181
- db.exec("COMMIT");
182
- } catch (error) {
183
- db.exec("ROLLBACK");
184
- throw error;
185
- }
186
- }
187
-
188
- /** Insert one edge; duplicate (source, target, kind, line, col) rows are ignored. */
189
- export function insertEdge(db, edge) {
190
- db.prepare(`
191
- INSERT OR IGNORE INTO edges(source, target, kind, line, col) VALUES (?, ?, ?, ?, ?)
192
- `).run(edge.source, edge.target, edge.kind, edge.line ?? null, edge.col ?? null);
193
- }
194
-
195
- /** Delete every edge whose source OR target belongs to a file. */
196
- export function deleteEdgesForFile(db, filePath) {
197
- db.prepare(`
198
- DELETE FROM edges WHERE source IN (SELECT id FROM nodes WHERE file_path = ?)
199
- OR target IN (SELECT id FROM nodes WHERE file_path = ?)
200
- `).run(filePath, filePath);
201
- }
202
-
203
- /** Insert one unresolved cross-file reference (status 'pending'). */
204
- export function insertUnresolvedRef(db, ref) {
205
- db.prepare(`
206
- INSERT INTO unresolved_refs(from_node_id, reference_name, reference_kind, line, col, file_path, module_specifier, status)
207
- VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
208
- `).run(ref.fromNodeId, ref.referenceName, ref.referenceKind, ref.line, ref.col, ref.filePath, ref.moduleSpecifier ?? null);
209
- }
210
-
211
- /** Delete every unresolved reference originating from a file. */
212
- export function deleteUnresolvedRefsForFile(db, filePath) {
213
- db.prepare("DELETE FROM unresolved_refs WHERE file_path = ?").run(filePath);
214
- }
215
-
216
- /** Summary counters used by /ast status. */
217
- export function stats(db) {
218
- const files = db.prepare("SELECT COUNT(*) AS count FROM files").get().count;
219
- const nodes = db.prepare("SELECT COUNT(*) AS count FROM nodes").get().count;
220
- const edges = db.prepare("SELECT COUNT(*) AS count FROM edges").get().count;
221
- const unresolved = db.prepare("SELECT COUNT(*) AS count FROM unresolved_refs WHERE status = 'pending'").get().count;
222
- return { files, nodes, edges, unresolved };
223
- }
224
-
225
- /** Store a provenance metadata value. */
226
- export function setMetadata(db, key, value) {
227
- db.prepare(`
228
- INSERT INTO project_metadata(key, value, updated_at) VALUES (?, ?, ?)
229
- ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
230
- `).run(key, value, Date.now());
231
- }
232
-
233
- /** Read a provenance metadata value, or undefined when absent. */
234
- export function getMetadata(db, key) {
235
- const row = db.prepare("SELECT value FROM project_metadata WHERE key = ?").get(key);
236
- return row?.value;
237
- }
1
+ // astgraph graph database — SQLite-backed node/edge graph (codegraph-inspired).
2
+ // Uses node:sqlite with WAL mode so concurrent readers never block on a writer.
3
+
4
+ import { mkdirSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
+ import { DatabaseSync } from "node:sqlite";
7
+
8
+ const SCHEMA = `
9
+ CREATE TABLE IF NOT EXISTS schema_versions (
10
+ version INTEGER PRIMARY KEY,
11
+ applied_at INTEGER NOT NULL,
12
+ description TEXT
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS files (
16
+ path TEXT PRIMARY KEY,
17
+ content_hash TEXT NOT NULL,
18
+ language TEXT NOT NULL,
19
+ size INTEGER NOT NULL,
20
+ modified_at INTEGER NOT NULL DEFAULT 0,
21
+ indexed_at INTEGER NOT NULL DEFAULT 0,
22
+ node_count INTEGER NOT NULL DEFAULT 0
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS nodes (
26
+ id TEXT PRIMARY KEY,
27
+ kind TEXT NOT NULL,
28
+ name TEXT NOT NULL,
29
+ qualified_name TEXT NOT NULL,
30
+ file_path TEXT NOT NULL,
31
+ language TEXT NOT NULL,
32
+ start_line INTEGER NOT NULL,
33
+ end_line INTEGER NOT NULL,
34
+ signature TEXT,
35
+ docstring TEXT,
36
+ is_exported INTEGER NOT NULL DEFAULT 0,
37
+ is_async INTEGER NOT NULL DEFAULT 0
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS edges (
41
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ source TEXT NOT NULL,
43
+ target TEXT NOT NULL,
44
+ kind TEXT NOT NULL,
45
+ line INTEGER,
46
+ col INTEGER,
47
+ FOREIGN KEY (source) REFERENCES nodes(id) ON DELETE CASCADE,
48
+ FOREIGN KEY (target) REFERENCES nodes(id) ON DELETE CASCADE
49
+ );
50
+
51
+ CREATE TABLE IF NOT EXISTS unresolved_refs (
52
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
53
+ from_node_id TEXT,
54
+ reference_name TEXT NOT NULL,
55
+ reference_kind TEXT NOT NULL,
56
+ line INTEGER NOT NULL,
57
+ col INTEGER NOT NULL,
58
+ file_path TEXT NOT NULL DEFAULT '',
59
+ module_specifier TEXT,
60
+ status TEXT NOT NULL DEFAULT 'pending',
61
+ FOREIGN KEY (from_node_id) REFERENCES nodes(id) ON DELETE CASCADE
62
+ );
63
+
64
+ CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
65
+ CREATE INDEX IF NOT EXISTS idx_nodes_lower_name ON nodes(lower(name));
66
+ CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes(file_path);
67
+ CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind);
68
+ CREATE INDEX IF NOT EXISTS idx_edges_source_kind ON edges(source, kind);
69
+ CREATE INDEX IF NOT EXISTS idx_edges_target_kind ON edges(target, kind);
70
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_edges_identity
71
+ ON edges(source, target, kind, IFNULL(line, -1), IFNULL(col, -1));
72
+ CREATE INDEX IF NOT EXISTS idx_unresolved_from ON unresolved_refs(from_node_id);
73
+ CREATE INDEX IF NOT EXISTS idx_unresolved_name ON unresolved_refs(reference_name);
74
+
75
+ CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
76
+ name,
77
+ qualified_name,
78
+ signature,
79
+ docstring,
80
+ content='nodes',
81
+ content_rowid='rowid'
82
+ );
83
+
84
+ CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
85
+ INSERT INTO nodes_fts(rowid, name, qualified_name, signature, docstring)
86
+ VALUES (NEW.rowid, NEW.name, NEW.qualified_name, NEW.signature, NEW.docstring);
87
+ END;
88
+
89
+ CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
90
+ INSERT INTO nodes_fts(nodes_fts, rowid, name, qualified_name, signature, docstring)
91
+ VALUES ('delete', OLD.rowid, OLD.name, OLD.qualified_name, OLD.signature, OLD.docstring);
92
+ END;
93
+
94
+ CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
95
+ INSERT INTO nodes_fts(nodes_fts, rowid, name, qualified_name, signature, docstring)
96
+ VALUES ('delete', OLD.rowid, OLD.name, OLD.qualified_name, OLD.signature, OLD.docstring);
97
+ INSERT INTO nodes_fts(rowid, name, qualified_name, signature, docstring)
98
+ VALUES (NEW.rowid, NEW.name, NEW.qualified_name, NEW.signature, NEW.docstring);
99
+ END;
100
+
101
+ CREATE TABLE IF NOT EXISTS project_metadata (
102
+ key TEXT PRIMARY KEY,
103
+ value TEXT NOT NULL,
104
+ updated_at INTEGER NOT NULL
105
+ );
106
+ `;
107
+
108
+ /** Open (creating when absent) the graph database at `path`. Returns a DatabaseSync. */
109
+ export function openDb(path) {
110
+ if (path !== ":memory:") mkdirSync(dirname(path), { recursive: true });
111
+ const db = new DatabaseSync(path);
112
+ db.exec("PRAGMA journal_mode = WAL");
113
+ db.exec("PRAGMA foreign_keys = ON");
114
+ db.exec("PRAGMA synchronous = NORMAL");
115
+ db.exec(SCHEMA);
116
+ const version = db.prepare("SELECT COUNT(*) AS count FROM schema_versions").get();
117
+ if (version.count === 0) {
118
+ db.prepare("INSERT INTO schema_versions(version, applied_at, description) VALUES (?, ?, ?)")
119
+ .run(1, Date.now(), "Initial astgraph schema");
120
+ }
121
+ return db;
122
+ }
123
+
124
+ /** Insert or replace the tracking record for one source file. */
125
+ export function upsertFileRecord(db, record) {
126
+ db.prepare(`
127
+ INSERT INTO files(path, content_hash, language, size, modified_at, indexed_at, node_count)
128
+ VALUES (?, ?, ?, ?, ?, ?, ?)
129
+ ON CONFLICT(path) DO UPDATE SET
130
+ content_hash = excluded.content_hash,
131
+ language = excluded.language,
132
+ size = excluded.size,
133
+ modified_at = excluded.modified_at,
134
+ indexed_at = excluded.indexed_at,
135
+ node_count = excluded.node_count
136
+ `).run(
137
+ record.path, record.contentHash, record.language, record.size,
138
+ record.modifiedAt ?? Date.now(), record.indexedAt ?? Date.now(), record.nodeCount ?? 0,
139
+ );
140
+ }
141
+
142
+ /** Read the tracking record for one source file, or undefined when absent. */
143
+ export function getFileRecord(db, path) {
144
+ const row = db.prepare("SELECT * FROM files WHERE path = ?").get(path);
145
+ if (row === undefined) return undefined;
146
+ return {
147
+ path: row.path, contentHash: row.content_hash, language: row.language,
148
+ size: row.size, modifiedAt: row.modified_at, indexedAt: row.indexed_at,
149
+ nodeCount: row.node_count,
150
+ };
151
+ }
152
+
153
+ /** Delete a file record; nodes cascade (and with them edges + refs + FTS rows). */
154
+ export function deleteFileRecord(db, path) {
155
+ db.prepare("DELETE FROM nodes WHERE file_path = ?").run(path);
156
+ db.prepare("DELETE FROM files WHERE path = ?").run(path);
157
+ }
158
+
159
+ /**
160
+ * Replace every node belonging to a file. Runs inside one transaction; the FTS
161
+ * triggers keep nodes_fts in sync.
162
+ */
163
+ export function replaceNodes(db, filePath, nodes) {
164
+ const insert = db.prepare(`
165
+ INSERT OR REPLACE INTO nodes(
166
+ id, kind, name, qualified_name, file_path, language,
167
+ start_line, end_line, signature, docstring, is_exported, is_async
168
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
169
+ `);
170
+ const clear = db.prepare("DELETE FROM nodes WHERE file_path = ?");
171
+ db.exec("BEGIN");
172
+ try {
173
+ clear.run(filePath);
174
+ for (const node of nodes) {
175
+ insert.run(
176
+ node.id, node.kind, node.name, node.qualifiedName, node.filePath, node.language,
177
+ node.startLine, node.endLine, node.signature ?? null, node.docstring ?? null,
178
+ node.isExported ? 1 : 0, node.isAsync ? 1 : 0,
179
+ );
180
+ }
181
+ db.exec("COMMIT");
182
+ } catch (error) {
183
+ db.exec("ROLLBACK");
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ /** Insert one edge; duplicate (source, target, kind, line, col) rows are ignored. */
189
+ export function insertEdge(db, edge) {
190
+ db.prepare(`
191
+ INSERT OR IGNORE INTO edges(source, target, kind, line, col) VALUES (?, ?, ?, ?, ?)
192
+ `).run(edge.source, edge.target, edge.kind, edge.line ?? null, edge.col ?? null);
193
+ }
194
+
195
+ /** Delete every edge whose source OR target belongs to a file. */
196
+ export function deleteEdgesForFile(db, filePath) {
197
+ db.prepare(`
198
+ DELETE FROM edges WHERE source IN (SELECT id FROM nodes WHERE file_path = ?)
199
+ OR target IN (SELECT id FROM nodes WHERE file_path = ?)
200
+ `).run(filePath, filePath);
201
+ }
202
+
203
+ /** Insert one unresolved cross-file reference (status 'pending'). */
204
+ export function insertUnresolvedRef(db, ref) {
205
+ db.prepare(`
206
+ INSERT INTO unresolved_refs(from_node_id, reference_name, reference_kind, line, col, file_path, module_specifier, status)
207
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
208
+ `).run(ref.fromNodeId, ref.referenceName, ref.referenceKind, ref.line, ref.col, ref.filePath, ref.moduleSpecifier ?? null);
209
+ }
210
+
211
+ /** Delete every unresolved reference originating from a file. */
212
+ export function deleteUnresolvedRefsForFile(db, filePath) {
213
+ db.prepare("DELETE FROM unresolved_refs WHERE file_path = ?").run(filePath);
214
+ }
215
+
216
+ /** Summary counters used by /ast status. */
217
+ export function stats(db) {
218
+ const files = db.prepare("SELECT COUNT(*) AS count FROM files").get().count;
219
+ const nodes = db.prepare("SELECT COUNT(*) AS count FROM nodes").get().count;
220
+ const edges = db.prepare("SELECT COUNT(*) AS count FROM edges").get().count;
221
+ const unresolved = db.prepare("SELECT COUNT(*) AS count FROM unresolved_refs WHERE status = 'pending'").get().count;
222
+ return { files, nodes, edges, unresolved };
223
+ }
224
+
225
+ /** Store a provenance metadata value. */
226
+ export function setMetadata(db, key, value) {
227
+ db.prepare(`
228
+ INSERT INTO project_metadata(key, value, updated_at) VALUES (?, ?, ?)
229
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
230
+ `).run(key, value, Date.now());
231
+ }
232
+
233
+ /** Read a provenance metadata value, or undefined when absent. */
234
+ export function getMetadata(db, key) {
235
+ const row = db.prepare("SELECT value FROM project_metadata WHERE key = ?").get(key);
236
+ return row?.value;
237
+ }