@nickmeriano/task 0.8.0 → 0.10.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 +137 -22
- package/dist/asks.test.d.ts +17 -0
- package/dist/asks.test.d.ts.map +1 -0
- package/dist/asks.test.js +278 -0
- package/dist/asks.test.js.map +1 -0
- package/dist/check.d.ts +3 -2
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +99 -60
- package/dist/check.js.map +1 -1
- package/dist/check.test.js +99 -60
- package/dist/check.test.js.map +1 -1
- package/dist/claim-io.d.ts +73 -0
- package/dist/claim-io.d.ts.map +1 -0
- package/dist/claim-io.js +344 -0
- package/dist/claim-io.js.map +1 -0
- package/dist/claim.d.ts +108 -23
- package/dist/claim.d.ts.map +1 -1
- package/dist/claim.js +320 -86
- package/dist/claim.js.map +1 -1
- package/dist/claim.test.d.ts +10 -2
- package/dist/claim.test.d.ts.map +1 -1
- package/dist/claim.test.js +282 -96
- package/dist/claim.test.js.map +1 -1
- package/dist/cli.js +346 -68
- package/dist/cli.js.map +1 -1
- package/dist/file-store.d.ts +70 -24
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +319 -141
- package/dist/file-store.js.map +1 -1
- package/dist/git-serve.d.ts +183 -0
- package/dist/git-serve.d.ts.map +1 -0
- package/dist/git-serve.js +503 -0
- package/dist/git-serve.js.map +1 -0
- package/dist/git-serve.test.d.ts +16 -0
- package/dist/git-serve.test.d.ts.map +1 -0
- package/dist/git-serve.test.js +183 -0
- package/dist/git-serve.test.js.map +1 -0
- package/dist/git.d.ts +65 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +114 -0
- package/dist/git.js.map +1 -0
- package/dist/id.d.ts +39 -0
- package/dist/id.d.ts.map +1 -0
- package/dist/id.js +67 -0
- package/dist/id.js.map +1 -0
- package/dist/inbox.d.ts +41 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +56 -0
- package/dist/inbox.js.map +1 -0
- package/dist/overview.d.ts +9 -0
- package/dist/overview.d.ts.map +1 -1
- package/dist/overview.js +19 -11
- package/dist/overview.js.map +1 -1
- package/dist/overview.test.js +6 -5
- package/dist/overview.test.js.map +1 -1
- package/dist/promote.test.d.ts +3 -2
- package/dist/promote.test.d.ts.map +1 -1
- package/dist/promote.test.js +29 -31
- package/dist/promote.test.js.map +1 -1
- package/dist/publish.d.ts +2 -17
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +4 -49
- package/dist/publish.js.map +1 -1
- package/dist/search.d.ts +3 -1
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +15 -2
- package/dist/search.js.map +1 -1
- package/dist/search.test.js +9 -9
- package/dist/search.test.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +218 -21
- package/dist/server.js.map +1 -1
- package/dist/store.d.ts +28 -14
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js.map +1 -1
- package/dist/store.test.js +88 -75
- package/dist/store.test.js.map +1 -1
- package/dist/ticket-doc.d.ts +26 -10
- package/dist/ticket-doc.d.ts.map +1 -1
- package/dist/ticket-doc.js +71 -27
- package/dist/ticket-doc.js.map +1 -1
- package/dist/types.d.ts +51 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +96 -24
- package/src/asks.test.ts +355 -0
- package/src/check.test.ts +129 -72
- package/src/check.ts +101 -58
- package/src/claim-io.ts +401 -0
- package/src/claim.test.ts +372 -101
- package/src/claim.ts +418 -99
- package/src/cli.ts +370 -67
- package/src/file-store.ts +357 -145
- package/src/git-serve.test.ts +240 -0
- package/src/git-serve.ts +595 -0
- package/src/git.ts +141 -0
- package/src/id.ts +68 -0
- package/src/inbox.ts +77 -0
- package/src/overview.test.ts +6 -5
- package/src/overview.ts +25 -10
- package/src/promote.test.ts +40 -28
- package/src/publish.ts +6 -53
- package/src/search.test.ts +11 -11
- package/src/search.ts +17 -3
- package/src/server.ts +221 -19
- package/src/store.test.ts +95 -77
- package/src/store.ts +28 -13
- package/src/ticket-doc.ts +93 -30
- package/src/types.ts +52 -17
- package/ui/dist/assets/index-BjsorZOU.js +229 -0
- package/ui/dist/assets/index-CoKCUYic.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-BJmOsOdR.js +0 -229
- package/ui/dist/assets/index-BoqQlqSU.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
// task init
|
|
7
7
|
// task add "Wire up webhooks" --tags api,infra --goal launch
|
|
8
8
|
// task list --status todo,in_progress
|
|
9
|
-
// task move TAS-
|
|
9
|
+
// task move TAS-x7k4m done
|
|
10
10
|
// task serve
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { readFileSync } from "node:fs";
|
|
13
|
-
import { basename, join } from "node:path";
|
|
13
|
+
import { basename, join, relative, sep } from "node:path";
|
|
14
14
|
import process from "node:process";
|
|
15
15
|
import { resolveAuthor } from "./author.js";
|
|
16
16
|
import { checkBoards, checkRoot } from "./check.js";
|
|
17
|
-
import { ClaimError, claim, claimNext, claimableTasks, promote, release, selectionBoards, } from "./claim.js";
|
|
17
|
+
import { ClaimError, claim, claimLockOnly, claimNext, claimableTasks, promote, release, selectionBoards, sweep, } from "./claim.js";
|
|
18
|
+
import { addAskRouted, setAskResolvedRouted } from "./claim-io.js";
|
|
19
|
+
import { buildInbox } from "./inbox.js";
|
|
18
20
|
import { STALE_DAYS, buildOverview } from "./overview.js";
|
|
19
21
|
import { searchStore } from "./search.js";
|
|
20
22
|
import { detectRepo, parseSlug, publish, resolveHost } from "./publish.js";
|
|
@@ -45,7 +47,6 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
45
47
|
"version",
|
|
46
48
|
"yes",
|
|
47
49
|
"needs-human",
|
|
48
|
-
"no-needs-human",
|
|
49
50
|
"archived",
|
|
50
51
|
"claimable",
|
|
51
52
|
"release",
|
|
@@ -58,6 +59,8 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
58
59
|
"fix",
|
|
59
60
|
"next",
|
|
60
61
|
"force",
|
|
62
|
+
"reopen",
|
|
63
|
+
"lock-only",
|
|
61
64
|
]);
|
|
62
65
|
/**
|
|
63
66
|
* Value flags that may repeat — `--board NIC --board TAS` accumulates in
|
|
@@ -65,14 +68,26 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
65
68
|
* last-one-wins.
|
|
66
69
|
*/
|
|
67
70
|
const REPEATABLE_FLAGS = new Set(["board"]);
|
|
71
|
+
/**
|
|
72
|
+
* Repeatable flags whose values are free text — collected verbatim into a
|
|
73
|
+
* list, never comma-joined, because an ask like "check A, then B" must
|
|
74
|
+
* survive as one ask.
|
|
75
|
+
*/
|
|
76
|
+
const MULTI_FLAGS = new Set(["ask"]);
|
|
68
77
|
function parseArgs(argv) {
|
|
69
78
|
const positional = [];
|
|
70
79
|
const flags = {};
|
|
80
|
+
const multi = {};
|
|
71
81
|
for (let i = 0; i < argv.length; i++) {
|
|
72
82
|
const arg = argv[i];
|
|
73
83
|
if (arg.startsWith("--")) {
|
|
74
84
|
const name = arg.slice(2);
|
|
75
85
|
const set = (key, value) => {
|
|
86
|
+
if (MULTI_FLAGS.has(key)) {
|
|
87
|
+
;
|
|
88
|
+
(multi[key] ??= []).push(value);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
76
91
|
const prior = flags[key];
|
|
77
92
|
flags[key] =
|
|
78
93
|
REPEATABLE_FLAGS.has(key) && typeof prior === "string" ? `${prior},${value}` : value;
|
|
@@ -92,7 +107,7 @@ function parseArgs(argv) {
|
|
|
92
107
|
positional.push(arg);
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
|
-
return { positional, flags };
|
|
110
|
+
return { positional, flags, multi };
|
|
96
111
|
}
|
|
97
112
|
function str(flags, name) {
|
|
98
113
|
const v = flags[name];
|
|
@@ -108,18 +123,18 @@ function openStore() {
|
|
|
108
123
|
fail("no .task directory found in this directory or any parent — run `task init` first");
|
|
109
124
|
return openBoard(root);
|
|
110
125
|
}
|
|
111
|
-
/** The prefix a ref carries, if any: "TAS-
|
|
126
|
+
/** The prefix a ref carries, if any: "TAS-x7k4m" → "TAS", "x7k4m" → null. */
|
|
112
127
|
function refPrefix(ref) {
|
|
113
|
-
const match = /^([A-Za-z0-9]+)
|
|
128
|
+
const match = /^([A-Za-z0-9]+)-[A-Za-z0-9]+$/.exec(ref.trim());
|
|
114
129
|
return match ? match[1].toUpperCase() : null;
|
|
115
130
|
}
|
|
116
131
|
/**
|
|
117
|
-
* The board a ref belongs to. A bare
|
|
132
|
+
* The board a ref belongs to. A bare key means the nearest board, as ever —
|
|
118
133
|
* but a prefixed id is an address, and it routes: if the prefix isn't the
|
|
119
134
|
* nearest board's, every board in the repo (walking up to the outermost board
|
|
120
|
-
* root, then down) is searched for it, so `task show TAS-
|
|
135
|
+
* root, then down) is searched for it, so `task show TAS-x7k4m` works from
|
|
121
136
|
* anywhere in a monorepo. What this must never do is what it used to: silently
|
|
122
|
-
* strip a foreign prefix and act on the nearest board's ticket of that
|
|
137
|
+
* strip a foreign prefix and act on the nearest board's ticket of that key.
|
|
123
138
|
*/
|
|
124
139
|
function openStoreFor(ref) {
|
|
125
140
|
const prefix = ref ? refPrefix(ref) : null;
|
|
@@ -205,10 +220,6 @@ function patchFromFlags(flags) {
|
|
|
205
220
|
const goal = str(flags, "goal");
|
|
206
221
|
if (goal !== undefined)
|
|
207
222
|
patch.goal = goal.trim() || null;
|
|
208
|
-
if (flags["needs-human"])
|
|
209
|
-
patch.needsHuman = true;
|
|
210
|
-
if (flags["no-needs-human"])
|
|
211
|
-
patch.needsHuman = false;
|
|
212
223
|
// Replace semantics like --tags; `--pr` (append one) is handled per-command
|
|
213
224
|
// because appending needs the task's current list.
|
|
214
225
|
const prs = str(flags, "prs");
|
|
@@ -268,18 +279,27 @@ function cmdInit(args) {
|
|
|
268
279
|
function cmdAdd(args) {
|
|
269
280
|
const title = args.positional.join(" ").trim();
|
|
270
281
|
if (!title)
|
|
271
|
-
fail(`usage: task add <title> [--description …] [--status …] [--tags a,b] [--goal <slug>] [--
|
|
282
|
+
fail(`usage: task add <title> [--description …] [--status …] [--tags a,b] [--goal <slug>] [--ask "<text>"]…`);
|
|
272
283
|
const store = openStore();
|
|
273
284
|
const patch = patchFromFlags(args.flags);
|
|
274
285
|
const pr = str(args.flags, "pr");
|
|
275
286
|
if (pr)
|
|
276
287
|
patch.prs = [...(patch.prs ?? []), pr];
|
|
277
|
-
|
|
288
|
+
let task = store.create({ title, ...patch });
|
|
289
|
+
const asks = (args.multi.ask ?? []).map((a) => a.trim()).filter(Boolean);
|
|
290
|
+
if (asks.length > 0) {
|
|
291
|
+
const author = resolveAuthor(str(args.flags, "author")).name;
|
|
292
|
+
for (const text of asks)
|
|
293
|
+
store.addAsk(task.key, text, author);
|
|
294
|
+
task = store.get(task.key);
|
|
295
|
+
}
|
|
278
296
|
if (args.flags.json) {
|
|
279
297
|
console.log(JSON.stringify({ task }, null, 2));
|
|
280
298
|
}
|
|
281
299
|
else {
|
|
282
300
|
printTask(task);
|
|
301
|
+
for (const ask of task.asks)
|
|
302
|
+
console.log(` ask [${ask.ordinal}] ${ask.text.split("\n")[0]}`);
|
|
283
303
|
}
|
|
284
304
|
}
|
|
285
305
|
/** `--board NIC --board TAS` (or `--board NIC,TAS`) → ["NIC", "TAS"]. */
|
|
@@ -460,7 +480,7 @@ function cmdArchive(args) {
|
|
|
460
480
|
}
|
|
461
481
|
const store = openStore();
|
|
462
482
|
const finished = store.list({ statuses: ["done", "canceled"] });
|
|
463
|
-
const archived = finished.map((t) => store.archive(t.
|
|
483
|
+
const archived = finished.map((t) => store.archive(t.key));
|
|
464
484
|
if (args.flags.json) {
|
|
465
485
|
console.log(JSON.stringify({ archived }, null, 2));
|
|
466
486
|
}
|
|
@@ -490,11 +510,11 @@ function cmdShow(args) {
|
|
|
490
510
|
if (!ref)
|
|
491
511
|
fail("usage: task show <id>");
|
|
492
512
|
const store = openStoreFor(ref);
|
|
493
|
-
const
|
|
494
|
-
const task = store.get(
|
|
513
|
+
const key = store.parseId(ref);
|
|
514
|
+
const task = store.get(key);
|
|
495
515
|
if (!task)
|
|
496
|
-
fail(`no such task: ${store.displayId(
|
|
497
|
-
const comments = store.comments(
|
|
516
|
+
fail(`no such task: ${store.displayId(key)}`);
|
|
517
|
+
const comments = store.comments(key);
|
|
498
518
|
// The goal rides along in full — title *and* description — so an agent
|
|
499
519
|
// picking the ticket up cold inherits the shared context without it being
|
|
500
520
|
// pasted into every ticket. null when unset or when the ref dangles.
|
|
@@ -504,18 +524,27 @@ function cmdShow(args) {
|
|
|
504
524
|
return;
|
|
505
525
|
}
|
|
506
526
|
// A linked task is only as useful as knowing whether it's still in the way.
|
|
507
|
-
const describeLinks = (
|
|
508
|
-
.map((
|
|
509
|
-
const other = store.get(
|
|
510
|
-
return other ? `${other.id} (${other.status})` : store.displayId(
|
|
527
|
+
const describeLinks = (keys) => keys
|
|
528
|
+
.map((k) => {
|
|
529
|
+
const other = store.get(k);
|
|
530
|
+
return other ? `${other.id} (${other.status})` : store.displayId(k);
|
|
511
531
|
})
|
|
512
532
|
.join(", ");
|
|
513
533
|
console.log(`${task.id} ${task.title}`);
|
|
514
534
|
console.log(`status ${task.status}`);
|
|
515
535
|
if (task.archived)
|
|
516
536
|
console.log(`archived yes — \`task unarchive ${task.id}\` to edit`);
|
|
517
|
-
|
|
518
|
-
|
|
537
|
+
const openAsks = task.asks.filter((a) => !a.resolvedAt);
|
|
538
|
+
if (openAsks.length) {
|
|
539
|
+
console.log(`needs a human — ${openAsks.length} open ask${openAsks.length === 1 ? "" : "s"}`);
|
|
540
|
+
}
|
|
541
|
+
for (const ask of task.asks) {
|
|
542
|
+
const state = ask.resolvedAt
|
|
543
|
+
? `✓ resolved${ask.resolvedBy ? ` by ${ask.resolvedBy}` : ""}`
|
|
544
|
+
: `○ open${ask.author ? ` · ${ask.author}` : ""} · ${ask.createdAt.slice(0, 10)}`;
|
|
545
|
+
console.log(`ask [${ask.ordinal}] ${ask.text.split("\n").join("\n ")}`);
|
|
546
|
+
console.log(` ${state}`);
|
|
547
|
+
}
|
|
519
548
|
if (task.tags.length)
|
|
520
549
|
console.log(`tags ${task.tags.join(", ")}`);
|
|
521
550
|
if (task.goal) {
|
|
@@ -548,17 +577,17 @@ function cmdUpdate(args) {
|
|
|
548
577
|
if (!ref)
|
|
549
578
|
fail("usage: task update <id> [--status …] [--title …] …");
|
|
550
579
|
const store = openStoreFor(ref);
|
|
551
|
-
const
|
|
580
|
+
const key = store.parseId(ref);
|
|
552
581
|
const patch = patchFromFlags(args.flags);
|
|
553
582
|
const pr = str(args.flags, "pr");
|
|
554
583
|
if (pr) {
|
|
555
584
|
// Append, dedup — `--pr <url>` is "attach this PR", not "replace the list".
|
|
556
|
-
const current = patch.prs ?? store.get(
|
|
585
|
+
const current = patch.prs ?? store.get(key)?.prs ?? [];
|
|
557
586
|
patch.prs = current.includes(pr) ? current : [...current, pr];
|
|
558
587
|
}
|
|
559
588
|
if (Object.keys(patch).length === 0)
|
|
560
589
|
fail("nothing to update — pass at least one flag");
|
|
561
|
-
const task = store.update(
|
|
590
|
+
const task = store.update(key, patch);
|
|
562
591
|
if (args.flags.json) {
|
|
563
592
|
console.log(JSON.stringify({ task }, null, 2));
|
|
564
593
|
}
|
|
@@ -570,7 +599,7 @@ function cmdMove(args) {
|
|
|
570
599
|
const [ref, status] = args.positional;
|
|
571
600
|
if (!ref || !status)
|
|
572
601
|
fail("usage: task move <id> <status>");
|
|
573
|
-
cmdUpdate({ positional: [ref], flags: { ...args.flags, status } });
|
|
602
|
+
cmdUpdate({ positional: [ref], flags: { ...args.flags, status }, multi: {} });
|
|
574
603
|
}
|
|
575
604
|
/**
|
|
576
605
|
* `task promote <id>` — backlog → todo, the human "build this" call; see
|
|
@@ -602,6 +631,121 @@ function cmdPromote(args) {
|
|
|
602
631
|
throw error;
|
|
603
632
|
}
|
|
604
633
|
}
|
|
634
|
+
/**
|
|
635
|
+
* `task ask <id> <text>` — file one thing a person owes this ticket. The ask
|
|
636
|
+
* is the primitive every human requirement funnels through: it derives the
|
|
637
|
+
* needs-human badge, feeds `task inbox`, and gates `done` until resolved.
|
|
638
|
+
* On a claimed ticket the write lands on the claim branch as a pushed commit
|
|
639
|
+
* (see claim-io.ts) — the worker's done gate has to see debt filed mid-claim.
|
|
640
|
+
*/
|
|
641
|
+
function cmdAsk(args) {
|
|
642
|
+
const [ref, ...rest] = args.positional;
|
|
643
|
+
const text = rest.join(" ").trim();
|
|
644
|
+
if (!ref || !text)
|
|
645
|
+
fail(`usage: task ask <id> "<what a person needs to do>" [--author <who>]`);
|
|
646
|
+
const store = openStoreFor(ref);
|
|
647
|
+
const author = resolveAuthor(str(args.flags, "author")).name;
|
|
648
|
+
const { ask, branch } = addAskRouted(store, store.parseId(ref), text, author);
|
|
649
|
+
if (args.flags.json) {
|
|
650
|
+
console.log(JSON.stringify({ ask, branch }, null, 2));
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
console.log(`asked on ${ask.taskId} [${ask.ordinal}] ${ask.text.split("\n")[0]}`);
|
|
654
|
+
if (branch)
|
|
655
|
+
console.log(`committed and pushed to ${branch} — the claim owns this ticket's asks`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* `task resolve <id> <n>` — check an ask off (`--reopen` puts it back). The
|
|
660
|
+
* ordinal is the one `task show` prints; the optional --comment lands as an
|
|
661
|
+
* attributed comment so the answer travels with the ticket, not just the
|
|
662
|
+
* checkbox. Resolving the last open ask is the moment a ticket usually moves
|
|
663
|
+
* to done — the hint below says so rather than doing it, because finishing a
|
|
664
|
+
* ticket is a statement about the *work*, not the asks.
|
|
665
|
+
*/
|
|
666
|
+
function cmdResolve(args) {
|
|
667
|
+
const [ref, askRef] = args.positional;
|
|
668
|
+
if (!ref || !askRef) {
|
|
669
|
+
fail('usage: task resolve <id> <ordinal> [--comment "<note>"] [--reopen] [--author <who>]');
|
|
670
|
+
}
|
|
671
|
+
const store = openStoreFor(ref);
|
|
672
|
+
const key = store.parseId(ref);
|
|
673
|
+
const author = resolveAuthor(str(args.flags, "author")).name;
|
|
674
|
+
const { ask, branch } = setAskResolvedRouted(store, key, askRef, !args.flags.reopen, author);
|
|
675
|
+
const note = str(args.flags, "comment")?.trim();
|
|
676
|
+
const comment = note
|
|
677
|
+
? store.addComment(key, `${args.flags.reopen ? "Reopened" : "Resolved"} ask [${ask.ordinal}] (${ask.text.split("\n")[0]}): ${note}`, author)
|
|
678
|
+
: null;
|
|
679
|
+
const task = store.get(key);
|
|
680
|
+
// Branch-resolved asks aren't in the local files — count what's still open
|
|
681
|
+
// on the side that was just written.
|
|
682
|
+
const remaining = branch
|
|
683
|
+
? undefined
|
|
684
|
+
: task.asks.filter((a) => !a.resolvedAt).length;
|
|
685
|
+
if (args.flags.json) {
|
|
686
|
+
console.log(JSON.stringify({ ask, branch, comment, openAsks: remaining ?? null }, null, 2));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const verb = args.flags.reopen ? "reopened" : "resolved";
|
|
690
|
+
console.log(`${verb} ${ask.taskId} [${ask.ordinal}] ${ask.text.split("\n")[0]}`);
|
|
691
|
+
if (branch) {
|
|
692
|
+
console.log(`committed and pushed to ${branch} — the worker sees it on its next pull`);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (args.flags.reopen)
|
|
696
|
+
return;
|
|
697
|
+
if (remaining !== undefined && remaining > 0) {
|
|
698
|
+
console.log(`${remaining} ask${remaining === 1 ? "" : "s"} still open on ${ask.taskId}`);
|
|
699
|
+
}
|
|
700
|
+
else if (task.status === "in_progress") {
|
|
701
|
+
console.log(`that was the last open ask — \`task move ${ask.taskId} done\` if the work is finished too`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* `task inbox` — what do I need to do NOW to unblock work: every open ask on
|
|
706
|
+
* effectively-in-progress tickets (file status, or a live claim branch —
|
|
707
|
+
* see inbox.ts), across the repo's boards, oldest owed first. Deliberately
|
|
708
|
+
* cross-board, like `claim --next`: the queue's attention is per-board, a
|
|
709
|
+
* person's isn't.
|
|
710
|
+
*/
|
|
711
|
+
function cmdInbox(args) {
|
|
712
|
+
let entries;
|
|
713
|
+
try {
|
|
714
|
+
const scope = findScopeRoot(process.cwd());
|
|
715
|
+
const boards = selectionBoards(process.cwd(), boardFlag(args)).map((store) => {
|
|
716
|
+
const rel = relative(scope, store.root).split(sep).join("/");
|
|
717
|
+
return { id: rel === "" ? "." : rel, store };
|
|
718
|
+
});
|
|
719
|
+
entries = buildInbox(boards);
|
|
720
|
+
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
if (error instanceof ClaimError)
|
|
723
|
+
fail(error.message);
|
|
724
|
+
throw error;
|
|
725
|
+
}
|
|
726
|
+
if (args.flags.json) {
|
|
727
|
+
console.log(JSON.stringify({ inbox: entries }, null, 2));
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (entries.length === 0) {
|
|
731
|
+
console.log("inbox zero — no open asks on in-progress work");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const open = entries.reduce((sum, e) => sum + e.asks.length, 0);
|
|
735
|
+
console.log(`${open} open ask${open === 1 ? "" : "s"} across ${entries.length} ticket${entries.length === 1 ? "" : "s"}`);
|
|
736
|
+
const age = (iso) => {
|
|
737
|
+
const days = Math.floor((Date.now() - Date.parse(iso)) / 86_400_000);
|
|
738
|
+
return days > 0 ? `${days}d` : "today";
|
|
739
|
+
};
|
|
740
|
+
for (const entry of entries) {
|
|
741
|
+
const via = entry.claim ? ` (claimed: ${entry.claim.branch})` : "";
|
|
742
|
+
console.log(`\n${entry.task.id} ${entry.task.title}${via}`);
|
|
743
|
+
for (const ask of entry.asks) {
|
|
744
|
+
const who = ask.author ? ` · ${ask.author}` : "";
|
|
745
|
+
console.log(` [${ask.ordinal}] ${ask.text.split("\n")[0]} (${age(ask.createdAt)}${who})`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
605
749
|
function cmdComment(args) {
|
|
606
750
|
const [ref, ...rest] = args.positional;
|
|
607
751
|
const body = rest.join(" ").trim();
|
|
@@ -632,8 +776,8 @@ function cmdLink(args, action) {
|
|
|
632
776
|
const store = openStoreFor(ref);
|
|
633
777
|
const relation = blocks ? "blocks" : "blocked_by";
|
|
634
778
|
const target = parseRefOn(store, (blocks ?? blockedBy));
|
|
635
|
-
const
|
|
636
|
-
const task = action === "link" ? store.link(
|
|
779
|
+
const key = store.parseId(ref);
|
|
780
|
+
const task = action === "link" ? store.link(key, relation, target) : store.unlink(key, relation, target);
|
|
637
781
|
if (args.flags.json) {
|
|
638
782
|
console.log(JSON.stringify({ task }, null, 2));
|
|
639
783
|
}
|
|
@@ -645,7 +789,7 @@ function cmdLink(args, action) {
|
|
|
645
789
|
console.log(`${subject} ${verb} blocks ${object}`);
|
|
646
790
|
}
|
|
647
791
|
}
|
|
648
|
-
const CLAIM_USAGE = 'usage: task claim <id> | task claim --next | task claim --release <id> --comment "<why>"';
|
|
792
|
+
const CLAIM_USAGE = 'usage: task claim <id> [--lock-only] | task claim --next | task claim --release <id> --comment "<why>"';
|
|
649
793
|
/**
|
|
650
794
|
* `task claim <id>` / `task claim --next` / `task claim --release <id>` — see
|
|
651
795
|
* claim.ts for the mechanics. Exit codes are the contract callers script
|
|
@@ -661,6 +805,14 @@ function cmdClaim(args) {
|
|
|
661
805
|
}
|
|
662
806
|
else {
|
|
663
807
|
console.log(`claimed ${result.task.id} — on ${result.branch} (from ${result.base}), status in_progress`);
|
|
808
|
+
// The backstop for branch-pinned sessions: a harness that assigned this
|
|
809
|
+
// checkout a branch expects the work to land there, and a plain claim
|
|
810
|
+
// just moved HEAD somewhere else. The skill carries the rule; this line
|
|
811
|
+
// is for the worker that hasn't read it.
|
|
812
|
+
const defaultLocal = result.base.replace(/^origin\//, "");
|
|
813
|
+
if (result.previous !== defaultLocal && result.previous !== result.branch) {
|
|
814
|
+
console.log(`note: this moved you off ${result.previous} — if your work must deliver on ${result.previous}, \`task claim --release ${result.task.id} --comment "…"\` and re-claim with --lock-only`);
|
|
815
|
+
}
|
|
664
816
|
}
|
|
665
817
|
};
|
|
666
818
|
try {
|
|
@@ -670,7 +822,7 @@ function cmdClaim(args) {
|
|
|
670
822
|
// retrying past lost races internally.
|
|
671
823
|
if (ref)
|
|
672
824
|
fail("pass an id or --next, not both");
|
|
673
|
-
if (args.flags.release)
|
|
825
|
+
if (args.flags.release || args.flags["lock-only"])
|
|
674
826
|
fail(CLAIM_USAGE);
|
|
675
827
|
const result = claimNext(selectionBoards(process.cwd(), boardFlag(args)), options);
|
|
676
828
|
if (!result) {
|
|
@@ -688,7 +840,23 @@ function cmdClaim(args) {
|
|
|
688
840
|
if (!ref)
|
|
689
841
|
fail(CLAIM_USAGE);
|
|
690
842
|
const store = openStoreFor(ref);
|
|
691
|
-
const
|
|
843
|
+
const key = store.parseId(ref);
|
|
844
|
+
if (args.flags["lock-only"]) {
|
|
845
|
+
// The branch-pinned session's claim: same lock, no checkout switch —
|
|
846
|
+
// the work delivers on the branch this session was handed, and the flip
|
|
847
|
+
// commit's delivered_by: trailer says so. See claimLockOnly.
|
|
848
|
+
if (args.flags.release)
|
|
849
|
+
fail(CLAIM_USAGE);
|
|
850
|
+
const result = claimLockOnly(store, key, options);
|
|
851
|
+
if (args.flags.json) {
|
|
852
|
+
console.log(JSON.stringify({ task: result.task, branch: result.branch, deliveredBy: result.deliveredBy }, null, 2));
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
console.log(`claimed ${result.task.id} — ${result.branch} on origin (lock only, from ${result.base}), status in_progress`);
|
|
856
|
+
console.log(`checkout untouched — keep working on ${result.deliveredBy} (recorded as delivered_by in the claim commit)`);
|
|
857
|
+
}
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
692
860
|
if (args.flags.release) {
|
|
693
861
|
// A released claim must leave its failure context behind: the branch
|
|
694
862
|
// (and whatever was tried on it) is about to evaporate, so the reason
|
|
@@ -697,28 +865,28 @@ function cmdClaim(args) {
|
|
|
697
865
|
if (!why) {
|
|
698
866
|
fail('task claim --release requires --comment "<why>" — the next worker inherits what was tried');
|
|
699
867
|
}
|
|
700
|
-
const result = release(store,
|
|
868
|
+
const result = release(store, key);
|
|
701
869
|
const commented = result.remote || result.local;
|
|
702
870
|
// After the branch deletion, so the comment survives it: release leaves
|
|
703
871
|
// the checkout on the default branch, and the comment file sits there
|
|
704
872
|
// uncommitted, to ride along with the next commit.
|
|
705
873
|
const comment = commented
|
|
706
|
-
? store.addComment(
|
|
874
|
+
? store.addComment(key, why, resolveAuthor(str(args.flags, "author")).name)
|
|
707
875
|
: null;
|
|
708
876
|
if (args.flags.json) {
|
|
709
877
|
console.log(JSON.stringify({ released: result, comment }, null, 2));
|
|
710
878
|
}
|
|
711
879
|
else if (!commented) {
|
|
712
|
-
console.log(`${store.displayId(
|
|
880
|
+
console.log(`${store.displayId(key)} wasn't claimed — no ${result.branch} to delete`);
|
|
713
881
|
}
|
|
714
882
|
else {
|
|
715
883
|
const where = [result.remote && "origin", result.local && "local"].filter(Boolean);
|
|
716
|
-
console.log(`released ${store.displayId(
|
|
717
|
-
console.log(`left the reason as a comment on ${store.displayId(
|
|
884
|
+
console.log(`released ${store.displayId(key)} — deleted ${result.branch} (${where.join(" and ")})`);
|
|
885
|
+
console.log(`left the reason as a comment on ${store.displayId(key)} — uncommitted, commit it with your next change`);
|
|
718
886
|
}
|
|
719
887
|
return;
|
|
720
888
|
}
|
|
721
|
-
printClaim(claim(store,
|
|
889
|
+
printClaim(claim(store, key, options));
|
|
722
890
|
}
|
|
723
891
|
catch (error) {
|
|
724
892
|
if (error instanceof ClaimError) {
|
|
@@ -728,6 +896,48 @@ function cmdClaim(args) {
|
|
|
728
896
|
throw error;
|
|
729
897
|
}
|
|
730
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* `task sweep` — the janitor: delete every claim branch on origin whose ticket
|
|
901
|
+
* is done, canceled, or missing on the current checkout. Zero judgment on
|
|
902
|
+
* purpose — the trigger belongs to a runner (CI on push-to-main or cron), not
|
|
903
|
+
* an agent. Exit codes: 0 clean (including nothing to do), 1 a deletion was
|
|
904
|
+
* attempted and failed — a failing sweep means this runner can't delete
|
|
905
|
+
* branches either, which is worth an alert.
|
|
906
|
+
*/
|
|
907
|
+
function cmdSweep(args) {
|
|
908
|
+
try {
|
|
909
|
+
const result = sweep(process.cwd());
|
|
910
|
+
if (args.flags.json) {
|
|
911
|
+
console.log(JSON.stringify({ sweep: result }, null, 2));
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
for (const entry of result.swept) {
|
|
915
|
+
console.log(`swept ${entry.branch} — ${entry.reason === "missing" ? `no ticket ${entry.ticket}` : `${entry.ticket} is ${entry.reason}`}`);
|
|
916
|
+
}
|
|
917
|
+
for (const entry of result.kept) {
|
|
918
|
+
console.log(`kept ${entry.branch} — ${entry.ticket} is ${entry.status}`);
|
|
919
|
+
}
|
|
920
|
+
for (const branch of result.skipped) {
|
|
921
|
+
console.log(`skipped ${branch} — not a name \`task claim\` mints (foreign or pre-migration); delete it by hand if it's stale`);
|
|
922
|
+
}
|
|
923
|
+
for (const entry of result.failed) {
|
|
924
|
+
console.error(`error: couldn't delete ${entry.branch} — ${entry.error}`);
|
|
925
|
+
}
|
|
926
|
+
if (!result.swept.length && !result.kept.length && !result.skipped.length && !result.failed.length) {
|
|
927
|
+
console.log("no claim branches on origin — nothing to sweep");
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (result.failed.length)
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
if (error instanceof ClaimError) {
|
|
935
|
+
console.error(`error: ${error.message}`);
|
|
936
|
+
process.exit(2);
|
|
937
|
+
}
|
|
938
|
+
throw error;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
731
941
|
/** `task instructions` — the shipped agent conventions, self-served at runtime. */
|
|
732
942
|
function cmdInstructions() {
|
|
733
943
|
// skill/ ships in the npm package; `../` from both dist/cli.js and
|
|
@@ -746,7 +956,10 @@ function cmdOverview(args) {
|
|
|
746
956
|
const claimed = overview.claimed === null
|
|
747
957
|
? "claimed ? (origin unreachable)"
|
|
748
958
|
: `claimed ${overview.claimed.length}${overview.claimed.length ? ` (${overview.claimed.join(", ")})` : ""}`;
|
|
749
|
-
|
|
959
|
+
const asks = overview.oldestAsk
|
|
960
|
+
? `asks ${overview.openAsks} open (oldest ${overview.oldestAsk.taskId}, ${overview.oldestAsk.createdAt.slice(0, 10)})`
|
|
961
|
+
: "asks 0 open";
|
|
962
|
+
console.log(` ${claimed} ${asks}`);
|
|
750
963
|
if (overview.goals.length) {
|
|
751
964
|
console.log("goals");
|
|
752
965
|
table(overview.goals.map((g) => [
|
|
@@ -946,9 +1159,9 @@ function cmdDelete(args) {
|
|
|
946
1159
|
if (!ref)
|
|
947
1160
|
fail("usage: task delete <id>");
|
|
948
1161
|
const store = openStoreFor(ref);
|
|
949
|
-
const
|
|
950
|
-
store.delete(
|
|
951
|
-
console.log(`deleted ${store.displayId(
|
|
1162
|
+
const key = store.parseId(ref);
|
|
1163
|
+
store.delete(key);
|
|
1164
|
+
console.log(`deleted ${store.displayId(key)}`);
|
|
952
1165
|
}
|
|
953
1166
|
/**
|
|
954
1167
|
* Bind the first free port at or after `first`, the way dev servers do: a board
|
|
@@ -1127,11 +1340,14 @@ subdirectory (it walks up to find .task/, like git).
|
|
|
1127
1340
|
Usage
|
|
1128
1341
|
task init [--name <name>] [--prefix <PREFIX>]
|
|
1129
1342
|
task add <title> [--description <text> | --description-file <path|->]
|
|
1130
|
-
[--status <s>] [--tags <a,b>] [--goal <slug>]
|
|
1343
|
+
[--status <s>] [--tags <a,b>] [--goal <slug>]
|
|
1344
|
+
[--ask "<text>"]…
|
|
1131
1345
|
new tickets land in backlog (the dump list)
|
|
1132
1346
|
unless --status says otherwise;
|
|
1133
1347
|
--description-file reads markdown from a file
|
|
1134
|
-
(or stdin with -) — no shell-quoting fights
|
|
1348
|
+
(or stdin with -) — no shell-quoting fights;
|
|
1349
|
+
--ask (repeatable) files what a person owes
|
|
1350
|
+
the ticket, right at creation
|
|
1135
1351
|
task list [--status <s1,s2>] [--tag <a,b>] [--goal <slug>]
|
|
1136
1352
|
[--needs-human] [--all] [--archived] [--claimable]
|
|
1137
1353
|
task search <query> [--status <s1,s2>] [--tag <a,b>] [--goal <slug>]
|
|
@@ -1145,18 +1361,38 @@ Usage
|
|
|
1145
1361
|
the shared context without chasing it
|
|
1146
1362
|
task update <id> [--title <t>] [--description <text> | --description-file <path|->]
|
|
1147
1363
|
[--status <s>] [--tags <a,b>] [--goal <slug>]
|
|
1148
|
-
[--needs-human | --no-needs-human]
|
|
1149
1364
|
[--pr <url>] [--prs <url1,url2>]
|
|
1150
1365
|
task move <id> <status> change a ticket's status — the one general
|
|
1151
1366
|
status API (backlog todo in_progress done
|
|
1152
|
-
canceled).
|
|
1153
|
-
|
|
1154
|
-
|
|
1367
|
+
canceled). Moving to done is refused while
|
|
1368
|
+
the ticket has open asks: done means nobody
|
|
1369
|
+
owes anything. The named verbs below exist
|
|
1370
|
+
only where a transition does more than write
|
|
1371
|
+
the status field
|
|
1372
|
+
task ask <id> "<text>" file an ask: one thing a person owes this
|
|
1373
|
+
ticket before it can be done. Asks derive the
|
|
1374
|
+
needs-human badge, feed \`task inbox\`, and
|
|
1375
|
+
gate \`done\` — they never block claiming (an
|
|
1376
|
+
agent works the code while a person mints the
|
|
1377
|
+
token; a true precondition is a blocked_by on
|
|
1378
|
+
a thin human ticket instead)
|
|
1379
|
+
task resolve <id> <n> [--comment "<note>"] [--reopen]
|
|
1380
|
+
check ask <n> off (\`task show\` prints the
|
|
1381
|
+
ordinals); the note lands as an attributed
|
|
1382
|
+
comment. --reopen puts a resolved ask back
|
|
1383
|
+
task inbox [--board <P>] what needs YOU, now: every open ask on
|
|
1384
|
+
effectively in-progress tickets — file status
|
|
1385
|
+
in_progress, or a live claim branch on origin
|
|
1386
|
+
(a claimed ticket is in progress whatever the
|
|
1387
|
+
merged file says, and its asks are read from
|
|
1388
|
+
the branch). Cross-board like claim --next;
|
|
1389
|
+
oldest owed first. Backlog/todo asks are out
|
|
1390
|
+
by design — claiming is what makes them yours
|
|
1155
1391
|
task promote <id> backlog → todo, the human "build this" call.
|
|
1156
1392
|
No quality gate — planning happens at claim
|
|
1157
|
-
time. Blocked
|
|
1158
|
-
|
|
1159
|
-
|
|
1393
|
+
time. Blocked tickets still promote —
|
|
1394
|
+
blockers are claim-time gates, reported as a
|
|
1395
|
+
note — so promoting can queue work behind
|
|
1160
1396
|
its blockers. Exit codes: 0 promoted, 2
|
|
1161
1397
|
refused (not a backlog ticket)
|
|
1162
1398
|
task link <id> --blocked-by <id>
|
|
@@ -1164,7 +1400,7 @@ Usage
|
|
|
1164
1400
|
both tasks (A blocked by B ⇔ B blocks A)
|
|
1165
1401
|
task unlink <id> (--blocks <id> | --blocked-by <id>)
|
|
1166
1402
|
task claim <id> [--force] claim a ticket before working it: branch
|
|
1167
|
-
<branchPrefix><prefix>-<
|
|
1403
|
+
<branchPrefix><prefix>-<key> off origin's
|
|
1168
1404
|
default branch, ticket → in_progress as its
|
|
1169
1405
|
first commit, pushed. The namespace defaults
|
|
1170
1406
|
to task/claim/ — set claims.branchPrefix in
|
|
@@ -1177,8 +1413,21 @@ Usage
|
|
|
1177
1413
|
many claim branches exist on origin, claiming
|
|
1178
1414
|
is refused (--force overrides). Exit codes:
|
|
1179
1415
|
0 claimed, 1 already claimed, 2 not claimable
|
|
1180
|
-
(not todo, blocked,
|
|
1181
|
-
|
|
1416
|
+
(not todo, blocked, dirty tree, at the cap)
|
|
1417
|
+
task claim <id> --lock-only [--force]
|
|
1418
|
+
take the lock without touching the checkout:
|
|
1419
|
+
the in_progress flip commit is built against
|
|
1420
|
+
origin's default branch in a temporary index
|
|
1421
|
+
and pushed as the claim branch — same atomic
|
|
1422
|
+
lock, same exit codes, but HEAD, the working
|
|
1423
|
+
tree and the index stay exactly where they
|
|
1424
|
+
are (dirty is fine). For sessions pinned to
|
|
1425
|
+
a provisioned branch (CI, a Claude web
|
|
1426
|
+
session): the lock lives on the claim
|
|
1427
|
+
branch, the work delivers on yours, and the
|
|
1428
|
+
flip commit's delivered_by: trailer records
|
|
1429
|
+
which. --release works on a lock-only claim
|
|
1430
|
+
the same way
|
|
1182
1431
|
task claim --next [--board <P>] [--force]
|
|
1183
1432
|
claim the top claimable ticket in one call,
|
|
1184
1433
|
retrying past lost races internally — the
|
|
@@ -1194,18 +1443,36 @@ Usage
|
|
|
1194
1443
|
the branch, so deleting it is the revert. The
|
|
1195
1444
|
comment is required and lands on the ticket
|
|
1196
1445
|
(attributed like task comment, uncommitted),
|
|
1197
|
-
so the next worker inherits what was tried
|
|
1446
|
+
so the next worker inherits what was tried.
|
|
1447
|
+
The deletion is verified with ls-remote: an
|
|
1448
|
+
environment that silently drops deletion
|
|
1449
|
+
pushes fails loudly instead of reporting a
|
|
1450
|
+
release that didn't happen
|
|
1451
|
+
task sweep the janitor: delete every claim branch on
|
|
1452
|
+
origin whose ticket is done, canceled, or
|
|
1453
|
+
missing on the current checkout, across all
|
|
1454
|
+
the repo's boards. Scoped to names \`claim\`
|
|
1455
|
+
mints (<prefix>-<valid key>): anything else
|
|
1456
|
+
under the namespace — pre-migration numeric
|
|
1457
|
+
claims, hand-made branches — is reported and
|
|
1458
|
+
left alone, since it may be an open PR's
|
|
1459
|
+
head. Pure git, zero judgment — run it from
|
|
1460
|
+
a runner that can delete remote branches (CI
|
|
1461
|
+
on push-to-main or cron) to heal stale
|
|
1462
|
+
claims left by environments that can't.
|
|
1463
|
+
Prints swept/kept/skipped; exits non-zero if
|
|
1464
|
+
a deletion was attempted and failed
|
|
1198
1465
|
task list --claimable [--board <P>]
|
|
1199
1466
|
the claim queue: todo tickets in position
|
|
1200
|
-
order, minus blocked /
|
|
1201
|
-
|
|
1202
|
-
|
|
1467
|
+
order, minus blocked / already claimed on
|
|
1468
|
+
origin. Top entry is next up. Same board
|
|
1469
|
+
scope as claim --next
|
|
1203
1470
|
task instructions print the shipped agent conventions
|
|
1204
1471
|
(skill/SKILL.md) — agents without the skill
|
|
1205
1472
|
installed self-serve the house rules
|
|
1206
1473
|
task overview [--json] one screen of board health: counts per
|
|
1207
1474
|
status, per-goal progress, claimed and
|
|
1208
|
-
|
|
1475
|
+
open-ask counts, stale tickets (untouched
|
|
1209
1476
|
${STALE_DAYS}+ days), oldest open ticket
|
|
1210
1477
|
task comment <id> <text> [--author <who>]
|
|
1211
1478
|
task delete <id>
|
|
@@ -1227,7 +1494,7 @@ Usage
|
|
|
1227
1494
|
task archive <id> move a done/canceled ticket to .task/archive/,
|
|
1228
1495
|
out of the board and off the hot path — still
|
|
1229
1496
|
readable via show and list --archived, and its
|
|
1230
|
-
|
|
1497
|
+
key stays reserved
|
|
1231
1498
|
task archive --all archive everything done or canceled
|
|
1232
1499
|
task unarchive <id> put an archived ticket back on the board
|
|
1233
1500
|
task whoami who your comments are attributed to
|
|
@@ -1259,15 +1526,18 @@ Usage
|
|
|
1259
1526
|
tasks — those are in .task/ either way
|
|
1260
1527
|
|
|
1261
1528
|
Values
|
|
1262
|
-
<id> TAS-
|
|
1263
|
-
|
|
1264
|
-
|
|
1529
|
+
<id> TAS-x7k4m, x7k4m, or any unique prefix of a key (task show x7
|
|
1530
|
+
works, git-style — ambiguity is an error naming the matches).
|
|
1531
|
+
Ids are random 5-character keys, minted branch-safely at
|
|
1532
|
+
\`task add\`. A bare key means the nearest board; a prefixed id
|
|
1533
|
+
routes to whichever board in the repo owns that prefix, so
|
|
1534
|
+
TAS-x7k4m works from anywhere in a monorepo
|
|
1265
1535
|
status ${STATUSES.join(" ")}
|
|
1266
1536
|
--tag a,b matches a task carrying *either* tag
|
|
1267
1537
|
--goal <slug> a task belongs to at most one goal. Goals answer "where is
|
|
1268
1538
|
this going" (one); tags answer "what kind" (many) — something
|
|
1269
1539
|
that wants two goals is a tag
|
|
1270
|
-
--needs-human
|
|
1540
|
+
--needs-human filter: tickets with open asks — something a person owes
|
|
1271
1541
|
--pr <url> attach a pull request (appends); --prs replaces the whole list
|
|
1272
1542
|
clearing --tags "" drops all tags, --goal "" clears it, --prs ""
|
|
1273
1543
|
detaches all PRs
|
|
@@ -1313,8 +1583,16 @@ function main() {
|
|
|
1313
1583
|
return cmdLink(args, "unlink");
|
|
1314
1584
|
case "claim":
|
|
1315
1585
|
return cmdClaim(args);
|
|
1586
|
+
case "sweep":
|
|
1587
|
+
return cmdSweep(args);
|
|
1316
1588
|
case "instructions":
|
|
1317
1589
|
return cmdInstructions();
|
|
1590
|
+
case "ask":
|
|
1591
|
+
return cmdAsk(args);
|
|
1592
|
+
case "resolve":
|
|
1593
|
+
return cmdResolve(args);
|
|
1594
|
+
case "inbox":
|
|
1595
|
+
return cmdInbox(args);
|
|
1318
1596
|
case "overview":
|
|
1319
1597
|
return cmdOverview(args);
|
|
1320
1598
|
case "goal":
|