sidecar-cli 0.1.0-beta.6 → 0.1.0-beta.8
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 +11 -55
- package/dist/cli.js +45 -31
- package/dist/lib/paths.js +7 -2
- package/dist/templates/agents.js +9 -1
- package/package.json +1 -1
- package/dist/lib/validation.js +0 -31
package/README.md
CHANGED
|
@@ -11,15 +11,7 @@ Sidecar is a local-first, CLI-first project memory and recording tool for human
|
|
|
11
11
|
- Keep project memory structured and local.
|
|
12
12
|
- Make session handoffs easier for humans and agents.
|
|
13
13
|
- Record decisions, work logs, tasks, notes, sessions, and artifacts in one stable CLI.
|
|
14
|
-
- Generate deterministic context and summary outputs
|
|
15
|
-
|
|
16
|
-
## v1 scope
|
|
17
|
-
|
|
18
|
-
- No cloud sync
|
|
19
|
-
- No remote server
|
|
20
|
-
- No GUI
|
|
21
|
-
- No MCP server
|
|
22
|
-
- No passive prompt capture
|
|
14
|
+
- Generate deterministic context and summary outputs from local project data.
|
|
23
15
|
|
|
24
16
|
## Install
|
|
25
17
|
|
|
@@ -76,7 +68,7 @@ npm run dev -- --help
|
|
|
76
68
|
Initialize in a project directory:
|
|
77
69
|
|
|
78
70
|
```bash
|
|
79
|
-
|
|
71
|
+
sidecar init
|
|
80
72
|
```
|
|
81
73
|
|
|
82
74
|
This creates:
|
|
@@ -86,6 +78,8 @@ This creates:
|
|
|
86
78
|
- `.sidecar/preferences.json`
|
|
87
79
|
- `.sidecar/AGENTS.md`
|
|
88
80
|
- `.sidecar/summary.md`
|
|
81
|
+
- `AGENTS.md` (repo root)
|
|
82
|
+
- `CLAUDE.md` (repo root)
|
|
89
83
|
|
|
90
84
|
Use `--force` to overwrite Sidecar-managed files.
|
|
91
85
|
|
|
@@ -161,6 +155,11 @@ npm run install:hooks
|
|
|
161
155
|
```
|
|
162
156
|
|
|
163
157
|
This installs a non-blocking pre-commit reminder that runs `npm run sidecar:reminder`.
|
|
158
|
+
If a pre-commit hook already exists, Sidecar will not overwrite it unless you run:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm run install:hooks -- --force
|
|
162
|
+
```
|
|
164
163
|
|
|
165
164
|
Agents can discover the CLI surface programmatically with:
|
|
166
165
|
|
|
@@ -195,48 +194,5 @@ This makes Sidecar easy to automate from scripts and AI agents.
|
|
|
195
194
|
|
|
196
195
|
## Release and distribution
|
|
197
196
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Tag formats:
|
|
201
|
-
|
|
202
|
-
- stable: `v1.2.3`
|
|
203
|
-
- beta: `v1.2.3-beta.1`
|
|
204
|
-
- rc: `v1.2.3-rc.1`
|
|
205
|
-
|
|
206
|
-
Behavior:
|
|
207
|
-
|
|
208
|
-
- stable tags publish npm `latest`
|
|
209
|
-
- beta tags publish npm `beta`
|
|
210
|
-
- rc tags publish npm `rc`
|
|
211
|
-
- all release tags create GitHub Releases and upload tarball assets
|
|
212
|
-
- Homebrew tap updates are stable-only (beta/rc intentionally skipped)
|
|
213
|
-
|
|
214
|
-
Workflows:
|
|
215
|
-
|
|
216
|
-
- CI: `.github/workflows/ci.yml`
|
|
217
|
-
- Release: `.github/workflows/release.yml`
|
|
218
|
-
|
|
219
|
-
Required configuration:
|
|
220
|
-
|
|
221
|
-
- `NPM_TOKEN` (secret)
|
|
222
|
-
- `HOMEBREW_TAP_REPO` (variable, optional)
|
|
223
|
-
- `HOMEBREW_TAP_GITHUB_TOKEN` (secret, optional)
|
|
224
|
-
|
|
225
|
-
See [RELEASE.md](./RELEASE.md) for full release steps and examples.
|
|
226
|
-
|
|
227
|
-
Quick preflight:
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
npm run release_check -- --tag v1.2.3
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
One-command release:
|
|
234
|
-
|
|
235
|
-
```bash
|
|
236
|
-
npm run release:stable -- --version 1.2.3
|
|
237
|
-
npm run release:beta -- --version 1.2.3 --pre 1
|
|
238
|
-
npm run release:rc -- --version 1.2.3 --pre 1
|
|
239
|
-
|
|
240
|
-
# preview only (no commit/tag/push)
|
|
241
|
-
npm run release:beta -- --version 1.2.3 --pre 1 --dry-run
|
|
242
|
-
```
|
|
197
|
+
See [RELEASE.md](./RELEASE.md) for publishing/release details.
|
|
198
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for code structure and contribution guidelines.
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { jsonFailure, jsonSuccess, printJsonEnvelope } from './lib/output.js';
|
|
|
12
12
|
import { bannerDisabled, renderBanner } from './lib/banner.js';
|
|
13
13
|
import { getUpdateNotice } from './lib/update-check.js';
|
|
14
14
|
import { requireInitialized } from './db/client.js';
|
|
15
|
-
import { renderAgentsMarkdown } from './templates/agents.js';
|
|
15
|
+
import { renderAgentsMarkdown, renderClaudeMarkdown } from './templates/agents.js';
|
|
16
16
|
import { refreshSummaryFile } from './services/summary-service.js';
|
|
17
17
|
import { buildContext } from './services/context-service.js';
|
|
18
18
|
import { getCapabilitiesManifest } from './services/capabilities-service.js';
|
|
@@ -58,6 +58,25 @@ function respondSuccess(command, asJson, data, lines = []) {
|
|
|
58
58
|
console.log(line);
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
function summaryWasRefreshedRecently(db, projectId) {
|
|
62
|
+
return Boolean(db
|
|
63
|
+
.prepare(`SELECT id FROM events WHERE project_id = ? AND type = 'summary_generated' AND created_at >= datetime('now', '-3 day') LIMIT 1`)
|
|
64
|
+
.get(projectId));
|
|
65
|
+
}
|
|
66
|
+
function renderSessionHygiene(command, asJson, warnings) {
|
|
67
|
+
if (asJson) {
|
|
68
|
+
printJsonEnvelope(jsonSuccess(command, { warnings, healthy: warnings.length === 0 }));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (warnings.length === 0) {
|
|
72
|
+
console.log('Session hygiene looks good.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log('Session hygiene warnings:');
|
|
76
|
+
for (const warning of warnings) {
|
|
77
|
+
console.log(`- ${warning}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
61
80
|
function renderContextText(data) {
|
|
62
81
|
const lines = [];
|
|
63
82
|
lines.push(`Project: ${data.projectName}`);
|
|
@@ -177,6 +196,15 @@ program
|
|
|
177
196
|
const rootPath = process.cwd();
|
|
178
197
|
const sidecar = getSidecarPaths(rootPath);
|
|
179
198
|
const projectName = opts.name?.trim() || path.basename(rootPath);
|
|
199
|
+
if (fs.existsSync(sidecar.sidecarPath)) {
|
|
200
|
+
const stat = fs.lstatSync(sidecar.sidecarPath);
|
|
201
|
+
if (stat.isSymbolicLink()) {
|
|
202
|
+
fail('Refusing to initialize: .sidecar is a symbolic link. Remove it and run init again.');
|
|
203
|
+
}
|
|
204
|
+
if (!stat.isDirectory()) {
|
|
205
|
+
fail('Refusing to initialize: .sidecar exists but is not a directory.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
180
208
|
if (fs.existsSync(sidecar.sidecarPath) && !opts.force) {
|
|
181
209
|
fail('Sidecar is already initialized in this project. Re-run with --force to recreate .sidecar files.');
|
|
182
210
|
}
|
|
@@ -187,6 +215,12 @@ program
|
|
|
187
215
|
sidecar.agentsPath,
|
|
188
216
|
sidecar.summaryPath,
|
|
189
217
|
];
|
|
218
|
+
const shouldWriteRootAgents = Boolean(opts.force) || !fs.existsSync(sidecar.rootAgentsPath);
|
|
219
|
+
const shouldWriteRootClaude = Boolean(opts.force) || !fs.existsSync(sidecar.rootClaudePath);
|
|
220
|
+
if (shouldWriteRootAgents)
|
|
221
|
+
files.push(sidecar.rootAgentsPath);
|
|
222
|
+
if (shouldWriteRootClaude)
|
|
223
|
+
files.push(sidecar.rootClaudePath);
|
|
190
224
|
fs.mkdirSync(sidecar.sidecarPath, { recursive: true });
|
|
191
225
|
if (opts.force) {
|
|
192
226
|
for (const file of [
|
|
@@ -220,6 +254,12 @@ program
|
|
|
220
254
|
output: { humanTime: true },
|
|
221
255
|
}));
|
|
222
256
|
fs.writeFileSync(sidecar.agentsPath, renderAgentsMarkdown(projectName));
|
|
257
|
+
if (shouldWriteRootAgents) {
|
|
258
|
+
fs.writeFileSync(sidecar.rootAgentsPath, renderAgentsMarkdown(projectName));
|
|
259
|
+
}
|
|
260
|
+
if (shouldWriteRootClaude) {
|
|
261
|
+
fs.writeFileSync(sidecar.rootClaudePath, renderClaudeMarkdown(projectName));
|
|
262
|
+
}
|
|
223
263
|
const db2 = new Database(sidecar.dbPath);
|
|
224
264
|
const refreshed = refreshSummaryFile(db2, rootPath, 1, 10);
|
|
225
265
|
db2.close();
|
|
@@ -632,22 +672,9 @@ session
|
|
|
632
672
|
const command = 'session verify';
|
|
633
673
|
try {
|
|
634
674
|
const { db, projectId } = requireInitialized();
|
|
635
|
-
const
|
|
636
|
-
.prepare(`SELECT id FROM events WHERE project_id = ? AND type = 'summary_generated' AND created_at >= datetime('now', '-3 day') LIMIT 1`)
|
|
637
|
-
.get(projectId));
|
|
638
|
-
const warnings = verifySessionHygiene(db, projectId, summaryRecent);
|
|
675
|
+
const warnings = verifySessionHygiene(db, projectId, summaryWasRefreshedRecently(db, projectId));
|
|
639
676
|
db.close();
|
|
640
|
-
|
|
641
|
-
printJsonEnvelope(jsonSuccess(command, { warnings, healthy: warnings.length === 0 }));
|
|
642
|
-
else {
|
|
643
|
-
if (warnings.length === 0) {
|
|
644
|
-
console.log('Session hygiene looks good.');
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
console.log('Session hygiene warnings:');
|
|
648
|
-
for (const w of warnings)
|
|
649
|
-
console.log(`- ${w}`);
|
|
650
|
-
}
|
|
677
|
+
renderSessionHygiene(command, Boolean(opts.json), warnings);
|
|
651
678
|
}
|
|
652
679
|
catch (err) {
|
|
653
680
|
handleCommandError(command, Boolean(opts.json), err);
|
|
@@ -662,22 +689,9 @@ program
|
|
|
662
689
|
const command = 'doctor';
|
|
663
690
|
try {
|
|
664
691
|
const { db, projectId } = requireInitialized();
|
|
665
|
-
const
|
|
666
|
-
.prepare(`SELECT id FROM events WHERE project_id = ? AND type = 'summary_generated' AND created_at >= datetime('now', '-3 day') LIMIT 1`)
|
|
667
|
-
.get(projectId));
|
|
668
|
-
const warnings = verifySessionHygiene(db, projectId, summaryRecent);
|
|
692
|
+
const warnings = verifySessionHygiene(db, projectId, summaryWasRefreshedRecently(db, projectId));
|
|
669
693
|
db.close();
|
|
670
|
-
|
|
671
|
-
printJsonEnvelope(jsonSuccess(command, { warnings, healthy: warnings.length === 0 }));
|
|
672
|
-
else {
|
|
673
|
-
if (warnings.length === 0) {
|
|
674
|
-
console.log('Session hygiene looks good.');
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
console.log('Session hygiene warnings:');
|
|
678
|
-
for (const w of warnings)
|
|
679
|
-
console.log(`- ${w}`);
|
|
680
|
-
}
|
|
694
|
+
renderSessionHygiene(command, Boolean(opts.json), warnings);
|
|
681
695
|
}
|
|
682
696
|
catch (err) {
|
|
683
697
|
handleCommandError(command, Boolean(opts.json), err);
|
package/dist/lib/paths.js
CHANGED
|
@@ -6,6 +6,8 @@ export function getSidecarPaths(rootPath) {
|
|
|
6
6
|
return {
|
|
7
7
|
rootPath,
|
|
8
8
|
sidecarPath,
|
|
9
|
+
rootAgentsPath: path.join(rootPath, 'AGENTS.md'),
|
|
10
|
+
rootClaudePath: path.join(rootPath, 'CLAUDE.md'),
|
|
9
11
|
dbPath: path.join(sidecarPath, 'sidecar.db'),
|
|
10
12
|
configPath: path.join(sidecarPath, 'config.json'),
|
|
11
13
|
preferencesPath: path.join(sidecarPath, 'preferences.json'),
|
|
@@ -16,8 +18,11 @@ export function getSidecarPaths(rootPath) {
|
|
|
16
18
|
export function findSidecarRoot(startDir = process.cwd()) {
|
|
17
19
|
let current = path.resolve(startDir);
|
|
18
20
|
while (true) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const candidate = path.join(current, SIDECAR_DIR);
|
|
22
|
+
if (fs.existsSync(candidate)) {
|
|
23
|
+
const stat = fs.lstatSync(candidate);
|
|
24
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
25
|
+
return current;
|
|
21
26
|
}
|
|
22
27
|
const parent = path.dirname(current);
|
|
23
28
|
if (parent === current)
|
package/dist/templates/agents.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
function renderSharedGuide(projectName, heading) {
|
|
2
2
|
return `# Sidecar Agent Guide
|
|
3
3
|
|
|
4
|
+
${heading}
|
|
5
|
+
|
|
4
6
|
Sidecar is the local project memory tool for this repository.
|
|
5
7
|
|
|
6
8
|
## MUST before final response
|
|
@@ -71,3 +73,9 @@ Run this before final response to catch missed Sidecar logging:
|
|
|
71
73
|
Project: ${projectName}.
|
|
72
74
|
`;
|
|
73
75
|
}
|
|
76
|
+
export function renderAgentsMarkdown(projectName) {
|
|
77
|
+
return renderSharedGuide(projectName, 'Use this guide for AI coding agents in this repository.');
|
|
78
|
+
}
|
|
79
|
+
export function renderClaudeMarkdown(projectName) {
|
|
80
|
+
return renderSharedGuide(projectName, 'Claude-specific note: treat this as required workflow, not advisory context.');
|
|
81
|
+
}
|
package/package.json
CHANGED
package/dist/lib/validation.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export const eventTypeSchema = z.enum([
|
|
3
|
-
'note',
|
|
4
|
-
'worklog',
|
|
5
|
-
'decision',
|
|
6
|
-
'task_update',
|
|
7
|
-
'summary',
|
|
8
|
-
'context',
|
|
9
|
-
]);
|
|
10
|
-
export const taskPrioritySchema = z.enum(['low', 'medium', 'high']);
|
|
11
|
-
export const addEventSchema = z.object({
|
|
12
|
-
type: eventTypeSchema,
|
|
13
|
-
title: z.string().trim().min(1).max(140),
|
|
14
|
-
body: z.string().trim().min(1),
|
|
15
|
-
tags: z.array(z.string().trim().min(1)).default([]),
|
|
16
|
-
});
|
|
17
|
-
export const addTaskSchema = z.object({
|
|
18
|
-
title: z.string().trim().min(1).max(240),
|
|
19
|
-
priority: taskPrioritySchema.default('medium'),
|
|
20
|
-
});
|
|
21
|
-
export const completeTaskSchema = z.object({
|
|
22
|
-
id: z.number().int().positive(),
|
|
23
|
-
});
|
|
24
|
-
export function parseTags(input) {
|
|
25
|
-
if (!input)
|
|
26
|
-
return [];
|
|
27
|
-
return input
|
|
28
|
-
.split(',')
|
|
29
|
-
.map((t) => t.trim())
|
|
30
|
-
.filter(Boolean);
|
|
31
|
-
}
|