get-tbd 0.2.3 → 0.3.0
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 +38 -6
- package/dist/bin.mjs +3494 -949
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +1827 -1312
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-1ouUTKQr.mjs → config-B1w3pAkY.mjs} +142 -290
- package/dist/config-B1w3pAkY.mjs.map +1 -0
- package/dist/config-DXhifxOw.mjs +3 -0
- package/dist/doc-cache-CamtXfi4.mjs +1035 -0
- package/dist/doc-cache-CamtXfi4.mjs.map +1 -0
- package/dist/doc-cache-CiBwpDTf.mjs +3 -0
- package/dist/doc-fork-BMjqzfAs.mjs +3 -0
- package/dist/doc-fork-CoPi1G1N.mjs +304 -0
- package/dist/doc-fork-CoPi1G1N.mjs.map +1 -0
- package/dist/docref-C7g0sjvL.mjs +167 -0
- package/dist/docref-C7g0sjvL.mjs.map +1 -0
- package/dist/docs/README.md +38 -6
- package/dist/docs/SKILL.md +13 -4
- package/dist/docs/guidelines/backward-compatibility-rules.md +1 -0
- package/dist/docs/guidelines/bun-monorepo-patterns.md +1 -0
- package/dist/docs/guidelines/cli-agent-skill-patterns.md +124 -38
- package/dist/docs/guidelines/commit-conventions.md +1 -0
- package/dist/docs/guidelines/common-doc-guidelines.md +1 -0
- package/dist/docs/guidelines/convex-limits-best-practices.md +1 -0
- package/dist/docs/guidelines/convex-rules.md +1 -0
- package/dist/docs/guidelines/electron-app-development-patterns.md +1 -0
- package/dist/docs/guidelines/error-handling-rules.md +1 -0
- package/dist/docs/guidelines/general-coding-rules.md +1 -0
- package/dist/docs/guidelines/general-comment-rules.md +1 -0
- package/dist/docs/guidelines/general-eng-agent-principles.md +1 -0
- package/dist/docs/guidelines/general-tdd-guidelines.md +1 -0
- package/dist/docs/guidelines/general-testing-rules.md +1 -0
- package/dist/docs/guidelines/golden-testing-guidelines.md +1 -0
- package/dist/docs/guidelines/pnpm-monorepo-patterns.md +1 -0
- package/dist/docs/guidelines/python-cli-patterns.md +1 -0
- package/dist/docs/guidelines/python-modern-guidelines.md +1 -0
- package/dist/docs/guidelines/python-rules.md +1 -0
- package/dist/docs/guidelines/release-notes-guidelines.md +1 -0
- package/dist/docs/guidelines/supply-chain-hardening.md +3 -2
- package/dist/docs/guidelines/tbd-sync-troubleshooting.md +1 -0
- package/dist/docs/guidelines/typescript-cli-tool-rules.md +1 -0
- package/dist/docs/guidelines/typescript-code-coverage.md +1 -0
- package/dist/docs/guidelines/typescript-rules.md +1 -0
- package/dist/docs/guidelines/typescript-sorting-patterns.md +1 -0
- package/dist/docs/guidelines/typescript-yaml-handling-rules.md +1 -0
- package/dist/docs/references/docmap-format.md +64 -0
- package/dist/docs/references/docref-format.md +71 -0
- package/dist/docs/shortcuts/standard/checkout-third-party-repo.md +17 -4
- package/dist/docs/shortcuts/standard/new-shortcut.md +5 -5
- package/dist/docs/shortcuts/standard/plan-implementation-with-beads.md +1 -1
- package/dist/docs/shortcuts/standard/suggest-upstream-improvements.md +69 -0
- package/dist/docs/shortcuts/standard/sync-failure-recovery.md +1 -1
- package/dist/docs/shortcuts/standard/welcome-user.md +28 -0
- package/dist/docs/shortcuts/system/skill-baseline.md +13 -4
- package/dist/docs/tbd-design.md +141 -0
- package/dist/docs/tbd-docs.md +169 -5
- package/dist/docs/tbd-prime.md +0 -1
- package/dist/docs/templates/qa-playbook.md +3 -1
- package/dist/docs/templates/research-brief.md +2 -2
- package/dist/fork-manifest-ByU7U2do.mjs +253 -0
- package/dist/fork-manifest-ByU7U2do.mjs.map +1 -0
- package/dist/fork-manifest-C7lGRq-6.mjs +3 -0
- package/dist/{id-mapping-mtoSP9Qt.mjs → id-mapping-D0iitY-F.mjs} +1 -1
- package/dist/{id-mapping-687_UEsy.mjs → id-mapping-DAIeLKzm.mjs} +8 -200
- package/dist/id-mapping-DAIeLKzm.mjs.map +1 -0
- package/dist/index.d.mts +90 -13
- package/dist/index.mjs +3 -3
- package/dist/lockfile-BR0laSDy.mjs +198 -0
- package/dist/lockfile-BR0laSDy.mjs.map +1 -0
- package/dist/paths-C1DpnZJW.mjs +405 -0
- package/dist/paths-C1DpnZJW.mjs.map +1 -0
- package/dist/{schemas-f0EcuAVu.mjs → schemas-lCwRk30L.mjs} +19 -6
- package/dist/schemas-lCwRk30L.mjs.map +1 -0
- package/dist/{src-DTyyuaG_.mjs → src-CxKOynr1.mjs} +3 -3
- package/dist/src-CxKOynr1.mjs.map +1 -0
- package/dist/tbd +3494 -949
- package/dist/yaml-utils-BPy991by.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/config-1ouUTKQr.mjs.map +0 -1
- package/dist/config-YRRW9l89.mjs +0 -3
- package/dist/id-mapping-687_UEsy.mjs.map +0 -1
- package/dist/schemas-f0EcuAVu.mjs.map +0 -1
- package/dist/src-DTyyuaG_.mjs.map +0 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//#region src/docref/docref.ts
|
|
2
|
+
/** Error thrown when a string is not a valid docref. */
|
|
3
|
+
var DocRefError = class extends Error {
|
|
4
|
+
constructor(input, detail) {
|
|
5
|
+
super(`Invalid docref ${JSON.stringify(input)}: ${detail}`);
|
|
6
|
+
this.input = input;
|
|
7
|
+
this.name = "DocRefError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
const GIT_SCHEMES = ["github", "gitlab"];
|
|
11
|
+
/**
|
|
12
|
+
* True for strings that address a local filesystem path: anchored relative
|
|
13
|
+
* (`./`, `../`), absolute (`/`), or a Windows drive-letter path (`C:/`, `C:\`).
|
|
14
|
+
*/
|
|
15
|
+
function looksLocal(input) {
|
|
16
|
+
return input.startsWith("./") || input.startsWith("../") || input.startsWith("/") || /^[A-Za-z]:[\\/]/.test(input);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a `host:owner/repo[@ref]//path[#fragment]` body (everything after the scheme).
|
|
20
|
+
*/
|
|
21
|
+
function parseGitBody(host, body, input) {
|
|
22
|
+
const sep = body.indexOf("//");
|
|
23
|
+
if (sep === -1) throw new DocRefError(input, `git docref must contain "//" separating repo from path`);
|
|
24
|
+
const repoPart = body.slice(0, sep);
|
|
25
|
+
const pathPart = body.slice(sep + 2);
|
|
26
|
+
const hashIndex = pathPart.indexOf("#");
|
|
27
|
+
const path = hashIndex === -1 ? pathPart : pathPart.slice(0, hashIndex);
|
|
28
|
+
const fragment = hashIndex === -1 ? void 0 : pathPart.slice(hashIndex + 1) || void 0;
|
|
29
|
+
if (!path) throw new DocRefError(input, "git docref has an empty path");
|
|
30
|
+
const atIndex = repoPart.indexOf("@");
|
|
31
|
+
const ownerRepo = atIndex === -1 ? repoPart : repoPart.slice(0, atIndex);
|
|
32
|
+
const ref = atIndex === -1 ? void 0 : repoPart.slice(atIndex + 1) || void 0;
|
|
33
|
+
const slash = ownerRepo.indexOf("/");
|
|
34
|
+
if (slash === -1) throw new DocRefError(input, "git docref must be \"owner/repo\"");
|
|
35
|
+
const owner = ownerRepo.slice(0, slash);
|
|
36
|
+
const repo = ownerRepo.slice(slash + 1);
|
|
37
|
+
if (!owner || !repo) throw new DocRefError(input, "git docref must be \"owner/repo\"");
|
|
38
|
+
return {
|
|
39
|
+
kind: "git",
|
|
40
|
+
host,
|
|
41
|
+
owner,
|
|
42
|
+
repo,
|
|
43
|
+
path,
|
|
44
|
+
...ref !== void 0 ? { ref } : {},
|
|
45
|
+
...fragment !== void 0 ? { fragment } : {}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* If `url` points at a known git host's file view, return the equivalent git
|
|
50
|
+
* docref; otherwise return null (caller keeps it as a plain URL).
|
|
51
|
+
* URL fragments are preserved; normalization must never silently drop data.
|
|
52
|
+
*/
|
|
53
|
+
function gitRefFromUrl(url) {
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(url);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
61
|
+
const fragment = parsed.hash ? parsed.hash.slice(1) || void 0 : void 0;
|
|
62
|
+
const frag = fragment !== void 0 ? { fragment } : {};
|
|
63
|
+
if (parsed.hostname === "github.com" && segments[2] === "blob" && segments.length >= 5) {
|
|
64
|
+
const [owner, repo, , ref, ...rest] = segments;
|
|
65
|
+
return {
|
|
66
|
+
kind: "git",
|
|
67
|
+
host: "github",
|
|
68
|
+
owner,
|
|
69
|
+
repo,
|
|
70
|
+
ref,
|
|
71
|
+
path: rest.join("/"),
|
|
72
|
+
...frag
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (parsed.hostname === "raw.githubusercontent.com" && segments.length >= 4) {
|
|
76
|
+
const [owner, repo, ref, ...rest] = segments;
|
|
77
|
+
return {
|
|
78
|
+
kind: "git",
|
|
79
|
+
host: "github",
|
|
80
|
+
owner,
|
|
81
|
+
repo,
|
|
82
|
+
ref,
|
|
83
|
+
path: rest.join("/"),
|
|
84
|
+
...frag
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (parsed.hostname === "gitlab.com" && segments[2] === "-" && segments[3] === "blob") {
|
|
88
|
+
const [owner, repo, , , ref, ...rest] = segments;
|
|
89
|
+
if (owner && repo && ref && rest.length > 0) return {
|
|
90
|
+
kind: "git",
|
|
91
|
+
host: "gitlab",
|
|
92
|
+
owner,
|
|
93
|
+
repo,
|
|
94
|
+
ref,
|
|
95
|
+
path: rest.join("/"),
|
|
96
|
+
...frag
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse a docref string into a structured {@link DocRef}.
|
|
103
|
+
* Throws {@link DocRefError} if the string is not a valid docref.
|
|
104
|
+
*/
|
|
105
|
+
function parseDocRef(input) {
|
|
106
|
+
const raw = input.trim();
|
|
107
|
+
if (!raw) throw new DocRefError(input, "empty");
|
|
108
|
+
if (raw.startsWith("internal:")) {
|
|
109
|
+
const path = raw.slice(9);
|
|
110
|
+
if (!path) throw new DocRefError(input, "internal docref has an empty path");
|
|
111
|
+
return {
|
|
112
|
+
kind: "internal",
|
|
113
|
+
path
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
for (const host of GIT_SCHEMES) {
|
|
117
|
+
const prefix = `${host}:`;
|
|
118
|
+
if (raw.startsWith(prefix)) return parseGitBody(host, raw.slice(prefix.length), input);
|
|
119
|
+
}
|
|
120
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) return gitRefFromUrl(raw) ?? {
|
|
121
|
+
kind: "url",
|
|
122
|
+
url: raw
|
|
123
|
+
};
|
|
124
|
+
if (looksLocal(raw)) return {
|
|
125
|
+
kind: "local",
|
|
126
|
+
path: raw
|
|
127
|
+
};
|
|
128
|
+
if (raw.startsWith("~")) throw new DocRefError(input, "home-relative (~) paths are not supported; use an absolute or ./-relative path");
|
|
129
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(raw)) throw new DocRefError(input, "unknown scheme");
|
|
130
|
+
throw new DocRefError(input, "local paths must start with \"./\", \"../\", or \"/\" (bare relative paths are not valid docrefs)");
|
|
131
|
+
}
|
|
132
|
+
/** Parse a docref, returning null instead of throwing on invalid input. */
|
|
133
|
+
function tryParseDocRef(input) {
|
|
134
|
+
try {
|
|
135
|
+
return parseDocRef(input);
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Serialize a {@link DocRef} back to its canonical string form. */
|
|
141
|
+
function formatDocRef(ref) {
|
|
142
|
+
switch (ref.kind) {
|
|
143
|
+
case "internal": return `internal:${ref.path}`;
|
|
144
|
+
case "local": return ref.path;
|
|
145
|
+
case "url": return ref.url;
|
|
146
|
+
case "git": {
|
|
147
|
+
const refPart = ref.ref ? `@${ref.ref}` : "";
|
|
148
|
+
const fragPart = ref.fragment ? `#${ref.fragment}` : "";
|
|
149
|
+
return `${ref.host}:${ref.owner}/${ref.repo}${refPart}//${ref.path}${fragPart}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Normalize a docref string to its canonical form (parse + re-format).
|
|
155
|
+
* Rationalizes git web URLs into the `github:`/`gitlab:` scheme.
|
|
156
|
+
*/
|
|
157
|
+
function normalizeDocRef(input) {
|
|
158
|
+
return formatDocRef(parseDocRef(input));
|
|
159
|
+
}
|
|
160
|
+
/** True if `input` parses as a valid docref. */
|
|
161
|
+
function isDocRef(input) {
|
|
162
|
+
return tryParseDocRef(input) !== null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
export { tryParseDocRef as a, parseDocRef as i, isDocRef as n, normalizeDocRef as r, DocRefError as t };
|
|
167
|
+
//# sourceMappingURL=docref-C7g0sjvL.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docref-C7g0sjvL.mjs","names":[],"sources":["../src/docref/docref.ts"],"sourcesContent":["/**\n * docref: a single-string, URI-like address for any document.\n *\n * This module is intentionally standalone and dependency-free (no tbd-internal\n * imports) so it can move to its own package later. It is the one address syntax\n * used everywhere a doc's source or location is named: config source strings, the\n * fork manifest's `source` field, `tbd docs add` arguments, and `local_dirs` entries.\n *\n * Supported forms (docref v0.1):\n * internal:guidelines/python-rules.md bundled doc shipped inside the consuming\n * tool (app-relative, not tbd-specific)\n * ./docs/general/ ../shared/ /abs/f.md local paths; must be anchored with\n * \"./\", \"../\", \"/\", or a Windows drive\n * letter (C:/ or C:\\)\n * https://example.com/style.md plain URL\n * github:owner/repo@ref//path/to/file.md git-hosted (also gitlab:)\n * github:owner/repo@ref//file.md#section optional fragment, preserved\n *\n * The grammar is deliberately strict: bare relative strings (\"guidelines/x.md\") and\n * home-relative paths (\"~/x.md\") are NOT valid docrefs. Consumers that want lenient\n * input may coerce at their own boundary (e.g. prepend \"./\") before parsing; a\n * strict grammar plus lenient consumers composes; the reverse cannot be tightened.\n *\n * Additional protocols (for example a host-bearing git scheme for forges beyond\n * GitHub/GitLab) may be added in future versions.\n *\n * Web URLs that point at a known git host are normalized to the `github:`/`gitlab:`\n * form so there is one canonical address for a given file:\n * https://github.com/o/r/blob/main/f.md -> github:o/r@main//f.md\n * https://raw.githubusercontent.com/o/r/main/f.md -> github:o/r@main//f.md\n */\n\n/** Scheme of a git-hosted docref. */\nexport type GitHost = 'github' | 'gitlab';\n\n/** A parsed document reference. */\nexport type DocRef =\n | { readonly kind: 'internal'; readonly path: string }\n | { readonly kind: 'local'; readonly path: string }\n | { readonly kind: 'url'; readonly url: string }\n | {\n readonly kind: 'git';\n readonly host: GitHost;\n readonly owner: string;\n readonly repo: string;\n readonly ref?: string;\n readonly path: string;\n /** Optional in-document anchor (e.g. a heading slug), preserved verbatim. */\n readonly fragment?: string;\n };\n\n/** Error thrown when a string is not a valid docref. */\nexport class DocRefError extends Error {\n constructor(\n public readonly input: string,\n detail: string,\n ) {\n super(`Invalid docref ${JSON.stringify(input)}: ${detail}`);\n this.name = 'DocRefError';\n }\n}\n\nconst GIT_SCHEMES: readonly GitHost[] = ['github', 'gitlab'];\n\n/**\n * True for strings that address a local filesystem path: anchored relative\n * (`./`, `../`), absolute (`/`), or a Windows drive-letter path (`C:/`, `C:\\`).\n */\nfunction looksLocal(input: string): boolean {\n return (\n input.startsWith('./') ||\n input.startsWith('../') ||\n input.startsWith('/') ||\n /^[A-Za-z]:[\\\\/]/.test(input)\n );\n}\n\n/** Strip a single leading `./` for tidy comparison; other forms are left as-is. */\nfunction tidyLocal(path: string): string {\n return path.startsWith('./') ? path.slice(2) : path;\n}\n\n/**\n * Parse a `host:owner/repo[@ref]//path[#fragment]` body (everything after the scheme).\n */\nfunction parseGitBody(host: GitHost, body: string, input: string): DocRef {\n const sep = body.indexOf('//');\n if (sep === -1) {\n throw new DocRefError(input, `git docref must contain \"//\" separating repo from path`);\n }\n const repoPart = body.slice(0, sep);\n const pathPart = body.slice(sep + 2);\n\n const hashIndex = pathPart.indexOf('#');\n const path = hashIndex === -1 ? pathPart : pathPart.slice(0, hashIndex);\n const fragment = hashIndex === -1 ? undefined : pathPart.slice(hashIndex + 1) || undefined;\n if (!path) {\n throw new DocRefError(input, 'git docref has an empty path');\n }\n\n const atIndex = repoPart.indexOf('@');\n const ownerRepo = atIndex === -1 ? repoPart : repoPart.slice(0, atIndex);\n const ref = atIndex === -1 ? undefined : repoPart.slice(atIndex + 1) || undefined;\n\n const slash = ownerRepo.indexOf('/');\n if (slash === -1) {\n throw new DocRefError(input, 'git docref must be \"owner/repo\"');\n }\n const owner = ownerRepo.slice(0, slash);\n const repo = ownerRepo.slice(slash + 1);\n if (!owner || !repo) {\n throw new DocRefError(input, 'git docref must be \"owner/repo\"');\n }\n\n return {\n kind: 'git',\n host,\n owner,\n repo,\n path,\n ...(ref !== undefined ? { ref } : {}),\n ...(fragment !== undefined ? { fragment } : {}),\n };\n}\n\n/**\n * If `url` points at a known git host's file view, return the equivalent git\n * docref; otherwise return null (caller keeps it as a plain URL).\n * URL fragments are preserved; normalization must never silently drop data.\n */\nfunction gitRefFromUrl(url: string): DocRef | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n const segments = parsed.pathname.split('/').filter(Boolean);\n const fragment = parsed.hash ? parsed.hash.slice(1) || undefined : undefined;\n const frag = fragment !== undefined ? { fragment } : {};\n\n // https://github.com/{owner}/{repo}/blob/{ref}/{path...}\n if (parsed.hostname === 'github.com' && segments[2] === 'blob' && segments.length >= 5) {\n const [owner, repo, , ref, ...rest] = segments;\n return {\n kind: 'git',\n host: 'github',\n owner: owner!,\n repo: repo!,\n ref,\n path: rest.join('/'),\n ...frag,\n };\n }\n // https://raw.githubusercontent.com/{owner}/{repo}/{ref}/{path...}\n if (parsed.hostname === 'raw.githubusercontent.com' && segments.length >= 4) {\n const [owner, repo, ref, ...rest] = segments;\n return {\n kind: 'git',\n host: 'github',\n owner: owner!,\n repo: repo!,\n ref,\n path: rest.join('/'),\n ...frag,\n };\n }\n // https://gitlab.com/{owner}/{repo}/-/blob/{ref}/{path...}\n if (parsed.hostname === 'gitlab.com' && segments[2] === '-' && segments[3] === 'blob') {\n const [owner, repo, , , ref, ...rest] = segments;\n if (owner && repo && ref && rest.length > 0) {\n return { kind: 'git', host: 'gitlab', owner, repo, ref, path: rest.join('/'), ...frag };\n }\n }\n return null;\n}\n\n/**\n * Parse a docref string into a structured {@link DocRef}.\n * Throws {@link DocRefError} if the string is not a valid docref.\n */\nexport function parseDocRef(input: string): DocRef {\n const raw = input.trim();\n if (!raw) {\n throw new DocRefError(input, 'empty');\n }\n\n // Internal bundled docs.\n if (raw.startsWith('internal:')) {\n const path = raw.slice('internal:'.length);\n if (!path) {\n throw new DocRefError(input, 'internal docref has an empty path');\n }\n return { kind: 'internal', path };\n }\n\n // Git-hosted schemes.\n for (const host of GIT_SCHEMES) {\n const prefix = `${host}:`;\n if (raw.startsWith(prefix)) {\n return parseGitBody(host, raw.slice(prefix.length), input);\n }\n }\n\n // Web URLs: normalize known git hosts, otherwise keep as a plain URL.\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\n return gitRefFromUrl(raw) ?? { kind: 'url', url: raw };\n }\n\n // Local filesystem paths (anchored; includes Windows drive letters).\n if (looksLocal(raw)) {\n return { kind: 'local', path: raw };\n }\n\n if (raw.startsWith('~')) {\n throw new DocRefError(\n input,\n 'home-relative (~) paths are not supported; use an absolute or ./-relative path',\n );\n }\n if (/^[a-z][a-z0-9+.-]*:/i.test(raw)) {\n throw new DocRefError(input, 'unknown scheme');\n }\n throw new DocRefError(\n input,\n 'local paths must start with \"./\", \"../\", or \"/\" (bare relative paths are not valid docrefs)',\n );\n}\n\n/** Parse a docref, returning null instead of throwing on invalid input. */\nexport function tryParseDocRef(input: string): DocRef | null {\n try {\n return parseDocRef(input);\n } catch {\n return null;\n }\n}\n\n/** Serialize a {@link DocRef} back to its canonical string form. */\nexport function formatDocRef(ref: DocRef): string {\n switch (ref.kind) {\n case 'internal':\n return `internal:${ref.path}`;\n case 'local':\n return ref.path;\n case 'url':\n return ref.url;\n case 'git': {\n const refPart = ref.ref ? `@${ref.ref}` : '';\n const fragPart = ref.fragment ? `#${ref.fragment}` : '';\n return `${ref.host}:${ref.owner}/${ref.repo}${refPart}//${ref.path}${fragPart}`;\n }\n }\n}\n\n/**\n * Normalize a docref string to its canonical form (parse + re-format).\n * Rationalizes git web URLs into the `github:`/`gitlab:` scheme.\n */\nexport function normalizeDocRef(input: string): string {\n return formatDocRef(parseDocRef(input));\n}\n\n/** True if `input` parses as a valid docref. */\nexport function isDocRef(input: string): boolean {\n return tryParseDocRef(input) !== null;\n}\n\n/**\n * Whether two docrefs address the same document, ignoring a leading `./` on\n * local paths. Comparison is purely syntactic; no case normalization of hosts\n * or owners. Useful for de-duping config entries.\n */\nexport function docRefsEqual(a: DocRef, b: DocRef): boolean {\n if (a.kind !== b.kind) return false;\n if (a.kind === 'local' && b.kind === 'local') {\n return tidyLocal(a.path) === tidyLocal(b.path);\n }\n return formatDocRef(a) === formatDocRef(b);\n}\n"],"mappings":"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"}
|
package/dist/docs/README.md
CHANGED
|
@@ -37,7 +37,6 @@ will work in Cursor, Codex, or any agent environment that can use the `tbd` CLI.
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
39
|
> [!TIP]
|
|
40
|
-
>
|
|
41
40
|
> If running on your own machine, install the `tbd` CLI yourself:
|
|
42
41
|
>
|
|
43
42
|
> **`npm install -g get-tbd@latest`**
|
|
@@ -50,6 +49,11 @@ will work in Cursor, Codex, or any agent environment that can use the `tbd` CLI.
|
|
|
50
49
|
>
|
|
51
50
|
> ***“install tbd (npm install -g get-tbd@latest) and run tbd prime for instructions to
|
|
52
51
|
> set up this project”***
|
|
52
|
+
>
|
|
53
|
+
> If tbd is already set up in the repo and you want the latest version, tell the agent:
|
|
54
|
+
>
|
|
55
|
+
> ***“upgrade tbd (npm install -g get-tbd@latest), run tbd setup --auto, and commit the
|
|
56
|
+
> changes”***
|
|
53
57
|
|
|
54
58
|
That’s it.
|
|
55
59
|
Running `tbd prime` gives agents full workflow context on how to use `tbd` and
|
|
@@ -122,7 +126,6 @@ agents handling different aspects that I manage) is slower, because it forces yo
|
|
|
122
126
|
design, but it gives higher quality results.
|
|
123
127
|
|
|
124
128
|
> [!NOTE]
|
|
125
|
-
>
|
|
126
129
|
> We use *Beads* (capitalized) to refer to Steve Yegge’s original
|
|
127
130
|
> [`bd` tool](https://github.com/steveyegge/beads).
|
|
128
131
|
> Lowercase “beads” refers generically to the issues stored in `tbd` or `bd`.
|
|
@@ -160,8 +163,8 @@ You just talk naturally.
|
|
|
160
163
|
|
|
161
164
|
> [!NOTE]
|
|
162
165
|
> For full technical details, see the [reference docs](packages/tbd/docs/tbd-docs.md)
|
|
163
|
-
> (run `tbd docs`) or the full
|
|
164
|
-
> (`tbd design`).
|
|
166
|
+
> (run `tbd docs show tbd-docs`) or the full
|
|
167
|
+
> [design doc](packages/tbd/docs/tbd-design.md) (`tbd design`).
|
|
165
168
|
|
|
166
169
|
- **Git-native:** Beads live in your repo, synced to a separate, dedicated `tbd-sync`
|
|
167
170
|
branch. Your code history stays clean—no bead churn polluting your logs.
|
|
@@ -225,7 +228,6 @@ practices. These aren’t generic tips; they’re mostly my own detailed and som
|
|
|
225
228
|
opinionated rules with concrete examples, built from months of heavy agentic coding.
|
|
226
229
|
|
|
227
230
|
> [!TIP]
|
|
228
|
-
>
|
|
229
231
|
> An example: I *strongly* believe there are much better ways to do testing
|
|
230
232
|
> proliferating hundreds of unit and integration tests.
|
|
231
233
|
> So (with help from some Opus 4.5 and GPT-5 Pro) I wrote a multi-page brief about
|
|
@@ -292,6 +294,25 @@ tbd setup --from-beads
|
|
|
292
294
|
> **Tip:** Run `tbd setup --auto` anytime to refresh skill files, hooks, and configs
|
|
293
295
|
> with the latest shortcuts, guidelines, and templates.
|
|
294
296
|
|
|
297
|
+
### Upgrading
|
|
298
|
+
|
|
299
|
+
Upgrading an existing installation is the same two commands, run by you or your agent:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm install -g get-tbd@latest # Upgrade the CLI
|
|
303
|
+
tbd setup --auto # Refresh skills/hooks and apply any format migration
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
If the new version bumps the repository format (`tbd_format` in `.tbd/config.yml`),
|
|
307
|
+
setup migrates it automatically and prints a notice; **commit the resulting diff** to
|
|
308
|
+
publish the upgrade to your team.
|
|
309
|
+
Teammates still on an older tbd then see “This repository requires a newer version of
|
|
310
|
+
tbd” until they run the same two commands.
|
|
311
|
+
Issue data is never touched by an upgrade, and the migration is revertible: see
|
|
312
|
+
“Aborting a Format Upgrade” under Troubleshooting in the CLI manual (`tbd docs manual`).
|
|
313
|
+
If you have forked docs in `docs/tbd/`, `tbd sync` prints a notice when their upstream
|
|
314
|
+
versions moved; run `tbd docs update` to merge the changes in.
|
|
315
|
+
|
|
295
316
|
### Team Setup
|
|
296
317
|
|
|
297
318
|
`tbd` is designed for teams where one person sets up the project and others join later.
|
|
@@ -394,11 +415,21 @@ tbd template --list # List all templates
|
|
|
394
415
|
tbd template plan-spec # Get a plan spec template
|
|
395
416
|
|
|
396
417
|
# Add your own from any URL
|
|
418
|
+
# (per-kind aliases for `tbd docs add <docref>`)
|
|
397
419
|
tbd guidelines --add=<url> --name=<name>
|
|
398
420
|
tbd shortcut --add=<url> --name=<name>
|
|
399
421
|
tbd template --add=<url> --name=<name>
|
|
400
422
|
```
|
|
401
423
|
|
|
424
|
+
**Forkable: see them in your repo.** By default these docs are served from a hidden,
|
|
425
|
+
gitignored cache. Fork any of them into `docs/tbd/` and they become visible on GitHub,
|
|
426
|
+
reviewable in PRs, and editable in place—tbd serves your copy instead, and
|
|
427
|
+
`tbd docs update` merges upstream improvements into it after an upgrade:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
tbd docs fork --all # Or fork by name: tbd docs fork <name> [<name>...]
|
|
431
|
+
```
|
|
432
|
+
|
|
402
433
|
**Available shortcuts:**
|
|
403
434
|
|
|
404
435
|
| Category | Shortcut | Purpose |
|
|
@@ -480,7 +511,8 @@ Every command supports these flags for automation:
|
|
|
480
511
|
```bash
|
|
481
512
|
tbd # Full orientation and workflow guidance
|
|
482
513
|
tbd readme # This file
|
|
483
|
-
tbd docs #
|
|
514
|
+
tbd docs # Managed-docs overview (cached, forked, and local docs)
|
|
515
|
+
tbd docs show tbd-docs # Full CLI reference (the manual; alias: tbd docs manual)
|
|
484
516
|
```
|
|
485
517
|
|
|
486
518
|
Or read online:
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -31,12 +31,15 @@ allowed-tools: Bash(tbd:*), Read, Write
|
|
|
31
31
|
## Installation
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npm install -g get-tbd@latest
|
|
34
|
+
npm install -g get-tbd@latest # Install or upgrade the CLI (same command for both)
|
|
35
35
|
tbd setup --auto --prefix=<name> # Fresh project (--prefix is REQUIRED: 2-8 alphabetic chars recommended. ALWAYS ASK THE USER FOR THE PREFIX; do not guess it)
|
|
36
|
-
tbd setup --auto # Existing tbd project (
|
|
36
|
+
tbd setup --auto # Existing tbd project — also the upgrade step (applies any format migration; commit the diff it reports)
|
|
37
37
|
tbd setup --from-beads # Migration from .beads/ if `bd` has been used
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
If tbd refuses with “This repository requires a newer version of tbd”, run the two
|
|
41
|
+
install/upgrade commands above.
|
|
42
|
+
|
|
40
43
|
## Routine Commands
|
|
41
44
|
|
|
42
45
|
```bash
|
|
@@ -48,7 +51,7 @@ tbd setup --auto # Run any time to refresh setup
|
|
|
48
51
|
tbd prime # Restore full context on tbd after compaction
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
## CRITICAL: You Operate tbd
|
|
54
|
+
## CRITICAL: You Operate tbd, the User Doesn’t
|
|
52
55
|
|
|
53
56
|
**You are the tbd operator:** Users talk naturally; you translate their requests to tbd
|
|
54
57
|
actions. DO NOT tell users to run tbd commands.
|
|
@@ -96,6 +99,10 @@ or want help → run `tbd shortcut welcome-user`
|
|
|
96
99
|
| **Documentation** | |
|
|
97
100
|
| “Research this topic” | `tbd shortcut new-research-brief` |
|
|
98
101
|
| “Document architecture” | `tbd shortcut new-architecture-doc` |
|
|
102
|
+
| “What guidelines/docs are there?” | `tbd docs list` |
|
|
103
|
+
| “Make the guidelines visible / customize doc X” | `tbd docs fork --category=general --category=<lang>` (recommended: general + the repo’s languages), or `tbd docs fork <name>` / `--all`; then edit in `docs/tbd/` |
|
|
104
|
+
| “Update the guidelines to the latest” | `tbd docs update`; on conflicts ask the user, then `--merge` or `--keep-ours` |
|
|
105
|
+
| “I deleted a forked doc file” | `tbd docs status` shows it `missing`; restore with `tbd docs fork <name> --force` or finalize with `tbd docs unfork <name>` |
|
|
99
106
|
| **Cleanup & Maintenance** | |
|
|
100
107
|
| “Clean up this code” / “Remove dead code” | `tbd shortcut code-cleanup-all` |
|
|
101
108
|
| “Fix repository problems” | `tbd doctor --fix` |
|
|
@@ -110,7 +117,7 @@ these apply to all code regardless of language.
|
|
|
110
117
|
Then load the group for the language or framework in use (TypeScript, Python, Convex,
|
|
111
118
|
etc.). Run `tbd guidelines --list` to see all available guidelines.
|
|
112
119
|
|
|
113
|
-
**Note:** Never gitignore `.tbd/workspaces
|
|
120
|
+
**Note:** Never gitignore `.tbd/workspaces/`; the outbox must be committed to your
|
|
114
121
|
working branch. See `tbd guidelines tbd-sync-troubleshooting` for details.
|
|
115
122
|
|
|
116
123
|
## CRITICAL: Session Closing Protocol
|
|
@@ -185,6 +192,8 @@ working branch. See `tbd guidelines tbd-sync-troubleshooting` for details.
|
|
|
185
192
|
| `tbd guidelines <name>` | Load coding guidelines |
|
|
186
193
|
| `tbd guidelines --list` | List guidelines |
|
|
187
194
|
| `tbd template <name>` | Output a template |
|
|
195
|
+
| `tbd docs` / `tbd docs list` | Managed-docs overview / cross-kind list with state markers |
|
|
196
|
+
| `tbd docs fork/unfork/update <name>` | Fork docs into `docs/tbd/`, return to upstream, pull upstream updates |
|
|
188
197
|
|
|
189
198
|
## Quick Reference
|
|
190
199
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
title: Backward Compatibility Rules
|
|
3
3
|
description: Guidelines for maintaining backward compatibility across code, APIs, file formats, and database schemas
|
|
4
4
|
author: Joshua Levy (github.com/jlevy) with LLM assistance
|
|
5
|
+
category: general
|
|
5
6
|
---
|
|
6
7
|
## Backward Compatibility Guidelines
|
|
7
8
|
|