sidecar-cli 0.1.0-beta.4
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 +219 -0
- package/dist/cli.js +727 -0
- package/dist/db/client.js +16 -0
- package/dist/db/schema.js +69 -0
- package/dist/lib/banner.js +12 -0
- package/dist/lib/db.js +36 -0
- package/dist/lib/errors.js +10 -0
- package/dist/lib/format.js +14 -0
- package/dist/lib/output.js +20 -0
- package/dist/lib/paths.js +27 -0
- package/dist/lib/validation.js +31 -0
- package/dist/services/artifact-service.js +12 -0
- package/dist/services/capabilities-service.js +198 -0
- package/dist/services/context-service.js +33 -0
- package/dist/services/event-service.js +82 -0
- package/dist/services/session-service.js +47 -0
- package/dist/services/summary-service.js +68 -0
- package/dist/services/task-service.js +54 -0
- package/dist/templates/agents.js +38 -0
- package/dist/templates/summary.js +52 -0
- package/dist/types/models.js +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { nowIso } from '../lib/format.js';
|
|
3
|
+
import { getSidecarPaths } from '../lib/paths.js';
|
|
4
|
+
import { renderSummaryMarkdown } from '../templates/summary.js';
|
|
5
|
+
import { createEvent } from './event-service.js';
|
|
6
|
+
export function readSummaryInputs(db, projectId, limit) {
|
|
7
|
+
const project = db.prepare(`SELECT name, root_path FROM projects WHERE id = ?`).get(projectId);
|
|
8
|
+
const decisions = db
|
|
9
|
+
.prepare(`SELECT created_at, title, summary FROM events WHERE project_id = ? AND type = 'decision' ORDER BY created_at DESC LIMIT ?`)
|
|
10
|
+
.all(projectId, limit);
|
|
11
|
+
const worklogs = db
|
|
12
|
+
.prepare(`SELECT created_at, title, summary FROM events WHERE project_id = ? AND type = 'worklog' ORDER BY created_at DESC LIMIT ?`)
|
|
13
|
+
.all(projectId, limit);
|
|
14
|
+
const notes = db
|
|
15
|
+
.prepare(`SELECT created_at, title, summary FROM events WHERE project_id = ? AND type = 'note' ORDER BY created_at DESC LIMIT ?`)
|
|
16
|
+
.all(projectId, limit);
|
|
17
|
+
const openTasks = db
|
|
18
|
+
.prepare(`SELECT id, title, priority, updated_at FROM tasks WHERE project_id = ? AND status = 'open' ORDER BY updated_at DESC LIMIT ?`)
|
|
19
|
+
.all(projectId, limit);
|
|
20
|
+
const artifacts = db
|
|
21
|
+
.prepare(`SELECT path, kind, note, created_at FROM artifacts WHERE project_id = ? ORDER BY created_at DESC LIMIT ?`)
|
|
22
|
+
.all(projectId, limit);
|
|
23
|
+
const activeSession = db
|
|
24
|
+
.prepare(`SELECT id, started_at, actor_type, actor_name FROM sessions WHERE project_id = ? AND ended_at IS NULL ORDER BY started_at DESC LIMIT 1`)
|
|
25
|
+
.get(projectId);
|
|
26
|
+
const recentEventCount = db
|
|
27
|
+
.prepare(`SELECT COUNT(*) as count FROM events WHERE project_id = ? AND created_at >= datetime('now', '-7 day')`)
|
|
28
|
+
.get(projectId).count;
|
|
29
|
+
return {
|
|
30
|
+
projectName: project.name,
|
|
31
|
+
projectPath: project.root_path,
|
|
32
|
+
decisions,
|
|
33
|
+
worklogs,
|
|
34
|
+
notes,
|
|
35
|
+
openTasks,
|
|
36
|
+
artifacts,
|
|
37
|
+
activeSession: activeSession ?? null,
|
|
38
|
+
recentEventCount,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function refreshSummaryFile(db, rootPath, projectId, limit = 10) {
|
|
42
|
+
const sidecarPaths = getSidecarPaths(rootPath);
|
|
43
|
+
const data = readSummaryInputs(db, projectId, limit);
|
|
44
|
+
const generatedAt = nowIso();
|
|
45
|
+
const markdown = renderSummaryMarkdown({
|
|
46
|
+
projectName: data.projectName,
|
|
47
|
+
projectPath: data.projectPath,
|
|
48
|
+
generatedAt,
|
|
49
|
+
activeSession: data.activeSession,
|
|
50
|
+
recentEventCount: data.recentEventCount,
|
|
51
|
+
decisions: data.decisions,
|
|
52
|
+
worklogs: data.worklogs,
|
|
53
|
+
notes: data.notes,
|
|
54
|
+
openTasks: data.openTasks,
|
|
55
|
+
artifacts: data.artifacts,
|
|
56
|
+
});
|
|
57
|
+
fs.writeFileSync(sidecarPaths.summaryPath, markdown);
|
|
58
|
+
const eventId = createEvent(db, {
|
|
59
|
+
projectId,
|
|
60
|
+
type: 'summary_generated',
|
|
61
|
+
title: 'Summary refreshed',
|
|
62
|
+
summary: 'Regenerated .sidecar/summary.md from local records',
|
|
63
|
+
source: 'generated',
|
|
64
|
+
createdBy: 'system',
|
|
65
|
+
details: { limit, path: sidecarPaths.summaryPath },
|
|
66
|
+
});
|
|
67
|
+
return { path: sidecarPaths.summaryPath, eventId, generatedAt };
|
|
68
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { nowIso } from '../lib/format.js';
|
|
2
|
+
import { createEvent } from './event-service.js';
|
|
3
|
+
export function addTask(db, input) {
|
|
4
|
+
const ts = nowIso();
|
|
5
|
+
const info = db
|
|
6
|
+
.prepare(`
|
|
7
|
+
INSERT INTO tasks (project_id, title, description, status, priority, created_at, updated_at, closed_at, origin_event_id)
|
|
8
|
+
VALUES (?, ?, ?, 'open', ?, ?, ?, NULL, NULL)
|
|
9
|
+
`)
|
|
10
|
+
.run(input.projectId, input.title, input.description ?? null, input.priority ?? 'medium', ts, ts);
|
|
11
|
+
const taskId = Number(info.lastInsertRowid);
|
|
12
|
+
const eventId = createEvent(db, {
|
|
13
|
+
projectId: input.projectId,
|
|
14
|
+
type: 'task_created',
|
|
15
|
+
title: `Task #${taskId} created`,
|
|
16
|
+
summary: input.title,
|
|
17
|
+
details: { taskId, description: input.description ?? null, priority: input.priority ?? 'medium' },
|
|
18
|
+
createdBy: input.by,
|
|
19
|
+
});
|
|
20
|
+
db.prepare(`UPDATE tasks SET origin_event_id = ? WHERE id = ?`).run(eventId, taskId);
|
|
21
|
+
return { taskId, eventId };
|
|
22
|
+
}
|
|
23
|
+
export function markTaskDone(db, input) {
|
|
24
|
+
const existing = db
|
|
25
|
+
.prepare(`SELECT id, title, status FROM tasks WHERE project_id = ? AND id = ?`)
|
|
26
|
+
.get(input.projectId, input.taskId);
|
|
27
|
+
if (!existing) {
|
|
28
|
+
return { ok: false, reason: 'Task not found' };
|
|
29
|
+
}
|
|
30
|
+
if (existing.status === 'done') {
|
|
31
|
+
return { ok: false, reason: 'Task is already done' };
|
|
32
|
+
}
|
|
33
|
+
const ts = nowIso();
|
|
34
|
+
db.prepare(`UPDATE tasks SET status = 'done', updated_at = ?, closed_at = ? WHERE id = ?`).run(ts, ts, input.taskId);
|
|
35
|
+
const eventId = createEvent(db, {
|
|
36
|
+
projectId: input.projectId,
|
|
37
|
+
type: 'task_completed',
|
|
38
|
+
title: `Task #${input.taskId} completed`,
|
|
39
|
+
summary: existing.title,
|
|
40
|
+
details: { taskId: input.taskId },
|
|
41
|
+
createdBy: input.by,
|
|
42
|
+
});
|
|
43
|
+
return { ok: true, eventId };
|
|
44
|
+
}
|
|
45
|
+
export function listTasks(db, input) {
|
|
46
|
+
if (input.status === 'all') {
|
|
47
|
+
return db
|
|
48
|
+
.prepare(`SELECT * FROM tasks WHERE project_id = ? ORDER BY CASE WHEN status = 'open' THEN 0 ELSE 1 END, updated_at DESC`)
|
|
49
|
+
.all(input.projectId);
|
|
50
|
+
}
|
|
51
|
+
return db
|
|
52
|
+
.prepare(`SELECT * FROM tasks WHERE project_id = ? AND status = ? ORDER BY updated_at DESC`)
|
|
53
|
+
.all(input.projectId, input.status);
|
|
54
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function renderAgentsMarkdown(projectName) {
|
|
2
|
+
return `# Sidecar Agent Guide
|
|
3
|
+
|
|
4
|
+
Sidecar is the local project memory tool for this repository.
|
|
5
|
+
|
|
6
|
+
## Required workflow
|
|
7
|
+
|
|
8
|
+
1. Run \`sidecar context --format markdown\`.
|
|
9
|
+
2. Do the work.
|
|
10
|
+
3. Record updates:
|
|
11
|
+
- decision: \`sidecar decision record ...\`
|
|
12
|
+
- worklog: \`sidecar worklog record ...\`
|
|
13
|
+
- follow-up task: \`sidecar task add ...\`
|
|
14
|
+
4. Run \`sidecar summary refresh\`.
|
|
15
|
+
|
|
16
|
+
## Commands to use
|
|
17
|
+
|
|
18
|
+
- Context: \`sidecar context --format markdown\`
|
|
19
|
+
- Decision: \`sidecar decision record --title "<title>" --summary "<summary>" --by agent\`
|
|
20
|
+
- Worklog: \`sidecar worklog record --done "<what changed>" --files src/a.ts,src/b.ts --by agent\`
|
|
21
|
+
- Task: \`sidecar task add "<follow-up>" --priority medium --by agent\`
|
|
22
|
+
- Summary: \`sidecar summary refresh\`
|
|
23
|
+
|
|
24
|
+
## Example workflow
|
|
25
|
+
|
|
26
|
+
\`\`\`bash
|
|
27
|
+
sidecar context --format markdown
|
|
28
|
+
sidecar session start --actor agent --name codex
|
|
29
|
+
sidecar decision record --title "Choose SQLite" --summary "Local, deterministic storage" --by agent
|
|
30
|
+
sidecar worklog record --goal "Refactor context output" --done "Improved markdown and json context payload" --files src/cli.ts,src/services/context-service.ts --by agent
|
|
31
|
+
sidecar task add "Add integration test for context output" --priority medium --by agent
|
|
32
|
+
sidecar summary refresh
|
|
33
|
+
sidecar session end --summary "Context and summary updates complete"
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
Project: ${projectName}.
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function renderSummaryMarkdown(input) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
lines.push('# Project Summary');
|
|
4
|
+
lines.push('');
|
|
5
|
+
lines.push(`Generated: ${input.generatedAt}`);
|
|
6
|
+
lines.push('');
|
|
7
|
+
lines.push('## Overview');
|
|
8
|
+
lines.push(`- Project: ${input.projectName}`);
|
|
9
|
+
lines.push(`- Path: ${input.projectPath}`);
|
|
10
|
+
lines.push(`- Events in last 7 days: ${input.recentEventCount}`);
|
|
11
|
+
lines.push(`- Open tasks: ${input.openTasks.length}`);
|
|
12
|
+
lines.push('');
|
|
13
|
+
lines.push('## Active Session');
|
|
14
|
+
if (!input.activeSession) {
|
|
15
|
+
lines.push('- None');
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
lines.push(`- #${input.activeSession.id} (${input.activeSession.actor_type}${input.activeSession.actor_name ? `: ${input.activeSession.actor_name}` : ''}) started ${input.activeSession.started_at}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push('');
|
|
21
|
+
lines.push('## Recent Decisions');
|
|
22
|
+
if (input.decisions.length === 0)
|
|
23
|
+
lines.push('- None');
|
|
24
|
+
for (const d of input.decisions)
|
|
25
|
+
lines.push(`- ${d.created_at} | ${d.title}: ${d.summary}`);
|
|
26
|
+
lines.push('');
|
|
27
|
+
lines.push('## Recent Work');
|
|
28
|
+
if (input.worklogs.length === 0)
|
|
29
|
+
lines.push('- None');
|
|
30
|
+
for (const w of input.worklogs)
|
|
31
|
+
lines.push(`- ${w.created_at} | ${w.title}: ${w.summary}`);
|
|
32
|
+
lines.push('');
|
|
33
|
+
lines.push('## Open Tasks');
|
|
34
|
+
if (input.openTasks.length === 0)
|
|
35
|
+
lines.push('- None');
|
|
36
|
+
for (const t of input.openTasks)
|
|
37
|
+
lines.push(`- #${t.id} [${t.priority ?? 'n/a'}] ${t.title}`);
|
|
38
|
+
lines.push('');
|
|
39
|
+
lines.push('## Recent Notes');
|
|
40
|
+
if (input.notes.length === 0)
|
|
41
|
+
lines.push('- None');
|
|
42
|
+
for (const n of input.notes)
|
|
43
|
+
lines.push(`- ${n.created_at} | ${n.title}: ${n.summary}`);
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push('## Artifacts');
|
|
46
|
+
if (input.artifacts.length === 0)
|
|
47
|
+
lines.push('- None');
|
|
48
|
+
for (const a of input.artifacts)
|
|
49
|
+
lines.push(`- ${a.created_at} | ${a.kind} | ${a.path}${a.note ? ` - ${a.note}` : ''}`);
|
|
50
|
+
lines.push('');
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sidecar-cli",
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
|
+
"description": "Local-first project memory and recording tool",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
7
|
+
"dev": "tsx src/cli.ts",
|
|
8
|
+
"start": "node dist/cli.js",
|
|
9
|
+
"test": "node -e \"console.log('No tests configured')\"",
|
|
10
|
+
"pack_release": "node scripts/create-release-tarball.mjs",
|
|
11
|
+
"clean": "node scripts/clean-dist.mjs",
|
|
12
|
+
"release_check": "node scripts/release-check.mjs",
|
|
13
|
+
"release:cut": "node scripts/cut-release.mjs",
|
|
14
|
+
"release:stable": "node scripts/cut-release.mjs --channel stable",
|
|
15
|
+
"release:beta": "node scripts/cut-release.mjs --channel beta",
|
|
16
|
+
"release:rc": "node scripts/cut-release.mjs --channel rc"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"private": false,
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"better-sqlite3": "^12.8.0",
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"commander": "^14.0.3",
|
|
30
|
+
"zod": "^4.3.6"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
34
|
+
"@types/node": "^25.5.0",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"sidecar": "dist/cli.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|