@nickmeriano/task 0.6.0 → 0.7.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 +41 -0
- package/dist/claim.d.ts +76 -0
- package/dist/claim.d.ts.map +1 -0
- package/dist/claim.js +237 -0
- package/dist/claim.js.map +1 -0
- package/dist/claim.test.d.ts +15 -0
- package/dist/claim.test.d.ts.map +1 -0
- package/dist/claim.test.js +185 -0
- package/dist/claim.test.js.map +1 -0
- package/dist/cli.js +85 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +22 -0
- package/src/claim.test.ts +242 -0
- package/src/claim.ts +302 -0
- package/src/cli.ts +78 -1
- package/src/index.ts +12 -0
- package/src/types.ts +8 -0
package/README.md
CHANGED
|
@@ -54,6 +54,47 @@ task serve # opens the kanban + table UI, live
|
|
|
54
54
|
task publish # same board, at a URL you can open on a phone
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
## Claiming — so two workers never pick up the same ticket
|
|
58
|
+
|
|
59
|
+
When agents (or agents *and* you) work one backlog, picking up a ticket needs
|
|
60
|
+
a lock. `task claim` is that lock, built out of things git already guarantees:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
task claim TAS-21 # claim it: branch + status flip + push
|
|
64
|
+
task list --claimable # the queue: what a worker may pick up next
|
|
65
|
+
task claim --release TAS-21 # abandon a claim cleanly
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **The claim is the work branch.** `task claim TAS-21` branches
|
|
69
|
+
`task/claim/tas-21` off origin's default branch, flips the ticket to
|
|
70
|
+
`in_progress` as the branch's first commit, and pushes. The branch existing
|
|
71
|
+
on origin *is* the claim — nothing side-band to clean up, because the branch
|
|
72
|
+
was needed anyway and dies at merge.
|
|
73
|
+
- **The namespace is yours.** `task/claim/` is only the default —
|
|
74
|
+
`"claimPrefix"` in `.task/config.json` moves the whole namespace, and it
|
|
75
|
+
lives in the committed config on purpose: every worker and every clone must
|
|
76
|
+
agree on what "claimed" looks like, or there is no lock. If your workers are
|
|
77
|
+
Claude Code cloud sessions, set `"claimPrefix": "claude/task/"` — that
|
|
78
|
+
runtime can push `claude/`-prefixed branches without extra ceremony. Pick
|
|
79
|
+
the value before the first claim; renaming later strands in-flight claims.
|
|
80
|
+
- **Atomic by construction.** The push only succeeds if the branch doesn't
|
|
81
|
+
exist yet (a compare-and-swap on the ref), so two concurrent claimers of the
|
|
82
|
+
same ticket get exactly one winner. Exit codes are the contract: `0` claimed,
|
|
83
|
+
`1` already claimed (pick the next ticket), `2` not claimable — not `todo`,
|
|
84
|
+
blocked, `--needs-human`, or a dirty working tree.
|
|
85
|
+
- **The lifecycle rides the branch.** Claim = `in_progress`; before the PR is
|
|
86
|
+
marked ready, `task done` + `task update --pr` on the branch; the merge lands
|
|
87
|
+
code and `done` together, and the branch auto-deletes. Between claim and
|
|
88
|
+
merge the default branch still says `todo` — the branch is the truth about
|
|
89
|
+
in-flight work, and `git ls-remote origin 'task/claim/*'` (or your
|
|
90
|
+
configured namespace) lists all of it.
|
|
91
|
+
- **Claim before you work — humans too.** The same verb covers manual work:
|
|
92
|
+
run `task claim <id>` before starting a ticket yourself and any scheduled
|
|
93
|
+
agent will skip it. `task list --claimable` shows `todo` tickets in position
|
|
94
|
+
order minus blocked, needs-human, and already-claimed — the top entry is
|
|
95
|
+
what an autonomous worker takes next, which makes column order your priority
|
|
96
|
+
queue.
|
|
97
|
+
|
|
57
98
|
## From anywhere
|
|
58
99
|
|
|
59
100
|
`task serve` is localhost, which is the right answer right up until you're not
|
package/dist/claim.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `task claim` — atomic ticket claiming, for humans and scheduled agents alike
|
|
3
|
+
* (NIC-7 / TAS-21). The claim *is* the ticket's work branch: one deterministic
|
|
4
|
+
* name per ticket, creating it on origin is claiming it, and git's atomic ref
|
|
5
|
+
* creation is the lock. Branch existence = claimed; the branch dies at merge,
|
|
6
|
+
* so there is no claim state to clean up.
|
|
7
|
+
*
|
|
8
|
+
* The CLI stays board-level and forge-agnostic — everything here is plain git
|
|
9
|
+
* against `origin`. PR/session awareness belongs to whatever drives the claim
|
|
10
|
+
* (the implement-task skill, a human).
|
|
11
|
+
*/
|
|
12
|
+
import type { Store } from "./store.ts";
|
|
13
|
+
import type { ProjectConfig, Task } from "./types.ts";
|
|
14
|
+
/**
|
|
15
|
+
* The branch namespace claims live under when the board doesn't configure
|
|
16
|
+
* one. Deliberately vendor-neutral: repos whose workers are Claude Code cloud
|
|
17
|
+
* sessions set `"claimPrefix": "claude/task/"` in `.task/config.json` — the
|
|
18
|
+
* one namespace that runtime can push without extra ceremony — and any other
|
|
19
|
+
* convention is equally valid. The key is committed with the board so every
|
|
20
|
+
* worker and every clone agree on what "claimed" looks like; a lock two
|
|
21
|
+
* sides spell differently is no lock at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_CLAIM_PREFIX = "task/claim/";
|
|
24
|
+
/**
|
|
25
|
+
* The board's claim namespace: configured `claimPrefix` (a trailing slash is
|
|
26
|
+
* implied) or the default. Validated here because it becomes a git ref and an
|
|
27
|
+
* ls-remote glob — a malformed value must fail the claim, not corrupt it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function claimNamespace(config: ProjectConfig): string;
|
|
30
|
+
/** { prefix: "TAS" }, 21 → "task/claim/tas-21" (or under the configured namespace). */
|
|
31
|
+
export declare function claimBranch(config: ProjectConfig, number: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Why a claim was refused: "claimed" (someone holds the branch — exit 1, pick
|
|
34
|
+
* the next ticket) vs "invalid" (the ticket or the tree isn't claimable —
|
|
35
|
+
* exit 2, fix something).
|
|
36
|
+
*/
|
|
37
|
+
export declare class ClaimError extends Error {
|
|
38
|
+
readonly kind: "claimed" | "invalid";
|
|
39
|
+
constructor(message: string, kind: "claimed" | "invalid");
|
|
40
|
+
}
|
|
41
|
+
/** Every claim branch that exists on origin right now — one network call. */
|
|
42
|
+
export declare function remoteClaims(cwd: string, namespace: string): Set<string>;
|
|
43
|
+
export interface ClaimResult {
|
|
44
|
+
task: Task;
|
|
45
|
+
branch: string;
|
|
46
|
+
base: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Claim `number`: validate, branch off origin's default branch, flip the
|
|
50
|
+
* ticket to in_progress as the branch's first commit, and push. The push
|
|
51
|
+
* carries `--force-with-lease=<branch>:` (empty expectation = "the ref must
|
|
52
|
+
* not exist"), so creating the remote branch is a compare-and-swap: two
|
|
53
|
+
* concurrent claimers of the same ticket, exactly one wins — and a branch
|
|
54
|
+
* someone pre-created without a claim commit can't be hijacked by a plain
|
|
55
|
+
* fast-forward. Leaves the winner checked out on the claim branch.
|
|
56
|
+
*/
|
|
57
|
+
export declare function claim(store: Store, number: number): ClaimResult;
|
|
58
|
+
export interface ReleaseResult {
|
|
59
|
+
branch: string;
|
|
60
|
+
/** Whether a branch was actually there to delete, per side. */
|
|
61
|
+
remote: boolean;
|
|
62
|
+
local: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Abandon a claim cleanly: delete the branch on origin and locally. The status
|
|
66
|
+
* flip only ever existed as a commit on that branch, so deleting it *is* the
|
|
67
|
+
* revert — the default branch never saw in_progress.
|
|
68
|
+
*/
|
|
69
|
+
export declare function release(store: Store, number: number): ReleaseResult;
|
|
70
|
+
/**
|
|
71
|
+
* The dispatcher's queue view: `todo` in position order, minus needs-human,
|
|
72
|
+
* minus blocked, minus tickets whose claim branch already exists on origin
|
|
73
|
+
* (one ls-remote for the whole namespace). The top entry is next up.
|
|
74
|
+
*/
|
|
75
|
+
export declare function claimableTasks(store: Store): Task[];
|
|
76
|
+
//# sourceMappingURL=claim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.d.ts","sourceRoot":"","sources":["../src/claim.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAErD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,gBAAgB,CAAA;AAKjD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAU5D;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAAA;gBACxB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS;CAIzD;AA6CD,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAQxE;AA4BD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,CAkF/D;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,MAAM,EAAE,OAAO,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACf;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CA6BnE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,CAOnD"}
|
package/dist/claim.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `task claim` — atomic ticket claiming, for humans and scheduled agents alike
|
|
3
|
+
* (NIC-7 / TAS-21). The claim *is* the ticket's work branch: one deterministic
|
|
4
|
+
* name per ticket, creating it on origin is claiming it, and git's atomic ref
|
|
5
|
+
* creation is the lock. Branch existence = claimed; the branch dies at merge,
|
|
6
|
+
* so there is no claim state to clean up.
|
|
7
|
+
*
|
|
8
|
+
* The CLI stays board-level and forge-agnostic — everything here is plain git
|
|
9
|
+
* against `origin`. PR/session awareness belongs to whatever drives the claim
|
|
10
|
+
* (the implement-task skill, a human).
|
|
11
|
+
*/
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { FileStore, TICKETS_DIR } from "./file-store.js";
|
|
15
|
+
/**
|
|
16
|
+
* The branch namespace claims live under when the board doesn't configure
|
|
17
|
+
* one. Deliberately vendor-neutral: repos whose workers are Claude Code cloud
|
|
18
|
+
* sessions set `"claimPrefix": "claude/task/"` in `.task/config.json` — the
|
|
19
|
+
* one namespace that runtime can push without extra ceremony — and any other
|
|
20
|
+
* convention is equally valid. The key is committed with the board so every
|
|
21
|
+
* worker and every clone agree on what "claimed" looks like; a lock two
|
|
22
|
+
* sides spell differently is no lock at all.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_CLAIM_PREFIX = "task/claim/";
|
|
25
|
+
/** Slash-terminated path segments of ref-safe characters. */
|
|
26
|
+
const CLAIM_PREFIX_SHAPE = /^([A-Za-z0-9._-]+\/)+$/;
|
|
27
|
+
/**
|
|
28
|
+
* The board's claim namespace: configured `claimPrefix` (a trailing slash is
|
|
29
|
+
* implied) or the default. Validated here because it becomes a git ref and an
|
|
30
|
+
* ls-remote glob — a malformed value must fail the claim, not corrupt it.
|
|
31
|
+
*/
|
|
32
|
+
export function claimNamespace(config) {
|
|
33
|
+
const raw = config.claimPrefix ?? DEFAULT_CLAIM_PREFIX;
|
|
34
|
+
const prefix = raw.endsWith("/") ? raw : `${raw}/`;
|
|
35
|
+
if (!CLAIM_PREFIX_SHAPE.test(prefix) || prefix.includes("..") || /(^|\/)\./.test(prefix)) {
|
|
36
|
+
throw new ClaimError(`invalid claimPrefix in .task/config.json: ${JSON.stringify(raw)} — use slash-separated segments like "task/claim/" or "claude/task/"`, "invalid");
|
|
37
|
+
}
|
|
38
|
+
return prefix;
|
|
39
|
+
}
|
|
40
|
+
/** { prefix: "TAS" }, 21 → "task/claim/tas-21" (or under the configured namespace). */
|
|
41
|
+
export function claimBranch(config, number) {
|
|
42
|
+
return `${claimNamespace(config)}${config.prefix.toLowerCase()}-${number}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Why a claim was refused: "claimed" (someone holds the branch — exit 1, pick
|
|
46
|
+
* the next ticket) vs "invalid" (the ticket or the tree isn't claimable —
|
|
47
|
+
* exit 2, fix something).
|
|
48
|
+
*/
|
|
49
|
+
export class ClaimError extends Error {
|
|
50
|
+
kind;
|
|
51
|
+
constructor(message, kind) {
|
|
52
|
+
super(message);
|
|
53
|
+
this.kind = kind;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function git(cwd, ...args) {
|
|
57
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
58
|
+
if (result.error)
|
|
59
|
+
throw result.error;
|
|
60
|
+
return {
|
|
61
|
+
status: result.status ?? 1,
|
|
62
|
+
stdout: (result.stdout ?? "").trim(),
|
|
63
|
+
stderr: (result.stderr ?? "").trim(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Run git and throw on failure — for the steps that have no soft outcome. */
|
|
67
|
+
function gitMust(cwd, ...args) {
|
|
68
|
+
const result = git(cwd, ...args);
|
|
69
|
+
if (result.status !== 0) {
|
|
70
|
+
throw new Error(`git ${args[0]} failed: ${result.stderr || result.stdout}`);
|
|
71
|
+
}
|
|
72
|
+
return result.stdout;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The base every claim branch starts from: origin's default branch. Resolved
|
|
76
|
+
* from `origin/HEAD` when the clone recorded it, with a main/master fallback
|
|
77
|
+
* for repos wired up by hand (`git remote add` + push never sets origin/HEAD).
|
|
78
|
+
*/
|
|
79
|
+
function defaultBase(cwd) {
|
|
80
|
+
const head = git(cwd, "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD");
|
|
81
|
+
if (head.status === 0)
|
|
82
|
+
return head.stdout.replace(/^refs\/remotes\//, "");
|
|
83
|
+
for (const name of ["main", "master"]) {
|
|
84
|
+
if (git(cwd, "show-ref", "--verify", "--quiet", `refs/remotes/origin/${name}`).status === 0) {
|
|
85
|
+
return `origin/${name}`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw new Error("couldn't resolve origin's default branch — origin/HEAD is unset and neither origin/main nor origin/master exists");
|
|
89
|
+
}
|
|
90
|
+
/** Every claim branch that exists on origin right now — one network call. */
|
|
91
|
+
export function remoteClaims(cwd, namespace) {
|
|
92
|
+
const out = gitMust(cwd, "ls-remote", "--heads", "origin", `${namespace}*`);
|
|
93
|
+
const names = new Set();
|
|
94
|
+
for (const line of out.split("\n")) {
|
|
95
|
+
const ref = line.split("\t")[1];
|
|
96
|
+
if (ref?.startsWith("refs/heads/"))
|
|
97
|
+
names.add(ref.slice("refs/heads/".length));
|
|
98
|
+
}
|
|
99
|
+
return names;
|
|
100
|
+
}
|
|
101
|
+
function remoteBranchExists(cwd, branch) {
|
|
102
|
+
return gitMust(cwd, "ls-remote", "--heads", "origin", branch) !== "";
|
|
103
|
+
}
|
|
104
|
+
/** Blockers still in the way — anything not done or canceled still blocks. */
|
|
105
|
+
function openBlockers(store, task) {
|
|
106
|
+
return task.blockedBy
|
|
107
|
+
.map((n) => store.get(n))
|
|
108
|
+
.filter((b) => b !== null && b.status !== "done" && b.status !== "canceled")
|
|
109
|
+
.map((b) => b.id);
|
|
110
|
+
}
|
|
111
|
+
/** Throws ClaimError("invalid") unless `task` is claimable right now. */
|
|
112
|
+
function assertClaimable(store, task, number) {
|
|
113
|
+
const id = store.displayId(number);
|
|
114
|
+
if (!task)
|
|
115
|
+
throw new ClaimError(`no such task: ${id}`, "invalid");
|
|
116
|
+
if (task.status !== "todo") {
|
|
117
|
+
throw new ClaimError(`${id} is ${task.status} — only todo tickets can be claimed`, "invalid");
|
|
118
|
+
}
|
|
119
|
+
if (task.needsHuman)
|
|
120
|
+
throw new ClaimError(`${id} needs a human — not claimable`, "invalid");
|
|
121
|
+
const blockers = openBlockers(store, task);
|
|
122
|
+
if (blockers.length) {
|
|
123
|
+
throw new ClaimError(`${id} is blocked by ${blockers.join(", ")} — not claimable`, "invalid");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Claim `number`: validate, branch off origin's default branch, flip the
|
|
128
|
+
* ticket to in_progress as the branch's first commit, and push. The push
|
|
129
|
+
* carries `--force-with-lease=<branch>:` (empty expectation = "the ref must
|
|
130
|
+
* not exist"), so creating the remote branch is a compare-and-swap: two
|
|
131
|
+
* concurrent claimers of the same ticket, exactly one wins — and a branch
|
|
132
|
+
* someone pre-created without a claim commit can't be hijacked by a plain
|
|
133
|
+
* fast-forward. Leaves the winner checked out on the claim branch.
|
|
134
|
+
*/
|
|
135
|
+
export function claim(store, number) {
|
|
136
|
+
if (!(store instanceof FileStore)) {
|
|
137
|
+
throw new ClaimError("claiming needs a text-format board — run `task migrate` first", "invalid");
|
|
138
|
+
}
|
|
139
|
+
const cwd = store.root;
|
|
140
|
+
const branch = claimBranch(store.config, number);
|
|
141
|
+
const id = store.displayId(number);
|
|
142
|
+
// The local-branch check comes before ticket validation on purpose: on the
|
|
143
|
+
// claim branch itself the ticket reads in_progress, and "already claimed"
|
|
144
|
+
// (exit 1, move on) is the truthful answer there — not a validation failure.
|
|
145
|
+
const tree = git(cwd, "status", "--porcelain");
|
|
146
|
+
if (tree.status !== 0) {
|
|
147
|
+
throw new ClaimError(`not a git repository: ${cwd}`, "invalid");
|
|
148
|
+
}
|
|
149
|
+
if (git(cwd, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`).status === 0) {
|
|
150
|
+
throw new ClaimError(`${id} is already claimed — ${branch} exists locally (finish it, or \`task claim --release ${id}\`)`, "claimed");
|
|
151
|
+
}
|
|
152
|
+
assertClaimable(store, store.get(number), number);
|
|
153
|
+
if (tree.stdout !== "") {
|
|
154
|
+
throw new ClaimError("working tree is dirty — commit or stash before claiming, the claim switches branches", "invalid");
|
|
155
|
+
}
|
|
156
|
+
gitMust(cwd, "fetch", "--quiet", "origin");
|
|
157
|
+
if (remoteBranchExists(cwd, branch)) {
|
|
158
|
+
throw new ClaimError(`${id} is already claimed — ${branch} exists on origin`, "claimed");
|
|
159
|
+
}
|
|
160
|
+
const base = defaultBase(cwd);
|
|
161
|
+
// So a failed claim can put the checkout back exactly where it was —
|
|
162
|
+
// a branch name usually, a bare sha when HEAD was detached.
|
|
163
|
+
const previous = git(cwd, "symbolic-ref", "--quiet", "--short", "HEAD").stdout ||
|
|
164
|
+
gitMust(cwd, "rev-parse", "HEAD");
|
|
165
|
+
gitMust(cwd, "checkout", "--quiet", "-b", branch, base);
|
|
166
|
+
const undo = () => {
|
|
167
|
+
git(cwd, "checkout", "--quiet", previous);
|
|
168
|
+
git(cwd, "branch", "--quiet", "-D", branch);
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
// Re-validate against the base branch: the pre-checkout validation read
|
|
172
|
+
// whatever happened to be checked out, this one reads the truth the claim
|
|
173
|
+
// will actually be built on.
|
|
174
|
+
assertClaimable(store, store.get(number), number);
|
|
175
|
+
store.update(number, { status: "in_progress" });
|
|
176
|
+
gitMust(cwd, "add", "--", join(store.taskDir, TICKETS_DIR, String(number)));
|
|
177
|
+
gitMust(cwd, "commit", "--quiet", "-m", `chore(board): claim ${id} → in_progress`);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
undo();
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
const push = git(cwd, "push", "--quiet", "-u", "origin", branch, `--force-with-lease=refs/heads/${branch}:`);
|
|
184
|
+
if (push.status !== 0) {
|
|
185
|
+
undo();
|
|
186
|
+
if (remoteBranchExists(cwd, branch)) {
|
|
187
|
+
throw new ClaimError(`${id} is already claimed — ${branch} was just created on origin`, "claimed");
|
|
188
|
+
}
|
|
189
|
+
throw new ClaimError(`${id}: push of ${branch} was rejected (likely a concurrent claim) — ${push.stderr || "no detail from git"}`, "claimed");
|
|
190
|
+
}
|
|
191
|
+
return { task: store.get(number), branch, base };
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Abandon a claim cleanly: delete the branch on origin and locally. The status
|
|
195
|
+
* flip only ever existed as a commit on that branch, so deleting it *is* the
|
|
196
|
+
* revert — the default branch never saw in_progress.
|
|
197
|
+
*/
|
|
198
|
+
export function release(store, number) {
|
|
199
|
+
const cwd = store.root;
|
|
200
|
+
const branch = claimBranch(store.config, number);
|
|
201
|
+
const onBranch = git(cwd, "symbolic-ref", "--quiet", "--short", "HEAD").stdout === branch;
|
|
202
|
+
if (onBranch) {
|
|
203
|
+
const tree = gitMust(cwd, "status", "--porcelain");
|
|
204
|
+
if (tree !== "") {
|
|
205
|
+
throw new ClaimError(`working tree on ${branch} is dirty — commit elsewhere or discard before releasing`, "invalid");
|
|
206
|
+
}
|
|
207
|
+
// Step off the branch so it can be deleted: onto the local default branch
|
|
208
|
+
// when there is one, detached onto the remote base otherwise.
|
|
209
|
+
const base = defaultBase(cwd);
|
|
210
|
+
const local = base.replace(/^origin\//, "");
|
|
211
|
+
if (git(cwd, "show-ref", "--verify", "--quiet", `refs/heads/${local}`).status === 0) {
|
|
212
|
+
gitMust(cwd, "checkout", "--quiet", local);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
gitMust(cwd, "checkout", "--quiet", "--detach", base);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const local = git(cwd, "branch", "--quiet", "-D", branch).status === 0;
|
|
219
|
+
const remote = remoteBranchExists(cwd, branch);
|
|
220
|
+
if (remote)
|
|
221
|
+
gitMust(cwd, "push", "--quiet", "origin", "--delete", branch);
|
|
222
|
+
return { branch, remote, local };
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The dispatcher's queue view: `todo` in position order, minus needs-human,
|
|
226
|
+
* minus blocked, minus tickets whose claim branch already exists on origin
|
|
227
|
+
* (one ls-remote for the whole namespace). The top entry is next up.
|
|
228
|
+
*/
|
|
229
|
+
export function claimableTasks(store) {
|
|
230
|
+
const claimed = remoteClaims(store.root, claimNamespace(store.config));
|
|
231
|
+
return store
|
|
232
|
+
.list({ statuses: ["todo"] })
|
|
233
|
+
.filter((t) => !t.needsHuman)
|
|
234
|
+
.filter((t) => openBlockers(store, t).length === 0)
|
|
235
|
+
.filter((t) => !claimed.has(claimBranch(store.config, t.number)));
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=claim.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.js","sourceRoot":"","sources":["../src/claim.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The properties TAS-21 exists for, checked from the outside — against a real
|
|
3
|
+
* bare "origin" and real CLI subprocesses, because the whole point of claiming
|
|
4
|
+
* is what happens between two independent checkouts:
|
|
5
|
+
*
|
|
6
|
+
* 1. Two concurrent `task claim <same-id>`: exactly one exits 0, the other
|
|
7
|
+
* exits 1 — git's atomic ref creation is the lock.
|
|
8
|
+
* 2. Validation failures exit 2 before anything touches git.
|
|
9
|
+
* 3. `task list --claimable` is the queue: position order, minus blocked,
|
|
10
|
+
* needs-human, and already-claimed tickets.
|
|
11
|
+
* 4. `task claim --release` deletes the branch both sides and the ticket is
|
|
12
|
+
* claimable again.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=claim.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.test.d.ts","sourceRoot":"","sources":["../src/claim.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The properties TAS-21 exists for, checked from the outside — against a real
|
|
3
|
+
* bare "origin" and real CLI subprocesses, because the whole point of claiming
|
|
4
|
+
* is what happens between two independent checkouts:
|
|
5
|
+
*
|
|
6
|
+
* 1. Two concurrent `task claim <same-id>`: exactly one exits 0, the other
|
|
7
|
+
* exits 1 — git's atomic ref creation is the lock.
|
|
8
|
+
* 2. Validation failures exit 2 before anything touches git.
|
|
9
|
+
* 3. `task list --claimable` is the queue: position order, minus blocked,
|
|
10
|
+
* needs-human, and already-claimed tickets.
|
|
11
|
+
* 4. `task claim --release` deletes the branch both sides and the ticket is
|
|
12
|
+
* claimable again.
|
|
13
|
+
*/
|
|
14
|
+
import assert from "node:assert/strict";
|
|
15
|
+
import { test } from "node:test";
|
|
16
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
17
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { initProject } from "./file-store.js";
|
|
23
|
+
const CLI = fileURLToPath(new URL("./cli.ts", import.meta.url));
|
|
24
|
+
function tempDir() {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), "task-claim-test-"));
|
|
26
|
+
process.on("exit", () => rmSync(dir, { recursive: true, force: true }));
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
function sh(cwd, command, ...args) {
|
|
30
|
+
const result = spawnSync(command, args, { cwd, encoding: "utf8" });
|
|
31
|
+
if (result.status !== 0) {
|
|
32
|
+
throw new Error(`${command} ${args.join(" ")} failed: ${result.stderr}`);
|
|
33
|
+
}
|
|
34
|
+
return result.stdout.trim();
|
|
35
|
+
}
|
|
36
|
+
/** The CLI as callers see it — a subprocess with an exit code. */
|
|
37
|
+
function cli(cwd, ...args) {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
execFile(process.execPath, ["--experimental-strip-types", CLI, ...args], { cwd }, (error, stdout, stderr) => {
|
|
40
|
+
resolve({ code: error ? (error.code ?? 1) : 0, stdout, stderr });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A bare origin holding a board with the claimable shapes:
|
|
46
|
+
* 1 "Ready one" todo
|
|
47
|
+
* 2 "Ready two" todo
|
|
48
|
+
* 3 "Blocked" todo, blocked by 1
|
|
49
|
+
* 4 "For a person" todo, needs-human
|
|
50
|
+
* 5 "Not groomed" backlog
|
|
51
|
+
* 6 "Jumped queue" todo, moved to the top of the column after creation —
|
|
52
|
+
* claimable order must be [6, 1, 2], proving position
|
|
53
|
+
* order beats number order.
|
|
54
|
+
*/
|
|
55
|
+
function fixture(claimPrefix) {
|
|
56
|
+
const home = tempDir();
|
|
57
|
+
const bare = join(home, "origin.git");
|
|
58
|
+
sh(home, "git", "init", "--quiet", "--bare", "-b", "main", bare);
|
|
59
|
+
const seed = join(home, "seed");
|
|
60
|
+
sh(home, "git", "clone", "--quiet", bare, seed);
|
|
61
|
+
sh(seed, "git", "config", "user.name", "Test");
|
|
62
|
+
sh(seed, "git", "config", "user.email", "test@example.com");
|
|
63
|
+
const store = initProject(seed, { name: "claim board", prefix: "CLM" });
|
|
64
|
+
if (claimPrefix !== undefined) {
|
|
65
|
+
const configPath = join(seed, ".task", "config.json");
|
|
66
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
67
|
+
config.claimPrefix = claimPrefix;
|
|
68
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
69
|
+
}
|
|
70
|
+
store.create({ title: "Ready one" });
|
|
71
|
+
store.create({ title: "Ready two" });
|
|
72
|
+
store.create({ title: "Blocked", blockedBy: [1] });
|
|
73
|
+
store.create({ title: "For a person", needsHuman: true });
|
|
74
|
+
store.create({ title: "Not groomed", status: "backlog" });
|
|
75
|
+
const jumper = store.create({ title: "Jumped queue", status: "backlog" });
|
|
76
|
+
// A status move without an explicit slot lands on top of the new column.
|
|
77
|
+
store.update(jumper.number, { status: "todo" });
|
|
78
|
+
sh(seed, "git", "add", "-A");
|
|
79
|
+
sh(seed, "git", "commit", "--quiet", "-m", "board");
|
|
80
|
+
sh(seed, "git", "push", "--quiet", "-u", "origin", "main");
|
|
81
|
+
let clones = 0;
|
|
82
|
+
const clone = () => {
|
|
83
|
+
const dir = join(home, `clone-${++clones}`);
|
|
84
|
+
sh(home, "git", "clone", "--quiet", bare, dir);
|
|
85
|
+
sh(dir, "git", "config", "user.name", "Test");
|
|
86
|
+
sh(dir, "git", "config", "user.email", "test@example.com");
|
|
87
|
+
return dir;
|
|
88
|
+
};
|
|
89
|
+
return { bare, clone };
|
|
90
|
+
}
|
|
91
|
+
test("two concurrent claims of one ticket: exactly one wins", async () => {
|
|
92
|
+
const { bare, clone } = fixture();
|
|
93
|
+
const a = clone();
|
|
94
|
+
const b = clone();
|
|
95
|
+
const [ra, rb] = await Promise.all([cli(a, "claim", "1"), cli(b, "claim", "1")]);
|
|
96
|
+
assert.deepEqual([ra.code, rb.code].sort(), [0, 1], `a: ${ra.stderr}\nb: ${rb.stderr}`);
|
|
97
|
+
const winner = ra.code === 0 ? a : b;
|
|
98
|
+
const loser = ra.code === 0 ? rb : ra;
|
|
99
|
+
assert.match(loser.stderr, /already claimed|rejected/);
|
|
100
|
+
// The winner is left on the claim branch, flip committed and pushed.
|
|
101
|
+
assert.equal(sh(winner, "git", "rev-parse", "--abbrev-ref", "HEAD"), "task/claim/clm-1");
|
|
102
|
+
assert.match(readFileSync(join(winner, ".task", "tickets", "1", "ticket.md"), "utf8"), /status: in_progress/);
|
|
103
|
+
assert.match(sh(winner, "git", "ls-remote", "--heads", "origin", "task/claim/clm-1"), /refs\/heads\/task\/claim\/clm-1/);
|
|
104
|
+
// Origin's default branch never saw the flip — the claim rides the branch.
|
|
105
|
+
assert.equal(sh(winner, "git", "diff", "--stat", "origin/main..HEAD", "--", "README.md"), "");
|
|
106
|
+
assert.match(sh(winner, "git", "show", "origin/main:.task/tickets/1/ticket.md"), /status: todo/);
|
|
107
|
+
void bare;
|
|
108
|
+
});
|
|
109
|
+
test("unclaimable tickets exit 2, before anything touches origin", async () => {
|
|
110
|
+
const { clone } = fixture();
|
|
111
|
+
const dir = clone();
|
|
112
|
+
assert.equal((await cli(dir, "claim", "5")).code, 2); // backlog
|
|
113
|
+
assert.equal((await cli(dir, "claim", "3")).code, 2); // blocked
|
|
114
|
+
assert.equal((await cli(dir, "claim", "4")).code, 2); // needs a human
|
|
115
|
+
assert.equal((await cli(dir, "claim", "99")).code, 2); // missing
|
|
116
|
+
assert.match((await cli(dir, "claim", "3")).stderr, /blocked by CLM-1/);
|
|
117
|
+
// A dirty tree refuses too — claiming switches branches.
|
|
118
|
+
writeFileSync(join(dir, "scratch.txt"), "wip");
|
|
119
|
+
const dirty = await cli(dir, "claim", "2");
|
|
120
|
+
assert.equal(dirty.code, 2);
|
|
121
|
+
assert.match(dirty.stderr, /working tree is dirty/);
|
|
122
|
+
rmSync(join(dir, "scratch.txt"));
|
|
123
|
+
assert.equal((await cli(dir, "claim", "2")).code, 0);
|
|
124
|
+
// Nothing above created a stray claim branch for the failed ids.
|
|
125
|
+
const remote = sh(dir, "git", "ls-remote", "--heads", "origin", "task/claim/*");
|
|
126
|
+
assert.deepEqual(remote.split("\n").map((l) => l.split("refs/heads/")[1]), ["task/claim/clm-2"]);
|
|
127
|
+
});
|
|
128
|
+
test("list --claimable is the queue: position order, minus everything unclaimable", async () => {
|
|
129
|
+
const { clone } = fixture();
|
|
130
|
+
const a = clone();
|
|
131
|
+
assert.equal((await cli(a, "claim", "6")).code, 0);
|
|
132
|
+
const b = clone();
|
|
133
|
+
const listed = await cli(b, "list", "--claimable", "--json");
|
|
134
|
+
assert.equal(listed.code, 0);
|
|
135
|
+
const { tasks } = JSON.parse(listed.stdout);
|
|
136
|
+
// 6 claimed, 3 blocked, 4 needs-human, 5 not todo — position order keeps
|
|
137
|
+
// 1 before 2 (creation order within the column).
|
|
138
|
+
assert.deepEqual(tasks.map((t) => t.number), [1, 2]);
|
|
139
|
+
assert.equal((await cli(b, "list", "--claimable", "--status", "todo")).code, 1);
|
|
140
|
+
});
|
|
141
|
+
test("release deletes the branch everywhere and reopens the claim", async () => {
|
|
142
|
+
const { clone } = fixture();
|
|
143
|
+
const dir = clone();
|
|
144
|
+
assert.equal((await cli(dir, "claim", "2")).code, 0);
|
|
145
|
+
assert.equal((await cli(dir, "claim", "2")).code, 1); // locally claimed too
|
|
146
|
+
const released = await cli(dir, "claim", "--release", "2");
|
|
147
|
+
assert.equal(released.code, 0, released.stderr);
|
|
148
|
+
assert.match(released.stdout, /released CLM-2/);
|
|
149
|
+
// Back on the default branch, branch gone on both sides, ticket todo again.
|
|
150
|
+
assert.equal(sh(dir, "git", "rev-parse", "--abbrev-ref", "HEAD"), "main");
|
|
151
|
+
assert.equal(sh(dir, "git", "ls-remote", "--heads", "origin", "task/claim/clm-2"), "");
|
|
152
|
+
assert.match(readFileSync(join(dir, ".task", "tickets", "2", "ticket.md"), "utf8"), /status: todo/);
|
|
153
|
+
assert.equal((await cli(dir, "claim", "2")).code, 0);
|
|
154
|
+
// Releasing an unclaimed ticket is a quiet no-op, not an error.
|
|
155
|
+
const noop = await cli(dir, "claim", "--release", "1");
|
|
156
|
+
assert.equal(noop.code, 0);
|
|
157
|
+
assert.match(noop.stdout, /wasn't claimed/);
|
|
158
|
+
});
|
|
159
|
+
test("a configured claimPrefix moves the whole namespace", async () => {
|
|
160
|
+
const { clone } = fixture("claude/task/");
|
|
161
|
+
const dir = clone();
|
|
162
|
+
assert.equal((await cli(dir, "claim", "1")).code, 0);
|
|
163
|
+
assert.equal(sh(dir, "git", "rev-parse", "--abbrev-ref", "HEAD"), "claude/task/clm-1");
|
|
164
|
+
assert.match(sh(dir, "git", "ls-remote", "--heads", "origin", "claude/task/*"), /refs\/heads\/claude\/task\/clm-1/);
|
|
165
|
+
// Every side of the feature reads the same key: the queue looks for claims
|
|
166
|
+
// under the configured namespace, and release deletes there too.
|
|
167
|
+
const other = clone();
|
|
168
|
+
const listed = await cli(other, "list", "--claimable", "--json");
|
|
169
|
+
const { tasks } = JSON.parse(listed.stdout);
|
|
170
|
+
assert.deepEqual(tasks.map((t) => t.number), [6, 2]);
|
|
171
|
+
assert.equal((await cli(other, "claim", "1")).code, 1);
|
|
172
|
+
assert.equal((await cli(dir, "claim", "--release", "1")).code, 0);
|
|
173
|
+
assert.equal(sh(dir, "git", "ls-remote", "--heads", "origin", "claude/task/*"), "");
|
|
174
|
+
// A missing trailing slash is forgiven; a malformed value fails loudly.
|
|
175
|
+
const bad = clone();
|
|
176
|
+
const configPath = join(bad, ".task", "config.json");
|
|
177
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
178
|
+
config.claimPrefix = "spaces are not refs/";
|
|
179
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
180
|
+
sh(bad, "git", "commit", "--quiet", "-am", "bad prefix");
|
|
181
|
+
const invalid = await cli(bad, "claim", "2");
|
|
182
|
+
assert.equal(invalid.code, 2);
|
|
183
|
+
assert.match(invalid.stderr, /invalid claimPrefix/);
|
|
184
|
+
});
|
|
185
|
+
//# sourceMappingURL=claim.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim.test.js","sourceRoot":"","sources":["../src/claim.test.ts"],"names":[],"mappings":"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"}
|