baton-issue-tracker 1.13.4 → 1.14.1

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.1",
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
+ }
@@ -12,8 +12,11 @@
12
12
  // baton init --specs C:\full\path\to\specs.md
13
13
 
14
14
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
15
- import { join } from 'node:path';
15
+ import { join, dirname } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
16
17
  import { initDB } from '../db/index.js';
18
+ import os from 'node:os';
19
+ import { registerAgent } from '../services/agentsService.js';
17
20
  import { Priority } from '../models/issue.js';
18
21
  import {
19
22
  createIssue,
@@ -156,7 +159,23 @@ export async function run(args = []) {
156
159
  clearAllIssues();
157
160
  }
158
161
 
159
- const templatePath = join('docs', 'BATON_AGENT_RULES.md');
162
+ // auto-register default deployment human user
163
+ let autoRegisterMessage = '';
164
+ try {
165
+ const activeName = process.env.BATON_AGENT || os.userInfo().username;
166
+ registerAgent(activeName, 'human');
167
+ autoRegisterMessage = `Auto-registered default human user: "${activeName}"`;
168
+ } catch (error){
169
+ if (!error.message?.includes('UNIQUE constraint failed')) {
170
+ autoRegisterMessage = `Warning: Could not auto-register default user: ${error.message}`;
171
+ }
172
+ }
173
+
174
+ if (autoRegisterMessage && !isJson) {
175
+ console.log(autoRegisterMessage);
176
+ }
177
+
178
+ const templatePath = join(dirname(fileURLToPath(import.meta.url)), '..', 'templates', 'BATON_AGENT_RULES.md');
160
179
 
161
180
  let rulesContent = '';
162
181
  if (existsSync(templatePath)) {
@@ -164,13 +183,10 @@ export async function run(args = []) {
164
183
  }
165
184
 
166
185
  const outputPath = join(process.cwd(), 'BATON_AGENT_RULES.md');
167
- if (existsSync(outputPath) && !flags.force) {
168
- console.error('Error: BATON_AGENT_RULES.md already exists. Use --force to overwrite.');
169
- return 1;
186
+ if (!existsSync(outputPath) || flags.force) {
187
+ writeFileSync(outputPath, rulesContent, 'utf8');
170
188
  }
171
189
 
172
- writeFileSync(outputPath, rulesContent, 'utf8');
173
-
174
190
  const resolvedSpecsPath = resolvePath(flags.specs, DEFAULT_SPECS_PATH);
175
191
  const createdIssues = generateIssuesFromSpecs(flags.specs);
176
192
  const envelope = {
@@ -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.
@@ -0,0 +1,47 @@
1
+ # Baton Agent Rules & System Instructions
2
+
3
+ This document defines the strict operational boundaries and workflows for all AI agents working within this repository.
4
+
5
+ ## 1. Single Source of Truth
6
+ **Baton is the absolute single source of truth for all tasks, issues, and progress.**
7
+ * **BANNED:** Do not use markdown TODO lists, scratchpad memory files, inline comments for tracking, or any other alternative task management methods.
8
+ * All state, priority, and assignment must be read from and written to the Baton CLI.
9
+
10
+ ## 2. Allowed Commands
11
+ Agents must interface with Baton using the following commands. **You MUST use the `--json` flag** on all commands to ensure machine-readable output.
12
+
13
+ | Action | Command | Purpose |
14
+ | :--- | :--- | :--- |
15
+ | **Check Identity** | `baton whoami --json` | Verify if you are registered and authorized to work. |
16
+ | **Register Self** | `baton register --name <name> --type agent --json` | Register yourself if `whoami` returns an error or is not found. |
17
+ | **Check Status** | `baton status --json` | View overall progress and issue counts. |
18
+ | **List Issues** | `baton list --status open --json` | View available work (can filter by status/priority). |
19
+ | **Search Issues** | `baton search "<query>" --json` | Search for existing issues to avoid creating duplicates. |
20
+ | **View Issue** | `baton view <id> --json` | Read the full details and requirements of a specific ticket. |
21
+ | **Claim Next Issue** | `baton claim --json` | Automatically claim the highest-priority open issue and mark it `In-Progress`. |
22
+ | **Submit for Review** | `baton submit <id> --json` | Mark a completed task as ready for human approval. |
23
+ | **Unclaim Issue** | `baton unclaim <id> --json` | Relinquish a task if you are stuck or hit the loop limit. |
24
+ | **Update Issue** | `baton update <id> [options] --json` | Update specific fields (e.g., `--description`) if required. |
25
+ | **Create Issue** | `baton create --title "<text>" --json` | Generate a new ticket (e.g., for reporting a bug). |
26
+ | **View History** | `baton log <id> --json` | Check previous attempts, rejections, or actions on a ticket. |
27
+
28
+ *Note: Commands like `init`, `loop`, `approve`, `reject`, `priority`, and `delete` are strictly reserved for human supervisors or system orchestration.*
29
+
30
+ ## 3. Standard Workflow
31
+ You must strictly adhere to this step-by-step lifecycle for every task:
32
+
33
+ 1. **Authenticate:** Run `baton whoami --json`. If you are not registered, run `baton register --name <your_name> --type agent --json` before proceeding.
34
+ 2. **Orient:** Run `baton status --json` to understand the current project state.
35
+ 3. **Find Work:** Run `baton list --status open --json` or `baton search --json` to review available issues.
36
+ 4. **Claim:** Run `baton claim --json` to automatically assign yourself the highest-priority task. Note the `id` returned.
37
+ 5. **Research & Execute:** Read the issue details (`baton view <id> --json`). Perform the necessary research, code changes, and testing to fulfill the requirements.
38
+ 6. **Submit for Review:** Once the work is complete and verified locally, run `baton submit <id> --json`. Wait for human approval or rejection.
39
+
40
+ ## 4. Loop Mitigation (The Three-Strike Rule)
41
+ To prevent infinite loops and wasted tokens, you must obey the **Three-Strike Rule**:
42
+
43
+ * If you attempt a task and fail to resolve it **three consecutive times** (e.g., your tests keep failing, or the human rejects your work three times for the same reason), you MUST:
44
+ 1. Halt execution on the task.
45
+ 2. **Unclaim the issue:** Run `baton unclaim <id> --json` to move it back to `Open`.
46
+ 3. Prepend the issue description with a clear explanation of the failure using `baton update <id> --description "FAILED 3 TIMES: [Your brief explanation] \n\n [Original Description]"`.
47
+ 4. Stop and prompt the human supervisor for intervention and guidance.