fluffy-context 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,31 +1,30 @@
1
1
  # Context Runtime
2
2
 
3
- Context Runtime 是一个面向 AI 编程会话的本地上下文运行时 CLI。0.2.0 在可靠 Context 闭环之上增加了无模型、确定性的 Agent API 和 Knowledge Discovery,让 Agent 可以先发现已验证的项目共识,再保存和恢复工作状态,而不是每次重新阅读大量项目内容。
3
+ `fluffy-context` 是面向 AI 编程会话的本地 Context Runtime CLI。0.3.1 提供可重复、安全调用的任务定位入口、MCP 工具,以及 Claude Code 的阶段性工作流引导,让 Agent 不必自行拼接恢复、共识发现与短期记录。
4
4
 
5
- 它更接近“Context 的 Git”,而不是代码备份工具:Git 仍然负责代码和真实文件变更,Context Runtime 负责 AI 工作状态的版本化保存与恢复。
5
+ 它更接近“Context 的 Git”,而不是代码备份工具:Git 仍然负责代码和真实文件变更;Context Runtime 负责 AI 工作状态、已验证共识与交接信息的本地保存和恢复。
6
6
 
7
7
  ## 适用场景
8
8
 
9
- - 下班前保存当前任务状态,第二天继续工作。
10
- - 保存完成项、待办、阻塞、决策和风险。
11
- - 将可复用业务知识独立记录,并在确认后纳入知识库。
12
- - 记录已经验证不可行的方案,避免新会话重复探索。
13
- - 通过摘要预算恢复上下文,减少不必要的 Token 消耗。
9
+ - 下班前保存当前任务状态,下一次会话继续工作。
10
+ - 以有界摘要恢复进行中的任务,降低重复阅读和 Token 消耗。
11
+ - 让 Agent 在开始任务时获得已验证 Knowledge、匹配 Deadend 与未吸收 Note。
12
+ - 通过 MCP 或 Claude Code Hooks 降低 Agent 主动调用 Context 工具的决策成本。
13
+ - 记录完成项、待办、阻塞、决策、风险与已验证不可行的方案。
14
14
 
15
15
  ## 环境要求
16
16
 
17
17
  - Node.js `>=20.19.0`
18
- - Git 可选。项目位于 Git 仓库内时,CLI 会记录当前分支和 commit 信息。
18
+ - Git 可选。项目在 Git 仓库内时,CLI 会记录分支与 commit。
19
19
 
20
20
  ## 安装
21
21
 
22
- 发布后可以使用 npm 全局安装:
23
-
24
22
  ```bash
25
23
  npm install --global fluffy-context
24
+ ctx --help
26
25
  ```
27
26
 
28
- 也可以在项目目录中直接运行:
27
+ 也可以在项目目录中使用:
29
28
 
30
29
  ```bash
31
30
  npx fluffy-context --help
@@ -33,27 +32,13 @@ npx fluffy-context --help
33
32
 
34
33
  ## 快速开始
35
34
 
36
- 在项目根目录初始化 Context Runtime:
35
+ 在项目根目录初始化:
37
36
 
38
37
  ```bash
39
38
  ctx init
40
39
  ```
41
40
 
42
- 这会创建以下本地文件:
43
-
44
- ```text
45
- .context/
46
- ├── manifest.json
47
- ├── index.json
48
- ├── contexts/
49
- ├── locks/
50
- ├── knowledge.json # 首次使用 learn 后创建
51
- ├── deadends.json # 首次使用 deadend 后创建
52
- └── notes.json # 首次使用 note add 后创建
53
- .contextignored
54
- ```
55
-
56
- 保存一次工作状态:
41
+ 这会创建本地 `.context/` 存储和 `.contextignored` 规则文件。保存阶段性工作:
57
42
 
58
43
  ```bash
59
44
  ctx checkpoint \
@@ -63,231 +48,145 @@ ctx checkpoint \
63
48
  --pending "补充异常路径测试,运行集成测试" \
64
49
  --decisions "订单状态由服务端状态机统一维护" \
65
50
  --risks "第三方回调可能重复到达" \
66
- --files "src/order/state-machine.ts,src/order/state-machine.test.ts"
51
+ --files "src/order/state-machine.ts,src/order-state-machine.test.ts"
67
52
  ```
68
53
 
69
- 第二天恢复:
54
+ 新会话中,Agent 或宿主优先使用:
70
55
 
71
56
  ```bash
72
- ctx resume
57
+ ctx orient "订单状态机异常路径" --max-chars 4000
73
58
  ```
74
59
 
75
- `resume` 默认返回轻量摘要,同时保留完整详情供按需使用。可以限制摘要字符数:
60
+ `ctx orient` 只读地聚合当前 Context 摘要、与查询匹配的**已验证** Knowledge 和 Deadend,以及当前 Context 尚未吸收的 Note。它不更新 `lastUsedAt`、不创建 Snapshot,也不会记录 usage 噪声。省略查询时可只恢复当前任务和 open Notes:
76
61
 
77
62
  ```bash
78
- ctx resume --max-chars 2000
63
+ ctx orient
64
+ ctx orient --context <context-id> --note-limit 10
79
65
  ```
80
66
 
81
- Agent 集成可以直接调用 `dist/src/agent/api.js` 中的 `saveContext`、`loadContext` 和 `searchContext`,不需要读取 `.context` 文件。推荐工作流是:先 `loadContext` 或 `ctx resume`,再用 `ctx knowledge discover` 或 `searchContext` 查找已验证共识,开发过程中记录 Note,阶段完成后 checkpoint,最后用 `learn` 和 `verify` 沉淀经过确认的知识。`loadContext` 默认不返回完整 details;候选 Knowledge、未验证 Deadend、模型推理、向量检索和远端服务不属于 0.2.0 默认能力。
67
+ ## Agent 工作流
82
68
 
83
- 查看版本:
69
+ 推荐阶段:
84
70
 
85
- ```bash
86
- ctx --version
87
- ```
88
-
89
- ## 常用命令
90
-
91
- 所有一级命令都支持独立帮助,帮助输出为面向终端和 Agent 的纯文本,不会读取或修改项目状态:
92
-
93
- ```bash
94
- ctx init --help
95
- ctx checkpoint --help
96
- ctx resume --help
97
- ctx knowledge verify --help
98
- ctx deadend verify --help
71
+ ```text
72
+ Orient → Plan → Implement → Verify → Handoff
99
73
  ```
100
74
 
101
- 业务命令仍然默认输出 JSON;未知选项、缺少选项值和无效位置参数会以非零退出码报告。
75
+ 1. **Orient**:调用 `ctx orient` 或 MCP `context_orient`,读取有界任务上下文。
76
+ 2. **Plan**:在广泛改动前先形成可执行计划。
77
+ 3. **Implement**:用 `ctx note add` 记录有价值的观察、决策、阻塞或行动;不要为每条记录创建 Snapshot。
78
+ 4. **Verify**:执行适用的构建和测试,并把有意义的失败记录为 Note。
79
+ 5. **Handoff**:任务阶段结束时显式运行一次 `ctx checkpoint`,记录完成项、待办、决策和风险;Hook 不会自动 checkpoint。
102
80
 
103
- ### `ctx init`
81
+ ## 常用命令
104
82
 
105
- 初始化项目的 Context Runtime 存储布局。重复执行是幂等的,不会覆盖已有 Context 或项目忽略规则。
83
+ 业务命令默认输出格式化 JSON;帮助输出为纯文本。错误写入标准错误并返回非零退出码。`ctx --version` 输出一个 JSON 字符串。
106
84
 
107
85
  ```bash
108
86
  ctx init
109
- ctx init path/to/project
110
- ctx init --path path/to/project
87
+ ctx checkpoint --title "修复支付回调" --progress "已定位签名校验失败原因"
88
+ ctx resume --max-chars 2000
89
+ ctx orient "支付回调" --knowledge-limit 5 --deadend-limit 3
90
+ ctx note add "测试环境缺少回调凭据" --kind problem --context <context-id>
91
+ ctx activity --context <context-id> --open
92
+ ctx knowledge discover "支付回调"
93
+ ctx knowledge verify <knowledge-id>
94
+ ctx deadend verify <deadend-id>
95
+ ctx status
96
+ ctx doctor
111
97
  ```
112
98
 
113
- ### `ctx checkpoint`
99
+ ### `ctx checkpoint` 和 `ctx resume`
114
100
 
115
- 保存结构化工作状态。第一次保存创建 baseline,后续有变化时保存 patch;内容没有变化时返回 `no_change`。
116
-
117
- ```bash
118
- ctx checkpoint \
119
- --context <context-id> \
120
- --title "修复支付回调" \
121
- --progress "已定位签名校验失败原因" \
122
- --last-error "测试环境缺少回调凭据" \
123
- --completed "复现问题,确认签名字段" \
124
- --pending "补充回归测试" \
125
- --decisions "保留原始请求体用于验签" \
126
- --risks "旧版客户端字段格式不同" \
127
- --files "src/payment/webhook.ts"
128
- ```
101
+ 第一次 checkpoint 创建 baseline,后续变化创建 patch。相同内容返回 `no_change`;短时间内的变化可能返回 `rate_limited`,应在完成更多阶段工作后再保存。`ctx resume` 返回轻量摘要和按需使用的完整 details,并会保留其既有的 `lastUsedAt` 更新语义。
129
102
 
130
- 列表参数使用逗号分隔。`--context` 省略时会创建新的 Context。默认情况下,实际保存之间至少间隔 10 秒;短时间内有变化的保存会返回 `rate_limited`,没有变化则返回 `no_change`。使用 `--absorb-notes` 时,只有真正保存了新 Snapshot 才会将当前 Context 下尚未吸收的 Note 标记为已吸收;`no_change` 和 `rate_limited` 不会改变 Note。
103
+ ### `ctx note` 和 `ctx activity`
131
104
 
132
- Checkpoint 会在写入前规范化新值:文本首尾空白会被移除,空白 `lastError` 会变成 `null`,列表中的空项会被移除。未提供字段会沿用已有值;显式传入空字符串或空列表会清除对应值。历史 Snapshot 不会被自动改写;`ctx doctor` 只负责报告历史数据损坏。
105
+ `ctx note add` 是开发过程中的低成本记录入口,不创建 Snapshot。阶段性 checkpoint 可通过 `--absorb-notes` 吸收当前 Context 的 open Note。`ctx activity` 提供只读的 Note 与 Snapshot 时间线,适合人类查看 Agent 的进度。
133
106
 
134
- ### `ctx resume`
107
+ ### Knowledge 与 Deadend
135
108
 
136
- 恢复一个 active 或 stable Context。默认优先选择当前分支上最近更新的 Context,并返回 Git 分支/commit 是否发生漂移。
109
+ `ctx learn` 和 `ctx deadend` 默认创建 `candidate` 项。普通发现与 `ctx orient` 只使用已验证项目共识;候选项需要通过 `ctx knowledge verify` 或 `ctx deadend verify` 显式确认,或以 `--all` 审查。
137
110
 
138
111
  ```bash
139
- ctx resume
140
- ctx resume --context <context-id>
141
- ctx resume --path path/to/project --max-chars 4000
112
+ ctx learn "订单取消后不能再次进入支付中状态" --scope project
113
+ ctx deadend --attempt "使用共享可变单例保存订单状态" --reason "并发测试出现跨用例状态泄漏"
114
+ ctx knowledge discover "订单取消"
142
115
  ```
143
116
 
144
- ### `ctx status`
117
+ ## MCP 集成
145
118
 
146
- 查看项目是否已初始化以及当前 Context 索引:
119
+ `ctx agent serve` 启动一个 MCP stdio server,暴露一个工具:`context_orient`。该 server 的标准输入和输出均属于 MCP 协议,不能在交互式终端直接使用或混入日志。
147
120
 
148
121
  ```bash
149
- ctx status
122
+ ctx agent serve
150
123
  ```
151
124
 
152
- ### `ctx doctor`
153
-
154
- 检查 Manifest、存储目录、索引、Context 元数据、当前 Snapshot 和索引重建一致性:
155
-
156
- ```bash
157
- ctx doctor
158
- ```
125
+ `context_orient` 接受可选的 `path`、`contextId`、`query`、`scope`、`maxChars`、`knowledgeLimit`、`deadendLimit` 和 `noteLimit`。它的结果与 `ctx orient` 相同:`ready` 是成功的任务定位结果,初始化但没有可恢复 Context 时返回成功的 `no_context`。输入验证或 Runtime 错误为单次工具错误,不会终止 server。
159
126
 
160
- ### `ctx learn`
127
+ ## Claude Code 集成
161
128
 
162
- 记录一条待确认的项目知识。Knowledge 默认是 `candidate`,不会进入普通的已验证知识列表。
129
+ 先查看项目是否需要安装集成:
163
130
 
164
131
  ```bash
165
- ctx learn "订单取消后不能再次进入支付中状态" \
166
- --scope project \
167
- --context <context-id> \
168
- --snapshot <snapshot-id> \
169
- --evidence "src/order/state-machine.ts,订单服务接口约束"
132
+ ctx integrate claude inspect
133
+ ctx integrate claude install
170
134
  ```
171
135
 
172
- 查看、发现和确认知识:
136
+ `install` 默认只输出预览,不创建或修改文件。确认后才显式写入:
173
137
 
174
138
  ```bash
175
- ctx knowledge
176
- ctx knowledge --all
177
- ctx knowledge discover "投保人认证"
178
- ctx knowledge discover "identity verification" --scope project --limit 10 --max-chars 4000
179
- ctx knowledge verify <knowledge-id>
139
+ ctx integrate claude install --apply
180
140
  ```
181
141
 
182
- `discover` 默认只匹配已验证 Knowledge,返回命中的字段、匹配原因、来源 Context/Snapshot 和 supporting evidence。候选项不会参与普通发现;需要审查候选或其它状态时使用 `--all` 或 `--status candidate,verified`。匹配采用确定性的规范化文本和业务别名规则,不依赖模型或向量数据库。
183
-
184
- 不提供 `--context` 时,知识仍可以记录为项目级候选知识;如果提供来源,则对应 Context 和 Snapshot 必须存在。
142
+ 安装会以幂等方式合并项目本地配置:
185
143
 
186
- ### `ctx note`
144
+ - `.claude/settings.json`:`SessionStart` 与 `UserPromptSubmit` Hook,分别调用 `ctx hook claude-code session-start` 和 `ctx hook claude-code user-prompt`。
145
+ - `.mcp.json`:名为 `fluffy-context` 的 stdio MCP server,调用 `ctx agent serve`。
187
146
 
188
- `ctx note` 是 Agent 在开发过程中记录短期问题、观察、行动和决策的低成本入口。它不会创建 Context Snapshot,记录会以追加方式保留,后续可由 checkpoint 显式吸收。
147
+ 安装会保留无关的 Hook、权限和 MCP server;遇到无效 JSON、无效相关结构或同名冲突配置会拒绝覆盖。Hook 仅注入有界的 Orient/Plan/Implement/Verify/Handoff 指引,输入损坏、项目未初始化或查询失败时会 fail open,不阻塞 Claude Code 任务,也不会自动创建 checkpoint。
189
148
 
190
- ```bash
191
- ctx note add "第三方人脸识别测试凭据缺失" \
192
- --kind problem \
193
- --context <context-id>
194
- ctx note add "保留原始请求体后验签恢复" \
195
- --kind decision \
196
- --status resolved \
197
- --context <context-id>
198
- ctx note list --context <context-id> --open
199
- ```
149
+ ## TypeScript Agent API
200
150
 
201
- ### `ctx activity`
151
+ 使用公开包入口,不要导入内部 `dist/...` 文件:
202
152
 
203
- `ctx activity` 面向人类查看 Agent 的开发活动,返回 Note 和已保存 Snapshot 的时间线。它是只读查询,不会创建额外事件日志。
153
+ ```ts
154
+ import { contextOrient, loadContext, saveContext, searchContext } from 'fluffy-context';
204
155
 
205
- ```bash
206
- ctx activity --context <context-id> --since 2026-08-21T00:00:00.000Z
207
- ctx activity --open --limit 20
156
+ const orientation = await contextOrient(process.cwd(), {
157
+ query: 'payment callback',
158
+ maxChars: 2400,
159
+ });
208
160
  ```
209
161
 
210
- ### `ctx deadend`
162
+ `fluffy-context/agent` 提供相同的 Agent API。公开类型声明随包发布。Runtime 的存储布局与内部模块不是兼容性承诺。
211
163
 
212
- 记录一条已尝试但不可行的路径。Deadend 默认是 `candidate`,不会默认注入恢复摘要。
164
+ ## `.contextignored` 与安全边界
213
165
 
214
- ```bash
215
- ctx deadend \
216
- --attempt "使用共享可变单例保存订单状态" \
217
- --reason "并发测试出现跨用例状态泄漏" \
218
- --scope project \
219
- --context <context-id> \
220
- --evidence "test/order-state.test.ts"
221
- ```
222
-
223
- 查看和确认死路:
224
-
225
- ```bash
226
- ctx deadends
227
- ctx deadends --all
228
- ctx deadend verify <deadend-id>
229
- ```
230
-
231
- ## `.contextignored`
232
-
233
- `ctx init` 会在项目根目录创建 `.contextignored`。它用于配置不应作为上下文关联文件保存的路径,语法接近 `.gitignore`:
166
+ `.contextignored` 语法接近 `.gitignore`,用于排除不应作为关联文件保存的项目路径:
234
167
 
235
168
  ```gitignore
236
- # 私有目录
237
169
  private/
238
-
239
- # 本地生成文件
240
170
  *.generated.ts
241
-
242
- # 不纳入上下文的临时记录
243
171
  notes/draft-*
244
172
  ```
245
173
 
246
- 内置保护规则始终优先,包括 `.context/`、`.git/`、依赖目录、构建产物、环境变量文件、密钥和常见凭据文件。项目规则不能通过否定规则覆盖这些保护。
247
-
248
- ## 存储与安全边界
249
-
250
- - Snapshot 按 baseline/patch 保存,不会每次复制整个项目文件树。
251
- - `index.json` 是可重建索引,不是唯一业务数据来源。
252
- - Context、Knowledge、Deadend 使用独立文件保存。
253
- - 写入使用临时文件替换,并通过项目级锁避免并发覆盖。
254
- - 默认只恢复摘要字段;完整结构化内容位于 `details` 中按需读取。
255
- - CLI 不会读取或保存被内置保护规则排除的文件内容。
256
-
257
- ## 命令输出
174
+ 内置保护规则优先,不能被否定规则绕过:`.context/`、`.git/`、依赖和构建目录、`.env` 文件、密钥、常见 credential/token 文件、绝对路径和 `..` 越界路径都不会被作为 Context 关联文件保存。
258
175
 
259
- CLI 默认向标准输出写入格式化 JSON,适合被脚本、Agent 或 IDE 集成:
260
-
261
- ```bash
262
- ctx --help
263
- ctx --version
264
- ```
265
-
266
- 错误信息写入标准错误,并以非零退出码结束。
267
-
268
- ## 开发
269
-
270
- 安装依赖并运行测试:
176
+ ## 开发与发布验证
271
177
 
272
178
  ```bash
273
179
  npm install
274
- npm test
275
- ```
276
-
277
- 只构建 TypeScript:
278
-
279
- ```bash
280
180
  npm run build
181
+ npm test
182
+ npm run pack:check
183
+ npm pack --dry-run --json
281
184
  ```
282
185
 
283
- 本项目核心运行时只依赖 Node.js 内置模块,当前没有运行时第三方依赖。
186
+ 运行时依赖 `@modelcontextprotocol/server` 与 `zod` 以提供 MCP 适配;核心 Context 存储仍然保持本地优先。`prepack` 会重新构建发布产物,`prepublishOnly` 在未来手动发布前运行测试;这些命令不会发布 npm 包。
284
187
 
285
188
  ## 当前范围
286
189
 
287
- 当前版本聚焦本地单项目的可靠闭环:
288
-
289
- ```text
290
- init → checkpoint → resume
291
- ```
190
+ 0.3.1 聚焦单项目、本地优先的可靠闭环和 Agent 采用路径:有界 Orient、显式 checkpoint、确定性 Knowledge/Deadend discovery、MCP `context_orient` 与可选的 Claude Code Hook 集成。
292
191
 
293
- 并提供基础的 Knowledge、Deadend、忽略规则、诊断、Snapshot 增量保存、摘要预算和保存限流能力。远程同步、多人协作、复杂语义检索、自动模型总结和 Context merge 不属于当前版本的已实现能力。
192
+ 远程同步、多人协作、复杂语义检索、自动模型总结、自动 checkpoint、Context merge,以及更多状态变更型 MCP 工具不属于当前版本范围。
@@ -0,0 +1,8 @@
1
+ import type { AgentLoadOptions, AgentLoadResult, AgentSaveResult, CheckpointInput, ContextSearchOptions, ContextSearchResult } from '../runtime/types.js';
2
+ import type { ContextOrientOptions, ContextOrientResult } from './types.js';
3
+ export declare function saveContext(startPath: string | undefined, input: CheckpointInput, options?: {
4
+ minSaveIntervalMs?: number;
5
+ }): Promise<AgentSaveResult>;
6
+ export declare function loadContext(startPath: string | undefined, options?: AgentLoadOptions): Promise<AgentLoadResult>;
7
+ export declare function contextOrient(startPath: string | undefined, options?: ContextOrientOptions): Promise<ContextOrientResult>;
8
+ export declare function searchContext(startPath: string | undefined, query: string, options?: ContextSearchOptions): Promise<ContextSearchResult>;
@@ -4,6 +4,7 @@ import { contextsRoot, contextMetadataPath } from '../storage/layout.js';
4
4
  import { isRecord, readJson } from '../storage/json-store.js';
5
5
  import { checkpoint, rebuildSnapshot, resume } from '../runtime/runtime.js';
6
6
  import { discoverDeadends, discoverKnowledge, listDeadends } from '../runtime/knowledge.js';
7
+ import { listNotes } from '../runtime/notes.js';
7
8
  function normalized(value) {
8
9
  return value.trim().toLocaleLowerCase();
9
10
  }
@@ -25,6 +26,64 @@ function limitedList(values, budget) {
25
26
  return result ? [result] : [];
26
27
  });
27
28
  }
29
+ function textLength(value) {
30
+ return Array.isArray(value) ? value.reduce((total, item) => total + item.length, 0) : value?.length ?? 0;
31
+ }
32
+ function emptyKnowledge(query) {
33
+ return { query, total: 0, truncated: false, hits: [], duplicateIds: [], possibleConflicts: [] };
34
+ }
35
+ function emptyDeadends(query) {
36
+ return { query, total: 0, truncated: false, hits: [] };
37
+ }
38
+ function limitKnowledge(result, budget) {
39
+ let truncated = false;
40
+ const hits = [];
41
+ for (const hit of result.hits) {
42
+ if (budget.remaining <= 0) {
43
+ truncated = true;
44
+ break;
45
+ }
46
+ const statement = limited(hit.knowledge.statement, budget);
47
+ const supportingEvidence = limitedList(hit.knowledge.supportingEvidence, budget);
48
+ if (statement.length < hit.knowledge.statement.length || supportingEvidence.length < hit.knowledge.supportingEvidence.length)
49
+ truncated = true;
50
+ hits.push({ ...hit, knowledge: { ...hit.knowledge, statement, supportingEvidence } });
51
+ }
52
+ if (hits.length < result.hits.length)
53
+ truncated = true;
54
+ return { result: { ...result, hits, truncated: result.truncated || truncated }, truncated };
55
+ }
56
+ function limitDeadends(result, budget) {
57
+ let truncated = false;
58
+ const hits = [];
59
+ for (const hit of result.hits) {
60
+ if (budget.remaining <= 0) {
61
+ truncated = true;
62
+ break;
63
+ }
64
+ const attempt = limited(hit.deadend.attempt, budget);
65
+ const reason = limited(hit.deadend.reason, budget);
66
+ const observedEvidence = limitedList(hit.deadend.observedEvidence, budget);
67
+ if (attempt.length < hit.deadend.attempt.length || reason.length < hit.deadend.reason.length || observedEvidence.length < hit.deadend.observedEvidence.length)
68
+ truncated = true;
69
+ hits.push({ ...hit, deadend: { ...hit.deadend, attempt, reason, observedEvidence } });
70
+ }
71
+ if (hits.length < result.hits.length)
72
+ truncated = true;
73
+ return { result: { ...result, hits, truncated: result.truncated || truncated }, truncated };
74
+ }
75
+ function limitNotes(notes, budget) {
76
+ const result = [];
77
+ for (const note of notes) {
78
+ if (budget.remaining <= 0)
79
+ return { notes: result, truncated: true };
80
+ const message = limited(note.message, budget);
81
+ result.push({ ...note, message });
82
+ if (message.length < note.message.length)
83
+ return { notes: result, truncated: true };
84
+ }
85
+ return { notes: result, truncated: false };
86
+ }
28
87
  function summary(content, maxChars, branchOrCommitDrift = false) {
29
88
  const budget = { remaining: Math.max(0, maxChars) };
30
89
  return {
@@ -91,6 +150,99 @@ export async function loadContext(startPath, options = {}) {
91
150
  verifiedDeadendIds: verifiedDeadends.map((item) => item.deadendId),
92
151
  };
93
152
  }
153
+ export async function contextOrient(startPath, options = {}) {
154
+ const projectRoot = await resolveProjectRoot(startPath);
155
+ const query = options.query === undefined ? null : options.query.trim();
156
+ if (query === '')
157
+ throw new Error('context orient query must not be empty');
158
+ let loaded;
159
+ try {
160
+ loaded = await resume(projectRoot, options.contextId, options.maxChars ?? 4000, { touchLastUsedAt: false });
161
+ }
162
+ catch (error) {
163
+ if (error instanceof Error && error.message === 'no active context found') {
164
+ return {
165
+ status: 'no_context',
166
+ projectRoot,
167
+ query,
168
+ context: null,
169
+ snapshot: null,
170
+ resumeSummary: null,
171
+ knowledge: emptyKnowledge(query ?? ''),
172
+ deadends: emptyDeadends(query ?? ''),
173
+ notes: [],
174
+ truncated: false,
175
+ };
176
+ }
177
+ throw error;
178
+ }
179
+ const notes = await listNotes(projectRoot, {
180
+ contextId: loaded.context.id,
181
+ openOnly: true,
182
+ limit: options.noteLimit ?? 20,
183
+ maxChars: options.maxChars ?? 4000,
184
+ });
185
+ const knowledge = query === null
186
+ ? emptyKnowledge('')
187
+ : await discoverKnowledge(projectRoot, query, {
188
+ scope: options.scope,
189
+ limit: options.knowledgeLimit ?? 10,
190
+ maxChars: options.maxChars ?? 4000,
191
+ });
192
+ const deadends = query === null
193
+ ? emptyDeadends('')
194
+ : await discoverDeadends(projectRoot, query, {
195
+ scope: options.scope,
196
+ limit: options.deadendLimit ?? 10,
197
+ maxChars: options.maxChars ?? 4000,
198
+ });
199
+ const budget = { remaining: Math.max(0, options.maxChars ?? 4000) };
200
+ const resumeSummary = {
201
+ progressSummary: limited(loaded.resumeSummary.progressSummary, budget),
202
+ lastError: loaded.resumeSummary.lastError === null ? null : limited(loaded.resumeSummary.lastError, budget),
203
+ completed: limitedList(loaded.resumeSummary.completed, budget),
204
+ pendingTasks: limitedList(loaded.resumeSummary.pendingTasks, budget),
205
+ decisions: limitedList(loaded.resumeSummary.decisions, budget),
206
+ risks: limitedList(loaded.resumeSummary.risks, budget),
207
+ relatedFiles: limitedList(loaded.resumeSummary.relatedFiles, budget),
208
+ branchOrCommitDrift: loaded.resumeSummary.branchOrCommitDrift,
209
+ };
210
+ const resumeTruncated = textLength(resumeSummary.progressSummary) < textLength(loaded.resumeSummary.progressSummary)
211
+ || textLength(resumeSummary.lastError) < textLength(loaded.resumeSummary.lastError)
212
+ || textLength(resumeSummary.completed) < textLength(loaded.resumeSummary.completed)
213
+ || textLength(resumeSummary.pendingTasks) < textLength(loaded.resumeSummary.pendingTasks)
214
+ || textLength(resumeSummary.decisions) < textLength(loaded.resumeSummary.decisions)
215
+ || textLength(resumeSummary.risks) < textLength(loaded.resumeSummary.risks)
216
+ || textLength(resumeSummary.relatedFiles) < textLength(loaded.resumeSummary.relatedFiles);
217
+ const limitedKnowledgeResult = limitKnowledge(knowledge, budget);
218
+ const limitedDeadendResult = limitDeadends(deadends, budget);
219
+ const limitedNotesResult = limitNotes(notes, budget);
220
+ return {
221
+ status: 'ready',
222
+ projectRoot,
223
+ query,
224
+ context: {
225
+ id: loaded.context.id,
226
+ title: loaded.context.title,
227
+ status: loaded.context.status,
228
+ branch: loaded.context.branch,
229
+ commit: loaded.context.commit,
230
+ updatedAt: loaded.context.updatedAt,
231
+ },
232
+ snapshot: {
233
+ snapshotId: loaded.snapshot.snapshotId,
234
+ mode: loaded.snapshot.mode,
235
+ createdAt: loaded.snapshot.createdAt,
236
+ branch: loaded.snapshot.branch,
237
+ commit: loaded.snapshot.commit,
238
+ },
239
+ resumeSummary,
240
+ knowledge: limitedKnowledgeResult.result,
241
+ deadends: limitedDeadendResult.result,
242
+ notes: limitedNotesResult.notes,
243
+ truncated: resumeTruncated || limitedKnowledgeResult.truncated || limitedDeadendResult.truncated || limitedNotesResult.truncated,
244
+ };
245
+ }
94
246
  export async function searchContext(startPath, query, options = {}) {
95
247
  const projectRoot = await resolveProjectRoot(startPath);
96
248
  const cleanQuery = query.trim();
@@ -0,0 +1,3 @@
1
+ export * from './api.js';
2
+ export type { ContextOrientContext, ContextOrientNoContextResult, ContextOrientOptions, ContextOrientReadyResult, ContextOrientResult, ContextOrientSnapshot, } from './types.js';
3
+ export type { AgentLoadOptions, AgentLoadResult, AgentSaveResult, ContextSearchHit, ContextSearchOptions, ContextSearchResult, } from '../runtime/types.js';
@@ -0,0 +1,50 @@
1
+ import type { ContextStatus, DeadendDiscoveryResult, KnowledgeDiscoveryResult, Note, ResumeSummary, SnapshotMode } from '../runtime/types.js';
2
+ export interface ContextOrientOptions {
3
+ contextId?: string;
4
+ query?: string;
5
+ scope?: string;
6
+ maxChars?: number;
7
+ knowledgeLimit?: number;
8
+ deadendLimit?: number;
9
+ noteLimit?: number;
10
+ }
11
+ export interface ContextOrientContext {
12
+ id: string;
13
+ title: string;
14
+ status: ContextStatus;
15
+ branch: string | null;
16
+ commit: string | null;
17
+ updatedAt: string;
18
+ }
19
+ export interface ContextOrientSnapshot {
20
+ snapshotId: string;
21
+ mode: SnapshotMode;
22
+ createdAt: string;
23
+ branch: string | null;
24
+ commit: string | null;
25
+ }
26
+ export interface ContextOrientReadyResult {
27
+ status: 'ready';
28
+ projectRoot: string;
29
+ query: string | null;
30
+ context: ContextOrientContext;
31
+ snapshot: ContextOrientSnapshot;
32
+ resumeSummary: ResumeSummary;
33
+ knowledge: KnowledgeDiscoveryResult;
34
+ deadends: DeadendDiscoveryResult;
35
+ notes: Note[];
36
+ truncated: boolean;
37
+ }
38
+ export interface ContextOrientNoContextResult {
39
+ status: 'no_context';
40
+ projectRoot: string;
41
+ query: string | null;
42
+ context: null;
43
+ snapshot: null;
44
+ resumeSummary: null;
45
+ knowledge: KnowledgeDiscoveryResult;
46
+ deadends: DeadendDiscoveryResult;
47
+ notes: Note[];
48
+ truncated: false;
49
+ }
50
+ export type ContextOrientResult = ContextOrientReadyResult | ContextOrientNoContextResult;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ interface Rule {
2
+ pattern: string;
3
+ negated: boolean;
4
+ }
5
+ export declare function readProjectRules(projectRoot: string): Promise<Rule[]>;
6
+ export declare function filterPaths(projectRoot: string, paths: string[]): Promise<string[]>;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};