get-tbd 0.1.24 → 0.1.25
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 +2 -2
- package/dist/bin.mjs +61 -35
- package/dist/bin.mjs.map +1 -1
- package/dist/cli.mjs +40 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/docs/README.md +2 -2
- package/dist/{id-mapping-DjVJIO4M.mjs → id-mapping-BSNsaOCC.mjs} +27 -12
- package/dist/id-mapping-BSNsaOCC.mjs.map +1 -0
- package/dist/{id-mapping-LjnDSEhN.mjs → id-mapping-DMMKwXZv.mjs} +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-BrM6xcdG.mjs → src-DuXy2Uyd.mjs} +2 -2
- package/dist/{src-BrM6xcdG.mjs.map → src-DuXy2Uyd.mjs.map} +1 -1
- package/dist/tbd +61 -35
- package/package.json +1 -1
- package/dist/id-mapping-DjVJIO4M.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -279,7 +279,7 @@ npm install -g get-tbd@latest
|
|
|
279
279
|
### Setup
|
|
280
280
|
|
|
281
281
|
```bash
|
|
282
|
-
# Fresh project (--prefix is REQUIRED—
|
|
282
|
+
# Fresh project (--prefix is REQUIRED—a short alphabetic name used as an issue ID prefix, e.g. myapp → issues like myapp-a1b2)
|
|
283
283
|
tbd setup --auto --prefix=myapp
|
|
284
284
|
|
|
285
285
|
# Joining an existing tbd project (no prefix needed—reads existing config)
|
|
@@ -299,7 +299,7 @@ tbd setup --from-beads
|
|
|
299
299
|
**First contributor:**
|
|
300
300
|
```bash
|
|
301
301
|
npm install -g get-tbd@latest
|
|
302
|
-
tbd setup --auto --prefix=
|
|
302
|
+
tbd setup --auto --prefix=proj # Short alphabetic prefix for issue IDs
|
|
303
303
|
git add .tbd/ .claude/ && git commit -m "Initialize tbd"
|
|
304
304
|
git push
|
|
305
305
|
```
|
package/dist/bin.mjs
CHANGED
|
@@ -14033,7 +14033,7 @@ function serializeIssue(issue) {
|
|
|
14033
14033
|
* Package version, derived from git at build time.
|
|
14034
14034
|
* Format: X.Y.Z for releases, X.Y.Z-dev.N.hash for dev builds.
|
|
14035
14035
|
*/
|
|
14036
|
-
const VERSION$1 = "0.1.
|
|
14036
|
+
const VERSION$1 = "0.1.25";
|
|
14037
14037
|
|
|
14038
14038
|
//#endregion
|
|
14039
14039
|
//#region src/cli/lib/version.ts
|
|
@@ -99751,28 +99751,32 @@ async function listIssues(baseDir) {
|
|
|
99751
99751
|
return [];
|
|
99752
99752
|
}
|
|
99753
99753
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
99754
|
-
const
|
|
99755
|
-
const filePath = join(issuesDir, file);
|
|
99756
|
-
try {
|
|
99757
|
-
return {
|
|
99758
|
-
file,
|
|
99759
|
-
content: await readFile(filePath, "utf-8")
|
|
99760
|
-
};
|
|
99761
|
-
} catch {
|
|
99762
|
-
return {
|
|
99763
|
-
file,
|
|
99764
|
-
content: null
|
|
99765
|
-
};
|
|
99766
|
-
}
|
|
99767
|
-
}));
|
|
99754
|
+
const BATCH_SIZE = 200;
|
|
99768
99755
|
const issues = [];
|
|
99769
|
-
for (
|
|
99770
|
-
|
|
99771
|
-
|
|
99772
|
-
const
|
|
99773
|
-
|
|
99774
|
-
|
|
99775
|
-
|
|
99756
|
+
for (let i = 0; i < mdFiles.length; i += BATCH_SIZE) {
|
|
99757
|
+
const batch = mdFiles.slice(i, i + BATCH_SIZE);
|
|
99758
|
+
const fileContents = await Promise.all(batch.map(async (file) => {
|
|
99759
|
+
const filePath = join(issuesDir, file);
|
|
99760
|
+
try {
|
|
99761
|
+
return {
|
|
99762
|
+
file,
|
|
99763
|
+
content: await readFile(filePath, "utf-8")
|
|
99764
|
+
};
|
|
99765
|
+
} catch {
|
|
99766
|
+
return {
|
|
99767
|
+
file,
|
|
99768
|
+
content: null
|
|
99769
|
+
};
|
|
99770
|
+
}
|
|
99771
|
+
}));
|
|
99772
|
+
for (const { file, content } of fileContents) {
|
|
99773
|
+
if (content === null) continue;
|
|
99774
|
+
try {
|
|
99775
|
+
const issue = parseIssue(content);
|
|
99776
|
+
issues.push(issue);
|
|
99777
|
+
} catch (error) {
|
|
99778
|
+
console.warn(`Skipping invalid issue file: ${file}`, error);
|
|
99779
|
+
}
|
|
99776
99780
|
}
|
|
99777
99781
|
}
|
|
99778
99782
|
return issues;
|
|
@@ -99814,30 +99818,44 @@ async function listIssues(baseDir) {
|
|
|
99814
99818
|
* crashed and break the lock. This is a heuristic — safe when the critical
|
|
99815
99819
|
* section is short-lived (sub-second for file I/O).
|
|
99816
99820
|
*
|
|
99817
|
-
* ##
|
|
99821
|
+
* ## Failure on timeout
|
|
99822
|
+
*
|
|
99823
|
+
* If the lock cannot be acquired within the timeout, a LockAcquisitionError is
|
|
99824
|
+
* thrown. This prevents the dangerous "degraded mode" where the critical section
|
|
99825
|
+
* runs without mutual exclusion, which can cause data loss (e.g., lost ID
|
|
99826
|
+
* mappings during concurrent `tbd create`).
|
|
99818
99827
|
*
|
|
99819
|
-
*
|
|
99820
|
-
*
|
|
99821
|
-
* Callers should design their critical sections to be safe without the lock
|
|
99822
|
-
* (e.g., using read-merge-write for append-only data).
|
|
99828
|
+
* IMPORTANT: `timeoutMs` must be greater than `staleMs` so stale locks from
|
|
99829
|
+
* crashed processes are always detected and broken before the timeout expires.
|
|
99823
99830
|
*/
|
|
99824
|
-
const DEFAULT_TIMEOUT_MS =
|
|
99831
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
99825
99832
|
const DEFAULT_POLL_MS = 50;
|
|
99826
99833
|
const DEFAULT_STALE_MS = 5e3;
|
|
99827
99834
|
/**
|
|
99835
|
+
* Error thrown when the lock cannot be acquired within the timeout.
|
|
99836
|
+
*/
|
|
99837
|
+
var LockAcquisitionError = class extends Error {
|
|
99838
|
+
constructor(lockPath, timeoutMs) {
|
|
99839
|
+
super(`Failed to acquire lock at ${lockPath} within ${timeoutMs}ms. Another process may be holding the lock. If this persists, delete the lock directory manually and retry.`);
|
|
99840
|
+
this.name = "LockAcquisitionError";
|
|
99841
|
+
}
|
|
99842
|
+
};
|
|
99843
|
+
/**
|
|
99828
99844
|
* Execute `fn` while holding a lockfile.
|
|
99829
99845
|
*
|
|
99830
99846
|
* The lock is a directory at `lockPath` (typically `<target-file>.lock`).
|
|
99831
99847
|
* Concurrent callers will wait up to `timeoutMs` for the lock, polling
|
|
99832
99848
|
* every `pollMs`. Stale locks older than `staleMs` are broken automatically.
|
|
99833
99849
|
*
|
|
99834
|
-
* If the lock cannot be acquired
|
|
99835
|
-
* This ensures
|
|
99850
|
+
* If the lock cannot be acquired within the timeout, a LockAcquisitionError
|
|
99851
|
+
* is thrown. This ensures mutual exclusion is never silently bypassed, which
|
|
99852
|
+
* prevents data loss from concurrent writes.
|
|
99836
99853
|
*
|
|
99837
99854
|
* @param lockPath - Path to use as the lock directory (e.g., "/path/to/ids.yml.lock")
|
|
99838
99855
|
* @param fn - Critical section to execute under the lock
|
|
99839
99856
|
* @param options - Timing parameters for lock acquisition
|
|
99840
99857
|
* @returns The return value of `fn`
|
|
99858
|
+
* @throws LockAcquisitionError if the lock cannot be acquired within the timeout
|
|
99841
99859
|
*
|
|
99842
99860
|
* @example
|
|
99843
99861
|
* ```ts
|
|
@@ -99873,10 +99891,11 @@ async function withLockfile(lockPath, fn, options) {
|
|
|
99873
99891
|
}
|
|
99874
99892
|
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
99875
99893
|
}
|
|
99894
|
+
if (!acquired) throw new LockAcquisitionError(lockPath, timeoutMs);
|
|
99876
99895
|
try {
|
|
99877
99896
|
return await fn();
|
|
99878
99897
|
} finally {
|
|
99879
|
-
|
|
99898
|
+
try {
|
|
99880
99899
|
await rmdir(lockPath);
|
|
99881
99900
|
} catch {}
|
|
99882
99901
|
}
|
|
@@ -100033,8 +100052,8 @@ async function loadIdMapping(baseDir) {
|
|
|
100033
100052
|
* commands run in parallel.
|
|
100034
100053
|
*
|
|
100035
100054
|
* The merge is safe because ID mappings are append-only — entries are never
|
|
100036
|
-
* intentionally removed.
|
|
100037
|
-
*
|
|
100055
|
+
* intentionally removed. If the lock cannot be acquired within the timeout,
|
|
100056
|
+
* a LockAcquisitionError is thrown rather than proceeding without protection.
|
|
100038
100057
|
*/
|
|
100039
100058
|
async function saveIdMapping(baseDir, mapping) {
|
|
100040
100059
|
const filePath = getMappingPath(baseDir);
|
|
@@ -104603,11 +104622,18 @@ var DoctorHandler = class extends BaseCommand {
|
|
|
104603
104622
|
healthChecks.push(await this.checkIdMappingDuplicates(options.fix));
|
|
104604
104623
|
healthChecks.push(await this.checkTempFiles(options.fix));
|
|
104605
104624
|
healthChecks.push(this.checkIssueValidity(this.issues));
|
|
104625
|
+
healthChecks.push(await this.checkWorktree(options.fix));
|
|
104626
|
+
const dataLocationResult = await this.checkDataLocation(options.fix);
|
|
104627
|
+
healthChecks.push(dataLocationResult);
|
|
104628
|
+
if (dataLocationResult.status === "ok" && dataLocationResult.message?.includes("migrated")) {
|
|
104629
|
+
this.dataSyncDir = await resolveDataSyncDir(this.cwd);
|
|
104630
|
+
try {
|
|
104631
|
+
this.issues = await listIssues(this.dataSyncDir);
|
|
104632
|
+
} catch {}
|
|
104633
|
+
}
|
|
104606
104634
|
const parsedMaxHistory = options.maxHistory ? parseInt(options.maxHistory, 10) : 50;
|
|
104607
104635
|
const maxHistory = Number.isNaN(parsedMaxHistory) || parsedMaxHistory < 0 ? 50 : parsedMaxHistory;
|
|
104608
104636
|
healthChecks.push(await this.checkMissingMappings(options.fix, maxHistory));
|
|
104609
|
-
healthChecks.push(await this.checkWorktree(options.fix));
|
|
104610
|
-
healthChecks.push(await this.checkDataLocation(options.fix));
|
|
104611
104637
|
healthChecks.push(await this.checkLocalSyncBranch());
|
|
104612
104638
|
healthChecks.push(await this.checkRemoteSyncBranch());
|
|
104613
104639
|
healthChecks.push(await this.checkLocalVsRemoteData());
|