get-tbd 0.1.21 → 0.1.23
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 +17 -19
- package/dist/bin.mjs +181 -12
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +110 -644
- package/dist/cli.mjs.map +1 -1
- package/dist/config-CB1tcqTZ.mjs +3 -0
- package/dist/config-CmEAGaxz.mjs +637 -0
- package/dist/config-CmEAGaxz.mjs.map +1 -0
- package/dist/docs/README.md +17 -19
- package/dist/docs/guidelines/bun-monorepo-patterns.md +816 -80
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +586 -16
- package/dist/docs/guidelines/python-cli-patterns.md +2 -2
- package/dist/docs/guidelines/tbd-sync-troubleshooting.md +27 -0
- package/dist/docs/guidelines/typescript-cli-tool-rules.md +465 -196
- package/dist/docs/tbd-design.md +86 -46
- package/dist/docs/tbd-docs.md +0 -6
- package/dist/id-mapping-0-R0X8zb.mjs +3 -0
- package/dist/{id-mapping-CD5c_ZVA.mjs → id-mapping-JGow6Jk4.mjs} +57 -3
- package/dist/{id-mapping-CD5c_ZVA.mjs.map → id-mapping-JGow6Jk4.mjs.map} +1 -1
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +2 -2
- package/dist/{src-BjMRpmMh.mjs → src-7qUDeWJf.mjs} +3 -3
- package/dist/{src-BjMRpmMh.mjs.map → src-7qUDeWJf.mjs.map} +1 -1
- package/dist/tbd +181 -12
- package/dist/{yaml-utils-x_kr2IId.mjs → yaml-utils-U7l9hhkh.mjs} +7 -1
- package/dist/yaml-utils-U7l9hhkh.mjs.map +1 -0
- package/package.json +4 -4
- package/dist/id-mapping-BqSnxlxk.mjs +0 -3
- package/dist/yaml-utils-x_kr2IId.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -138,20 +138,20 @@ status or context or knowledge and know what to do next:
|
|
|
138
138
|
|
|
139
139
|
| What you say | What happens | What runs |
|
|
140
140
|
| --- | --- | --- |
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
141
|
+
| “Let’s plan a new feature that …” | Agent creates a spec from a template | [`tbd shortcut new-plan-spec`](packages/tbd/docs/shortcuts/standard/new-plan-spec.md) |
|
|
142
|
+
| “Break this spec into beads” | Agent creates implementation beads from the spec | [`tbd shortcut plan-implementation-with-beads`](packages/tbd/docs/shortcuts/standard/plan-implementation-with-beads.md) |
|
|
143
|
+
| “Implement these beads” | Agent works through beads systematically | [`tbd shortcut implement-beads`](packages/tbd/docs/shortcuts/standard/implement-beads.md) |
|
|
144
|
+
| “Create a bead for the bug where …” | Agent creates and tracks a bead | `tbd create "..." --type=bug` |
|
|
145
|
+
| “Let’s work on current beads” | Agent finds ready beads and starts working | `tbd ready` |
|
|
146
|
+
| “Review this code” | Agent performs comprehensive code review with all guidelines | [`tbd shortcut review-code`](packages/tbd/docs/shortcuts/standard/review-code.md) |
|
|
147
|
+
| “Review this PR” | Agent reviews a GitHub pull request and can comment/fix | [`tbd shortcut review-github-pr`](packages/tbd/docs/shortcuts/standard/review-github-pr.md) |
|
|
148
|
+
| “Use the shortcut to commit” | Agent runs full pre-commit checks, code review, and commits | [`tbd shortcut code-review-and-commit`](packages/tbd/docs/shortcuts/standard/code-review-and-commit.md) |
|
|
149
|
+
| “Create a PR” | Agent creates or updates the pull request | [`tbd shortcut create-or-update-pr-simple`](packages/tbd/docs/shortcuts/standard/create-or-update-pr-simple.md) |
|
|
150
|
+
| “Let’s create a research brief on …” | Agent creates a research document using a template | [`tbd shortcut new-research-brief`](packages/tbd/docs/shortcuts/standard/new-research-brief.md) |
|
|
151
|
+
| “How could we test this better?” | Agent loads TDD and testing guidelines | [`tbd guidelines general-tdd-guidelines`](packages/tbd/docs/guidelines/general-tdd-guidelines.md) |
|
|
152
|
+
| “How can we make this a well-designed TypeScript CLI?” | Agent loads TypeScript CLI guidelines | [`tbd guidelines typescript-cli-tool-rules`](packages/tbd/docs/guidelines/typescript-cli-tool-rules.md) |
|
|
153
|
+
| “Can you review if this TypeScript package setup follows best practices” | Agent loads monorepo patterns | [`tbd guidelines pnpm-monorepo-patterns`](packages/tbd/docs/guidelines/pnpm-monorepo-patterns.md) |
|
|
154
|
+
| “How can we do a better job of testing?” | Agent loads golden testing guidelines | [`tbd guidelines golden-testing-guidelines`](packages/tbd/docs/guidelines/golden-testing-guidelines.md) |
|
|
155
155
|
|
|
156
156
|
Under the hood, your agent runs these `tbd` commands automatically.
|
|
157
157
|
You just talk naturally.
|
|
@@ -165,8 +165,8 @@ You just talk naturally.
|
|
|
165
165
|
|
|
166
166
|
- **Git-native:** Beads live in your repo, synced to a separate, dedicated `tbd-sync`
|
|
167
167
|
branch. Your code history stays clean—no bead churn polluting your logs.
|
|
168
|
-
- **Agent friendly:** JSON output,
|
|
169
|
-
|
|
168
|
+
- **Agent friendly:** JSON output, simple commands that agents understand.
|
|
169
|
+
Installs itself as a skill in Claude Code.
|
|
170
170
|
- **Markdown + YAML frontmatter:** One file per bead, human-readable and editable.
|
|
171
171
|
This eliminates most merge conflicts.
|
|
172
172
|
- **Beads alternative:** Largely compatible with `bd` at the CLI level, but with a
|
|
@@ -472,8 +472,6 @@ Every command supports these flags for automation:
|
|
|
472
472
|
| Flag | Purpose |
|
|
473
473
|
| --- | --- |
|
|
474
474
|
| `--json` | Machine-parseable output |
|
|
475
|
-
| `--non-interactive` | Fail if input required |
|
|
476
|
-
| `--yes` | Auto-confirm prompts |
|
|
477
475
|
| `--dry-run` | Preview changes |
|
|
478
476
|
| `--quiet` | Minimal output |
|
|
479
477
|
|
|
@@ -533,7 +531,7 @@ It does *not* aim to solve real-time multi-agent coordination, which is a separa
|
|
|
533
531
|
problem requiring sub-second messaging and atomic claims.
|
|
534
532
|
Tools like [Agent Mail](https://github.com/Dicklesworthstone/mcp_agent_mail) and
|
|
535
533
|
[Gas Town](https://github.com/steveyegge/gastown) address that space and are
|
|
536
|
-
complementary to `tbd`—you could layer real-time coordination on top of `tbd
|
|
534
|
+
complementary to `tbd`—you could layer real-time coordination on top of `tbd`’s durable
|
|
537
535
|
tracking. See the [design doc](packages/tbd/docs/tbd-design.md) for a detailed
|
|
538
536
|
comparison.
|
|
539
537
|
|
package/dist/bin.mjs
CHANGED
|
@@ -6734,6 +6734,12 @@ const Dependency = objectType({
|
|
|
6734
6734
|
*
|
|
6735
6735
|
* Note: Fields use .nullable() in addition to .optional() because
|
|
6736
6736
|
* YAML parses `field: null` as JavaScript null, not undefined.
|
|
6737
|
+
*
|
|
6738
|
+
* Design note: We could add the short ID to this schema. We didn't originally
|
|
6739
|
+
* because it's one more field to maintain consistency around across files.
|
|
6740
|
+
* Having it here might make recovery of lost ID mappings far easier, but for
|
|
6741
|
+
* now we have more reliable management of the mappings file (ids.yml) and
|
|
6742
|
+
* consider it authoritative. See IdMappingYamlSchema (§2.6.8).
|
|
6737
6743
|
*/
|
|
6738
6744
|
const IssueSchema = BaseEntity.extend({
|
|
6739
6745
|
type: literalType("is"),
|
|
@@ -14027,7 +14033,7 @@ function serializeIssue(issue) {
|
|
|
14027
14033
|
* Package version, derived from git at build time.
|
|
14028
14034
|
* Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
|
|
14029
14035
|
*/
|
|
14030
|
-
const VERSION$1 = "0.1.
|
|
14036
|
+
const VERSION$1 = "0.1.23";
|
|
14031
14037
|
|
|
14032
14038
|
//#endregion
|
|
14033
14039
|
//#region src/cli/lib/version.ts
|
|
@@ -96557,15 +96563,12 @@ function sanitizeTab(tab, fallbackTab) {
|
|
|
96557
96563
|
*/
|
|
96558
96564
|
function getCommandContext(command) {
|
|
96559
96565
|
const opts = command.optsWithGlobals();
|
|
96560
|
-
const isCI = Boolean(process.env.CI);
|
|
96561
96566
|
return {
|
|
96562
96567
|
dryRun: opts.dryRun ?? false,
|
|
96563
96568
|
verbose: opts.verbose ?? false,
|
|
96564
96569
|
quiet: opts.quiet ?? false,
|
|
96565
96570
|
json: opts.json ?? false,
|
|
96566
96571
|
color: opts.color ?? "auto",
|
|
96567
|
-
nonInteractive: opts.nonInteractive ?? (!process.stdin.isTTY || isCI),
|
|
96568
|
-
yes: opts.yes ?? false,
|
|
96569
96572
|
sync: opts.sync !== false,
|
|
96570
96573
|
debug: opts.debug ?? false
|
|
96571
96574
|
};
|
|
@@ -97854,6 +97857,20 @@ function isCompatibleFormat(format) {
|
|
|
97854
97857
|
*
|
|
97855
97858
|
* See: tbd-design.md §2.2.2 Config File
|
|
97856
97859
|
*/
|
|
97860
|
+
var config_exports = /* @__PURE__ */ __exportAll({
|
|
97861
|
+
IncompatibleFormatError: () => IncompatibleFormatError,
|
|
97862
|
+
findTbdRoot: () => findTbdRoot,
|
|
97863
|
+
hasSeenWelcome: () => hasSeenWelcome,
|
|
97864
|
+
initConfig: () => initConfig,
|
|
97865
|
+
isInitialized: () => isInitialized,
|
|
97866
|
+
markWelcomeSeen: () => markWelcomeSeen,
|
|
97867
|
+
readConfig: () => readConfig,
|
|
97868
|
+
readConfigWithMigration: () => readConfigWithMigration,
|
|
97869
|
+
readLocalState: () => readLocalState,
|
|
97870
|
+
updateLocalState: () => updateLocalState,
|
|
97871
|
+
writeConfig: () => writeConfig,
|
|
97872
|
+
writeLocalState: () => writeLocalState
|
|
97873
|
+
});
|
|
97857
97874
|
/**
|
|
97858
97875
|
* Error thrown when the config format version is from a newer tbd version.
|
|
97859
97876
|
* This prevents older tbd versions from silently stripping new config fields.
|
|
@@ -99852,11 +99869,13 @@ function naturalSort(arr) {
|
|
|
99852
99869
|
var id_mapping_exports = /* @__PURE__ */ __exportAll({
|
|
99853
99870
|
addIdMapping: () => addIdMapping,
|
|
99854
99871
|
calculateOptimalLength: () => calculateOptimalLength,
|
|
99872
|
+
createShortIdMapping: () => createShortIdMapping,
|
|
99855
99873
|
generateUniqueShortId: () => generateUniqueShortId,
|
|
99856
99874
|
hasShortId: () => hasShortId,
|
|
99857
99875
|
loadIdMapping: () => loadIdMapping,
|
|
99858
99876
|
mergeIdMappings: () => mergeIdMappings,
|
|
99859
99877
|
parseIdMappingFromYaml: () => parseIdMappingFromYaml,
|
|
99878
|
+
reconcileMappings: () => reconcileMappings,
|
|
99860
99879
|
resolveToInternalId: () => resolveToInternalId,
|
|
99861
99880
|
saveIdMapping: () => saveIdMapping
|
|
99862
99881
|
});
|
|
@@ -99955,6 +99974,22 @@ function hasShortId(mapping, shortId) {
|
|
|
99955
99974
|
return mapping.shortToUlid.has(shortId);
|
|
99956
99975
|
}
|
|
99957
99976
|
/**
|
|
99977
|
+
* Create a short ID mapping for a new internal ID.
|
|
99978
|
+
* Generates a unique short ID and registers it in the mapping.
|
|
99979
|
+
*
|
|
99980
|
+
* @param internalId - The internal ID (is-{ulid})
|
|
99981
|
+
* @param mapping - The ID mapping to update
|
|
99982
|
+
* @returns The generated short ID
|
|
99983
|
+
*/
|
|
99984
|
+
function createShortIdMapping(internalId, mapping) {
|
|
99985
|
+
const ulid = extractUlidFromInternalId(internalId);
|
|
99986
|
+
const existing = mapping.ulidToShort.get(ulid);
|
|
99987
|
+
if (existing) return existing;
|
|
99988
|
+
const shortId = generateUniqueShortId(mapping);
|
|
99989
|
+
addIdMapping(mapping, ulid, shortId);
|
|
99990
|
+
return shortId;
|
|
99991
|
+
}
|
|
99992
|
+
/**
|
|
99958
99993
|
* Resolve any ID input to an internal ID ({prefix}-{ulid}).
|
|
99959
99994
|
*
|
|
99960
99995
|
* Handles:
|
|
@@ -100001,6 +100036,44 @@ function parseIdMappingFromYaml(content) {
|
|
|
100001
100036
|
};
|
|
100002
100037
|
}
|
|
100003
100038
|
/**
|
|
100039
|
+
* Ensure all given internal IDs have short ID mappings.
|
|
100040
|
+
* Creates missing mappings for any IDs without entries.
|
|
100041
|
+
*
|
|
100042
|
+
* This repairs state after git merges that may add issue files
|
|
100043
|
+
* without corresponding mapping entries (e.g., when outbox issues
|
|
100044
|
+
* are merged from a feature branch but ids.yml doesn't include them).
|
|
100045
|
+
*
|
|
100046
|
+
* When a `historicalMapping` is provided, the function will try to recover
|
|
100047
|
+
* the original short ID from that mapping before generating a new random one.
|
|
100048
|
+
* This preserves ID stability so that existing references (in docs, PRs,
|
|
100049
|
+
* conversations) remain valid.
|
|
100050
|
+
*
|
|
100051
|
+
* @param internalIds - Array of internal IDs (is-{ulid}) to reconcile
|
|
100052
|
+
* @param mapping - The ID mapping to update (mutated in-place)
|
|
100053
|
+
* @param historicalMapping - Optional mapping from prior state (e.g., git history) to recover original short IDs
|
|
100054
|
+
* @returns Object with `created` (IDs that got new random short IDs) and `recovered` (IDs restored from history)
|
|
100055
|
+
*/
|
|
100056
|
+
function reconcileMappings(internalIds, mapping, historicalMapping) {
|
|
100057
|
+
const created = [];
|
|
100058
|
+
const recovered = [];
|
|
100059
|
+
for (const id of internalIds) {
|
|
100060
|
+
const ulid = extractUlidFromInternalId(id);
|
|
100061
|
+
if (mapping.ulidToShort.has(ulid)) continue;
|
|
100062
|
+
const historicalShortId = historicalMapping?.ulidToShort.get(ulid);
|
|
100063
|
+
if (historicalShortId && !mapping.shortToUlid.has(historicalShortId)) {
|
|
100064
|
+
addIdMapping(mapping, ulid, historicalShortId);
|
|
100065
|
+
recovered.push(id);
|
|
100066
|
+
} else {
|
|
100067
|
+
createShortIdMapping(id, mapping);
|
|
100068
|
+
created.push(id);
|
|
100069
|
+
}
|
|
100070
|
+
}
|
|
100071
|
+
return {
|
|
100072
|
+
created,
|
|
100073
|
+
recovered
|
|
100074
|
+
};
|
|
100075
|
+
}
|
|
100076
|
+
/**
|
|
100004
100077
|
* Merge two ID mappings by combining all entries from both.
|
|
100005
100078
|
* ID mappings are always additive (new IDs are only added, never removed),
|
|
100006
100079
|
* so merging simply unions all key-value pairs.
|
|
@@ -102768,6 +102841,9 @@ async function importFromWorkspace(tbdRoot, dataSyncDir, options) {
|
|
|
102768
102841
|
const sourceMapping = await loadIdMapping(sourceDir);
|
|
102769
102842
|
const targetMapping = await loadIdMapping(dataSyncDir);
|
|
102770
102843
|
for (const [shortId, ulid] of sourceMapping.shortToUlid) if (!targetMapping.shortToUlid.has(shortId)) addIdMapping(targetMapping, ulid, shortId);
|
|
102844
|
+
const reconcileResult = reconcileMappings(sourceIssues.map((i) => i.id), targetMapping, sourceMapping);
|
|
102845
|
+
if (reconcileResult.recovered.length > 0) log.info(`Recovered ${reconcileResult.recovered.length} ID mapping(s) from workspace`);
|
|
102846
|
+
if (reconcileResult.created.length > 0) log.info(`Created ${reconcileResult.created.length} new ID mapping(s) for imported issues`);
|
|
102771
102847
|
await saveIdMapping(dataSyncDir, targetMapping);
|
|
102772
102848
|
let cleared = false;
|
|
102773
102849
|
if (shouldClear && imported > 0) {
|
|
@@ -102922,8 +102998,8 @@ var SyncHandler = class extends BaseCommand {
|
|
|
102922
102998
|
else if (options.push) await this.pushChanges(syncBranch, remote);
|
|
102923
102999
|
else await this.fullSync(syncBranch, remote, {
|
|
102924
103000
|
force: options.force,
|
|
102925
|
-
|
|
102926
|
-
|
|
103001
|
+
autoSave: options.autoSave,
|
|
103002
|
+
outbox: options.outbox
|
|
102927
103003
|
});
|
|
102928
103004
|
}
|
|
102929
103005
|
/**
|
|
@@ -103226,6 +103302,24 @@ var SyncHandler = class extends BaseCommand {
|
|
|
103226
103302
|
await git("-C", worktreePath, "merge", `${remote}/${syncBranch}`, "-m", "tbd sync: merge remote changes");
|
|
103227
103303
|
this.output.debug(`Merged ${behindCommits} commit(s) from remote`);
|
|
103228
103304
|
if (headBeforeMerge) await this.showGitLogDebug("Commits received", `${headBeforeMerge}..${syncBranch}`);
|
|
103305
|
+
const postMergeIssues = await listIssues(this.dataSyncDir);
|
|
103306
|
+
const postMergeMapping = await loadIdMapping(this.dataSyncDir);
|
|
103307
|
+
let historicalMapping;
|
|
103308
|
+
try {
|
|
103309
|
+
const remoteIdsContent = await git("show", `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
103310
|
+
if (remoteIdsContent) historicalMapping = parseIdMappingFromYaml(remoteIdsContent);
|
|
103311
|
+
} catch {}
|
|
103312
|
+
const reconcileResult = reconcileMappings(postMergeIssues.map((i) => i.id), postMergeMapping, historicalMapping);
|
|
103313
|
+
const totalReconciled = reconcileResult.created.length + reconcileResult.recovered.length;
|
|
103314
|
+
if (totalReconciled > 0) {
|
|
103315
|
+
await saveIdMapping(this.dataSyncDir, postMergeMapping);
|
|
103316
|
+
await git("-C", worktreePath, "add", "-A");
|
|
103317
|
+
try {
|
|
103318
|
+
await git("-C", worktreePath, "commit", "--no-verify", "-m", `tbd sync: reconcile ${totalReconciled} missing ID mapping(s)`);
|
|
103319
|
+
} catch {}
|
|
103320
|
+
if (reconcileResult.recovered.length > 0) this.output.debug(`Recovered ${reconcileResult.recovered.length} ID mapping(s) from history`);
|
|
103321
|
+
if (reconcileResult.created.length > 0) this.output.debug(`Created ${reconcileResult.created.length} new ID mapping(s) (no history available)`);
|
|
103322
|
+
}
|
|
103229
103323
|
} catch {
|
|
103230
103324
|
this.output.info(`Merge conflict, attempting file-level resolution`);
|
|
103231
103325
|
const localIssues = await listIssues(this.dataSyncDir);
|
|
@@ -103238,18 +103332,29 @@ var SyncHandler = class extends BaseCommand {
|
|
|
103238
103332
|
} catch {
|
|
103239
103333
|
this.output.debug(`Issue ${localIssue.id} not on remote, keeping local`);
|
|
103240
103334
|
}
|
|
103335
|
+
let conflictRemoteMapping;
|
|
103241
103336
|
try {
|
|
103242
103337
|
const remoteIdsContent = await git("show", `${remote}/${syncBranch}:${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
103243
103338
|
if (remoteIdsContent) {
|
|
103339
|
+
conflictRemoteMapping = parseIdMappingFromYaml(remoteIdsContent);
|
|
103244
103340
|
const localMapping = await loadIdMapping(this.dataSyncDir);
|
|
103245
|
-
const
|
|
103246
|
-
const mergedMapping = mergeIdMappings(localMapping, remoteMapping);
|
|
103341
|
+
const mergedMapping = mergeIdMappings(localMapping, conflictRemoteMapping);
|
|
103247
103342
|
await saveIdMapping(this.dataSyncDir, mergedMapping);
|
|
103248
|
-
this.output.debug(`Merged ID mappings: ${localMapping.shortToUlid.size} local + ${
|
|
103343
|
+
this.output.debug(`Merged ID mappings: ${localMapping.shortToUlid.size} local + ${conflictRemoteMapping.shortToUlid.size} remote = ${mergedMapping.shortToUlid.size} total`);
|
|
103249
103344
|
}
|
|
103250
103345
|
} catch (error) {
|
|
103251
103346
|
this.output.debug(`Could not merge ids.yml: ${error.message}`);
|
|
103252
103347
|
}
|
|
103348
|
+
{
|
|
103349
|
+
const allIssues = await listIssues(this.dataSyncDir);
|
|
103350
|
+
const currentMapping = await loadIdMapping(this.dataSyncDir);
|
|
103351
|
+
const reconcileResult = reconcileMappings(allIssues.map((i) => i.id), currentMapping, conflictRemoteMapping);
|
|
103352
|
+
if (reconcileResult.created.length + reconcileResult.recovered.length > 0) {
|
|
103353
|
+
await saveIdMapping(this.dataSyncDir, currentMapping);
|
|
103354
|
+
if (reconcileResult.recovered.length > 0) this.output.debug(`Recovered ${reconcileResult.recovered.length} ID mapping(s) from remote`);
|
|
103355
|
+
if (reconcileResult.created.length > 0) this.output.debug(`Created ${reconcileResult.created.length} new ID mapping(s) after conflict resolution`);
|
|
103356
|
+
}
|
|
103357
|
+
}
|
|
103253
103358
|
await git("-C", worktreePath, "add", "-A");
|
|
103254
103359
|
const conflictCheck = await git("-C", worktreePath, "diff", "--cached", "-S<<<<<<< ", "--name-only");
|
|
103255
103360
|
if (conflictCheck.trim()) {
|
|
@@ -103313,7 +103418,7 @@ var SyncHandler = class extends BaseCommand {
|
|
|
103313
103418
|
this.output.error(`Push failed: ${displayError}`);
|
|
103314
103419
|
console.log(` ${aheadCommits} commit(s) not pushed to remote.`);
|
|
103315
103420
|
});
|
|
103316
|
-
if (errorType === "permanent" &&
|
|
103421
|
+
if (errorType === "permanent" && options.autoSave !== false) await this.handlePermanentFailure();
|
|
103317
103422
|
else if (!this.ctx.json) if (errorType === "transient") {
|
|
103318
103423
|
console.log("");
|
|
103319
103424
|
console.log(" This appears to be a temporary issue. Options:");
|
|
@@ -103328,7 +103433,7 @@ var SyncHandler = class extends BaseCommand {
|
|
|
103328
103433
|
}
|
|
103329
103434
|
return;
|
|
103330
103435
|
}
|
|
103331
|
-
if (
|
|
103436
|
+
if (options.outbox !== false) await this.maybeImportOutbox(syncBranch, remote);
|
|
103332
103437
|
this.output.data({
|
|
103333
103438
|
summary,
|
|
103334
103439
|
conflicts: conflicts.length
|
|
@@ -104351,6 +104456,7 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104351
104456
|
healthChecks.push(await this.checkIdMappingDuplicates(options.fix));
|
|
104352
104457
|
healthChecks.push(await this.checkTempFiles(options.fix));
|
|
104353
104458
|
healthChecks.push(this.checkIssueValidity(this.issues));
|
|
104459
|
+
healthChecks.push(await this.checkMissingMappings(options.fix));
|
|
104354
104460
|
healthChecks.push(await this.checkWorktree(options.fix));
|
|
104355
104461
|
healthChecks.push(await this.checkDataLocation(options.fix));
|
|
104356
104462
|
healthChecks.push(await this.checkLocalSyncBranch());
|
|
@@ -104694,6 +104800,63 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104694
104800
|
suggestion: "Manually fix or delete invalid issue files"
|
|
104695
104801
|
};
|
|
104696
104802
|
}
|
|
104803
|
+
/**
|
|
104804
|
+
* Check for issues that have no short ID mapping in ids.yml.
|
|
104805
|
+
*
|
|
104806
|
+
* This can happen when a git merge brings in issue files (e.g., from
|
|
104807
|
+
* a feature branch with outbox issues) without the corresponding
|
|
104808
|
+
* ids.yml entries. Without a mapping, any command that tries to
|
|
104809
|
+
* display the issue ID will crash.
|
|
104810
|
+
*
|
|
104811
|
+
* With --fix, creates missing mappings automatically.
|
|
104812
|
+
*/
|
|
104813
|
+
async checkMissingMappings(fix) {
|
|
104814
|
+
if (this.issues.length === 0) return {
|
|
104815
|
+
name: "ID mapping coverage",
|
|
104816
|
+
status: "ok"
|
|
104817
|
+
};
|
|
104818
|
+
const { loadIdMapping, saveIdMapping, reconcileMappings } = await Promise.resolve().then(() => id_mapping_exports);
|
|
104819
|
+
const mapping = await loadIdMapping(this.dataSyncDir);
|
|
104820
|
+
const missingIds = [];
|
|
104821
|
+
for (const issue of this.issues) {
|
|
104822
|
+
const ulid = extractUlidFromInternalId(issue.id);
|
|
104823
|
+
if (!mapping.ulidToShort.has(ulid)) missingIds.push(issue.id);
|
|
104824
|
+
}
|
|
104825
|
+
if (missingIds.length === 0) return {
|
|
104826
|
+
name: "ID mapping coverage",
|
|
104827
|
+
status: "ok"
|
|
104828
|
+
};
|
|
104829
|
+
if (fix && !this.checkDryRun("Create missing ID mappings")) {
|
|
104830
|
+
const { parseIdMappingFromYaml } = await Promise.resolve().then(() => id_mapping_exports);
|
|
104831
|
+
let historicalMapping;
|
|
104832
|
+
try {
|
|
104833
|
+
const syncBranch = (await Promise.resolve().then(() => config_exports).then((m) => m.readConfig(this.cwd))).sync.branch;
|
|
104834
|
+
const priorContent = await git("log", "-1", "--format=%H", syncBranch, "--", `${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
104835
|
+
if (priorContent.trim()) {
|
|
104836
|
+
const idsContent = await git("show", `${priorContent.trim()}:${DATA_SYNC_DIR}/mappings/ids.yml`);
|
|
104837
|
+
if (idsContent) historicalMapping = parseIdMappingFromYaml(idsContent);
|
|
104838
|
+
}
|
|
104839
|
+
} catch {}
|
|
104840
|
+
const result = reconcileMappings(missingIds, mapping, historicalMapping);
|
|
104841
|
+
await saveIdMapping(this.dataSyncDir, mapping);
|
|
104842
|
+
const parts = [];
|
|
104843
|
+
if (result.recovered.length > 0) parts.push(`recovered ${result.recovered.length} from git history`);
|
|
104844
|
+
if (result.created.length > 0) parts.push(`created ${result.created.length} new`);
|
|
104845
|
+
return {
|
|
104846
|
+
name: "ID mapping coverage",
|
|
104847
|
+
status: "ok",
|
|
104848
|
+
message: parts.join(", ")
|
|
104849
|
+
};
|
|
104850
|
+
}
|
|
104851
|
+
return {
|
|
104852
|
+
name: "ID mapping coverage",
|
|
104853
|
+
status: "error",
|
|
104854
|
+
message: `${missingIds.length} issue(s) without short ID mapping`,
|
|
104855
|
+
details: missingIds.map((id) => `${id} (no short ID)`),
|
|
104856
|
+
fixable: true,
|
|
104857
|
+
suggestion: "Run: tbd doctor --fix to create missing mappings"
|
|
104858
|
+
};
|
|
104859
|
+
}
|
|
104697
104860
|
async checkClaudeSkill() {
|
|
104698
104861
|
const claudePaths = getClaudePaths(this.cwd);
|
|
104699
104862
|
try {
|
|
@@ -108468,6 +108631,9 @@ var SetupDefaultHandler = class extends BaseCommand {
|
|
|
108468
108631
|
]);
|
|
108469
108632
|
if (tbdGitignoreResult.created) console.log(` ${colors.success("✓")} Created .tbd/.gitignore`);
|
|
108470
108633
|
else if (tbdGitignoreResult.added.length > 0) console.log(` ${colors.success("✓")} Updated .tbd/.gitignore with new patterns`);
|
|
108634
|
+
const gitattributesResult = await ensureGitignorePatterns(join(projectDir, TBD_DIR, ".gitattributes"), ["# Protect ID mappings from merge deletion (always keep all rows)", "**/mappings/ids.yml merge=union"]);
|
|
108635
|
+
if (gitattributesResult.created) console.log(` ${colors.success("✓")} Created .tbd/.gitattributes (merge protection)`);
|
|
108636
|
+
else if (gitattributesResult.added.length > 0) console.log(` ${colors.success("✓")} Updated .tbd/.gitattributes (merge protection)`);
|
|
108471
108637
|
console.log("Checking integrations...");
|
|
108472
108638
|
await new SetupAutoHandler(this.cmd).run(projectDir);
|
|
108473
108639
|
console.log("");
|
|
@@ -108598,6 +108764,9 @@ Example:
|
|
|
108598
108764
|
]);
|
|
108599
108765
|
if (tbdGitignoreResult.created) console.log(` ${colors.success("✓")} Created .tbd/.gitignore`);
|
|
108600
108766
|
else if (tbdGitignoreResult.added.length > 0) console.log(` ${colors.success("✓")} Updated .tbd/.gitignore`);
|
|
108767
|
+
const gitattributesResult = await ensureGitignorePatterns(join(cwd, TBD_DIR, ".gitattributes"), ["# Protect ID mappings from merge deletion (always keep all rows)", "**/mappings/ids.yml merge=union"]);
|
|
108768
|
+
if (gitattributesResult.created) console.log(` ${colors.success("✓")} Created .tbd/.gitattributes (merge protection)`);
|
|
108769
|
+
else if (gitattributesResult.added.length > 0) console.log(` ${colors.success("✓")} Updated .tbd/.gitattributes (merge protection)`);
|
|
108601
108770
|
try {
|
|
108602
108771
|
await initWorktree(cwd);
|
|
108603
108772
|
const health = await checkWorktreeHealth(cwd);
|
|
@@ -108970,7 +109139,7 @@ const workspaceCommand = new Command("workspace").description("Manage workspaces
|
|
|
108970
109139
|
function createProgram() {
|
|
108971
109140
|
const program = new Command().name("tbd").description("Git-native issue tracking for AI agents and humans").version(VERSION, "--version", "Show version number").helpOption("--help", "Display help for command").showHelpAfterError("(add --help for additional information)");
|
|
108972
109141
|
configureColoredHelp(program);
|
|
108973
|
-
program.option("--dry-run", "Show what would be done without making changes").option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--json", "Output as JSON").option("--color <when>", "Colorize output: auto, always, never", "auto").option("--
|
|
109142
|
+
program.option("--dry-run", "Show what would be done without making changes").option("--verbose", "Enable verbose output").option("--quiet", "Suppress non-essential output").option("--json", "Output as JSON").option("--color <when>", "Colorize output: auto, always, never", "auto").option("--no-sync", "Skip automatic sync after write operations").option("--debug", "Show internal IDs alongside public IDs for debugging");
|
|
108974
109143
|
program.commandsGroup("Documentation:");
|
|
108975
109144
|
program.addCommand(readmeCommand);
|
|
108976
109145
|
program.addCommand(primeCommand);
|