ctxr-cli 0.39.0 → 0.40.0

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/dist/version.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Kept in sync with package.json's "version" field. */
2
- export declare const CLI_VERSION = "0.39.0";
2
+ export declare const CLI_VERSION = "0.40.0";
3
3
  /**
4
4
  * The npm package this executable is published as. Bound here, beside the
5
5
  * version, because the release check resolves the latest published release by
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Kept in sync with package.json's "version" field. */
2
- export const CLI_VERSION = '0.39.0';
2
+ export const CLI_VERSION = '0.40.0';
3
3
  /**
4
4
  * The npm package this executable is published as. Bound here, beside the
5
5
  * version, because the release check resolves the latest published release by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxr-cli",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Context store for AI coding agents — markdown and git, catalog and graph retrieval, no vector database.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  // what was already written: the next run picks them up.
22
22
 
23
23
  import { execFileSync } from 'node:child_process';
24
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
24
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
25
25
  import { tmpdir } from 'node:os';
26
26
  import path from 'node:path';
27
27
  import { fileURLToPath } from 'node:url';
@@ -187,9 +187,17 @@ function prepareWorktree(root, settings) {
187
187
  return { worktree, continuing: open[0]?.number };
188
188
  }
189
189
 
190
- function captureFileName(meeting) {
191
- const slug = meeting.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || meeting.id;
192
- return `circleback-${meeting.createdAt.slice(0, 10)}-${slug}.md`;
190
+ /**
191
+ * Where a meeting's record goes in the inbox: named for its date and title, with the meeting id added when that
192
+ * name is already taken. The slug keeps only ASCII letters and digits, so two titles mostly in another script can
193
+ * reduce to the same name, and one record must never overwrite another, from this run or already in the inbox.
194
+ */
195
+ function captureFilePath(inbox, meeting) {
196
+ const date = meeting.createdAt.slice(0, 10);
197
+ const slug = meeting.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
198
+ const named = path.join(inbox, `circleback-${date}-${slug || meeting.id}.md`);
199
+ if (!existsSync(named)) return named;
200
+ return path.join(inbox, `circleback-${date}-${slug ? `${slug}-` : ''}${meeting.id}.md`);
193
201
  }
194
202
 
195
203
  /** The mechanical part of a capture. The speaker judgment is the agent's, added afterwards. */
@@ -300,7 +308,7 @@ function main() {
300
308
  deferred.push(line(meeting));
301
309
  continue;
302
310
  }
303
- const file = path.join(inbox, captureFileName(meeting));
311
+ const file = captureFilePath(inbox, meeting);
304
312
  writeFileSync(file, renderCapture(meeting, transcript, settings));
305
313
  report.push(file);
306
314
  }