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
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
|
+
}
|
package/source/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
168
|
-
|
|
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.
|