fluffy-context 0.1.0 → 0.3.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/LICENSE +21 -0
- package/README.md +86 -136
- package/dist/src/agent/api.d.ts +8 -0
- package/dist/src/agent/api.js +285 -0
- package/dist/src/agent/index.d.ts +3 -0
- package/dist/src/agent/index.js +1 -0
- package/dist/src/agent/types.d.ts +50 -0
- package/dist/src/agent/types.js +1 -0
- package/dist/src/capture/context-filter.d.ts +7 -0
- package/dist/src/cli/main.d.ts +2 -0
- package/dist/src/cli/main.js +382 -30
- package/dist/src/git/git-adapter.d.ts +2 -0
- package/dist/src/hooks/claude-code.d.ts +1 -0
- package/dist/src/hooks/claude-code.js +84 -0
- package/dist/src/integrations/claude-code.d.ts +19 -0
- package/dist/src/integrations/claude-code.js +122 -0
- package/dist/src/mcp/main.d.ts +1 -0
- package/dist/src/mcp/main.js +5 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.js +40 -0
- package/dist/src/project/project-resolver.d.ts +1 -0
- package/dist/src/runtime/diagnostics.d.ts +6 -0
- package/dist/src/runtime/diagnostics.js +34 -5
- package/dist/src/runtime/init.d.ts +7 -0
- package/dist/src/runtime/knowledge.d.ts +9 -0
- package/dist/src/runtime/knowledge.js +179 -12
- package/dist/src/runtime/notes.d.ts +6 -0
- package/dist/src/runtime/notes.js +173 -0
- package/dist/src/runtime/runtime.d.ts +11 -0
- package/dist/src/runtime/runtime.js +48 -18
- package/dist/src/runtime/types.d.ts +294 -0
- package/dist/src/storage/atomic-write.d.ts +1 -0
- package/dist/src/storage/json-store.d.ts +6 -0
- package/dist/src/storage/layout.d.ts +13 -0
- package/dist/src/storage/layout.js +3 -0
- package/dist/src/storage/lock.d.ts +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +1 -0
- package/package.json +48 -11
- package/skills/fluffy-context/SKILL.md +345 -0
|
@@ -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 {};
|
package/dist/src/cli/main.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { checkpoint, resume } from '../runtime/runtime.js';
|
|
3
|
+
import { contextOrient } from '../agent/api.js';
|
|
4
|
+
import { runClaudeCodeHook } from '../hooks/claude-code.js';
|
|
5
|
+
import { inspectClaudeIntegration, installClaudeIntegration } from '../integrations/claude-code.js';
|
|
3
6
|
import { doctor, status } from '../runtime/diagnostics.js';
|
|
4
7
|
import { initProject } from '../runtime/init.js';
|
|
5
|
-
import { learnKnowledge, listKnowledge, verifyKnowledge, recordDeadend, listDeadends, verifyDeadend } from '../runtime/knowledge.js';
|
|
8
|
+
import { discoverKnowledge, learnKnowledge, listKnowledge, verifyKnowledge, recordDeadend, listDeadends, verifyDeadend } from '../runtime/knowledge.js';
|
|
9
|
+
import { addNote, listActivity, listNotes } from '../runtime/notes.js';
|
|
10
|
+
import { VERSION } from '../version.js';
|
|
6
11
|
function option(args, name) {
|
|
7
12
|
const index = args.indexOf(name);
|
|
8
13
|
if (index < 0)
|
|
9
14
|
return undefined;
|
|
10
15
|
const value = args[index + 1];
|
|
11
|
-
if (
|
|
16
|
+
if (value === undefined || value.startsWith('--'))
|
|
12
17
|
throw new Error(`missing value for ${name}`);
|
|
13
18
|
return value;
|
|
14
19
|
}
|
|
15
|
-
function required(args, name) {
|
|
16
|
-
const value = option(args, name);
|
|
17
|
-
if (!value)
|
|
18
|
-
throw new Error(`missing required option ${name}`);
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
20
|
function listOption(args, name) {
|
|
22
21
|
const value = option(args, name);
|
|
23
|
-
return value ? value.split(',').map((item) => item.trim()).filter(Boolean)
|
|
22
|
+
return value === undefined ? undefined : value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
24
23
|
}
|
|
25
|
-
function positionals(args,
|
|
24
|
+
function positionals(args, valueOptions) {
|
|
26
25
|
const values = [];
|
|
27
26
|
for (let index = 0; index < args.length; index += 1) {
|
|
28
|
-
if (args[index].startsWith('
|
|
29
|
-
if (
|
|
27
|
+
if (args[index].startsWith('-')) {
|
|
28
|
+
if (valueOptions.includes(args[index]))
|
|
30
29
|
index += 1;
|
|
31
30
|
continue;
|
|
32
31
|
}
|
|
@@ -34,15 +33,19 @@ function positionals(args, options) {
|
|
|
34
33
|
}
|
|
35
34
|
return values;
|
|
36
35
|
}
|
|
37
|
-
function validateOptions(args, allowed) {
|
|
36
|
+
function validateOptions(args, allowed, valueOptions = []) {
|
|
38
37
|
for (let index = 0; index < args.length; index += 1) {
|
|
39
38
|
const argument = args[index];
|
|
40
39
|
if (!argument.startsWith('-'))
|
|
41
40
|
continue;
|
|
42
41
|
if (!allowed.includes(argument))
|
|
43
42
|
throw new Error(`unknown option ${argument}`);
|
|
44
|
-
if (argument
|
|
43
|
+
if (valueOptions.includes(argument)) {
|
|
44
|
+
const value = args[index + 1];
|
|
45
|
+
if (value === undefined || value.startsWith('-'))
|
|
46
|
+
throw new Error(`missing value for ${argument}`);
|
|
45
47
|
index += 1;
|
|
48
|
+
}
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
function numericOption(args, name, defaultValue) {
|
|
@@ -54,33 +57,250 @@ function numericOption(args, name, defaultValue) {
|
|
|
54
57
|
throw new Error(`${name} must be a non-negative integer`);
|
|
55
58
|
return parsed;
|
|
56
59
|
}
|
|
60
|
+
function enumOption(args, name, values) {
|
|
61
|
+
const value = option(args, name);
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (!values.includes(value))
|
|
65
|
+
throw new Error(`${name} must be one of: ${values.join(', ')}`);
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
const HELP = {
|
|
69
|
+
init: `usage: ctx init [path] [--path <path>]
|
|
70
|
+
|
|
71
|
+
Initialize the local .context runtime layout. Re-running init is safe.
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--path <path> Project path`,
|
|
75
|
+
checkpoint: `usage: ctx checkpoint [options]
|
|
76
|
+
|
|
77
|
+
Save structured work state as a baseline or incremental snapshot.
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--path <path> Project path
|
|
81
|
+
--context <id> Existing context ID
|
|
82
|
+
--title <text> Context title
|
|
83
|
+
--progress <text> Current progress summary
|
|
84
|
+
--last-error <text> Last error (blank clears it)
|
|
85
|
+
--completed <items> Comma-separated completed items
|
|
86
|
+
--pending <items> Comma-separated pending tasks
|
|
87
|
+
--decisions <items> Comma-separated decisions
|
|
88
|
+
--risks <items> Comma-separated risks
|
|
89
|
+
--files <paths> Comma-separated related paths
|
|
90
|
+
--absorb-notes Mark open notes for this context as absorbed`,
|
|
91
|
+
resume: `usage: ctx resume [options]
|
|
92
|
+
|
|
93
|
+
Resume the most relevant active or stable context.
|
|
94
|
+
|
|
95
|
+
Options:
|
|
96
|
+
--path <path> Project path
|
|
97
|
+
--context <id> Context ID
|
|
98
|
+
--max-chars <number> Maximum summary characters`,
|
|
99
|
+
orient: `usage: ctx orient [query] [options]
|
|
100
|
+
|
|
101
|
+
Prepare bounded task context from the current work, verified consensus, matching deadends, and open notes.
|
|
102
|
+
|
|
103
|
+
Options:
|
|
104
|
+
--path <path> Project path
|
|
105
|
+
--context <id> Context ID
|
|
106
|
+
--scope <scope> Exact consensus scope
|
|
107
|
+
--max-chars <number> Maximum display characters
|
|
108
|
+
--knowledge-limit <number> Maximum knowledge matches (default: 10)
|
|
109
|
+
--deadend-limit <number> Maximum deadend matches (default: 10)
|
|
110
|
+
--note-limit <number> Maximum open notes (default: 20)`,
|
|
111
|
+
agent: `usage: ctx agent serve
|
|
112
|
+
|
|
113
|
+
Start an MCP stdio server for Agent integrations.
|
|
114
|
+
|
|
115
|
+
Run "ctx agent serve --help" for details.`,
|
|
116
|
+
'agent serve': `usage: ctx agent serve
|
|
117
|
+
|
|
118
|
+
Start an MCP stdio server exposing the context_orient tool.
|
|
119
|
+
|
|
120
|
+
The server owns standard input/output; do not use it interactively.`,
|
|
121
|
+
hook: `usage: ctx hook claude-code session-start|user-prompt
|
|
122
|
+
|
|
123
|
+
Run a Claude Code lifecycle hook. This command reads Hook JSON from standard input.`,
|
|
124
|
+
'hook claude-code': `usage: ctx hook claude-code session-start|user-prompt
|
|
125
|
+
|
|
126
|
+
Emit best-effort, read-only phased Context guidance for Claude Code.`,
|
|
127
|
+
integrate: `usage: ctx integrate claude inspect|install [--path <path>] [--apply]
|
|
128
|
+
|
|
129
|
+
Inspect or explicitly install project-local Claude Code hooks and MCP configuration.`,
|
|
130
|
+
'integrate claude': `usage: ctx integrate claude inspect|install [--path <path>] [--apply]
|
|
131
|
+
|
|
132
|
+
Inspect configuration, or preview/install project-local Claude Code integration.`,
|
|
133
|
+
'integrate claude inspect': `usage: ctx integrate claude inspect [--path <path>]
|
|
134
|
+
|
|
135
|
+
Report whether the project has the Claude Code hooks and MCP server configured.`,
|
|
136
|
+
'integrate claude install': `usage: ctx integrate claude install [--path <path>] [--apply]
|
|
137
|
+
|
|
138
|
+
Preview configuration changes by default. Pass --apply to merge project-local .claude/settings.json and .mcp.json.`,
|
|
139
|
+
status: `usage: ctx status [--path <path>]
|
|
140
|
+
|
|
141
|
+
Show the initialized project and context index.`,
|
|
142
|
+
doctor: `usage: ctx doctor [--path <path>]
|
|
143
|
+
|
|
144
|
+
Check runtime files and snapshot integrity without modifying them.`,
|
|
145
|
+
learn: `usage: ctx learn <statement> [options]
|
|
146
|
+
|
|
147
|
+
Record a candidate project knowledge item.
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
--path <path> Project path
|
|
151
|
+
--kind <kind> Knowledge kind
|
|
152
|
+
--scope <scope> Knowledge scope
|
|
153
|
+
--context <id> Source context ID
|
|
154
|
+
--snapshot <id> Source snapshot ID
|
|
155
|
+
--evidence <items> Comma-separated evidence references`,
|
|
156
|
+
knowledge: `usage: ctx knowledge [--all] [--path <path>]
|
|
157
|
+
ctx knowledge verify <knowledge-id> [--path <path>]
|
|
158
|
+
ctx knowledge discover <query> [options]
|
|
159
|
+
|
|
160
|
+
List candidate/verified knowledge, verify one item, or discover matching knowledge.
|
|
161
|
+
|
|
162
|
+
Options:
|
|
163
|
+
--all Include unverified items
|
|
164
|
+
--path <path> Project path`,
|
|
165
|
+
'knowledge verify': `usage: ctx knowledge verify <knowledge-id> [--path <path>]
|
|
166
|
+
|
|
167
|
+
Mark a knowledge item as verified.`,
|
|
168
|
+
'knowledge discover': `usage: ctx knowledge discover <query> [options]
|
|
169
|
+
|
|
170
|
+
Find matching project knowledge using deterministic lexical and alias rules.
|
|
171
|
+
|
|
172
|
+
Options:
|
|
173
|
+
--path <path> Project path
|
|
174
|
+
--scope <scope> Exact knowledge scope
|
|
175
|
+
--status <statuses> Comma-separated candidate/verified/deprecated/rejected
|
|
176
|
+
--all Include all knowledge statuses
|
|
177
|
+
--limit <number> Maximum matches (default: 10)
|
|
178
|
+
--max-chars <number> Maximum text characters per match`,
|
|
179
|
+
deadend: `usage: ctx deadend [attempt] [options]
|
|
180
|
+
ctx deadend verify <deadend-id> [--path <path>]
|
|
181
|
+
|
|
182
|
+
Record or verify a candidate deadend.
|
|
183
|
+
|
|
184
|
+
Options:
|
|
185
|
+
--path <path> Project path
|
|
186
|
+
--attempt <text> Attempt description
|
|
187
|
+
--reason <text> Why it failed
|
|
188
|
+
-m <text> Alias for --reason
|
|
189
|
+
--scope <scope> Deadend scope
|
|
190
|
+
--context <id> Source context ID
|
|
191
|
+
--snapshot <id> Source snapshot ID
|
|
192
|
+
--evidence <items> Comma-separated evidence references`,
|
|
193
|
+
'deadend verify': `usage: ctx deadend verify <deadend-id> [--path <path>]
|
|
194
|
+
|
|
195
|
+
Mark a deadend as verified.`,
|
|
196
|
+
deadends: `usage: ctx deadends [--all] [--path <path>]
|
|
197
|
+
|
|
198
|
+
List recorded deadends.`,
|
|
199
|
+
note: `usage: ctx note add <message> [options]
|
|
200
|
+
ctx note list [options]
|
|
201
|
+
|
|
202
|
+
Agent-facing short-term notes for problems, actions, observations, and decisions.
|
|
203
|
+
|
|
204
|
+
Run \"ctx note add --help\" or \"ctx note list --help\" for details.`,
|
|
205
|
+
'note add': `usage: ctx note add <message> [options]
|
|
206
|
+
|
|
207
|
+
Append a short note without creating a Context Snapshot.
|
|
208
|
+
|
|
209
|
+
Options:
|
|
210
|
+
--path <path> Project path
|
|
211
|
+
--kind <kind> problem|action|observation|decision
|
|
212
|
+
--context <id> Context ID
|
|
213
|
+
--snapshot <id> Snapshot ID
|
|
214
|
+
--files <paths> Comma-separated related paths
|
|
215
|
+
--actor <actor> agent|human
|
|
216
|
+
--status <status> open|resolved`,
|
|
217
|
+
'note list': `usage: ctx note list [options]
|
|
218
|
+
|
|
219
|
+
List recent notes for an Agent or integration.
|
|
220
|
+
|
|
221
|
+
Options:
|
|
222
|
+
--path <path> Project path
|
|
223
|
+
--context <id> Context ID
|
|
224
|
+
--open Only unresolved and unabsorbed notes
|
|
225
|
+
--limit <number> Maximum notes (default: 50)
|
|
226
|
+
--max-chars <number> Maximum message characters`,
|
|
227
|
+
activity: `usage: ctx activity [options]
|
|
228
|
+
|
|
229
|
+
Show a Human-oriented timeline of notes and saved checkpoints.
|
|
230
|
+
|
|
231
|
+
Options:
|
|
232
|
+
--path <path> Project path
|
|
233
|
+
--context <id> Context ID
|
|
234
|
+
--since <ISO> Only activity at or after this time
|
|
235
|
+
--open Only open notes
|
|
236
|
+
--limit <number> Maximum items (default: 50)
|
|
237
|
+
--max-chars <number> Maximum message characters`,
|
|
238
|
+
};
|
|
57
239
|
function usage() {
|
|
58
|
-
return
|
|
240
|
+
return `usage: ctx init|checkpoint|resume|orient|agent|hook|integrate|status|doctor|learn|knowledge|deadend|deadends|note|activity [options]
|
|
241
|
+
|
|
242
|
+
Run \"ctx <command> --help\" for command details.`;
|
|
59
243
|
}
|
|
60
244
|
function print(value) {
|
|
61
245
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
62
246
|
}
|
|
247
|
+
async function stdin() {
|
|
248
|
+
let value = '';
|
|
249
|
+
for await (const chunk of process.stdin)
|
|
250
|
+
value += chunk.toString();
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function printHelp(args) {
|
|
254
|
+
const command = args[0];
|
|
255
|
+
if (!command) {
|
|
256
|
+
process.stdout.write(`${usage()}\n`);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
const key = args[1] === 'verify' || args[1] === 'add' || args[1] === 'list' || args[1] === 'discover' || args[1] === 'serve' || args[1] === 'claude' ? `${command} ${args[1]}` : command;
|
|
260
|
+
const nestedKey = key === 'integrate claude' && (args[2] === 'inspect' || args[2] === 'install') ? `${key} ${args[2]}` : key;
|
|
261
|
+
const help = HELP[nestedKey];
|
|
262
|
+
if (!help)
|
|
263
|
+
return false;
|
|
264
|
+
if (args.includes('--help')) {
|
|
265
|
+
process.stdout.write(`${help}\n`);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
function validatePositionals(values, max, usageText) {
|
|
271
|
+
if (values.length > max)
|
|
272
|
+
throw new Error(usageText);
|
|
273
|
+
}
|
|
63
274
|
async function run(args) {
|
|
64
275
|
if (args.includes('--version')) {
|
|
65
|
-
print(
|
|
276
|
+
print(VERSION);
|
|
66
277
|
return;
|
|
67
278
|
}
|
|
68
|
-
if (args.length === 0 || args
|
|
69
|
-
|
|
279
|
+
if (args.length === 0 || args[0] === '--help') {
|
|
280
|
+
printHelp([]);
|
|
70
281
|
return;
|
|
71
282
|
}
|
|
283
|
+
if (args.includes('--help') && printHelp(args))
|
|
284
|
+
return;
|
|
72
285
|
const command = args[0];
|
|
73
286
|
const target = option(args, '--path');
|
|
74
287
|
switch (command) {
|
|
75
|
-
case 'init':
|
|
76
|
-
|
|
288
|
+
case 'init': {
|
|
289
|
+
validateOptions(args.slice(1), ['--path'], ['--path']);
|
|
290
|
+
const values = positionals(args.slice(1), ['--path']);
|
|
291
|
+
validatePositionals(values, 1, HELP.init);
|
|
292
|
+
print(await initProject(target ?? values[0]));
|
|
77
293
|
return;
|
|
294
|
+
}
|
|
78
295
|
case 'checkpoint': {
|
|
296
|
+
validateOptions(args.slice(1), ['--path', '--context', '--title', '--progress', '--last-error', '--completed', '--pending', '--decisions', '--risks', '--files', '--absorb-notes'], ['--path', '--context', '--title', '--progress', '--last-error', '--completed', '--pending', '--decisions', '--risks', '--files']);
|
|
297
|
+
validatePositionals(positionals(args.slice(1), ['--path', '--context', '--title', '--progress', '--last-error', '--completed', '--pending', '--decisions', '--risks', '--files']), 0, HELP.checkpoint);
|
|
79
298
|
const input = {
|
|
80
299
|
contextId: option(args, '--context'),
|
|
300
|
+
absorbNotes: args.includes('--absorb-notes'),
|
|
81
301
|
title: option(args, '--title'),
|
|
82
302
|
progressSummary: option(args, '--progress'),
|
|
83
|
-
lastError: option(args, '--last-error')
|
|
303
|
+
lastError: option(args, '--last-error'),
|
|
84
304
|
completed: listOption(args, '--completed'),
|
|
85
305
|
pendingTasks: listOption(args, '--pending'),
|
|
86
306
|
decisions: listOption(args, '--decisions'),
|
|
@@ -91,18 +311,72 @@ async function run(args) {
|
|
|
91
311
|
return;
|
|
92
312
|
}
|
|
93
313
|
case 'resume':
|
|
94
|
-
validateOptions(args.slice(1), ['--path', '--context', '--max-chars']);
|
|
314
|
+
validateOptions(args.slice(1), ['--path', '--context', '--max-chars'], ['--path', '--context', '--max-chars']);
|
|
315
|
+
validatePositionals(positionals(args.slice(1), ['--path', '--context', '--max-chars']), 0, HELP.resume);
|
|
95
316
|
print(await resume(target, option(args, '--context'), numericOption(args, '--max-chars', 4000)));
|
|
96
317
|
return;
|
|
318
|
+
case 'orient': {
|
|
319
|
+
const valueOptions = ['--path', '--context', '--scope', '--max-chars', '--knowledge-limit', '--deadend-limit', '--note-limit'];
|
|
320
|
+
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
321
|
+
const query = positionals(args.slice(1), valueOptions);
|
|
322
|
+
validatePositionals(query, 1, HELP.orient);
|
|
323
|
+
print(await contextOrient(target, {
|
|
324
|
+
contextId: option(args, '--context'),
|
|
325
|
+
query: query[0],
|
|
326
|
+
scope: option(args, '--scope'),
|
|
327
|
+
maxChars: numericOption(args, '--max-chars', 4000),
|
|
328
|
+
knowledgeLimit: numericOption(args, '--knowledge-limit', 10),
|
|
329
|
+
deadendLimit: numericOption(args, '--deadend-limit', 10),
|
|
330
|
+
noteLimit: numericOption(args, '--note-limit', 20),
|
|
331
|
+
}));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
case 'agent':
|
|
335
|
+
if (args[1] !== 'serve')
|
|
336
|
+
throw new Error(HELP.agent);
|
|
337
|
+
validateOptions(args.slice(2), [], []);
|
|
338
|
+
validatePositionals(positionals(args.slice(2), []), 0, HELP['agent serve']);
|
|
339
|
+
await import('../mcp/main.js');
|
|
340
|
+
return;
|
|
341
|
+
case 'hook': {
|
|
342
|
+
if (args[1] !== 'claude-code' || !['session-start', 'user-prompt'].includes(args[2]))
|
|
343
|
+
throw new Error(HELP['hook claude-code']);
|
|
344
|
+
validateOptions(args.slice(3), [], []);
|
|
345
|
+
validatePositionals(positionals(args.slice(3), []), 0, HELP['hook claude-code']);
|
|
346
|
+
process.stdout.write(await runClaudeCodeHook(args[2], await stdin()));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
case 'integrate': {
|
|
350
|
+
if (args[1] !== 'claude' || !['inspect', 'install'].includes(args[2]))
|
|
351
|
+
throw new Error(HELP['integrate claude']);
|
|
352
|
+
const valueOptions = ['--path'];
|
|
353
|
+
const allowed = args[2] === 'install' ? [...valueOptions, '--apply'] : valueOptions;
|
|
354
|
+
validateOptions(args.slice(3), allowed, valueOptions);
|
|
355
|
+
validatePositionals(positionals(args.slice(3), valueOptions), 0, HELP[`integrate claude ${args[2]}`]);
|
|
356
|
+
if (args[2] === 'inspect')
|
|
357
|
+
print(await inspectClaudeIntegration(target));
|
|
358
|
+
else
|
|
359
|
+
print(await installClaudeIntegration(target, args.includes('--apply')));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
97
362
|
case 'status':
|
|
363
|
+
validateOptions(args.slice(1), ['--path'], ['--path']);
|
|
364
|
+
validatePositionals(positionals(args.slice(1), ['--path']), 0, HELP.status);
|
|
98
365
|
print(await status(target));
|
|
99
366
|
return;
|
|
100
367
|
case 'doctor':
|
|
368
|
+
validateOptions(args.slice(1), ['--path'], ['--path']);
|
|
369
|
+
validatePositionals(positionals(args.slice(1), ['--path']), 0, HELP.doctor);
|
|
101
370
|
print(await doctor(target));
|
|
102
371
|
return;
|
|
103
372
|
case 'learn': {
|
|
104
|
-
const
|
|
373
|
+
const valueOptions = ['--path', '--kind', '--scope', '--context', '--snapshot', '--evidence'];
|
|
374
|
+
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
375
|
+
const statement = positionals(args.slice(1), valueOptions);
|
|
376
|
+
if (statement.length === 0)
|
|
377
|
+
throw new Error(HELP.learn);
|
|
105
378
|
const input = {
|
|
379
|
+
kind: option(args, '--kind'),
|
|
106
380
|
scope: option(args, '--scope'),
|
|
107
381
|
sourceContextId: option(args, '--context'),
|
|
108
382
|
sourceSnapshotId: option(args, '--snapshot'),
|
|
@@ -111,26 +385,104 @@ async function run(args) {
|
|
|
111
385
|
print(await learnKnowledge(target, statement.join(' '), input));
|
|
112
386
|
return;
|
|
113
387
|
}
|
|
114
|
-
case '
|
|
115
|
-
if (args[1] === '
|
|
116
|
-
|
|
117
|
-
|
|
388
|
+
case 'note': {
|
|
389
|
+
if (args[1] === 'add') {
|
|
390
|
+
const valueOptions = ['--path', '--kind', '--context', '--snapshot', '--files', '--actor', '--status'];
|
|
391
|
+
validateOptions(args.slice(2), valueOptions, valueOptions);
|
|
392
|
+
const values = positionals(args.slice(2), valueOptions);
|
|
393
|
+
if (values.length === 0)
|
|
394
|
+
throw new Error(HELP['note add']);
|
|
395
|
+
const input = {
|
|
396
|
+
kind: enumOption(args, '--kind', ['problem', 'action', 'observation', 'decision']),
|
|
397
|
+
actor: enumOption(args, '--actor', ['agent', 'human']),
|
|
398
|
+
contextId: option(args, '--context'),
|
|
399
|
+
snapshotId: option(args, '--snapshot'),
|
|
400
|
+
relatedFiles: listOption(args, '--files'),
|
|
401
|
+
status: enumOption(args, '--status', ['open', 'resolved']),
|
|
402
|
+
};
|
|
403
|
+
print(await addNote(target, values.join(' '), input));
|
|
404
|
+
}
|
|
405
|
+
else if (args[1] === 'list') {
|
|
406
|
+
const valueOptions = ['--path', '--context', '--limit', '--max-chars'];
|
|
407
|
+
validateOptions(args.slice(2), [...valueOptions, '--open'], valueOptions);
|
|
408
|
+
validatePositionals(positionals(args.slice(2), valueOptions), 0, HELP['note list']);
|
|
409
|
+
print(await listNotes(target, { contextId: option(args, '--context'), openOnly: args.includes('--open'), limit: numericOption(args, '--limit', 50), maxChars: numericOption(args, '--max-chars', 500) }));
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
throw new Error(HELP.note);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
case 'activity': {
|
|
417
|
+
const valueOptions = ['--path', '--context', '--since', '--limit', '--max-chars'];
|
|
418
|
+
validateOptions(args.slice(1), [...valueOptions, '--open'], valueOptions);
|
|
419
|
+
validatePositionals(positionals(args.slice(1), valueOptions), 0, HELP.activity);
|
|
420
|
+
const query = {
|
|
421
|
+
contextId: option(args, '--context'),
|
|
422
|
+
since: option(args, '--since'),
|
|
423
|
+
openOnly: args.includes('--open'),
|
|
424
|
+
limit: numericOption(args, '--limit', 50),
|
|
425
|
+
maxChars: numericOption(args, '--max-chars', 500),
|
|
426
|
+
};
|
|
427
|
+
print(await listActivity(target, query));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
case 'knowledge': {
|
|
431
|
+
if (args[1] === 'discover') {
|
|
432
|
+
const valueOptions = ['--path', '--scope', '--status', '--limit', '--max-chars'];
|
|
433
|
+
validateOptions(args.slice(2), [...valueOptions, '--all'], valueOptions);
|
|
434
|
+
const query = positionals(args.slice(2), valueOptions);
|
|
435
|
+
if (query.length === 0)
|
|
436
|
+
throw new Error(HELP['knowledge discover']);
|
|
437
|
+
const statuses = args.includes('--all') ? ['candidate', 'verified', 'deprecated', 'rejected'] : (listOption(args, '--status') ?? ['verified']);
|
|
438
|
+
if (statuses.some((status) => !['candidate', 'verified', 'deprecated', 'rejected'].includes(status)))
|
|
439
|
+
throw new Error('--status contains an invalid knowledge status');
|
|
440
|
+
print(await discoverKnowledge(target, query.join(' '), {
|
|
441
|
+
scope: option(args, '--scope'),
|
|
442
|
+
statuses: statuses,
|
|
443
|
+
limit: numericOption(args, '--limit', 10),
|
|
444
|
+
maxChars: numericOption(args, '--max-chars', 4000),
|
|
445
|
+
}));
|
|
446
|
+
}
|
|
447
|
+
else if (args[1] === 'verify') {
|
|
448
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
449
|
+
const values = positionals(args.slice(2), ['--path']);
|
|
450
|
+
if (values.length !== 1)
|
|
451
|
+
throw new Error(HELP['knowledge verify']);
|
|
452
|
+
print(await verifyKnowledge(target, values[0]));
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
validateOptions(args.slice(1), ['--all', '--path'], ['--path']);
|
|
456
|
+
validatePositionals(positionals(args.slice(1), ['--path']), 0, HELP.knowledge);
|
|
118
457
|
print(await listKnowledge(target, args.includes('--all')));
|
|
458
|
+
}
|
|
119
459
|
return;
|
|
460
|
+
}
|
|
120
461
|
case 'deadend':
|
|
121
|
-
if (args[1] === 'verify')
|
|
122
|
-
|
|
462
|
+
if (args[1] === 'verify') {
|
|
463
|
+
validateOptions(args.slice(2), ['--path'], ['--path']);
|
|
464
|
+
const values = positionals(args.slice(2), ['--path']);
|
|
465
|
+
if (values.length !== 1)
|
|
466
|
+
throw new Error(HELP['deadend verify']);
|
|
467
|
+
print(await verifyDeadend(target, values[0]));
|
|
468
|
+
}
|
|
123
469
|
else {
|
|
470
|
+
const valueOptions = ['--path', '--attempt', '--reason', '-m', '--scope', '--context', '--snapshot', '--evidence'];
|
|
471
|
+
validateOptions(args.slice(1), valueOptions, valueOptions);
|
|
472
|
+
const values = positionals(args.slice(1), valueOptions);
|
|
473
|
+
validatePositionals(values, 1, HELP.deadend);
|
|
124
474
|
const input = {
|
|
125
475
|
scope: option(args, '--scope'),
|
|
126
476
|
sourceContextId: option(args, '--context'),
|
|
127
477
|
sourceSnapshotId: option(args, '--snapshot'),
|
|
128
478
|
evidence: listOption(args, '--evidence'),
|
|
129
479
|
};
|
|
130
|
-
print(await recordDeadend(target, option(args, '--attempt') ??
|
|
480
|
+
print(await recordDeadend(target, option(args, '--attempt') ?? values[0], option(args, '--reason') ?? option(args, '-m') ?? '', input));
|
|
131
481
|
}
|
|
132
482
|
return;
|
|
133
483
|
case 'deadends':
|
|
484
|
+
validateOptions(args.slice(1), ['--all', '--path'], ['--path']);
|
|
485
|
+
validatePositionals(positionals(args.slice(1), ['--path']), 0, HELP.deadends);
|
|
134
486
|
print(await listDeadends(target, args.includes('--all')));
|
|
135
487
|
return;
|
|
136
488
|
default:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runClaudeCodeHook(event: 'session-start' | 'user-prompt', raw: string): Promise<string>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { contextOrient } from '../agent/api.js';
|
|
2
|
+
const HOOK_RESPONSE = '{}\n';
|
|
3
|
+
function text(value) {
|
|
4
|
+
return typeof value === 'string' ? value : undefined;
|
|
5
|
+
}
|
|
6
|
+
function compact(value, maxChars) {
|
|
7
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
8
|
+
return normalized.length <= maxChars ? normalized : `${normalized.slice(0, maxChars - 1)}…`;
|
|
9
|
+
}
|
|
10
|
+
function lines(label, values, limit) {
|
|
11
|
+
return values.slice(0, limit).map((value) => `- ${label}: ${compact(value, 240)}`);
|
|
12
|
+
}
|
|
13
|
+
function readyContext(result) {
|
|
14
|
+
const content = [
|
|
15
|
+
`当前 Context:${compact(result.context.title, 160)}`,
|
|
16
|
+
`进度:${compact(result.resumeSummary.progressSummary, 360)}`,
|
|
17
|
+
...lines('待办', result.resumeSummary.pendingTasks, 4),
|
|
18
|
+
...lines('决策', result.resumeSummary.decisions, 3),
|
|
19
|
+
...lines('风险', result.resumeSummary.risks, 3),
|
|
20
|
+
...result.knowledge.hits.slice(0, 5).map((hit) => `- 已验证知识:${compact(hit.knowledge.statement, 240)}`),
|
|
21
|
+
...result.deadends.hits.slice(0, 3).map((hit) => `- 已验证死路(避免):${compact(hit.deadend.attempt, 180)};${compact(hit.deadend.reason, 180)}`),
|
|
22
|
+
...result.notes.slice(0, 8).map((note) => `- 开放 Note:${compact(note.message, 220)}`),
|
|
23
|
+
];
|
|
24
|
+
if (result.truncated)
|
|
25
|
+
content.push('- 上下文已按预算截断;需要时使用 context_orient 或 ctx orient 进一步查询。');
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
function sessionContext(result) {
|
|
29
|
+
const phases = [
|
|
30
|
+
'开发阶段:Orient → Plan → Implement → Verify → Handoff。',
|
|
31
|
+
'先基于以下上下文明确计划;实现中记录重要观察/决策/阻塞为 ctx note;验证后显式 ctx checkpoint,总结完成、待办、决策和风险。不要自动保存 Snapshot。',
|
|
32
|
+
];
|
|
33
|
+
if (result.status === 'no_context') {
|
|
34
|
+
phases.push('当前没有可恢复的 Context。开始有意义的工作后,使用 ctx checkpoint 创建一个。');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
phases.push(...readyContext(result));
|
|
38
|
+
}
|
|
39
|
+
return phases.join('\n');
|
|
40
|
+
}
|
|
41
|
+
function promptContext(result) {
|
|
42
|
+
const phases = [
|
|
43
|
+
'开发阶段:Implement → Verify → Handoff。',
|
|
44
|
+
'实现时用 ctx note 记录重要观察、决策或阻塞;完成后运行适用的验证。阶段结束时显式 ctx checkpoint(可 --absorb-notes),不要由 Hook 自动保存。',
|
|
45
|
+
];
|
|
46
|
+
if (result.status === 'no_context') {
|
|
47
|
+
phases.push('当前没有可恢复的 Context;如果这项工作会继续,先建立 checkpoint。');
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
phases.push(...readyContext(result));
|
|
51
|
+
}
|
|
52
|
+
return phases.join('\n');
|
|
53
|
+
}
|
|
54
|
+
async function parseInput(raw) {
|
|
55
|
+
try {
|
|
56
|
+
const value = JSON.parse(raw);
|
|
57
|
+
return value !== null && typeof value === 'object' ? value : null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function runClaudeCodeHook(event, raw) {
|
|
64
|
+
const input = await parseInput(raw);
|
|
65
|
+
if (!input)
|
|
66
|
+
return HOOK_RESPONSE;
|
|
67
|
+
try {
|
|
68
|
+
const cwd = text(input.cwd);
|
|
69
|
+
const result = event === 'session-start'
|
|
70
|
+
? await contextOrient(cwd, { maxChars: 1200, noteLimit: 8 })
|
|
71
|
+
: await contextOrient(cwd, {
|
|
72
|
+
query: compact(text(input.prompt) ?? '', 500) || undefined,
|
|
73
|
+
maxChars: 1800,
|
|
74
|
+
knowledgeLimit: 5,
|
|
75
|
+
deadendLimit: 3,
|
|
76
|
+
noteLimit: 8,
|
|
77
|
+
});
|
|
78
|
+
const additionalContext = event === 'session-start' ? sessionContext(result) : promptContext(result);
|
|
79
|
+
return `${JSON.stringify({ additionalContext })}\n`;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return HOOK_RESPONSE;
|
|
83
|
+
}
|
|
84
|
+
}
|