mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
feat: Read My Mind phase 1, per-case intent profiles (capture + API + skill)
Per-case profiles of user intent (docs/readmymind-plan.md): user-stated goals plus the user's recently submitted prompts, captured from the Claude session transcript behind the new synced readMyMindEnabled setting (default OFF). - intent-store.ts: keyed by owner + realpath(workingDir), FIFO/size caps, consecutive-dupe collapse, atomic 0600 writes to ~/.codeman/intents.json - transcript-watcher.ts: new transcript:user_prompt event for typed user turns (tool_result-only entries stay silent); capture wiring in server.ts is claude-only and gated on the setting per event - readmymind-routes.ts: GET/PUT/DELETE /api/sessions/:id/intent, ownership via findSessionOrFail, strict Zod schema - agent skill: SKILL.md recipe + endpoints.md rows so agents can read and record intent (PUT replaces: read + merge; never delete unprompted) - groundwork for the phase-2 predictor button; nothing is ever auto-sent Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -232,6 +232,88 @@ describe('TranscriptWatcher', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('User prompt capture (Read My Mind)', () => {
|
||||
it('emits transcript:user_prompt with the raw text for string content', async () => {
|
||||
writeFileSync(testFile, '');
|
||||
watcher.start(testFile);
|
||||
|
||||
const promptHandler = vi.fn();
|
||||
watcher.on('transcript:user_prompt', promptHandler);
|
||||
|
||||
const ts = new Date().toISOString();
|
||||
appendFileSync(
|
||||
testFile,
|
||||
JSON.stringify({ type: 'user', timestamp: ts, message: { role: 'user', content: 'fix the login bug' } }) + '\n'
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(promptHandler).toHaveBeenCalledWith('fix the login bug', ts);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits joined text blocks but stays silent for tool_result-only entries', async () => {
|
||||
writeFileSync(testFile, '');
|
||||
watcher.start(testFile);
|
||||
|
||||
const promptHandler = vi.fn();
|
||||
watcher.on('transcript:user_prompt', promptHandler);
|
||||
|
||||
appendFileSync(
|
||||
testFile,
|
||||
JSON.stringify({
|
||||
type: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
message: {
|
||||
role: 'user',
|
||||
content: [{ type: 'tool_result', tool_use_id: 'toolu_1', content: 'ok', is_error: false }],
|
||||
},
|
||||
}) + '\n'
|
||||
);
|
||||
appendFileSync(
|
||||
testFile,
|
||||
JSON.stringify({
|
||||
type: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
message: {
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: 'run the tests' },
|
||||
{ type: 'text', text: 'then push' },
|
||||
],
|
||||
},
|
||||
}) + '\n'
|
||||
);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(promptHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
expect(promptHandler).toHaveBeenCalledWith('run the tests then push', expect.any(String));
|
||||
});
|
||||
|
||||
it('does not emit for whitespace-only string content', async () => {
|
||||
writeFileSync(testFile, '');
|
||||
watcher.start(testFile);
|
||||
|
||||
const promptHandler = vi.fn();
|
||||
watcher.on('transcript:user_prompt', promptHandler);
|
||||
|
||||
appendFileSync(
|
||||
testFile,
|
||||
JSON.stringify({
|
||||
type: 'user',
|
||||
timestamp: new Date().toISOString(),
|
||||
message: { role: 'user', content: ' ' },
|
||||
}) + '\n'
|
||||
);
|
||||
|
||||
// Wait for the entry to be processed, then assert no emission happened.
|
||||
await vi.waitFor(() => {
|
||||
expect(watcher.getState().entryCount).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
expect(promptHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('State Management', () => {
|
||||
it('should return a copy of state', () => {
|
||||
const state1 = watcher.getState();
|
||||
|
||||
Reference in New Issue
Block a user