sidecar-cli 0.1.0-beta.6 → 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 CHANGED
@@ -161,6 +161,11 @@ npm run install:hooks
161
161
  ```
162
162
 
163
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
+ ```
164
169
 
165
170
  Agents can discover the CLI surface programmatically with:
166
171
 
@@ -223,6 +228,7 @@ Required configuration:
223
228
  - `HOMEBREW_TAP_GITHUB_TOKEN` (secret, optional)
224
229
 
225
230
  See [RELEASE.md](./RELEASE.md) for full release steps and examples.
231
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for code structure and contribution guidelines.
226
232
 
227
233
  Quick preflight:
228
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 summaryRecent = Boolean(db
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
- if (opts.json)
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 summaryRecent = Boolean(db
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
- if (opts.json)
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
- if (fs.existsSync(path.join(current, SIDECAR_DIR))) {
20
- return current;
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidecar-cli",
3
- "version": "0.1.0-beta.6",
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",
@@ -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
- }