@sudocode-ai/cli 0.1.21 → 0.1.23

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.
Files changed (62) hide show
  1. package/dist/cli/config-commands.d.ts +28 -0
  2. package/dist/cli/config-commands.d.ts.map +1 -0
  3. package/dist/cli/config-commands.js +203 -0
  4. package/dist/cli/config-commands.js.map +1 -0
  5. package/dist/cli/feedback-commands.d.ts.map +1 -1
  6. package/dist/cli/feedback-commands.js +4 -0
  7. package/dist/cli/feedback-commands.js.map +1 -1
  8. package/dist/cli/init-commands.d.ts +8 -0
  9. package/dist/cli/init-commands.d.ts.map +1 -1
  10. package/dist/cli/init-commands.js +61 -27
  11. package/dist/cli/init-commands.js.map +1 -1
  12. package/dist/cli/issue-commands.d.ts.map +1 -1
  13. package/dist/cli/issue-commands.js +16 -0
  14. package/dist/cli/issue-commands.js.map +1 -1
  15. package/dist/cli/query-commands.d.ts.map +1 -1
  16. package/dist/cli/query-commands.js +4 -0
  17. package/dist/cli/query-commands.js.map +1 -1
  18. package/dist/cli/reference-commands.d.ts.map +1 -1
  19. package/dist/cli/reference-commands.js +4 -1
  20. package/dist/cli/reference-commands.js.map +1 -1
  21. package/dist/cli/relationship-commands.d.ts.map +1 -1
  22. package/dist/cli/relationship-commands.js +7 -1
  23. package/dist/cli/relationship-commands.js.map +1 -1
  24. package/dist/cli/spec-commands.d.ts.map +1 -1
  25. package/dist/cli/spec-commands.js +13 -0
  26. package/dist/cli/spec-commands.js.map +1 -1
  27. package/dist/cli/sync-commands.d.ts.map +1 -1
  28. package/dist/cli/sync-commands.js +15 -2
  29. package/dist/cli/sync-commands.js.map +1 -1
  30. package/dist/cli/update-commands.d.ts.map +1 -1
  31. package/dist/cli/update-commands.js +251 -8
  32. package/dist/cli/update-commands.js.map +1 -1
  33. package/dist/cli.js +25 -0
  34. package/dist/cli.js.map +1 -1
  35. package/dist/config.d.ts +34 -3
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +181 -22
  38. package/dist/config.js.map +1 -1
  39. package/dist/import.d.ts.map +1 -1
  40. package/dist/import.js +20 -2
  41. package/dist/import.js.map +1 -1
  42. package/dist/install-source.d.ts +37 -0
  43. package/dist/install-source.d.ts.map +1 -0
  44. package/dist/install-source.js +136 -0
  45. package/dist/install-source.js.map +1 -0
  46. package/dist/operations/external-links.d.ts.map +1 -1
  47. package/dist/operations/external-links.js +3 -3
  48. package/dist/operations/external-links.js.map +1 -1
  49. package/dist/telemetry.d.ts +84 -0
  50. package/dist/telemetry.d.ts.map +1 -0
  51. package/dist/telemetry.js +410 -0
  52. package/dist/telemetry.js.map +1 -0
  53. package/dist/update-checker.d.ts.map +1 -1
  54. package/dist/update-checker.js +30 -2
  55. package/dist/update-checker.js.map +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/dist/version.js +16 -5
  58. package/dist/version.js.map +1 -1
  59. package/dist/watcher.d.ts.map +1 -1
  60. package/dist/watcher.js +222 -46
  61. package/dist/watcher.js.map +1 -1
  62. package/package.json +3 -2
@@ -6,7 +6,9 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import * as os from "os";
8
8
  import { VERSION } from "./version.js";
9
+ import { isBinaryInstall } from "./install-source.js";
9
10
  const PACKAGE_NAME = "@sudocode-ai/cli";
11
+ const GITHUB_REPO = "sudocode-ai/sudocode";
10
12
  const CACHE_DIR = path.join(os.tmpdir(), "sudocode-cli");
11
13
  const CACHE_FILE = path.join(CACHE_DIR, "update-cache.json");
12
14
  const DISMISS_FILE = path.join(CACHE_DIR, "update-dismissed.json");
@@ -32,6 +34,30 @@ async function fetchLatestVersion() {
32
34
  return null;
33
35
  }
34
36
  }
37
+ /**
38
+ * Fetch latest version from GitHub Releases (for binary installs)
39
+ */
40
+ async function fetchLatestVersionFromGitHub() {
41
+ try {
42
+ const response = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, {
43
+ headers: {
44
+ Accept: "application/vnd.github+json",
45
+ },
46
+ });
47
+ if (!response.ok) {
48
+ return null;
49
+ }
50
+ const data = (await response.json());
51
+ const tag = data.tag_name;
52
+ if (!tag)
53
+ return null;
54
+ // Strip leading "v" from tag (e.g. "v0.1.22" → "0.1.22")
55
+ return tag.startsWith("v") ? tag.slice(1) : tag;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
35
61
  /**
36
62
  * Read cached version info
37
63
  */
@@ -84,8 +110,10 @@ export async function checkForUpdates() {
84
110
  updateAvailable: VERSION !== cached.latest,
85
111
  };
86
112
  }
87
- // Fetch latest version from npm
88
- const latest = await fetchLatestVersion();
113
+ // Fetch latest version from appropriate source
114
+ const latest = isBinaryInstall()
115
+ ? await fetchLatestVersionFromGitHub()
116
+ : await fetchLatestVersion();
89
117
  if (!latest) {
90
118
  return null;
91
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"update-checker.js","sourceRoot":"","sources":["../src/update-checker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update-checker.js","sourceRoot":"","sources":["../src/update-checker.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,QAAe,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAgBnC;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,QAAe,CAAC"}
package/dist/version.js CHANGED
@@ -10,11 +10,22 @@ const __dirname = path.dirname(__filename);
10
10
  * Get the CLI version from package.json
11
11
  */
12
12
  export function getVersion() {
13
- // In development (running from src/), go up one level
14
- // In production (running from dist/), go up one level
15
- const packageJsonPath = path.join(__dirname, "..", "package.json");
16
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
17
- return packageJson.version;
13
+ // In compiled binaries (SEA/Bun), import.meta.url resolves to a virtual path.
14
+ // Fall back to reading package.json relative to the executable.
15
+ const candidates = [
16
+ path.join(__dirname, "..", "package.json"),
17
+ path.join(path.dirname(process.execPath), "..", "package.json"),
18
+ path.join(path.dirname(process.execPath), "package.json"),
19
+ ];
20
+ for (const p of candidates) {
21
+ try {
22
+ return JSON.parse(fs.readFileSync(p, "utf8")).version;
23
+ }
24
+ catch {
25
+ continue;
26
+ }
27
+ }
28
+ return "0.0.0-unknown";
18
29
  }
19
30
  /**
20
31
  * The current CLI version
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,sDAAsD;IACtD,sDAAsD;IACtD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;IACzE,OAAO,WAAW,CAAC,OAAO,CAAC;AAC7B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,8EAA8E;IAC9E,gEAAgE;IAChE,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC;QAC/D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;KAC1D,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAkB3C,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAuClF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAiCD,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAu0BpE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CA+BnE"}
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAoB3C,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AA2ElF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ7D;AAiCD,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC;IACtB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,YAAY,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CA4/BpE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CA+BnE"}
package/dist/watcher.js CHANGED
@@ -7,19 +7,43 @@ import * as path from "path";
7
7
  import * as fs from "fs";
8
8
  import { syncMarkdownToJSONL, syncJSONLToMarkdown } from "./sync.js";
9
9
  import { importFromJSONL } from "./import.js";
10
- import { listSpecs, getSpec, } from "./operations/specs.js";
11
- import { listIssues, getIssue } from "./operations/issues.js";
10
+ import { exportToJSONL } from "./export.js";
11
+ import { getSpecByFilePath, listSpecs, getSpec, deleteSpec, } from "./operations/specs.js";
12
+ import { listIssues, getIssue, deleteIssue } from "./operations/issues.js";
12
13
  import { parseMarkdownFile } from "./markdown.js";
13
14
  import { listFeedback } from "./operations/feedback.js";
14
15
  import { getTags } from "./operations/tags.js";
15
- import { findExistingEntityFile, generateUniqueFilename, } from "./filename-generator.js";
16
+ import { findExistingEntityFile, generateUniqueFilename, syncFileWithRename, } from "./filename-generator.js";
16
17
  import { getOutgoingRelationships } from "./operations/relationships.js";
17
18
  import * as crypto from "crypto";
19
+ import { getConfig, isMarkdownFirst } from "./config.js";
18
20
  // Guard against processing our own file writes (oscillation prevention)
19
21
  // Track files currently being processed to prevent same-file oscillation
20
22
  const filesBeingProcessed = new Set();
21
23
  // Content hash cache for detecting actual content changes (oscillation prevention)
22
24
  const contentHashCache = new Map();
25
+ // File path to entity ID cache for markdown-first mode deletions
26
+ // When a file is deleted, we need to know which entity to delete from DB
27
+ // Key: absolute file path, Value: { entityId, entityType }
28
+ const filePathToEntityCache = new Map();
29
+ /**
30
+ * Update the file path to entity mapping cache
31
+ */
32
+ function updateFilePathCache(filePath, entityId, entityType) {
33
+ filePathToEntityCache.set(filePath, { entityId, entityType });
34
+ }
35
+ /**
36
+ * Remove a file path from the entity mapping cache
37
+ */
38
+ function removeFilePathFromCache(filePath) {
39
+ filePathToEntityCache.delete(filePath);
40
+ }
41
+ /**
42
+ * Get entity info from file path cache
43
+ */
44
+ function getEntityFromFilePathCache(filePath) {
45
+ return filePathToEntityCache.get(filePath);
46
+ }
23
47
  // Simple async mutex to serialize file processing (prevents race conditions)
24
48
  // When markdown sync triggers JSONL export, we need to ensure the JSONL import
25
49
  // doesn't run until the export is complete
@@ -343,10 +367,50 @@ export function startWatcher(options) {
343
367
  // Markdown file changed - sync to database and JSONL
344
368
  onLog(`[watch] ${event} ${path.relative(baseDir, filePath)}`);
345
369
  if (event === "unlink") {
346
- // File was deleted - DB/JSONL is source of truth, so we don't delete entities
347
- // The file was likely deleted because the entity was deleted via API or JSONL
348
370
  const relPath = path.relative(baseDir, filePath);
349
- onLog(`[watch] Markdown file deleted: ${relPath} (DB/JSONL is source of truth)`);
371
+ const config = getConfig(baseDir);
372
+ if (isMarkdownFirst(config)) {
373
+ // Markdown is source of truth - delete entity from DB
374
+ const cachedEntity = getEntityFromFilePathCache(filePath);
375
+ if (cachedEntity) {
376
+ const { entityId, entityType } = cachedEntity;
377
+ try {
378
+ if (entityType === "spec") {
379
+ deleteSpec(db, entityId);
380
+ }
381
+ else {
382
+ deleteIssue(db, entityId);
383
+ }
384
+ onLog(`[watch] Deleted ${entityType} ${entityId} (markdown file removed, markdown is source of truth)`);
385
+ // Export to JSONL to reflect deletion
386
+ await exportToJSONL(db, { outputDir: baseDir });
387
+ removeFilePathFromCache(filePath);
388
+ }
389
+ catch (err) {
390
+ onError(new Error(`Failed to delete ${entityType} ${entityId} after file deletion: ${err}`));
391
+ }
392
+ }
393
+ else {
394
+ // Try to find entity by file path in DB (for specs)
395
+ const entityType = relPath.startsWith("specs/") ? "spec" : "issue";
396
+ if (entityType === "spec") {
397
+ const spec = getSpecByFilePath(db, relPath);
398
+ if (spec) {
399
+ deleteSpec(db, spec.id);
400
+ onLog(`[watch] Deleted spec ${spec.id} (markdown file removed, markdown is source of truth)`);
401
+ await exportToJSONL(db, { outputDir: baseDir });
402
+ }
403
+ }
404
+ // Issues don't have file_path in DB, so we can't look them up
405
+ onLog(`[watch] Markdown file deleted: ${relPath} (no cached entity mapping)`);
406
+ }
407
+ }
408
+ else {
409
+ // JSONL is source of truth - ignore file deletion
410
+ onLog(`[watch] Markdown file deleted: ${relPath} (DB/JSONL is source of truth, entity preserved)`);
411
+ }
412
+ removeFilePathFromCache(filePath);
413
+ return;
350
414
  }
351
415
  else {
352
416
  // Parse markdown to determine entity and sync direction
@@ -384,19 +448,28 @@ export function startWatcher(options) {
384
448
  onLog(`[watch] Skipping sync for ${entityType} ${entityId} (file content unchanged)`);
385
449
  syncDirection = "skip";
386
450
  }
387
- // Content differs - determine sync direction based on timestamps
451
+ // Content differs - determine sync direction based on config and timestamps
388
452
  else {
389
- const fileStat = fs.statSync(filePath);
390
- const fileTime = fileStat.mtimeMs;
391
- const dbTime = parseTimestampAsUTC(dbEntity.updated_at);
392
- if (dbTime > fileTime) {
393
- // DB is newer than file - sync DB markdown
394
- syncDirection = "db-to-markdown";
395
- onLog(`[watch] DB is newer for ${entityType} ${entityId}, syncing DB → markdown`);
453
+ const config = getConfig(baseDir);
454
+ if (isMarkdownFirst(config)) {
455
+ // Markdown is source of truth - always sync markdown → DB
456
+ syncDirection = "markdown-to-db";
457
+ onLog(`[watch] Syncing ${entityType} ${entityId} markdown DB (markdown is source of truth)`);
396
458
  }
397
459
  else {
398
- // File is newer than DB - sync markdown → DB (content update only)
399
- syncDirection = "markdown-to-db";
460
+ // JSONL/DB is source of truth - use timestamp comparison
461
+ const fileStat = fs.statSync(filePath);
462
+ const fileTime = fileStat.mtimeMs;
463
+ const dbTime = parseTimestampAsUTC(dbEntity.updated_at);
464
+ if (dbTime > fileTime) {
465
+ // DB is newer than file - sync DB → markdown
466
+ syncDirection = "db-to-markdown";
467
+ onLog(`[watch] DB is newer for ${entityType} ${entityId}, syncing DB → markdown`);
468
+ }
469
+ else {
470
+ // File is newer than DB - sync markdown → DB (content update only)
471
+ syncDirection = "markdown-to-db";
472
+ }
400
473
  }
401
474
  }
402
475
  }
@@ -406,14 +479,57 @@ export function startWatcher(options) {
406
479
  }
407
480
  // Handle orphaned files (no corresponding DB entry)
408
481
  if (syncDirection === "orphaned") {
409
- onLog(`[watch] Orphaned file detected: ${relPath} (no corresponding DB entry)`);
410
- // Delete orphaned file to maintain 1:1 mapping
411
- try {
412
- fs.unlinkSync(filePath);
413
- onLog(`[watch] Deleted orphaned file: ${relPath}`);
482
+ const config = getConfig(baseDir);
483
+ if (isMarkdownFirst(config)) {
484
+ // Markdown is source of truth - CREATE entity from markdown file
485
+ onLog(`[watch] Creating ${entityType} from orphaned file: ${relPath} (markdown is source of truth)`);
486
+ try {
487
+ const result = await syncMarkdownToJSONL(db, filePath, {
488
+ outputDir: baseDir,
489
+ autoExport: true,
490
+ autoInitialize: true, // Generate ID, set defaults
491
+ writeBackFrontmatter: true,
492
+ });
493
+ if (result.success && result.entityId) {
494
+ onLog(`[watch] Created ${entityType} ${result.entityId} from markdown file: ${relPath}`);
495
+ // Update file path cache
496
+ updateFilePathCache(filePath, result.entityId, entityType);
497
+ // Emit event
498
+ if (onEntitySync) {
499
+ const entity = entityType === "spec"
500
+ ? getSpec(db, result.entityId)
501
+ : getIssue(db, result.entityId);
502
+ await onEntitySync({
503
+ entityType,
504
+ entityId: result.entityId,
505
+ action: "created",
506
+ filePath,
507
+ baseDir,
508
+ source: "markdown",
509
+ timestamp: new Date(),
510
+ entity: entity ?? undefined,
511
+ version: 1,
512
+ });
513
+ }
514
+ }
515
+ else {
516
+ onError(new Error(`Failed to create ${entityType} from orphaned file ${relPath}: ${result.error || "Unknown error"}`));
517
+ }
518
+ }
519
+ catch (err) {
520
+ onError(new Error(`Failed to create ${entityType} from orphaned file ${relPath}: ${err}`));
521
+ }
414
522
  }
415
- catch (err) {
416
- onError(new Error(`Failed to delete orphaned file ${relPath}: ${err}`));
523
+ else {
524
+ // JSONL/DB is source of truth - delete orphaned file
525
+ onLog(`[watch] Orphaned file detected: ${relPath} (no corresponding DB entry)`);
526
+ try {
527
+ fs.unlinkSync(filePath);
528
+ onLog(`[watch] Deleted orphaned file: ${relPath}`);
529
+ }
530
+ catch (err) {
531
+ onError(new Error(`Failed to delete orphaned file ${relPath}: ${err}`));
532
+ }
417
533
  }
418
534
  return;
419
535
  }
@@ -456,6 +572,8 @@ export function startWatcher(options) {
456
572
  });
457
573
  if (result.success) {
458
574
  onLog(`[watch] Synced ${result.entityType} ${result.entityId} (${result.action})`);
575
+ // Update file path to entity cache for markdown-first deletions
576
+ updateFilePathCache(filePath, result.entityId, result.entityType);
459
577
  // Emit typed callback event for markdown sync
460
578
  if (onEntitySync) {
461
579
  // Get full entity data to include in event
@@ -541,17 +659,18 @@ export function startWatcher(options) {
541
659
  : getIssue(db, entityId);
542
660
  // Find markdown file path
543
661
  let entityFilePath;
544
- if (entityType === "spec" && entity && "file_path" in entity) {
662
+ const entityDir = path.join(baseDir, entityType === "spec" ? "specs" : "issues");
663
+ if (entityType === "spec" && entity && "file_path" in entity && entity.file_path) {
545
664
  entityFilePath = path.join(baseDir, entity.file_path);
546
665
  }
547
- else if (entityType === "issue" &&
548
- entity &&
549
- "file_path" in entity) {
550
- entityFilePath = path.join(baseDir, entity.file_path);
666
+ else if (entity && "title" in entity && entity.title) {
667
+ // Use syncFileWithRename to find existing file or generate proper filename
668
+ entityFilePath = syncFileWithRename(entityId, entityDir, entity.title);
551
669
  }
552
670
  else {
553
- // Fallback to default path
554
- entityFilePath = path.join(baseDir, entityType === "spec" ? "specs" : "issues", `${entityId}.md`);
671
+ // Last resort fallback — find by ID scan or generate with ID-only name
672
+ const existing = findExistingEntityFile(entityId, entityDir);
673
+ entityFilePath = existing ?? path.join(entityDir, `${entityId}.md`);
555
674
  }
556
675
  await onEntitySync({
557
676
  entityType,
@@ -659,7 +778,7 @@ export function startWatcher(options) {
659
778
  watcher.on("add", (filePath) => handleFileChange(filePath, "add"));
660
779
  watcher.on("change", (filePath) => handleFileChange(filePath, "change"));
661
780
  watcher.on("unlink", (filePath) => handleFileChange(filePath, "unlink"));
662
- watcher.on("ready", () => {
781
+ watcher.on("ready", async () => {
663
782
  const watched = watcher.getWatched();
664
783
  stats.filesWatched = Object.keys(watched).reduce((total, dir) => total + watched[dir].length, 0);
665
784
  onLog(`[watch] Watching ${stats.filesWatched} files in ${baseDir}`);
@@ -700,13 +819,50 @@ export function startWatcher(options) {
700
819
  onLog(`[watch] Warning: Failed to initialize JSONL cache: ${error instanceof Error ? error.message : String(error)}`);
701
820
  // Continue anyway - cache will be populated on first change
702
821
  }
703
- // Clean up orphaned markdown files on startup (files without corresponding DB entries)
704
- // This ensures 1:1 mapping between DB and markdown files
822
+ // Handle orphaned markdown files on startup (files without corresponding DB entries)
823
+ // Behavior depends on source of truth setting:
824
+ // - JSONL mode (default): Delete orphaned files to maintain 1:1 mapping
825
+ // - Markdown mode: Create entities from orphaned files (markdown is authoritative)
705
826
  try {
827
+ const config = getConfig(baseDir);
828
+ const markdownFirst = isMarkdownFirst(config);
706
829
  let orphanedCount = 0;
830
+ let createdCount = 0;
707
831
  // Build set of valid entity IDs from database
708
832
  const validSpecIds = new Set(listSpecs(db).map((s) => s.id));
709
833
  const validIssueIds = new Set(listIssues(db).map((i) => i.id));
834
+ // Helper function to handle orphaned file
835
+ const handleOrphanedFile = async (filePath, entityType, relPath) => {
836
+ if (markdownFirst) {
837
+ // Markdown is source of truth - create entity from file
838
+ try {
839
+ const result = await syncMarkdownToJSONL(db, filePath, {
840
+ outputDir: baseDir,
841
+ autoExport: false, // Don't export yet, we'll batch export after
842
+ autoInitialize: true,
843
+ writeBackFrontmatter: true,
844
+ });
845
+ if (result.success && result.entityId) {
846
+ createdCount++;
847
+ onLog(`[watch] Created ${entityType} ${result.entityId} from orphaned file: ${relPath}`);
848
+ // Update file path cache
849
+ updateFilePathCache(filePath, result.entityId, entityType);
850
+ }
851
+ else {
852
+ onLog(`[watch] Warning: Failed to create ${entityType} from ${relPath}: ${result.error || "Unknown error"}`);
853
+ }
854
+ }
855
+ catch (err) {
856
+ onLog(`[watch] Warning: Failed to create ${entityType} from ${relPath}: ${err}`);
857
+ }
858
+ }
859
+ else {
860
+ // JSONL is source of truth - delete orphaned file
861
+ fs.unlinkSync(filePath);
862
+ orphanedCount++;
863
+ onLog(`[watch] Deleted orphaned ${entityType} file: ${relPath}`);
864
+ }
865
+ };
710
866
  // Check specs directory
711
867
  const specsPath = path.join(baseDir, "specs");
712
868
  if (fs.existsSync(specsPath)) {
@@ -719,16 +875,24 @@ export function startWatcher(options) {
719
875
  const parsed = parseMarkdownFile(filePath, db, baseDir);
720
876
  const entityId = parsed.data.id;
721
877
  if (!entityId || !validSpecIds.has(entityId)) {
722
- fs.unlinkSync(filePath);
723
- orphanedCount++;
724
- onLog(`[watch] Deleted orphaned spec file: specs/${file}`);
878
+ await handleOrphanedFile(filePath, "spec", `specs/${file}`);
879
+ }
880
+ else {
881
+ // File has valid entity - update cache
882
+ updateFilePathCache(filePath, entityId, "spec");
725
883
  }
726
884
  }
727
885
  catch {
728
886
  // If parsing fails, treat as orphaned
729
- fs.unlinkSync(filePath);
730
- orphanedCount++;
731
- onLog(`[watch] Deleted orphaned spec file (invalid): specs/${file}`);
887
+ if (markdownFirst) {
888
+ // Try to create entity even from invalid file (autoInitialize will handle it)
889
+ await handleOrphanedFile(filePath, "spec", `specs/${file}`);
890
+ }
891
+ else {
892
+ fs.unlinkSync(filePath);
893
+ orphanedCount++;
894
+ onLog(`[watch] Deleted orphaned spec file (invalid): specs/${file}`);
895
+ }
732
896
  }
733
897
  }
734
898
  }
@@ -744,25 +908,37 @@ export function startWatcher(options) {
744
908
  const parsed = parseMarkdownFile(filePath, db, baseDir);
745
909
  const entityId = parsed.data.id;
746
910
  if (!entityId || !validIssueIds.has(entityId)) {
747
- fs.unlinkSync(filePath);
748
- orphanedCount++;
749
- onLog(`[watch] Deleted orphaned issue file: issues/${file}`);
911
+ await handleOrphanedFile(filePath, "issue", `issues/${file}`);
912
+ }
913
+ else {
914
+ // File has valid entity - update cache
915
+ updateFilePathCache(filePath, entityId, "issue");
750
916
  }
751
917
  }
752
918
  catch {
753
919
  // If parsing fails, treat as orphaned
754
- fs.unlinkSync(filePath);
755
- orphanedCount++;
756
- onLog(`[watch] Deleted orphaned issue file (invalid): issues/${file}`);
920
+ if (markdownFirst) {
921
+ await handleOrphanedFile(filePath, "issue", `issues/${file}`);
922
+ }
923
+ else {
924
+ fs.unlinkSync(filePath);
925
+ orphanedCount++;
926
+ onLog(`[watch] Deleted orphaned issue file (invalid): issues/${file}`);
927
+ }
757
928
  }
758
929
  }
759
930
  }
931
+ // Batch export to JSONL if we created entities
932
+ if (createdCount > 0) {
933
+ await exportToJSONL(db, { outputDir: baseDir });
934
+ onLog(`[watch] Created ${createdCount} entities from orphaned markdown files`);
935
+ }
760
936
  if (orphanedCount > 0) {
761
937
  onLog(`[watch] Cleaned up ${orphanedCount} orphaned markdown file(s)`);
762
938
  }
763
939
  }
764
940
  catch (error) {
765
- onLog(`[watch] Warning: Failed to clean up orphaned files: ${error instanceof Error ? error.message : String(error)}`);
941
+ onLog(`[watch] Warning: Failed to process orphaned files: ${error instanceof Error ? error.message : String(error)}`);
766
942
  }
767
943
  });
768
944
  watcher.on("error", (error) => {