bosun 0.41.2 → 0.41.4
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/.env.example +1 -1
- package/agent/agent-pool.mjs +9 -2
- package/agent/agent-prompt-catalog.mjs +971 -0
- package/agent/agent-prompts.mjs +2 -970
- package/agent/agent-supervisor.mjs +119 -6
- package/agent/autofix-git.mjs +33 -0
- package/agent/autofix-prompts.mjs +151 -0
- package/agent/autofix.mjs +11 -175
- package/agent/bosun-skills.mjs +3 -2
- package/bosun.config.example.json +17 -0
- package/bosun.schema.json +87 -188
- package/cli.mjs +34 -1
- package/config/config-doctor.mjs +5 -250
- package/config/config-file-names.mjs +5 -0
- package/config/config.mjs +89 -493
- package/config/executor-config.mjs +493 -0
- package/config/repo-root.mjs +1 -2
- package/config/workspace-health.mjs +242 -0
- package/git/git-safety.mjs +15 -0
- package/github/github-oauth-portal.mjs +46 -0
- package/infra/library-manager-utils.mjs +22 -0
- package/infra/library-manager-well-known-sources.mjs +578 -0
- package/infra/library-manager.mjs +512 -1030
- package/infra/monitor.mjs +35 -9
- package/infra/session-tracker.mjs +10 -7
- package/kanban/kanban-adapter.mjs +17 -1
- package/lib/codebase-audit-manifests.mjs +117 -0
- package/lib/codebase-audit.mjs +18 -115
- package/package.json +18 -3
- package/server/setup-web-server.mjs +58 -5
- package/server/ui-server.mjs +1394 -79
- package/shell/codex-config-file.mjs +178 -0
- package/shell/codex-config.mjs +538 -575
- package/task/task-cli.mjs +54 -3
- package/task/task-executor.mjs +143 -13
- package/task/task-store.mjs +409 -1
- package/telegram/telegram-bot.mjs +127 -0
- package/tools/apply-pr-suggestions.mjs +401 -0
- package/tools/syntax-check.mjs +28 -9
- package/ui/app.js +3 -14
- package/ui/components/kanban-board.js +227 -4
- package/ui/components/session-list.js +85 -5
- package/ui/demo-defaults.js +338 -84
- package/ui/demo.html +155 -0
- package/ui/modules/session-api.js +96 -0
- package/ui/modules/settings-schema.js +1 -2
- package/ui/modules/state.js +43 -3
- package/ui/setup.html +4 -5
- package/ui/styles/components.css +58 -4
- package/ui/tabs/agents.js +12 -15
- package/ui/tabs/control.js +1 -0
- package/ui/tabs/library.js +484 -22
- package/ui/tabs/manual-flows.js +105 -29
- package/ui/tabs/tasks.js +848 -141
- package/ui/tabs/telemetry.js +129 -11
- package/ui/tabs/workflow-canvas-utils.mjs +130 -0
- package/ui/tabs/workflows.js +293 -23
- package/voice/voice-tool-definitions.mjs +757 -0
- package/voice/voice-tools.mjs +34 -778
- package/workflow/manual-flow-audit.mjs +165 -0
- package/workflow/manual-flows.mjs +164 -259
- package/workflow/workflow-engine.mjs +147 -58
- package/workflow/workflow-nodes/definitions.mjs +1207 -0
- package/workflow/workflow-nodes/transforms.mjs +612 -0
- package/workflow/workflow-nodes.mjs +358 -63
- package/workflow/workflow-templates.mjs +313 -191
- package/workflow-templates/_helpers.mjs +154 -0
- package/workflow-templates/agents.mjs +61 -4
- package/workflow-templates/code-quality.mjs +7 -7
- package/workflow-templates/github.mjs +20 -10
- package/workflow-templates/task-batch.mjs +44 -11
- package/workflow-templates/task-lifecycle.mjs +31 -6
- package/workspace/worktree-manager.mjs +277 -3
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function executeAnnotationAudit(formValues, rootDir, context) {
|
|
5
|
+
const {
|
|
6
|
+
targetDir = "",
|
|
7
|
+
fileExtensions = ".mjs, .js, .ts, .tsx, .jsx, .py",
|
|
8
|
+
skipGenerated = true,
|
|
9
|
+
phases = "all",
|
|
10
|
+
dryRun = false,
|
|
11
|
+
} = formValues;
|
|
12
|
+
|
|
13
|
+
const extensions = fileExtensions
|
|
14
|
+
.split(",")
|
|
15
|
+
.map((extension) => extension.trim())
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
|
|
18
|
+
const scanRoot = targetDir ? resolve(rootDir, targetDir) : rootDir;
|
|
19
|
+
const inventory = buildInventory(scanRoot, extensions, skipGenerated, rootDir);
|
|
20
|
+
|
|
21
|
+
if (dryRun) {
|
|
22
|
+
return {
|
|
23
|
+
mode: "dry-run",
|
|
24
|
+
filesScanned: inventory.length,
|
|
25
|
+
filesNeedingSummary: inventory.filter((file) => !file.has_summary).length,
|
|
26
|
+
filesNeedingWarn: inventory.filter((file) => !file.has_warn).length,
|
|
27
|
+
phases,
|
|
28
|
+
inventory,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (context.taskManager && typeof context.taskManager.createTask === "function") {
|
|
33
|
+
const taskDescription = buildAuditTaskDescription(formValues, inventory);
|
|
34
|
+
const task = await context.taskManager.createTask({
|
|
35
|
+
title: `docs(audit): codebase annotation audit`,
|
|
36
|
+
description: taskDescription,
|
|
37
|
+
priority: "high",
|
|
38
|
+
labels: ["audit", "documentation", "annotation"],
|
|
39
|
+
skills: ["codebase-annotation-audit"],
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
mode: "task-dispatched",
|
|
43
|
+
taskId: task.id || task._id,
|
|
44
|
+
filesScanned: inventory.length,
|
|
45
|
+
filesNeedingSummary: inventory.filter((file) => !file.has_summary).length,
|
|
46
|
+
phases,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const auditDir = resolve(rootDir, ".bosun", "audit");
|
|
51
|
+
mkdirSync(auditDir, { recursive: true });
|
|
52
|
+
writeFileSync(
|
|
53
|
+
resolve(auditDir, "inventory.json"),
|
|
54
|
+
JSON.stringify(inventory, null, 2) + "\n",
|
|
55
|
+
"utf8",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
mode: "inventory-saved",
|
|
60
|
+
inventoryPath: resolve(auditDir, "inventory.json"),
|
|
61
|
+
filesScanned: inventory.length,
|
|
62
|
+
filesNeedingSummary: inventory.filter((file) => !file.has_summary).length,
|
|
63
|
+
filesNeedingWarn: inventory.filter((file) => !file.has_warn).length,
|
|
64
|
+
phases,
|
|
65
|
+
instructions:
|
|
66
|
+
"Inventory saved. Assign a docs(audit) task to an agent with the codebase-annotation-audit skill to complete annotation.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildInventory(scanDir, extensions, skipGenerated, repoRoot) {
|
|
71
|
+
const inventory = [];
|
|
72
|
+
const generatedPatterns = [
|
|
73
|
+
/node_modules/,
|
|
74
|
+
/\.min\.\w+$/,
|
|
75
|
+
/package-lock\.json$/,
|
|
76
|
+
/yarn\.lock$/,
|
|
77
|
+
/pnpm-lock\.yaml$/,
|
|
78
|
+
/\.next\//,
|
|
79
|
+
/dist\//,
|
|
80
|
+
/build\//,
|
|
81
|
+
/coverage\//,
|
|
82
|
+
/\.bosun-worktrees\//,
|
|
83
|
+
/\.git\//,
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
function walk(dir) {
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
90
|
+
} catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const fullPath = resolve(dir, entry.name);
|
|
96
|
+
const relPath = fullPath.replace(repoRoot, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
97
|
+
|
|
98
|
+
if (entry.isDirectory()) {
|
|
99
|
+
if (skipGenerated && generatedPatterns.some((pattern) => pattern.test(relPath))) continue;
|
|
100
|
+
if (entry.name.startsWith(".") && entry.name !== ".bosun") continue;
|
|
101
|
+
walk(fullPath);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!entry.isFile()) continue;
|
|
106
|
+
if (extensions.length > 0 && !extensions.some((ext) => entry.name.endsWith(ext))) continue;
|
|
107
|
+
if (skipGenerated && generatedPatterns.some((pattern) => pattern.test(relPath))) continue;
|
|
108
|
+
|
|
109
|
+
let content = "";
|
|
110
|
+
let lines = 0;
|
|
111
|
+
let hasSummary = false;
|
|
112
|
+
let hasWarn = false;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
content = readFileSync(fullPath, "utf8");
|
|
116
|
+
lines = content.split("\n").length;
|
|
117
|
+
hasSummary = /(?:CLAUDE|BOSUN):SUMMARY/i.test(content);
|
|
118
|
+
hasWarn = /(?:CLAUDE|BOSUN):WARN/i.test(content);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ext = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
|
|
123
|
+
inventory.push({
|
|
124
|
+
path: relPath,
|
|
125
|
+
lang: ext,
|
|
126
|
+
lines,
|
|
127
|
+
has_summary: hasSummary,
|
|
128
|
+
has_warn: hasWarn,
|
|
129
|
+
category: categorizeFile(relPath),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
walk(scanDir);
|
|
135
|
+
return inventory;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function categorizeFile(relPath) {
|
|
139
|
+
if (/test|spec|__tests__/i.test(relPath)) return "test";
|
|
140
|
+
if (/\.config\.|tsconfig|jest\.config|webpack|vite\.config|\.env/i.test(relPath)) return "config";
|
|
141
|
+
if (/\.min\.|dist\/|build\/|generated/i.test(relPath)) return "generated";
|
|
142
|
+
if (/util|helper|lib\//i.test(relPath)) return "util";
|
|
143
|
+
return "core";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function buildAuditTaskDescription(formValues, inventory) {
|
|
147
|
+
const needsSummary = inventory.filter((file) => !file.has_summary).length;
|
|
148
|
+
const needsWarn = inventory.filter((file) => !file.has_warn).length;
|
|
149
|
+
return `## Codebase Annotation Audit
|
|
150
|
+
|
|
151
|
+
**Phases:** ${formValues.phases || "all"}
|
|
152
|
+
**Target:** ${formValues.targetDir || "(entire repo)"}
|
|
153
|
+
**Extensions:** ${formValues.fileExtensions || "all source files"}
|
|
154
|
+
|
|
155
|
+
### Inventory Summary
|
|
156
|
+
- Total files: ${inventory.length}
|
|
157
|
+
- Files needing CLAUDE:SUMMARY: ${needsSummary}
|
|
158
|
+
- Files needing CLAUDE:WARN review: ${needsWarn}
|
|
159
|
+
|
|
160
|
+
### Instructions
|
|
161
|
+
Follow the codebase-annotation-audit skill (loaded in your skills).
|
|
162
|
+
Run phases as specified above. Do NOT change any program behavior — documentation only.
|
|
163
|
+
${formValues.commitMessage ? `\nCommit with: \`${formValues.commitMessage}\`` : ""}
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
8
8
|
import { dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { executeAnnotationAudit } from "./manual-flow-audit.mjs";
|
|
10
11
|
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = dirname(__filename);
|
|
@@ -48,6 +49,7 @@ const __dirname = dirname(__filename);
|
|
|
48
49
|
* @property {string} [completedAt]
|
|
49
50
|
* @property {Object} [result] — executor output
|
|
50
51
|
* @property {string} [error]
|
|
52
|
+
* @property {Object} [metadata]
|
|
51
53
|
*/
|
|
52
54
|
|
|
53
55
|
// ── Directories ──────────────────────────────────────────────────────────────
|
|
@@ -839,22 +841,30 @@ export function deleteFlowTemplate(templateId, rootDir) {
|
|
|
839
841
|
* @param {string} rootDir
|
|
840
842
|
* @returns {ManualFlowRun}
|
|
841
843
|
*/
|
|
842
|
-
export function createRun(templateId, formValues, rootDir) {
|
|
844
|
+
export function createRun(templateId, formValues, rootDir, opts = {}) {
|
|
843
845
|
ensureDirs(rootDir);
|
|
844
846
|
const template = getFlowTemplate(templateId, rootDir);
|
|
845
847
|
if (!template) throw new Error(`Template not found: ${templateId}`);
|
|
846
848
|
|
|
847
|
-
|
|
849
|
+
validateRequiredManualFlowFields(template, formValues);
|
|
850
|
+
const resolved = resolveManualFlowValues(template, formValues);
|
|
851
|
+
const run = createManualFlowRunRecord(templateId, template.name, resolved, opts);
|
|
852
|
+
|
|
853
|
+
writeRunToDisk(run, rootDir);
|
|
854
|
+
return run;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function validateRequiredManualFlowFields(template, formValues) {
|
|
848
858
|
for (const field of template.fields) {
|
|
849
|
-
if (field.required)
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
859
|
+
if (!field.required) continue;
|
|
860
|
+
const value = formValues[field.id];
|
|
861
|
+
if (value === undefined || value === null || value === "") {
|
|
862
|
+
throw new Error(`Required field missing: ${field.label} (${field.id})`);
|
|
854
863
|
}
|
|
855
864
|
}
|
|
865
|
+
}
|
|
856
866
|
|
|
857
|
-
|
|
867
|
+
function resolveManualFlowValues(template, formValues) {
|
|
858
868
|
const resolved = {};
|
|
859
869
|
for (const field of template.fields) {
|
|
860
870
|
if (formValues[field.id] !== undefined && formValues[field.id] !== null) {
|
|
@@ -863,21 +873,49 @@ export function createRun(templateId, formValues, rootDir) {
|
|
|
863
873
|
resolved[field.id] = field.defaultValue;
|
|
864
874
|
}
|
|
865
875
|
}
|
|
876
|
+
return resolved;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function normalizeManualFlowRunMetadata(metadata = {}) {
|
|
880
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
881
|
+
const normalized = {};
|
|
882
|
+
|
|
883
|
+
const repository = String(
|
|
884
|
+
metadata.repository || metadata.targetRepo || metadata.repo || "",
|
|
885
|
+
).trim();
|
|
886
|
+
if (repository) {
|
|
887
|
+
normalized.repository = repository;
|
|
888
|
+
normalized.targetRepo = repository;
|
|
889
|
+
}
|
|
866
890
|
|
|
867
|
-
const
|
|
891
|
+
const workspaceId = String(metadata.workspaceId || metadata.workspace || "").trim();
|
|
892
|
+
if (workspaceId) normalized.workspaceId = workspaceId;
|
|
893
|
+
|
|
894
|
+
const workspaceDir = String(metadata.workspaceDir || metadata.rootDir || "").trim();
|
|
895
|
+
if (workspaceDir) normalized.workspaceDir = workspaceDir;
|
|
896
|
+
|
|
897
|
+
const projectId = String(metadata.projectId || metadata.project || "").trim();
|
|
898
|
+
if (projectId) normalized.projectId = projectId;
|
|
899
|
+
|
|
900
|
+
const triggerSource = String(metadata.triggerSource || metadata.source || "").trim();
|
|
901
|
+
if (triggerSource) normalized.triggerSource = triggerSource;
|
|
902
|
+
|
|
903
|
+
return Object.keys(normalized).length > 0 ? normalized : null;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function createManualFlowRunRecord(templateId, templateName, formValues, opts = {}) {
|
|
907
|
+
return {
|
|
868
908
|
id: `mfr-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`,
|
|
869
909
|
templateId,
|
|
870
|
-
templateName
|
|
871
|
-
formValues
|
|
910
|
+
templateName,
|
|
911
|
+
formValues,
|
|
872
912
|
status: "pending",
|
|
873
913
|
startedAt: new Date().toISOString(),
|
|
874
914
|
completedAt: null,
|
|
875
915
|
result: null,
|
|
876
916
|
error: null,
|
|
917
|
+
metadata: normalizeManualFlowRunMetadata(opts?.metadata),
|
|
877
918
|
};
|
|
878
|
-
|
|
879
|
-
writeRunToDisk(run, rootDir);
|
|
880
|
-
return run;
|
|
881
919
|
}
|
|
882
920
|
|
|
883
921
|
/**
|
|
@@ -998,7 +1036,9 @@ export function listRuns(rootDir, opts = {}) {
|
|
|
998
1036
|
* @returns {Promise<ManualFlowRun>}
|
|
999
1037
|
*/
|
|
1000
1038
|
export async function executeFlow(templateId, formValues, rootDir, context = {}) {
|
|
1001
|
-
const run = createRun(templateId, formValues, rootDir
|
|
1039
|
+
const run = createRun(templateId, formValues, rootDir, {
|
|
1040
|
+
metadata: context?.runMetadata,
|
|
1041
|
+
});
|
|
1002
1042
|
|
|
1003
1043
|
try {
|
|
1004
1044
|
startRun(run.id, rootDir);
|
|
@@ -1036,186 +1076,8 @@ export async function executeFlow(templateId, formValues, rootDir, context = {})
|
|
|
1036
1076
|
return completeRun(run.id, result, rootDir);
|
|
1037
1077
|
} catch (err) {
|
|
1038
1078
|
console.warn("[manual-flows] execution failed for " + run.id + ": " + (err?.message || String(err)));
|
|
1039
|
-
return failRun(run.id, "Execution failed", rootDir);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// ── Built-in Executors ───────────────────────────────────────────────────────
|
|
1044
|
-
|
|
1045
|
-
/**
|
|
1046
|
-
* Execute the Codebase Annotation Audit flow.
|
|
1047
|
-
* Creates a Bosun task with the audit skill injected, or runs a lightweight
|
|
1048
|
-
* inventory inline for dry runs.
|
|
1049
|
-
*/
|
|
1050
|
-
async function executeAnnotationAudit(formValues, rootDir, context) {
|
|
1051
|
-
const {
|
|
1052
|
-
targetDir = "",
|
|
1053
|
-
fileExtensions = ".mjs, .js, .ts, .tsx, .jsx, .py",
|
|
1054
|
-
skipGenerated = true,
|
|
1055
|
-
phases = "all",
|
|
1056
|
-
dryRun = false,
|
|
1057
|
-
} = formValues;
|
|
1058
|
-
|
|
1059
|
-
// Parse extensions
|
|
1060
|
-
const extensions = fileExtensions
|
|
1061
|
-
.split(",")
|
|
1062
|
-
.map((e) => e.trim())
|
|
1063
|
-
.filter(Boolean);
|
|
1064
|
-
|
|
1065
|
-
// Build inventory inline (lightweight — no agent needed)
|
|
1066
|
-
const scanRoot = targetDir ? resolve(rootDir, targetDir) : rootDir;
|
|
1067
|
-
const inventory = buildInventory(scanRoot, extensions, skipGenerated, rootDir);
|
|
1068
|
-
|
|
1069
|
-
if (dryRun) {
|
|
1070
|
-
return {
|
|
1071
|
-
mode: "dry-run",
|
|
1072
|
-
filesScanned: inventory.length,
|
|
1073
|
-
filesNeedingSummary: inventory.filter((f) => !f.has_summary).length,
|
|
1074
|
-
filesNeedingWarn: inventory.filter((f) => !f.has_warn).length,
|
|
1075
|
-
phases,
|
|
1076
|
-
inventory,
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
// For actual execution, create a task that the agent will pick up
|
|
1081
|
-
if (context.taskManager && typeof context.taskManager.createTask === "function") {
|
|
1082
|
-
const taskDescription = buildAuditTaskDescription(formValues, inventory);
|
|
1083
|
-
const task = await context.taskManager.createTask({
|
|
1084
|
-
title: `docs(audit): codebase annotation audit`,
|
|
1085
|
-
description: taskDescription,
|
|
1086
|
-
priority: "high",
|
|
1087
|
-
labels: ["audit", "documentation", "annotation"],
|
|
1088
|
-
skills: ["codebase-annotation-audit"],
|
|
1089
|
-
});
|
|
1090
|
-
return {
|
|
1091
|
-
mode: "task-dispatched",
|
|
1092
|
-
taskId: task.id || task._id,
|
|
1093
|
-
filesScanned: inventory.length,
|
|
1094
|
-
filesNeedingSummary: inventory.filter((f) => !f.has_summary).length,
|
|
1095
|
-
phases,
|
|
1096
|
-
};
|
|
1079
|
+
return failRun(run.id, err?.message || "Execution failed", rootDir);
|
|
1097
1080
|
}
|
|
1098
|
-
|
|
1099
|
-
// Fallback: return inventory with instructions for manual execution
|
|
1100
|
-
const auditDir = resolve(rootDir, ".bosun", "audit");
|
|
1101
|
-
mkdirSync(auditDir, { recursive: true });
|
|
1102
|
-
writeFileSync(
|
|
1103
|
-
resolve(auditDir, "inventory.json"),
|
|
1104
|
-
JSON.stringify(inventory, null, 2) + "\n",
|
|
1105
|
-
"utf8",
|
|
1106
|
-
);
|
|
1107
|
-
|
|
1108
|
-
return {
|
|
1109
|
-
mode: "inventory-saved",
|
|
1110
|
-
inventoryPath: resolve(auditDir, "inventory.json"),
|
|
1111
|
-
filesScanned: inventory.length,
|
|
1112
|
-
filesNeedingSummary: inventory.filter((f) => !f.has_summary).length,
|
|
1113
|
-
filesNeedingWarn: inventory.filter((f) => !f.has_warn).length,
|
|
1114
|
-
phases,
|
|
1115
|
-
instructions:
|
|
1116
|
-
"Inventory saved. Assign a docs(audit) task to an agent with the codebase-annotation-audit skill to complete annotation.",
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
/**
|
|
1121
|
-
* Scan files to build an audit inventory.
|
|
1122
|
-
*/
|
|
1123
|
-
function buildInventory(scanDir, extensions, skipGenerated, repoRoot) {
|
|
1124
|
-
const inventory = [];
|
|
1125
|
-
const GENERATED_PATTERNS = [
|
|
1126
|
-
/node_modules/,
|
|
1127
|
-
/\.min\.\w+$/,
|
|
1128
|
-
/package-lock\.json$/,
|
|
1129
|
-
/yarn\.lock$/,
|
|
1130
|
-
/pnpm-lock\.yaml$/,
|
|
1131
|
-
/\.next\//,
|
|
1132
|
-
/dist\//,
|
|
1133
|
-
/build\//,
|
|
1134
|
-
/coverage\//,
|
|
1135
|
-
/\.bosun-worktrees\//,
|
|
1136
|
-
/\.git\//,
|
|
1137
|
-
];
|
|
1138
|
-
|
|
1139
|
-
function walk(dir) {
|
|
1140
|
-
let entries;
|
|
1141
|
-
try {
|
|
1142
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
1143
|
-
} catch {
|
|
1144
|
-
return;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
for (const entry of entries) {
|
|
1148
|
-
const fullPath = resolve(dir, entry.name);
|
|
1149
|
-
const relPath = fullPath.replace(repoRoot, "").replace(/\\/g, "/").replace(/^\//, "");
|
|
1150
|
-
|
|
1151
|
-
if (entry.isDirectory()) {
|
|
1152
|
-
if (skipGenerated && GENERATED_PATTERNS.some((p) => p.test(relPath))) continue;
|
|
1153
|
-
if (entry.name.startsWith(".") && entry.name !== ".bosun") continue;
|
|
1154
|
-
walk(fullPath);
|
|
1155
|
-
continue;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
if (!entry.isFile()) continue;
|
|
1159
|
-
if (extensions.length > 0 && !extensions.some((ext) => entry.name.endsWith(ext))) continue;
|
|
1160
|
-
if (skipGenerated && GENERATED_PATTERNS.some((p) => p.test(relPath))) continue;
|
|
1161
|
-
|
|
1162
|
-
let content = "";
|
|
1163
|
-
let lines = 0;
|
|
1164
|
-
let hasSummary = false;
|
|
1165
|
-
let hasWarn = false;
|
|
1166
|
-
|
|
1167
|
-
try {
|
|
1168
|
-
content = readFileSync(fullPath, "utf8");
|
|
1169
|
-
lines = content.split("\n").length;
|
|
1170
|
-
hasSummary = /(?:CLAUDE|BOSUN):SUMMARY/i.test(content);
|
|
1171
|
-
hasWarn = /(?:CLAUDE|BOSUN):WARN/i.test(content);
|
|
1172
|
-
} catch { /* unreadable */ }
|
|
1173
|
-
|
|
1174
|
-
const ext = entry.name.includes(".") ? entry.name.slice(entry.name.lastIndexOf(".")) : "";
|
|
1175
|
-
const category = categorizeFile(relPath, ext);
|
|
1176
|
-
|
|
1177
|
-
inventory.push({
|
|
1178
|
-
path: relPath,
|
|
1179
|
-
lang: ext,
|
|
1180
|
-
lines,
|
|
1181
|
-
has_summary: hasSummary,
|
|
1182
|
-
has_warn: hasWarn,
|
|
1183
|
-
category,
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
walk(scanDir);
|
|
1189
|
-
return inventory;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
function categorizeFile(relPath, ext) {
|
|
1193
|
-
if (/test|spec|__tests__/i.test(relPath)) return "test";
|
|
1194
|
-
if (/\.config\.|tsconfig|jest\.config|webpack|vite\.config|\.env/i.test(relPath)) return "config";
|
|
1195
|
-
if (/\.min\.|dist\/|build\/|generated/i.test(relPath)) return "generated";
|
|
1196
|
-
if (/util|helper|lib\//i.test(relPath)) return "util";
|
|
1197
|
-
return "core";
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
function buildAuditTaskDescription(formValues, inventory) {
|
|
1201
|
-
const needsSummary = inventory.filter((f) => !f.has_summary).length;
|
|
1202
|
-
const needsWarn = inventory.filter((f) => !f.has_warn).length;
|
|
1203
|
-
return `## Codebase Annotation Audit
|
|
1204
|
-
|
|
1205
|
-
**Phases:** ${formValues.phases || "all"}
|
|
1206
|
-
**Target:** ${formValues.targetDir || "(entire repo)"}
|
|
1207
|
-
**Extensions:** ${formValues.fileExtensions || "all source files"}
|
|
1208
|
-
|
|
1209
|
-
### Inventory Summary
|
|
1210
|
-
- Total files: ${inventory.length}
|
|
1211
|
-
- Files needing CLAUDE:SUMMARY: ${needsSummary}
|
|
1212
|
-
- Files needing CLAUDE:WARN review: ${needsWarn}
|
|
1213
|
-
|
|
1214
|
-
### Instructions
|
|
1215
|
-
Follow the codebase-annotation-audit skill (loaded in your skills).
|
|
1216
|
-
Run phases as specified above. Do NOT change any program behavior — documentation only.
|
|
1217
|
-
${formValues.commitMessage ? `\nCommit with: \`${formValues.commitMessage}\`` : ""}
|
|
1218
|
-
`;
|
|
1219
1081
|
}
|
|
1220
1082
|
|
|
1221
1083
|
/** Stub executor for skill generation flow. */
|
|
@@ -1302,28 +1164,11 @@ async function executeContextIndexFull(formValues, rootDir, _context = {}) {
|
|
|
1302
1164
|
}
|
|
1303
1165
|
|
|
1304
1166
|
async function executeResearchAgent(formValues, rootDir, context = {}) {
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
throw new Error("Research problem is required.");
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
const domain = String(formValues?.domain || "computer-science").trim() || "computer-science";
|
|
1311
|
-
const maxIterationsRaw = Number(formValues?.maxIterations);
|
|
1312
|
-
const maxIterations = Number.isFinite(maxIterationsRaw)
|
|
1313
|
-
? Math.min(50, Math.max(1, Math.floor(maxIterationsRaw)))
|
|
1314
|
-
: 10;
|
|
1315
|
-
const searchLiterature = formValues?.searchLiterature !== false;
|
|
1316
|
-
const executionMode = String(formValues?.executionMode || "workflow").trim().toLowerCase();
|
|
1167
|
+
const researchConfig = resolveResearchAgentConfig(formValues);
|
|
1168
|
+
const { problem, domain, maxIterations, searchLiterature, executionMode } = researchConfig;
|
|
1317
1169
|
|
|
1318
1170
|
if (executionMode === "task") {
|
|
1319
|
-
const taskDescription =
|
|
1320
|
-
`Run iterative research for the following problem:\n\n` +
|
|
1321
|
-
`${problem}\n\n` +
|
|
1322
|
-
`Domain: ${domain}\n` +
|
|
1323
|
-
`Max iterations: ${maxIterations}\n` +
|
|
1324
|
-
`Search literature first: ${searchLiterature}\n\n` +
|
|
1325
|
-
`Use a generate -> verify -> revise loop. If verification identifies critical flaws, ` +
|
|
1326
|
-
`regenerate from a fundamentally different approach.`;
|
|
1171
|
+
const taskDescription = buildResearchTaskDescription(researchConfig);
|
|
1327
1172
|
if (context.taskManager && typeof context.taskManager.createTask === "function") {
|
|
1328
1173
|
const task = await context.taskManager.createTask({
|
|
1329
1174
|
title: `research: iterative agent (${domain})`,
|
|
@@ -1340,27 +1185,18 @@ async function executeResearchAgent(formValues, rootDir, context = {}) {
|
|
|
1340
1185
|
searchLiterature,
|
|
1341
1186
|
};
|
|
1342
1187
|
}
|
|
1343
|
-
return
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
maxIterations,
|
|
1348
|
-
searchLiterature,
|
|
1349
|
-
instructions: "Task manager unavailable. Create a high-priority research task using the provided configuration.",
|
|
1350
|
-
};
|
|
1188
|
+
return buildResearchInstructionsResult(
|
|
1189
|
+
researchConfig,
|
|
1190
|
+
"Task manager unavailable. Create a high-priority research task using the provided configuration.",
|
|
1191
|
+
);
|
|
1351
1192
|
}
|
|
1352
1193
|
|
|
1353
1194
|
const engine = context.engine;
|
|
1354
1195
|
if (!engine || typeof engine.execute !== "function") {
|
|
1355
|
-
return
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
maxIterations,
|
|
1360
|
-
searchLiterature,
|
|
1361
|
-
instructions:
|
|
1362
|
-
"Workflow execution mode requires an active workflow engine. Retry from the Workflows launcher or switch to Task mode.",
|
|
1363
|
-
};
|
|
1196
|
+
return buildResearchInstructionsResult(
|
|
1197
|
+
researchConfig,
|
|
1198
|
+
"Workflow execution mode requires an active workflow engine. Retry from the Workflows launcher or switch to Task mode.",
|
|
1199
|
+
);
|
|
1364
1200
|
}
|
|
1365
1201
|
|
|
1366
1202
|
const { installTemplate } = await import("./workflow-templates.mjs");
|
|
@@ -1369,14 +1205,7 @@ async function executeResearchAgent(formValues, rootDir, context = {}) {
|
|
|
1369
1205
|
installTemplate(templateId, engine);
|
|
1370
1206
|
}
|
|
1371
1207
|
|
|
1372
|
-
const input =
|
|
1373
|
-
problem,
|
|
1374
|
-
domain,
|
|
1375
|
-
maxIterations,
|
|
1376
|
-
searchLiterature,
|
|
1377
|
-
_previousFeedback: "",
|
|
1378
|
-
triggerSource: "manual",
|
|
1379
|
-
};
|
|
1208
|
+
const input = buildResearchWorkflowInput(researchConfig);
|
|
1380
1209
|
|
|
1381
1210
|
Promise.resolve()
|
|
1382
1211
|
.then(() => engine.execute(templateId, input, { force: true, triggerSource: "manual" }))
|
|
@@ -1396,30 +1225,76 @@ async function executeResearchAgent(formValues, rootDir, context = {}) {
|
|
|
1396
1225
|
};
|
|
1397
1226
|
}
|
|
1398
1227
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1228
|
+
function resolveResearchAgentConfig(formValues) {
|
|
1229
|
+
const problem = String(formValues?.problem || "").trim();
|
|
1230
|
+
if (!problem) {
|
|
1231
|
+
throw new Error("Research problem is required.");
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const domain = String(formValues?.domain || "computer-science").trim() || "computer-science";
|
|
1235
|
+
const maxIterationsRaw = Number(formValues?.maxIterations);
|
|
1236
|
+
const maxIterations = Number.isFinite(maxIterationsRaw)
|
|
1237
|
+
? Math.min(50, Math.max(1, Math.floor(maxIterationsRaw)))
|
|
1238
|
+
: 10;
|
|
1239
|
+
|
|
1240
|
+
return {
|
|
1241
|
+
problem,
|
|
1242
|
+
domain,
|
|
1243
|
+
maxIterations,
|
|
1244
|
+
searchLiterature: formValues?.searchLiterature !== false,
|
|
1245
|
+
executionMode: String(formValues?.executionMode || "workflow").trim().toLowerCase(),
|
|
1406
1246
|
};
|
|
1247
|
+
}
|
|
1407
1248
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1249
|
+
function buildResearchTaskDescription({ problem, domain, maxIterations, searchLiterature }) {
|
|
1250
|
+
return (
|
|
1251
|
+
`Run iterative research for the following problem:\n\n` +
|
|
1252
|
+
`${problem}\n\n` +
|
|
1253
|
+
`Domain: ${domain}\n` +
|
|
1254
|
+
`Max iterations: ${maxIterations}\n` +
|
|
1255
|
+
`Search literature first: ${searchLiterature}\n\n` +
|
|
1256
|
+
`Use a generate -> verify -> revise loop. If verification identifies critical flaws, ` +
|
|
1257
|
+
`regenerate from a fundamentally different approach.`
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function buildResearchInstructionsResult({ problem, domain, maxIterations, searchLiterature }, instructions) {
|
|
1262
|
+
return {
|
|
1263
|
+
mode: "instructions",
|
|
1264
|
+
problem,
|
|
1265
|
+
domain,
|
|
1266
|
+
maxIterations,
|
|
1267
|
+
searchLiterature,
|
|
1268
|
+
instructions,
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function buildResearchWorkflowInput({ problem, domain, maxIterations, searchLiterature }) {
|
|
1273
|
+
return {
|
|
1274
|
+
problem,
|
|
1275
|
+
domain,
|
|
1276
|
+
maxIterations,
|
|
1277
|
+
searchLiterature,
|
|
1278
|
+
_previousFeedback: "",
|
|
1279
|
+
triggerSource: "manual",
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/** Executor for user-created custom templates. */
|
|
1284
|
+
async function executeCustomFlow(template, formValues, rootDir, context) {
|
|
1285
|
+
const templateValues = buildCustomFlowTemplateValues(template, formValues, {
|
|
1286
|
+
repository:
|
|
1287
|
+
context?.runMetadata?.repository ||
|
|
1288
|
+
context?.runMetadata?.targetRepo ||
|
|
1289
|
+
"",
|
|
1290
|
+
workspaceId: context?.runMetadata?.workspaceId || "",
|
|
1291
|
+
});
|
|
1292
|
+
const action = resolveCustomFlowAction(template);
|
|
1411
1293
|
const actionKind = String(action?.kind || "task").trim().toLowerCase();
|
|
1412
1294
|
|
|
1413
1295
|
if (context.taskManager && typeof context.taskManager.createTask === "function") {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
: {};
|
|
1417
|
-
const taskTitleTemplate = String(taskAction?.title || "").trim();
|
|
1418
|
-
const taskDescriptionTemplate = String(taskAction?.description || "").trim();
|
|
1419
|
-
const taskPriority = String(taskAction?.priority || "medium").trim() || "medium";
|
|
1420
|
-
const taskLabels = Array.isArray(taskAction?.labels)
|
|
1421
|
-
? taskAction.labels.map((label) => String(label || "").trim()).filter(Boolean)
|
|
1422
|
-
: [];
|
|
1296
|
+
const { taskTitleTemplate, taskDescriptionTemplate, taskPriority, taskLabels } =
|
|
1297
|
+
resolveCustomFlowTaskConfig(actionKind, action);
|
|
1423
1298
|
|
|
1424
1299
|
const renderedTitle = renderTemplateString(taskTitleTemplate, templateValues);
|
|
1425
1300
|
const renderedDescription = renderTemplateString(taskDescriptionTemplate, templateValues);
|
|
@@ -1460,6 +1335,37 @@ async function executeCustomFlow(template, formValues, rootDir, context) {
|
|
|
1460
1335
|
};
|
|
1461
1336
|
}
|
|
1462
1337
|
|
|
1338
|
+
function buildCustomFlowTemplateValues(template, formValues, extraValues = {}) {
|
|
1339
|
+
return {
|
|
1340
|
+
...(formValues || {}),
|
|
1341
|
+
...(extraValues || {}),
|
|
1342
|
+
templateName: template?.name || "",
|
|
1343
|
+
templateId: template?.id || "",
|
|
1344
|
+
category: template?.category || "custom",
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function resolveCustomFlowAction(template) {
|
|
1349
|
+
return template?.action && typeof template.action === "object"
|
|
1350
|
+
? template.action
|
|
1351
|
+
: { kind: "task" };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function resolveCustomFlowTaskConfig(actionKind, action) {
|
|
1355
|
+
const taskAction = actionKind === "task" && action?.task && typeof action.task === "object"
|
|
1356
|
+
? action.task
|
|
1357
|
+
: {};
|
|
1358
|
+
|
|
1359
|
+
return {
|
|
1360
|
+
taskTitleTemplate: String(taskAction?.title || "").trim(),
|
|
1361
|
+
taskDescriptionTemplate: String(taskAction?.description || "").trim(),
|
|
1362
|
+
taskPriority: String(taskAction?.priority || "medium").trim() || "medium",
|
|
1363
|
+
taskLabels: Array.isArray(taskAction?.labels)
|
|
1364
|
+
? taskAction.labels.map((label) => String(label || "").trim()).filter(Boolean)
|
|
1365
|
+
: [],
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1463
1369
|
function renderTemplateString(templateText = "", values = {}) {
|
|
1464
1370
|
const raw = String(templateText || "");
|
|
1465
1371
|
if (!raw) return "";
|
|
@@ -1484,4 +1390,3 @@ function writeRunToDisk(run, rootDir) {
|
|
|
1484
1390
|
mkdirSync(dir, { recursive: true });
|
|
1485
1391
|
writeFileSync(resolve(dir, `${run.id}.json`), JSON.stringify(run, null, 2) + "\n", "utf8");
|
|
1486
1392
|
}
|
|
1487
|
-
|