flavor-code 1.2.10 → 1.2.13

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 (142) hide show
  1. package/README.md +33 -43
  2. package/README.zh-CN.md +34 -42
  3. package/dist/{app-USAFQVSX.js → app-Z3YWFIBB.js} +42 -9
  4. package/dist/astgraph/db.mjs +237 -0
  5. package/dist/astgraph/extract.mjs +239 -0
  6. package/dist/astgraph/flavor-plugin.json +25 -0
  7. package/dist/astgraph/grammars.mjs +68 -0
  8. package/dist/astgraph/index.js +301 -0
  9. package/dist/astgraph/indexer.mjs +175 -0
  10. package/dist/astgraph/query.mjs +165 -0
  11. package/dist/astgraph/resolve.mjs +93 -0
  12. package/dist/astgraph/vendor/tree-sitter-javascript.wasm +0 -0
  13. package/dist/astgraph/vendor/tree-sitter-tsx.wasm +0 -0
  14. package/dist/astgraph/vendor/tree-sitter-typescript.wasm +0 -0
  15. package/dist/astgraph/vendor/tree-sitter.js +3980 -0
  16. package/dist/astgraph/vendor/tree-sitter.wasm +0 -0
  17. package/dist/astgraph/vendor/zod/LICENSE +21 -0
  18. package/dist/astgraph/vendor/zod/index.js +4 -0
  19. package/dist/astgraph/vendor/zod/locales/index.js +1 -0
  20. package/dist/astgraph/vendor/zod/locales/package.json +7 -0
  21. package/dist/astgraph/vendor/zod/package.json +135 -0
  22. package/dist/astgraph/vendor/zod/v4/classic/checks.js +1 -0
  23. package/dist/astgraph/vendor/zod/v4/classic/coerce.js +17 -0
  24. package/dist/astgraph/vendor/zod/v4/classic/compat.js +31 -0
  25. package/dist/astgraph/vendor/zod/v4/classic/errors.js +48 -0
  26. package/dist/astgraph/vendor/zod/v4/classic/external.js +20 -0
  27. package/dist/astgraph/vendor/zod/v4/classic/from-json-schema.js +599 -0
  28. package/dist/astgraph/vendor/zod/v4/classic/index.js +4 -0
  29. package/dist/astgraph/vendor/zod/v4/classic/iso.js +30 -0
  30. package/dist/astgraph/vendor/zod/v4/classic/package.json +7 -0
  31. package/dist/astgraph/vendor/zod/v4/classic/parse.js +15 -0
  32. package/dist/astgraph/vendor/zod/v4/classic/schemas.js +1395 -0
  33. package/dist/astgraph/vendor/zod/v4/core/api.js +1087 -0
  34. package/dist/astgraph/vendor/zod/v4/core/checks.js +575 -0
  35. package/dist/astgraph/vendor/zod/v4/core/core.js +78 -0
  36. package/dist/astgraph/vendor/zod/v4/core/doc.js +35 -0
  37. package/dist/astgraph/vendor/zod/v4/core/errors.js +185 -0
  38. package/dist/astgraph/vendor/zod/v4/core/index.js +16 -0
  39. package/dist/astgraph/vendor/zod/v4/core/json-schema-generator.js +95 -0
  40. package/dist/astgraph/vendor/zod/v4/core/json-schema-processors.js +601 -0
  41. package/dist/astgraph/vendor/zod/v4/core/json-schema.js +1 -0
  42. package/dist/astgraph/vendor/zod/v4/core/package.json +7 -0
  43. package/dist/astgraph/vendor/zod/v4/core/parse.js +93 -0
  44. package/dist/astgraph/vendor/zod/v4/core/regexes.js +139 -0
  45. package/dist/astgraph/vendor/zod/v4/core/registries.js +51 -0
  46. package/dist/astgraph/vendor/zod/v4/core/schemas.js +2239 -0
  47. package/dist/astgraph/vendor/zod/v4/core/standard-schema.js +1 -0
  48. package/dist/astgraph/vendor/zod/v4/core/to-json-schema.js +448 -0
  49. package/dist/astgraph/vendor/zod/v4/core/util.js +674 -0
  50. package/dist/astgraph/vendor/zod/v4/core/versions.js +5 -0
  51. package/dist/astgraph/vendor/zod/v4/index.js +3 -0
  52. package/dist/astgraph/vendor/zod/v4/locales/ar.js +106 -0
  53. package/dist/astgraph/vendor/zod/v4/locales/az.js +105 -0
  54. package/dist/astgraph/vendor/zod/v4/locales/be.js +156 -0
  55. package/dist/astgraph/vendor/zod/v4/locales/bg.js +120 -0
  56. package/dist/astgraph/vendor/zod/v4/locales/ca.js +107 -0
  57. package/dist/astgraph/vendor/zod/v4/locales/cs.js +111 -0
  58. package/dist/astgraph/vendor/zod/v4/locales/da.js +115 -0
  59. package/dist/astgraph/vendor/zod/v4/locales/de.js +108 -0
  60. package/dist/astgraph/vendor/zod/v4/locales/el.js +109 -0
  61. package/dist/astgraph/vendor/zod/v4/locales/en.js +113 -0
  62. package/dist/astgraph/vendor/zod/v4/locales/eo.js +109 -0
  63. package/dist/astgraph/vendor/zod/v4/locales/es.js +132 -0
  64. package/dist/astgraph/vendor/zod/v4/locales/fa.js +114 -0
  65. package/dist/astgraph/vendor/zod/v4/locales/fi.js +112 -0
  66. package/dist/astgraph/vendor/zod/v4/locales/fr-CA.js +107 -0
  67. package/dist/astgraph/vendor/zod/v4/locales/fr.js +125 -0
  68. package/dist/astgraph/vendor/zod/v4/locales/he.js +214 -0
  69. package/dist/astgraph/vendor/zod/v4/locales/hr.js +122 -0
  70. package/dist/astgraph/vendor/zod/v4/locales/hu.js +108 -0
  71. package/dist/astgraph/vendor/zod/v4/locales/hy.js +147 -0
  72. package/dist/astgraph/vendor/zod/v4/locales/id.js +106 -0
  73. package/dist/astgraph/vendor/zod/v4/locales/index.js +52 -0
  74. package/dist/astgraph/vendor/zod/v4/locales/is.js +109 -0
  75. package/dist/astgraph/vendor/zod/v4/locales/it.js +108 -0
  76. package/dist/astgraph/vendor/zod/v4/locales/ja.js +107 -0
  77. package/dist/astgraph/vendor/zod/v4/locales/ka.js +112 -0
  78. package/dist/astgraph/vendor/zod/v4/locales/kh.js +5 -0
  79. package/dist/astgraph/vendor/zod/v4/locales/km.js +110 -0
  80. package/dist/astgraph/vendor/zod/v4/locales/ko.js +111 -0
  81. package/dist/astgraph/vendor/zod/v4/locales/lt.js +203 -0
  82. package/dist/astgraph/vendor/zod/v4/locales/mk.js +109 -0
  83. package/dist/astgraph/vendor/zod/v4/locales/ms.js +107 -0
  84. package/dist/astgraph/vendor/zod/v4/locales/nl.js +110 -0
  85. package/dist/astgraph/vendor/zod/v4/locales/no.js +108 -0
  86. package/dist/astgraph/vendor/zod/v4/locales/ota.js +109 -0
  87. package/dist/astgraph/vendor/zod/v4/locales/package.json +7 -0
  88. package/dist/astgraph/vendor/zod/v4/locales/pl.js +109 -0
  89. package/dist/astgraph/vendor/zod/v4/locales/ps.js +114 -0
  90. package/dist/astgraph/vendor/zod/v4/locales/pt.js +108 -0
  91. package/dist/astgraph/vendor/zod/v4/locales/ro.js +119 -0
  92. package/dist/astgraph/vendor/zod/v4/locales/ru.js +156 -0
  93. package/dist/astgraph/vendor/zod/v4/locales/sl.js +109 -0
  94. package/dist/astgraph/vendor/zod/v4/locales/sv.js +110 -0
  95. package/dist/astgraph/vendor/zod/v4/locales/ta.js +110 -0
  96. package/dist/astgraph/vendor/zod/v4/locales/th.js +110 -0
  97. package/dist/astgraph/vendor/zod/v4/locales/tr.js +105 -0
  98. package/dist/astgraph/vendor/zod/v4/locales/ua.js +5 -0
  99. package/dist/astgraph/vendor/zod/v4/locales/uk.js +108 -0
  100. package/dist/astgraph/vendor/zod/v4/locales/ur.js +110 -0
  101. package/dist/astgraph/vendor/zod/v4/locales/uz.js +110 -0
  102. package/dist/astgraph/vendor/zod/v4/locales/vi.js +108 -0
  103. package/dist/astgraph/vendor/zod/v4/locales/yo.js +107 -0
  104. package/dist/astgraph/vendor/zod/v4/locales/zh-CN.js +109 -0
  105. package/dist/astgraph/vendor/zod/v4/locales/zh-TW.js +107 -0
  106. package/dist/astgraph/vendor/zod/v4/mini/checks.js +1 -0
  107. package/dist/astgraph/vendor/zod/v4/mini/coerce.js +22 -0
  108. package/dist/astgraph/vendor/zod/v4/mini/external.js +14 -0
  109. package/dist/astgraph/vendor/zod/v4/mini/index.js +3 -0
  110. package/dist/astgraph/vendor/zod/v4/mini/iso.js +34 -0
  111. package/dist/astgraph/vendor/zod/v4/mini/package.json +7 -0
  112. package/dist/astgraph/vendor/zod/v4/mini/parse.js +1 -0
  113. package/dist/astgraph/vendor/zod/v4/mini/schemas.js +961 -0
  114. package/dist/astgraph/vendor/zod/v4/package.json +7 -0
  115. package/dist/{chunk-G32MCAZA.js → chunk-G6Q2Y2A7.js} +1 -1
  116. package/dist/{chunk-WGYNTR4Q.js → chunk-Y5MZDG7I.js} +3364 -626
  117. package/dist/cli.js +62 -23
  118. package/dist/desktop/main.js +2374 -435
  119. package/dist/desktop-renderer/assets/index-CCnSpff0.js +151 -0
  120. package/dist/desktop-renderer/index.html +1 -1
  121. package/dist/init/project.d.ts +8 -0
  122. package/dist/models/structured.d.ts +14 -0
  123. package/dist/pals/address.d.ts +6 -0
  124. package/dist/pals/auth.d.ts +7 -0
  125. package/dist/pals/broker-cli.d.ts +18 -0
  126. package/dist/pals/broker.d.ts +156 -0
  127. package/dist/pals/client.d.ts +109 -0
  128. package/dist/pals/lifecycle.d.ts +13 -0
  129. package/dist/pals/prompt.d.ts +23 -0
  130. package/dist/pals/protocol.d.ts +526 -0
  131. package/dist/pals/tools.d.ts +44 -0
  132. package/dist/permissions/engine.d.ts +2 -0
  133. package/dist/plugins/types.d.ts +2 -2
  134. package/dist/production.d.ts +12 -0
  135. package/dist/sdk/index.js +2 -2
  136. package/dist/tools/shell.d.ts +1 -1
  137. package/dist/tools/types.d.ts +4 -0
  138. package/dist/ui/commands.d.ts +35 -3
  139. package/dist/ui/session.d.ts +51 -2
  140. package/dist/ui/transcript.d.ts +6 -0
  141. package/package.json +5 -3
  142. package/dist/desktop-renderer/assets/index-BVxdOZFW.js +0 -151
package/README.md CHANGED
@@ -35,50 +35,10 @@ Flavor Code connects to OpenAI, Anthropic, or compatible services and works with
35
35
  | 🧭 | **Controlled progress on complex tasks** | Task plans, sub-agents, steering, follow-ups, `/loop`, and `/goal` |
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
- | 🎨 | **D2C design-to-code** | Import Pixso exports; the agent generates Vue/React implementations with automatic pixel-level visual evaluation (Electron only) |
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
+ | 🎨 | **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) |
39
40
  | 🛡️ | **Clear permission boundaries** | Independent control over read, write, Shell, network, and destructive actions; Docker supported |
40
41
 
41
- ## 1.2.10 D2C Acceptance & Delivery
42
-
43
- 1.2.10 hardens the D2C acceptance loop: failed authentication prerequisites block protected scenarios immediately, request recording survives navigation, repair prompts accept extra instructions, and the backend process restarts automatically when its source changes.
44
-
45
- | Feature | How to use |
46
- | --- | --- |
47
- | **Auth-prerequisite fail-fast** | When a login / sign-in scenario (e.g. `POST /api/v1/auth/login`) fails during interactive acceptance, later protected scenarios are reported as blocked by that failed prerequisite instead of being executed one by one, so the root cause is visible immediately. |
48
- | **Navigation-safe request recording** | Request recording now persists across in-app navigation through `sessionStorage` (up to 500 entries). Requests fired after a click that navigates to another page are captured too, so post-navigation behavior can still be asserted. |
49
- | **Extra repair instructions** | Before repairing failed interaction scenarios, type extra requirements in the “补充修复要求” box; they are injected into the repair prompt as user-supplied constraints together with the failure details. |
50
- | **Acceptance as a separate stage** | The D2C workbench now splits “API Integration” and “Acceptance & Delivery” into two explicit stages; after an automated repair run finishes, the workbench switches to the acceptance tab automatically. |
51
- | **Backend source fingerprinting** | The mock/server backend is fingerprinted when it starts. If its source files change, the runtime detects the fingerprint difference and restarts the backend before acceptance, so tests always run against the code currently on disk. |
52
-
53
- ## 1.2.9 Runtime Productivity
54
-
55
- 1.2.9 adds layered project instructions, safe writes, background jobs, persistent terminals, and native web tools. Usually you can just describe the goal in natural language and the agent picks the right tool; when you need precise control, name the tool and its parameters explicitly in the prompt.
56
-
57
- | Feature | How to use |
58
- | --- | --- |
59
- | **Layered project instructions** | Put `AGENTS.md` / `CLAUDE.md` in the project root or subdirectories; use `AGENTS.local.md` / `CLAUDE.local.md` for local additions in the same directory. Root rules load at startup; subdirectory rules load automatically when the agent touches files there. |
60
- | **Per-turn change summary** | No configuration needed. After a successful `Write`, `Edit`, or `ApplyPatch`, the turn shows a color-coded `CHANGESET` receipt with workspace-relative paths, `CREATE` / `UPDATE` / `DELETE` operations, per-file line counts, and a total. At most 8 files are shown, with an explicit shown/total footer when more changed. |
61
- | **File version protection** | No configuration needed. If the IDE, a formatter, or another process modifies a file after the agent read it, the next write fails with `Stale file`; ask the agent to re-read before editing. |
62
- | **Standard tool presentation protocol** | Tool authors can declare `outputSchema`, `renderForModel`, `presentCall`, and `presentResult`, so the same result can use an appropriate form in the model context, CLI, and desktop. The CLI visually separates file diffs, web evidence, job receipts, foreground `COMMAND` output, and persistent `TERMINAL` output from the final answer. |
63
- | **Background Shell / Jobs** | Say "start the dev server in the background" and the agent calls `Shell` with `background: true`. Use `JobList` to view jobs, `JobRead` for incremental output, `JobWait` to wait, and `JobKill` to stop. The CLI shows a color-bordered `JOB` receipt separating job metadata, logs, and the final answer; logs show at most the latest 12 lines and lists at most 8 items. Windows prefers UTF-8 and falls back automatically on GBK/GB18030 system diagnostics. |
64
- | **Foreground command results** | Foreground `Shell` calls render as state-colored `COMMAND` receipts with separate command, stdout, stderr, and exit regions. Long output keeps the first and last 8 lines and explicitly folds the middle; persistent PTY output uses the distinct `TERMINAL` label. |
65
- | **Desktop background status** | Electron automatically shows the number of running jobs in the session title bar, updated live on start, output, exit, or cancel. |
66
- | **Persistent PTY** | Say "open a persistent terminal and keep interacting". The agent uses `TerminalOpen` to create a terminal, `TerminalWrite` for input, `TerminalRead` for incremental output, and `TerminalClose` to close it. |
67
- | **Unified D2C/E2E process lifecycle** | No usage change. Preview and backend services still start/stop from the E2E/D2C workbench, but the underlying layer unifies output limits, process-tree termination, and idempotent cleanup. |
68
- | **Native WebSearch** | Say "search the web for ...", or explicitly ask for `WebSearch`. It uses keyless DuckDuckGo Lite by default and degrades to Bing on connection failure, HTTP rejection, or no parseable results; up to 20 results per call. The CLI puts the top 5 into a bordered `WEB SEARCH` evidence block with titles and compact sources in search order. |
69
- | **Native WebFetch** | Say "read this page: `https://...`", or explicitly ask for `WebFetch`. Supports HTTP(S), redirects, HTML-to-text, timeouts, and response size limits, and is compatible with Clash/TUN Fake-IP DNS. Direct access to Fake-IP, intranet, or cloud metadata addresses is still blocked; network operations still follow Flavor permission approval. |
70
-
71
- Common precise usage:
72
-
73
- ```text
74
- Start npm run dev with Shell in background mode, then use JobRead to inspect the startup logs.
75
- Open a persistent terminal, run a Python REPL in it, execute two snippets, then close the terminal.
76
- Use WebSearch to find the official TypeScript 7 migration notes, then WebFetch the most relevant official page.
77
- This directory has its own conventions; follow src/payments/AGENTS.md before modifying code here.
78
- ```
79
-
80
- See [Technical Design Report §38](./技术方案报告.md#38-129-运行时生产力与原生-web-能力) for tool parameters, state machines, security boundaries, and extension interfaces; acceptance criteria are in the [Runtime productivity spec](./docs/specs/2026-08-13-runtime-productivity-waves.md).
81
-
82
42
  ## Quick Start
83
43
 
84
44
  > [!IMPORTANT]
@@ -194,10 +154,39 @@ Common commands:
194
154
  | `/mcp` | View and manage MCP servers |
195
155
  | `/loop <goal>` | Run an autonomous loop with verification |
196
156
  | `/goal <objective>` | Run the plan, execute, adversarial-review workflow |
157
+ | `/pals`, `/chat`, `/co-work` | Discover and collaborate with other local CLI instances |
197
158
  | `/audit` | View tool failure audits |
198
159
 
199
160
  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.
200
161
 
162
+ #### CLI pals and cross-project work
163
+
164
+ 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:
165
+
166
+ ```bash
167
+ # terminal A, in project A
168
+ flavor --pal-name A
169
+
170
+ # terminal B, in project B
171
+ flavor --pal-name B
172
+ ```
173
+
174
+ Useful commands:
175
+
176
+ ```text
177
+ /pals # aliases and per-process UUIDs
178
+ /pals --verbose # also show project paths and timestamps
179
+ /pals rename api # rename this active instance
180
+ /chat B Update the API and tests # deliver to B and start its agent safely
181
+ /co-work B Upgrade B, then adapt A # negotiate one plan before parallel work
182
+ /co-work status [co-work-uuid]
183
+ /co-work cancel <co-work-uuid> [reason]
184
+ ```
185
+
186
+ `/chat` is bidirectional and task-oriented. If B is idle, the attributed message starts a normal model turn; if B is already running, it becomes steering, or a follow-up when another local submission is pending. Remote text is converted to a safe non-slash prompt, so `/exit`-like text is not dispatched as a local command. B can answer with `/chat A ...`.
187
+
188
+ `/co-work` first places both agents in planning and waits for both to accept the same hashed plan and declare READY. Early READY intents are retained, and only the broker's exactly-once START event opens parallel execution. Each agent works only in its own project, receives only its assigned tasks, and reports bounded completion evidence. The broker-selected integration owner verifies all assertions and emits END or FAIL through `CoWorkIntegrate`. Communication uses authenticated, bounded local IPC with no TCP listener; peer input cannot approve tools or access the other workspace. UUID/alias routing and the protocol already support a third active client; durable artifact exchange, broker-restart journaling/recovery, and large-group coordination are later hardening work. See the [CLI pals specification](./docs/specs/2026-08-14-cli-pals-cowork.md).
189
+
201
190
  ### Electron Desktop
202
191
 
203
192
  ```bash
@@ -209,7 +198,7 @@ npm run desktop:dist # Windows NSIS installer
209
198
 
210
199
  The desktop app provides project and session switching, streaming Markdown, tool and diff views, permission confirmations, task status, and management of Skills, MCP, memory, and models.
211
200
 
212
- The **D2C** module in the sidebar supports a complete design-to-code loop: import a Pixso-exported HTML directory, choose a target framework (Vue 3 / React), and submit the generation task to the current session. The agent implements it under `src/d2c-output/<task>/` following the `d2c-pixso` skill (SOP); a Vite dev server then starts automatically for pixel-level comparison, producing a visual-fidelity score and a structured diff report (region offsets, color deviations, font differences). The results workbench offers overlay, curtain, flicker, and heatmap comparison modes, an SVG annotation layer, and a severity-sorted issue list, so each diff can be accepted or rejected individually and trigger module-level fixes. Once visual review passes, you can import a Swagger/OpenAPI document to auto-generate Axios wrappers and an Express mock server, moving into API integration and interactive acceptance.
201
+ The **E2E** module in the sidebar drives a rough requirement or an existing design export through the full delivery pipeline: it generates a PRD and an interactive prototype for review, then moves into D2C visual implementation (Vue 3 / React) under `src/d2c-output/<task>/`. A Vite dev server starts automatically for pixel-level comparison, producing a visual-fidelity score and a structured diff report (region offsets, color deviations, font differences); the results workbench offers overlay, curtain, flicker, and heatmap modes, an SVG annotation layer, and a severity-sorted issue list. After visual review, a Swagger/OpenAPI contract is generated or imported to auto-create Axios wrappers and an Express mock server, followed by autonomous interactive acceptance and scored delivery.
213
202
 
214
203
  ### VS Code / Qoder
215
204
 
@@ -384,6 +373,7 @@ npm run build
384
373
  - [Multimodal image attachments spec](./docs/specs/2026-07-30-multimodal-image-attachments.md)
385
374
  - [D2C design-to-code spec](./docs/specs/2026-08-09-d2c-design-to-code.md)
386
375
  - [D2C review & integration spec](./docs/specs/2026-08-10-d2c-review-and-integration.md)
376
+ - [E2E requirement-to-delivery spec](./docs/specs/2026-08-12-e2e-requirement-to-delivery.md)
387
377
  - [1.2.9 runtime productivity spec](./docs/specs/2026-08-13-runtime-productivity-waves.md)
388
378
  - [VS Code next steps](./docs/specs/2026-08-01-flavor-code-vscode-next.md)
389
379
 
package/README.zh-CN.md CHANGED
@@ -35,50 +35,10 @@ Flavor Code 接入 OpenAI、Anthropic 或兼容服务,在受控工作区内使
35
35
  | 🧭 | **复杂任务可控推进** | 任务计划、子 Agent、steering、follow-up、`/loop` 和 `/goal` |
36
36
  | ⏪ | **结果可追溯、可恢复** | 完整时间线、checkpoint、rewind、trace、Diff 和失败审计 |
37
37
  | 🧠 | **本地长期上下文** | 记忆、Skill、插件和项目指南均保存在本机 |
38
- | 🎨 | **D2C 设计转代码** | 导入 Pixso 导出结果,由 Agent 生成 Vue/React 实现并自动进行像素级视觉评估(仅 Electron) |
38
+ | 🔎 | **代码图导航** | 本地 AST 代码图索引(`.flavor/astgraph/`),通过 `ast_search`/`ast_callers`/`ast_impact` 等查询精确定位符号、追踪可达性 |
39
+ | 🎨 | **E2E 需求到交付** | 从粗需求或设计稿到可交付产品:PRD、交互原型、视觉还原、接口联调、自主验收与评分交付(仅 Electron) |
39
40
  | 🛡️ | **明确的权限边界** | 分别控制读、写、Shell、网络和破坏性操作,也可使用 Docker |
40
41
 
41
- ## 1.2.10 D2C 验收与交付
42
-
43
- 1.2.10 强化 D2C 验收闭环:认证前置失败快速阻断、请求记录跨导航保留、修复提示支持补充要求,后端源码变化时自动重启。
44
-
45
- | 功能 | 使用方式 |
46
- | --- | --- |
47
- | **认证前置失败快速阻断** | 交互验收中,登录/认证类场景(如 `POST /api/v1/auth/login`)失败后,后续受保护场景不再逐个执行,直接标记为被该失败前置场景阻断,根因一目了然。 |
48
- | **跨导航请求记录** | 请求记录通过 `sessionStorage` 在页面导航之间保留(最多 500 条),点击跳转等导航之后的请求同样会被捕获,可继续参与断言。 |
49
- | **修复补充要求** | 修复失败的交互场景前,可在“补充修复要求”输入框填写额外约束,它们会与失败详情一起作为“用户补充要求”注入修复提示词。 |
50
- | **验收与交付独立阶段** | D2C 工作台将“接口联调”与“验收与交付”拆分为两个明确阶段;自动修复完成后自动切换到验收页签。 |
51
- | **后端源码指纹检测** | 启动时为 mock/server 后端源码计算指纹;源码文件变化后,运行时检测到指纹差异会自动重启后端,确保验收针对磁盘上的最新代码。 |
52
-
53
- ## 1.2.9 运行时生产力
54
-
55
- 1.2.9 新增分层项目指令、安全写入、后台任务、持久终端和原生 Web 工具。通常只需用自然语言描述目标,Agent 会选择合适的工具;需要精确控制时,也可以在提示词中明确指定工具和参数。
56
-
57
- | 功能 | 使用方式 |
58
- | --- | --- |
59
- | **分层项目指令** | 在项目根目录或子目录放置 `AGENTS.md` / `CLAUDE.md`;同目录需要本地补充时使用 `AGENTS.local.md` / `CLAUDE.local.md`。根规则启动时加载,子目录规则在 Agent 访问该目录文件时自动加载。 |
60
- | **每轮成果物汇总** | 无需配置。`Write`、`Edit` 或 `ApplyPatch` 成功后,回合结束会显示带语义色的 `CHANGESET` 收据,使用工作区相对路径列出 `CREATE` / `UPDATE` / `DELETE` 操作、各文件行数和总计。最多展示 8 个文件,超出时明确显示已展示数与总数。 |
61
- | **文件版本保护** | 无需配置。如果 IDE、格式化器或其他进程在 Agent 读取后修改了文件,后续写入会报 `Stale file`;让 Agent 重新读取后再修改即可。 |
62
- | **标准工具展示协议** | 工具作者可声明 `outputSchema`、`renderForModel`、`presentCall` 和 `presentResult`,让同一结果在模型上下文、CLI 与桌面端分别使用合适的形式。CLI 会把文件 Diff、Web 证据、Job 运行收据、前台 `COMMAND` 和持久 `TERMINAL` 与最终回答明确分开。 |
63
- | **后台 Shell / Job** | 提示“在后台启动开发服务器”,Agent 会调用 `Shell` 并设置 `background: true`。使用 `JobList` 查看任务、`JobRead` 增量读取输出、`JobWait` 等待结束、`JobKill` 停止任务。CLI 使用带状态色边界的 `JOB` 收据区分任务元数据、日志与最终回答;日志最多显示最近 12 行,列表最多显示 8 项。Windows 优先使用 UTF-8,遇到 GBK/GB18030 系统诊断时自动回退。 |
64
- | **前台命令结果** | 前台 `Shell` 显示为带状态色的 `COMMAND` 收据,分别展示命令、stdout、stderr 和退出状态。长输出保留开头 8 行与结尾 8 行,并明确折叠中间部分;持久 PTY 使用独立的 `TERMINAL` 标签。 |
65
- | **桌面后台状态** | Electron 会在会话标题栏自动显示运行中的 Job 数量,并在任务启动、输出、退出或取消时实时更新。 |
66
- | **持久 PTY** | 提示“打开一个持久终端并继续交互”。Agent 使用 `TerminalOpen` 创建终端、`TerminalWrite` 输入、`TerminalRead` 增量读取输出,并用 `TerminalClose` 关闭。 |
67
- | **D2C/E2E 统一进程生命周期** | 使用方式不变。预览和后端服务仍从 E2E/D2C 工作台启动或停止,底层统一处理输出限制、进程树终止和幂等清理。 |
68
- | **原生 WebSearch** | 提示“搜索 Web 上的……”,或明确要求使用 `WebSearch`。默认使用无需密钥的 DuckDuckGo Lite;连接失败、HTTP 拒绝或没有可解析结果时自动降级到 Bing。单次最多返回 20 条;CLI 将前 5 条放入带边界的 `WEB SEARCH` 证据块,按搜索排名显示标题和紧凑来源。 |
69
- | **原生 WebFetch** | 提示“读取这个网页:`https://...`”,或明确要求使用 `WebFetch`。支持 HTTP(S)、重定向、HTML 转文本、超时和响应大小限制,并兼容 Clash/TUN Fake-IP DNS。直接访问 Fake-IP、内网或云元数据地址仍会被拦截;网络操作继续遵守 Flavor 权限审批。 |
70
-
71
- 常见的精确用法:
72
-
73
- ```text
74
- 使用 Shell 后台模式启动 npm run dev,然后通过 JobRead 检查启动日志。
75
- 打开持久终端,在其中运行 Python REPL,连续执行两段代码后关闭终端。
76
- 使用 WebSearch 搜索 TypeScript 7 官方迁移说明,再用 WebFetch 读取最相关的官方页面。
77
- 这个目录有独立约定,请先遵守 src/payments/AGENTS.md 再修改代码。
78
- ```
79
-
80
- 原生工具的参数、状态机、安全边界和扩展接口详见[技术方案报告第 38 节](./技术方案报告.md#38-129-运行时生产力与原生-web-能力);验收规格见[运行时生产力规范](./docs/specs/2026-08-13-runtime-productivity-waves.md)。
81
-
82
42
  ## 快速开始
83
43
 
84
44
  > [!IMPORTANT]
@@ -194,10 +154,39 @@ OAuth PKCE 的运行时行为与配置约定见 [PKCE 规范](./docs/specs/pkce-
194
154
  | `/mcp` | 查看和管理 MCP 服务 |
195
155
  | `/loop <goal>` | 运行带验证的自治循环 |
196
156
  | `/goal <objective>` | 运行规划、执行、对抗审查流程 |
157
+ | `/pals`、`/chat`、`/co-work` | 发现并协作其他本机 CLI 实例 |
197
158
  | `/audit` | 查看工具失败审计 |
198
159
 
199
160
  运行中可以提交 steering 或排队 follow-up;当前模型响应结束后,任务会在安全边界处接收新指令。
200
161
 
162
+ #### CLI Pals 与跨项目协作
163
+
164
+ 同一 Windows 或 macOS 用户下的交互式 CLI 可以通过纯本地 IPC 协作(Windows named pipe 或 Unix socket,不回退到 TCP)。先给每个窗口一个容易识别的别名:
165
+
166
+ ```bash
167
+ # 终端 A,位于项目 A
168
+ flavor --pal-name A
169
+
170
+ # 终端 B,位于项目 B
171
+ flavor --pal-name B
172
+ ```
173
+
174
+ 常用命令:
175
+
176
+ ```text
177
+ /pals # 查看别名和每进程 UUID
178
+ /pals --verbose # 额外显示项目路径和时间
179
+ /pals rename api # 重命名当前活动实例
180
+ /chat B 更新 API 和测试 # 投递给 B,并安全启动其 Agent
181
+ /co-work B 先升级 B,再兼容 A # 先协商同一计划,再并行开工
182
+ /co-work status [co-work-uuid]
183
+ /co-work cancel <co-work-uuid> [reason]
184
+ ```
185
+
186
+ `/chat` 支持双向任务通信。B 空闲时,带来源标识的消息会启动正常模型回合;B 正在运行时,消息会成为 steering;已有本地提交待运行时则成为 follow-up。远端文本会转换成安全的非斜杠 prompt,因此 `/exit` 一类文本不会被当成本地命令分派。B 可以用 `/chat A ...` 回复。
187
+
188
+ `/co-work` 会先让双方进入规划,等待双方接受同一个哈希计划并声明 READY;较早的 READY 意图会被保留,只有 broker 恰好一次的 START 事件才会放行并行执行。每个 Agent 只在自己的项目内工作,只接收分配给自己的任务,并提交有界的完成证据。broker 指定的集成负责人会检查所有断言,再通过 `CoWorkIntegrate` 广播 END 或 FAIL。通信使用经过认证、有大小上限的本机 IPC,不开放 TCP 监听;peer 输入不能代替本机工具审批,也不能访问另一工作区。UUID/别名路由和协议已能支持第三个活动实例;持久化成果物交换、broker 重启日志与恢复、大规模多方协调属于后续强化。详见 [CLI Pals 规范](./docs/specs/2026-08-14-cli-pals-cowork.md)。
189
+
201
190
  ### Electron 桌面端
202
191
 
203
192
  ```bash
@@ -209,6 +198,8 @@ npm run desktop:dist # Windows NSIS 安装包
209
198
 
210
199
  桌面端提供项目和会话切换、流式 Markdown、工具与 Diff 展示、权限确认、任务状态,以及 Skill、MCP、记忆和模型管理。
211
200
 
201
+ 侧栏的 **E2E** 模块覆盖从粗需求到可验收成果物的完整交付链路:从粗需求生成 PRD 与可交互原型(支持审阅与退回),确认后进入 D2C 视觉还原(Vue 3 / React),自动启动 Vite dev server 进行像素级对比,输出视觉还原度评分与结构化差异报告,并提供叠加、帘幕、闪烁与热力图等对比模式、SVG 标注层和按严重度排序的问题列表;视觉审阅通过后,自动生成或导入 Swagger/OpenAPI 契约以创建 Axios 封装与 Express mock 服务,随后进行自主交互验收,最终完成评分与成果物交付。
202
+
212
203
  ### VS Code / Qoder
213
204
 
214
205
  ```bash
@@ -381,6 +372,7 @@ npm run build
381
372
  - [控制面、沙箱与 VS Code 规范](./docs/specs/2026-07-29-control-plane-sandbox-vscode.md)
382
373
  - [多模态图片规范](./docs/specs/2026-07-30-multimodal-image-attachments.md)
383
374
  - [VS Code 后续规划](./docs/specs/2026-08-01-flavor-code-vscode-next.md)
375
+ - [E2E 需求到交付规范](./docs/specs/2026-08-12-e2e-requirement-to-delivery.md)
384
376
 
385
377
  ## 安全提示
386
378
 
@@ -15,7 +15,7 @@ import {
15
15
  packageVersion,
16
16
  redactErrorText,
17
17
  transcriptReducer
18
- } from "./chunk-WGYNTR4Q.js";
18
+ } from "./chunk-Y5MZDG7I.js";
19
19
  import "./chunk-XFCJXRJ2.js";
20
20
  import {
21
21
  Box_default,
@@ -1249,8 +1249,10 @@ function buildSlashCandidates(commands, plugins, skills) {
1249
1249
  for (const command of commands) {
1250
1250
  candidates.set(command.name, { name: command.name, kind: "command", description: command.description });
1251
1251
  }
1252
- for (const name of plugins) {
1253
- if (!candidates.has(name)) candidates.set(name, { name, kind: "plugin" });
1252
+ for (const plugin of plugins) {
1253
+ if (!candidates.has(plugin.name)) {
1254
+ candidates.set(plugin.name, plugin.description === void 0 ? { name: plugin.name, kind: "plugin" } : { name: plugin.name, kind: "plugin", description: plugin.description });
1255
+ }
1254
1256
  }
1255
1257
  for (const skill of skills) {
1256
1258
  if (!candidates.has(skill.name)) {
@@ -1825,7 +1827,20 @@ function completionKeyAction(key, menuOpen) {
1825
1827
  function slashKeyAction(key, completion) {
1826
1828
  return completionKeyAction(key, completion !== null);
1827
1829
  }
1828
- function App({ workspace, home, resumeSession }) {
1830
+ function appRuntimeOptions(props, output, onApprovalChange) {
1831
+ return {
1832
+ workspace: props.workspace,
1833
+ ...props.home === void 0 ? {} : { home: props.home },
1834
+ ...props.resumeSession === void 0 ? {} : { resumeSession: props.resumeSession },
1835
+ collaboration: {
1836
+ instanceId: props.instanceId,
1837
+ ...props.palAlias === void 0 ? {} : { alias: props.palAlias }
1838
+ },
1839
+ output,
1840
+ onApprovalChange
1841
+ };
1842
+ }
1843
+ function App({ workspace, home, resumeSession, instanceId, palAlias }) {
1829
1844
  const { exit } = use_app_default();
1830
1845
  const { stdout } = useStdout();
1831
1846
  const [runtime, setRuntime] = useState2();
@@ -1912,13 +1927,13 @@ function App({ workspace, home, resumeSession }) {
1912
1927
  flushText();
1913
1928
  dispatch({ type: "session", event });
1914
1929
  };
1915
- void createProductionRuntime({
1930
+ void createProductionRuntime(appRuntimeOptions({
1916
1931
  workspace,
1932
+ instanceId,
1917
1933
  ...home === void 0 ? {} : { home },
1918
1934
  ...resumeSession === void 0 ? {} : { resumeSession },
1919
- output: receive,
1920
- onApprovalChange: () => setRevision((value) => value + 1)
1921
- }).then(async (created) => {
1935
+ ...palAlias === void 0 ? {} : { palAlias }
1936
+ }, receive, () => setRevision((value) => value + 1))).then(async (created) => {
1922
1937
  if (disposed) {
1923
1938
  await created.dispose();
1924
1939
  return;
@@ -1945,7 +1960,7 @@ function App({ workspace, home, resumeSession }) {
1945
1960
  void closeAndDisposeRuntime(runtimeRef.current, (error) => process.stderr.write(`flavor cleanup: ${error}
1946
1961
  `));
1947
1962
  };
1948
- }, [workspace, home, resumeSession]);
1963
+ }, [workspace, home, resumeSession, instanceId, palAlias]);
1949
1964
  useEffect3(() => installSigintHandler(process, interrupt), [interrupt]);
1950
1965
  useEffect3(() => {
1951
1966
  if (runtime?.services.ideContext === void 0) {
@@ -2719,6 +2734,23 @@ function TurnView({
2719
2734
  turn.blocks.map((block, index) => block.kind === "status" ? /* @__PURE__ */ jsx5(StatusBlockView, { block, interactive, workspaceName }, block.id) : /* @__PURE__ */ jsx5(Box_default, { children: /* @__PURE__ */ jsx5(AssistantText, { text: block.text }) }, `${turn.id}-text-${index}`))
2720
2735
  ] });
2721
2736
  }
2737
+ if (turn.source?.kind === "pal") {
2738
+ const context = turn.source.context === void 0 ? "" : ` \xB7 ${turn.source.context}`;
2739
+ return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
2740
+ /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [
2741
+ /* @__PURE__ */ jsxs4(Text, { color: "cyanBright", bold: true, children: [
2742
+ "PAL \xB7 ",
2743
+ turn.source.alias,
2744
+ " (",
2745
+ turn.source.instanceId.slice(0, 8),
2746
+ ")",
2747
+ context
2748
+ ] }),
2749
+ /* @__PURE__ */ jsx5(Text, { color: "ansi:whiteBright", children: turn.prompt })
2750
+ ] }),
2751
+ /* @__PURE__ */ jsx5(Box_default, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: turn.blocks.map((block, index) => block.kind === "status" ? /* @__PURE__ */ jsx5(StatusBlockView, { block, interactive, workspaceName }, block.id) : /* @__PURE__ */ jsx5(Box_default, { marginBottom: 1, children: /* @__PURE__ */ jsx5(AssistantText, { text: block.text }) }, `${turn.id}-text-${index}`)) })
2752
+ ] });
2753
+ }
2722
2754
  return /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "column", marginBottom: 1, children: [
2723
2755
  /* @__PURE__ */ jsxs4(Box_default, { flexDirection: "row", backgroundColor: "#3a3a3a", paddingX: 1, paddingY: 0, children: [
2724
2756
  /* @__PURE__ */ jsx5(Text, { color: "ansi:whiteBright", bold: true, backgroundColor: "#3a3a3a", children: "\u276F" }),
@@ -3535,6 +3567,7 @@ export {
3535
3567
  PromptLine,
3536
3568
  SinglePendingPrompt,
3537
3569
  TerminalLayout,
3570
+ appRuntimeOptions,
3538
3571
  classifyTerminalInput,
3539
3572
  closeAndDisposeRuntime,
3540
3573
  completionKeyAction,
@@ -0,0 +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
+ }