baton-issue-tracker 1.13.4 → 1.14.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baton-issue-tracker",
3
- "version": "1.13.4",
3
+ "version": "1.14.0",
4
4
  "description": "A CLI issue tracker for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
package/source/cli.js CHANGED
@@ -31,6 +31,7 @@ import { run as runSubmit } from './commands/submit.js';
31
31
  import { run as runUnassign } from './commands/unassign.js';
32
32
  import { run as runUnclaim } from './commands/unclaim.js';
33
33
  import { run as runClaim } from './commands/claim.js';
34
+ import { run as runAssign} from './commands/assign.js';
34
35
 
35
36
  import { authenticateContext } from './services/authService.js';
36
37
 
@@ -163,6 +164,7 @@ async function main() {
163
164
  submit: () => runSubmit(args),
164
165
  unassign: () => runUnassign(args),
165
166
  unclaim: () => runUnclaim(args),
167
+ assign: () => runAssign(args),
166
168
  };
167
169
 
168
170
  const handler = handlers[command];
@@ -0,0 +1,66 @@
1
+ // commands/assign.js
2
+ // assign command for the issue tracker
3
+ // usage: baton assign <id> <agent-name> [--json]
4
+ // AI was consulted for a portion of this file
5
+
6
+ import { getAgentByName } from '../services/agentsService.js';
7
+ import { assignIssue } from '../services/issuesService.js';
8
+ import { getCurrentActor } from '../services/authService.js';
9
+ import { hasFlag, renderOutput } from '../util.js';
10
+
11
+ const DECIMAL_BASE = 10;
12
+
13
+ /**
14
+ * Runs the assign command to associate an issue with a registered agent.
15
+ * Usage: baton assign <id> <agent-name> [--json]
16
+ * @param {string[]} args - The command line arguments
17
+ * @returns {Promise<number>} The exit code: 0 is success, 1 is error.
18
+ */
19
+ export async function run(args = []) {
20
+ // Check if the user requested JSON output formatting via the --json flag
21
+ const isJson = hasFlag(args, '--json');
22
+
23
+ // Strip out any flags starting with '-' to isolate the raw [issueIdStr, agentName] positional arguments
24
+ const [issueIdStr, agentName] = args.filter(arg => !arg.startsWith('-'));
25
+
26
+ // Guard Clause: Ensure both the issue ID and the agent name were provided
27
+ if (!issueIdStr || !agentName) {
28
+ console.error('Usage Error: Missing arguments.\nUsage: baton assign <id> <agent-name> [--json]');
29
+ return 1;
30
+ }
31
+
32
+ // Convert the extracted issue ID string into a safe decimal integer
33
+ const issueId = parseInt(issueIdStr, DECIMAL_BASE);
34
+
35
+ // Guard Clause: If parsing failed (e.g., the user typed letters instead of an ID), reject it
36
+ if (isNaN(issueId)) {
37
+ console.error(`Error: Invalid issue ID "${issueIdStr}". Must be an integer.`);
38
+ return 1;
39
+ }
40
+
41
+ try {
42
+ // Look up the current operator. If null, error
43
+ const actor = getCurrentActor() || Object.assign(new Error('No authenticated context found.'), { isAuthErr: true });
44
+ // If that identifier property exists, it means the lookup failed; error
45
+ if (actor.isAuthErr) throw actor;
46
+
47
+ // Look up the target assignee by name. If not found, error
48
+ const target = getAgentByName(agentName) || Object.assign(new Error(`Agent/User "${agentName}" is not registered.`), { isTargetErr: true });
49
+ // If the lookup failed, throw the target error
50
+ if (target.isTargetErr) throw target;
51
+
52
+ // Execute the assignment in the database layer, passing the parsed ID, assignee ID, and operator ID
53
+ const issue = assignIssue(issueId, target.id, actor.id);
54
+
55
+ // Render the final output payload. If --json is on, it prints raw data. Otherwise, executes callback.
56
+ renderOutput(isJson, { status: 'success', issueId: issue.id, assignee: { id: target.id, name: target.name, type: target.type } }, () => {
57
+ console.log(`Success: Issue #${issueId} assigned to "${agentName}"`);
58
+ });
59
+
60
+ return 0; // Command completed successfully
61
+ } catch (error) {
62
+ // Intercepts any thrown operational error (or database crash) and outputs a clean message
63
+ console.error(`Error: ${error.message}`);
64
+ return 1; // Return error exit code
65
+ }
66
+ }
@@ -14,6 +14,8 @@
14
14
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
16
  import { initDB } from '../db/index.js';
17
+ import os from 'node:os';
18
+ import { registerAgent } from '../services/agentsService.js';
17
19
  import { Priority } from '../models/issue.js';
18
20
  import {
19
21
  createIssue,
@@ -156,6 +158,22 @@ export async function run(args = []) {
156
158
  clearAllIssues();
157
159
  }
158
160
 
161
+ // auto-register default deployment human user
162
+ let autoRegisterMessage = '';
163
+ try {
164
+ const activeName = process.env.BATON_AGENT || os.userInfo().username;
165
+ registerAgent(activeName, 'human');
166
+ autoRegisterMessage = `Auto-registered default human user: "${activeName}"`;
167
+ } catch (error){
168
+ if (!error.message?.includes('UNIQUE constraint failed')) {
169
+ autoRegisterMessage = `Warning: Could not auto-register default user: ${error.message}`;
170
+ }
171
+ }
172
+
173
+ if (autoRegisterMessage && !isJson) {
174
+ console.log(autoRegisterMessage);
175
+ }
176
+
159
177
  const templatePath = join('docs', 'BATON_AGENT_RULES.md');
160
178
 
161
179
  let rulesContent = '';
@@ -232,6 +232,7 @@ export function updateIssue(id, oldIssue, { title, description, tokenLimit, stat
232
232
  updates.status = toUpdate || status;
233
233
  }
234
234
 
235
+
235
236
  // Normalize priority argument
236
237
  if (priority !== undefined) {
237
238
  const priorityValues = Object.values(Priority);
@@ -276,6 +277,30 @@ export function unassignIssue(issueId){
276
277
  return getIssue(issueId);
277
278
  }
278
279
 
280
+ /**
281
+ * Assigns an issue to a specific registered agent or human.
282
+ * Logs an edit event.
283
+ * @param {number} issueId
284
+ * @param {number} assigneeId
285
+ * @returns {Issue} - the issue that matches the ID
286
+ */
287
+ export function assignIssue(issueId, assigneeId) {
288
+ const db = getDB();
289
+
290
+ // Verify the issue exists first (will throw if not found)
291
+ findById(db, issueId);
292
+
293
+ db.update(issuesTable)
294
+ .set({ assigneeId: assigneeId })
295
+ .where(eq(issuesTable.id, issueId))
296
+ .run();
297
+
298
+ // Pass actorId directly instead of hacking the global module variable
299
+ logActivity(db, issueId, Action.EDIT, `Issue #${issueId} was assigned.`);
300
+
301
+ return getIssue(issueId);
302
+ }
303
+
279
304
  /**
280
305
  * Change the status of an issue from in-review to closed
281
306
  * Logs a closed event.