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/bin.mjs +258 -132
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +154 -49
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-b20Kf5pW.mjs → config-B38rbI9u.mjs} +2 -2
- package/dist/{config-b20Kf5pW.mjs.map → config-B38rbI9u.mjs.map} +1 -1
- package/dist/{config-BZte2m3w.mjs → config-C0ITTrtc.mjs} +1 -1
- package/dist/docs/guidelines/bun-monorepo-patterns.md +328 -58
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +399 -64
- package/dist/docs/guidelines/typescript-cli-tool-rules.md +39 -8
- package/dist/docs/guidelines/typescript-code-coverage.md +27 -3
- package/dist/docs/guidelines/typescript-rules.md +18 -0
- package/dist/docs/guidelines/typescript-yaml-handling-rules.md +12 -0
- package/dist/docs/tbd-design.md +16 -1
- package/dist/docs/tbd-docs.md +11 -2
- package/dist/{id-mapping-BtBwq5nG.mjs → id-mapping-CqrrLgeX.mjs} +2 -2
- package/dist/{id-mapping-BtBwq5nG.mjs.map → id-mapping-CqrrLgeX.mjs.map} +1 -1
- package/dist/{id-mapping-BA_xn516.mjs → id-mapping-Ctfl_nc1.mjs} +1 -1
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +3 -3
- package/dist/{schemas-BQYmDnkv.mjs → schemas-C8mOQykE.mjs} +17 -5
- package/dist/schemas-C8mOQykE.mjs.map +1 -0
- package/dist/{src-DQcOQnFp.mjs → src-D2xEmH4L.mjs} +3 -3
- package/dist/{src-DQcOQnFp.mjs.map → src-D2xEmH4L.mjs.map} +1 -1
- package/dist/tbd +258 -132
- package/package.json +2 -2
- package/dist/schemas-BQYmDnkv.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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 +=
|
|
1934
|
-
const batch = mdFiles.slice(i, i +
|
|
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
|
-
|
|
1974
|
+
error: formatUnknownError(error)
|
|
1946
1975
|
};
|
|
1947
1976
|
}
|
|
1948
1977
|
}));
|
|
1949
|
-
for (const
|
|
1950
|
-
if (
|
|
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
|
-
|
|
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}: ${
|
|
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
|
|
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("
|
|
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
|
-
|
|
3095
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
6864
|
+
const { parseIdMappingFromYaml, mergeIdMappings } = await import("./id-mapping-Ctfl_nc1.mjs");
|
|
6760
6865
|
let historicalMapping;
|
|
6761
6866
|
try {
|
|
6762
|
-
const syncBranch = (await import("./config-
|
|
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`);
|