attention-manifest-protocol 0.5.0 → 0.6.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CHANGELOG.zh-CN.md +21 -0
  3. package/README.md +3 -1
  4. package/README.zh-CN.md +3 -1
  5. package/lib/bayes/calibrationRegistry.d.ts +12 -0
  6. package/lib/bayes/calibrationRegistry.js +22 -0
  7. package/lib/database/adapter/filesystemStore.d.ts +7 -0
  8. package/lib/database/adapter/filesystemStore.js +9 -0
  9. package/lib/database/entity/entity.d.ts +15 -0
  10. package/lib/database/entity/entity.js +27 -0
  11. package/lib/database/repository/fileOutlineRepository.d.ts +12 -0
  12. package/lib/database/repository/fileOutlineRepository.js +14 -0
  13. package/lib/file/index.d.ts +2 -0
  14. package/lib/file/index.js +1 -0
  15. package/lib/file/runtime.d.ts +2 -0
  16. package/lib/file/runtime.js +2 -0
  17. package/lib/file/scopedStore.js +1 -0
  18. package/lib/file/service.d.ts +16 -0
  19. package/lib/file/service.js +45 -1
  20. package/lib/file/types.d.ts +6 -0
  21. package/lib/knowledge/registry.d.ts +6 -0
  22. package/lib/knowledge/registry.js +13 -0
  23. package/lib/module/tool/tool.d.ts +6 -0
  24. package/lib/module/tool/tool.js +18 -0
  25. package/lib/outline/index.d.ts +1 -0
  26. package/lib/outline/index.js +1 -0
  27. package/lib/outline/outline.d.ts +19 -0
  28. package/lib/outline/outline.js +42 -0
  29. package/lib/outline/outlineKeywords.d.ts +10 -0
  30. package/lib/outline/outlineKeywords.js +35 -0
  31. package/lib/outline/rangeBridge.d.ts +151 -0
  32. package/lib/outline/rangeBridge.js +122 -0
  33. package/lib/outline/sqlOutline.d.ts +14 -0
  34. package/lib/outline/sqlOutline.js +38 -0
  35. package/lib/protocol/registry.d.ts +6 -0
  36. package/lib/protocol/registry.js +13 -0
  37. package/lib/registry/memory.d.ts +22 -0
  38. package/lib/registry/memory.js +38 -0
  39. package/lib/runtime/behavior.d.ts +6 -0
  40. package/lib/runtime/behavior.js +24 -0
  41. package/lib/skill/registry.d.ts +6 -0
  42. package/lib/skill/registry.js +13 -0
  43. package/lib/syntax/syntaxRegistry.d.ts +21 -0
  44. package/lib/syntax/syntaxRegistry.js +87 -0
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
6
6
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-11-03
9
+
10
+ ### Added
11
+
12
+ - `outlineAddress(range, charset, bounds)` and the related line/byte request helpers convert an outline node's `range` into the `startLine`/`endLine` and `offsetBytes`/`endBytes` fields the workspace read and write tools accept, removing the UTF-8 BOM prefix and failing closed on reversed, out-of-bounds or non-representable values.
13
+ - Every mutable registry is read-write: `SkillRegistry`, `KnowledgeRegistry`, `ProtocolRegistry`, `ToolRegistry`, `OutlineRegistry`, `OutlineKeywordRegistry`, `SyntaxRegistry`, `SqlDialectRegistry`, `CalibrationRegistry`, `BehaviorRegistry` and `EntityRegistry` expose `update` and `remove` (plus `clear` for list-shaped containers) on top of `register`.
14
+
15
+ ### Changed
16
+
17
+ - Writing a file now rebuilds that file's outline and drops its previous cache generation in the same call, so a later outline read never serves the pre-write tree and storage keeps one cache row per file instead of one per edit.
18
+ - `SyntaxRegistry.updateLanguage` and `removeLanguage` release the language's extensions so a new language can claim them.
19
+
20
+ ### Fixed
21
+
22
+ - The session-scoped filesystem store did not forward `removeOutline`, silently dropping cache invalidation; it now scopes and forwards it.
23
+ - The calibration registry now accepts a host-supplied model for a scope it has never fitted, so persisted calibration can be restored.
24
+
25
+ ### Validation
26
+
27
+ - Joint regression: 168/168 across registry, discovery, outline, runtime and database suites; source and tools typechecks passed.
28
+
8
29
  ## [0.5.0] - 2026-11-03
9
30
 
10
31
  ### Added
@@ -5,6 +5,27 @@
5
5
  格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循
6
6
  [语义化版本](https://semver.org/lang/zh-CN/)。
7
7
 
8
+ ## [0.6.0] - 2026-11-03
9
+
10
+ ### 新增
11
+
12
+ - `outlineAddress(range, charset, bounds)` 及配套的行/字节请求辅助函数,把大纲节点的 `range` 转成 workspace 读写工具接受的 `startLine`/`endLine` 与 `offsetBytes`/`endBytes`,扣除 UTF-8 BOM 前缀;倒序、越界或无法精确表示时报错而不静默裁剪。
13
+ - 所有可变注册中心均可读写:`SkillRegistry`、`KnowledgeRegistry`、`ProtocolRegistry`、`ToolRegistry`、`OutlineRegistry`、`OutlineKeywordRegistry`、`SyntaxRegistry`、`SqlDialectRegistry`、`CalibrationRegistry`、`BehaviorRegistry` 与 `EntityRegistry` 在 `register` 之外提供 `update` 与 `remove`(列表型容器另有 `clear`)。
14
+
15
+ ### 变更
16
+
17
+ - 写入文件时在同一次调用内重建该文件大纲并删除旧缓存代次,之后读取大纲不会返回写入前的树,存储中也保持每个文件一条缓存而非每次编辑一条。
18
+ - `SyntaxRegistry.updateLanguage` 与 `removeLanguage` 会释放语言的扩展名,允许新语言重新占用。
19
+
20
+ ### 修复
21
+
22
+ - 会话限定的文件存储未转发 `removeOutline`,导致缓存失效被静默丢弃;现已按会话键转发。
23
+ - 校准注册中心现在接受宿主为从未拟合过的 scope 提供模型,便于恢复已持久化的校准。
24
+
25
+ ### 验证
26
+
27
+ - 联合回归 168/168 通过(注册中心、发现、大纲、运行时与数据库套件);源代码与工具两套类型检查通过。
28
+
8
29
  ## [0.5.0] - 2026-11-03
9
30
 
10
31
  ### 新增
package/README.md CHANGED
@@ -59,6 +59,8 @@ The catalog follows `entity → cache → service → repository → SQL → SQL
59
59
 
60
60
  These registries are domain-owned catalogs, not a mandate to duplicate every concept as a generic registry. Existing runtime state and policies for ability, cognition, progress and behavior should stay in their domain models; add a read-only catalog/discovery projection only when enumeration is needed. Agents should not write directly to active registries by default. A future contribution flow should create a sourced, versioned proposal and require host authorization, review and audit before activation.
61
61
 
62
+ Every mutable registry is read-write for the host that owns it: `SkillRegistry`, `KnowledgeRegistry`, `ProtocolRegistry`, `ToolRegistry`, `OutlineRegistry`, `OutlineKeywordRegistry`, `SyntaxRegistry` (languages, token kinds and node kinds), `SqlDialectRegistry`, `CalibrationRegistry`, `BehaviorRegistry` and `EntityRegistry` all expose `update`/`remove` (and `clear` where the container is a list), on top of `register` and their read accessors. An update keeps the entry's registration slot and re-runs the same validation as register, so a host can change or drop an entry in place instead of building a second registry that disagrees with the first; `SyntaxRegistry.updateLanguage`/`removeLanguage` also release and re-use extensions. The host is still the only caller allowed to write these catalogs.
63
+
62
64
  The `environment` module describes runtimes, applications, languages and project types as `HostCatalog` modules. `category` is `runtime`, `application`, `language` or `projectType`; `availability` is `unknown`, `available` or `unavailable`, independent of the component lifecycle `status`. Optional `platforms`, `extensions`, `relatedIds`, `skillIds`, `knowledgeIds`, `toolIds` and `protocolIds` link metadata without granting execution access. Hosts explicitly confirm availability; catalog registration does not install software or probe the machine.
63
65
 
64
66
  ```ts
@@ -185,7 +187,7 @@ The `amp_protocol_workspace` v1 response contains `protocol`, `version`, `revisi
185
187
  | `amp_tool_filesystem_dependency` | Cycles, topological order and bounded closures |
186
188
  | `amp_tool_filesystem_ast` | Lossless syntax tree: every non-whitespace token, declaration/markup hierarchy, UTF-8/line ranges; read-only, never executes |
187
189
 
188
- The global variant is `amp_tool_globalfs_*`, a naming adapter in [src/global/filesystem.ts](src/global/filesystem.ts) that grants no new root or permission. Outline nodes carry `isFile`/`isDirectory`/`file?`/`directory?`/`path` (for example `add.py#add`) and `range` (UTF-8 bytes plus 1-based lines as bigint; decimal strings on the wire). Strategies cover code, Vue, project manifests, Markdown/TeX/plain text, CSS/SCSS/Sass, HTML, XML/OPML and Office; Office parsing covers DOCX/XLSX/PPTX and treats DOC/XLS/PPT/PUB/ACCDB as best-effort CFB.
190
+ The global variant is `amp_tool_globalfs_*`, a naming adapter in [src/global/filesystem.ts](src/global/filesystem.ts) that grants no new root or permission. Outline nodes carry `isFile`/`isDirectory`/`file?`/`directory?`/`path` (for example `add.py#add`) and `range` (UTF-8 bytes plus 1-based lines as bigint; decimal strings on the wire). `outlineAddress(range, charset, bounds)` in [src/outline/rangeBridge.ts](src/outline/rangeBridge.ts) converts that range into the `startLine`/`endLine` and `offsetBytes`/`endBytes` fields the workspace `read`, `write` and `diff_write` tools accept: it removes the UTF-8 BOM prefix, keeps bytes half-open and lines inclusive, and fails closed on reversed, out-of-bounds or non-representable values instead of clamping. A successful file write rebuilds that file's outline and drops its previous cache generation in the same call, so a later outline read never serves the pre-write tree. Strategies cover code, Vue, project manifests, Markdown/TeX/plain text, CSS/SCSS/Sass, HTML, XML/OPML and Office; Office parsing covers DOCX/XLSX/PPTX and treats DOC/XLS/PPT/PUB/ACCDB as best-effort CFB.
189
191
 
190
192
  ### Hooks
191
193
 
package/README.zh-CN.md CHANGED
@@ -59,6 +59,8 @@ Context 使用结构树和类型化关系图。项目依赖、导入、调用与
59
59
 
60
60
  这些 registry 是各自领域的宿主目录,不要求每个概念都复制成同构 registry。Ability、cognition、progress 等若已有运行时状态、策略或事件模型,应由现有领域模块维护;需要统一枚举时增加只读 catalog/discovery 投影即可。Agent 默认只读正式目录;可选的登记工作流应先创建带来源、版本和提案状态的候选项,再经宿主权限校验、人工审核与审计后发布,不授予模型直接改写已生效条目的能力。
61
61
 
62
+ 每个可变的注册中心对宿主都是可读写的:`SkillRegistry`、`KnowledgeRegistry`、`ProtocolRegistry`、`ToolRegistry`、`OutlineRegistry`、`OutlineKeywordRegistry`、`SyntaxRegistry`(语言、token 类别与节点类别)、`SqlDialectRegistry`、`CalibrationRegistry`、`BehaviorRegistry` 与 `EntityRegistry` 都在 `register` 和读取接口之外提供 `update`/`remove`(列表型容器另有 `clear`)。更新保留原登记位置并执行与注册相同的校验,宿主可以原地修改或删除条目,而不必再建一份与前者不一致的注册中心;`SyntaxRegistry.updateLanguage`/`removeLanguage` 会释放并允许复用扩展名。写这些目录的仍只能是宿主。
63
+
62
64
  `environment` 模块将运行时、应用程序、编程语言与项目类型作为 `HostCatalog` 的 module 条目维护。`category` 为 `runtime`、`application`、`language` 或 `projectType`;`availability` 为 `unknown`、`available` 或 `unavailable`,与组件生命周期 `status` 分开。可选的 `platforms`、`extensions`、`relatedIds`、`skillIds`、`knowledgeIds`、`toolIds`、`protocolIds` 描述平台、格式与关联,不授予执行权限。可用性由宿主确认,登记目录不会安装软件或探测本机。
63
65
 
64
66
  ```ts
@@ -188,7 +190,7 @@ import { ToolIds, parseId } from "attention-manifest-protocol/naming";
188
190
  | `amp_tool_filesystem_ast` | 无损语法树:每个非空白 token、声明/块/标签层级、UTF-8 字节与行范围;只读,不执行 |
189
191
 
190
192
  全局变体为 `amp_tool_globalfs_*`,由 [src/global/filesystem.ts](src/global/filesystem.ts) 做命名适配;它不扩大任何根或权限。
191
- 大纲节点带 `isFile`/`isDirectory`/`file?`/`directory?`/`path`(如 `add.py#add`)与 `range`(UTF-8 字节 + 1-based 行,bigint;JSON 中为十进制字符串)。策略覆盖代码、Vue、项目清单、Markdown/TeX/纯文本、CSS/SCSS/Sass、HTML、XML/OPML 与 Office;Office 文件由 `office.ts` 解析 DOCX/XLSX/PPTX,DOC/XLS/PPT/PUB/ACCDB 为 CFB best-effort。
193
+ 大纲节点带 `isFile`/`isDirectory`/`file?`/`directory?`/`path`(如 `add.py#add`)与 `range`(UTF-8 字节 + 1-based 行,bigint;JSON 中为十进制字符串)。`outlineAddress(range, charset, bounds)`([src/outline/rangeBridge.ts](src/outline/rangeBridge.ts))把该 range 转成 workspace `read`、`write`、`diff_write` 接受的 `startLine`/`endLine` 与 `offsetBytes`/`endBytes`:扣除 UTF-8 BOM 前缀,字节半开、行闭区间;倒序、越界或无法精确表示时直接报错而不做静默裁剪。文件写入成功后会在同一次调用内重建该文件大纲并删除旧的缓存代次,之后读取大纲不会返回写入前的树。策略覆盖代码、Vue、项目清单、Markdown/TeX/纯文本、CSS/SCSS/Sass、HTML、XML/OPML 与 Office;Office 文件由 `office.ts` 解析 DOCX/XLSX/PPTX,DOC/XLS/PPT/PUB/ACCDB 为 CFB best-effort。
192
194
 
193
195
  ### Hooks
194
196
 
@@ -35,5 +35,17 @@ export declare class CalibrationRegistry {
35
35
  register(scope: string, points: readonly CalibrationPoint[], options?: RegisterOptions): RegisterResult;
36
36
  calibrate(scope: string, predicted: number): number;
37
37
  model(scope: string): CalibrationModel | undefined;
38
+ /**
39
+ * Set the model of one scope.
40
+ *
41
+ * Unlike `register`, which fits a model from samples, this stores a complete
42
+ * model as-is: it replaces the scope's current model or creates it. Hosts
43
+ * that persist or hand-author a calibration use this to restore it.
44
+ */
45
+ update(model: CalibrationModel): CalibrationModel;
46
+ /** Remove one scope's model, returning it to raw probabilities. */
47
+ remove(scope: string): boolean;
48
+ /** Remove every calibrated scope. */
49
+ clear(): number;
38
50
  scopes(): string[];
39
51
  }
@@ -55,6 +55,28 @@ export class CalibrationRegistry {
55
55
  model(scope) {
56
56
  return this.models.get(scope);
57
57
  }
58
+ /**
59
+ * Set the model of one scope.
60
+ *
61
+ * Unlike `register`, which fits a model from samples, this stores a complete
62
+ * model as-is: it replaces the scope's current model or creates it. Hosts
63
+ * that persist or hand-author a calibration use this to restore it.
64
+ */
65
+ update(model) {
66
+ const stored = { ...model };
67
+ this.models.set(stored.scope, stored);
68
+ return { ...stored };
69
+ }
70
+ /** Remove one scope's model, returning it to raw probabilities. */
71
+ remove(scope) {
72
+ return this.models.delete(scope);
73
+ }
74
+ /** Remove every calibrated scope. */
75
+ clear() {
76
+ const count = this.models.size;
77
+ this.models.clear();
78
+ return count;
79
+ }
58
80
  scopes() {
59
81
  return [...this.models.keys()];
60
82
  }
@@ -88,6 +88,13 @@ export declare class SqliteFilesystemStore implements FilesystemStore {
88
88
  * @param outline - Parsed outline.
89
89
  */
90
90
  saveOutline(rootId: string, outline: OutlineResult): void;
91
+ /**
92
+ * Drop every cached outline of one file, whatever its fingerprint.
93
+ *
94
+ * @param rootId - Session root id.
95
+ * @param path - Root-relative POSIX path.
96
+ */
97
+ removeOutline(rootId: string, path: string): void;
91
98
  /**
92
99
  * Load a cached import report by exact fingerprint.
93
100
  *
@@ -236,6 +236,15 @@ export class SqliteFilesystemStore {
236
236
  saveOutline(rootId, outline) {
237
237
  this.outlines.save(rootId, outline, now());
238
238
  }
239
+ /**
240
+ * Drop every cached outline of one file, whatever its fingerprint.
241
+ *
242
+ * @param rootId - Session root id.
243
+ * @param path - Root-relative POSIX path.
244
+ */
245
+ removeOutline(rootId, path) {
246
+ this.outlines.remove(rootId, path);
247
+ }
239
248
  /**
240
249
  * Load a cached import report by exact fingerprint.
241
250
  *
@@ -688,6 +688,21 @@ export declare class EntityRegistry {
688
688
  * @throws EntityMetadataError for invalid or duplicate metadata.
689
689
  */
690
690
  register(entity: Entity): this;
691
+ /**
692
+ * Replace one registered entity, keeping its registration position.
693
+ *
694
+ * @param entity - Validated replacement metadata with the same name.
695
+ * @returns This registry, for chaining.
696
+ * @throws EntityMetadataError for invalid metadata or an unknown name.
697
+ */
698
+ update(entity: Entity): this;
699
+ /**
700
+ * Remove one entity.
701
+ *
702
+ * @param name - Logical entity name.
703
+ * @returns `true` when it was registered.
704
+ */
705
+ remove(name: EntityName): boolean;
691
706
  /**
692
707
  * Look up an entity by logical name.
693
708
  *
@@ -225,6 +225,33 @@ export class EntityRegistry {
225
225
  this.entities.set(entity.name, entity);
226
226
  return this;
227
227
  }
228
+ /**
229
+ * Replace one registered entity, keeping its registration position.
230
+ *
231
+ * @param entity - Validated replacement metadata with the same name.
232
+ * @returns This registry, for chaining.
233
+ * @throws EntityMetadataError for invalid metadata or an unknown name.
234
+ */
235
+ update(entity) {
236
+ const problems = validateEntity(entity);
237
+ if (problems.length > 0) {
238
+ throw new EntityMetadataError(entity.name, problems);
239
+ }
240
+ if (!this.entities.has(entity.name)) {
241
+ throw new EntityMetadataError(entity.name, ["no entity with this name is registered"]);
242
+ }
243
+ this.entities.set(entity.name, entity);
244
+ return this;
245
+ }
246
+ /**
247
+ * Remove one entity.
248
+ *
249
+ * @param name - Logical entity name.
250
+ * @returns `true` when it was registered.
251
+ */
252
+ remove(name) {
253
+ return this.entities.delete(name);
254
+ }
228
255
  /**
229
256
  * Look up an entity by logical name.
230
257
  *
@@ -28,6 +28,18 @@ export declare class FileOutlineRepository extends BaseRepository<FileOutlineRow
28
28
  * @param at - ISO timestamp for the write.
29
29
  */
30
30
  save(rootId: string, outline: OutlineResult, at: string): void;
31
+ /**
32
+ * Drop every cached outline of one file regardless of fingerprint.
33
+ *
34
+ * The cache is keyed by content fingerprint, so a write leaves the previous
35
+ * generation behind forever. A writer calls this after it replaces the file
36
+ * and saves the new outline, keeping one row per file instead of one row per
37
+ * edit.
38
+ *
39
+ * @param rootId - Session root id.
40
+ * @param path - Root-relative POSIX path.
41
+ */
42
+ remove(rootId: string, path: string): void;
31
43
  /**
32
44
  * Load the outline of one exact fingerprint.
33
45
  *
@@ -73,6 +73,20 @@ export class FileOutlineRepository extends BaseRepository {
73
73
  created_at: at,
74
74
  });
75
75
  }
76
+ /**
77
+ * Drop every cached outline of one file regardless of fingerprint.
78
+ *
79
+ * The cache is keyed by content fingerprint, so a write leaves the previous
80
+ * generation behind forever. A writer calls this after it replaces the file
81
+ * and saves the new outline, keeping one row per file instead of one row per
82
+ * edit.
83
+ *
84
+ * @param rootId - Session root id.
85
+ * @param path - Root-relative POSIX path.
86
+ */
87
+ remove(rootId, path) {
88
+ this.deleteWhere(conditions(FileOutlineTable, ["root_id", "path"]), rootId, path);
89
+ }
76
90
  /**
77
91
  * Load the outline of one exact fingerprint.
78
92
  *
@@ -32,6 +32,8 @@ export { DefaultSyntaxRegistry, SyntaxRegistry, buildSyntaxTreeFromText, createS
32
32
  export type { SyntaxLanguage, SyntaxLanguageDefinition, SyntaxNode, SyntaxNodeKind, SyntaxNodeKindDefinition, SyntaxToken, SyntaxTokenKind, SyntaxTokenKindDefinition, SyntaxTree, SyntaxTreeOptions, WireSyntaxNode, WireSyntaxToken, WireSyntaxTree, } from "../syntax/syntaxTree.js";
33
33
  export { OutlineIndex, buildOutlineIndex, filterOutline, hasLabelPrefix, wireOutline, wireOutlineNode, wireOutlineRange, outlineFromWire } from "../outline/outlineIndex.js";
34
34
  export type { OutlinePayload } from "../outline/outlineIndex.js";
35
+ export { OutlineRangeError, outlineAddress, outlineBomOffset, outlineByteRange, outlineLineRange, readByteRequest, readLineRequest, writeByteRequest, writeLineRequest, } from "../outline/rangeBridge.js";
36
+ export type { OutlineAddress, OutlineByteRange, OutlineLineRange, OutlineRangeErrorCode } from "../outline/rangeBridge.js";
35
37
  export { extractImports, importedBy, isParseableSource, readSourceFile, resolveImportSpecifier, tokenizeSource } from "./importGraph.js";
36
38
  export type { ResolvedSpecifier, SourceFileSnapshot, SourceToken } from "./importGraph.js";
37
39
  export { extractExports } from "./exportGraph.js";
package/lib/file/index.js CHANGED
@@ -22,6 +22,7 @@ export { DefaultOutlineKeywords, OutlineKeywordRegistry, createOutlineKeywordReg
22
22
  export { buildSyntaxTree } from "./syntaxTree.js";
23
23
  export { DefaultSyntaxRegistry, SyntaxRegistry, buildSyntaxTreeFromText, createSyntaxRegistry, disposeSyntaxNode, syntaxLanguageOf, wireSyntaxTree, } from "../syntax/syntaxTree.js";
24
24
  export { OutlineIndex, buildOutlineIndex, filterOutline, hasLabelPrefix, wireOutline, wireOutlineNode, wireOutlineRange, outlineFromWire } from "../outline/outlineIndex.js";
25
+ export { OutlineRangeError, outlineAddress, outlineBomOffset, outlineByteRange, outlineLineRange, readByteRequest, readLineRequest, writeByteRequest, writeLineRequest, } from "../outline/rangeBridge.js";
25
26
  export { extractImports, importedBy, isParseableSource, readSourceFile, resolveImportSpecifier, tokenizeSource } from "./importGraph.js";
26
27
  export { extractExports } from "./exportGraph.js";
27
28
  export { DependencyGraph, buildDependencyReport, graphFromImports } from "./dependency.js";
@@ -50,6 +50,8 @@ export declare class SessionRuntime {
50
50
  persistenceKey(rootId: string): string;
51
51
  loadOutline(rootId: string, path: string, fingerprint: string): import("./types.js").OutlineResult | undefined;
52
52
  saveOutline(rootId: string, outline: import("../outline/types.js").OutlineResult): void;
53
+ /** Drop every cached generation of one file's outline after it changed. */
54
+ removeOutline(rootId: string, path: string): void;
53
55
  /**
54
56
  * Resolve a caller path and return the tree that owns it.
55
57
  */
@@ -25,6 +25,8 @@ export class SessionRuntime {
25
25
  persistenceKey(rootId) { return JSON.stringify([this.descriptor.workspaceId ?? "default", this.descriptor.id, rootId]); }
26
26
  loadOutline(rootId, path, fingerprint) { return this.store?.loadOutline(rootId, path, fingerprint); }
27
27
  saveOutline(rootId, outline) { this.store?.saveOutline(rootId, outline); }
28
+ /** Drop every cached generation of one file's outline after it changed. */
29
+ removeOutline(rootId, path) { this.store?.removeOutline?.(rootId, path); }
28
30
  /**
29
31
  * Resolve a caller path and return the tree that owns it.
30
32
  */
@@ -13,6 +13,7 @@ export const scopeFilesystemStore = (store, session) => {
13
13
  }),
14
14
  loadOutline: (rootId, path, fingerprint) => store.loadOutline(key(rootId), path, fingerprint),
15
15
  saveOutline: (rootId, outline) => store.saveOutline(key(rootId), outline),
16
+ ...(store.removeOutline === (void 0) ? {} : { removeOutline: (rootId, path) => store.removeOutline(key(rootId), path) }),
16
17
  loadImports: (rootId, path, fingerprint) => store.loadImports(key(rootId), path, fingerprint),
17
18
  saveImports: (rootId, report) => store.saveImports(key(rootId), report),
18
19
  loadExports: (rootId, path, fingerprint) => store.loadExports(key(rootId), path, fingerprint),
@@ -152,6 +152,22 @@ export declare class FilesystemService {
152
152
  */
153
153
  read(runtime: SessionRuntime, request: import("./types.js").ReadRequest): ReadPage;
154
154
  write(runtime: SessionRuntime, request: import("./types.js").WriteRequest): Promise<WriteResult>;
155
+ /**
156
+ * Rebuild the outline cache of one written file.
157
+ *
158
+ * A write changes the file's fingerprint, and the cache is keyed by that
159
+ * fingerprint, so a stale row can never be read — but the old generation
160
+ * would linger in storage forever and the next read would parse again. This
161
+ * replaces every generation of the file with the outline of the new content
162
+ * at write time, so "the file changed" and "its outline changed" happen
163
+ * together. A file that cannot be outlined (binary, oversized, Office) is
164
+ * dropped from the cache and is not an error for the write itself.
165
+ *
166
+ * @param runtime - Session runtime owning the root.
167
+ * @param root - The root the written path belongs to.
168
+ * @param path - Caller path of the written file.
169
+ */
170
+ private refreshOutline;
155
171
  outline(runtime: SessionRuntime, request: OutlineRequest): OutlineResult;
156
172
  imports(runtime: SessionRuntime, request: ImportRequest): ImportReport;
157
173
  exports(runtime: SessionRuntime, request: ExportRequest): ExportReport;
@@ -28,7 +28,7 @@ import { isOfficeFile, officeExports, officeReferences, parseOffice } from "../o
28
28
  import { buildSyntaxTree } from "./syntaxTree.js";
29
29
  import { wireSyntaxTree } from "../syntax/syntaxTree.js";
30
30
  import { buildDependencyReport, graphFromImports } from "./dependency.js";
31
- import { isFilesystemError } from "./errors.js";
31
+ import { fail, isFilesystemError } from "./errors.js";
32
32
  import { readPage } from "./read.js";
33
33
  import { writeFile } from "./write.js";
34
34
  import { SessionRuntime } from "./runtime.js";
@@ -276,8 +276,52 @@ export class FilesystemService {
276
276
  };
277
277
  const result = await writeFile(runtime.sandbox, runtime.descriptor.id, request, settings);
278
278
  runtime.persist(rooted.root, tree.snapshot());
279
+ this.refreshOutline(runtime, rooted.root, request.path);
279
280
  return result;
280
281
  }
282
+ /**
283
+ * Rebuild the outline cache of one written file.
284
+ *
285
+ * A write changes the file's fingerprint, and the cache is keyed by that
286
+ * fingerprint, so a stale row can never be read — but the old generation
287
+ * would linger in storage forever and the next read would parse again. This
288
+ * replaces every generation of the file with the outline of the new content
289
+ * at write time, so "the file changed" and "its outline changed" happen
290
+ * together. A file that cannot be outlined (binary, oversized, Office) is
291
+ * dropped from the cache and is not an error for the write itself.
292
+ *
293
+ * @param runtime - Session runtime owning the root.
294
+ * @param root - The root the written path belongs to.
295
+ * @param path - Caller path of the written file.
296
+ */
297
+ refreshOutline(runtime, root, path) {
298
+ let relative;
299
+ let node;
300
+ try {
301
+ const rooted = runtime.sandbox.resolve(path, "read");
302
+ relative = rooted.relative;
303
+ node = runtime.treeFor(root).node(relative);
304
+ }
305
+ catch {
306
+ return;
307
+ }
308
+ if (node === (void 0) || node.type !== "File") {
309
+ return;
310
+ }
311
+ try {
312
+ runtime.removeOutline(root.id, relative);
313
+ }
314
+ catch {
315
+ return fail("store_failure");
316
+ }
317
+ try {
318
+ const outline = buildOutline(runtime.sandbox, relative, { anchor: { file: node } }, { file: node });
319
+ runtime.saveOutline(root.id, outline);
320
+ }
321
+ catch {
322
+ // An unoutlinable file stays uncached; the write already succeeded.
323
+ }
324
+ }
281
325
  outline(runtime, request) {
282
326
  const { rooted, node, tree } = runtime.node(request.path, "read");
283
327
  const cached = runtime.loadOutline(rooted.root.id, rooted.relative, node.fingerprint);
@@ -354,6 +354,12 @@ export interface FilesystemStore {
354
354
  listWrites(sessionId: string, limit: number): WriteJournalEntry[];
355
355
  loadOutline(rootId: string, path: string, fingerprint: string): OutlineResult | undefined;
356
356
  saveOutline(rootId: string, outline: OutlineResult): void;
357
+ /**
358
+ * Drop every cached outline of one file. A writer calls this before saving
359
+ * the rebuilt outline so one file keeps one cache row instead of one per
360
+ * edited fingerprint. Optional: in-memory stores may only keep the latest.
361
+ */
362
+ removeOutline?(rootId: string, path: string): void;
357
363
  loadImports(rootId: string, path: string, fingerprint: string): ImportReport | undefined;
358
364
  saveImports(rootId: string, report: ImportReport): void;
359
365
  loadExports(rootId: string, path: string, fingerprint: string): ExportReport | undefined;
@@ -13,6 +13,12 @@ export interface KnowledgeEntry {
13
13
  export declare class KnowledgeRegistry {
14
14
  private readonly entries;
15
15
  register(knowledge: KnowledgeEntry): KnowledgeEntry;
16
+ /** Replace an existing knowledge entry. The id must stay the same. */
17
+ update(knowledge: KnowledgeEntry): KnowledgeEntry;
18
+ /** Remove one knowledge entry; the result says whether it existed. */
19
+ remove(id: string): boolean;
20
+ /** Remove every knowledge entry and report how many were dropped. */
21
+ clear(): number;
16
22
  get(id: string): KnowledgeEntry | undefined;
17
23
  list(): KnowledgeEntry[];
18
24
  has(id: string): boolean;
@@ -25,6 +25,19 @@ export class KnowledgeRegistry {
25
25
  validateKnowledge(knowledge);
26
26
  return this.entries.register(knowledge);
27
27
  }
28
+ /** Replace an existing knowledge entry. The id must stay the same. */
29
+ update(knowledge) {
30
+ validateKnowledge(knowledge);
31
+ return this.entries.update(knowledge.id, knowledge);
32
+ }
33
+ /** Remove one knowledge entry; the result says whether it existed. */
34
+ remove(id) {
35
+ return this.entries.remove(id);
36
+ }
37
+ /** Remove every knowledge entry and report how many were dropped. */
38
+ clear() {
39
+ return this.entries.clear();
40
+ }
28
41
  get(id) {
29
42
  return this.entries.get(id);
30
43
  }
@@ -20,6 +20,12 @@ export interface ToolExecutor {
20
20
  export declare class ToolRegistry {
21
21
  private readonly tools;
22
22
  register(descriptor: ToolDescriptor): this;
23
+ /** Replace an existing tool descriptor, keeping its registration slot. */
24
+ update(descriptor: ToolDescriptor): this;
25
+ /** Remove one tool descriptor. */
26
+ remove(name: string): boolean;
27
+ /** Remove every tool descriptor. */
28
+ clear(): number;
23
29
  get(name: string): ToolDescriptor | undefined;
24
30
  list(): ToolDescriptor[];
25
31
  get size(): number;
@@ -12,6 +12,24 @@ export class ToolRegistry {
12
12
  this.tools.set(descriptor.name, descriptor);
13
13
  return this;
14
14
  }
15
+ /** Replace an existing tool descriptor, keeping its registration slot. */
16
+ update(descriptor) {
17
+ if (!this.tools.has(descriptor.name)) {
18
+ throw new Error(`unknown tool ${descriptor.name}`);
19
+ }
20
+ this.tools.set(descriptor.name, descriptor);
21
+ return this;
22
+ }
23
+ /** Remove one tool descriptor. */
24
+ remove(name) {
25
+ return this.tools.delete(name);
26
+ }
27
+ /** Remove every tool descriptor. */
28
+ clear() {
29
+ const count = this.tools.size;
30
+ this.tools.clear();
31
+ return count;
32
+ }
15
33
  get(name) {
16
34
  return this.tools.get(name);
17
35
  }
@@ -2,6 +2,7 @@
2
2
  export * from "./types.js";
3
3
  export * from "./outline.js";
4
4
  export * from "./outlineIndex.js";
5
+ export * from "./rangeBridge.js";
5
6
  export * from "./outlineKeywords.js";
6
7
  export { builtinOutlineStrategies } from "./outlineStrategies.js";
7
8
  export { hasOfficeOutlineParser, registerOfficeOutlineParser } from "./officeBridge.js";
@@ -2,6 +2,7 @@
2
2
  export * from "./types.js";
3
3
  export * from "./outline.js";
4
4
  export * from "./outlineIndex.js";
5
+ export * from "./rangeBridge.js";
5
6
  export * from "./outlineKeywords.js";
6
7
  export { builtinOutlineStrategies } from "./outlineStrategies.js";
7
8
  export { hasOfficeOutlineParser, registerOfficeOutlineParser } from "./officeBridge.js";
@@ -90,6 +90,25 @@ export declare class OutlineRegistry {
90
90
  * @param strategy - Strategy to add.
91
91
  */
92
92
  register(strategy: OutlineStrategy): void;
93
+ /**
94
+ * Replace one registered strategy in place, keeping its selection position.
95
+ *
96
+ * @param strategy - Replacement; its `id` must already be registered.
97
+ */
98
+ update(strategy: OutlineStrategy): void;
99
+ /**
100
+ * Remove one strategy.
101
+ *
102
+ * @param id - Strategy id.
103
+ * @returns `true` when it was registered.
104
+ */
105
+ remove(id: string): boolean;
106
+ /**
107
+ * Remove every strategy.
108
+ *
109
+ * @returns Number of removed strategies.
110
+ */
111
+ clear(): number;
93
112
  /**
94
113
  * Strategies in selection order.
95
114
  */
@@ -541,6 +541,48 @@ export class OutlineRegistry {
541
541
  this.byId.set(strategy.id, strategy);
542
542
  this.strategies.push(strategy);
543
543
  }
544
+ /**
545
+ * Replace one registered strategy in place, keeping its selection position.
546
+ *
547
+ * @param strategy - Replacement; its `id` must already be registered.
548
+ */
549
+ update(strategy) {
550
+ if (typeof (strategy) !== "object" || strategy === null || typeof (strategy.id) !== "string" || strategy.id.trim().length === 0) {
551
+ throw new Error("invalid outline strategy");
552
+ }
553
+ const index = this.strategies.findIndex((current) => current.id === strategy.id);
554
+ if (index < 0) {
555
+ throw new Error(`unknown outline strategy: ${strategy.id}`);
556
+ }
557
+ this.strategies[index] = strategy;
558
+ this.byId.set(strategy.id, strategy);
559
+ }
560
+ /**
561
+ * Remove one strategy.
562
+ *
563
+ * @param id - Strategy id.
564
+ * @returns `true` when it was registered.
565
+ */
566
+ remove(id) {
567
+ const index = this.strategies.findIndex((current) => current.id === id);
568
+ if (index < 0) {
569
+ return false;
570
+ }
571
+ this.strategies.splice(index, 1);
572
+ this.byId.delete(id);
573
+ return true;
574
+ }
575
+ /**
576
+ * Remove every strategy.
577
+ *
578
+ * @returns Number of removed strategies.
579
+ */
580
+ clear() {
581
+ const count = this.strategies.length;
582
+ this.strategies.length = 0;
583
+ this.byId.clear();
584
+ return count;
585
+ }
544
586
  /**
545
587
  * Strategies in selection order.
546
588
  */
@@ -25,6 +25,16 @@ export declare class OutlineKeywordRegistry {
25
25
  * the first category and is a no-op, so aliases cannot silently reclassify.
26
26
  */
27
27
  register(category: OutlineKeywordCategory, word: string): this;
28
+ /**
29
+ * Move one word to another category, replacing its previous classification.
30
+ */
31
+ update(category: OutlineKeywordCategory, word: string): this;
32
+ /**
33
+ * Remove one word.
34
+ *
35
+ * @returns `true` when it was registered.
36
+ */
37
+ remove(word: string): boolean;
28
38
  /**
29
39
  * Category of one word, or `undefined`.
30
40
  */