get-tbd 0.1.24 → 0.1.26

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 (45) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.mjs +195 -49
  3. package/dist/bin.mjs.map +1 -1
  4. package/dist/cli.mjs +126 -44
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{config-CB1tcqTZ.mjs → config-BZte2m3w.mjs} +1 -1
  7. package/dist/{config-CmEAGaxz.mjs → config-b20Kf5pW.mjs} +3 -2
  8. package/dist/config-b20Kf5pW.mjs.map +1 -0
  9. package/dist/docs/README.md +2 -2
  10. package/dist/docs/SKILL.md +31 -31
  11. package/dist/docs/guidelines/cli-agent-skill-patterns.md +1 -1
  12. package/dist/docs/guidelines/convex-limits-best-practices.md +16 -16
  13. package/dist/docs/guidelines/convex-rules.md +3 -3
  14. package/dist/docs/guidelines/electron-app-development-patterns.md +1 -1
  15. package/dist/docs/guidelines/error-handling-rules.md +2 -2
  16. package/dist/docs/guidelines/general-coding-rules.md +2 -2
  17. package/dist/docs/guidelines/general-comment-rules.md +2 -2
  18. package/dist/docs/guidelines/general-eng-assistant-rules.md +2 -2
  19. package/dist/docs/guidelines/python-rules.md +4 -4
  20. package/dist/docs/guidelines/typescript-rules.md +17 -17
  21. package/dist/docs/guidelines/typescript-yaml-handling-rules.md +8 -8
  22. package/dist/docs/shortcuts/standard/new-guideline.md +4 -4
  23. package/dist/docs/shortcuts/standard/new-validation-plan.md +13 -13
  24. package/dist/docs/shortcuts/standard/revise-all-architecture-docs.md +1 -1
  25. package/dist/docs/shortcuts/standard/setup-github-cli.md +1 -1
  26. package/dist/docs/shortcuts/standard/welcome-user.md +12 -12
  27. package/dist/docs/shortcuts/system/skill-baseline.md +31 -31
  28. package/dist/id-mapping-BA_xn516.mjs +3 -0
  29. package/dist/{id-mapping-DjVJIO4M.mjs → id-mapping-BtBwq5nG.mjs} +68 -15
  30. package/dist/id-mapping-BtBwq5nG.mjs.map +1 -0
  31. package/dist/index.mjs +2 -2
  32. package/dist/schemas-BQYmDnkv.mjs +311 -0
  33. package/dist/schemas-BQYmDnkv.mjs.map +1 -0
  34. package/dist/{src-BrM6xcdG.mjs → src-DQcOQnFp.mjs} +4 -3
  35. package/dist/{src-BrM6xcdG.mjs.map → src-DQcOQnFp.mjs.map} +1 -1
  36. package/dist/tbd +195 -49
  37. package/dist/yaml-utils-BPy991by.mjs +273 -0
  38. package/dist/yaml-utils-BPy991by.mjs.map +1 -0
  39. package/dist/yaml-utils-swV780m5.mjs +3 -0
  40. package/package.json +1 -1
  41. package/dist/config-CmEAGaxz.mjs.map +0 -1
  42. package/dist/id-mapping-DjVJIO4M.mjs.map +0 -1
  43. package/dist/id-mapping-LjnDSEhN.mjs +0 -3
  44. package/dist/yaml-utils-U7l9hhkh.mjs +0 -581
  45. package/dist/yaml-utils-U7l9hhkh.mjs.map +0 -1
package/dist/cli.mjs CHANGED
@@ -1,7 +1,8 @@
1
- import { D as IssueKind, a as stringifyYaml, c as ordering, d as ATTIC_ENTRY_FIELD_ORDER, f as AtticEntrySchema, i as sortKeys, k as IssueStatus, l as PAGINATION_LINE_THRESHOLD, r as parseYamlWithConflictDetection, s as comparisonChain, t as detectDuplicateYamlKeys, u as PARENT_CONTEXT_MAX_LINES } from "./yaml-utils-U7l9hhkh.mjs";
2
- import { a as insertAfterFrontmatter, c as noopLogger, i as serializeIssue, n as parseIssue, o as parseMarkdown, r as parseMarkdownWithFrontmatter, s as stripFrontmatter, t as VERSION$1 } from "./src-BrM6xcdG.mjs";
3
- import { A as isValidWorkspaceName, C as TBD_SHORTCUTS_STANDARD, D as WORKTREE_DIR, E as WORKSPACES_DIR, M as resolveDataSyncDir, O as WORKTREE_DIR_NAME, S as TBD_GUIDELINES_DIR, T as TBD_TEMPLATES_DIR, _ as DEFAULT_SHORTCUT_PATHS, a as isInitialized, b as TBD_DIR, c as readConfigWithMigration, d as writeConfig, g as DEFAULT_GUIDELINES_PATHS, h as DATA_SYNC_DIR_NAME, i as initConfig, j as resolveAtticDir, k as getWorkspaceDir, l as readLocalState, m as DATA_SYNC_DIR, n as findTbdRoot, o as markWelcomeSeen, p as CHARS_PER_TOKEN, r as hasSeenWelcome, s as readConfig, u as updateLocalState, v as DEFAULT_TEMPLATE_PATHS, w as TBD_SHORTCUTS_SYSTEM, x as TBD_DOCS_DIR, y as SYNC_BRANCH } from "./config-CmEAGaxz.mjs";
4
- import { _ as generateInternalId, a as hasShortId, b as validateIssueId, c as parseIdMappingFromYaml, d as saveIdMapping, f as extractPrefix, g as formatDisplayId, h as formatDebugId, i as generateUniqueShortId, l as reconcileMappings, m as extractUlidFromInternalId, o as loadIdMapping, p as extractShortId, s as mergeIdMappings, t as addIdMapping, u as resolveToInternalId, v as makeInternalId, y as normalizeIssueId } from "./id-mapping-DjVJIO4M.mjs";
1
+ import { _ as IssueKind, n as AtticEntrySchema, t as ATTIC_ENTRY_FIELD_ORDER, y as IssueStatus } from "./schemas-BQYmDnkv.mjs";
2
+ import { a as insertAfterFrontmatter, c as noopLogger, i as serializeIssue, n as parseIssue, o as parseMarkdown, r as parseMarkdownWithFrontmatter, s as stripFrontmatter, t as VERSION$1 } from "./src-DQcOQnFp.mjs";
3
+ import { a as parseYamlWithConflictDetection, d as PAGINATION_LINE_THRESHOLD, f as PARENT_CONTEXT_MAX_LINES, l as comparisonChain, n as detectDuplicateYamlKeys, o as sortKeys, s as stringifyYaml, u as ordering } from "./yaml-utils-BPy991by.mjs";
4
+ import { A as isValidWorkspaceName, C as TBD_SHORTCUTS_STANDARD, D as WORKTREE_DIR, E as WORKSPACES_DIR, M as resolveDataSyncDir, O as WORKTREE_DIR_NAME, S as TBD_GUIDELINES_DIR, T as TBD_TEMPLATES_DIR, _ as DEFAULT_SHORTCUT_PATHS, a as isInitialized, b as TBD_DIR, c as readConfigWithMigration, d as writeConfig, g as DEFAULT_GUIDELINES_PATHS, h as DATA_SYNC_DIR_NAME, i as initConfig, j as resolveAtticDir, k as getWorkspaceDir, l as readLocalState, m as DATA_SYNC_DIR, n as findTbdRoot, o as markWelcomeSeen, p as CHARS_PER_TOKEN, r as hasSeenWelcome, s as readConfig, u as updateLocalState, v as DEFAULT_TEMPLATE_PATHS, w as TBD_SHORTCUTS_SYSTEM, x as TBD_DOCS_DIR, y as SYNC_BRANCH } from "./config-b20Kf5pW.mjs";
5
+ import { _ as formatDisplayId, a as hasShortId, b as normalizeIssueId, c as parseIdMappingFromYaml, d as resolveToInternalId, f as saveIdMapping, g as formatDebugId, h as extractUlidFromInternalId, i as generateUniqueShortId, l as reconcileMappings, m as extractShortId, o as loadIdMapping, p as extractPrefix, s as mergeIdMappings, t as addIdMapping, u as resolveIdMappingConflicts, v as generateInternalId, x as validateIssueId, y as makeInternalId } from "./id-mapping-BtBwq5nG.mjs";
5
6
  import { createRequire } from "node:module";
6
7
  import matter from "gray-matter";
7
8
  import { parse } from "yaml";
@@ -1514,6 +1515,7 @@ async function initWorktree(baseDir, remote = "origin", syncBranch = SYNC_BRANCH
1514
1515
  await writeFile(join(dataSyncPath, "meta.yml"), "schema_version: 1\n");
1515
1516
  await writeFile(join(dataSyncPath, "issues", ".gitkeep"), "");
1516
1517
  await writeFile(join(dataSyncPath, "mappings", ".gitkeep"), "");
1518
+ await writeFile(join(dataSyncPath, "mappings", ".gitattributes"), "ids.yml merge=union\n");
1517
1519
  await git("-C", worktreePath, "add", ".");
1518
1520
  await git("-C", worktreePath, "commit", "--no-verify", "-m", "Initialize tbd-sync branch");
1519
1521
  return {
@@ -1751,9 +1753,16 @@ async function migrateDataToWorktree(baseDir, removeSource = false) {
1751
1753
  await mkdir(correctMappingsPath, { recursive: true });
1752
1754
  for (const file of issueFiles) await cp(join(wrongIssuesPath, file), join(correctIssuesPath, file));
1753
1755
  for (const file of mappingFiles) if (file === "ids.yml") {
1754
- const { loadIdMapping, mergeIdMappings, saveIdMapping } = await import("./id-mapping-LjnDSEhN.mjs");
1755
- const sourceMapping = await loadIdMapping(wrongPath);
1756
- await saveIdMapping(correctPath, mergeIdMappings(await loadIdMapping(correctPath), sourceMapping));
1756
+ const { readFile } = await import("node:fs/promises");
1757
+ const { loadIdMapping, mergeIdMappings, saveIdMapping, resolveIdMappingConflicts } = await import("./id-mapping-BA_xn516.mjs");
1758
+ const sourceMapping = resolveIdMappingConflicts(await readFile(join(wrongMappingsPath, file), "utf-8"));
1759
+ let targetMapping;
1760
+ try {
1761
+ targetMapping = resolveIdMappingConflicts(await readFile(join(correctMappingsPath, file), "utf-8"));
1762
+ } catch {
1763
+ targetMapping = await loadIdMapping(correctPath);
1764
+ }
1765
+ await saveIdMapping(correctPath, mergeIdMappings(targetMapping, sourceMapping));
1757
1766
  } else await cp(join(wrongMappingsPath, file), join(correctMappingsPath, file));
1758
1767
  const totalFiles = issueFiles.length + mappingFiles.length;
1759
1768
  await git("-C", worktreePath, "add", "-A");
@@ -1919,28 +1928,32 @@ async function listIssues(baseDir) {
1919
1928
  return [];
1920
1929
  }
1921
1930
  const mdFiles = files.filter((f) => f.endsWith(".md"));
1922
- const fileContents = await Promise.all(mdFiles.map(async (file) => {
1923
- const filePath = join(issuesDir, file);
1924
- try {
1925
- return {
1926
- file,
1927
- content: await readFile(filePath, "utf-8")
1928
- };
1929
- } catch {
1930
- return {
1931
- file,
1932
- content: null
1933
- };
1934
- }
1935
- }));
1931
+ const BATCH_SIZE = 200;
1936
1932
  const issues = [];
1937
- for (const { file, content } of fileContents) {
1938
- if (content === null) continue;
1939
- try {
1940
- const issue = parseIssue(content);
1941
- issues.push(issue);
1942
- } catch (error) {
1943
- console.warn(`Skipping invalid issue file: ${file}`, error);
1933
+ for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
1934
+ const batch = mdFiles.slice(i, i + BATCH_SIZE);
1935
+ const fileContents = await Promise.all(batch.map(async (file) => {
1936
+ const filePath = join(issuesDir, file);
1937
+ try {
1938
+ return {
1939
+ file,
1940
+ content: await readFile(filePath, "utf-8")
1941
+ };
1942
+ } catch {
1943
+ return {
1944
+ file,
1945
+ content: null
1946
+ };
1947
+ }
1948
+ }));
1949
+ for (const { file, content } of fileContents) {
1950
+ if (content === null) continue;
1951
+ try {
1952
+ const issue = parseIssue(content);
1953
+ issues.push(issue);
1954
+ } catch (error) {
1955
+ console.warn(`Skipping invalid issue file: ${file}`, error);
1956
+ }
1944
1957
  }
1945
1958
  }
1946
1959
  return issues;
@@ -2715,10 +2728,7 @@ function matchesSpecPath(storedPath, queryPath) {
2715
2728
  if (!normalizedStored || !normalizedQuery) return false;
2716
2729
  if (normalizedStored === normalizedQuery) return true;
2717
2730
  if (normalizedStored.endsWith("/" + normalizedQuery)) return true;
2718
- const storedFilename = basename(normalizedStored);
2719
- const queryFilename = basename(normalizedQuery);
2720
- if (!normalizedQuery.includes("/") && storedFilename === normalizedQuery) return true;
2721
- if (!normalizedQuery.includes("/") && storedFilename === queryFilename) return true;
2731
+ if (!normalizedQuery.includes("/") && basename(normalizedStored) === normalizedQuery) return true;
2722
2732
  return false;
2723
2733
  }
2724
2734
  /**
@@ -4063,7 +4073,7 @@ async function fetchWithGhFallback(url, options) {
4063
4073
  * See: docs/project/specs/active/plan-2026-01-26-configurable-doc-cache-sync.md
4064
4074
  */
4065
4075
  /** Prefix for internal bundled doc sources */
4066
- const INTERNAL_PREFIX = "internal:";
4076
+ const INTERNAL_SOURCE_PREFIX = "internal:";
4067
4077
  /**
4068
4078
  * Syncs documentation files from configured sources.
4069
4079
  *
@@ -4096,7 +4106,7 @@ var DocSync = class {
4096
4106
  * // => { type: 'url', location: 'https://...' }
4097
4107
  */
4098
4108
  parseSource(source) {
4099
- if (source.startsWith(INTERNAL_PREFIX)) return {
4109
+ if (source.startsWith(INTERNAL_SOURCE_PREFIX)) return {
4100
4110
  type: "internal",
4101
4111
  location: source.slice(9)
4102
4112
  };
@@ -4275,7 +4285,7 @@ async function generateDefaultDocCacheConfig() {
4275
4285
  const entries = await readdir(fullDir, { withFileTypes: true });
4276
4286
  for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".md")) {
4277
4287
  const relativePath = `${prefix}/${entry.name}`;
4278
- config[relativePath] = `${INTERNAL_PREFIX}${relativePath}`;
4288
+ config[relativePath] = `${INTERNAL_SOURCE_PREFIX}${relativePath}`;
4279
4289
  }
4280
4290
  } catch {}
4281
4291
  }
@@ -4312,8 +4322,6 @@ function isDocsStale(lastSyncAt, autoSyncHours) {
4312
4322
  const lastSync = new Date(lastSyncAt).getTime();
4313
4323
  return (Date.now() - lastSync) / (1e3 * 60 * 60) >= autoSyncHours;
4314
4324
  }
4315
- /** Prefix for internal bundled doc sources */
4316
- const INTERNAL_SOURCE_PREFIX = "internal:";
4317
4325
  /**
4318
4326
  * Check if an internal bundled doc exists.
4319
4327
  *
@@ -5138,6 +5146,19 @@ var SyncHandler = class extends BaseCommand {
5138
5146
  } catch {
5139
5147
  this.output.debug("Remote sync branch does not exist yet");
5140
5148
  }
5149
+ {
5150
+ const { access, writeFile } = await import("node:fs/promises");
5151
+ const attrPath = join(this.dataSyncDir, "mappings", ".gitattributes");
5152
+ try {
5153
+ await access(attrPath);
5154
+ } catch {
5155
+ await writeFile(attrPath, "ids.yml merge=union\n");
5156
+ await git("-C", worktreePath, "add", attrPath);
5157
+ try {
5158
+ await git("-C", worktreePath, "commit", "--no-verify", "-m", "chore: add merge=union for ids.yml");
5159
+ } catch {}
5160
+ }
5161
+ }
5141
5162
  if (behindCommits > 0) {
5142
5163
  let headBeforeMerge = "";
5143
5164
  try {
@@ -5182,7 +5203,8 @@ var SyncHandler = class extends BaseCommand {
5182
5203
  const remoteIdsContent = await git("show", `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`);
5183
5204
  if (remoteIdsContent) {
5184
5205
  conflictRemoteMapping = parseIdMappingFromYaml(remoteIdsContent);
5185
- const localMapping = await loadIdMapping(this.dataSyncDir);
5206
+ const { readFile } = await import("node:fs/promises");
5207
+ const localMapping = resolveIdMappingConflicts(await readFile(join(this.dataSyncDir, "mappings", "ids.yml"), "utf-8"));
5186
5208
  const mergedMapping = mergeIdMappings(localMapping, conflictRemoteMapping);
5187
5209
  await saveIdMapping(this.dataSyncDir, mergedMapping);
5188
5210
  this.output.debug(`Merged ID mappings: ${localMapping.shortToUlid.size} local + ${conflictRemoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`);
@@ -6298,14 +6320,22 @@ var DoctorHandler = class extends BaseCommand {
6298
6320
  healthChecks.push(await this.checkIssuesDirectory());
6299
6321
  healthChecks.push(this.checkOrphanedDependencies(this.issues));
6300
6322
  healthChecks.push(this.checkDuplicateIds(this.issues));
6323
+ healthChecks.push(await this.checkIdMappingConflicts(options.fix));
6301
6324
  healthChecks.push(await this.checkIdMappingDuplicates(options.fix));
6302
6325
  healthChecks.push(await this.checkTempFiles(options.fix));
6303
6326
  healthChecks.push(this.checkIssueValidity(this.issues));
6327
+ healthChecks.push(await this.checkWorktree(options.fix));
6328
+ const dataLocationResult = await this.checkDataLocation(options.fix);
6329
+ healthChecks.push(dataLocationResult);
6330
+ if (dataLocationResult.status === "ok" && dataLocationResult.message?.includes("migrated")) {
6331
+ this.dataSyncDir = await resolveDataSyncDir(this.cwd);
6332
+ try {
6333
+ this.issues = await listIssues(this.dataSyncDir);
6334
+ } catch {}
6335
+ }
6304
6336
  const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;
6305
6337
  const maxHistory = Number.isNaN(parsedMaxHistory) || parsedMaxHistory < 0 ? 50 : parsedMaxHistory;
6306
6338
  healthChecks.push(await this.checkMissingMappings(options.fix, maxHistory));
6307
- healthChecks.push(await this.checkWorktree(options.fix));
6308
- healthChecks.push(await this.checkDataLocation(options.fix));
6309
6339
  healthChecks.push(await this.checkLocalSyncBranch());
6310
6340
  healthChecks.push(await this.checkRemoteSyncBranch());
6311
6341
  healthChecks.push(await this.checkLocalVsRemoteData());
@@ -6503,6 +6533,58 @@ var DoctorHandler = class extends BaseCommand {
6503
6533
  };
6504
6534
  }
6505
6535
  /**
6536
+ * Check 5b: Merge conflict markers in ids.yml.
6537
+ *
6538
+ * After a failed git merge during sync, ids.yml may retain unresolved
6539
+ * conflict markers (<<<<<<< / ======= / >>>>>>>). This blocks all tbd
6540
+ * commands since YAML parsing throws MergeConflictError.
6541
+ *
6542
+ * For ids.yml, both sides are simple key-value pairs that are append-only,
6543
+ * so the resolution is trivial: keep all entries from both sides.
6544
+ *
6545
+ * With --fix, extracts both sides, merges them, and re-saves.
6546
+ */
6547
+ async checkIdMappingConflicts(fix) {
6548
+ const mappingPath = join(this.dataSyncDir, "mappings", "ids.yml");
6549
+ let content;
6550
+ try {
6551
+ content = await readFile(mappingPath, "utf-8");
6552
+ } catch {
6553
+ return {
6554
+ name: "ID mapping conflicts",
6555
+ status: "ok"
6556
+ };
6557
+ }
6558
+ const { hasMergeConflictMarkers } = await import("./yaml-utils-swV780m5.mjs");
6559
+ if (!hasMergeConflictMarkers(content)) return {
6560
+ name: "ID mapping conflicts",
6561
+ status: "ok"
6562
+ };
6563
+ if (fix && !this.checkDryRun("Resolve merge conflicts in ids.yml")) try {
6564
+ const { resolveIdMappingConflicts, saveIdMapping } = await import("./id-mapping-BA_xn516.mjs");
6565
+ const resolved = resolveIdMappingConflicts(content);
6566
+ await saveIdMapping(this.dataSyncDir, resolved);
6567
+ return {
6568
+ name: "ID mapping conflicts",
6569
+ status: "ok",
6570
+ message: `resolved merge conflicts (${resolved.shortToUlid.size} entries)`
6571
+ };
6572
+ } catch (error) {
6573
+ return {
6574
+ name: "ID mapping conflicts",
6575
+ status: "error",
6576
+ message: `failed to resolve conflicts: ${error instanceof Error ? error.message : String(error)}`
6577
+ };
6578
+ }
6579
+ return {
6580
+ name: "ID mapping conflicts",
6581
+ status: "error",
6582
+ message: "ids.yml contains unresolved merge conflict markers",
6583
+ fixable: true,
6584
+ suggestion: "Run: tbd doctor --fix to auto-resolve"
6585
+ };
6586
+ }
6587
+ /**
6506
6588
  * Check for duplicate keys in the ID mapping file (ids.yml).
6507
6589
  *
6508
6590
  * After a git merge conflict resolution that keeps entries from both sides,
@@ -6528,7 +6610,7 @@ var DoctorHandler = class extends BaseCommand {
6528
6610
  status: "ok"
6529
6611
  };
6530
6612
  if (fix && !this.checkDryRun("Fix duplicate ID mapping keys")) try {
6531
- const { loadIdMapping, saveIdMapping } = await import("./id-mapping-LjnDSEhN.mjs");
6613
+ const { loadIdMapping, saveIdMapping } = await import("./id-mapping-BA_xn516.mjs");
6532
6614
  const mapping = await loadIdMapping(this.dataSyncDir);
6533
6615
  await saveIdMapping(this.dataSyncDir, mapping);
6534
6616
  return {
@@ -6662,7 +6744,7 @@ var DoctorHandler = class extends BaseCommand {
6662
6744
  name: "ID mapping coverage",
6663
6745
  status: "ok"
6664
6746
  };
6665
- const { loadIdMapping, saveIdMapping, reconcileMappings } = await import("./id-mapping-LjnDSEhN.mjs");
6747
+ const { loadIdMapping, saveIdMapping, reconcileMappings } = await import("./id-mapping-BA_xn516.mjs");
6666
6748
  const mapping = await loadIdMapping(this.dataSyncDir);
6667
6749
  const missingIds = [];
6668
6750
  for (const issue of this.issues) {
@@ -6674,10 +6756,10 @@ var DoctorHandler = class extends BaseCommand {
6674
6756
  status: "ok"
6675
6757
  };
6676
6758
  if (fix && !this.checkDryRun("Create missing ID mappings")) {
6677
- const { parseIdMappingFromYaml, mergeIdMappings } = await import("./id-mapping-LjnDSEhN.mjs");
6759
+ const { parseIdMappingFromYaml, mergeIdMappings } = await import("./id-mapping-BA_xn516.mjs");
6678
6760
  let historicalMapping;
6679
6761
  try {
6680
- const syncBranch = (await import("./config-CB1tcqTZ.mjs").then((m) => m.readConfig(this.cwd))).sync.branch;
6762
+ const syncBranch = (await import("./config-BZte2m3w.mjs").then((m) => m.readConfig(this.cwd))).sync.branch;
6681
6763
  const logArgs = ["log", "--format=%H"];
6682
6764
  if (maxHistory > 0) logArgs.push(`-${maxHistory}`);
6683
6765
  logArgs.push(syncBranch, "--", `${DATA_SYNC_DIR}/mappings/ids.yml`);