fluffy-context 0.3.1 → 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.
- package/README.md +61 -5
- package/dist/src/agent/api.d.ts +5 -1
- package/dist/src/agent/api.js +50 -10
- package/dist/src/agent/index.d.ts +1 -1
- package/dist/src/agent/types.d.ts +21 -0
- package/dist/src/cli/main.js +177 -9
- package/dist/src/cognition/feedback.d.ts +9 -0
- package/dist/src/cognition/feedback.js +30 -0
- package/dist/src/cognition/git-observer.d.ts +13 -0
- package/dist/src/cognition/git-observer.js +61 -0
- package/dist/src/cognition/journal.d.ts +24 -0
- package/dist/src/cognition/journal.js +113 -0
- package/dist/src/cognition/migration-v1.d.ts +7 -0
- package/dist/src/cognition/migration-v1.js +201 -0
- package/dist/src/cognition/orient.d.ts +13 -0
- package/dist/src/cognition/orient.js +94 -0
- package/dist/src/cognition/projections.d.ts +7 -0
- package/dist/src/cognition/projections.js +130 -0
- package/dist/src/cognition/readiness.d.ts +2 -0
- package/dist/src/cognition/readiness.js +81 -0
- package/dist/src/cognition/retrieval.d.ts +26 -0
- package/dist/src/cognition/retrieval.js +273 -0
- package/dist/src/cognition/runtime.d.ts +12 -0
- package/dist/src/cognition/runtime.js +295 -0
- package/dist/src/cognition/types.d.ts +170 -0
- package/dist/src/cognition/types.js +1 -0
- package/dist/src/cognition/v1-bridge.d.ts +7 -0
- package/dist/src/cognition/v1-bridge.js +82 -0
- package/dist/src/git/git-adapter.d.ts +7 -0
- package/dist/src/git/git-adapter.js +16 -6
- package/dist/src/hooks/claude-code.js +53 -0
- package/dist/src/runtime/diagnostics.js +6 -0
- package/dist/src/runtime/knowledge.d.ts +2 -0
- package/dist/src/runtime/knowledge.js +68 -0
- package/dist/src/runtime/notes.js +8 -1
- package/dist/src/runtime/runtime.d.ts +1 -0
- package/dist/src/runtime/runtime.js +28 -0
- package/dist/src/runtime/types.d.ts +19 -0
- package/dist/src/storage/layout.d.ts +19 -0
- package/dist/src/storage/layout.js +57 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# Context Runtime
|
|
2
2
|
|
|
3
|
-
`fluffy-context`
|
|
3
|
+
`fluffy-context` 正在从面向 AI 编程会话的本地 Context Runtime CLI,演进为 **Agent 无关的本地 Knowledge Runtime**。它负责自动捕获开发过程中产生的事件、候选共识、问题、不可达路径和分支工作状态,并向任意 Agent 注入有界、可验证的业务认知。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Git 仍然负责代码和真实文件变更,Issue 系统仍然负责任务,项目文档仍然负责设计;`fluffy-context` 补齐的是持续演进的项目认知层。最终架构是 `project → knowledge runtime → LLM`:CLI、MCP、Claude Hook 和未来 IDE/Agent 集成只是事件入口与信息注入适配器,而不是认知本体。
|
|
6
|
+
|
|
7
|
+
0.6.0 在 branch-aware 注入闭环上补齐了可治理的确定性检索:迁移并启用 Runtime 后,Orient 从 v2 journal 的可重建索引检索已验证、适用的 Knowledge 与 Deadend,并返回有界的选择、排除与截断说明。v1 的 checkpoint、Note、Knowledge 与 Deadend 写入仍会最佳努力镜像为 v2 事件;Runtime 也会协调检测到的外部 v1 变更。自动产物默认仍仅进入 Candidate,Verified Knowledge 仍需治理确认。
|
|
8
|
+
|
|
9
|
+
## 版本演进
|
|
10
|
+
|
|
11
|
+
| 版本 | 主要能力 | 关键特点 |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| 0.1.0 | 本地 Context CLI、baseline/patch Snapshot、resume、Note、Candidate Knowledge/Deadend | Git 继续作为代码权威;Context 只保存有界任务状态,不复制项目文件;checkpoint 显式触发且支持 no-op、限流与敏感路径过滤。 |
|
|
14
|
+
| 0.2.0 | 确定性 Knowledge Discovery、Agent TypeScript API、可安装 Skill | Candidate 与 Verified 分离;默认只注入已验证共识;不依赖向量库、模型或远端服务。 |
|
|
15
|
+
| 0.3.x | MCP `context_orient`、Claude Code Hook 与集成配置生成 | 通过 stdio MCP 和 fail-open Hook 降低 Agent 主动调用成本;集成安装仅在 `--apply` 时写入项目配置。 |
|
|
16
|
+
| 0.4.0 | v2 事件 journal、v1 显式迁移、可重建投影、本地 Runtime、Git 观察 | v2 与 v1 并存;NDJSON hash chain 是 v2 权威;Runtime 仅监听 loopback 并记录分支、ref 与 HEAD 元数据。 |
|
|
17
|
+
| 0.5.0 | branch-aware Orient、v1 写入镜像、Runtime 协调、宿主事件捕获 | Context 按当前 worktree/ref 选择,避免跨分支任务状态泄漏;稳定宿主 ID 才会写入限长、脱敏后的 session/prompt intent。 |
|
|
18
|
+
| 0.6.0 | journal 派生的确定性检索、治理终态、可解释 Orient、显式使用反馈 | 启用 v2 后直接从可重建索引检索已验证且适用的共识;使用反馈只影响稳定排序,绝不自动验证或提升 Candidate。 |
|
|
19
|
+
|
|
20
|
+
所有版本均保持本地优先、显式治理与 fail-open 集成原则。v1 数据不会在迁移过程中被重写;当 v2 未启用、不可用、损坏或尚未同步时,读取接口会回退到兼容的 v1 行为。
|
|
6
21
|
|
|
7
22
|
## 适用场景
|
|
8
23
|
|
|
@@ -91,11 +106,48 @@ ctx note add "测试环境缺少回调凭据" --kind problem --context <context-
|
|
|
91
106
|
ctx activity --context <context-id> --open
|
|
92
107
|
ctx knowledge discover "支付回调"
|
|
93
108
|
ctx knowledge verify <knowledge-id>
|
|
109
|
+
ctx knowledge deprecate <knowledge-id> --reason "已被新的结论取代"
|
|
94
110
|
ctx deadend verify <deadend-id>
|
|
111
|
+
ctx deadend obsolete <deadend-id> --reason "当前架构已不再适用"
|
|
112
|
+
ctx feedback use knowledge <knowledge-id> --event-id <stable-id>
|
|
113
|
+
ctx journal verify
|
|
114
|
+
ctx migrate v1 --dry-run
|
|
115
|
+
ctx migrate v1 --apply
|
|
116
|
+
ctx migrate verify
|
|
117
|
+
ctx runtime enable
|
|
118
|
+
ctx runtime start
|
|
119
|
+
ctx runtime status
|
|
120
|
+
ctx runtime stop
|
|
95
121
|
ctx status
|
|
96
122
|
ctx doctor
|
|
97
123
|
```
|
|
98
124
|
|
|
125
|
+
### v2 迁移与本地 Runtime
|
|
126
|
+
|
|
127
|
+
v2 与既有 `.context/` 数据并存,迁移绝不会重写或修复 v1 文件。先执行只读检查,再显式导入并验证投影一致性:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
ctx migrate v1 --dry-run
|
|
131
|
+
ctx migrate v1 --apply
|
|
132
|
+
ctx migrate verify
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
只有完成迁移后才可启用 Runtime:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
ctx runtime enable
|
|
139
|
+
ctx runtime start
|
|
140
|
+
ctx runtime status
|
|
141
|
+
ctx runtime stop
|
|
142
|
+
ctx runtime disable
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Runtime 是单项目、按需启动的本地守护进程。它只绑定 `127.0.0.1` 的动态端口,并要求存储在项目本地 `.context/v2/runtime/secret` 中的能力令牌;令牌不会显示在 CLI 状态输出或写入事件日志。启动后,Runtime 持续观察当前 worktree 的 Git 状态:初始状态会记录 `git.state.observed`,分支/ref 切换记录 `git.branch.changed`,HEAD 更新记录 `git.head.changed`。这些事件会重建 branch workspace 投影,但不会自动创建 checkpoint 或将候选信息提升为已验证共识。
|
|
146
|
+
|
|
147
|
+
完成迁移并显式 `ctx runtime enable` 后,`ctx orient`、MCP `context_orient` 与 Claude Hook 会优先选择当前 worktree/ref 的 v2 workspace;它不会借用其他分支的 Context。v2 可用时,Knowledge 与 Deadend 由 journal 重放出的确定性倒排索引直接检索,默认仅返回已验证且适用的记录;无匹配 workspace、v2 状态不可用、journal 损坏、v1 尚未同步或 Runtime 已禁用时,接口会安全回退到既有 v1 选择逻辑。Runtime 停止时仍可读取已验证的 v2 状态,但运行中的 Runtime 负责持续 Git 观察和 v1 写入镜像。
|
|
148
|
+
|
|
149
|
+
Claude Hook 仅在宿主提供稳定的 session 与 event 标识时记录 `host.session.started` 或 `host.prompt.submitted`。提示只保存按 `promptIntentMaxChars` 截断并经过敏感值脱敏后的 intent,绝不保存原始提示;缺少稳定标识时不写入事件。默认 Hook 不启动守护进程;将 v2 policy 的 `allowHookStartup` 设为 `true` 后,SessionStart 才会在严格超时内尽力启动 Runtime,失败仍会 fail open。UserPromptSubmit 不会启动 Runtime。
|
|
150
|
+
|
|
99
151
|
### `ctx checkpoint` 和 `ctx resume`
|
|
100
152
|
|
|
101
153
|
第一次 checkpoint 创建 baseline,后续变化创建 patch。相同内容返回 `no_change`;短时间内的变化可能返回 `rate_limited`,应在完成更多阶段工作后再保存。`ctx resume` 返回轻量摘要和按需使用的完整 details,并会保留其既有的 `lastUsedAt` 更新语义。
|
|
@@ -106,7 +158,11 @@ ctx doctor
|
|
|
106
158
|
|
|
107
159
|
### Knowledge 与 Deadend
|
|
108
160
|
|
|
109
|
-
`ctx learn` 和 `ctx deadend` 默认创建 `candidate` 项。普通发现与 `ctx orient` 只使用已验证项目共识;候选项需要通过 `ctx knowledge verify` 或 `ctx deadend verify` 显式确认,或以 `--all`
|
|
161
|
+
`ctx learn` 和 `ctx deadend` 默认创建 `candidate` 项。普通发现与 `ctx orient` 只使用已验证项目共识;候选项需要通过 `ctx knowledge verify` 或 `ctx deadend verify` 显式确认,或以 `--all` 审查。验证后的 Knowledge 可用 `ctx knowledge deprecate` 退役,候选项可用 `ctx knowledge reject` 拒绝;验证后的 Deadend 可用 `ctx deadend obsolete` 退役,候选项可用 `ctx deadend reject` 拒绝。终态记录保留证据与治理原因,但不会被默认注入。
|
|
162
|
+
|
|
163
|
+
新记录默认具有 `global` 适用性。需要限制到当前 worktree/ref 时,可使用 `--branch-mode exact`;`--paths` 只保存精确项目相对路径约束,当前版本不会推断目录前缀、Git 祖先关系、依赖关系或语义相似路径。`ctx orient` 的 `explanation` 字段会有界地给出 v2/v1 选择方式、排除计数和各类别截断原因。
|
|
164
|
+
|
|
165
|
+
`ctx feedback use knowledge|deadend ... --event-id <stable-id>` 只记录一个幂等的 v2 使用信号。它只作为同等词法相关性结果的确定性排序因素,永不改变验证状态、confidence、evidence 或适用性;Orient、MCP 与 Hook 不会隐式写入该信号。
|
|
110
166
|
|
|
111
167
|
```bash
|
|
112
168
|
ctx learn "订单取消后不能再次进入支付中状态" --scope project
|
|
@@ -187,6 +243,6 @@ npm pack --dry-run --json
|
|
|
187
243
|
|
|
188
244
|
## 当前范围
|
|
189
245
|
|
|
190
|
-
0.
|
|
246
|
+
0.6.0 聚焦单项目、本地优先、可治理的 branch-aware Knowledge Runtime MVP:有界且可解释的 Orient、显式 checkpoint、journal 派生的确定性 Knowledge/Deadend 检索、终态治理、显式使用反馈、MCP `context_orient`、可选 Claude Code Hook,以及 v1 到 v2 的显式事件日志迁移和 Git-aware 本地 Runtime。完成迁移并启用 Runtime 后,Orient、MCP 与 Hook 使用当前 worktree/ref 的 workspace 选择分支正确的 Context,并在 v2 不可用时保持 v1 回退;Runtime 负责可靠捕获生命周期、Git 元数据与后续 v1 写入镜像。带稳定宿主标识的 Hook 事件仅以脱敏、限长 intent 进入 journal。
|
|
191
247
|
|
|
192
|
-
远程同步、多人协作、复杂语义检索、自动模型总结、自动 checkpoint、Context merge
|
|
248
|
+
远程同步、多人协作、复杂语义检索、自动模型总结、自动 checkpoint、Context merge、自动验证候选共识,以及更多状态变更型 MCP 工具不属于当前版本范围。
|
package/dist/src/agent/api.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { AgentLoadOptions, AgentLoadResult, AgentSaveResult, CheckpointInput, ContextSearchOptions, ContextSearchResult } from '../runtime/types.js';
|
|
2
|
-
import type { ContextOrientOptions, ContextOrientResult } from './types.js';
|
|
2
|
+
import type { ContextOrientOptions, ContextOrientResult, ContextUseInput } from './types.js';
|
|
3
3
|
export declare function saveContext(startPath: string | undefined, input: CheckpointInput, options?: {
|
|
4
4
|
minSaveIntervalMs?: number;
|
|
5
5
|
}): Promise<AgentSaveResult>;
|
|
6
6
|
export declare function loadContext(startPath: string | undefined, options?: AgentLoadOptions): Promise<AgentLoadResult>;
|
|
7
7
|
export declare function contextOrient(startPath: string | undefined, options?: ContextOrientOptions): Promise<ContextOrientResult>;
|
|
8
|
+
export declare function recordContextUse(startPath: string | undefined, input: ContextUseInput): Promise<{
|
|
9
|
+
recorded: boolean;
|
|
10
|
+
eventId: string;
|
|
11
|
+
}>;
|
|
8
12
|
export declare function searchContext(startPath: string | undefined, query: string, options?: ContextSearchOptions): Promise<ContextSearchResult>;
|
package/dist/src/agent/api.js
CHANGED
|
@@ -2,7 +2,10 @@ import { readdir } from 'node:fs/promises';
|
|
|
2
2
|
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
3
3
|
import { contextsRoot, contextMetadataPath } from '../storage/layout.js';
|
|
4
4
|
import { isRecord, readJson } from '../storage/json-store.js';
|
|
5
|
-
import { checkpoint, rebuildSnapshot, resume } from '../runtime/runtime.js';
|
|
5
|
+
import { checkpoint, rebuildSnapshot, resume, resumeSnapshot } from '../runtime/runtime.js';
|
|
6
|
+
import { recordUse } from '../cognition/feedback.js';
|
|
7
|
+
import { selectV2Orientation } from '../cognition/orient.js';
|
|
8
|
+
import { discoverV2Deadends, discoverV2Knowledge } from '../cognition/retrieval.js';
|
|
6
9
|
import { discoverDeadends, discoverKnowledge, listDeadends } from '../runtime/knowledge.js';
|
|
7
10
|
import { listNotes } from '../runtime/notes.js';
|
|
8
11
|
function normalized(value) {
|
|
@@ -157,7 +160,10 @@ export async function contextOrient(startPath, options = {}) {
|
|
|
157
160
|
throw new Error('context orient query must not be empty');
|
|
158
161
|
let loaded;
|
|
159
162
|
try {
|
|
160
|
-
|
|
163
|
+
const v2 = await selectV2Orientation(projectRoot, options.contextId);
|
|
164
|
+
loaded = v2
|
|
165
|
+
? await resumeSnapshot(projectRoot, v2.contextId, v2.snapshotId, options.maxChars ?? 4000)
|
|
166
|
+
: await resume(projectRoot, options.contextId, options.maxChars ?? 4000, { touchLastUsedAt: false });
|
|
161
167
|
}
|
|
162
168
|
catch (error) {
|
|
163
169
|
if (error instanceof Error && error.message === 'no active context found') {
|
|
@@ -172,26 +178,43 @@ export async function contextOrient(startPath, options = {}) {
|
|
|
172
178
|
deadends: emptyDeadends(query ?? ''),
|
|
173
179
|
notes: [],
|
|
174
180
|
truncated: false,
|
|
181
|
+
explanation: {
|
|
182
|
+
mode: 'v1-fallback',
|
|
183
|
+
selection: 'v1',
|
|
184
|
+
retrieval: { knowledge: {}, deadends: {} },
|
|
185
|
+
truncation: { summary: false, knowledge: false, deadends: false, notes: false },
|
|
186
|
+
},
|
|
175
187
|
};
|
|
176
188
|
}
|
|
177
189
|
throw error;
|
|
178
190
|
}
|
|
179
|
-
const
|
|
191
|
+
const v2 = await selectV2Orientation(projectRoot, options.contextId);
|
|
192
|
+
const notes = (await listNotes(projectRoot, {
|
|
180
193
|
contextId: loaded.context.id,
|
|
181
194
|
openOnly: true,
|
|
182
|
-
limit: options.noteLimit ?? 20,
|
|
195
|
+
limit: v2 ? Number.MAX_SAFE_INTEGER : options.noteLimit ?? 20,
|
|
196
|
+
maxChars: options.maxChars ?? 4000,
|
|
197
|
+
})).filter((note) => !v2 || v2.noteIds.has(note.noteId)).slice(0, options.noteLimit ?? 20);
|
|
198
|
+
const v2Knowledge = query === null || !v2 ? null : discoverV2Knowledge(v2.retrieval, query, v2.currentGit, {
|
|
199
|
+
scope: options.scope,
|
|
200
|
+
limit: options.knowledgeLimit ?? 10,
|
|
201
|
+
maxChars: options.maxChars ?? 4000,
|
|
202
|
+
});
|
|
203
|
+
const v2Deadends = query === null || !v2 ? null : discoverV2Deadends(v2.retrieval, query, v2.currentGit, {
|
|
204
|
+
scope: options.scope,
|
|
205
|
+
limit: options.deadendLimit ?? 10,
|
|
183
206
|
maxChars: options.maxChars ?? 4000,
|
|
184
207
|
});
|
|
185
|
-
const
|
|
208
|
+
const applicableKnowledge = query === null
|
|
186
209
|
? emptyKnowledge('')
|
|
187
|
-
: await discoverKnowledge(projectRoot, query, {
|
|
210
|
+
: v2Knowledge?.result ?? await discoverKnowledge(projectRoot, query, {
|
|
188
211
|
scope: options.scope,
|
|
189
212
|
limit: options.knowledgeLimit ?? 10,
|
|
190
213
|
maxChars: options.maxChars ?? 4000,
|
|
191
214
|
});
|
|
192
|
-
const
|
|
215
|
+
const applicableDeadends = query === null
|
|
193
216
|
? emptyDeadends('')
|
|
194
|
-
: await discoverDeadends(projectRoot, query, {
|
|
217
|
+
: v2Deadends?.result ?? await discoverDeadends(projectRoot, query, {
|
|
195
218
|
scope: options.scope,
|
|
196
219
|
limit: options.deadendLimit ?? 10,
|
|
197
220
|
maxChars: options.maxChars ?? 4000,
|
|
@@ -214,8 +237,8 @@ export async function contextOrient(startPath, options = {}) {
|
|
|
214
237
|
|| textLength(resumeSummary.decisions) < textLength(loaded.resumeSummary.decisions)
|
|
215
238
|
|| textLength(resumeSummary.risks) < textLength(loaded.resumeSummary.risks)
|
|
216
239
|
|| textLength(resumeSummary.relatedFiles) < textLength(loaded.resumeSummary.relatedFiles);
|
|
217
|
-
const limitedKnowledgeResult = limitKnowledge(
|
|
218
|
-
const limitedDeadendResult = limitDeadends(
|
|
240
|
+
const limitedKnowledgeResult = limitKnowledge(applicableKnowledge, budget);
|
|
241
|
+
const limitedDeadendResult = limitDeadends(applicableDeadends, budget);
|
|
219
242
|
const limitedNotesResult = limitNotes(notes, budget);
|
|
220
243
|
return {
|
|
221
244
|
status: 'ready',
|
|
@@ -241,8 +264,25 @@ export async function contextOrient(startPath, options = {}) {
|
|
|
241
264
|
deadends: limitedDeadendResult.result,
|
|
242
265
|
notes: limitedNotesResult.notes,
|
|
243
266
|
truncated: resumeTruncated || limitedKnowledgeResult.truncated || limitedDeadendResult.truncated || limitedNotesResult.truncated,
|
|
267
|
+
explanation: {
|
|
268
|
+
mode: v2 ? 'v2' : 'v1-fallback',
|
|
269
|
+
selection: v2?.selection ?? 'v1',
|
|
270
|
+
retrieval: {
|
|
271
|
+
knowledge: v2Knowledge?.exclusions ?? {},
|
|
272
|
+
deadends: v2Deadends?.exclusions ?? {},
|
|
273
|
+
},
|
|
274
|
+
truncation: {
|
|
275
|
+
summary: resumeTruncated,
|
|
276
|
+
knowledge: limitedKnowledgeResult.truncated,
|
|
277
|
+
deadends: limitedDeadendResult.truncated,
|
|
278
|
+
notes: limitedNotesResult.truncated,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
244
281
|
};
|
|
245
282
|
}
|
|
283
|
+
export async function recordContextUse(startPath, input) {
|
|
284
|
+
return recordUse(startPath, input);
|
|
285
|
+
}
|
|
246
286
|
export async function searchContext(startPath, query, options = {}) {
|
|
247
287
|
const projectRoot = await resolveProjectRoot(startPath);
|
|
248
288
|
const cleanQuery = query.trim();
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './api.js';
|
|
2
|
-
export type { ContextOrientContext, ContextOrientNoContextResult, ContextOrientOptions, ContextOrientReadyResult, ContextOrientResult, ContextOrientSnapshot, } from './types.js';
|
|
2
|
+
export type { ContextOrientContext, ContextUseInput, ContextOrientExplanation, ContextOrientNoContextResult, ContextOrientOptions, ContextOrientReadyResult, ContextOrientResult, ContextOrientSnapshot, } from './types.js';
|
|
3
3
|
export type { AgentLoadOptions, AgentLoadResult, AgentSaveResult, ContextSearchHit, ContextSearchOptions, ContextSearchResult, } from '../runtime/types.js';
|
|
@@ -23,6 +23,25 @@ export interface ContextOrientSnapshot {
|
|
|
23
23
|
branch: string | null;
|
|
24
24
|
commit: string | null;
|
|
25
25
|
}
|
|
26
|
+
export interface ContextUseInput {
|
|
27
|
+
entity: 'knowledge' | 'deadend';
|
|
28
|
+
entityId: string;
|
|
29
|
+
eventId: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ContextOrientExplanation {
|
|
32
|
+
mode: 'v2' | 'v1-fallback';
|
|
33
|
+
selection: 'requested-context' | 'exact-worktree-ref' | 'legacy-ref-branch' | 'v1';
|
|
34
|
+
retrieval: {
|
|
35
|
+
knowledge: Record<'status_not_verified' | 'applicability_mismatch' | 'scope_mismatch' | 'query_no_match' | 'result_limit', number> | {};
|
|
36
|
+
deadends: Record<'status_not_verified' | 'applicability_mismatch' | 'scope_mismatch' | 'query_no_match' | 'result_limit', number> | {};
|
|
37
|
+
};
|
|
38
|
+
truncation: {
|
|
39
|
+
summary: boolean;
|
|
40
|
+
knowledge: boolean;
|
|
41
|
+
deadends: boolean;
|
|
42
|
+
notes: boolean;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
26
45
|
export interface ContextOrientReadyResult {
|
|
27
46
|
status: 'ready';
|
|
28
47
|
projectRoot: string;
|
|
@@ -34,6 +53,7 @@ export interface ContextOrientReadyResult {
|
|
|
34
53
|
deadends: DeadendDiscoveryResult;
|
|
35
54
|
notes: Note[];
|
|
36
55
|
truncated: boolean;
|
|
56
|
+
explanation: ContextOrientExplanation;
|
|
37
57
|
}
|
|
38
58
|
export interface ContextOrientNoContextResult {
|
|
39
59
|
status: 'no_context';
|
|
@@ -46,5 +66,6 @@ export interface ContextOrientNoContextResult {
|
|
|
46
66
|
deadends: DeadendDiscoveryResult;
|
|
47
67
|
notes: Note[];
|
|
48
68
|
truncated: false;
|
|
69
|
+
explanation: ContextOrientExplanation;
|
|
49
70
|
}
|
|
50
71
|
export type ContextOrientResult = ContextOrientReadyResult | ContextOrientNoContextResult;
|
package/dist/src/cli/main.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { checkpoint, resume } from '../runtime/runtime.js';
|
|
3
3
|
import { contextOrient } from '../agent/api.js';
|
|
4
|
+
import { importV1, verifyV1Import } from '../cognition/migration-v1.js';
|
|
5
|
+
import { recordUse } from '../cognition/feedback.js';
|
|
6
|
+
import { readJournal } from '../cognition/journal.js';
|
|
7
|
+
import { projectEvents } from '../cognition/projections.js';
|
|
8
|
+
import { disableRuntime, enableRuntime, runtimeStatus, serveRuntime, startRuntime, stopRuntime } from '../cognition/runtime.js';
|
|
9
|
+
import { migrationReadiness } from '../cognition/readiness.js';
|
|
4
10
|
import { runClaudeCodeHook } from '../hooks/claude-code.js';
|
|
11
|
+
import { readGitWorktreeState } from '../git/git-adapter.js';
|
|
5
12
|
import { inspectClaudeIntegration, installClaudeIntegration } from '../integrations/claude-code.js';
|
|
6
13
|
import { doctor, status } from '../runtime/diagnostics.js';
|
|
7
14
|
import { initProject } from '../runtime/init.js';
|
|
8
|
-
import { discoverKnowledge, learnKnowledge, listKnowledge, verifyKnowledge, recordDeadend, listDeadends, verifyDeadend } from '../runtime/knowledge.js';
|
|
15
|
+
import { discoverKnowledge, learnKnowledge, listKnowledge, verifyKnowledge, recordDeadend, listDeadends, verifyDeadend, transitionKnowledge, transitionDeadend } from '../runtime/knowledge.js';
|
|
9
16
|
import { addNote, listActivity, listNotes } from '../runtime/notes.js';
|
|
10
17
|
import { VERSION } from '../version.js';
|
|
11
18
|
function option(args, name) {
|
|
@@ -155,16 +162,23 @@ Options:
|
|
|
155
162
|
--evidence <items> Comma-separated evidence references`,
|
|
156
163
|
knowledge: `usage: ctx knowledge [--all] [--path <path>]
|
|
157
164
|
ctx knowledge verify <knowledge-id> [--path <path>]
|
|
165
|
+
ctx knowledge deprecate|reject <knowledge-id> --reason <text> [--path <path>]
|
|
158
166
|
ctx knowledge discover <query> [options]
|
|
159
167
|
|
|
160
|
-
List
|
|
168
|
+
List, govern, or discover project knowledge.
|
|
161
169
|
|
|
162
170
|
Options:
|
|
163
171
|
--all Include unverified items
|
|
164
172
|
--path <path> Project path`,
|
|
165
173
|
'knowledge verify': `usage: ctx knowledge verify <knowledge-id> [--path <path>]
|
|
166
174
|
|
|
167
|
-
Mark a knowledge item as verified.`,
|
|
175
|
+
Mark a candidate knowledge item as verified.`,
|
|
176
|
+
'knowledge deprecate': `usage: ctx knowledge deprecate <knowledge-id> --reason <text> [--path <path>]
|
|
177
|
+
|
|
178
|
+
Retire a verified knowledge item with an explicit governance reason.`,
|
|
179
|
+
'knowledge reject': `usage: ctx knowledge reject <knowledge-id> --reason <text> [--path <path>]
|
|
180
|
+
|
|
181
|
+
Reject a candidate knowledge item with an explicit governance reason.`,
|
|
168
182
|
'knowledge discover': `usage: ctx knowledge discover <query> [options]
|
|
169
183
|
|
|
170
184
|
Find matching project knowledge using deterministic lexical and alias rules.
|
|
@@ -178,8 +192,9 @@ Options:
|
|
|
178
192
|
--max-chars <number> Maximum text characters per match`,
|
|
179
193
|
deadend: `usage: ctx deadend [attempt] [options]
|
|
180
194
|
ctx deadend verify <deadend-id> [--path <path>]
|
|
195
|
+
ctx deadend obsolete|reject <deadend-id> --reason <text> [--path <path>]
|
|
181
196
|
|
|
182
|
-
Record or
|
|
197
|
+
Record or govern a candidate deadend.
|
|
183
198
|
|
|
184
199
|
Options:
|
|
185
200
|
--path <path> Project path
|
|
@@ -192,7 +207,25 @@ Options:
|
|
|
192
207
|
--evidence <items> Comma-separated evidence references`,
|
|
193
208
|
'deadend verify': `usage: ctx deadend verify <deadend-id> [--path <path>]
|
|
194
209
|
|
|
195
|
-
Mark a deadend as verified.`,
|
|
210
|
+
Mark a candidate deadend as verified.`,
|
|
211
|
+
'deadend obsolete': `usage: ctx deadend obsolete <deadend-id> --reason <text> [--path <path>]
|
|
212
|
+
|
|
213
|
+
Retire a verified deadend with an explicit governance reason.`,
|
|
214
|
+
'deadend reject': `usage: ctx deadend reject <deadend-id> --reason <text> [--path <path>]
|
|
215
|
+
|
|
216
|
+
Reject a candidate deadend with an explicit governance reason.`,
|
|
217
|
+
feedback: `usage: ctx feedback use knowledge|deadend <id> --event-id <stable-id> [--path <path>]
|
|
218
|
+
|
|
219
|
+
Record explicit use of verified v2 retrieval material.`,
|
|
220
|
+
'feedback use': `usage: ctx feedback use knowledge|deadend <id> --event-id <stable-id> [--path <path>]
|
|
221
|
+
|
|
222
|
+
Record an idempotent use signal that affects deterministic rank only.`,
|
|
223
|
+
journal: `usage: ctx journal verify [--path <path>]
|
|
224
|
+
|
|
225
|
+
Validate the v2 journal hash chain and replay projections in memory.`,
|
|
226
|
+
'journal verify': `usage: ctx journal verify [--path <path>]
|
|
227
|
+
|
|
228
|
+
Validate journal integrity without writing derived state.`,
|
|
196
229
|
deadends: `usage: ctx deadends [--all] [--path <path>]
|
|
197
230
|
|
|
198
231
|
List recorded deadends.`,
|
|
@@ -235,9 +268,51 @@ Options:
|
|
|
235
268
|
--open Only open notes
|
|
236
269
|
--limit <number> Maximum items (default: 50)
|
|
237
270
|
--max-chars <number> Maximum message characters`,
|
|
271
|
+
migrate: `usage: ctx migrate v1 --dry-run|--apply [--path <path>]
|
|
272
|
+
ctx migrate verify [--path <path>]
|
|
273
|
+
|
|
274
|
+
Inspect, explicitly import, or verify v1 Context data for the event-journal runtime.`,
|
|
275
|
+
'migrate v1': `usage: ctx migrate v1 --dry-run|--apply [--path <path>]
|
|
276
|
+
|
|
277
|
+
Validate v1 Context, Snapshot, Note, Knowledge, and Deadend data for v2 migration.
|
|
278
|
+
|
|
279
|
+
Options:
|
|
280
|
+
--dry-run Inspect only; no v2 data is created
|
|
281
|
+
--apply Explicitly append immutable v1 provenance events
|
|
282
|
+
--path <path> Project path`,
|
|
283
|
+
'migrate verify': `usage: ctx migrate verify [--path <path>]
|
|
284
|
+
|
|
285
|
+
Rebuild v2 projections and verify imported v1 parity.
|
|
286
|
+
|
|
287
|
+
Options:
|
|
288
|
+
--path <path> Project path`,
|
|
289
|
+
runtime: `usage: ctx runtime enable|disable|start|stop|status [--path <path>]
|
|
290
|
+
|
|
291
|
+
Control the local event-driven Knowledge Runtime after v1 migration verification.`,
|
|
292
|
+
'runtime status': `usage: ctx runtime status [--path <path>]
|
|
293
|
+
|
|
294
|
+
Show the read-only runtime policy, migration and authenticated lease status.
|
|
295
|
+
|
|
296
|
+
Options:
|
|
297
|
+
--path <path> Project path`,
|
|
298
|
+
'runtime enable': `usage: ctx runtime enable [--path <path>]
|
|
299
|
+
|
|
300
|
+
Explicitly enable the migrated local Knowledge Runtime.`,
|
|
301
|
+
'runtime disable': `usage: ctx runtime disable [--path <path>]
|
|
302
|
+
|
|
303
|
+
Stop the local runtime and disable future startup without deleting v2 history.`,
|
|
304
|
+
'runtime start': `usage: ctx runtime start [--path <path>]
|
|
305
|
+
|
|
306
|
+
Start the enabled runtime as a loopback-only background process.`,
|
|
307
|
+
'runtime stop': `usage: ctx runtime stop [--path <path>]
|
|
308
|
+
|
|
309
|
+
Stop the local runtime through authenticated loopback IPC.`,
|
|
310
|
+
'runtime serve': `usage: ctx runtime serve [--path <path>]
|
|
311
|
+
|
|
312
|
+
Internal runtime daemon entry point.`,
|
|
238
313
|
};
|
|
239
314
|
function usage() {
|
|
240
|
-
return `usage: ctx init|checkpoint|resume|orient|agent|hook|integrate|status|doctor|learn|knowledge|deadend|deadends|note|activity [options]
|
|
315
|
+
return `usage: ctx init|checkpoint|resume|orient|agent|hook|integrate|status|doctor|learn|knowledge|deadend|deadends|note|activity|migrate|runtime|feedback|journal [options]
|
|
241
316
|
|
|
242
317
|
Run \"ctx <command> --help\" for command details.`;
|
|
243
318
|
}
|
|
@@ -256,7 +331,17 @@ function printHelp(args) {
|
|
|
256
331
|
process.stdout.write(`${usage()}\n`);
|
|
257
332
|
return true;
|
|
258
333
|
}
|
|
259
|
-
const
|
|
334
|
+
const nested = (command === 'agent' && args[1] === 'serve')
|
|
335
|
+
|| (command === 'hook' && args[1] === 'claude-code')
|
|
336
|
+
|| (command === 'integrate' && args[1] === 'claude')
|
|
337
|
+
|| (command === 'knowledge' && ['verify', 'discover', 'deprecate', 'reject'].includes(args[1]))
|
|
338
|
+
|| (command === 'deadend' && ['verify', 'obsolete', 'reject'].includes(args[1]))
|
|
339
|
+
|| (command === 'feedback' && args[1] === 'use')
|
|
340
|
+
|| (command === 'journal' && args[1] === 'verify')
|
|
341
|
+
|| (command === 'note' && ['add', 'list'].includes(args[1]))
|
|
342
|
+
|| (command === 'migrate' && ['v1', 'verify'].includes(args[1]))
|
|
343
|
+
|| (command === 'runtime' && ['enable', 'disable', 'start', 'stop', 'status', 'serve'].includes(args[1]));
|
|
344
|
+
const key = nested ? `${command} ${args[1]}` : command;
|
|
260
345
|
const nestedKey = key === 'integrate claude' && (args[2] === 'inspect' || args[2] === 'install') ? `${key} ${args[2]}` : key;
|
|
261
346
|
const help = HELP[nestedKey];
|
|
262
347
|
if (!help)
|
|
@@ -370,17 +455,20 @@ async function run(args) {
|
|
|
370
455
|
print(await doctor(target));
|
|
371
456
|
return;
|
|
372
457
|
case 'learn': {
|
|
373
|
-
const valueOptions = ['--path', '--kind', '--scope', '--context', '--snapshot', '--evidence'];
|
|
458
|
+
const valueOptions = ['--path', '--kind', '--scope', '--context', '--snapshot', '--evidence', '--branch-mode', '--paths'];
|
|
374
459
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
375
460
|
const statement = positionals(args.slice(1), valueOptions);
|
|
376
461
|
if (statement.length === 0)
|
|
377
462
|
throw new Error(HELP.learn);
|
|
463
|
+
const branchMode = enumOption(args, '--branch-mode', ['global', 'exact']) ?? 'global';
|
|
464
|
+
const git = branchMode === 'exact' ? await readGitWorktreeState(target ?? process.cwd()) : null;
|
|
378
465
|
const input = {
|
|
379
466
|
kind: option(args, '--kind'),
|
|
380
467
|
scope: option(args, '--scope'),
|
|
381
468
|
sourceContextId: option(args, '--context'),
|
|
382
469
|
sourceSnapshotId: option(args, '--snapshot'),
|
|
383
470
|
evidence: listOption(args, '--evidence'),
|
|
471
|
+
applicability: { project: true, branchMode, ...(branchMode === 'exact' ? { branches: [git?.ref ?? git?.branch ?? 'detached'], sourceCommit: git?.commit ?? null } : {}), ...(listOption(args, '--paths') ? { paths: listOption(args, '--paths') } : {}) },
|
|
384
472
|
};
|
|
385
473
|
print(await learnKnowledge(target, statement.join(' '), input));
|
|
386
474
|
return;
|
|
@@ -413,6 +501,69 @@ async function run(args) {
|
|
|
413
501
|
}
|
|
414
502
|
return;
|
|
415
503
|
}
|
|
504
|
+
case 'feedback': {
|
|
505
|
+
if (args[1] !== 'use' || !['knowledge', 'deadend'].includes(args[2]))
|
|
506
|
+
throw new Error(HELP.feedback);
|
|
507
|
+
validateOptions(args.slice(3), ['--path', '--event-id'], ['--path', '--event-id']);
|
|
508
|
+
const values = positionals(args.slice(3), ['--path', '--event-id']);
|
|
509
|
+
if (values.length !== 1)
|
|
510
|
+
throw new Error(HELP['feedback use']);
|
|
511
|
+
print(await recordUse(target, { entity: args[2], entityId: values[0], eventId: option(args, '--event-id') ?? '' }));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
case 'journal': {
|
|
515
|
+
if (args[1] !== 'verify')
|
|
516
|
+
throw new Error(HELP.journal);
|
|
517
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
518
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP['journal verify']);
|
|
519
|
+
const root = target ?? process.cwd();
|
|
520
|
+
const events = await readJournal(root);
|
|
521
|
+
const projection = projectEvents(events);
|
|
522
|
+
const last = events.at(-1);
|
|
523
|
+
print({ verified: true, eventCount: events.length, cursor: { eventId: last?.eventId ?? null, hash: last?.hash ?? null }, counts: { contexts: Object.keys(projection.contexts).length, notes: Object.keys(projection.notes).length, knowledge: Object.keys(projection.knowledge).length, deadends: Object.keys(projection.deadends).length } });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
case 'migrate': {
|
|
527
|
+
if (args[1] === 'v1') {
|
|
528
|
+
validateOptions(args.slice(2), ['--dry-run', '--apply', '--path'], ['--path']);
|
|
529
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP['migrate v1']);
|
|
530
|
+
if (args.includes('--dry-run') === args.includes('--apply'))
|
|
531
|
+
throw new Error(HELP['migrate v1']);
|
|
532
|
+
print(args.includes('--apply') ? await importV1(target) : await migrationReadiness(target));
|
|
533
|
+
}
|
|
534
|
+
else if (args[1] === 'verify') {
|
|
535
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
536
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP['migrate verify']);
|
|
537
|
+
print(await verifyV1Import(target));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
throw new Error(HELP.migrate);
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
case 'runtime': {
|
|
545
|
+
if (!['enable', 'disable', 'start', 'stop', 'status', 'serve'].includes(args[1]))
|
|
546
|
+
throw new Error(HELP.runtime);
|
|
547
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
548
|
+
validatePositionals(positionals(args.slice(2), ['--path']), 0, HELP[`runtime ${args[1]}`]);
|
|
549
|
+
if (args[1] === 'enable')
|
|
550
|
+
print(await enableRuntime(target));
|
|
551
|
+
else if (args[1] === 'disable')
|
|
552
|
+
print(await disableRuntime(target));
|
|
553
|
+
else if (args[1] === 'start')
|
|
554
|
+
print(await startRuntime(target));
|
|
555
|
+
else if (args[1] === 'stop')
|
|
556
|
+
print(await stopRuntime(target));
|
|
557
|
+
else if (args[1] === 'status')
|
|
558
|
+
print(await runtimeStatus(target));
|
|
559
|
+
else {
|
|
560
|
+
const runtime = await serveRuntime(target);
|
|
561
|
+
process.once('SIGINT', () => { void runtime.close('signal'); });
|
|
562
|
+
process.once('SIGTERM', () => { void runtime.close('signal'); });
|
|
563
|
+
await new Promise(() => undefined);
|
|
564
|
+
}
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
416
567
|
case 'activity': {
|
|
417
568
|
const valueOptions = ['--path', '--context', '--since', '--limit', '--max-chars'];
|
|
418
569
|
validateOptions(args.slice(1), [...valueOptions, '--open'], valueOptions);
|
|
@@ -451,6 +602,13 @@ async function run(args) {
|
|
|
451
602
|
throw new Error(HELP['knowledge verify']);
|
|
452
603
|
print(await verifyKnowledge(target, values[0]));
|
|
453
604
|
}
|
|
605
|
+
else if (args[1] === 'deprecate' || args[1] === 'reject') {
|
|
606
|
+
validateOptions(args.slice(2), ['--path', '--reason'], ['--path', '--reason']);
|
|
607
|
+
const values = positionals(args.slice(2), ['--path', '--reason']);
|
|
608
|
+
if (values.length !== 1)
|
|
609
|
+
throw new Error(HELP[`knowledge ${args[1]}`]);
|
|
610
|
+
print(await transitionKnowledge(target, values[0], args[1] === 'deprecate' ? 'deprecated' : 'rejected', option(args, '--reason') ?? ''));
|
|
611
|
+
}
|
|
454
612
|
else {
|
|
455
613
|
validateOptions(args.slice(1), ['--all', '--path'], ['--path']);
|
|
456
614
|
validatePositionals(positionals(args.slice(1), ['--path']), 0, HELP.knowledge);
|
|
@@ -466,16 +624,26 @@ async function run(args) {
|
|
|
466
624
|
throw new Error(HELP['deadend verify']);
|
|
467
625
|
print(await verifyDeadend(target, values[0]));
|
|
468
626
|
}
|
|
627
|
+
else if (args[1] === 'obsolete' || args[1] === 'reject') {
|
|
628
|
+
validateOptions(args.slice(2), ['--path', '--reason'], ['--path', '--reason']);
|
|
629
|
+
const values = positionals(args.slice(2), ['--path', '--reason']);
|
|
630
|
+
if (values.length !== 1)
|
|
631
|
+
throw new Error(HELP[`deadend ${args[1]}`]);
|
|
632
|
+
print(await transitionDeadend(target, values[0], args[1] === 'obsolete' ? 'obsolete' : 'rejected', option(args, '--reason') ?? ''));
|
|
633
|
+
}
|
|
469
634
|
else {
|
|
470
|
-
const valueOptions = ['--path', '--attempt', '--reason', '-m', '--scope', '--context', '--snapshot', '--evidence'];
|
|
635
|
+
const valueOptions = ['--path', '--attempt', '--reason', '-m', '--scope', '--context', '--snapshot', '--evidence', '--branch-mode', '--paths'];
|
|
471
636
|
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
472
637
|
const values = positionals(args.slice(1), valueOptions);
|
|
473
638
|
validatePositionals(values, 1, HELP.deadend);
|
|
639
|
+
const branchMode = enumOption(args, '--branch-mode', ['global', 'exact']) ?? 'global';
|
|
640
|
+
const git = branchMode === 'exact' ? await readGitWorktreeState(target ?? process.cwd()) : null;
|
|
474
641
|
const input = {
|
|
475
642
|
scope: option(args, '--scope'),
|
|
476
643
|
sourceContextId: option(args, '--context'),
|
|
477
644
|
sourceSnapshotId: option(args, '--snapshot'),
|
|
478
645
|
evidence: listOption(args, '--evidence'),
|
|
646
|
+
applicability: { project: true, branchMode, ...(branchMode === 'exact' ? { branches: [git?.ref ?? git?.branch ?? 'detached'], sourceCommit: git?.commit ?? null } : {}), ...(listOption(args, '--paths') ? { paths: listOption(args, '--paths') } : {}) },
|
|
479
647
|
};
|
|
480
648
|
print(await recordDeadend(target, option(args, '--attempt') ?? values[0], option(args, '--reason') ?? option(args, '-m') ?? '', input));
|
|
481
649
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
2
|
+
import { readGitWorktreeState } from '../git/git-adapter.js';
|
|
3
|
+
import { appendEvent, readJournal } from './journal.js';
|
|
4
|
+
import { v2Enabled } from './orient.js';
|
|
5
|
+
import { rebuildProjections } from './projections.js';
|
|
6
|
+
import { buildRetrievalProjection } from './retrieval.js';
|
|
7
|
+
export async function recordUse(startPath, input) {
|
|
8
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
9
|
+
if (!input.eventId.trim())
|
|
10
|
+
throw new Error('feedback event ID must not be empty');
|
|
11
|
+
if (!await v2Enabled(projectRoot))
|
|
12
|
+
throw new Error('record use requires an enabled v2 runtime');
|
|
13
|
+
const events = await readJournal(projectRoot);
|
|
14
|
+
const entry = buildRetrievalProjection(events).entries[`${input.entity}:${input.entityId}`];
|
|
15
|
+
if (!entry)
|
|
16
|
+
throw new Error(`${input.entity} not found in v2 retrieval: ${input.entityId}`);
|
|
17
|
+
if (entry.status !== 'verified')
|
|
18
|
+
throw new Error(`record use requires verified ${input.entity}: ${input.entityId}`);
|
|
19
|
+
const git = await readGitWorktreeState(projectRoot);
|
|
20
|
+
const result = await appendEvent(projectRoot, {
|
|
21
|
+
type: 'record.used',
|
|
22
|
+
source: { kind: 'cli' },
|
|
23
|
+
idempotencyKey: `record-use:${input.entity}:${input.entityId}:${input.eventId.trim()}`,
|
|
24
|
+
git: { worktreeId: git.worktreeId, branch: git.branch, ref: git.ref, head: git.commit },
|
|
25
|
+
payload: { feedbackId: input.eventId.trim(), entity: input.entity, entityId: input.entityId, outcome: 'used' },
|
|
26
|
+
});
|
|
27
|
+
if (result.appended)
|
|
28
|
+
await rebuildProjections(projectRoot);
|
|
29
|
+
return { recorded: result.appended, eventId: result.event.eventId };
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type GitWorktreeState } from '../git/git-adapter.js';
|
|
2
|
+
export declare class GitObserver {
|
|
3
|
+
private readonly projectRoot;
|
|
4
|
+
private previous;
|
|
5
|
+
private pending;
|
|
6
|
+
constructor(projectRoot: string);
|
|
7
|
+
observeNow(): Promise<{
|
|
8
|
+
state: GitWorktreeState;
|
|
9
|
+
changed: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
drain(): Promise<void>;
|
|
12
|
+
private observe;
|
|
13
|
+
}
|