get-tbd 0.1.26 → 0.1.28

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/cli.mjs CHANGED
@@ -1,9 +1,10 @@
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";
1
+ import { S as IssueTitle, b as IssueSchema, g as ISSUE_TITLE_MAX_LENGTH, n as AtticEntrySchema, t as ATTIC_ENTRY_FIELD_ORDER, x as IssueStatus, y as IssueKind } from "./schemas-C8mOQykE.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-D2xEmH4L.mjs";
3
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";
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-B38rbI9u.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-CqrrLgeX.mjs";
6
6
  import { createRequire } from "node:module";
7
+ import { ZodError } from "zod";
7
8
  import matter from "gray-matter";
8
9
  import { parse } from "yaml";
9
10
  import { Command } from "commander";
@@ -1754,7 +1755,7 @@ async function migrateDataToWorktree(baseDir, removeSource = false) {
1754
1755
  for (const file of issueFiles) await cp(join(wrongIssuesPath, file), join(correctIssuesPath, file));
1755
1756
  for (const file of mappingFiles) if (file === "ids.yml") {
1756
1757
  const { readFile } = await import("node:fs/promises");
1757
- const { loadIdMapping, mergeIdMappings, saveIdMapping, resolveIdMappingConflicts } = await import("./id-mapping-BA_xn516.mjs");
1758
+ const { loadIdMapping, mergeIdMappings, saveIdMapping, resolveIdMappingConflicts } = await import("./id-mapping-Ctfl_nc1.mjs");
1758
1759
  const sourceMapping = resolveIdMappingConflicts(await readFile(join(wrongMappingsPath, file), "utf-8"));
1759
1760
  let targetMapping;
1760
1761
  try {
@@ -1883,6 +1884,29 @@ const initCommand = new Command("init").description("Initialize tbd in a git rep
1883
1884
  await new InitHandler(command).run(options);
1884
1885
  });
1885
1886
 
1887
+ //#endregion
1888
+ //#region src/utils/zod-error-utils.ts
1889
+ /**
1890
+ * Helpers for rendering Zod errors without relying on object inspection.
1891
+ */
1892
+ /**
1893
+ * Format a ZodError as concise path-qualified messages for CLI output.
1894
+ */
1895
+ function formatZodError(error) {
1896
+ const messages = error.issues.map((issue) => {
1897
+ return `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`;
1898
+ });
1899
+ return messages.length > 0 ? messages.join("; ") : error.message;
1900
+ }
1901
+ /**
1902
+ * Format unknown thrown values as safe strings for warnings and diagnostics.
1903
+ */
1904
+ function formatUnknownError(error) {
1905
+ if (error instanceof ZodError) return formatZodError(error);
1906
+ if (error instanceof Error) return error.message;
1907
+ return String(error);
1908
+ }
1909
+
1886
1910
  //#endregion
1887
1911
  //#region src/file/storage.ts
1888
1912
  /**
@@ -1894,6 +1918,10 @@ const initCommand = new Command("init").description("Initialize tbd in a git rep
1894
1918
  * See: tbd-design.md §3.2 Storage Layer
1895
1919
  */
1896
1920
  /**
1921
+ * Maximum issue files read concurrently to avoid exhausting file descriptors in large repos.
1922
+ */
1923
+ const ISSUE_READ_BATCH_SIZE = 200;
1924
+ /**
1897
1925
  * Get the path to an issue file.
1898
1926
  */
1899
1927
  function getIssuePath(baseDir, id) {
@@ -1911,7 +1939,8 @@ async function readIssue(baseDir, id) {
1911
1939
  * Uses atomic write to prevent corruption.
1912
1940
  */
1913
1941
  async function writeIssue(baseDir, issue) {
1914
- await writeFile(getIssuePath(baseDir, issue.id), serializeIssue(issue));
1942
+ const validIssue = IssueSchema.parse(issue);
1943
+ await writeFile(getIssuePath(baseDir, validIssue.id), serializeIssue(validIssue));
1915
1944
  }
1916
1945
  /**
1917
1946
  * List all issues in the worktree.
@@ -1919,7 +1948,8 @@ async function writeIssue(baseDir, issue) {
1919
1948
  *
1920
1949
  * Uses parallel file reading for better performance with many issues.
1921
1950
  */
1922
- async function listIssues(baseDir) {
1951
+ async function listIssues(baseDir, options = {}) {
1952
+ const warnOnInvalid = options.warnOnInvalid ?? true;
1923
1953
  const issuesDir = join(baseDir, "issues");
1924
1954
  let files;
1925
1955
  try {
@@ -1928,10 +1958,9 @@ async function listIssues(baseDir) {
1928
1958
  return [];
1929
1959
  }
1930
1960
  const mdFiles = files.filter((f) => f.endsWith(".md"));
1931
- const BATCH_SIZE = 200;
1932
1961
  const issues = [];
1933
- for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
1934
- const batch = mdFiles.slice(i, i + BATCH_SIZE);
1962
+ for (let i = 0; i < mdFiles.length; i += ISSUE_READ_BATCH_SIZE) {
1963
+ const batch = mdFiles.slice(i, i + ISSUE_READ_BATCH_SIZE);
1935
1964
  const fileContents = await Promise.all(batch.map(async (file) => {
1936
1965
  const filePath = join(issuesDir, file);
1937
1966
  try {
@@ -1939,25 +1968,38 @@ async function listIssues(baseDir) {
1939
1968
  file,
1940
1969
  content: await readFile(filePath, "utf-8")
1941
1970
  };
1942
- } catch {
1971
+ } catch (error) {
1943
1972
  return {
1944
1973
  file,
1945
- content: null
1974
+ error: formatUnknownError(error)
1946
1975
  };
1947
1976
  }
1948
1977
  }));
1949
- for (const { file, content } of fileContents) {
1950
- if (content === null) continue;
1978
+ for (const result of fileContents) {
1979
+ if ("error" in result) {
1980
+ reportInvalidIssueFile({
1981
+ file: result.file,
1982
+ reason: `failed to read file: ${result.error}`
1983
+ }, warnOnInvalid, options.onInvalidIssue);
1984
+ continue;
1985
+ }
1951
1986
  try {
1952
- const issue = parseIssue(content);
1987
+ const issue = parseIssue(result.content);
1953
1988
  issues.push(issue);
1954
1989
  } catch (error) {
1955
- console.warn(`Skipping invalid issue file: ${file}`, error);
1990
+ reportInvalidIssueFile({
1991
+ file: result.file,
1992
+ reason: formatUnknownError(error)
1993
+ }, warnOnInvalid, options.onInvalidIssue);
1956
1994
  }
1957
1995
  }
1958
1996
  }
1959
1997
  return issues;
1960
1998
  }
1999
+ function reportInvalidIssueFile(invalidIssue, warnOnInvalid, onInvalidIssue) {
2000
+ onInvalidIssue?.(invalidIssue);
2001
+ if (warnOnInvalid) console.warn(`Skipping invalid issue file: ${invalidIssue.file}: ${invalidIssue.reason}`);
2002
+ }
1961
2003
 
1962
2004
  //#endregion
1963
2005
  //#region src/lib/priority.ts
@@ -2150,6 +2192,22 @@ async function resolveAndValidatePath(inputPath, projectRoot, cwd) {
2150
2192
  return resolved;
2151
2193
  }
2152
2194
 
2195
+ //#endregion
2196
+ //#region src/cli/lib/issue-input-validation.ts
2197
+ /**
2198
+ * CLI validation helpers for user-provided issue fields.
2199
+ */
2200
+ /**
2201
+ * Validate a CLI-provided issue title with actionable user-facing errors.
2202
+ */
2203
+ function validateIssueTitle(title, options) {
2204
+ if (options.rejectBlank ? title.trim().length === 0 : title.length === 0) throw new ValidationError(options.emptyMessage);
2205
+ if (title.length > ISSUE_TITLE_MAX_LENGTH) throw new ValidationError(`Title is too long (${title.length} chars, max ${ISSUE_TITLE_MAX_LENGTH}). Move detail into the description body.`);
2206
+ const result = IssueTitle.safeParse(title);
2207
+ if (!result.success) throw new ValidationError(`Invalid title: ${formatZodError(result.error)}`);
2208
+ return title;
2209
+ }
2210
+
2153
2211
  //#endregion
2154
2212
  //#region src/cli/commands/create.ts
2155
2213
  /**
@@ -2161,6 +2219,10 @@ var CreateHandler = class extends BaseCommand {
2161
2219
  async run(title, options) {
2162
2220
  const tbdRoot = await requireInit();
2163
2221
  if (!title && !options.fromFile) throw new ValidationError("Title is required. Use: tbd create \"Issue title\"");
2222
+ const validatedTitle = title === void 0 ? void 0 : validateIssueTitle(title, {
2223
+ emptyMessage: "Title is required. Use: tbd create \"Issue title\"",
2224
+ rejectBlank: true
2225
+ });
2164
2226
  const kind = this.parseKind(options.type ?? "task");
2165
2227
  const priority = this.validatePriority(options.priority ?? "2");
2166
2228
  let description = options.description;
@@ -2177,7 +2239,7 @@ var CreateHandler = class extends BaseCommand {
2177
2239
  throw new ValidationError(getPathErrorMessage(error));
2178
2240
  }
2179
2241
  if (this.checkDryRun("Would create issue", {
2180
- title,
2242
+ title: validatedTitle,
2181
2243
  kind,
2182
2244
  priority,
2183
2245
  spec: specPath,
@@ -2209,7 +2271,7 @@ var CreateHandler = class extends BaseCommand {
2209
2271
  type: "is",
2210
2272
  id,
2211
2273
  version: 1,
2212
- title,
2274
+ title: validatedTitle,
2213
2275
  kind,
2214
2276
  status: "open",
2215
2277
  priority,
@@ -2241,9 +2303,9 @@ var CreateHandler = class extends BaseCommand {
2241
2303
  this.output.data({
2242
2304
  id: displayId,
2243
2305
  internalId: id,
2244
- title
2306
+ title: validatedTitle
2245
2307
  }, () => {
2246
- this.output.success(`Created ${displayId}: ${title}`);
2308
+ this.output.success(`Created ${displayId}: ${validatedTitle}`);
2247
2309
  });
2248
2310
  }
2249
2311
  parseKind(value) {
@@ -2862,6 +2924,30 @@ const listCommand = new Command("list").description("List issues").option("--sta
2862
2924
  await new ListHandler(command).run(options);
2863
2925
  });
2864
2926
 
2927
+ //#endregion
2928
+ //#region src/cli/lib/dependency-format.ts
2929
+ /**
2930
+ * Compute display-ready dependency directions for an issue.
2931
+ */
2932
+ function getDependencyDirections(issue, allIssues, displayId) {
2933
+ const blocks = issue.dependencies.filter((dep) => dep.type === "blocks").map((dep) => displayId(dep.target));
2934
+ const blockedBy = [];
2935
+ for (const other of allIssues) for (const dep of other.dependencies) if (dep.type === "blocks" && dep.target === issue.id) blockedBy.push(displayId(other.id));
2936
+ return {
2937
+ blocks,
2938
+ blockedBy
2939
+ };
2940
+ }
2941
+ /**
2942
+ * Render dependency directions as YAML comments for round-trippable show output.
2943
+ */
2944
+ function formatDependencyDirectionComments(directions) {
2945
+ const lines = [];
2946
+ if (directions.blocks.length > 0) lines.push(`# Blocks: ${directions.blocks.join(", ")}`);
2947
+ if (directions.blockedBy.length > 0) lines.push(`# Blocked by: ${directions.blockedBy.join(", ")}`);
2948
+ return lines;
2949
+ }
2950
+
2865
2951
  //#endregion
2866
2952
  //#region src/cli/commands/show.ts
2867
2953
  /**
@@ -2874,14 +2960,18 @@ const listCommand = new Command("list").description("List issues").option("--sta
2874
2960
  *
2875
2961
  * @param issue - The issue to render
2876
2962
  * @param colors - Color functions
2877
- * @param maxLines - If set, truncate output to this many lines with an omission notice
2963
+ * @param dependencyDirections - Optional human-facing dependency direction comments
2878
2964
  * @returns Array of formatted lines
2879
2965
  */
2880
- function renderIssueLines(issue, colors) {
2966
+ function renderIssueLines(issue, colors, dependencyDirections) {
2881
2967
  const serialized = serializeIssue(issue);
2882
2968
  const output = [];
2969
+ const dependencyDirectionComments = dependencyDirections ? formatDependencyDirectionComments(dependencyDirections) : [];
2883
2970
  for (const line of serialized.split("\n")) if (line === "---") output.push(colors.dim(line));
2884
- else if (line.startsWith("id:")) output.push(`${colors.dim("id:")} ${colors.id(line.slice(4))}`);
2971
+ else if (line.startsWith("dependencies:")) {
2972
+ for (const comment of dependencyDirectionComments) output.push(colors.dim(comment));
2973
+ output.push(line);
2974
+ } else if (line.startsWith("id:")) output.push(`${colors.dim("id:")} ${colors.id(line.slice(4))}`);
2885
2975
  else if (line.startsWith("status:")) {
2886
2976
  const status = line.slice(8).trim();
2887
2977
  const statusColor = getStatusColor(status, colors);
@@ -2924,6 +3014,10 @@ var ShowHandler = class extends BaseCommand {
2924
3014
  } catch {}
2925
3015
  const maxLines = options.maxLines ? parseInt(options.maxLines, 10) : void 0;
2926
3016
  const displayId = ctx.displayId(issue.id);
3017
+ const allIssues = ctx.cli.json ? void 0 : await listIssues(ctx.dataSyncDir);
3018
+ const displayDependencyId = (dependencyId) => ctx.displayId(dependencyId);
3019
+ const dependencyDirections = allIssues ? getDependencyDirections(issue, allIssues, displayDependencyId) : void 0;
3020
+ const parentDependencyDirections = allIssues && parentIssue ? getDependencyDirections(parentIssue, allIssues, displayDependencyId) : void 0;
2927
3021
  const displayIssue = {
2928
3022
  ...issue,
2929
3023
  displayId,
@@ -2934,11 +3028,11 @@ var ShowHandler = class extends BaseCommand {
2934
3028
  };
2935
3029
  this.output.data(displayIssue, () => {
2936
3030
  const colors = this.output.getColors();
2937
- printWithTruncation(renderIssueLines(issue, colors), colors, maxLines);
3031
+ printWithTruncation(renderIssueLines(issue, colors, dependencyDirections), colors, maxLines);
2938
3032
  if (parentIssue) {
2939
3033
  console.log("");
2940
3034
  console.log(colors.dim("The parent of this bead is:"));
2941
- printWithTruncation(renderIssueLines(parentIssue, colors), colors, PARENT_CONTEXT_MAX_LINES);
3035
+ printWithTruncation(renderIssueLines(parentIssue, colors, parentDependencyDirections), colors, PARENT_CONTEXT_MAX_LINES);
2942
3036
  }
2943
3037
  if (options.showOrder) {
2944
3038
  console.log("");
@@ -3059,7 +3153,10 @@ var UpdateHandler = class extends BaseCommand {
3059
3153
  }
3060
3154
  try {
3061
3155
  const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);
3062
- if (typeof frontmatter.title === "string") updates.title = frontmatter.title;
3156
+ if (typeof frontmatter.title === "string") updates.title = validateIssueTitle(frontmatter.title, {
3157
+ emptyMessage: "Title cannot be empty",
3158
+ rejectBlank: true
3159
+ });
3063
3160
  if (typeof frontmatter.status === "string") {
3064
3161
  const result = IssueStatus.safeParse(frontmatter.status);
3065
3162
  if (result.success) updates.status = result.data;
@@ -3090,10 +3187,10 @@ var UpdateHandler = class extends BaseCommand {
3090
3187
  }
3091
3188
  return updates;
3092
3189
  }
3093
- if (options.title !== void 0) {
3094
- if (!options.title.trim()) throw new ValidationError("Title cannot be empty");
3095
- updates.title = options.title;
3096
- }
3190
+ if (options.title !== void 0) updates.title = validateIssueTitle(options.title, {
3191
+ emptyMessage: "Title cannot be empty",
3192
+ rejectBlank: true
3193
+ });
3097
3194
  if (options.status) {
3098
3195
  const result = IssueStatus.safeParse(options.status);
3099
3196
  if (!result.success) throw new ValidationError(`Invalid status: ${options.status}`);
@@ -3776,13 +3873,7 @@ var DependsListHandler = class extends BaseCommand {
3776
3873
  }
3777
3874
  const showDebug = this.ctx.debug;
3778
3875
  const prefix = (await readConfig(tbdRoot)).display.id_prefix;
3779
- const blocks = issue.dependencies.filter((dep) => dep.type === "blocks").map((dep) => showDebug ? formatDebugId(dep.target, mapping, prefix) : formatDisplayId(dep.target, mapping, prefix));
3780
- const blockedBy = [];
3781
- for (const other of allIssues) for (const dep of other.dependencies) if (dep.type === "blocks" && dep.target === internalId) blockedBy.push(showDebug ? formatDebugId(other.id, mapping, prefix) : formatDisplayId(other.id, mapping, prefix));
3782
- const deps = {
3783
- blocks,
3784
- blockedBy
3785
- };
3876
+ const deps = getDependencyDirections(issue, allIssues, (dependencyId) => showDebug ? formatDebugId(dependencyId, mapping, prefix) : formatDisplayId(dependencyId, mapping, prefix));
3786
3877
  this.output.data(deps, () => {
3787
3878
  const colors = this.output.getColors();
3788
3879
  if (deps.blocks.length > 0) console.log(`${colors.bold("Blocks:")} ${deps.blocks.join(", ")}`);
@@ -6302,6 +6393,7 @@ var DoctorHandler = class extends BaseCommand {
6302
6393
  cwd = "";
6303
6394
  config = null;
6304
6395
  issues = [];
6396
+ invalidIssueFiles = [];
6305
6397
  async run(options) {
6306
6398
  const tbdRoot = await requireInit();
6307
6399
  this.cwd = tbdRoot;
@@ -6310,7 +6402,11 @@ var DoctorHandler = class extends BaseCommand {
6310
6402
  this.config = await readConfig(this.cwd);
6311
6403
  } catch {}
6312
6404
  try {
6313
- this.issues = await listIssues(this.dataSyncDir);
6405
+ this.invalidIssueFiles = [];
6406
+ this.issues = await listIssues(this.dataSyncDir, {
6407
+ warnOnInvalid: false,
6408
+ onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue)
6409
+ });
6314
6410
  } catch {}
6315
6411
  const statusInfo = await this.gatherStatusInfo();
6316
6412
  const statsInfo = await this.gatherStatsInfo();
@@ -6323,14 +6419,18 @@ var DoctorHandler = class extends BaseCommand {
6323
6419
  healthChecks.push(await this.checkIdMappingConflicts(options.fix));
6324
6420
  healthChecks.push(await this.checkIdMappingDuplicates(options.fix));
6325
6421
  healthChecks.push(await this.checkTempFiles(options.fix));
6326
- healthChecks.push(this.checkIssueValidity(this.issues));
6422
+ healthChecks.push(this.checkIssueValidity(this.issues, this.invalidIssueFiles));
6327
6423
  healthChecks.push(await this.checkWorktree(options.fix));
6328
6424
  const dataLocationResult = await this.checkDataLocation(options.fix);
6329
6425
  healthChecks.push(dataLocationResult);
6330
6426
  if (dataLocationResult.status === "ok" && dataLocationResult.message?.includes("migrated")) {
6331
6427
  this.dataSyncDir = await resolveDataSyncDir(this.cwd);
6332
6428
  try {
6333
- this.issues = await listIssues(this.dataSyncDir);
6429
+ this.invalidIssueFiles = [];
6430
+ this.issues = await listIssues(this.dataSyncDir, {
6431
+ warnOnInvalid: false,
6432
+ onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue)
6433
+ });
6334
6434
  } catch {}
6335
6435
  }
6336
6436
  const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;
@@ -6561,7 +6661,7 @@ var DoctorHandler = class extends BaseCommand {
6561
6661
  status: "ok"
6562
6662
  };
6563
6663
  if (fix && !this.checkDryRun("Resolve merge conflicts in ids.yml")) try {
6564
- const { resolveIdMappingConflicts, saveIdMapping } = await import("./id-mapping-BA_xn516.mjs");
6664
+ const { resolveIdMappingConflicts, saveIdMapping } = await import("./id-mapping-Ctfl_nc1.mjs");
6565
6665
  const resolved = resolveIdMappingConflicts(content);
6566
6666
  await saveIdMapping(this.dataSyncDir, resolved);
6567
6667
  return {
@@ -6610,7 +6710,7 @@ var DoctorHandler = class extends BaseCommand {
6610
6710
  status: "ok"
6611
6711
  };
6612
6712
  if (fix && !this.checkDryRun("Fix duplicate ID mapping keys")) try {
6613
- const { loadIdMapping, saveIdMapping } = await import("./id-mapping-BA_xn516.mjs");
6713
+ const { loadIdMapping, saveIdMapping } = await import("./id-mapping-Ctfl_nc1.mjs");
6614
6714
  const mapping = await loadIdMapping(this.dataSyncDir);
6615
6715
  await saveIdMapping(this.dataSyncDir, mapping);
6616
6716
  return {
@@ -6635,11 +6735,11 @@ var DoctorHandler = class extends BaseCommand {
6635
6735
  };
6636
6736
  }
6637
6737
  async checkTempFiles(fix) {
6638
- const issuesPath = join(CONFIG_DIR, "issues");
6639
6738
  const issuesDir = join(this.dataSyncDir, "issues");
6739
+ const issuesPath = join(DATA_SYNC_DIR, "issues");
6640
6740
  let tempFiles = [];
6641
6741
  try {
6642
- tempFiles = (await readdir(issuesDir)).filter((f) => f.endsWith(".tmp"));
6742
+ tempFiles = (await readdir(issuesDir)).filter((f) => f.endsWith(".tmp") || /\.tmp-\d+$/.test(f));
6643
6743
  } catch {
6644
6744
  return {
6645
6745
  name: "Temp files",
@@ -6673,8 +6773,12 @@ var DoctorHandler = class extends BaseCommand {
6673
6773
  suggestion: "Run: tbd doctor --fix"
6674
6774
  };
6675
6775
  }
6676
- checkIssueValidity(issues) {
6776
+ checkIssueValidity(issues, invalidIssueFiles) {
6677
6777
  const invalid = [];
6778
+ for (const invalidIssueFile of invalidIssueFiles) invalid.push({
6779
+ id: invalidIssueFile.file,
6780
+ reason: invalidIssueFile.reason
6781
+ });
6678
6782
  for (const issue of issues) {
6679
6783
  const issueId = issue.id ?? "unknown";
6680
6784
  if (!issue.id) {
@@ -6724,7 +6828,8 @@ var DoctorHandler = class extends BaseCommand {
6724
6828
  return {
6725
6829
  name: "Issue validity",
6726
6830
  status: "error",
6727
- message: `${invalid.length} invalid issue(s)`,
6831
+ message: `${invalid.length} invalid issue file(s)`,
6832
+ path: join(CONFIG_DIR, "issues"),
6728
6833
  details: invalid.map((i) => `${i.id}: ${i.reason}`),
6729
6834
  suggestion: "Manually fix or delete invalid issue files"
6730
6835
  };
@@ -6744,7 +6849,7 @@ var DoctorHandler = class extends BaseCommand {
6744
6849
  name: "ID mapping coverage",
6745
6850
  status: "ok"
6746
6851
  };
6747
- const { loadIdMapping, saveIdMapping, reconcileMappings } = await import("./id-mapping-BA_xn516.mjs");
6852
+ const { loadIdMapping, saveIdMapping, reconcileMappings } = await import("./id-mapping-Ctfl_nc1.mjs");
6748
6853
  const mapping = await loadIdMapping(this.dataSyncDir);
6749
6854
  const missingIds = [];
6750
6855
  for (const issue of this.issues) {
@@ -6756,10 +6861,10 @@ var DoctorHandler = class extends BaseCommand {
6756
6861
  status: "ok"
6757
6862
  };
6758
6863
  if (fix && !this.checkDryRun("Create missing ID mappings")) {
6759
- const { parseIdMappingFromYaml, mergeIdMappings } = await import("./id-mapping-BA_xn516.mjs");
6864
+ const { parseIdMappingFromYaml, mergeIdMappings } = await import("./id-mapping-Ctfl_nc1.mjs");
6760
6865
  let historicalMapping;
6761
6866
  try {
6762
- const syncBranch = (await import("./config-BZte2m3w.mjs").then((m) => m.readConfig(this.cwd))).sync.branch;
6867
+ const syncBranch = (await import("./config-C0ITTrtc.mjs").then((m) => m.readConfig(this.cwd))).sync.branch;
6763
6868
  const logArgs = ["log", "--format=%H"];
6764
6869
  if (maxHistory > 0) logArgs.push(`-${maxHistory}`);
6765
6870
  logArgs.push(syncBranch, "--", `${DATA_SYNC_DIR}/mappings/ids.yml`);