baton-issue-tracker 1.8.0 → 1.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/package.json +1 -1
- package/source/cli.js +8 -0
- package/source/commands/reject.js +99 -0
- package/source/commands/submit.js +76 -0
- package/source/services/issuesService.js +36 -7
package/package.json
CHANGED
package/source/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { run as runNext } from './commands/next.js';
|
|
|
18
18
|
import { run as runLoop } from './commands/loop.js';
|
|
19
19
|
import { run as runStatus } from './commands/status.js';
|
|
20
20
|
import { run as runApprove } from './commands/approve.js';
|
|
21
|
+
import { run as runReject } from './commands/reject.js';
|
|
21
22
|
import { wantsHelp } from './util.js';
|
|
22
23
|
import { run as runView} from './commands/view.js';
|
|
23
24
|
import { run as runSearch } from './commands/search.js';
|
|
@@ -28,6 +29,7 @@ import { run as runDelete } from './commands/delete.js';
|
|
|
28
29
|
import { run as runPriority } from './commands/priority.js';
|
|
29
30
|
import { run as runLog } from './commands/log.js';
|
|
30
31
|
import { run as runRegister } from './commands/register.js';
|
|
32
|
+
import { run as runSubmit } from './commands/submit.js';
|
|
31
33
|
|
|
32
34
|
import { authenticateContext } from './services/authService.js';
|
|
33
35
|
|
|
@@ -47,6 +49,7 @@ Commands:
|
|
|
47
49
|
list Lists issues filtered by status and priority
|
|
48
50
|
create Creates an issue with specified fields
|
|
49
51
|
approve Move an issue from in-review to closed
|
|
52
|
+
submit Submit finished work for human review
|
|
50
53
|
priority Set an issue's priority level
|
|
51
54
|
update Updates an issue's specified fields
|
|
52
55
|
delete Deletes an issue
|
|
@@ -78,6 +81,8 @@ Options:
|
|
|
78
81
|
create --token-limit <n> Optional token budget for this issue
|
|
79
82
|
create --json Output as JSON (for AI agents)
|
|
80
83
|
approve <id> [--json]
|
|
84
|
+
submit <id> [--json]
|
|
85
|
+
reject <id> --reason <text> Reject an issue with a given reason
|
|
81
86
|
priority <id> <level> [--json] low | medium | high
|
|
82
87
|
update --title <text> New title
|
|
83
88
|
update --description <text> New description
|
|
@@ -105,6 +110,7 @@ Examples:
|
|
|
105
110
|
baton create --title "Fix login bug" --priority high
|
|
106
111
|
baton create --title "Refactor auth" --description "Clean up JWT logic" --token-limit 4000
|
|
107
112
|
baton approve 5
|
|
113
|
+
baton submit 14
|
|
108
114
|
baton priority 5 high
|
|
109
115
|
baton priority 3 low
|
|
110
116
|
baton update 3 --title "Revised title"
|
|
@@ -138,11 +144,13 @@ async function main() {
|
|
|
138
144
|
search: () => runSearch(args),
|
|
139
145
|
list: () => runList(args),
|
|
140
146
|
approve: () => runApprove(args),
|
|
147
|
+
reject: () => runReject(args),
|
|
141
148
|
priority: () => runPriority(args),
|
|
142
149
|
create: () => runCreate(args),
|
|
143
150
|
update: () => runUpdate(args),
|
|
144
151
|
delete: () => runDelete(args),
|
|
145
152
|
log: () => runLog(args),
|
|
153
|
+
submit: () => runSubmit(args),
|
|
146
154
|
};
|
|
147
155
|
|
|
148
156
|
const handler = handlers[command];
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// reject.js
|
|
2
|
+
// supports rejecting an issue.
|
|
3
|
+
//
|
|
4
|
+
// AI Was used in generating this file.
|
|
5
|
+
// Usage:
|
|
6
|
+
// baton reject <id> [options]
|
|
7
|
+
//
|
|
8
|
+
// Options:
|
|
9
|
+
// --reason <text> Reason for rejection (required)
|
|
10
|
+
// --json Output in JSON format
|
|
11
|
+
// -h, --help Show this help
|
|
12
|
+
//
|
|
13
|
+
// Examples:
|
|
14
|
+
// baton reject 5 --reason "Needs more detail"
|
|
15
|
+
|
|
16
|
+
import { rejectIssue, getIssue } from '../services/issuesService.js';
|
|
17
|
+
import { Status } from '../models/issue.js';
|
|
18
|
+
import { hasFlag, getFlagValue, wantsHelp, renderOutput, renderError, serializeIssue } from '../util.js';
|
|
19
|
+
|
|
20
|
+
const USAGE = "Usage:\n baton reject <id> [options]\n\nOptions:\n --reason <text> Reason for rejection (required)\n --json Output in JSON format\n -h, --help Show this help";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Rejects an issue for a specified ID.
|
|
24
|
+
*
|
|
25
|
+
* @param {string[]} args - The command line arguments
|
|
26
|
+
* @returns {Promise<number>} The exit code: 0 is success, 1 is error.
|
|
27
|
+
*/
|
|
28
|
+
export async function run(args) {
|
|
29
|
+
const isJson = hasFlag(args, '--json');
|
|
30
|
+
|
|
31
|
+
// (0) Help check
|
|
32
|
+
if (wantsHelp(args)) {
|
|
33
|
+
console.log(USAGE);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// (1) Parse arguments
|
|
38
|
+
const idArgs = args.filter(arg => !arg.startsWith('-'));
|
|
39
|
+
if (idArgs.length === 0) {
|
|
40
|
+
renderError(isJson, `No ID provided.\n${USAGE}`, 'MISSING_ID');
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const id = Number(idArgs[0]);
|
|
45
|
+
if (!Number.isInteger(id)) {
|
|
46
|
+
renderError(isJson, `Invalid ID "${idArgs[0]}". ID must be an integer.`, 'INVALID_ID');
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!hasFlag(args, "--reason")) {
|
|
51
|
+
renderError(isJson, `Missing reason for reject.\n${USAGE}`, 'MISSING_REASON');
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let reasonText;
|
|
56
|
+
try {
|
|
57
|
+
reasonText = getFlagValue(args, "--reason");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
renderError(isJson, error.message, 'INVALID_REASON');
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!reasonText || reasonText.trim() === "") {
|
|
64
|
+
renderError(isJson, "Reason for rejection cannot be empty.", 'EMPTY_REASON');
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let issue;
|
|
69
|
+
try {
|
|
70
|
+
issue = await getIssue(id);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error.message.includes("not found")) {
|
|
73
|
+
renderError(isJson, error.message, 'NOT_FOUND');
|
|
74
|
+
} else {
|
|
75
|
+
renderError(isJson, error.message);
|
|
76
|
+
}
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (issue.status !== Status.IN_REVIEW) {
|
|
81
|
+
renderError(isJson, `Issue #${id} is currently "${issue.status}". Only issues in "${Status.IN_REVIEW}" status can be rejected.`, 'INVALID_STATE');
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// (3) Perform rejection
|
|
87
|
+
const updatedIssue = await rejectIssue(id, reasonText);
|
|
88
|
+
const envelope = { status: 'success', issue: serializeIssue(updatedIssue) };
|
|
89
|
+
|
|
90
|
+
renderOutput(isJson, envelope, () => {
|
|
91
|
+
console.log(`Issue #${id} rejected successfully and moved back to "${updatedIssue.status}".`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return 0;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
renderError(isJson, error.message);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// submit.js
|
|
2
|
+
// AI was consulted to generate the contents of the file. It was then reviewed and edited by a human.
|
|
3
|
+
// Allows an agent to submit "finished" work for human review.
|
|
4
|
+
// The issue must be in the in-progress status to be submitted for review.
|
|
5
|
+
// Usage: baton submit <id> [--json]
|
|
6
|
+
//
|
|
7
|
+
// Options:
|
|
8
|
+
// --json Output as JSON (for AI agents)
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// baton submit 14
|
|
12
|
+
|
|
13
|
+
import { getIssue, submitForReview } from '../services/issuesService.js';
|
|
14
|
+
import { Status } from '../models/issue.js';
|
|
15
|
+
import {
|
|
16
|
+
hasFlag,
|
|
17
|
+
renderOutput,
|
|
18
|
+
renderError,
|
|
19
|
+
serializeIssue,
|
|
20
|
+
} from '../util.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Submits an in-progress issue for human review.
|
|
24
|
+
* @param {string[]} args - The command line arguments
|
|
25
|
+
* @returns {Promise<number>} The exit code: 0 is success, 1 is error
|
|
26
|
+
*/
|
|
27
|
+
export async function run(args) {
|
|
28
|
+
const isJson = hasFlag(args, '--json');
|
|
29
|
+
|
|
30
|
+
const idArgs = args.filter((arg) => arg !== '--json');
|
|
31
|
+
if (idArgs.length === 0) {
|
|
32
|
+
renderError(isJson, 'Missing issue ID.\nUsage: baton submit <id>', 'MISSING_ID');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const id = Number(idArgs[0]);
|
|
37
|
+
if (!Number.isInteger(id)) {
|
|
38
|
+
renderError(isJson, `Invalid ID "${idArgs[0]}". ID must be an integer.`, 'INVALID_ID');
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let issue;
|
|
43
|
+
try {
|
|
44
|
+
issue = getIssue(id);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.message.includes('not found')) {
|
|
47
|
+
renderError(isJson, error.message, 'NOT_FOUND');
|
|
48
|
+
} else {
|
|
49
|
+
renderError(isJson, error.message);
|
|
50
|
+
}
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (issue.status !== Status.IN_PROGRESS) {
|
|
55
|
+
renderError(
|
|
56
|
+
isJson,
|
|
57
|
+
`Issue #${id} is currently "${issue.status}". Only issues in "${Status.IN_PROGRESS}" can be submitted for review.`,
|
|
58
|
+
'INVALID_STATE',
|
|
59
|
+
);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const updatedIssue = submitForReview(id);
|
|
65
|
+
const envelope = { status: 'success', issue: serializeIssue(updatedIssue) };
|
|
66
|
+
|
|
67
|
+
renderOutput(isJson, envelope, () => {
|
|
68
|
+
console.log(`Success: Issue #${id} submitted for review.`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
renderError(isJson, error.message);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -33,6 +33,14 @@ function logActivity(db, issueId, action, details = null) {
|
|
|
33
33
|
.run();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Returns the active actor ID set during authentication.
|
|
38
|
+
* @returns {number|null}
|
|
39
|
+
*/
|
|
40
|
+
export function getActiveActor() {
|
|
41
|
+
return currentActorId;
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Convert a raw database row to an Issue instance.
|
|
38
46
|
* @private
|
|
@@ -280,16 +288,37 @@ export function rejectIssue(id, reason) {
|
|
|
280
288
|
}
|
|
281
289
|
|
|
282
290
|
/**
|
|
283
|
-
* Change the status of an issue to in-review.
|
|
284
|
-
* Logs a
|
|
285
|
-
* @param {number}
|
|
291
|
+
* Change the status of an issue from in-progress to in-review.
|
|
292
|
+
* Logs a state_change event attributed to the current actor.
|
|
293
|
+
* @param {number} issueId
|
|
286
294
|
* @returns {Issue}
|
|
295
|
+
* @throws {Error} If issueId is invalid, issue is not found, or status is not in-progress
|
|
287
296
|
*/
|
|
288
|
-
export function submitForReview(
|
|
297
|
+
export function submitForReview(issueId) {
|
|
298
|
+
if (!Number.isInteger(issueId)) {
|
|
299
|
+
throw new Error('issueId must be an integer');
|
|
300
|
+
}
|
|
301
|
+
|
|
289
302
|
const db = getDB();
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
const existing = findById(db, issueId);
|
|
304
|
+
|
|
305
|
+
if (existing.status !== Status.IN_PROGRESS) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Issue #${issueId} is currently "${existing.status}". Only issues in "${Status.IN_PROGRESS}" can be submitted for review.`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
db.update(issuesTable)
|
|
312
|
+
.set({ status: Status.IN_REVIEW })
|
|
313
|
+
.where(eq(issuesTable.id, issueId))
|
|
314
|
+
.run();
|
|
315
|
+
logActivity(
|
|
316
|
+
db,
|
|
317
|
+
issueId,
|
|
318
|
+
Action.STATE_CHANGE,
|
|
319
|
+
`Issue #${issueId} was submitted for review.`,
|
|
320
|
+
);
|
|
321
|
+
return getIssue(issueId);
|
|
293
322
|
}
|
|
294
323
|
|
|
295
324
|
/**
|