get-tbd 0.1.26 → 0.1.27
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 +119 -29
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +114 -35
- 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/{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-BIE27KDA.mjs} +3 -3
- package/dist/{src-DQcOQnFp.mjs.map → src-BIE27KDA.mjs.map} +1 -1
- package/dist/tbd +119 -29
- package/package.json +1 -1
- package/dist/schemas-BQYmDnkv.mjs.map +0 -1
package/dist/bin.mjs
CHANGED
|
@@ -6710,6 +6710,18 @@ const IssueKind = enumType([
|
|
|
6710
6710
|
"chore"
|
|
6711
6711
|
]);
|
|
6712
6712
|
/**
|
|
6713
|
+
* Maximum issue title length before detail belongs in the description body.
|
|
6714
|
+
*/
|
|
6715
|
+
const ISSUE_TITLE_MAX_LENGTH = 500;
|
|
6716
|
+
/**
|
|
6717
|
+
* Maximum issue body section length to keep issue files practical to review and sync.
|
|
6718
|
+
*/
|
|
6719
|
+
const ISSUE_BODY_MAX_LENGTH = 5e4;
|
|
6720
|
+
/**
|
|
6721
|
+
* Issue title text as persisted in issue files.
|
|
6722
|
+
*/
|
|
6723
|
+
const IssueTitle = stringType().min(1).max(ISSUE_TITLE_MAX_LENGTH);
|
|
6724
|
+
/**
|
|
6713
6725
|
* Priority: 0 (highest/critical) to 4 (lowest).
|
|
6714
6726
|
*/
|
|
6715
6727
|
const Priority = numberType().int().min(0).max(4);
|
|
@@ -6743,12 +6755,12 @@ const Dependency = objectType({
|
|
|
6743
6755
|
*/
|
|
6744
6756
|
const IssueSchema = BaseEntity.extend({
|
|
6745
6757
|
type: literalType("is"),
|
|
6746
|
-
title:
|
|
6758
|
+
title: IssueTitle,
|
|
6747
6759
|
kind: IssueKind.default("task"),
|
|
6748
6760
|
status: IssueStatus.default("open"),
|
|
6749
6761
|
priority: Priority.default(2),
|
|
6750
|
-
description: stringType().max(
|
|
6751
|
-
notes: stringType().max(
|
|
6762
|
+
description: stringType().max(ISSUE_BODY_MAX_LENGTH).nullable().optional(),
|
|
6763
|
+
notes: stringType().max(ISSUE_BODY_MAX_LENGTH).nullable().optional(),
|
|
6752
6764
|
spec_path: stringType().nullable().optional(),
|
|
6753
6765
|
assignee: stringType().nullable().optional(),
|
|
6754
6766
|
labels: arrayType(stringType()).default([]),
|
|
@@ -14045,7 +14057,7 @@ function serializeIssue(issue) {
|
|
|
14045
14057
|
* Package version, derived from git at build time.
|
|
14046
14058
|
* Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
|
|
14047
14059
|
*/
|
|
14048
|
-
const VERSION$1 = "0.1.
|
|
14060
|
+
const VERSION$1 = "0.1.27";
|
|
14049
14061
|
|
|
14050
14062
|
//#endregion
|
|
14051
14063
|
//#region src/cli/lib/version.ts
|
|
@@ -99726,6 +99738,29 @@ function formatDebugId(internalId, mapping, prefix = "tbd") {
|
|
|
99726
99738
|
return `${formatDisplayId(internalId, mapping, prefix)} (${internalId})`;
|
|
99727
99739
|
}
|
|
99728
99740
|
|
|
99741
|
+
//#endregion
|
|
99742
|
+
//#region src/utils/zod-error-utils.ts
|
|
99743
|
+
/**
|
|
99744
|
+
* Helpers for rendering Zod errors without relying on object inspection.
|
|
99745
|
+
*/
|
|
99746
|
+
/**
|
|
99747
|
+
* Format a ZodError as concise path-qualified messages for CLI output.
|
|
99748
|
+
*/
|
|
99749
|
+
function formatZodError(error) {
|
|
99750
|
+
const messages = error.issues.map((issue) => {
|
|
99751
|
+
return `${issue.path.length > 0 ? issue.path.join(".") : "<root>"}: ${issue.message}`;
|
|
99752
|
+
});
|
|
99753
|
+
return messages.length > 0 ? messages.join("; ") : error.message;
|
|
99754
|
+
}
|
|
99755
|
+
/**
|
|
99756
|
+
* Format unknown thrown values as safe strings for warnings and diagnostics.
|
|
99757
|
+
*/
|
|
99758
|
+
function formatUnknownError(error) {
|
|
99759
|
+
if (error instanceof ZodError) return formatZodError(error);
|
|
99760
|
+
if (error instanceof Error) return error.message;
|
|
99761
|
+
return String(error);
|
|
99762
|
+
}
|
|
99763
|
+
|
|
99729
99764
|
//#endregion
|
|
99730
99765
|
//#region src/file/storage.ts
|
|
99731
99766
|
/**
|
|
@@ -99737,6 +99772,10 @@ function formatDebugId(internalId, mapping, prefix = "tbd") {
|
|
|
99737
99772
|
* See: tbd-design.md §3.2 Storage Layer
|
|
99738
99773
|
*/
|
|
99739
99774
|
/**
|
|
99775
|
+
* Maximum issue files read concurrently to avoid exhausting file descriptors in large repos.
|
|
99776
|
+
*/
|
|
99777
|
+
const ISSUE_READ_BATCH_SIZE = 200;
|
|
99778
|
+
/**
|
|
99740
99779
|
* Get the path to an issue file.
|
|
99741
99780
|
*/
|
|
99742
99781
|
function getIssuePath(baseDir, id) {
|
|
@@ -99754,7 +99793,8 @@ async function readIssue(baseDir, id) {
|
|
|
99754
99793
|
* Uses atomic write to prevent corruption.
|
|
99755
99794
|
*/
|
|
99756
99795
|
async function writeIssue(baseDir, issue) {
|
|
99757
|
-
|
|
99796
|
+
const validIssue = IssueSchema.parse(issue);
|
|
99797
|
+
await writeFile(getIssuePath(baseDir, validIssue.id), serializeIssue(validIssue));
|
|
99758
99798
|
}
|
|
99759
99799
|
/**
|
|
99760
99800
|
* List all issues in the worktree.
|
|
@@ -99762,7 +99802,8 @@ async function writeIssue(baseDir, issue) {
|
|
|
99762
99802
|
*
|
|
99763
99803
|
* Uses parallel file reading for better performance with many issues.
|
|
99764
99804
|
*/
|
|
99765
|
-
async function listIssues(baseDir) {
|
|
99805
|
+
async function listIssues(baseDir, options = {}) {
|
|
99806
|
+
const warnOnInvalid = options.warnOnInvalid ?? true;
|
|
99766
99807
|
const issuesDir = join(baseDir, "issues");
|
|
99767
99808
|
let files;
|
|
99768
99809
|
try {
|
|
@@ -99771,10 +99812,9 @@ async function listIssues(baseDir) {
|
|
|
99771
99812
|
return [];
|
|
99772
99813
|
}
|
|
99773
99814
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
99774
|
-
const BATCH_SIZE = 200;
|
|
99775
99815
|
const issues = [];
|
|
99776
|
-
for (let i = 0; i < mdFiles.length; i +=
|
|
99777
|
-
const batch = mdFiles.slice(i, i +
|
|
99816
|
+
for (let i = 0; i < mdFiles.length; i += ISSUE_READ_BATCH_SIZE) {
|
|
99817
|
+
const batch = mdFiles.slice(i, i + ISSUE_READ_BATCH_SIZE);
|
|
99778
99818
|
const fileContents = await Promise.all(batch.map(async (file) => {
|
|
99779
99819
|
const filePath = join(issuesDir, file);
|
|
99780
99820
|
try {
|
|
@@ -99782,25 +99822,38 @@ async function listIssues(baseDir) {
|
|
|
99782
99822
|
file,
|
|
99783
99823
|
content: await readFile(filePath, "utf-8")
|
|
99784
99824
|
};
|
|
99785
|
-
} catch {
|
|
99825
|
+
} catch (error) {
|
|
99786
99826
|
return {
|
|
99787
99827
|
file,
|
|
99788
|
-
|
|
99828
|
+
error: formatUnknownError(error)
|
|
99789
99829
|
};
|
|
99790
99830
|
}
|
|
99791
99831
|
}));
|
|
99792
|
-
for (const
|
|
99793
|
-
if (
|
|
99832
|
+
for (const result of fileContents) {
|
|
99833
|
+
if ("error" in result) {
|
|
99834
|
+
reportInvalidIssueFile({
|
|
99835
|
+
file: result.file,
|
|
99836
|
+
reason: `failed to read file: ${result.error}`
|
|
99837
|
+
}, warnOnInvalid, options.onInvalidIssue);
|
|
99838
|
+
continue;
|
|
99839
|
+
}
|
|
99794
99840
|
try {
|
|
99795
|
-
const issue = parseIssue(content);
|
|
99841
|
+
const issue = parseIssue(result.content);
|
|
99796
99842
|
issues.push(issue);
|
|
99797
99843
|
} catch (error) {
|
|
99798
|
-
|
|
99844
|
+
reportInvalidIssueFile({
|
|
99845
|
+
file: result.file,
|
|
99846
|
+
reason: formatUnknownError(error)
|
|
99847
|
+
}, warnOnInvalid, options.onInvalidIssue);
|
|
99799
99848
|
}
|
|
99800
99849
|
}
|
|
99801
99850
|
}
|
|
99802
99851
|
return issues;
|
|
99803
99852
|
}
|
|
99853
|
+
function reportInvalidIssueFile(invalidIssue, warnOnInvalid, onInvalidIssue) {
|
|
99854
|
+
onInvalidIssue?.(invalidIssue);
|
|
99855
|
+
if (warnOnInvalid) console.warn(`Skipping invalid issue file: ${invalidIssue.file}: ${invalidIssue.reason}`);
|
|
99856
|
+
}
|
|
99804
99857
|
|
|
99805
99858
|
//#endregion
|
|
99806
99859
|
//#region src/utils/lockfile.ts
|
|
@@ -100516,6 +100569,22 @@ async function resolveAndValidatePath(inputPath, projectRoot, cwd) {
|
|
|
100516
100569
|
return resolved;
|
|
100517
100570
|
}
|
|
100518
100571
|
|
|
100572
|
+
//#endregion
|
|
100573
|
+
//#region src/cli/lib/issue-input-validation.ts
|
|
100574
|
+
/**
|
|
100575
|
+
* CLI validation helpers for user-provided issue fields.
|
|
100576
|
+
*/
|
|
100577
|
+
/**
|
|
100578
|
+
* Validate a CLI-provided issue title with actionable user-facing errors.
|
|
100579
|
+
*/
|
|
100580
|
+
function validateIssueTitle(title, options) {
|
|
100581
|
+
if (options.rejectBlank ? title.trim().length === 0 : title.length === 0) throw new ValidationError(options.emptyMessage);
|
|
100582
|
+
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.`);
|
|
100583
|
+
const result = IssueTitle.safeParse(title);
|
|
100584
|
+
if (!result.success) throw new ValidationError(`Invalid title: ${formatZodError(result.error)}`);
|
|
100585
|
+
return title;
|
|
100586
|
+
}
|
|
100587
|
+
|
|
100519
100588
|
//#endregion
|
|
100520
100589
|
//#region src/cli/commands/create.ts
|
|
100521
100590
|
/**
|
|
@@ -100527,6 +100596,10 @@ var CreateHandler = class extends BaseCommand {
|
|
|
100527
100596
|
async run(title, options) {
|
|
100528
100597
|
const tbdRoot = await requireInit();
|
|
100529
100598
|
if (!title && !options.fromFile) throw new ValidationError("Title is required. Use: tbd create \"Issue title\"");
|
|
100599
|
+
const validatedTitle = title === void 0 ? void 0 : validateIssueTitle(title, {
|
|
100600
|
+
emptyMessage: "Title is required. Use: tbd create \"Issue title\"",
|
|
100601
|
+
rejectBlank: true
|
|
100602
|
+
});
|
|
100530
100603
|
const kind = this.parseKind(options.type ?? "task");
|
|
100531
100604
|
const priority = this.validatePriority(options.priority ?? "2");
|
|
100532
100605
|
let description = options.description;
|
|
@@ -100543,7 +100616,7 @@ var CreateHandler = class extends BaseCommand {
|
|
|
100543
100616
|
throw new ValidationError(getPathErrorMessage(error));
|
|
100544
100617
|
}
|
|
100545
100618
|
if (this.checkDryRun("Would create issue", {
|
|
100546
|
-
title,
|
|
100619
|
+
title: validatedTitle,
|
|
100547
100620
|
kind,
|
|
100548
100621
|
priority,
|
|
100549
100622
|
spec: specPath,
|
|
@@ -100575,7 +100648,7 @@ var CreateHandler = class extends BaseCommand {
|
|
|
100575
100648
|
type: "is",
|
|
100576
100649
|
id,
|
|
100577
100650
|
version: 1,
|
|
100578
|
-
title,
|
|
100651
|
+
title: validatedTitle,
|
|
100579
100652
|
kind,
|
|
100580
100653
|
status: "open",
|
|
100581
100654
|
priority,
|
|
@@ -100607,9 +100680,9 @@ var CreateHandler = class extends BaseCommand {
|
|
|
100607
100680
|
this.output.data({
|
|
100608
100681
|
id: displayId,
|
|
100609
100682
|
internalId: id,
|
|
100610
|
-
title
|
|
100683
|
+
title: validatedTitle
|
|
100611
100684
|
}, () => {
|
|
100612
|
-
this.output.success(`Created ${displayId}: ${
|
|
100685
|
+
this.output.success(`Created ${displayId}: ${validatedTitle}`);
|
|
100613
100686
|
});
|
|
100614
100687
|
}
|
|
100615
100688
|
parseKind(value) {
|
|
@@ -101425,7 +101498,10 @@ var UpdateHandler = class extends BaseCommand {
|
|
|
101425
101498
|
}
|
|
101426
101499
|
try {
|
|
101427
101500
|
const { frontmatter, description, notes } = parseMarkdownWithFrontmatter(content);
|
|
101428
|
-
if (typeof frontmatter.title === "string") updates.title = frontmatter.title
|
|
101501
|
+
if (typeof frontmatter.title === "string") updates.title = validateIssueTitle(frontmatter.title, {
|
|
101502
|
+
emptyMessage: "Title cannot be empty",
|
|
101503
|
+
rejectBlank: true
|
|
101504
|
+
});
|
|
101429
101505
|
if (typeof frontmatter.status === "string") {
|
|
101430
101506
|
const result = IssueStatus.safeParse(frontmatter.status);
|
|
101431
101507
|
if (result.success) updates.status = result.data;
|
|
@@ -101456,10 +101532,10 @@ var UpdateHandler = class extends BaseCommand {
|
|
|
101456
101532
|
}
|
|
101457
101533
|
return updates;
|
|
101458
101534
|
}
|
|
101459
|
-
if (options.title !== void 0) {
|
|
101460
|
-
|
|
101461
|
-
|
|
101462
|
-
}
|
|
101535
|
+
if (options.title !== void 0) updates.title = validateIssueTitle(options.title, {
|
|
101536
|
+
emptyMessage: "Title cannot be empty",
|
|
101537
|
+
rejectBlank: true
|
|
101538
|
+
});
|
|
101463
101539
|
if (options.status) {
|
|
101464
101540
|
const result = IssueStatus.safeParse(options.status);
|
|
101465
101541
|
if (!result.success) throw new ValidationError(`Invalid status: ${options.status}`);
|
|
@@ -104668,6 +104744,7 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104668
104744
|
cwd = "";
|
|
104669
104745
|
config = null;
|
|
104670
104746
|
issues = [];
|
|
104747
|
+
invalidIssueFiles = [];
|
|
104671
104748
|
async run(options) {
|
|
104672
104749
|
const tbdRoot = await requireInit();
|
|
104673
104750
|
this.cwd = tbdRoot;
|
|
@@ -104676,7 +104753,11 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104676
104753
|
this.config = await readConfig(this.cwd);
|
|
104677
104754
|
} catch {}
|
|
104678
104755
|
try {
|
|
104679
|
-
this.
|
|
104756
|
+
this.invalidIssueFiles = [];
|
|
104757
|
+
this.issues = await listIssues(this.dataSyncDir, {
|
|
104758
|
+
warnOnInvalid: false,
|
|
104759
|
+
onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue)
|
|
104760
|
+
});
|
|
104680
104761
|
} catch {}
|
|
104681
104762
|
const statusInfo = await this.gatherStatusInfo();
|
|
104682
104763
|
const statsInfo = await this.gatherStatsInfo();
|
|
@@ -104689,14 +104770,18 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104689
104770
|
healthChecks.push(await this.checkIdMappingConflicts(options.fix));
|
|
104690
104771
|
healthChecks.push(await this.checkIdMappingDuplicates(options.fix));
|
|
104691
104772
|
healthChecks.push(await this.checkTempFiles(options.fix));
|
|
104692
|
-
healthChecks.push(this.checkIssueValidity(this.issues));
|
|
104773
|
+
healthChecks.push(this.checkIssueValidity(this.issues, this.invalidIssueFiles));
|
|
104693
104774
|
healthChecks.push(await this.checkWorktree(options.fix));
|
|
104694
104775
|
const dataLocationResult = await this.checkDataLocation(options.fix);
|
|
104695
104776
|
healthChecks.push(dataLocationResult);
|
|
104696
104777
|
if (dataLocationResult.status === "ok" && dataLocationResult.message?.includes("migrated")) {
|
|
104697
104778
|
this.dataSyncDir = await resolveDataSyncDir(this.cwd);
|
|
104698
104779
|
try {
|
|
104699
|
-
this.
|
|
104780
|
+
this.invalidIssueFiles = [];
|
|
104781
|
+
this.issues = await listIssues(this.dataSyncDir, {
|
|
104782
|
+
warnOnInvalid: false,
|
|
104783
|
+
onInvalidIssue: (invalidIssue) => this.invalidIssueFiles.push(invalidIssue)
|
|
104784
|
+
});
|
|
104700
104785
|
} catch {}
|
|
104701
104786
|
}
|
|
104702
104787
|
const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;
|
|
@@ -105039,8 +105124,12 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
105039
105124
|
suggestion: "Run: tbd doctor --fix"
|
|
105040
105125
|
};
|
|
105041
105126
|
}
|
|
105042
|
-
checkIssueValidity(issues) {
|
|
105127
|
+
checkIssueValidity(issues, invalidIssueFiles) {
|
|
105043
105128
|
const invalid = [];
|
|
105129
|
+
for (const invalidIssueFile of invalidIssueFiles) invalid.push({
|
|
105130
|
+
id: invalidIssueFile.file,
|
|
105131
|
+
reason: invalidIssueFile.reason
|
|
105132
|
+
});
|
|
105044
105133
|
for (const issue of issues) {
|
|
105045
105134
|
const issueId = issue.id ?? "unknown";
|
|
105046
105135
|
if (!issue.id) {
|
|
@@ -105090,7 +105179,8 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
105090
105179
|
return {
|
|
105091
105180
|
name: "Issue validity",
|
|
105092
105181
|
status: "error",
|
|
105093
|
-
message: `${invalid.length} invalid issue(s)`,
|
|
105182
|
+
message: `${invalid.length} invalid issue file(s)`,
|
|
105183
|
+
path: join(CONFIG_DIR, "issues"),
|
|
105094
105184
|
details: invalid.map((i) => `${i.id}: ${i.reason}`),
|
|
105095
105185
|
suggestion: "Manually fix or delete invalid issue files"
|
|
105096
105186
|
};
|