sidecar-cli 0.1.0-beta.5 → 0.1.0-beta.7
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 +21 -6
- package/dist/cli.js +38 -30
- package/dist/lib/paths.js +6 -2
- package/dist/templates/agents.js +11 -0
- package/package.json +3 -2
- package/dist/lib/validation.js +0 -31
package/README.md
CHANGED
|
@@ -144,14 +144,28 @@ sidecar session end --summary "Initialization and recording flow implemented"
|
|
|
144
144
|
## AI agent usage
|
|
145
145
|
|
|
146
146
|
Sidecar generates `.sidecar/AGENTS.md` during `init`.
|
|
147
|
+
This repo also includes a root `AGENTS.md` so the policy is visible before any `.sidecar` lookup.
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
Required minimum for any code change:
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
|
|
151
|
+
1. `sidecar context --format markdown`
|
|
152
|
+
2. `sidecar worklog record --done "<what changed>" --files <paths> --by agent`
|
|
153
|
+
3. if behavior/design changed: `sidecar decision record ...`
|
|
154
|
+
4. if follow-up exists: `sidecar task add ...`
|
|
155
|
+
5. `sidecar summary refresh`
|
|
156
|
+
|
|
157
|
+
Optional local enforcement:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run install:hooks
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
This installs a non-blocking pre-commit reminder that runs `npm run sidecar:reminder`.
|
|
164
|
+
If a pre-commit hook already exists, Sidecar will not overwrite it unless you run:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm run install:hooks -- --force
|
|
168
|
+
```
|
|
155
169
|
|
|
156
170
|
Agents can discover the CLI surface programmatically with:
|
|
157
171
|
|
|
@@ -214,6 +228,7 @@ Required configuration:
|
|
|
214
228
|
- `HOMEBREW_TAP_GITHUB_TOKEN` (secret, optional)
|
|
215
229
|
|
|
216
230
|
See [RELEASE.md](./RELEASE.md) for full release steps and examples.
|
|
231
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for code structure and contribution guidelines.
|
|
217
232
|
|
|
218
233
|
Quick preflight:
|
|
219
234
|
|
package/dist/cli.js
CHANGED
|
@@ -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,9 @@ program
|
|
|
187
215
|
sidecar.agentsPath,
|
|
188
216
|
sidecar.summaryPath,
|
|
189
217
|
];
|
|
218
|
+
const shouldWriteRootAgents = Boolean(opts.force) || !fs.existsSync(sidecar.rootAgentsPath);
|
|
219
|
+
if (shouldWriteRootAgents)
|
|
220
|
+
files.push(sidecar.rootAgentsPath);
|
|
190
221
|
fs.mkdirSync(sidecar.sidecarPath, { recursive: true });
|
|
191
222
|
if (opts.force) {
|
|
192
223
|
for (const file of [
|
|
@@ -220,6 +251,9 @@ program
|
|
|
220
251
|
output: { humanTime: true },
|
|
221
252
|
}));
|
|
222
253
|
fs.writeFileSync(sidecar.agentsPath, renderAgentsMarkdown(projectName));
|
|
254
|
+
if (shouldWriteRootAgents) {
|
|
255
|
+
fs.writeFileSync(sidecar.rootAgentsPath, renderAgentsMarkdown(projectName));
|
|
256
|
+
}
|
|
223
257
|
const db2 = new Database(sidecar.dbPath);
|
|
224
258
|
const refreshed = refreshSummaryFile(db2, rootPath, 1, 10);
|
|
225
259
|
db2.close();
|
|
@@ -632,22 +666,9 @@ session
|
|
|
632
666
|
const command = 'session verify';
|
|
633
667
|
try {
|
|
634
668
|
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);
|
|
669
|
+
const warnings = verifySessionHygiene(db, projectId, summaryWasRefreshedRecently(db, projectId));
|
|
639
670
|
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
|
-
}
|
|
671
|
+
renderSessionHygiene(command, Boolean(opts.json), warnings);
|
|
651
672
|
}
|
|
652
673
|
catch (err) {
|
|
653
674
|
handleCommandError(command, Boolean(opts.json), err);
|
|
@@ -662,22 +683,9 @@ program
|
|
|
662
683
|
const command = 'doctor';
|
|
663
684
|
try {
|
|
664
685
|
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);
|
|
686
|
+
const warnings = verifySessionHygiene(db, projectId, summaryWasRefreshedRecently(db, projectId));
|
|
669
687
|
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
|
-
}
|
|
688
|
+
renderSessionHygiene(command, Boolean(opts.json), warnings);
|
|
681
689
|
}
|
|
682
690
|
catch (err) {
|
|
683
691
|
handleCommandError(command, Boolean(opts.json), err);
|
package/dist/lib/paths.js
CHANGED
|
@@ -6,6 +6,7 @@ export function getSidecarPaths(rootPath) {
|
|
|
6
6
|
return {
|
|
7
7
|
rootPath,
|
|
8
8
|
sidecarPath,
|
|
9
|
+
rootAgentsPath: path.join(rootPath, 'AGENTS.md'),
|
|
9
10
|
dbPath: path.join(sidecarPath, 'sidecar.db'),
|
|
10
11
|
configPath: path.join(sidecarPath, 'config.json'),
|
|
11
12
|
preferencesPath: path.join(sidecarPath, 'preferences.json'),
|
|
@@ -16,8 +17,11 @@ export function getSidecarPaths(rootPath) {
|
|
|
16
17
|
export function findSidecarRoot(startDir = process.cwd()) {
|
|
17
18
|
let current = path.resolve(startDir);
|
|
18
19
|
while (true) {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const candidate = path.join(current, SIDECAR_DIR);
|
|
21
|
+
if (fs.existsSync(candidate)) {
|
|
22
|
+
const stat = fs.lstatSync(candidate);
|
|
23
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
24
|
+
return current;
|
|
21
25
|
}
|
|
22
26
|
const parent = path.dirname(current);
|
|
23
27
|
if (parent === current)
|
package/dist/templates/agents.js
CHANGED
|
@@ -3,6 +3,16 @@ export function renderAgentsMarkdown(projectName) {
|
|
|
3
3
|
|
|
4
4
|
Sidecar is the local project memory tool for this repository.
|
|
5
5
|
|
|
6
|
+
## MUST before final response
|
|
7
|
+
|
|
8
|
+
If you changed code, you MUST run these commands in order:
|
|
9
|
+
|
|
10
|
+
1. \`sidecar context --format markdown\`
|
|
11
|
+
2. \`sidecar worklog record --done "<what changed>" --files <paths> --by agent\`
|
|
12
|
+
3. if behavior/design/architecture changed: \`sidecar decision record --title "<decision>" --summary "<why>" --by agent\`
|
|
13
|
+
4. if follow-up exists: \`sidecar task add "<follow-up>" --priority medium --by agent\`
|
|
14
|
+
5. \`sidecar summary refresh\`
|
|
15
|
+
|
|
6
16
|
## Required workflow
|
|
7
17
|
|
|
8
18
|
For any code change, run these in order before final response:
|
|
@@ -18,6 +28,7 @@ For any code change, run these in order before final response:
|
|
|
18
28
|
- If files changed: always record a worklog.
|
|
19
29
|
- If behavior/design choice changed: record a decision.
|
|
20
30
|
- If follow-up work exists: add a task.
|
|
31
|
+
- Never skip summary refresh after recording work.
|
|
21
32
|
|
|
22
33
|
## Definition of Done
|
|
23
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sidecar-cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.7",
|
|
4
4
|
"description": "Local-first project memory and recording tool",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npm run clean && tsc -p tsconfig.json",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"release:stable": "node scripts/cut-release.mjs --channel stable",
|
|
15
15
|
"release:beta": "node scripts/cut-release.mjs --channel beta",
|
|
16
16
|
"release:rc": "node scripts/cut-release.mjs --channel rc",
|
|
17
|
-
"sidecar:reminder": "node scripts/sidecar-reminder.mjs"
|
|
17
|
+
"sidecar:reminder": "node scripts/sidecar-reminder.mjs",
|
|
18
|
+
"install:hooks": "node scripts/install-git-hooks.mjs"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [],
|
|
20
21
|
"author": "",
|
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
|
-
}
|