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/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as LocalStateSchema, a as insertAfterFrontmatter, i as serializeIssue, l as
|
|
1
|
+
import { S as IssueKind, T as LocalStateSchema, a as insertAfterFrontmatter, b as IdMappingYamlSchema, c as stringifyYaml, d as ConfigSchema, i as serializeIssue, l as AtticEntrySchema, n as parseIssue, o as stripFrontmatter, r as parseMarkdownWithFrontmatter, s as parseYamlWithConflictDetection, t as VERSION$1, w as IssueStatus } from "./src-Ct16P2Ox.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
|
-
import { parse
|
|
4
|
+
import { parse } from "yaml";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import pc from "picocolors";
|
|
7
7
|
import { marked } from "marked";
|
|
@@ -963,11 +963,8 @@ async function readConfigWithMigration(baseDir) {
|
|
|
963
963
|
*/
|
|
964
964
|
async function writeConfig(baseDir, config) {
|
|
965
965
|
const configPath = join(baseDir, CONFIG_FILE);
|
|
966
|
-
let content =
|
|
967
|
-
|
|
968
|
-
lineWidth: 0
|
|
969
|
-
});
|
|
970
|
-
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:");
|
|
966
|
+
let content = stringifyYaml(config, { lineWidth: 0 });
|
|
967
|
+
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:");
|
|
971
968
|
await writeFile(configPath, content);
|
|
972
969
|
}
|
|
973
970
|
/**
|
|
@@ -1026,10 +1023,7 @@ async function readLocalState(baseDir) {
|
|
|
1026
1023
|
async function writeLocalState(baseDir, state) {
|
|
1027
1024
|
const statePath = join(baseDir, STATE_FILE);
|
|
1028
1025
|
await mkdir(join(baseDir, ".tbd"), { recursive: true });
|
|
1029
|
-
await writeFile(statePath,
|
|
1030
|
-
sortMapEntries: true,
|
|
1031
|
-
lineWidth: 0
|
|
1032
|
-
}));
|
|
1026
|
+
await writeFile(statePath, stringifyYaml(state, { lineWidth: 0 }));
|
|
1033
1027
|
}
|
|
1034
1028
|
/**
|
|
1035
1029
|
* Update specific fields in local state (merge with existing).
|
|
@@ -1286,6 +1280,67 @@ async function ensureGitignorePatterns(gitignorePath, patterns, header) {
|
|
|
1286
1280
|
};
|
|
1287
1281
|
}
|
|
1288
1282
|
|
|
1283
|
+
//#endregion
|
|
1284
|
+
//#region src/cli/lib/prefix-detection.ts
|
|
1285
|
+
/**
|
|
1286
|
+
* Prefix validation and beads prefix extraction module.
|
|
1287
|
+
*
|
|
1288
|
+
* Provides functions to validate prefixes and extract prefix from beads config.
|
|
1289
|
+
* Used by setup commands to validate user-provided prefixes and migrate from beads.
|
|
1290
|
+
*/
|
|
1291
|
+
/** Maximum length for a valid prefix */
|
|
1292
|
+
const MAX_PREFIX_LENGTH = 20;
|
|
1293
|
+
/** Minimum length for a valid prefix */
|
|
1294
|
+
const MIN_PREFIX_LENGTH = 1;
|
|
1295
|
+
/** Recommended minimum length */
|
|
1296
|
+
const RECOMMENDED_MIN_LENGTH = 2;
|
|
1297
|
+
/** Recommended maximum length */
|
|
1298
|
+
const RECOMMENDED_MAX_LENGTH = 8;
|
|
1299
|
+
/**
|
|
1300
|
+
* Check if a prefix is valid (hard rules, always enforced).
|
|
1301
|
+
* - Must be 1-20 characters
|
|
1302
|
+
* - Must start with a letter (a-z)
|
|
1303
|
+
* - Must end with alphanumeric (a-z0-9)
|
|
1304
|
+
* - Middle characters can be alphanumeric, dot, or underscore
|
|
1305
|
+
* - No dashes allowed (breaks ID syntax)
|
|
1306
|
+
*/
|
|
1307
|
+
function isValidPrefix(s) {
|
|
1308
|
+
if (!s) return false;
|
|
1309
|
+
if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;
|
|
1310
|
+
if (!/^[a-z]/.test(s)) return false;
|
|
1311
|
+
if (s.length > 1 && !/[a-z0-9]$/.test(s)) return false;
|
|
1312
|
+
return /^[a-z][a-z0-9._]*$/.test(s);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Check if a prefix follows recommended format (soft rules).
|
|
1316
|
+
* - Must be 2-8 characters
|
|
1317
|
+
* - Must be alphabetic only (a-z)
|
|
1318
|
+
*
|
|
1319
|
+
* Prefixes that don't match this can still be used with --force.
|
|
1320
|
+
*/
|
|
1321
|
+
function isRecommendedPrefix(s) {
|
|
1322
|
+
if (!s) return false;
|
|
1323
|
+
if (s.length < RECOMMENDED_MIN_LENGTH || s.length > RECOMMENDED_MAX_LENGTH) return false;
|
|
1324
|
+
return /^[a-z]+$/.test(s);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Get prefix from existing beads config.
|
|
1328
|
+
*
|
|
1329
|
+
* Looks for .beads/config.yaml and extracts display.id_prefix
|
|
1330
|
+
*
|
|
1331
|
+
* @param cwd Current working directory
|
|
1332
|
+
* @returns The beads prefix, or null if not found
|
|
1333
|
+
*/
|
|
1334
|
+
async function getBeadsPrefix(cwd) {
|
|
1335
|
+
try {
|
|
1336
|
+
const prefix = (parse(await readFile(join(cwd, ".beads", "config.yaml"), "utf-8"))?.display)?.id_prefix;
|
|
1337
|
+
if (typeof prefix === "string" && isValidPrefix(prefix)) return prefix;
|
|
1338
|
+
return null;
|
|
1339
|
+
} catch {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1289
1344
|
//#endregion
|
|
1290
1345
|
//#region src/utils/time-utils.ts
|
|
1291
1346
|
/**
|
|
@@ -2131,7 +2186,15 @@ var InitHandler = class extends BaseCommand {
|
|
|
2131
2186
|
} catch (error) {
|
|
2132
2187
|
if (error instanceof CLIError) throw error;
|
|
2133
2188
|
}
|
|
2134
|
-
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-
|
|
2189
|
+
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>");
|
|
2190
|
+
const prefix = options.prefix;
|
|
2191
|
+
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");
|
|
2192
|
+
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").
|
|
2193
|
+
|
|
2194
|
+
If you really want to use this prefix, add --force to override.
|
|
2195
|
+
|
|
2196
|
+
Example:
|
|
2197
|
+
tbd init --prefix=${prefix} --force`);
|
|
2135
2198
|
if (this.checkDryRun("Would initialize tbd repository", options)) return;
|
|
2136
2199
|
await this.execute(async () => {
|
|
2137
2200
|
await initConfig(cwd, VERSION, options.prefix);
|
|
@@ -2195,7 +2258,7 @@ var InitHandler = class extends BaseCommand {
|
|
|
2195
2258
|
});
|
|
2196
2259
|
}
|
|
2197
2260
|
};
|
|
2198
|
-
const initCommand = new Command("init").description("Initialize tbd in a git repository").option("--prefix <name>", "Project prefix for display IDs (
|
|
2261
|
+
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) => {
|
|
2199
2262
|
await new InitHandler(command).run(options);
|
|
2200
2263
|
});
|
|
2201
2264
|
|
|
@@ -2566,7 +2629,10 @@ async function loadIdMapping(baseDir) {
|
|
|
2566
2629
|
ulidToShort: /* @__PURE__ */ new Map()
|
|
2567
2630
|
};
|
|
2568
2631
|
}
|
|
2569
|
-
const
|
|
2632
|
+
const rawData = parseYamlWithConflictDetection(content, filePath) ?? {};
|
|
2633
|
+
const parseResult = IdMappingYamlSchema.safeParse(rawData);
|
|
2634
|
+
if (!parseResult.success) throw new Error(`Invalid ID mapping format in ${filePath}: ${parseResult.error.message}`);
|
|
2635
|
+
const data = parseResult.data;
|
|
2570
2636
|
const shortToUlid = /* @__PURE__ */ new Map();
|
|
2571
2637
|
const ulidToShort = /* @__PURE__ */ new Map();
|
|
2572
2638
|
for (const [shortId, ulid] of Object.entries(data)) {
|
|
@@ -2587,7 +2653,7 @@ async function saveIdMapping(baseDir, mapping) {
|
|
|
2587
2653
|
const data = {};
|
|
2588
2654
|
const sortedKeys = naturalSort(Array.from(mapping.shortToUlid.keys()));
|
|
2589
2655
|
for (const key of sortedKeys) data[key] = mapping.shortToUlid.get(key);
|
|
2590
|
-
await writeFile(filePath,
|
|
2656
|
+
await writeFile(filePath, stringifyYaml(data));
|
|
2591
2657
|
}
|
|
2592
2658
|
/**
|
|
2593
2659
|
* Calculate the optimal short ID length based on existing ID count.
|
|
@@ -2656,6 +2722,55 @@ function resolveToInternalId(input, mapping) {
|
|
|
2656
2722
|
if (!ulid) throw new Error(`Unknown issue ID: ${input}. Short ID "${shortId}" not found in mapping.`);
|
|
2657
2723
|
return makeInternalId(ulid);
|
|
2658
2724
|
}
|
|
2725
|
+
/**
|
|
2726
|
+
* Parse an ID mapping from raw YAML content.
|
|
2727
|
+
* Used for loading mappings from git show output during conflict resolution.
|
|
2728
|
+
*
|
|
2729
|
+
* @throws MergeConflictError if content contains merge conflict markers
|
|
2730
|
+
*/
|
|
2731
|
+
function parseIdMappingFromYaml(content) {
|
|
2732
|
+
const rawData = parseYamlWithConflictDetection(content) ?? {};
|
|
2733
|
+
const parseResult = IdMappingYamlSchema.safeParse(rawData);
|
|
2734
|
+
if (!parseResult.success) throw new Error(`Invalid ID mapping format: ${parseResult.error.message}`);
|
|
2735
|
+
const data = parseResult.data;
|
|
2736
|
+
const shortToUlid = /* @__PURE__ */ new Map();
|
|
2737
|
+
const ulidToShort = /* @__PURE__ */ new Map();
|
|
2738
|
+
for (const [shortId, ulid] of Object.entries(data)) {
|
|
2739
|
+
shortToUlid.set(shortId, ulid);
|
|
2740
|
+
ulidToShort.set(ulid, shortId);
|
|
2741
|
+
}
|
|
2742
|
+
return {
|
|
2743
|
+
shortToUlid,
|
|
2744
|
+
ulidToShort
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Merge two ID mappings by combining all entries from both.
|
|
2749
|
+
* ID mappings are always additive (new IDs are only added, never removed),
|
|
2750
|
+
* so merging simply unions all key-value pairs.
|
|
2751
|
+
*
|
|
2752
|
+
* If the same short ID maps to different ULIDs in each mapping (a conflict),
|
|
2753
|
+
* the local mapping takes precedence (caller should log a warning).
|
|
2754
|
+
*
|
|
2755
|
+
* @param local - The local ID mapping
|
|
2756
|
+
* @param remote - The remote ID mapping
|
|
2757
|
+
* @returns Merged mapping with all entries from both
|
|
2758
|
+
*/
|
|
2759
|
+
function mergeIdMappings(local, remote) {
|
|
2760
|
+
const merged = {
|
|
2761
|
+
shortToUlid: new Map(local.shortToUlid),
|
|
2762
|
+
ulidToShort: new Map(local.ulidToShort)
|
|
2763
|
+
};
|
|
2764
|
+
for (const [shortId, ulid] of remote.shortToUlid) if (!merged.shortToUlid.has(shortId)) {
|
|
2765
|
+
merged.shortToUlid.set(shortId, ulid);
|
|
2766
|
+
merged.ulidToShort.set(ulid, shortId);
|
|
2767
|
+
}
|
|
2768
|
+
for (const [ulid, shortId] of remote.ulidToShort) if (!merged.ulidToShort.has(ulid) && !merged.shortToUlid.has(shortId)) {
|
|
2769
|
+
merged.shortToUlid.set(shortId, ulid);
|
|
2770
|
+
merged.ulidToShort.set(ulid, shortId);
|
|
2771
|
+
}
|
|
2772
|
+
return merged;
|
|
2773
|
+
}
|
|
2659
2774
|
|
|
2660
2775
|
//#endregion
|
|
2661
2776
|
//#region src/lib/priority.ts
|
|
@@ -2864,8 +2979,9 @@ var CreateHandler = class extends BaseCommand {
|
|
|
2864
2979
|
let description = options.description;
|
|
2865
2980
|
if (options.file) try {
|
|
2866
2981
|
description = await readFile(options.file, "utf-8");
|
|
2867
|
-
} catch {
|
|
2868
|
-
|
|
2982
|
+
} catch (error) {
|
|
2983
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2984
|
+
throw new CLIError(`Failed to read description from file '${options.file}': ${message}`);
|
|
2869
2985
|
}
|
|
2870
2986
|
let specPath;
|
|
2871
2987
|
if (options.spec) try {
|
|
@@ -3520,15 +3636,8 @@ function normalizePath(path) {
|
|
|
3520
3636
|
*/
|
|
3521
3637
|
var ListHandler = class extends BaseCommand {
|
|
3522
3638
|
async run(options) {
|
|
3523
|
-
const
|
|
3524
|
-
let issues;
|
|
3525
|
-
let dataCtx;
|
|
3526
|
-
try {
|
|
3527
|
-
dataCtx = await loadDataContext(tbdRoot);
|
|
3528
|
-
issues = await listIssues(dataCtx.dataSyncDir);
|
|
3529
|
-
} catch {
|
|
3530
|
-
throw new CLIError("Failed to read issues");
|
|
3531
|
-
}
|
|
3639
|
+
const dataCtx = await loadDataContext(await requireInit());
|
|
3640
|
+
let issues = await listIssues(dataCtx.dataSyncDir);
|
|
3532
3641
|
issues = this.filterIssues(issues, options, dataCtx.mapping);
|
|
3533
3642
|
issues = this.sortIssues(issues, options.sort ?? "priority", dataCtx.mapping);
|
|
3534
3643
|
issues = applyLimit(issues, options.limit);
|
|
@@ -4841,8 +4950,8 @@ var DocSync = class {
|
|
|
4841
4950
|
* Parse a source string into a DocSource.
|
|
4842
4951
|
*
|
|
4843
4952
|
* @example
|
|
4844
|
-
* parseSource('internal:shortcuts/standard/commit
|
|
4845
|
-
* // => { type: 'internal', location: 'shortcuts/standard/commit
|
|
4953
|
+
* parseSource('internal:shortcuts/standard/code-review-and-commit.md')
|
|
4954
|
+
* // => { type: 'internal', location: 'shortcuts/standard/code-review-and-commit.md' }
|
|
4846
4955
|
*
|
|
4847
4956
|
* @example
|
|
4848
4957
|
* parseSource('https://raw.githubusercontent.com/org/repo/main/file.md')
|
|
@@ -5528,7 +5637,24 @@ var SyncHandler = class extends BaseCommand {
|
|
|
5528
5637
|
} catch {
|
|
5529
5638
|
this.output.debug(`Issue ${localIssue.id} not on remote, keeping local`);
|
|
5530
5639
|
}
|
|
5640
|
+
try {
|
|
5641
|
+
const remoteIdsContent = await git("show", `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
5642
|
+
if (remoteIdsContent) {
|
|
5643
|
+
const localMapping = await loadIdMapping(this.dataSyncDir);
|
|
5644
|
+
const remoteMapping = parseIdMappingFromYaml(remoteIdsContent);
|
|
5645
|
+
const mergedMapping = mergeIdMappings(localMapping, remoteMapping);
|
|
5646
|
+
await saveIdMapping(this.dataSyncDir, mergedMapping);
|
|
5647
|
+
this.output.debug(`Merged ID mappings: ${localMapping.shortToUlid.size} local + ${remoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`);
|
|
5648
|
+
}
|
|
5649
|
+
} catch (error) {
|
|
5650
|
+
this.output.debug(`Could not merge ids.yml: ${error.message}`);
|
|
5651
|
+
}
|
|
5531
5652
|
await git("-C", worktreePath, "add", "-A");
|
|
5653
|
+
const conflictCheck = await git("-C", worktreePath, "diff", "--cached", "-S<<<<<<< ", "--name-only");
|
|
5654
|
+
if (conflictCheck.trim()) {
|
|
5655
|
+
const conflictedFiles = conflictCheck.trim().split("\n");
|
|
5656
|
+
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}`);
|
|
5657
|
+
}
|
|
5532
5658
|
try {
|
|
5533
5659
|
await git("-C", worktreePath, "commit", "--no-verify", "-m", "tbd sync: resolved merge conflicts");
|
|
5534
5660
|
} catch {
|
|
@@ -5674,7 +5800,7 @@ async function readState() {
|
|
|
5674
5800
|
* Update local state file.
|
|
5675
5801
|
*/
|
|
5676
5802
|
async function updateState(updates) {
|
|
5677
|
-
await writeFile(STATE_FILE,
|
|
5803
|
+
await writeFile(STATE_FILE, stringifyYaml({
|
|
5678
5804
|
...await readState(),
|
|
5679
5805
|
...updates
|
|
5680
5806
|
}));
|
|
@@ -6143,7 +6269,7 @@ async function saveConflictToAttic(atticDir, conflict, winnerSource) {
|
|
|
6143
6269
|
}
|
|
6144
6270
|
};
|
|
6145
6271
|
const safeTimestamp = timestamp.replace(/:/g, "-");
|
|
6146
|
-
await writeFile(join(atticDir, `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`),
|
|
6272
|
+
await writeFile(join(atticDir, `${conflict.issue_id}_${safeTimestamp}_${conflict.field}.yml`), stringifyYaml(entry));
|
|
6147
6273
|
}
|
|
6148
6274
|
/**
|
|
6149
6275
|
* Get the target/source directory for workspace operations.
|
|
@@ -7677,7 +7803,9 @@ async function listAtticEntries(filterById) {
|
|
|
7677
7803
|
if (!parsed) continue;
|
|
7678
7804
|
if (filterById && parsed.entityId !== filterById) continue;
|
|
7679
7805
|
try {
|
|
7680
|
-
const
|
|
7806
|
+
const filePath = join(atticPath, file);
|
|
7807
|
+
const rawData = parseYamlWithConflictDetection(await readFile(filePath, "utf-8"), filePath);
|
|
7808
|
+
const entry = AtticEntrySchema.parse(rawData);
|
|
7681
7809
|
entries.push(entry);
|
|
7682
7810
|
} catch {}
|
|
7683
7811
|
}
|
|
@@ -8316,7 +8444,7 @@ var DocsHandler = class extends BaseCommand {
|
|
|
8316
8444
|
console.log(colors.bold("Workflows (Shortcuts):"));
|
|
8317
8445
|
console.log(" tbd shortcut --list List all available shortcuts");
|
|
8318
8446
|
console.log(" tbd shortcut new-plan-spec Plan a new feature");
|
|
8319
|
-
console.log(" tbd shortcut commit
|
|
8447
|
+
console.log(" tbd shortcut code-review-and-commit Commit code properly");
|
|
8320
8448
|
console.log(" tbd shortcut create-or-update-pr-simple Create a pull request");
|
|
8321
8449
|
console.log("");
|
|
8322
8450
|
console.log(colors.bold("Guidelines (Coding Standards):"));
|
|
@@ -8657,8 +8785,8 @@ var UninstallHandler = class extends BaseCommand {
|
|
|
8657
8785
|
force: true
|
|
8658
8786
|
});
|
|
8659
8787
|
console.log(` ${colors.success("✓")} Removed .tbd directory`);
|
|
8660
|
-
} catch {
|
|
8661
|
-
throw new CLIError(
|
|
8788
|
+
} catch (error) {
|
|
8789
|
+
throw new CLIError(`Failed to remove .tbd directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
8662
8790
|
}
|
|
8663
8791
|
console.log("");
|
|
8664
8792
|
this.output.success("tbd has been uninstalled from this repository.");
|
|
@@ -8840,6 +8968,7 @@ var DocCache = class {
|
|
|
8840
8968
|
return {
|
|
8841
8969
|
title: typeof parsed.title === "string" ? parsed.title : void 0,
|
|
8842
8970
|
description: typeof parsed.description === "string" ? parsed.description : void 0,
|
|
8971
|
+
category: typeof parsed.category === "string" ? parsed.category : void 0,
|
|
8843
8972
|
tags: Array.isArray(parsed.tags) ? parsed.tags.filter((t) => typeof t === "string") : void 0
|
|
8844
8973
|
};
|
|
8845
8974
|
} catch {
|
|
@@ -8968,7 +9097,7 @@ function buildTableRows(docs, skipNames = []) {
|
|
|
8968
9097
|
* // ## Available Shortcuts
|
|
8969
9098
|
* // | Name | Description |
|
|
8970
9099
|
* // | --- | --- |
|
|
8971
|
-
* // | commit
|
|
9100
|
+
* // | code-review-and-commit | Run pre-commit checks, review changes, and commit code |
|
|
8972
9101
|
* // ...
|
|
8973
9102
|
* // ## Available Guidelines
|
|
8974
9103
|
* // | Name | Description |
|
|
@@ -9453,16 +9582,6 @@ async function addDoc(tbdRoot, options) {
|
|
|
9453
9582
|
*
|
|
9454
9583
|
* See: docs/project/specs/active/plan-2026-01-22-doc-cache-abstraction.md
|
|
9455
9584
|
*/
|
|
9456
|
-
/**
|
|
9457
|
-
* Infer category from shortcut name.
|
|
9458
|
-
* Returns undefined if no category matches.
|
|
9459
|
-
*/
|
|
9460
|
-
function inferCategory(name) {
|
|
9461
|
-
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";
|
|
9462
|
-
if (name.includes("implement-") || name.includes("coding-spike")) return "implementation";
|
|
9463
|
-
if (name.includes("review-") || name.includes("precommit") || name.includes("cleanup-") || name.includes("validation-plan")) return "quality";
|
|
9464
|
-
if (name.includes("commit-") || name.includes("pr-") || name.includes("merge-")) return "shipping";
|
|
9465
|
-
}
|
|
9466
9585
|
var ShortcutHandler = class extends BaseCommand {
|
|
9467
9586
|
async run(query, options) {
|
|
9468
9587
|
await this.execute(async () => {
|
|
@@ -9520,13 +9639,14 @@ var ShortcutHandler = class extends BaseCommand {
|
|
|
9520
9639
|
async handleList(cache, includeAll, category) {
|
|
9521
9640
|
let docs = cache.list(includeAll);
|
|
9522
9641
|
if (category) docs = docs.filter((d) => {
|
|
9523
|
-
return
|
|
9642
|
+
return d.frontmatter?.category === category;
|
|
9524
9643
|
});
|
|
9525
9644
|
if (this.ctx.json) {
|
|
9526
9645
|
this.output.data(docs.map((d) => ({
|
|
9527
9646
|
name: d.name,
|
|
9528
9647
|
title: d.frontmatter?.title,
|
|
9529
9648
|
description: d.frontmatter?.description,
|
|
9649
|
+
category: d.frontmatter?.category,
|
|
9530
9650
|
path: d.path,
|
|
9531
9651
|
sourceDir: d.sourceDir,
|
|
9532
9652
|
sizeBytes: d.sizeBytes,
|
|
@@ -9680,7 +9800,7 @@ var ShortcutHandler = class extends BaseCommand {
|
|
|
9680
9800
|
}
|
|
9681
9801
|
}
|
|
9682
9802
|
};
|
|
9683
|
-
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,
|
|
9803
|
+
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) => {
|
|
9684
9804
|
await new ShortcutHandler(command).run(query, options);
|
|
9685
9805
|
});
|
|
9686
9806
|
|
|
@@ -10047,47 +10167,6 @@ const templateCommand = new Command("template").description("Find and output doc
|
|
|
10047
10167
|
await new TemplateHandler(command).run(query, options);
|
|
10048
10168
|
});
|
|
10049
10169
|
|
|
10050
|
-
//#endregion
|
|
10051
|
-
//#region src/cli/lib/prefix-detection.ts
|
|
10052
|
-
/**
|
|
10053
|
-
* Prefix validation and beads prefix extraction module.
|
|
10054
|
-
*
|
|
10055
|
-
* Provides functions to validate prefixes and extract prefix from beads config.
|
|
10056
|
-
* Used by setup commands to validate user-provided prefixes and migrate from beads.
|
|
10057
|
-
*/
|
|
10058
|
-
/** Maximum length for a valid prefix */
|
|
10059
|
-
const MAX_PREFIX_LENGTH = 10;
|
|
10060
|
-
/** Minimum length for a valid prefix */
|
|
10061
|
-
const MIN_PREFIX_LENGTH = 1;
|
|
10062
|
-
/**
|
|
10063
|
-
* Check if a prefix is valid.
|
|
10064
|
-
* - Must be 1-10 characters
|
|
10065
|
-
* - Must start with a letter
|
|
10066
|
-
* - Must be alphanumeric only (lowercase)
|
|
10067
|
-
*/
|
|
10068
|
-
function isValidPrefix(s) {
|
|
10069
|
-
if (!s) return false;
|
|
10070
|
-
if (s.length < MIN_PREFIX_LENGTH || s.length > MAX_PREFIX_LENGTH) return false;
|
|
10071
|
-
return /^[a-z][a-z0-9]*$/.test(s);
|
|
10072
|
-
}
|
|
10073
|
-
/**
|
|
10074
|
-
* Get prefix from existing beads config.
|
|
10075
|
-
*
|
|
10076
|
-
* Looks for .beads/config.yaml and extracts display.id_prefix
|
|
10077
|
-
*
|
|
10078
|
-
* @param cwd Current working directory
|
|
10079
|
-
* @returns The beads prefix, or null if not found
|
|
10080
|
-
*/
|
|
10081
|
-
async function getBeadsPrefix(cwd) {
|
|
10082
|
-
try {
|
|
10083
|
-
const prefix = (parse(await readFile(join(cwd, ".beads", "config.yaml"), "utf-8"))?.display)?.id_prefix;
|
|
10084
|
-
if (typeof prefix === "string" && isValidPrefix(prefix)) return prefix;
|
|
10085
|
-
return null;
|
|
10086
|
-
} catch {
|
|
10087
|
-
return null;
|
|
10088
|
-
}
|
|
10089
|
-
}
|
|
10090
|
-
|
|
10091
10170
|
//#endregion
|
|
10092
10171
|
//#region src/cli/commands/setup.ts
|
|
10093
10172
|
/**
|
|
@@ -10882,8 +10961,14 @@ var SetupDefaultHandler = class extends BaseCommand {
|
|
|
10882
10961
|
}
|
|
10883
10962
|
const beadsPrefix = await getBeadsPrefix(cwd);
|
|
10884
10963
|
const prefix = options.prefix ?? beadsPrefix;
|
|
10885
|
-
if (!prefix) throw new CLIError("Could not read prefix from beads config.\nPlease specify a prefix (2-
|
|
10886
|
-
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-
|
|
10964
|
+
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");
|
|
10965
|
+
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");
|
|
10966
|
+
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").
|
|
10967
|
+
|
|
10968
|
+
If you really want to use this prefix, add --force to override.
|
|
10969
|
+
|
|
10970
|
+
Example:
|
|
10971
|
+
tbd setup --from-beads --prefix=${prefix} --force`);
|
|
10887
10972
|
await this.initializeTbd(cwd, prefix);
|
|
10888
10973
|
if (options.ghCli === false) {
|
|
10889
10974
|
const config = await readConfig(cwd);
|
|
@@ -10922,7 +11007,13 @@ var SetupDefaultHandler = class extends BaseCommand {
|
|
|
10922
11007
|
const colors = this.output.getColors();
|
|
10923
11008
|
const prefix = options.prefix;
|
|
10924
11009
|
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.");
|
|
10925
|
-
if (!isValidPrefix(prefix)) throw new CLIError("Invalid prefix format.\nPrefix must be 1-
|
|
11010
|
+
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");
|
|
11011
|
+
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").
|
|
11012
|
+
|
|
11013
|
+
If you really want to use this prefix, add --force to override.
|
|
11014
|
+
|
|
11015
|
+
Example:
|
|
11016
|
+
tbd setup --auto --prefix=${prefix} --force`);
|
|
10926
11017
|
console.log(`Initializing with prefix "${prefix}"...`);
|
|
10927
11018
|
await this.initializeTbd(cwd, prefix);
|
|
10928
11019
|
if (options.ghCli === false) {
|
|
@@ -11195,7 +11286,7 @@ var SetupAutoHandler = class extends BaseCommand {
|
|
|
11195
11286
|
return result;
|
|
11196
11287
|
}
|
|
11197
11288
|
};
|
|
11198
|
-
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) => {
|
|
11289
|
+
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) => {
|
|
11199
11290
|
if (options.auto || options.interactive) {
|
|
11200
11291
|
await new SetupDefaultHandler(command).run(options);
|
|
11201
11292
|
return;
|
|
@@ -11219,7 +11310,8 @@ const setupCommand = new Command("setup").description("Configure tbd integration
|
|
|
11219
11310
|
console.log(" --from-beads Migrate from Beads to tbd (implies --auto)");
|
|
11220
11311
|
console.log("");
|
|
11221
11312
|
console.log("Options:");
|
|
11222
|
-
console.log(" --prefix <name> Project prefix for issue IDs (
|
|
11313
|
+
console.log(" --prefix <name> Project prefix for issue IDs (2-8 alphabetic recommended)");
|
|
11314
|
+
console.log(" --force Allow non-recommended prefix format");
|
|
11223
11315
|
console.log(" --no-gh-cli Disable automatic GitHub CLI installation hook");
|
|
11224
11316
|
console.log("");
|
|
11225
11317
|
console.log("Examples:");
|
|
@@ -11421,15 +11513,31 @@ function isJsonMode() {
|
|
|
11421
11513
|
return process.argv.includes("--json");
|
|
11422
11514
|
}
|
|
11423
11515
|
/**
|
|
11516
|
+
* Check if --debug flag is present in argv.
|
|
11517
|
+
*/
|
|
11518
|
+
function isDebugMode() {
|
|
11519
|
+
return process.argv.includes("--debug");
|
|
11520
|
+
}
|
|
11521
|
+
/**
|
|
11424
11522
|
* Output error in the appropriate format (JSON or text).
|
|
11523
|
+
* In debug mode, shows full error details and stack trace.
|
|
11425
11524
|
*/
|
|
11426
11525
|
function outputError(message, error) {
|
|
11526
|
+
const debugMode = isDebugMode();
|
|
11427
11527
|
if (isJsonMode()) {
|
|
11428
11528
|
const errorObj = { error: message };
|
|
11429
11529
|
if (error instanceof CLIError) errorObj.type = error.name;
|
|
11430
11530
|
if (error && error.message !== message) errorObj.details = error.message;
|
|
11531
|
+
if (debugMode && error?.stack) errorObj.stack = error.stack;
|
|
11431
11532
|
console.error(JSON.stringify(errorObj));
|
|
11432
|
-
} else
|
|
11533
|
+
} else {
|
|
11534
|
+
console.error(`Error: ${message}`);
|
|
11535
|
+
if (debugMode && error?.stack) {
|
|
11536
|
+
console.error("");
|
|
11537
|
+
console.error("Stack trace:");
|
|
11538
|
+
console.error(error.stack);
|
|
11539
|
+
}
|
|
11540
|
+
}
|
|
11433
11541
|
}
|
|
11434
11542
|
/**
|
|
11435
11543
|
* Check if running with no command (just options or nothing).
|