get-tbd 0.1.13 → 0.1.15
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/README.md +47 -28
- package/dist/bin.mjs +410 -170
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +202 -94
- package/dist/cli.mjs.map +1 -1
- package/dist/docs/README.md +47 -28
- package/dist/docs/SKILL.md +61 -18
- package/dist/docs/guidelines/bun-monorepo-patterns.md +2096 -0
- package/dist/docs/guidelines/cli-agent-skill-patterns.md +79 -5
- package/dist/docs/guidelines/error-handling-rules.md +66 -0
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +2868 -0
- package/dist/docs/guidelines/release-notes-guidelines.md +140 -0
- package/dist/docs/guidelines/{sync-troubleshooting.md → tbd-sync-troubleshooting.md} +1 -1
- package/dist/docs/guidelines/typescript-sorting-patterns.md +234 -0
- package/dist/docs/guidelines/typescript-yaml-handling-rules.md +195 -0
- package/dist/docs/install/claude-header.md +13 -6
- package/dist/docs/shortcuts/standard/agent-handoff.md +1 -0
- package/dist/docs/shortcuts/standard/checkout-third-party-repo.md +50 -0
- package/dist/docs/shortcuts/standard/{cleanup-all.md → code-cleanup-all.md} +3 -2
- package/dist/docs/shortcuts/standard/{cleanup-update-docstrings.md → code-cleanup-docstrings.md} +1 -0
- package/dist/docs/shortcuts/standard/{cleanup-remove-trivial-tests.md → code-cleanup-tests.md} +1 -0
- package/dist/docs/shortcuts/standard/{commit-code.md → code-review-and-commit.md} +1 -0
- package/dist/docs/shortcuts/standard/coding-spike.md +54 -0
- package/dist/docs/shortcuts/standard/create-or-update-pr-simple.md +1 -0
- package/dist/docs/shortcuts/standard/create-or-update-pr-with-validation-plan.md +1 -0
- package/dist/docs/shortcuts/standard/implement-beads.md +1 -0
- package/dist/docs/shortcuts/standard/merge-upstream.md +1 -0
- package/dist/docs/shortcuts/standard/new-architecture-doc.md +1 -0
- package/dist/docs/shortcuts/standard/new-guideline.md +8 -0
- package/dist/docs/shortcuts/standard/new-plan-spec.md +1 -0
- package/dist/docs/shortcuts/standard/new-research-brief.md +1 -0
- package/dist/docs/shortcuts/standard/new-shortcut.md +27 -1
- package/dist/docs/shortcuts/standard/new-validation-plan.md +1 -0
- package/dist/docs/shortcuts/standard/plan-implementation-with-beads.md +1 -0
- package/dist/docs/shortcuts/standard/precommit-process.md +1 -0
- package/dist/docs/shortcuts/standard/review-code-python.md +1 -0
- package/dist/docs/shortcuts/standard/review-code-typescript.md +1 -0
- package/dist/docs/shortcuts/standard/review-code.md +1 -0
- package/dist/docs/shortcuts/standard/review-github-pr.md +89 -17
- package/dist/docs/shortcuts/standard/revise-all-architecture-docs.md +1 -0
- package/dist/docs/shortcuts/standard/revise-architecture-doc.md +1 -0
- package/dist/docs/shortcuts/standard/setup-github-cli.md +1 -0
- package/dist/docs/shortcuts/standard/sync-failure-recovery.md +6 -53
- package/dist/docs/shortcuts/standard/update-specs-status.md +1 -0
- package/dist/docs/shortcuts/standard/welcome-user.md +2 -1
- package/dist/docs/shortcuts/system/skill-brief.md +1 -1
- package/dist/docs/shortcuts/system/skill.md +48 -12
- package/dist/docs/skill-brief.md +1 -1
- package/dist/docs/tbd-design.md +13 -1
- package/dist/index.d.mts +20 -6
- package/dist/index.mjs +2 -2
- package/dist/{src-BfhjLZXE.mjs → src-Ct16P2Ox.mjs} +154 -22
- package/dist/src-Ct16P2Ox.mjs.map +1 -0
- package/dist/tbd +410 -170
- package/package.json +1 -1
- package/dist/docs/guidelines/typescript-monorepo-patterns.md +0 -72
- package/dist/src-BfhjLZXE.mjs.map +0 -1
package/dist/bin.mjs
CHANGED
|
@@ -6638,8 +6638,16 @@ const IssueId = stringType().regex(/^is-[0-9a-z]{26}$/);
|
|
|
6638
6638
|
* Short ID: 1+ base36 characters used for external/display IDs.
|
|
6639
6639
|
* Typically 4 chars for new IDs (e.g., a7k2, b3m9).
|
|
6640
6640
|
* Imports may preserve longer numeric IDs (e.g., "100" from "tbd-100").
|
|
6641
|
+
* Legacy imports may include dots (e.g., "208.1" from hierarchical numbering).
|
|
6642
|
+
* Imports may include dashes/underscores (e.g., "stat-in_progress" from JSONL).
|
|
6641
6643
|
*/
|
|
6642
|
-
const ShortId = stringType().regex(/^[0-9a-z]+$/);
|
|
6644
|
+
const ShortId = stringType().regex(/^[0-9a-z._-]+$/);
|
|
6645
|
+
/**
|
|
6646
|
+
* ULID: 26 lowercase alphanumeric characters.
|
|
6647
|
+
* Used in internal IDs and ID mappings.
|
|
6648
|
+
* Example: 01hx5zzkbkactav9wevgemmvrz
|
|
6649
|
+
*/
|
|
6650
|
+
const Ulid = stringType().regex(/^[0-9a-z]{26}$/);
|
|
6643
6651
|
/**
|
|
6644
6652
|
* External Issue ID input: accepts {prefix}-{short} or just {short}.
|
|
6645
6653
|
* Examples: bd-a7k2, a7k2, bd-100, 100
|
|
@@ -6744,15 +6752,15 @@ const GitRemoteName = stringType().min(1).max(255).regex(/^[a-zA-Z0-9._-]+$/, "I
|
|
|
6744
6752
|
/**
|
|
6745
6753
|
* Doc cache configuration - maps destination paths to source locations.
|
|
6746
6754
|
*
|
|
6747
|
-
* Keys are destination paths relative to .tbd/docs/ (e.g., "shortcuts/standard/commit
|
|
6755
|
+
* Keys are destination paths relative to .tbd/docs/ (e.g., "shortcuts/standard/code-review-and-commit.md")
|
|
6748
6756
|
* Values are source locations:
|
|
6749
|
-
* - internal: prefix for bundled docs (e.g., "internal:shortcuts/standard/commit
|
|
6757
|
+
* - internal: prefix for bundled docs (e.g., "internal:shortcuts/standard/code-review-and-commit.md")
|
|
6750
6758
|
* - Full URL for external docs (e.g., "https://raw.githubusercontent.com/org/repo/main/file.md")
|
|
6751
6759
|
*
|
|
6752
6760
|
* Example:
|
|
6753
6761
|
* ```yaml
|
|
6754
6762
|
* doc_cache:
|
|
6755
|
-
* shortcuts/standard/commit
|
|
6763
|
+
* shortcuts/standard/code-review-and-commit.md: internal:shortcuts/standard/code-review-and-commit.md
|
|
6756
6764
|
* shortcuts/custom/my-shortcut.md: https://raw.githubusercontent.com/org/repo/main/shortcuts/my-shortcut.md
|
|
6757
6765
|
* ```
|
|
6758
6766
|
*/
|
|
@@ -6835,77 +6843,12 @@ const AtticEntrySchema = objectType({
|
|
|
6835
6843
|
remote_updated_at: Timestamp
|
|
6836
6844
|
})
|
|
6837
6845
|
});
|
|
6838
|
-
|
|
6839
|
-
//#endregion
|
|
6840
|
-
//#region src/utils/markdown-utils.ts
|
|
6841
6846
|
/**
|
|
6842
|
-
*
|
|
6843
|
-
*
|
|
6844
|
-
*
|
|
6845
|
-
*/
|
|
6846
|
-
/**
|
|
6847
|
-
* Normalize line endings to LF.
|
|
6848
|
-
*/
|
|
6849
|
-
function normalizeLineEndings(content) {
|
|
6850
|
-
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
6851
|
-
}
|
|
6852
|
-
/**
|
|
6853
|
-
* Parse markdown content into frontmatter and body.
|
|
6854
|
-
* Handles both LF and CRLF line endings.
|
|
6855
|
-
*
|
|
6856
|
-
* @returns Object with frontmatter (null if none) and body
|
|
6847
|
+
* ID mapping YAML file schema for ids.yml.
|
|
6848
|
+
* Maps short IDs to ULIDs.
|
|
6849
|
+
* Format: { "a7k2": "01hx5zzkbkactav9wevgemmvrz", ... }
|
|
6857
6850
|
*/
|
|
6858
|
-
|
|
6859
|
-
const normalized = normalizeLineEndings(content);
|
|
6860
|
-
if (!matter.test(normalized)) return {
|
|
6861
|
-
frontmatter: null,
|
|
6862
|
-
body: content
|
|
6863
|
-
};
|
|
6864
|
-
try {
|
|
6865
|
-
const parsed = matter(normalized);
|
|
6866
|
-
const data = parsed.data;
|
|
6867
|
-
let frontmatter = null;
|
|
6868
|
-
if (data && Object.keys(data).length > 0) {
|
|
6869
|
-
const lines = [];
|
|
6870
|
-
for (const [key, value] of Object.entries(data)) if (Array.isArray(value)) {
|
|
6871
|
-
lines.push(`${key}:`);
|
|
6872
|
-
for (const item of value) lines.push(` - ${String(item)}`);
|
|
6873
|
-
} else if (typeof value === "object" && value !== null) {
|
|
6874
|
-
lines.push(`${key}:`);
|
|
6875
|
-
for (const [subKey, subValue] of Object.entries(value)) lines.push(` ${subKey}: ${String(subValue)}`);
|
|
6876
|
-
} else lines.push(`${key}: ${String(value)}`);
|
|
6877
|
-
frontmatter = lines.join("\n");
|
|
6878
|
-
} else frontmatter = "";
|
|
6879
|
-
const body = parsed.content.replace(/^\n+/, "");
|
|
6880
|
-
return {
|
|
6881
|
-
frontmatter,
|
|
6882
|
-
body
|
|
6883
|
-
};
|
|
6884
|
-
} catch {
|
|
6885
|
-
return {
|
|
6886
|
-
frontmatter: null,
|
|
6887
|
-
body: content
|
|
6888
|
-
};
|
|
6889
|
-
}
|
|
6890
|
-
}
|
|
6891
|
-
/**
|
|
6892
|
-
* Strip YAML frontmatter from markdown content.
|
|
6893
|
-
* Returns the body content without frontmatter, with leading newlines trimmed.
|
|
6894
|
-
* Handles both LF and CRLF line endings.
|
|
6895
|
-
*/
|
|
6896
|
-
function stripFrontmatter(content) {
|
|
6897
|
-
return parseMarkdown(content).body;
|
|
6898
|
-
}
|
|
6899
|
-
/**
|
|
6900
|
-
* Insert content after YAML frontmatter.
|
|
6901
|
-
* If no frontmatter exists, prepends the content.
|
|
6902
|
-
* Content is inserted directly after ---. Include leading newlines in toInsert if needed.
|
|
6903
|
-
*/
|
|
6904
|
-
function insertAfterFrontmatter(content, toInsert) {
|
|
6905
|
-
const { frontmatter, body } = parseMarkdown(content);
|
|
6906
|
-
if (frontmatter === null) return toInsert + content;
|
|
6907
|
-
return `${frontmatter ? `---\n${frontmatter}\n---` : "---\n---"}\n${toInsert}\n\n${body}`;
|
|
6908
|
-
}
|
|
6851
|
+
const IdMappingYamlSchema = recordType(ShortId, Ulid);
|
|
6909
6852
|
|
|
6910
6853
|
//#endregion
|
|
6911
6854
|
//#region ../../node_modules/.pnpm/yaml@2.8.2/node_modules/yaml/dist/nodes/identity.js
|
|
@@ -13557,6 +13500,197 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
13557
13500
|
exports.visitAsync = visit.visitAsync;
|
|
13558
13501
|
}));
|
|
13559
13502
|
|
|
13503
|
+
//#endregion
|
|
13504
|
+
//#region src/lib/settings.ts
|
|
13505
|
+
var import_dist$2 = require_dist$2();
|
|
13506
|
+
/**
|
|
13507
|
+
* Default line width for YAML serialization.
|
|
13508
|
+
* 88 characters is a good balance between readability and avoiding excessive wrapping.
|
|
13509
|
+
* (Matches Python's Black formatter default.)
|
|
13510
|
+
*/
|
|
13511
|
+
const YAML_LINE_WIDTH = 88;
|
|
13512
|
+
/**
|
|
13513
|
+
* Default string type for YAML serialization.
|
|
13514
|
+
* 'PLAIN' means no forced quoting - YAML only quotes when necessary.
|
|
13515
|
+
*/
|
|
13516
|
+
const YAML_DEFAULT_STRING_TYPE = "PLAIN";
|
|
13517
|
+
/**
|
|
13518
|
+
* Default key type for YAML serialization.
|
|
13519
|
+
* 'PLAIN' means object keys are unquoted unless required.
|
|
13520
|
+
*/
|
|
13521
|
+
const YAML_DEFAULT_KEY_TYPE = "PLAIN";
|
|
13522
|
+
/**
|
|
13523
|
+
* Default YAML serialization options for readable output.
|
|
13524
|
+
*
|
|
13525
|
+
* Design principles:
|
|
13526
|
+
* - No forced quoting: YAML only quotes when necessary for special characters
|
|
13527
|
+
* - Reasonable line wrapping: 88 chars prevents overly long lines
|
|
13528
|
+
* - Plain keys: No unnecessary quotes around object keys
|
|
13529
|
+
* - Sorted keys: Deterministic output for diffs and version control
|
|
13530
|
+
*/
|
|
13531
|
+
const YAML_STRINGIFY_OPTIONS = {
|
|
13532
|
+
lineWidth: YAML_LINE_WIDTH,
|
|
13533
|
+
defaultStringType: YAML_DEFAULT_STRING_TYPE,
|
|
13534
|
+
defaultKeyType: YAML_DEFAULT_KEY_TYPE,
|
|
13535
|
+
sortMapEntries: true
|
|
13536
|
+
};
|
|
13537
|
+
/**
|
|
13538
|
+
* YAML serialization options for compact output (e.g., frontmatter).
|
|
13539
|
+
* Uses lineWidth: 0 to prevent wrapping within values.
|
|
13540
|
+
*/
|
|
13541
|
+
const YAML_STRINGIFY_OPTIONS_COMPACT = {
|
|
13542
|
+
lineWidth: 0,
|
|
13543
|
+
defaultStringType: YAML_DEFAULT_STRING_TYPE,
|
|
13544
|
+
defaultKeyType: YAML_DEFAULT_KEY_TYPE,
|
|
13545
|
+
sortMapEntries: true
|
|
13546
|
+
};
|
|
13547
|
+
|
|
13548
|
+
//#endregion
|
|
13549
|
+
//#region src/utils/yaml-utils.ts
|
|
13550
|
+
/**
|
|
13551
|
+
* YAML utility functions.
|
|
13552
|
+
*
|
|
13553
|
+
* Provides centralized YAML parsing and serialization with:
|
|
13554
|
+
* - Merge conflict detection for user-editable files
|
|
13555
|
+
* - Consistent, readable formatting defaults
|
|
13556
|
+
* - Proper handling of special characters (colons, quotes, etc.)
|
|
13557
|
+
*
|
|
13558
|
+
* IMPORTANT: Always use these utilities instead of raw yaml package functions.
|
|
13559
|
+
* This ensures consistent formatting and proper error handling across the codebase.
|
|
13560
|
+
*/
|
|
13561
|
+
/**
|
|
13562
|
+
* Serialize data to YAML with readable formatting.
|
|
13563
|
+
*
|
|
13564
|
+
* Uses consistent defaults:
|
|
13565
|
+
* - No forced quoting (YAML only quotes when necessary)
|
|
13566
|
+
* - lineWidth of 88 provides reasonable wrapping for long strings
|
|
13567
|
+
* - Plain keys without quotes
|
|
13568
|
+
* - Sorted keys for deterministic output
|
|
13569
|
+
*
|
|
13570
|
+
* @param data - Data to serialize
|
|
13571
|
+
* @param options - Optional overrides for default options
|
|
13572
|
+
* @returns YAML string
|
|
13573
|
+
*/
|
|
13574
|
+
function stringifyYaml(data, options) {
|
|
13575
|
+
return (0, import_dist$2.stringify)(data, {
|
|
13576
|
+
...YAML_STRINGIFY_OPTIONS,
|
|
13577
|
+
...options
|
|
13578
|
+
});
|
|
13579
|
+
}
|
|
13580
|
+
/**
|
|
13581
|
+
* Serialize data to YAML without line wrapping (compact mode).
|
|
13582
|
+
* Useful for frontmatter where values should stay on single lines.
|
|
13583
|
+
*
|
|
13584
|
+
* @param data - Data to serialize
|
|
13585
|
+
* @returns YAML string with no line wrapping
|
|
13586
|
+
*/
|
|
13587
|
+
function stringifyYamlCompact(data) {
|
|
13588
|
+
return (0, import_dist$2.stringify)(data, YAML_STRINGIFY_OPTIONS_COMPACT);
|
|
13589
|
+
}
|
|
13590
|
+
/**
|
|
13591
|
+
* Error thrown when YAML content contains unresolved merge conflict markers.
|
|
13592
|
+
*/
|
|
13593
|
+
var MergeConflictError = class extends Error {
|
|
13594
|
+
constructor(message, filePath) {
|
|
13595
|
+
super(message);
|
|
13596
|
+
this.filePath = filePath;
|
|
13597
|
+
this.name = "MergeConflictError";
|
|
13598
|
+
}
|
|
13599
|
+
};
|
|
13600
|
+
/**
|
|
13601
|
+
* Regex patterns for git merge conflict markers.
|
|
13602
|
+
*/
|
|
13603
|
+
const CONFLICT_PATTERNS = {
|
|
13604
|
+
start: /^<<<<<<< /m,
|
|
13605
|
+
separator: /^=======/m,
|
|
13606
|
+
end: /^>>>>>>> /m
|
|
13607
|
+
};
|
|
13608
|
+
/**
|
|
13609
|
+
* Check if content contains git merge conflict markers.
|
|
13610
|
+
*/
|
|
13611
|
+
function hasMergeConflictMarkers(content) {
|
|
13612
|
+
return CONFLICT_PATTERNS.start.test(content) || CONFLICT_PATTERNS.separator.test(content) || CONFLICT_PATTERNS.end.test(content);
|
|
13613
|
+
}
|
|
13614
|
+
/**
|
|
13615
|
+
* Parse YAML content with merge conflict detection.
|
|
13616
|
+
*
|
|
13617
|
+
* If the content contains merge conflict markers, throws a MergeConflictError
|
|
13618
|
+
* with a helpful message instead of a cryptic YAML parse error.
|
|
13619
|
+
*
|
|
13620
|
+
* @param content - The YAML content to parse
|
|
13621
|
+
* @param filePath - Optional file path for error messages
|
|
13622
|
+
* @returns Parsed YAML data
|
|
13623
|
+
* @throws MergeConflictError if content has conflict markers
|
|
13624
|
+
* @throws Error if YAML is invalid for other reasons
|
|
13625
|
+
*/
|
|
13626
|
+
function parseYamlWithConflictDetection(content, filePath) {
|
|
13627
|
+
if (hasMergeConflictMarkers(content)) throw new MergeConflictError(`File${filePath ? ` in ${filePath}` : ""} contains unresolved git merge conflict markers.\nThis usually happens when 'tbd sync' encountered conflicts that weren't properly resolved.\nTo fix: manually edit the file to resolve conflicts, or run 'tbd doctor --fix'.`, filePath);
|
|
13628
|
+
return (0, import_dist$2.parse)(content);
|
|
13629
|
+
}
|
|
13630
|
+
|
|
13631
|
+
//#endregion
|
|
13632
|
+
//#region src/utils/markdown-utils.ts
|
|
13633
|
+
/**
|
|
13634
|
+
* Markdown utilities for processing markdown content.
|
|
13635
|
+
*
|
|
13636
|
+
* Uses gray-matter for parsing and centralized yaml-utils for stringify to ensure
|
|
13637
|
+
* proper handling of special YAML characters (colons, quotes, etc.).
|
|
13638
|
+
*/
|
|
13639
|
+
/**
|
|
13640
|
+
* Normalize line endings to LF.
|
|
13641
|
+
*/
|
|
13642
|
+
function normalizeLineEndings(content) {
|
|
13643
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
13644
|
+
}
|
|
13645
|
+
/**
|
|
13646
|
+
* Parse markdown content into frontmatter and body.
|
|
13647
|
+
* Handles both LF and CRLF line endings.
|
|
13648
|
+
*
|
|
13649
|
+
* @returns Object with frontmatter (null if none) and body
|
|
13650
|
+
*/
|
|
13651
|
+
function parseMarkdown(content) {
|
|
13652
|
+
const normalized = normalizeLineEndings(content);
|
|
13653
|
+
if (!matter.test(normalized)) return {
|
|
13654
|
+
frontmatter: null,
|
|
13655
|
+
body: content
|
|
13656
|
+
};
|
|
13657
|
+
try {
|
|
13658
|
+
const parsed = matter(normalized);
|
|
13659
|
+
const data = parsed.data;
|
|
13660
|
+
let frontmatter = null;
|
|
13661
|
+
if (data && Object.keys(data).length > 0) frontmatter = stringifyYamlCompact(data).trimEnd();
|
|
13662
|
+
else frontmatter = "";
|
|
13663
|
+
const body = parsed.content.replace(/^\n+/, "");
|
|
13664
|
+
return {
|
|
13665
|
+
frontmatter,
|
|
13666
|
+
body
|
|
13667
|
+
};
|
|
13668
|
+
} catch {
|
|
13669
|
+
return {
|
|
13670
|
+
frontmatter: null,
|
|
13671
|
+
body: content
|
|
13672
|
+
};
|
|
13673
|
+
}
|
|
13674
|
+
}
|
|
13675
|
+
/**
|
|
13676
|
+
* Strip YAML frontmatter from markdown content.
|
|
13677
|
+
* Returns the body content without frontmatter, with leading newlines trimmed.
|
|
13678
|
+
* Handles both LF and CRLF line endings.
|
|
13679
|
+
*/
|
|
13680
|
+
function stripFrontmatter(content) {
|
|
13681
|
+
return parseMarkdown(content).body;
|
|
13682
|
+
}
|
|
13683
|
+
/**
|
|
13684
|
+
* Insert content after YAML frontmatter.
|
|
13685
|
+
* If no frontmatter exists, prepends the content.
|
|
13686
|
+
* Content is inserted directly after ---. Include leading newlines in toInsert if needed.
|
|
13687
|
+
*/
|
|
13688
|
+
function insertAfterFrontmatter(content, toInsert) {
|
|
13689
|
+
const { frontmatter, body } = parseMarkdown(content);
|
|
13690
|
+
if (frontmatter === null) return toInsert + content;
|
|
13691
|
+
return `${frontmatter ? `---\n${frontmatter}\n---` : "---\n---"}\n${toInsert}\n\n${body}`;
|
|
13692
|
+
}
|
|
13693
|
+
|
|
13560
13694
|
//#endregion
|
|
13561
13695
|
//#region src/file/parser.ts
|
|
13562
13696
|
/**
|
|
@@ -13577,14 +13711,13 @@ var require_dist$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
13577
13711
|
*
|
|
13578
13712
|
* See: tbd-design.md §2.1 Markdown + YAML Front Matter Format
|
|
13579
13713
|
*/
|
|
13580
|
-
var import_dist$2 = require_dist$2();
|
|
13581
13714
|
/**
|
|
13582
13715
|
* gray-matter options using the 'yaml' package as engine.
|
|
13583
13716
|
* This preserves date strings instead of converting them to Date objects.
|
|
13584
13717
|
*/
|
|
13585
13718
|
const matterOptions = { engines: { yaml: {
|
|
13586
13719
|
parse: (str) => (0, import_dist$2.parse)(str),
|
|
13587
|
-
stringify: (obj) => (
|
|
13720
|
+
stringify: (obj) => stringifyYaml(obj)
|
|
13588
13721
|
} } };
|
|
13589
13722
|
/**
|
|
13590
13723
|
* Parse a Markdown file with YAML front matter.
|
|
@@ -13641,8 +13774,7 @@ function serializeIssue(issue) {
|
|
|
13641
13774
|
for (const key of Object.keys(metadata).sort()) sortedMetadata[key] = metadata[key];
|
|
13642
13775
|
const parts = [
|
|
13643
13776
|
"---",
|
|
13644
|
-
(
|
|
13645
|
-
sortMapEntries: true,
|
|
13777
|
+
stringifyYaml(sortedMetadata, {
|
|
13646
13778
|
lineWidth: 0,
|
|
13647
13779
|
nullStr: "null"
|
|
13648
13780
|
}).trim(),
|
|
@@ -13664,7 +13796,7 @@ function serializeIssue(issue) {
|
|
|
13664
13796
|
* Package version, derived from git at build time.
|
|
13665
13797
|
* Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
|
|
13666
13798
|
*/
|
|
13667
|
-
const VERSION$1 = "0.1.
|
|
13799
|
+
const VERSION$1 = "0.1.15";
|
|
13668
13800
|
|
|
13669
13801
|
//#endregion
|
|
13670
13802
|
//#region src/cli/lib/version.ts
|
|
@@ -97476,11 +97608,8 @@ async function readConfigWithMigration(baseDir) {
|
|
|
97476
97608
|
*/
|
|
97477
97609
|
async function writeConfig(baseDir, config) {
|
|
97478
97610
|
const configPath = join(baseDir, CONFIG_FILE);
|
|
97479
|
-
let content = (
|
|
97480
|
-
|
|
97481
|
-
lineWidth: 0
|
|
97482
|
-
});
|
|
97483
|
-
if (config.docs_cache && Object.keys(config.docs_cache).length > 0) content = content.replace("docs_cache:", "# Documentation cache configuration.\n# files: Maps destination paths (relative to .tbd/docs/) to source locations.\n# Sources can be:\n# - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/commit-code.md\")\n# - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n# lookup_path: Search paths for doc lookup (like shell $PATH). Earlier paths take precedence.\n#\n# To sync docs: tbd sync --docs\n# To check status: tbd sync --status\n#\n# Auto-sync: Docs are automatically synced when stale (default: every 24 hours).\n# Configure with settings.doc_auto_sync_hours (0 = disabled).\ndocs_cache:");
|
|
97611
|
+
let content = stringifyYaml(config, { lineWidth: 0 });
|
|
97612
|
+
if (config.docs_cache && Object.keys(config.docs_cache).length > 0) content = content.replace("docs_cache:", "# Documentation cache configuration.\n# files: Maps destination paths (relative to .tbd/docs/) to source locations.\n# Sources can be:\n# - internal: prefix for bundled docs (e.g., \"internal:shortcuts/standard/code-review-and-commit.md\")\n# - Full URL for external docs (e.g., \"https://raw.githubusercontent.com/org/repo/main/file.md\")\n# lookup_path: Search paths for doc lookup (like shell $PATH). Earlier paths take precedence.\n#\n# To sync docs: tbd sync --docs\n# To check status: tbd sync --status\n#\n# Auto-sync: Docs are automatically synced when stale (default: every 24 hours).\n# Configure with settings.doc_auto_sync_hours (0 = disabled).\ndocs_cache:");
|
|
97484
97613
|
await writeFile(configPath, content);
|
|
97485
97614
|
}
|
|
97486
97615
|
/**
|
|
@@ -97539,10 +97668,7 @@ async function readLocalState(baseDir) {
|
|
|
97539
97668
|
async function writeLocalState(baseDir, state) {
|
|
97540
97669
|
const statePath = join(baseDir, STATE_FILE);
|
|
97541
97670
|
await mkdir(join(baseDir, ".tbd"), { recursive: true });
|
|
97542
|
-
await writeFile(statePath, (
|
|
97543
|
-
sortMapEntries: true,
|
|
97544
|
-
lineWidth: 0
|
|
97545
|
-
}));
|
|
97671
|
+
await writeFile(statePath, stringifyYaml(state, { lineWidth: 0 }));
|
|
97546
97672
|
}
|
|
97547
97673
|
/**
|
|
97548
97674
|
* Update specific fields in local state (merge with existing).
|
|
@@ -97799,6 +97925,67 @@ async function ensureGitignorePatterns(gitignorePath, patterns, header) {
|
|
|
97799
97925
|
};
|
|
97800
97926
|
}
|
|
97801
97927
|
|
|
97928
|
+
//#endregion
|
|
97929
|
+
//#region src/cli/lib/prefix-detection.ts
|
|
97930
|
+
/**
|
|
97931
|
+
* Prefix validation and beads prefix extraction module.
|
|
97932
|
+
*
|
|
97933
|
+
* Provides functions to validate prefixes and extract prefix from beads config.
|
|
97934
|
+
* Used by setup commands to validate user-provided prefixes and migrate from beads.
|
|
97935
|
+
*/
|
|
97936
|
+
/** Maximum length for a valid prefix */
|
|
97937
|
+
const MAX_PREFIX_LENGTH = 20;
|
|
97938
|
+
/** Minimum length for a valid prefix */
|
|
97939
|
+
const MIN_PREFIX_LENGTH = 1;
|
|
97940
|
+
/** Recommended minimum length */
|
|
97941
|
+
const RECOMMENDED_MIN_LENGTH = 2;
|
|
97942
|
+
/** Recommended maximum length */
|
|
97943
|
+
const RECOMMENDED_MAX_LENGTH = 8;
|
|
97944
|
+
/**
|
|
97945
|
+
* Check if a prefix is valid (hard rules, always enforced).
|
|
97946
|
+
* - Must be 1-20 characters
|
|
97947
|
+
* - Must start with a letter (a-z)
|
|
97948
|
+
* - Must end with alphanumeric (a-z0-9)
|
|
97949
|
+
* - Middle characters can be alphanumeric, dot, or underscore
|
|
97950
|
+
* - No dashes allowed (breaks ID syntax)
|
|
97951
|
+
*/
|
|
97952
|
+
function isValidPrefix(s) {
|
|
97953
|
+
if (!s) return false;
|
|
97954
|
+
if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;
|
|
97955
|
+
if (!/^[a-z]/.test(s)) return false;
|
|
97956
|
+
if (s.length > 1 && !/[a-z0-9]$/.test(s)) return false;
|
|
97957
|
+
return /^[a-z][a-z0-9._]*$/.test(s);
|
|
97958
|
+
}
|
|
97959
|
+
/**
|
|
97960
|
+
* Check if a prefix follows recommended format (soft rules).
|
|
97961
|
+
* - Must be 2-8 characters
|
|
97962
|
+
* - Must be alphabetic only (a-z)
|
|
97963
|
+
*
|
|
97964
|
+
* Prefixes that don't match this can still be used with --force.
|
|
97965
|
+
*/
|
|
97966
|
+
function isRecommendedPrefix(s) {
|
|
97967
|
+
if (!s) return false;
|
|
97968
|
+
if (s.length < RECOMMENDED_MIN_LENGTH || s.length > RECOMMENDED_MAX_LENGTH) return false;
|
|
97969
|
+
return /^[a-z]+$/.test(s);
|
|
97970
|
+
}
|
|
97971
|
+
/**
|
|
97972
|
+
* Get prefix from existing beads config.
|
|
97973
|
+
*
|
|
97974
|
+
* Looks for .beads/config.yaml and extracts display.id_prefix
|
|
97975
|
+
*
|
|
97976
|
+
* @param cwd Current working directory
|
|
97977
|
+
* @returns The beads prefix, or null if not found
|
|
97978
|
+
*/
|
|
97979
|
+
async function getBeadsPrefix(cwd) {
|
|
97980
|
+
try {
|
|
97981
|
+
const prefix = ((0, import_dist$2.parse)(await readFile(join(cwd, ".beads", "config.yaml"), "utf-8"))?.display)?.id_prefix;
|
|
97982
|
+
if (typeof prefix === "string" && isValidPrefix(prefix)) return prefix;
|
|
97983
|
+
return null;
|
|
97984
|
+
} catch {
|
|
97985
|
+
return null;
|
|
97986
|
+
}
|
|
97987
|
+
}
|
|
97988
|
+
|
|
97802
97989
|
//#endregion
|
|
97803
97990
|
//#region src/utils/time-utils.ts
|
|
97804
97991
|
/**
|
|
@@ -98644,7 +98831,15 @@ var InitHandler = class extends BaseCommand {
|
|
|
98644
98831
|
} catch (error) {
|
|
98645
98832
|
if (error instanceof CLIError) throw error;
|
|
98646
98833
|
}
|
|
98647
|
-
if (!options.prefix) throw new ValidationError("The --prefix option is required\n\nUsage: tbd init --prefix=<name>\n\nThe prefix is used for display IDs (e.g., proj-a7k2, myapp-b3m9)\nChoose a short 2-
|
|
98834
|
+
if (!options.prefix) throw new ValidationError("The --prefix option is required\n\nUsage: tbd init --prefix=<name>\n\nThe prefix is used for display IDs (e.g., proj-a7k2, myapp-b3m9)\nChoose a short 2-8 letter prefix for your project (e.g., tbd, myp, proj).\n\nFor full setup with integrations: tbd setup --auto --prefix=<name>");
|
|
98835
|
+
const prefix = options.prefix;
|
|
98836
|
+
if (!isValidPrefix(prefix)) throw new ValidationError("Invalid prefix format.\nPrefix must be 1-20 lowercase characters:\n - Must start with a letter (a-z)\n - Must end with alphanumeric (a-z, 0-9)\n - Middle characters can include dots (.) and underscores (_)\n - No dashes allowed (breaks ID syntax)\n\nExample:\n tbd init --prefix=tbd");
|
|
98837
|
+
if (!isRecommendedPrefix(prefix) && !options.force) throw new ValidationError(`Prefix "${prefix}" is not recommended.\nRecommended prefixes are 2-8 alphabetic characters (e.g., "tbd", "myp", "proj").
|
|
98838
|
+
|
|
98839
|
+
If you really want to use this prefix, add --force to override.
|
|
98840
|
+
|
|
98841
|
+
Example:
|
|
98842
|
+
tbd init --prefix=${prefix} --force`);
|
|
98648
98843
|
if (this.checkDryRun("Would initialize tbd repository", options)) return;
|
|
98649
98844
|
await this.execute(async () => {
|
|
98650
98845
|
await initConfig(cwd, VERSION, options.prefix);
|
|
@@ -98708,7 +98903,7 @@ var InitHandler = class extends BaseCommand {
|
|
|
98708
98903
|
});
|
|
98709
98904
|
}
|
|
98710
98905
|
};
|
|
98711
|
-
const initCommand = new Command("init").description("Initialize tbd in a git repository").option("--prefix <name>", "Project prefix for display IDs (
|
|
98906
|
+
const initCommand = new Command("init").description("Initialize tbd in a git repository").option("--prefix <name>", "Project prefix for display IDs (2-8 alphabetic recommended)").option("--force", "Allow non-recommended prefix format").option("--sync-branch <name>", "Sync branch name (default: tbd-sync)").option("--remote <name>", "Remote name (default: origin)").action(async (options, command) => {
|
|
98712
98907
|
await new InitHandler(command).run(options);
|
|
98713
98908
|
});
|
|
98714
98909
|
|
|
@@ -99174,7 +99369,10 @@ async function loadIdMapping(baseDir) {
|
|
|
99174
99369
|
ulidToShort: /* @__PURE__ */ new Map()
|
|
99175
99370
|
};
|
|
99176
99371
|
}
|
|
99177
|
-
const
|
|
99372
|
+
const rawData = parseYamlWithConflictDetection(content, filePath) ?? {};
|
|
99373
|
+
const parseResult = IdMappingYamlSchema.safeParse(rawData);
|
|
99374
|
+
if (!parseResult.success) throw new Error(`Invalid ID mapping format in ${filePath}: ${parseResult.error.message}`);
|
|
99375
|
+
const data = parseResult.data;
|
|
99178
99376
|
const shortToUlid = /* @__PURE__ */ new Map();
|
|
99179
99377
|
const ulidToShort = /* @__PURE__ */ new Map();
|
|
99180
99378
|
for (const [shortId, ulid] of Object.entries(data)) {
|
|
@@ -99195,7 +99393,7 @@ async function saveIdMapping(baseDir, mapping) {
|
|
|
99195
99393
|
const data = {};
|
|
99196
99394
|
const sortedKeys = naturalSort(Array.from(mapping.shortToUlid.keys()));
|
|
99197
99395
|
for (const key of sortedKeys) data[key] = mapping.shortToUlid.get(key);
|
|
99198
|
-
await writeFile(filePath, (
|
|
99396
|
+
await writeFile(filePath, stringifyYaml(data));
|
|
99199
99397
|
}
|
|
99200
99398
|
/**
|
|
99201
99399
|
* Calculate the optimal short ID length based on existing ID count.
|
|
@@ -99264,6 +99462,55 @@ function resolveToInternalId(input, mapping) {
|
|
|
99264
99462
|
if (!ulid) throw new Error(`Unknown issue ID: ${input}. Short ID "${shortId}" not found in mapping.`);
|
|
99265
99463
|
return makeInternalId(ulid);
|
|
99266
99464
|
}
|
|
99465
|
+
/**
|
|
99466
|
+
* Parse an ID mapping from raw YAML content.
|
|
99467
|
+
* Used for loading mappings from git show output during conflict resolution.
|
|
99468
|
+
*
|
|
99469
|
+
* @throws MergeConflictError if content contains merge conflict markers
|
|
99470
|
+
*/
|
|
99471
|
+
function parseIdMappingFromYaml(content) {
|
|
99472
|
+
const rawData = parseYamlWithConflictDetection(content) ?? {};
|
|
99473
|
+
const parseResult = IdMappingYamlSchema.safeParse(rawData);
|
|
99474
|
+
if (!parseResult.success) throw new Error(`Invalid ID mapping format: ${parseResult.error.message}`);
|
|
99475
|
+
const data = parseResult.data;
|
|
99476
|
+
const shortToUlid = /* @__PURE__ */ new Map();
|
|
99477
|
+
const ulidToShort = /* @__PURE__ */ new Map();
|
|
99478
|
+
for (const [shortId, ulid] of Object.entries(data)) {
|
|
99479
|
+
shortToUlid.set(shortId, ulid);
|
|
99480
|
+
ulidToShort.set(ulid, shortId);
|
|
99481
|
+
}
|
|
99482
|
+
return {
|
|
99483
|
+
shortToUlid,
|
|
99484
|
+
ulidToShort
|
|
99485
|
+
};
|
|
99486
|
+
}
|
|
99487
|
+
/**
|
|
99488
|
+
* Merge two ID mappings by combining all entries from both.
|
|
99489
|
+
* ID mappings are always additive (new IDs are only added, never removed),
|
|
99490
|
+
* so merging simply unions all key-value pairs.
|
|
99491
|
+
*
|
|
99492
|
+
* If the same short ID maps to different ULIDs in each mapping (a conflict),
|
|
99493
|
+
* the local mapping takes precedence (caller should log a warning).
|
|
99494
|
+
*
|
|
99495
|
+
* @param local - The local ID mapping
|
|
99496
|
+
* @param remote - The remote ID mapping
|
|
99497
|
+
* @returns Merged mapping with all entries from both
|
|
99498
|
+
*/
|
|
99499
|
+
function mergeIdMappings(local, remote) {
|
|
99500
|
+
const merged = {
|
|
99501
|
+
shortToUlid: new Map(local.shortToUlid),
|
|
99502
|
+
ulidToShort: new Map(local.ulidToShort)
|
|
99503
|
+
};
|
|
99504
|
+
for (const [shortId, ulid] of remote.shortToUlid) if (!merged.shortToUlid.has(shortId)) {
|
|
99505
|
+
merged.shortToUlid.set(shortId, ulid);
|
|
99506
|
+
merged.ulidToShort.set(ulid, shortId);
|
|
99507
|
+
}
|
|
99508
|
+
for (const [ulid, shortId] of remote.ulidToShort) if (!merged.ulidToShort.has(ulid) && !merged.shortToUlid.has(shortId)) {
|
|
99509
|
+
merged.shortToUlid.set(shortId, ulid);
|
|
99510
|
+
merged.ulidToShort.set(ulid, shortId);
|
|
99511
|
+
}
|
|
99512
|
+
return merged;
|
|
99513
|
+
}
|
|
99267
99514
|
|
|
99268
99515
|
//#endregion
|
|
99269
99516
|
//#region src/lib/priority.ts
|
|
@@ -99472,8 +99719,9 @@ var CreateHandler = class extends BaseCommand {
|
|
|
99472
99719
|
let description = options.description;
|
|
99473
99720
|
if (options.file) try {
|
|
99474
99721
|
description = await readFile(options.file, "utf-8");
|
|
99475
|
-
} catch {
|
|
99476
|
-
|
|
99722
|
+
} catch (error) {
|
|
99723
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
99724
|
+
throw new CLIError(`Failed to read description from file '${options.file}': ${message}`);
|
|
99477
99725
|
}
|
|
99478
99726
|
let specPath;
|
|
99479
99727
|
if (options.spec) try {
|
|
@@ -100128,15 +100376,8 @@ function normalizePath(path) {
|
|
|
100128
100376
|
*/
|
|
100129
100377
|
var ListHandler = class extends BaseCommand {
|
|
100130
100378
|
async run(options) {
|
|
100131
|
-
const
|
|
100132
|
-
let issues;
|
|
100133
|
-
let dataCtx;
|
|
100134
|
-
try {
|
|
100135
|
-
dataCtx = await loadDataContext(tbdRoot);
|
|
100136
|
-
issues = await listIssues(dataCtx.dataSyncDir);
|
|
100137
|
-
} catch {
|
|
100138
|
-
throw new CLIError("Failed to read issues");
|
|
100139
|
-
}
|
|
100379
|
+
const dataCtx = await loadDataContext(await requireInit());
|
|
100380
|
+
let issues = await listIssues(dataCtx.dataSyncDir);
|
|
100140
100381
|
issues = this.filterIssues(issues, options, dataCtx.mapping);
|
|
100141
100382
|
issues = this.sortIssues(issues, options.sort ?? "priority", dataCtx.mapping);
|
|
100142
100383
|
issues = applyLimit(issues, options.limit);
|
|
@@ -101449,8 +101690,8 @@ var DocSync = class {
|
|
|
101449
101690
|
* Parse a source string into a DocSource.
|
|
101450
101691
|
*
|
|
101451
101692
|
* @example
|
|
101452
|
-
* parseSource('internal:shortcuts/standard/commit
|
|
101453
|
-
* // => { type: 'internal', location: 'shortcuts/standard/commit
|
|
101693
|
+
* parseSource('internal:shortcuts/standard/code-review-and-commit.md')
|
|
101694
|
+
* // => { type: 'internal', location: 'shortcuts/standard/code-review-and-commit.md' }
|
|
101454
101695
|
*
|
|
101455
101696
|
* @example
|
|
101456
101697
|
* parseSource('https://raw.githubusercontent.com/org/repo/main/file.md')
|
|
@@ -102136,7 +102377,24 @@ var SyncHandler = class extends BaseCommand {
|
|
|
102136
102377
|
} catch {
|
|
102137
102378
|
this.output.debug(`Issue ${localIssue.id} not on remote, keeping local`);
|
|
102138
102379
|
}
|
|
102380
|
+
try {
|
|
102381
|
+
const remoteIdsContent = await git("show", `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
102382
|
+
if (remoteIdsContent) {
|
|
102383
|
+
const localMapping = await loadIdMapping(this.dataSyncDir);
|
|
102384
|
+
const remoteMapping = parseIdMappingFromYaml(remoteIdsContent);
|
|
102385
|
+
const mergedMapping = mergeIdMappings(localMapping, remoteMapping);
|
|
102386
|
+
await saveIdMapping(this.dataSyncDir, mergedMapping);
|
|
102387
|
+
this.output.debug(`Merged ID mappings: ${localMapping.shortToUlid.size} local + ${remoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`);
|
|
102388
|
+
}
|
|
102389
|
+
} catch (error) {
|
|
102390
|
+
this.output.debug(`Could not merge ids.yml: ${error.message}`);
|
|
102391
|
+
}
|
|
102139
102392
|
await git("-C", worktreePath, "add", "-A");
|
|
102393
|
+
const conflictCheck = await git("-C", worktreePath, "diff", "--cached", "-S<<<<<<< ", "--name-only");
|
|
102394
|
+
if (conflictCheck.trim()) {
|
|
102395
|
+
const conflictedFiles = conflictCheck.trim().split("\n");
|
|
102396
|
+
throw new SyncError(`Cannot commit: ${conflictedFiles.length} file(s) still have merge conflict markers:\n` + conflictedFiles.map((f) => ` - ${f}`).join("\n") + `\n\nThis is a bug in tbd sync. Please report it and manually resolve conflicts in:\n ${worktreePath}`);
|
|
102397
|
+
}
|
|
102140
102398
|
try {
|
|
102141
102399
|
await git("-C", worktreePath, "commit", "--no-verify", "-m", "tbd sync: resolved merge conflicts");
|
|
102142
102400
|
} catch {
|
|
@@ -102282,7 +102540,7 @@ async function readState() {
|
|
|
102282
102540
|
* Update local state file.
|
|
102283
102541
|
*/
|
|
102284
102542
|
async function updateState(updates) {
|
|
102285
|
-
await writeFile(STATE_FILE, (
|
|
102543
|
+
await writeFile(STATE_FILE, stringifyYaml({
|
|
102286
102544
|
...await readState(),
|
|
102287
102545
|
...updates
|
|
102288
102546
|
}));
|
|
@@ -102751,7 +103009,7 @@ async function saveConflictToAttic(atticDir, conflict, winnerSource) {
|
|
|
102751
103009
|
}
|
|
102752
103010
|
};
|
|
102753
103011
|
const safeTimestamp = timestamp.replace(/:/g, "-");
|
|
102754
|
-
await writeFile(join(atticDir, `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`), (
|
|
103012
|
+
await writeFile(join(atticDir, `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`), stringifyYaml(entry));
|
|
102755
103013
|
}
|
|
102756
103014
|
/**
|
|
102757
103015
|
* Get the target/source directory for workspace operations.
|
|
@@ -104285,7 +104543,9 @@ async function listAtticEntries(filterById) {
|
|
|
104285
104543
|
if (!parsed) continue;
|
|
104286
104544
|
if (filterById && parsed.entityId !== filterById) continue;
|
|
104287
104545
|
try {
|
|
104288
|
-
const
|
|
104546
|
+
const filePath = join(atticPath, file);
|
|
104547
|
+
const rawData = parseYamlWithConflictDetection(await readFile(filePath, "utf-8"), filePath);
|
|
104548
|
+
const entry = AtticEntrySchema.parse(rawData);
|
|
104289
104549
|
entries.push(entry);
|
|
104290
104550
|
} catch {}
|
|
104291
104551
|
}
|
|
@@ -104997,7 +105257,7 @@ var DocsHandler = class extends BaseCommand {
|
|
|
104997
105257
|
console.log(colors.bold("Workflows (Shortcuts):"));
|
|
104998
105258
|
console.log(" tbd shortcut --list List all available shortcuts");
|
|
104999
105259
|
console.log(" tbd shortcut new-plan-spec Plan a new feature");
|
|
105000
|
-
console.log(" tbd shortcut commit
|
|
105260
|
+
console.log(" tbd shortcut code-review-and-commit Commit code properly");
|
|
105001
105261
|
console.log(" tbd shortcut create-or-update-pr-simple Create a pull request");
|
|
105002
105262
|
console.log("");
|
|
105003
105263
|
console.log(colors.bold("Guidelines (Coding Standards):"));
|
|
@@ -105338,8 +105598,8 @@ var UninstallHandler = class extends BaseCommand {
|
|
|
105338
105598
|
force: true
|
|
105339
105599
|
});
|
|
105340
105600
|
console.log(` ${colors.success("✓")} Removed .tbd directory`);
|
|
105341
|
-
} catch {
|
|
105342
|
-
throw new CLIError(
|
|
105601
|
+
} catch (error) {
|
|
105602
|
+
throw new CLIError(`Failed to remove .tbd directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
105343
105603
|
}
|
|
105344
105604
|
console.log("");
|
|
105345
105605
|
this.output.success("tbd has been uninstalled from this repository.");
|
|
@@ -105521,6 +105781,7 @@ var DocCache = class {
|
|
|
105521
105781
|
return {
|
|
105522
105782
|
title: typeof parsed.title === "string" ? parsed.title : void 0,
|
|
105523
105783
|
description: typeof parsed.description === "string" ? parsed.description : void 0,
|
|
105784
|
+
category: typeof parsed.category === "string" ? parsed.category : void 0,
|
|
105524
105785
|
tags: Array.isArray(parsed.tags) ? parsed.tags.filter((t) => typeof t === "string") : void 0
|
|
105525
105786
|
};
|
|
105526
105787
|
} catch {
|
|
@@ -105649,7 +105910,7 @@ function buildTableRows(docs, skipNames = []) {
|
|
|
105649
105910
|
* // ## Available Shortcuts
|
|
105650
105911
|
* // | Name | Description |
|
|
105651
105912
|
* // | --- | --- |
|
|
105652
|
-
* // | commit
|
|
105913
|
+
* // | code-review-and-commit | Run pre-commit checks, review changes, and commit code |
|
|
105653
105914
|
* // ...
|
|
105654
105915
|
* // ## Available Guidelines
|
|
105655
105916
|
* // | Name | Description |
|
|
@@ -106134,16 +106395,6 @@ async function addDoc(tbdRoot, options) {
|
|
|
106134
106395
|
*
|
|
106135
106396
|
* See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md
|
|
106136
106397
|
*/
|
|
106137
|
-
/**
|
|
106138
|
-
* Infer category from shortcut name.
|
|
106139
|
-
* Returns undefined if no category matches.
|
|
106140
|
-
*/
|
|
106141
|
-
function inferCategory(name) {
|
|
106142
|
-
if (name.includes("plan-spec") || name.includes("architecture") || name.includes("research") || name.includes("validation-spec") || name.includes("implementation-spec") || name.includes("beads-from-spec") || name.includes("update-spec") || name.includes("refine-spec") || name.includes("revise-")) return "planning";
|
|
106143
|
-
if (name.includes("implement-") || name.includes("coding-spike")) return "implementation";
|
|
106144
|
-
if (name.includes("review-") || name.includes("precommit") || name.includes("cleanup-") || name.includes("validation-plan")) return "quality";
|
|
106145
|
-
if (name.includes("commit-") || name.includes("pr-") || name.includes("merge-")) return "shipping";
|
|
106146
|
-
}
|
|
106147
106398
|
var ShortcutHandler = class extends BaseCommand {
|
|
106148
106399
|
async run(query, options) {
|
|
106149
106400
|
await this.execute(async () => {
|
|
@@ -106201,13 +106452,14 @@ var ShortcutHandler = class extends BaseCommand {
|
|
|
106201
106452
|
async handleList(cache, includeAll, category) {
|
|
106202
106453
|
let docs = cache.list(includeAll);
|
|
106203
106454
|
if (category) docs = docs.filter((d) => {
|
|
106204
|
-
return
|
|
106455
|
+
return d.frontmatter?.category === category;
|
|
106205
106456
|
});
|
|
106206
106457
|
if (this.ctx.json) {
|
|
106207
106458
|
this.output.data(docs.map((d) => ({
|
|
106208
106459
|
name: d.name,
|
|
106209
106460
|
title: d.frontmatter?.title,
|
|
106210
106461
|
description: d.frontmatter?.description,
|
|
106462
|
+
category: d.frontmatter?.category,
|
|
106211
106463
|
path: d.path,
|
|
106212
106464
|
sourceDir: d.sourceDir,
|
|
106213
106465
|
sizeBytes: d.sizeBytes,
|
|
@@ -106361,7 +106613,7 @@ var ShortcutHandler = class extends BaseCommand {
|
|
|
106361
106613
|
}
|
|
106362
106614
|
}
|
|
106363
106615
|
};
|
|
106364
|
-
const shortcutCommand = new Command("shortcut").description("Find and output documentation shortcuts").argument("[query]", "Shortcut name or description to search for").option("--list", "List all available shortcuts").option("--all", "Include shadowed shortcuts (use with --list)").option("--category <category>", "Filter by category: planning,
|
|
106616
|
+
const shortcutCommand = new Command("shortcut").description("Find and output documentation shortcuts").argument("[query]", "Shortcut name or description to search for").option("--list", "List all available shortcuts").option("--all", "Include shadowed shortcuts (use with --list)").option("--category <category>", "Filter by category: planning, documentation, review, git, cleanup, session, meta").option("--refresh", "Refresh the cached shortcut directory").option("--quiet", "Suppress output (use with --refresh)").option("--add <url>", "Add a shortcut from a URL").option("--name <name>", "Name for the added shortcut (required with --add)").action(async (query, options, command) => {
|
|
106365
106617
|
await new ShortcutHandler(command).run(query, options);
|
|
106366
106618
|
});
|
|
106367
106619
|
|
|
@@ -106728,47 +106980,6 @@ const templateCommand = new Command("template").description("Find and output doc
|
|
|
106728
106980
|
await new TemplateHandler(command).run(query, options);
|
|
106729
106981
|
});
|
|
106730
106982
|
|
|
106731
|
-
//#endregion
|
|
106732
|
-
//#region src/cli/lib/prefix-detection.ts
|
|
106733
|
-
/**
|
|
106734
|
-
* Prefix validation and beads prefix extraction module.
|
|
106735
|
-
*
|
|
106736
|
-
* Provides functions to validate prefixes and extract prefix from beads config.
|
|
106737
|
-
* Used by setup commands to validate user-provided prefixes and migrate from beads.
|
|
106738
|
-
*/
|
|
106739
|
-
/** Maximum length for a valid prefix */
|
|
106740
|
-
const MAX_PREFIX_LENGTH = 10;
|
|
106741
|
-
/** Minimum length for a valid prefix */
|
|
106742
|
-
const MIN_PREFIX_LENGTH = 1;
|
|
106743
|
-
/**
|
|
106744
|
-
* Check if a prefix is valid.
|
|
106745
|
-
* - Must be 1-10 characters
|
|
106746
|
-
* - Must start with a letter
|
|
106747
|
-
* - Must be alphanumeric only (lowercase)
|
|
106748
|
-
*/
|
|
106749
|
-
function isValidPrefix(s) {
|
|
106750
|
-
if (!s) return false;
|
|
106751
|
-
if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;
|
|
106752
|
-
return /^[a-z][a-z0-9]*$/.test(s);
|
|
106753
|
-
}
|
|
106754
|
-
/**
|
|
106755
|
-
* Get prefix from existing beads config.
|
|
106756
|
-
*
|
|
106757
|
-
* Looks for .beads/config.yaml and extracts display.id_prefix
|
|
106758
|
-
*
|
|
106759
|
-
* @param cwd Current working directory
|
|
106760
|
-
* @returns The beads prefix, or null if not found
|
|
106761
|
-
*/
|
|
106762
|
-
async function getBeadsPrefix(cwd) {
|
|
106763
|
-
try {
|
|
106764
|
-
const prefix = ((0, import_dist$2.parse)(await readFile(join(cwd, ".beads", "config.yaml"), "utf-8"))?.display)?.id_prefix;
|
|
106765
|
-
if (typeof prefix === "string" && isValidPrefix(prefix)) return prefix;
|
|
106766
|
-
return null;
|
|
106767
|
-
} catch {
|
|
106768
|
-
return null;
|
|
106769
|
-
}
|
|
106770
|
-
}
|
|
106771
|
-
|
|
106772
106983
|
//#endregion
|
|
106773
106984
|
//#region src/cli/commands/setup.ts
|
|
106774
106985
|
/**
|
|
@@ -107563,8 +107774,14 @@ var SetupDefaultHandler = class extends BaseCommand {
|
|
|
107563
107774
|
}
|
|
107564
107775
|
const beadsPrefix = await getBeadsPrefix(cwd);
|
|
107565
107776
|
const prefix = options.prefix ?? beadsPrefix;
|
|
107566
|
-
if (!prefix) throw new CLIError("Could not read prefix from beads config.\nPlease specify a prefix (2-
|
|
107567
|
-
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-
|
|
107777
|
+
if (!prefix) throw new CLIError("Could not read prefix from beads config.\nPlease specify a prefix (2-8 letters recommended):\n tbd setup --auto --prefix=tbd");
|
|
107778
|
+
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-20 lowercase characters:\n - Must start with a letter (a-z)\n - Must end with alphanumeric (a-z, 0-9)\n - Middle characters can include dots (.) and underscores (_)\n - No dashes allowed (breaks ID syntax)\n\nPlease specify a valid prefix:\n tbd setup --from-beads --prefix=tbd");
|
|
107779
|
+
if (!(beadsPrefix && prefix === beadsPrefix) && !isRecommendedPrefix(prefix) && !options.force) throw new CLIError(`Prefix "${prefix}" is not recommended.\nRecommended prefixes are 2-8 alphabetic characters (e.g., "tbd", "myp", "proj").
|
|
107780
|
+
|
|
107781
|
+
If you really want to use this prefix, add --force to override.
|
|
107782
|
+
|
|
107783
|
+
Example:
|
|
107784
|
+
tbd setup --from-beads --prefix=${prefix} --force`);
|
|
107568
107785
|
await this.initializeTbd(cwd, prefix);
|
|
107569
107786
|
if (options.ghCli === false) {
|
|
107570
107787
|
const config = await readConfig(cwd);
|
|
@@ -107603,7 +107820,13 @@ var SetupDefaultHandler = class extends BaseCommand {
|
|
|
107603
107820
|
const colors = this.output.getColors();
|
|
107604
107821
|
const prefix = options.prefix;
|
|
107605
107822
|
if (!prefix) throw new CLIError("--prefix is required for tbd setup --auto\n\nThe --prefix flag specifies your project name for issue IDs.\nUse a short 2-4 letter prefix so issue IDs stand out clearly.\n\nExample:\n tbd setup --auto --prefix=tbd # Issues: tbd-a1b2\n tbd setup --auto --prefix=myp # Issues: myp-c3d4\n\nNote: If migrating from beads, the prefix is automatically read from your beads config.");
|
|
107606
|
-
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-
|
|
107823
|
+
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-20 lowercase characters:\n - Must start with a letter (a-z)\n - Must end with alphanumeric (a-z, 0-9)\n - Middle characters can include dots (.) and underscores (_)\n - No dashes allowed (breaks ID syntax)\n\nExample:\n tbd setup --auto --prefix=tbd");
|
|
107824
|
+
if (!isRecommendedPrefix(prefix) && !options.force) throw new CLIError(`Prefix "${prefix}" is not recommended.\nRecommended prefixes are 2-8 alphabetic characters (e.g., "tbd", "myp", "proj").
|
|
107825
|
+
|
|
107826
|
+
If you really want to use this prefix, add --force to override.
|
|
107827
|
+
|
|
107828
|
+
Example:
|
|
107829
|
+
tbd setup --auto --prefix=${prefix} --force`);
|
|
107607
107830
|
console.log(`Initializing with prefix "${prefix}"...`);
|
|
107608
107831
|
await this.initializeTbd(cwd, prefix);
|
|
107609
107832
|
if (options.ghCli === false) {
|
|
@@ -107876,7 +108099,7 @@ var SetupAutoHandler = class extends BaseCommand {
|
|
|
107876
108099
|
return result;
|
|
107877
108100
|
}
|
|
107878
108101
|
};
|
|
107879
|
-
const setupCommand = new Command("setup").description("Configure tbd integration with editors and tools").option("--auto", "Non-interactive mode with smart defaults (for agents/scripts)").option("--interactive", "Interactive mode with prompts (for humans)").option("--from-beads", "Migrate from Beads to tbd").option("--prefix <name>", "Project prefix for issue IDs (required for fresh setup)").option("--no-gh-cli", "Disable automatic GitHub CLI installation hook").action(async (options, command) => {
|
|
108102
|
+
const setupCommand = new Command("setup").description("Configure tbd integration with editors and tools").option("--auto", "Non-interactive mode with smart defaults (for agents/scripts)").option("--interactive", "Interactive mode with prompts (for humans)").option("--from-beads", "Migrate from Beads to tbd").option("--prefix <name>", "Project prefix for issue IDs (required for fresh setup)").option("--force", "Allow non-recommended prefix format (not 2-8 alphabetic)").option("--no-gh-cli", "Disable automatic GitHub CLI installation hook").action(async (options, command) => {
|
|
107880
108103
|
if (options.auto || options.interactive) {
|
|
107881
108104
|
await new SetupDefaultHandler(command).run(options);
|
|
107882
108105
|
return;
|
|
@@ -107900,7 +108123,8 @@ const setupCommand = new Command("setup").description("Configure tbd integration
|
|
|
107900
108123
|
console.log(" --from-beads Migrate from Beads to tbd (implies --auto)");
|
|
107901
108124
|
console.log("");
|
|
107902
108125
|
console.log("Options:");
|
|
107903
|
-
console.log(" --prefix <name> Project prefix for issue IDs (
|
|
108126
|
+
console.log(" --prefix <name> Project prefix for issue IDs (2-8 alphabetic recommended)");
|
|
108127
|
+
console.log(" --force Allow non-recommended prefix format");
|
|
107904
108128
|
console.log(" --no-gh-cli Disable automatic GitHub CLI installation hook");
|
|
107905
108129
|
console.log("");
|
|
107906
108130
|
console.log("Examples:");
|
|
@@ -108102,15 +108326,31 @@ function isJsonMode() {
|
|
|
108102
108326
|
return process.argv.includes("--json");
|
|
108103
108327
|
}
|
|
108104
108328
|
/**
|
|
108329
|
+
* Check if --debug flag is present in argv.
|
|
108330
|
+
*/
|
|
108331
|
+
function isDebugMode() {
|
|
108332
|
+
return process.argv.includes("--debug");
|
|
108333
|
+
}
|
|
108334
|
+
/**
|
|
108105
108335
|
* Output error in the appropriate format (JSON or text).
|
|
108336
|
+
* In debug mode, shows full error details and stack trace.
|
|
108106
108337
|
*/
|
|
108107
108338
|
function outputError(message, error) {
|
|
108339
|
+
const debugMode = isDebugMode();
|
|
108108
108340
|
if (isJsonMode()) {
|
|
108109
108341
|
const errorObj = { error: message };
|
|
108110
108342
|
if (error instanceof CLIError) errorObj.type = error.name;
|
|
108111
108343
|
if (error && error.message !== message) errorObj.details = error.message;
|
|
108344
|
+
if (debugMode && error?.stack) errorObj.stack = error.stack;
|
|
108112
108345
|
console.error(JSON.stringify(errorObj));
|
|
108113
|
-
} else
|
|
108346
|
+
} else {
|
|
108347
|
+
console.error(`Error: ${message}`);
|
|
108348
|
+
if (debugMode && error?.stack) {
|
|
108349
|
+
console.error("");
|
|
108350
|
+
console.error("Stack trace:");
|
|
108351
|
+
console.error(error.stack);
|
|
108352
|
+
}
|
|
108353
|
+
}
|
|
108114
108354
|
}
|
|
108115
108355
|
/**
|
|
108116
108356
|
* Check if running with no command (just options or nothing).
|