jules-orchestrator-kit 0.29.1 → 0.32.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 +169 -86
- package/bin/agentctl.mjs +267 -4
- package/bin/mcp-server.mjs +0 -0
- package/index.mjs +21 -1
- package/package.json +1 -1
- package/scripts/stale-base-check.mjs +2 -2
- package/src/config.mjs +18 -2
- package/src/dag-engine.mjs +76 -0
- package/src/engine.mjs +331 -32
- package/src/envelope.mjs +32 -2
- package/src/execution_envelope.mjs +9 -1
- package/src/git.mjs +41 -0
- package/src/mcp.mjs +34 -0
- package/src/memory.mjs +148 -0
- package/src/ops/checkpoint.mjs +167 -0
- package/src/ops/command-registry.mjs +274 -0
- package/src/ops/doctor-planner.mjs +214 -0
- package/src/ops/doctor-registry.mjs +442 -0
- package/src/ops/ide-scaffold.mjs +139 -0
- package/src/ops/receipts.mjs +171 -0
- package/src/ops/swarm-actions.mjs +156 -0
- package/src/ops/task-actions.mjs +214 -0
- package/src/ops/tdd-generator.mjs +115 -0
- package/src/ops/transaction.mjs +278 -0
- package/src/prompt-guard.mjs +8 -1
- package/src/provider.mjs +164 -8
- package/src/remediation.mjs +164 -0
- package/src/security.mjs +14 -6
- package/src/stack-detector.mjs +15 -5
- package/src/state.mjs +71 -7
- package/src/task-optimizer.mjs +306 -0
- package/src/telemetry.mjs +35 -2
- package/src/tui.mjs +2 -6
- package/src/ux/capabilities.mjs +180 -0
- package/src/ux/diff-viewer.mjs +289 -0
- package/src/ux/key-decoder.mjs +281 -0
- package/src/ux/layout.mjs +195 -0
- package/src/ux/log-viewer.mjs +110 -0
- package/src/ux/palette.mjs +179 -0
- package/src/ux/queue-model.mjs +206 -0
- package/src/ux/renderer.mjs +188 -0
- package/src/ux/swarm-model.mjs +146 -0
- package/src/ux/terminal-session.mjs +215 -0
- package/src/ux/widgets.mjs +323 -0
- package/src/webhook.mjs +120 -0
- package/src/wizard-task.mjs +6 -0
package/src/mcp.mjs
CHANGED
|
@@ -148,6 +148,19 @@ export const MCP_TOOLS = [
|
|
|
148
148
|
},
|
|
149
149
|
},
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
name: "optimize_jules_prompt",
|
|
153
|
+
description: "Evaluate task prompt falsifiability, check static path validity, and synthesize optimized task envelope.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
prompt: { type: "string", description: "Raw task prompt instructions to analyze or optimize" },
|
|
158
|
+
fix: { type: "boolean", description: "If true, synthesizes and returns an optimized task envelope" },
|
|
159
|
+
verifyCmd: { type: "string", description: "Optional explicit verification command" },
|
|
160
|
+
},
|
|
161
|
+
required: ["prompt"],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
151
164
|
];
|
|
152
165
|
|
|
153
166
|
export async function handleMcpRequest(request, opts = {}) {
|
|
@@ -291,6 +304,27 @@ export async function handleMcpRequest(request, opts = {}) {
|
|
|
291
304
|
};
|
|
292
305
|
}
|
|
293
306
|
|
|
307
|
+
if (toolName === "optimize_jules_prompt") {
|
|
308
|
+
if (!args || typeof args.prompt !== "string") {
|
|
309
|
+
return {
|
|
310
|
+
jsonrpc: "2.0",
|
|
311
|
+
id,
|
|
312
|
+
error: { code: -32602, message: "Invalid parameters: 'prompt' must be a string" },
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const { scorePromptFalsifiability, optimizeTaskPrompt } = await import("./task-optimizer.mjs");
|
|
316
|
+
const res = args.fix
|
|
317
|
+
? optimizeTaskPrompt(args.prompt, { rootDir: root, verifyCmd: args.verifyCmd })
|
|
318
|
+
: scorePromptFalsifiability(args.prompt, { rootDir: root, verifyCmd: args.verifyCmd });
|
|
319
|
+
return {
|
|
320
|
+
jsonrpc: "2.0",
|
|
321
|
+
id,
|
|
322
|
+
result: {
|
|
323
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }],
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
294
328
|
return {
|
|
295
329
|
jsonrpc: "2.0",
|
|
296
330
|
id,
|
package/src/memory.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
function atomicWrite(file, content) {
|
|
5
|
+
const dir = dirname(file);
|
|
6
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
7
|
+
const tmp = `${file}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
8
|
+
writeFileSync(tmp, content, "utf8");
|
|
9
|
+
renameSync(tmp, file);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getLearningsPath(root = process.cwd()) {
|
|
13
|
+
return join(root, ".agent", "knowledge", "learnings.json");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getSystemLearningsMdPath(root = process.cwd()) {
|
|
17
|
+
return join(root, ".agent", "SYSTEM_LEARNINGS.md");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadLearnings(root = process.cwd()) {
|
|
21
|
+
const dbFile = getLearningsPath(root);
|
|
22
|
+
if (!existsSync(dbFile)) return [];
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(readFileSync(dbFile, "utf8"));
|
|
25
|
+
} catch (_) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function recordLearning(root = process.cwd(), learning = {}) {
|
|
31
|
+
const { agent = "agent", trigger, solution, category = "GENERAL" } = learning;
|
|
32
|
+
if (!trigger || !solution) {
|
|
33
|
+
throw new Error("Trigger and solution are required to record a learning.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dbFile = getLearningsPath(root);
|
|
37
|
+
const mdFile = getSystemLearningsMdPath(root);
|
|
38
|
+
const db = loadLearnings(root);
|
|
39
|
+
|
|
40
|
+
const cleanTrigger = trigger.trim();
|
|
41
|
+
const cleanSolution = solution.trim();
|
|
42
|
+
|
|
43
|
+
const isDuplicate = db.some(
|
|
44
|
+
(l) =>
|
|
45
|
+
l.trigger.toLowerCase() === cleanTrigger.toLowerCase() &&
|
|
46
|
+
l.solution.toLowerCase() === cleanSolution.toLowerCase()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (!isDuplicate) {
|
|
50
|
+
db.push({
|
|
51
|
+
date: new Date().toISOString().split("T")[0],
|
|
52
|
+
agent,
|
|
53
|
+
category,
|
|
54
|
+
trigger: cleanTrigger,
|
|
55
|
+
solution: cleanSolution,
|
|
56
|
+
});
|
|
57
|
+
atomicWrite(dbFile, JSON.stringify(db, null, 2));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Render Markdown table
|
|
61
|
+
const sanitize = (str) => str.replace(/\|/g, "|").replace(/\r?\n/g, "<br>");
|
|
62
|
+
let md = `<!-- 🚫 AUTO-GENERATED. AGENTS: DO NOT EDIT THIS FILE DIRECTLY. -->\n`;
|
|
63
|
+
md += `# 🧠 System Learnings & Platform Quirks\n\n`;
|
|
64
|
+
md += `> **⚠️ TILL AGENTER:** Redigera ALDRIG denna fil manuellt. Registrera lärdomar via:\n`;
|
|
65
|
+
md += `> \`agentctl learning add "<trigger>" "<solution>"\`\n\n`;
|
|
66
|
+
md += `| Datum | Agent | Trigger / Symptom | Mandatory Rule / Solution |\n`;
|
|
67
|
+
md += `| :--- | :--- | :--- | :--- |\n`;
|
|
68
|
+
|
|
69
|
+
db.forEach((l) => {
|
|
70
|
+
md += `| ${l.date} | \`${l.agent}\` | ${sanitize(l.trigger)} | **${sanitize(l.solution)}** |\n`;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
atomicWrite(mdFile, md);
|
|
74
|
+
return { recorded: !isDuplicate, count: db.length };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function hydratePrompt(root = process.cwd(), promptText = "", opts = {}) {
|
|
78
|
+
const max = opts.max || 5;
|
|
79
|
+
const learnings = loadLearnings(root);
|
|
80
|
+
if (learnings.length === 0) return promptText;
|
|
81
|
+
|
|
82
|
+
const lowerPrompt = promptText.toLowerCase();
|
|
83
|
+
const matched = learnings.filter((l) => {
|
|
84
|
+
const triggerWords = l.trigger.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
85
|
+
return triggerWords.some((w) => lowerPrompt.includes(w));
|
|
86
|
+
}).slice(0, max);
|
|
87
|
+
|
|
88
|
+
const activeLearnings = matched.length > 0 ? matched : learnings.slice(-max);
|
|
89
|
+
|
|
90
|
+
let header = "\n\n<ACTIVE_SYSTEM_LEARNINGS>\n";
|
|
91
|
+
header += "The following platform quirks & resolution rules apply to this task:\n";
|
|
92
|
+
activeLearnings.forEach((l) => {
|
|
93
|
+
header += `- [${l.category || "RULE"}] WHEN: ${l.trigger} → THEN: ${l.solution}\n`;
|
|
94
|
+
});
|
|
95
|
+
header += "</ACTIVE_SYSTEM_LEARNINGS>\n";
|
|
96
|
+
|
|
97
|
+
// Check for baton pass state
|
|
98
|
+
const batonFile = join(root, ".agent", "state", "baton_pass.json");
|
|
99
|
+
let batonBlock = "";
|
|
100
|
+
if (existsSync(batonFile)) {
|
|
101
|
+
try {
|
|
102
|
+
const baton = JSON.parse(readFileSync(batonFile, "utf8"));
|
|
103
|
+
batonBlock = `\n<BATON_PASS_STATE>\n`;
|
|
104
|
+
batonBlock += `Previous Task ID: ${baton.taskId || "unknown"}\n`;
|
|
105
|
+
batonBlock += `Last Status: ${baton.status || "BLOCKED"}\n`;
|
|
106
|
+
if (baton.summary) batonBlock += `Summary: ${baton.summary}\n`;
|
|
107
|
+
batonBlock += `</BATON_PASS_STATE>\n`;
|
|
108
|
+
} catch (_) {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return `${header}${batonBlock}\n${promptText}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function harvestFailure(root = process.cwd(), opts = {}) {
|
|
115
|
+
const { exitCode = 4, logPath, diffText = "", taskId = "unknown", agent = "jules" } = opts;
|
|
116
|
+
|
|
117
|
+
let logContent = "";
|
|
118
|
+
if (logPath && existsSync(logPath)) {
|
|
119
|
+
try {
|
|
120
|
+
logContent = readFileSync(logPath, "utf8");
|
|
121
|
+
} catch (_) {}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Guard: Test Weakening check
|
|
125
|
+
const testWeakeningRegex = /-\s*(it\(|test\(|expect\(|assert\.)/g;
|
|
126
|
+
if (testWeakeningRegex.test(diffText)) {
|
|
127
|
+
return { status: "REJECTED", reason: "TEST_WEAKENING: Attempted to delete or weaken test assertions." };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Guard: Diff Limit check (75 KiB limit)
|
|
131
|
+
const MAX_DIFF_BYTES = 75 * 1024;
|
|
132
|
+
if (Buffer.byteLength(diffText, "utf8") > MAX_DIFF_BYTES) {
|
|
133
|
+
return { status: "REJECTED", reason: "DIFF_PAYLOAD_LIMIT: Diff payload exceeds 75 KiB limit." };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Extract error trace line
|
|
137
|
+
const errorLine = logContent.split("\n").find((line) => /Error:|FAIL|TypeError|AssertionError/.test(line)) || "Unknown execution failure";
|
|
138
|
+
|
|
139
|
+
const candidate = {
|
|
140
|
+
agent,
|
|
141
|
+
category: "OODA_HARVEST",
|
|
142
|
+
trigger: `[OODA Exit ${exitCode}] ${errorLine.slice(0, 120)}`,
|
|
143
|
+
solution: `Verify root cause from log (Task ${taskId}) before altering logic. Avoid fragile hacks or removing assertions.`,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const res = recordLearning(root, candidate);
|
|
147
|
+
return { status: "HARVESTED", candidate, recorded: res.recorded };
|
|
148
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { git, changedFiles, diffText } from "../git.mjs";
|
|
4
|
+
import { resolveRoot } from "../config.mjs";
|
|
5
|
+
|
|
6
|
+
export class CheckpointError extends Error {
|
|
7
|
+
constructor(message, opts = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "CheckpointError";
|
|
10
|
+
this.code = opts.code || 1;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ensures checkpoint storage directory exists.
|
|
16
|
+
*/
|
|
17
|
+
export function getCheckpointDir(root = resolveRoot()) {
|
|
18
|
+
const dir = join(root, ".agent", "state", "checkpoints");
|
|
19
|
+
try {
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
} catch (_) {}
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates an atomic pre-flight snapshot of the repository state before an agent task runs.
|
|
27
|
+
* @param {string} sessionId
|
|
28
|
+
* @param {object} options
|
|
29
|
+
* @returns {object} Checkpoint metadata
|
|
30
|
+
*/
|
|
31
|
+
export function createCheckpoint(sessionId = `session-${Date.now()}`, options = {}) {
|
|
32
|
+
const root = options.root || resolveRoot();
|
|
33
|
+
const dir = getCheckpointDir(root);
|
|
34
|
+
|
|
35
|
+
let headSha = "";
|
|
36
|
+
try {
|
|
37
|
+
headSha = git(["rev-parse", "HEAD"], { cwd: root, ignoreError: true }).trim();
|
|
38
|
+
} catch (_) {}
|
|
39
|
+
|
|
40
|
+
let branch = "";
|
|
41
|
+
try {
|
|
42
|
+
branch = git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: root, ignoreError: true }).trim();
|
|
43
|
+
} catch (_) {}
|
|
44
|
+
|
|
45
|
+
const uncommittedFiles = changedFiles(root, branch || "main", "working-tree");
|
|
46
|
+
const diffContent = diffText(root, branch || "main", "working-tree");
|
|
47
|
+
|
|
48
|
+
const snapshot = {
|
|
49
|
+
version: 1,
|
|
50
|
+
id: sessionId,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
headSha,
|
|
53
|
+
branch,
|
|
54
|
+
uncommittedFiles,
|
|
55
|
+
diffContent,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const snapshotPath = join(dir, `${sessionId}.json`);
|
|
59
|
+
writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
60
|
+
|
|
61
|
+
// Keep last 10 checkpoints
|
|
62
|
+
pruneCheckpoints(root, 10);
|
|
63
|
+
|
|
64
|
+
return snapshot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Restores working tree and git state to a previously saved checkpoint.
|
|
69
|
+
* @param {string} sessionId or '--latest'
|
|
70
|
+
* @param {object} options
|
|
71
|
+
* @returns {object} Restore result summary
|
|
72
|
+
*/
|
|
73
|
+
export function restoreCheckpoint(sessionId = "--latest", options = {}) {
|
|
74
|
+
const root = options.root || resolveRoot();
|
|
75
|
+
const dir = getCheckpointDir(root);
|
|
76
|
+
|
|
77
|
+
let targetId = sessionId;
|
|
78
|
+
if (!targetId || targetId === "--latest" || targetId === "latest") {
|
|
79
|
+
const list = listCheckpoints(root);
|
|
80
|
+
if (list.length === 0) {
|
|
81
|
+
throw new CheckpointError("No checkpoints found to restore.");
|
|
82
|
+
}
|
|
83
|
+
targetId = list[0].id;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const snapshotFile = join(dir, `${targetId}.json`);
|
|
87
|
+
if (!existsSync(snapshotFile)) {
|
|
88
|
+
throw new CheckpointError(`Checkpoint snapshot file not found for session '${targetId}'.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let snapshot;
|
|
92
|
+
try {
|
|
93
|
+
snapshot = JSON.parse(readFileSync(snapshotFile, "utf-8"));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw new CheckpointError(`Corrupted checkpoint file '${targetId}.json': ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (snapshot.headSha) {
|
|
99
|
+
try {
|
|
100
|
+
git(["reset", "--hard", snapshot.headSha], { cwd: root, ignoreError: true });
|
|
101
|
+
} catch (err) {
|
|
102
|
+
throw new CheckpointError(`Failed git reset --hard ${snapshot.headSha}: ${err.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Clean untracked files generated during session
|
|
107
|
+
try {
|
|
108
|
+
git(["clean", "-fd"], { cwd: root, ignoreError: true });
|
|
109
|
+
} catch (_) {}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
id: snapshot.id,
|
|
114
|
+
headSha: snapshot.headSha,
|
|
115
|
+
restoredAt: new Date().toISOString(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Lists all active checkpoints ordered by timestamp descending.
|
|
121
|
+
*/
|
|
122
|
+
export function listCheckpoints(root = resolveRoot()) {
|
|
123
|
+
const dir = getCheckpointDir(root);
|
|
124
|
+
if (!existsSync(dir)) return [];
|
|
125
|
+
|
|
126
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
127
|
+
const items = [];
|
|
128
|
+
|
|
129
|
+
for (const file of files) {
|
|
130
|
+
const filePath = join(dir, file);
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
133
|
+
items.push({
|
|
134
|
+
id: data.id || file.replace(/\.json$/, ""),
|
|
135
|
+
timestamp: data.timestamp || new Date(statSync(filePath).mtimeMs).toISOString(),
|
|
136
|
+
headSha: data.headSha || "",
|
|
137
|
+
branch: data.branch || "",
|
|
138
|
+
filePath,
|
|
139
|
+
});
|
|
140
|
+
} catch (_) {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
items.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
144
|
+
return items;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Prunes checkpoints keeping only the most recent N sessions.
|
|
149
|
+
*/
|
|
150
|
+
export function pruneCheckpoints(root = resolveRoot(), maxRetention = 10) {
|
|
151
|
+
const list = listCheckpoints(root);
|
|
152
|
+
if (list.length <= maxRetention) return 0;
|
|
153
|
+
|
|
154
|
+
const toRemove = list.slice(maxRetention);
|
|
155
|
+
let prunedCount = 0;
|
|
156
|
+
|
|
157
|
+
for (const item of toRemove) {
|
|
158
|
+
try {
|
|
159
|
+
if (existsSync(item.filePath)) {
|
|
160
|
+
rmSync(item.filePath, { force: true });
|
|
161
|
+
prunedCount++;
|
|
162
|
+
}
|
|
163
|
+
} catch (_) {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return prunedCount;
|
|
167
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {"low" | "moderate" | "high" | "destructive"} RiskLevel
|
|
3
|
+
* @typedef {"Create" | "Inspect" | "Operate" | "Repair" | "Configure"} CommandCategory
|
|
4
|
+
*
|
|
5
|
+
* @typedef {Object} CommandFlag
|
|
6
|
+
* @property {string} name
|
|
7
|
+
* @property {"boolean" | "string"} type
|
|
8
|
+
* @property {string} description
|
|
9
|
+
* @property {any} [default]
|
|
10
|
+
*
|
|
11
|
+
* @typedef {Object} CommandDescriptor
|
|
12
|
+
* @property {string} id
|
|
13
|
+
* @property {string[]} path
|
|
14
|
+
* @property {string} title
|
|
15
|
+
* @property {string} description
|
|
16
|
+
* @property {CommandCategory} category
|
|
17
|
+
* @property {boolean} mutates
|
|
18
|
+
* @property {RiskLevel} risk
|
|
19
|
+
* @property {"never" | "optional" | "authoring-auto"} interactive
|
|
20
|
+
* @property {boolean} requiresRepository
|
|
21
|
+
* @property {string[]} shortcuts
|
|
22
|
+
* @property {string[]} examples
|
|
23
|
+
* @property {CommandFlag[]} flags
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @type {CommandDescriptor[]} */
|
|
27
|
+
export const COMMAND_REGISTRY = [
|
|
28
|
+
{
|
|
29
|
+
id: "doctor",
|
|
30
|
+
path: ["doctor"],
|
|
31
|
+
title: "doctor",
|
|
32
|
+
description: "Run repository diagnostics and guided fixes",
|
|
33
|
+
category: "Repair",
|
|
34
|
+
mutates: false,
|
|
35
|
+
risk: "low",
|
|
36
|
+
interactive: "optional",
|
|
37
|
+
requiresRepository: true,
|
|
38
|
+
shortcuts: ["d", "doc"],
|
|
39
|
+
examples: [
|
|
40
|
+
"agentctl doctor",
|
|
41
|
+
"agentctl doctor --interactive",
|
|
42
|
+
"agentctl doctor --fix safe --yes",
|
|
43
|
+
"agentctl doctor --json",
|
|
44
|
+
],
|
|
45
|
+
flags: [
|
|
46
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen diagnostic matrix" },
|
|
47
|
+
{ name: "fix", type: "string", description: "Apply fix classes (e.g. 'safe' or check IDs)" },
|
|
48
|
+
{ name: "probe", type: "boolean", description: "Enable active network/execution probes" },
|
|
49
|
+
{ name: "json", type: "boolean", description: "Output structured JSON doctor report" },
|
|
50
|
+
{ name: "yes", type: "boolean", description: "Bypass interactive confirmation for safe fixes" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "queue",
|
|
55
|
+
path: ["queue"],
|
|
56
|
+
title: "queue",
|
|
57
|
+
description: "Browse and manage canonical task queue",
|
|
58
|
+
category: "Operate",
|
|
59
|
+
mutates: false,
|
|
60
|
+
risk: "low",
|
|
61
|
+
interactive: "optional",
|
|
62
|
+
requiresRepository: true,
|
|
63
|
+
shortcuts: ["q"],
|
|
64
|
+
examples: [
|
|
65
|
+
"agentctl queue",
|
|
66
|
+
"agentctl queue --interactive",
|
|
67
|
+
"agentctl queue --json",
|
|
68
|
+
],
|
|
69
|
+
flags: [
|
|
70
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen queue dashboard" },
|
|
71
|
+
{ name: "json", type: "boolean", description: "Output structured JSON queue snapshot" },
|
|
72
|
+
{ name: "limit", type: "string", description: "Maximum tasks to include in snapshot" },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "swarm",
|
|
77
|
+
path: ["swarm"],
|
|
78
|
+
title: "swarm",
|
|
79
|
+
description: "Inspect active worker slots and concurrency scheduler",
|
|
80
|
+
category: "Operate",
|
|
81
|
+
mutates: false,
|
|
82
|
+
risk: "low",
|
|
83
|
+
interactive: "optional",
|
|
84
|
+
requiresRepository: true,
|
|
85
|
+
shortcuts: ["s"],
|
|
86
|
+
examples: [
|
|
87
|
+
"agentctl swarm",
|
|
88
|
+
"agentctl swarm --interactive",
|
|
89
|
+
"agentctl swarm --json",
|
|
90
|
+
],
|
|
91
|
+
flags: [
|
|
92
|
+
{ name: "interactive", type: "boolean", description: "Open full-screen swarm dashboard" },
|
|
93
|
+
{ name: "json", type: "boolean", description: "Output structured JSON swarm snapshot" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "task-create",
|
|
98
|
+
path: ["task", "create"],
|
|
99
|
+
title: "task create",
|
|
100
|
+
description: "Author a scoped, falsifiable task",
|
|
101
|
+
category: "Create",
|
|
102
|
+
mutates: true,
|
|
103
|
+
risk: "moderate",
|
|
104
|
+
interactive: "authoring-auto",
|
|
105
|
+
requiresRepository: true,
|
|
106
|
+
shortcuts: ["tc", "create"],
|
|
107
|
+
examples: [
|
|
108
|
+
'agentctl task create --title "Fix webhook" --prompt "Add retry handling" --verify-cmd "npm test"',
|
|
109
|
+
"agentctl task create --interactive",
|
|
110
|
+
],
|
|
111
|
+
flags: [
|
|
112
|
+
{ name: "title", type: "string", description: "Short title for task" },
|
|
113
|
+
{ name: "prompt", type: "string", description: "Detailed task instructions" },
|
|
114
|
+
{ name: "verify-cmd", type: "string", description: "Verification command" },
|
|
115
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive task wizard" },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "task-optimize",
|
|
120
|
+
path: ["task", "optimize"],
|
|
121
|
+
title: "task optimize",
|
|
122
|
+
description: "Score task prompt falsifiability and static path resolution",
|
|
123
|
+
category: "Inspect",
|
|
124
|
+
mutates: false,
|
|
125
|
+
risk: "low",
|
|
126
|
+
interactive: "never",
|
|
127
|
+
requiresRepository: true,
|
|
128
|
+
shortcuts: ["to", "optimize"],
|
|
129
|
+
examples: [
|
|
130
|
+
'agentctl task optimize "Fix JWT token expiry in src/auth.js"',
|
|
131
|
+
'agentctl task optimize "Refactor auth" --fix',
|
|
132
|
+
'agentctl task optimize --file prompt.txt --json',
|
|
133
|
+
],
|
|
134
|
+
flags: [
|
|
135
|
+
{ name: "fix", type: "boolean", description: "Synthesize optimized markdown task envelope" },
|
|
136
|
+
{ name: "json", type: "boolean", description: "Output structured JSON prompt evaluation" },
|
|
137
|
+
{ name: "file", type: "string", description: "Path to text file containing task prompt" },
|
|
138
|
+
{ name: "dir", type: "string", description: "Target repository directory root" },
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "init",
|
|
143
|
+
path: ["init"],
|
|
144
|
+
title: "init",
|
|
145
|
+
description: "Configure Stack Oracle and Jules provider manifests",
|
|
146
|
+
category: "Configure",
|
|
147
|
+
mutates: true,
|
|
148
|
+
risk: "moderate",
|
|
149
|
+
interactive: "optional",
|
|
150
|
+
requiresRepository: true,
|
|
151
|
+
shortcuts: ["i"],
|
|
152
|
+
examples: [
|
|
153
|
+
"agentctl init",
|
|
154
|
+
"agentctl init --interactive",
|
|
155
|
+
"agentctl init --tier pro --yes",
|
|
156
|
+
],
|
|
157
|
+
flags: [
|
|
158
|
+
{ name: "tier", type: "string", description: "Target configuration tier (free, pro, enterprise)" },
|
|
159
|
+
{ name: "interactive", type: "boolean", description: "Launch interactive onboarding wizard" },
|
|
160
|
+
{ name: "yes", type: "boolean", description: "Accept auto-detected Stack Oracle defaults" },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "dashboard",
|
|
165
|
+
path: ["dashboard"],
|
|
166
|
+
title: "dashboard",
|
|
167
|
+
description: "Start local web dashboard server",
|
|
168
|
+
category: "Inspect",
|
|
169
|
+
mutates: false,
|
|
170
|
+
risk: "low",
|
|
171
|
+
interactive: "never",
|
|
172
|
+
requiresRepository: true,
|
|
173
|
+
shortcuts: ["dash"],
|
|
174
|
+
examples: [
|
|
175
|
+
"agentctl dashboard",
|
|
176
|
+
"agentctl dashboard --port 3000",
|
|
177
|
+
],
|
|
178
|
+
flags: [
|
|
179
|
+
{ name: "port", type: "string", description: "HTTP server port (default 3000)" },
|
|
180
|
+
{ name: "host", type: "string", description: "Bind host address (default 127.0.0.1)" },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "status",
|
|
185
|
+
path: ["status"],
|
|
186
|
+
title: "status",
|
|
187
|
+
description: "Show operating status and health summary",
|
|
188
|
+
category: "Inspect",
|
|
189
|
+
mutates: false,
|
|
190
|
+
risk: "low",
|
|
191
|
+
interactive: "never",
|
|
192
|
+
requiresRepository: false,
|
|
193
|
+
shortcuts: ["st"],
|
|
194
|
+
examples: [
|
|
195
|
+
"agentctl status",
|
|
196
|
+
"agentctl status --json",
|
|
197
|
+
],
|
|
198
|
+
flags: [
|
|
199
|
+
{ name: "json", type: "boolean", description: "Output status in JSON format" },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Lookup command descriptor by ID, command path, or shortcut alias.
|
|
206
|
+
* @param {string} idOrAlias
|
|
207
|
+
* @returns {CommandDescriptor | null}
|
|
208
|
+
*/
|
|
209
|
+
export function getCommandDescriptor(idOrAlias) {
|
|
210
|
+
if (!idOrAlias) return null;
|
|
211
|
+
const target = String(idOrAlias).trim().toLowerCase();
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
COMMAND_REGISTRY.find((cmd) => {
|
|
215
|
+
if (cmd.id.toLowerCase() === target) return true;
|
|
216
|
+
if (cmd.path.join(" ").toLowerCase() === target) return true;
|
|
217
|
+
if (cmd.shortcuts.some((s) => s.toLowerCase() === target)) return true;
|
|
218
|
+
return false;
|
|
219
|
+
}) || null
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* List commands filtered by category.
|
|
225
|
+
* @param {CommandCategory} [category]
|
|
226
|
+
* @returns {CommandDescriptor[]}
|
|
227
|
+
*/
|
|
228
|
+
export function listCommandsByCategory(category) {
|
|
229
|
+
if (!category) return COMMAND_REGISTRY;
|
|
230
|
+
return COMMAND_REGISTRY.filter((cmd) => cmd.category === category);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Format clean CLI --help text for a command descriptor.
|
|
235
|
+
* @param {CommandDescriptor} descriptor
|
|
236
|
+
* @returns {string}
|
|
237
|
+
*/
|
|
238
|
+
export function formatCommandHelp(descriptor) {
|
|
239
|
+
const lines = [];
|
|
240
|
+
lines.push(`Usage: agentctl ${descriptor.path.join(" ")} [flags]`);
|
|
241
|
+
lines.push("");
|
|
242
|
+
lines.push(`Description: ${descriptor.description}`);
|
|
243
|
+
lines.push(`Category: ${descriptor.category}`);
|
|
244
|
+
lines.push(`Risk Tier: ${descriptor.risk.toUpperCase()}`);
|
|
245
|
+
lines.push("");
|
|
246
|
+
|
|
247
|
+
if (descriptor.flags && descriptor.flags.length > 0) {
|
|
248
|
+
lines.push("Flags:");
|
|
249
|
+
for (const flag of descriptor.flags) {
|
|
250
|
+
lines.push(` --${flag.name.padEnd(16)} ${flag.description}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push("");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (descriptor.examples && descriptor.examples.length > 0) {
|
|
256
|
+
lines.push("Examples:");
|
|
257
|
+
for (const ex of descriptor.examples) {
|
|
258
|
+
lines.push(` $ ${ex}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join("\n");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Format JSON representation of full command registry.
|
|
267
|
+
* @returns {{ ok: true, commands: CommandDescriptor[] }}
|
|
268
|
+
*/
|
|
269
|
+
export function formatRegistryJson() {
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
commands: COMMAND_REGISTRY,
|
|
273
|
+
};
|
|
274
|
+
}
|