jonah-fleet 1.8.0 → 1.9.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/CHANGELOG.md +45 -0
- package/README.md +3 -3
- package/dist/commands/labels.d.ts +1 -1
- package/dist/commands/labels.d.ts.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/telemetry.d.ts.map +1 -1
- package/dist/index.js +666 -151
- package/dist/lib/daemon-keys.d.ts.map +1 -1
- package/dist/lib/daemon.d.ts.map +1 -1
- package/dist/lib/fleet-query.d.ts.map +1 -1
- package/dist/lib/labels.d.ts +20 -0
- package/dist/lib/labels.d.ts.map +1 -1
- package/dist/lib/manifest.d.ts.map +1 -1
- package/dist/lib/presets.d.ts +6 -1
- package/dist/lib/presets.d.ts.map +1 -1
- package/dist/lib/runner.d.ts +48 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/terminal-card.d.ts +15 -1
- package/dist/lib/terminal-card.d.ts.map +1 -1
- package/package.json +1 -1
- package/schema.json +10 -5
- package/templates/prompts/ORCHESTRATION.md +49 -11
- package/templates/prompts/_prompt-template.md +10 -13
- package/templates/prompts/analytics-review.md +4 -2
- package/templates/prompts/autowork.md +43 -12
- package/templates/prompts/dependency-update-security-check.md +4 -2
- package/templates/prompts/design-review.md +125 -0
- package/templates/prompts/issues-housekeeping.md +9 -7
- package/templates/prompts/optimizer.md +31 -12
- package/templates/prompts/peer-review.md +29 -4
- package/templates/prompts/product-planning.md +14 -10
- package/templates/workflows/autowork-cron.yml +99 -39
- package/templates/workflows/dependency-check-cron.yml +89 -3
- package/templates/workflows/design-review-cron.yml +221 -0
- package/templates/workflows/issues-housekeeping-cron.yml +89 -3
- package/templates/workflows/prompt-optimizer-cron.yml +96 -10
- package/templates/workflows/sync-fleet.yml +4 -0
- package/templates/workflows/trigger-autowork-manual.yml +116 -12
- package/templates/workflows/trigger-autowork-on-bug.yml +135 -5
- package/templates/workflows/trigger-autowork-on-merge.yml +135 -5
- package/templates/workflows/trigger-review-routine.yml +119 -18
package/dist/index.js
CHANGED
|
@@ -13,10 +13,10 @@ import path from "path";
|
|
|
13
13
|
|
|
14
14
|
// src/lib/presets.ts
|
|
15
15
|
var DEFAULT_MODELS_CONFIG = {
|
|
16
|
-
default: "gemini-3.
|
|
17
|
-
"issues-housekeeping": "gemini-3.
|
|
18
|
-
"dependency-update-security-check": "gemini-3.
|
|
19
|
-
"analytics-review": "gemini-3.
|
|
16
|
+
default: "gemini-3.8-flash-high",
|
|
17
|
+
"issues-housekeeping": "gemini-3.8-flash-medium",
|
|
18
|
+
"dependency-update-security-check": "gemini-3.8-flash-medium",
|
|
19
|
+
"analytics-review": "gemini-3.8-flash-medium"
|
|
20
20
|
};
|
|
21
21
|
var DEFAULT_BUDGETS_CONFIG = {
|
|
22
22
|
weeklyTokens: 875e4,
|
|
@@ -27,7 +27,8 @@ var DEFAULT_BUDGETS_CONFIG = {
|
|
|
27
27
|
"issues-housekeeping": 40,
|
|
28
28
|
"dependency-update-security-check": 25,
|
|
29
29
|
"product-planning": 45,
|
|
30
|
-
"analytics-review": 30
|
|
30
|
+
"analytics-review": 30,
|
|
31
|
+
"design-review": 50
|
|
31
32
|
},
|
|
32
33
|
maxIterations: {
|
|
33
34
|
autowork: 65,
|
|
@@ -36,7 +37,8 @@ var DEFAULT_BUDGETS_CONFIG = {
|
|
|
36
37
|
"issues-housekeeping": 30,
|
|
37
38
|
"dependency-update-security-check": 20,
|
|
38
39
|
"product-planning": 40,
|
|
39
|
-
"analytics-review": 25
|
|
40
|
+
"analytics-review": 25,
|
|
41
|
+
"design-review": 30
|
|
40
42
|
}
|
|
41
43
|
};
|
|
42
44
|
var PRESET_CONFIGS = {
|
|
@@ -48,7 +50,8 @@ var PRESET_CONFIGS = {
|
|
|
48
50
|
"issues-housekeeping": false,
|
|
49
51
|
"dependency-update-security-check": false,
|
|
50
52
|
"product-planning": false,
|
|
51
|
-
"analytics-review": false
|
|
53
|
+
"analytics-review": false,
|
|
54
|
+
"design-review": false
|
|
52
55
|
},
|
|
53
56
|
skills: [
|
|
54
57
|
"tdd",
|
|
@@ -66,7 +69,8 @@ var PRESET_CONFIGS = {
|
|
|
66
69
|
"issues-housekeeping": true,
|
|
67
70
|
"dependency-update-security-check": true,
|
|
68
71
|
"product-planning": false,
|
|
69
|
-
"analytics-review": false
|
|
72
|
+
"analytics-review": false,
|
|
73
|
+
"design-review": false
|
|
70
74
|
},
|
|
71
75
|
skills: [
|
|
72
76
|
"tdd",
|
|
@@ -88,7 +92,8 @@ var PRESET_CONFIGS = {
|
|
|
88
92
|
"issues-housekeeping": true,
|
|
89
93
|
"dependency-update-security-check": true,
|
|
90
94
|
"product-planning": true,
|
|
91
|
-
"analytics-review": true
|
|
95
|
+
"analytics-review": true,
|
|
96
|
+
"design-review": true
|
|
92
97
|
},
|
|
93
98
|
skills: [
|
|
94
99
|
"tdd",
|
|
@@ -117,9 +122,10 @@ var ROUTINE_TO_WORKFLOW_MAP = {
|
|
|
117
122
|
"issues-housekeeping": ["issues-housekeeping-cron.yml"],
|
|
118
123
|
"dependency-update-security-check": ["dependency-check-cron.yml"],
|
|
119
124
|
"product-planning": [],
|
|
120
|
-
"analytics-review": []
|
|
125
|
+
"analytics-review": [],
|
|
126
|
+
"design-review": ["design-review-cron.yml"]
|
|
121
127
|
};
|
|
122
|
-
var FLEET_VERSION = "1.
|
|
128
|
+
var FLEET_VERSION = "1.8.0";
|
|
123
129
|
var SCHEMA_URL = "https://raw.githubusercontent.com/juliendurandeu/jonah-fleet/main/schema.json";
|
|
124
130
|
|
|
125
131
|
// src/lib/manifest.ts
|
|
@@ -153,7 +159,8 @@ function createDefaultManifest(preset = "standard") {
|
|
|
153
159
|
"issues-housekeeping": true,
|
|
154
160
|
"dependency-update-security-check": true,
|
|
155
161
|
"product-planning": false,
|
|
156
|
-
"analytics-review": false
|
|
162
|
+
"analytics-review": false,
|
|
163
|
+
"design-review": false
|
|
157
164
|
},
|
|
158
165
|
skills: PRESET_CONFIGS.standard.skills,
|
|
159
166
|
models: { ...DEFAULT_MODELS_CONFIG },
|
|
@@ -935,46 +942,74 @@ async function queryRepoFleetStatus(repoIdentifier, executor = defaultGhExecutor
|
|
|
935
942
|
}
|
|
936
943
|
const logContents = [];
|
|
937
944
|
if (fs4.existsSync(repoIdentifier) && fs4.statSync(repoIdentifier).isDirectory()) {
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
945
|
+
const runsDir = path4.join(repoIdentifier, ".jonah-fleet/runs");
|
|
946
|
+
const legacyLogsDir = path4.join(repoIdentifier, ".github/prompts/logs");
|
|
947
|
+
const collectLogs = (dir) => {
|
|
948
|
+
if (!fs4.existsSync(dir)) return;
|
|
949
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
950
|
+
for (const entry of entries) {
|
|
951
|
+
const fullPath = path4.join(dir, entry.name);
|
|
952
|
+
if (entry.isDirectory()) {
|
|
953
|
+
collectLogs(fullPath);
|
|
954
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
955
|
+
try {
|
|
956
|
+
logContents.push(fs4.readFileSync(fullPath, "utf8"));
|
|
957
|
+
} catch {
|
|
951
958
|
}
|
|
952
959
|
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
collectLogs(runsDir);
|
|
963
|
+
collectLogs(legacyLogsDir);
|
|
956
964
|
} else {
|
|
957
965
|
try {
|
|
958
|
-
const
|
|
959
|
-
"
|
|
960
|
-
|
|
966
|
+
const issuesRaw = await executor([
|
|
967
|
+
"issue",
|
|
968
|
+
"list",
|
|
969
|
+
"--repo",
|
|
970
|
+
repoIdentifier,
|
|
971
|
+
"--label",
|
|
972
|
+
"routine-log",
|
|
973
|
+
"--state",
|
|
974
|
+
"all",
|
|
975
|
+
"--limit",
|
|
976
|
+
"25",
|
|
977
|
+
"--json",
|
|
978
|
+
"body"
|
|
961
979
|
]);
|
|
962
|
-
const
|
|
963
|
-
if (Array.isArray(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
const fileRaw = await executor(["api", `repos/${repoIdentifier}/contents/${file.path}`]);
|
|
968
|
-
const parsed = JSON.parse(fileRaw);
|
|
969
|
-
if (parsed.content) {
|
|
970
|
-
logContents.push(Buffer.from(parsed.content, "base64").toString("utf8"));
|
|
971
|
-
}
|
|
972
|
-
} catch {
|
|
980
|
+
const issues = JSON.parse(issuesRaw);
|
|
981
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
982
|
+
for (const issue of issues) {
|
|
983
|
+
if (issue.body) {
|
|
984
|
+
logContents.push(issue.body);
|
|
973
985
|
}
|
|
974
986
|
}
|
|
975
987
|
}
|
|
976
988
|
} catch {
|
|
977
989
|
}
|
|
990
|
+
if (logContents.length === 0) {
|
|
991
|
+
try {
|
|
992
|
+
const treeRaw = await executor([
|
|
993
|
+
"api",
|
|
994
|
+
`repos/${repoIdentifier}/git/trees/HEAD?recursive=1`
|
|
995
|
+
]);
|
|
996
|
+
const tree = JSON.parse(treeRaw);
|
|
997
|
+
if (Array.isArray(tree.tree)) {
|
|
998
|
+
const logFiles = tree.tree.filter((node) => node.path && node.path.startsWith(".github/prompts/logs/") && node.path.endsWith(".md")).slice(-15);
|
|
999
|
+
for (const file of logFiles) {
|
|
1000
|
+
try {
|
|
1001
|
+
const fileRaw = await executor(["api", `repos/${repoIdentifier}/contents/${file.path}`]);
|
|
1002
|
+
const parsed = JSON.parse(fileRaw);
|
|
1003
|
+
if (parsed.content) {
|
|
1004
|
+
logContents.push(Buffer.from(parsed.content, "base64").toString("utf8"));
|
|
1005
|
+
}
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
978
1013
|
}
|
|
979
1014
|
if (logContents.length > 0) {
|
|
980
1015
|
result.tokenUsage = computeTokenSpendFromLogs(logContents, now);
|
|
@@ -1080,7 +1115,13 @@ var DEFAULT_PROTECTED_LABEL_PATTERNS = [
|
|
|
1080
1115
|
"blocked",
|
|
1081
1116
|
"autorelease:*",
|
|
1082
1117
|
"dependencies",
|
|
1083
|
-
"security"
|
|
1118
|
+
"security",
|
|
1119
|
+
"routine-log",
|
|
1120
|
+
"routine:*",
|
|
1121
|
+
"status:*",
|
|
1122
|
+
"runner:*",
|
|
1123
|
+
"historical-migration",
|
|
1124
|
+
"needs-attention"
|
|
1084
1125
|
];
|
|
1085
1126
|
function isLabelProtected(labelName, protectedPatterns = DEFAULT_PROTECTED_LABEL_PATTERNS) {
|
|
1086
1127
|
for (const pattern of protectedPatterns) {
|
|
@@ -1275,6 +1316,66 @@ async function pruneLabels(options = {}) {
|
|
|
1275
1316
|
}
|
|
1276
1317
|
return result;
|
|
1277
1318
|
}
|
|
1319
|
+
var FLEET_CORE_LABELS = [
|
|
1320
|
+
{ name: "routine-log", color: "5319e7", description: "Autonomous routine execution log" },
|
|
1321
|
+
{ name: "status:running", color: "fbca04", description: "Routine execution in progress" },
|
|
1322
|
+
{ name: "status:success", color: "0e8a16", description: "Routine execution succeeded" },
|
|
1323
|
+
{ name: "status:failure", color: "d93f0b", description: "Routine execution failed" },
|
|
1324
|
+
{ name: "needs-attention", color: "e11d48", description: "Requires maintainer triage" },
|
|
1325
|
+
{ name: "runner:github-actions", color: "1f883d", description: "Executed via GitHub Actions" },
|
|
1326
|
+
{ name: "runner:local", color: "bfd4f2", description: "Executed via local machine daemon" },
|
|
1327
|
+
{ name: "routine:autowork", color: "0052cc", description: "Routine: autowork" },
|
|
1328
|
+
{ name: "routine:peer-review", color: "0052cc", description: "Routine: peer-review" },
|
|
1329
|
+
{ name: "routine:optimizer", color: "0052cc", description: "Routine: optimizer" },
|
|
1330
|
+
{ name: "routine:issues-housekeeping", color: "0052cc", description: "Routine: issues-housekeeping" },
|
|
1331
|
+
{ name: "routine:dependency-check", color: "0052cc", description: "Routine: dependency-check" },
|
|
1332
|
+
{ name: "routine:analytics-review", color: "0052cc", description: "Routine: analytics-review" },
|
|
1333
|
+
{ name: "routine:product-planning", color: "0052cc", description: "Routine: product-planning" },
|
|
1334
|
+
{ name: "routine:design-review", color: "0052cc", description: "Routine: design-review" },
|
|
1335
|
+
{ name: "historical-migration", color: "d4c5f9", description: "Migrated from historical git logs" }
|
|
1336
|
+
];
|
|
1337
|
+
async function provisionLabels(options = {}) {
|
|
1338
|
+
const cwd = options.cwd || process.cwd();
|
|
1339
|
+
const executor = options.executor || defaultGhExecutor;
|
|
1340
|
+
const repo = await resolveRepoName(options.repo, cwd, executor);
|
|
1341
|
+
const existingRaw = await fetchRepoLabels(repo, executor, cwd).catch(() => []);
|
|
1342
|
+
const existingMap = new Set(existingRaw.map((l) => l.name));
|
|
1343
|
+
const result = {
|
|
1344
|
+
repo,
|
|
1345
|
+
created: [],
|
|
1346
|
+
alreadyExists: [],
|
|
1347
|
+
errors: []
|
|
1348
|
+
};
|
|
1349
|
+
for (const def of FLEET_CORE_LABELS) {
|
|
1350
|
+
if (existingMap.has(def.name)) {
|
|
1351
|
+
result.alreadyExists.push(def.name);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
try {
|
|
1355
|
+
const args = [
|
|
1356
|
+
"label",
|
|
1357
|
+
"create",
|
|
1358
|
+
def.name,
|
|
1359
|
+
"--color",
|
|
1360
|
+
def.color,
|
|
1361
|
+
"--description",
|
|
1362
|
+
def.description,
|
|
1363
|
+
"--force"
|
|
1364
|
+
];
|
|
1365
|
+
if (repo && repo !== "current") {
|
|
1366
|
+
args.push("--repo", repo);
|
|
1367
|
+
}
|
|
1368
|
+
await executor(args);
|
|
1369
|
+
result.created.push(def.name);
|
|
1370
|
+
} catch (err) {
|
|
1371
|
+
result.errors.push({
|
|
1372
|
+
label: def.name,
|
|
1373
|
+
error: err.message || String(err)
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return result;
|
|
1378
|
+
}
|
|
1278
1379
|
|
|
1279
1380
|
// src/commands/init.ts
|
|
1280
1381
|
async function promptQuestion(query, defaultValue) {
|
|
@@ -1505,8 +1606,15 @@ Run 'jonah-fleet sync --force' to apply updates.
|
|
|
1505
1606
|
saveManifest(cwd, manifest);
|
|
1506
1607
|
const result = installFleet(cwd, manifest, { force: true });
|
|
1507
1608
|
console.log(pc2.green(`\u2713 Synchronized with Jonah Fleet v${FLEET_VERSION}`));
|
|
1508
|
-
console.log(pc2.green(`\u2713 Updated ${result.promptsInstalled.length} prompts, ${result.workflowsInstalled.length} workflows, and ${result.skillsInstalled.length} skills
|
|
1509
|
-
|
|
1609
|
+
console.log(pc2.green(`\u2713 Updated ${result.promptsInstalled.length} prompts, ${result.workflowsInstalled.length} workflows, and ${result.skillsInstalled.length} skills.`));
|
|
1610
|
+
try {
|
|
1611
|
+
const labelRes = await provisionLabels({ cwd });
|
|
1612
|
+
if (labelRes.created.length > 0) {
|
|
1613
|
+
console.log(pc2.green(`\u2713 Provisioned ${labelRes.created.length} missing fleet label(s)`));
|
|
1614
|
+
}
|
|
1615
|
+
} catch {
|
|
1616
|
+
}
|
|
1617
|
+
console.log();
|
|
1510
1618
|
}
|
|
1511
1619
|
|
|
1512
1620
|
// src/commands/status.ts
|
|
@@ -1792,31 +1900,29 @@ async function runStatus(options = {}) {
|
|
|
1792
1900
|
}
|
|
1793
1901
|
const drift = checkDrift(cwd, manifest);
|
|
1794
1902
|
const hasDrift = drift.missingPrompts.length > 0 || drift.modifiedPrompts.length > 0 || drift.missingWorkflows.length > 0 || drift.modifiedWorkflows.length > 0 || drift.missingSkills.length > 0;
|
|
1795
|
-
const
|
|
1903
|
+
const runsDir = path7.join(cwd, ".jonah-fleet/runs");
|
|
1904
|
+
const legacyLogsDir = path7.join(cwd, ".github/prompts/logs");
|
|
1796
1905
|
let tokenUsage = void 0;
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
}
|
|
1906
|
+
const logContents = [];
|
|
1907
|
+
const collectLogs = (dir) => {
|
|
1908
|
+
if (!fs7.existsSync(dir)) return;
|
|
1909
|
+
const entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1910
|
+
for (const entry of entries) {
|
|
1911
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1912
|
+
if (entry.isDirectory()) {
|
|
1913
|
+
collectLogs(fullPath);
|
|
1914
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
1915
|
+
try {
|
|
1916
|
+
logContents.push(fs7.readFileSync(fullPath, "utf8"));
|
|
1917
|
+
} catch {
|
|
1810
1918
|
}
|
|
1811
1919
|
}
|
|
1812
|
-
};
|
|
1813
|
-
try {
|
|
1814
|
-
collectLogs(logsDir);
|
|
1815
|
-
} catch {
|
|
1816
|
-
}
|
|
1817
|
-
if (logContents.length > 0) {
|
|
1818
|
-
tokenUsage = computeTokenSpendFromLogs(logContents);
|
|
1819
1920
|
}
|
|
1921
|
+
};
|
|
1922
|
+
collectLogs(runsDir);
|
|
1923
|
+
collectLogs(legacyLogsDir);
|
|
1924
|
+
if (logContents.length > 0) {
|
|
1925
|
+
tokenUsage = computeTokenSpendFromLogs(logContents);
|
|
1820
1926
|
}
|
|
1821
1927
|
if (options.json) {
|
|
1822
1928
|
console.log(
|
|
@@ -2268,9 +2374,10 @@ async function emitTelemetry(summary, endpoint, customFetch = globalThis.fetch)
|
|
|
2268
2374
|
}
|
|
2269
2375
|
function collectLocalTelemetryLogs(dir, repositoryName = "local") {
|
|
2270
2376
|
const summaries = [];
|
|
2271
|
-
const
|
|
2272
|
-
|
|
2377
|
+
const runsDir = path8.join(dir, ".jonah-fleet/runs");
|
|
2378
|
+
const legacyLogsDir = path8.join(dir, ".github/prompts/logs");
|
|
2273
2379
|
const traverse = (currentDir) => {
|
|
2380
|
+
if (!fs8.existsSync(currentDir)) return;
|
|
2274
2381
|
const entries = fs8.readdirSync(currentDir, { withFileTypes: true });
|
|
2275
2382
|
for (const entry of entries) {
|
|
2276
2383
|
const fullPath = path8.join(currentDir, entry.name);
|
|
@@ -2286,7 +2393,12 @@ function collectLocalTelemetryLogs(dir, repositoryName = "local") {
|
|
|
2286
2393
|
}
|
|
2287
2394
|
}
|
|
2288
2395
|
};
|
|
2289
|
-
|
|
2396
|
+
if (fs8.existsSync(runsDir)) {
|
|
2397
|
+
traverse(runsDir);
|
|
2398
|
+
}
|
|
2399
|
+
if (fs8.existsSync(legacyLogsDir)) {
|
|
2400
|
+
traverse(legacyLogsDir);
|
|
2401
|
+
}
|
|
2290
2402
|
return summaries;
|
|
2291
2403
|
}
|
|
2292
2404
|
async function collectRepoTelemetry(repoIdentifier, executor = defaultGhExecutor, options = {}) {
|
|
@@ -2294,6 +2406,35 @@ async function collectRepoTelemetry(repoIdentifier, executor = defaultGhExecutor
|
|
|
2294
2406
|
if (fs8.existsSync(repoIdentifier) && fs8.statSync(repoIdentifier).isDirectory()) {
|
|
2295
2407
|
return collectLocalTelemetryLogs(repoIdentifier, repoIdentifier);
|
|
2296
2408
|
}
|
|
2409
|
+
try {
|
|
2410
|
+
const issuesRaw = await executor([
|
|
2411
|
+
"issue",
|
|
2412
|
+
"list",
|
|
2413
|
+
"--repo",
|
|
2414
|
+
repoIdentifier,
|
|
2415
|
+
"--label",
|
|
2416
|
+
"routine-log",
|
|
2417
|
+
"--state",
|
|
2418
|
+
"all",
|
|
2419
|
+
"--limit",
|
|
2420
|
+
String(options.maxLogs || 25),
|
|
2421
|
+
"--json",
|
|
2422
|
+
"body,number,title,createdAt"
|
|
2423
|
+
]);
|
|
2424
|
+
const issues = JSON.parse(issuesRaw);
|
|
2425
|
+
if (Array.isArray(issues) && issues.length > 0) {
|
|
2426
|
+
for (const issue of issues) {
|
|
2427
|
+
if (issue.body) {
|
|
2428
|
+
const summary = parseLogToTelemetry(issue.body, { repository: repoIdentifier });
|
|
2429
|
+
if (summary) summaries.push(summary);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
if (summaries.length > 0) {
|
|
2433
|
+
return summaries;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
} catch {
|
|
2437
|
+
}
|
|
2297
2438
|
try {
|
|
2298
2439
|
const treeRaw = await executor([
|
|
2299
2440
|
"api",
|
|
@@ -2412,11 +2553,16 @@ async function runTelemetry(options = {}) {
|
|
|
2412
2553
|
if (options.action === "emit" || options.log) {
|
|
2413
2554
|
let logPath = options.log;
|
|
2414
2555
|
if (!logPath) {
|
|
2415
|
-
const
|
|
2416
|
-
if (fs9.existsSync(
|
|
2556
|
+
const reportFile = path9.join(cwd, ".jonah-fleet/run-report.md");
|
|
2557
|
+
if (fs9.existsSync(reportFile)) {
|
|
2558
|
+
logPath = reportFile;
|
|
2559
|
+
} else {
|
|
2560
|
+
const runsDir = path9.join(cwd, ".jonah-fleet/runs");
|
|
2561
|
+
const legacyLogsDir = path9.join(cwd, ".github/prompts/logs");
|
|
2417
2562
|
let latestFile = null;
|
|
2418
2563
|
let latestMtime = 0;
|
|
2419
2564
|
const findLogs = (dir) => {
|
|
2565
|
+
if (!fs9.existsSync(dir)) return;
|
|
2420
2566
|
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
2421
2567
|
for (const entry of entries) {
|
|
2422
2568
|
const p = path9.join(dir, entry.name);
|
|
@@ -2430,7 +2576,8 @@ async function runTelemetry(options = {}) {
|
|
|
2430
2576
|
}
|
|
2431
2577
|
}
|
|
2432
2578
|
};
|
|
2433
|
-
findLogs(
|
|
2579
|
+
findLogs(runsDir);
|
|
2580
|
+
findLogs(legacyLogsDir);
|
|
2434
2581
|
logPath = latestFile || void 0;
|
|
2435
2582
|
}
|
|
2436
2583
|
}
|
|
@@ -2635,8 +2782,10 @@ async function cleanupStaleWorktrees(repoRoot) {
|
|
|
2635
2782
|
// src/lib/terminal-card.ts
|
|
2636
2783
|
import fs11 from "fs";
|
|
2637
2784
|
import path11 from "path";
|
|
2638
|
-
import { execFileSync } from "child_process";
|
|
2785
|
+
import { execFileSync, execFile as execFile3 } from "child_process";
|
|
2786
|
+
import { promisify as promisify3 } from "util";
|
|
2639
2787
|
import pc9 from "picocolors";
|
|
2788
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
2640
2789
|
function stripAnsi(text) {
|
|
2641
2790
|
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2642
2791
|
}
|
|
@@ -2722,6 +2871,31 @@ function fetchTargetTitle(repoRoot, target) {
|
|
|
2722
2871
|
}
|
|
2723
2872
|
return null;
|
|
2724
2873
|
}
|
|
2874
|
+
async function fetchTargetTitleAsync(repoRoot, target) {
|
|
2875
|
+
try {
|
|
2876
|
+
const prMatch = target.match(/PR\s*#?(\d+)/i);
|
|
2877
|
+
if (prMatch) {
|
|
2878
|
+
const { stdout } = await execFileAsync3("gh", ["pr", "view", prMatch[1], "--json", "title", "-q", ".title"], {
|
|
2879
|
+
cwd: repoRoot,
|
|
2880
|
+
encoding: "utf8",
|
|
2881
|
+
timeout: 3e3
|
|
2882
|
+
});
|
|
2883
|
+
return stdout.trim() || null;
|
|
2884
|
+
}
|
|
2885
|
+
const issueMatch = target.match(/Issue\s*#?(\d+)/i);
|
|
2886
|
+
if (issueMatch) {
|
|
2887
|
+
const { stdout } = await execFileAsync3("gh", ["issue", "view", issueMatch[1], "--json", "title", "-q", ".title"], {
|
|
2888
|
+
cwd: repoRoot,
|
|
2889
|
+
encoding: "utf8",
|
|
2890
|
+
timeout: 3e3
|
|
2891
|
+
});
|
|
2892
|
+
return stdout.trim() || null;
|
|
2893
|
+
}
|
|
2894
|
+
} catch {
|
|
2895
|
+
return null;
|
|
2896
|
+
}
|
|
2897
|
+
return null;
|
|
2898
|
+
}
|
|
2725
2899
|
function sanitizeWorktreePaths(text) {
|
|
2726
2900
|
let cleaned = text.replace(/file:\/\/\/[^\s"'()]+?\/\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, "");
|
|
2727
2901
|
cleaned = cleaned.replace(/(?:^|[\s"'(`[])(?:\/[^\s"'()]+?)?\.jonah-fleet\/worktrees\/[^/\s"'()]+\//g, (match) => {
|
|
@@ -2756,16 +2930,29 @@ function extractExecutionSummary(output) {
|
|
|
2756
2930
|
return sanitizeWorktreePaths(summary);
|
|
2757
2931
|
}
|
|
2758
2932
|
function findLatestRunLog(repoRoot, routine) {
|
|
2933
|
+
const runsDir = path11.join(repoRoot, ".jonah-fleet", "runs");
|
|
2934
|
+
if (fs11.existsSync(runsDir)) {
|
|
2935
|
+
try {
|
|
2936
|
+
const files = fs11.readdirSync(runsDir).filter((f) => f.startsWith(`${routine}-`) && f.endsWith(".md"));
|
|
2937
|
+
if (files.length > 0) {
|
|
2938
|
+
files.sort().reverse();
|
|
2939
|
+
return path11.join(runsDir, files[0]);
|
|
2940
|
+
}
|
|
2941
|
+
} catch {
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2759
2944
|
const logsDir = path11.join(repoRoot, ".github", "prompts", "logs", routine);
|
|
2760
|
-
if (
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2945
|
+
if (fs11.existsSync(logsDir)) {
|
|
2946
|
+
try {
|
|
2947
|
+
const files = fs11.readdirSync(logsDir).filter((f) => f.endsWith(".md") && !f.startsWith("_"));
|
|
2948
|
+
if (files.length > 0) {
|
|
2949
|
+
files.sort().reverse();
|
|
2950
|
+
return path11.join(logsDir, files[0]);
|
|
2951
|
+
}
|
|
2952
|
+
} catch {
|
|
2953
|
+
}
|
|
2768
2954
|
}
|
|
2955
|
+
return null;
|
|
2769
2956
|
}
|
|
2770
2957
|
function parseRunLog(logContent) {
|
|
2771
2958
|
const summary = {
|
|
@@ -2890,14 +3077,38 @@ function detectClaimedIssue(chunk) {
|
|
|
2890
3077
|
function detectClaimedPR(chunk) {
|
|
2891
3078
|
const findingMatch = chunk.match(/(?:addressing\s+review\s+findings|fixing\s+review\s+findings)[^\n#]*?#(\d+)/i);
|
|
2892
3079
|
if (findingMatch) return `PR #${findingMatch[1]}`;
|
|
2893
|
-
const reviewMatch = chunk.match(/Starting\s+review[^\n#]*?#(\d+)/i);
|
|
3080
|
+
const reviewMatch = chunk.match(/Starting\s+review[^\n]*?(?:on\s+|PR\s+)#(\d+)/i) || chunk.match(/Starting\s+review(?![^\n]*?(?:run\s+log|tracking\s+log|log\s+issue))[^\n#]*?#(\d+)/i);
|
|
2894
3081
|
if (reviewMatch) return `PR #${reviewMatch[1]}`;
|
|
2895
3082
|
const prMatch = chunk.match(/(?:selected|target|reviewing)\s+(?:target\s+)?PR:?\s*\[?PR\s*#?(\d+)/i);
|
|
2896
3083
|
if (prMatch) return `PR #${prMatch[1]}`;
|
|
2897
|
-
const ghPrMatch = chunk.match(/gh\s+pr\s+(?:view|diff|checkout|review|edit|ready)\s+(\d+)/i);
|
|
3084
|
+
const ghPrMatch = chunk.match(/gh\s+pr\s+(?:view|diff|checkout|review|edit|ready|comment)\s+(\d+)/i);
|
|
2898
3085
|
if (ghPrMatch) return `PR #${ghPrMatch[1]}`;
|
|
2899
3086
|
return null;
|
|
2900
3087
|
}
|
|
3088
|
+
function cleanTargetTitle(title, maxLength = 28) {
|
|
3089
|
+
if (!title) return "";
|
|
3090
|
+
let cleaned = title.trim();
|
|
3091
|
+
cleaned = cleaned.replace(/^(?:feat|fix|chore|docs|refactor|test|perf|style|ci|build)(?:\([^)]+\))?!?!?:\s*/i, "");
|
|
3092
|
+
cleaned = cleaned.replace(/(?:\s*\(\s*#\d+\s*\))+$/, "");
|
|
3093
|
+
cleaned = cleaned.trim();
|
|
3094
|
+
if (!cleaned) return "";
|
|
3095
|
+
if (cleaned.length > maxLength) {
|
|
3096
|
+
const slice = cleaned.slice(0, maxLength);
|
|
3097
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
3098
|
+
if (lastSpace > maxLength - 8) {
|
|
3099
|
+
return slice.slice(0, lastSpace).trimEnd() + "...";
|
|
3100
|
+
}
|
|
3101
|
+
return slice.trimEnd() + "...";
|
|
3102
|
+
}
|
|
3103
|
+
return cleaned;
|
|
3104
|
+
}
|
|
3105
|
+
function formatTargetLabel(baseLabel, title, maxLength = 28) {
|
|
3106
|
+
if (!title) return baseLabel;
|
|
3107
|
+
const cleanBase = baseLabel.replace(/\s*\([^)]*\)$/, "").trim();
|
|
3108
|
+
const cleanedTitle = cleanTargetTitle(title, maxLength);
|
|
3109
|
+
if (!cleanedTitle) return cleanBase;
|
|
3110
|
+
return `${cleanBase} (${cleanedTitle})`;
|
|
3111
|
+
}
|
|
2901
3112
|
function formatActionDescription(toolName, params) {
|
|
2902
3113
|
const name = toolName || "unknown";
|
|
2903
3114
|
if (name === "run_command") {
|
|
@@ -3397,24 +3608,144 @@ function discoverSkillsPrompt(targetDir) {
|
|
|
3397
3608
|
function buildRoutinePrompt(targetDir, routine, options = {}) {
|
|
3398
3609
|
const skillsPrompt = discoverSkillsPrompt(targetDir);
|
|
3399
3610
|
const promptFile = `.github/prompts/${routine}.md`;
|
|
3611
|
+
const repoContext = `Working repository is located at ${targetDir}. All git, gh, and workspace commands must execute strictly within this repository.`;
|
|
3612
|
+
const logPrompt = options.routineIssueNumber ? ` Tracking run log issue: #${options.routineIssueNumber}.` : "";
|
|
3400
3613
|
if (routine === "autowork") {
|
|
3401
3614
|
if (options.issue) {
|
|
3402
|
-
return `You are the Autowork routine for this repository. Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}Your target is issue #${options.issue}. You are in Targeted mode: work issue #${options.issue} directly, ahead of Phase 1 convergence and priority scan
|
|
3615
|
+
return `You are the Autowork routine for this repository. ${repoContext} Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}Your target is issue #${options.issue}. You are in Targeted mode: work issue #${options.issue} directly, ahead of Phase 1 convergence and priority scan.${logPrompt}`;
|
|
3403
3616
|
}
|
|
3404
|
-
return `You are the Autowork routine for this repository. Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}You are in Scan mode: check open PRs for review comments to fix, close merged issues, then pick the highest-priority unclaimed issue
|
|
3617
|
+
return `You are the Autowork routine for this repository. ${repoContext} Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}You are in Scan mode: check open PRs for review comments to fix, close merged issues, then pick the highest-priority unclaimed issue.${logPrompt}`;
|
|
3405
3618
|
}
|
|
3406
3619
|
if (routine === "peer-review") {
|
|
3407
3620
|
if (options.pr) {
|
|
3408
|
-
return `You are the Peer Review routine for this repository. Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}Your target is pull request #${options.pr}. You are in Targeted mode: review PR #${options.pr} directly
|
|
3621
|
+
return `You are the Peer Review routine for this repository. ${repoContext} Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}Your target is pull request #${options.pr}. You are in Targeted mode: review PR #${options.pr} directly.${logPrompt}`;
|
|
3622
|
+
}
|
|
3623
|
+
return `You are the Peer Review routine for this repository. ${repoContext} Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}You are in Scan mode: check open PRs and select the highest-priority PR to review.${logPrompt}`;
|
|
3624
|
+
}
|
|
3625
|
+
return `You are the ${routine} routine for this repository. ${repoContext} Read and follow the instructions in ${promptFile} exactly. ${skillsPrompt}${logPrompt}`;
|
|
3626
|
+
}
|
|
3627
|
+
function tryCreateLocalRunIssue(cwd, routine, timestamp, targetLabel, hostname) {
|
|
3628
|
+
try {
|
|
3629
|
+
const title = `[${routine}] run ${timestamp} (local)`;
|
|
3630
|
+
const body = `### Autonomous Routine Execution in Progress (runner:local)
|
|
3631
|
+
- **Routine**: \`${routine}\`
|
|
3632
|
+
- **Timestamp**: \`${timestamp}\`
|
|
3633
|
+
- **Target**: \`${targetLabel}\`
|
|
3634
|
+
- **Host**: \`${hostname}\`
|
|
3635
|
+
|
|
3636
|
+
_Running via Jonah Fleet CLI runner._`;
|
|
3637
|
+
const labels = `routine-log,routine:${routine},status:running,runner:local`;
|
|
3638
|
+
const out = execSync2(`gh issue create --title ${JSON.stringify(title)} --body ${JSON.stringify(body)} --label ${JSON.stringify(labels)}`, {
|
|
3639
|
+
cwd,
|
|
3640
|
+
encoding: "utf8",
|
|
3641
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3642
|
+
}).trim();
|
|
3643
|
+
const match = out.match(/(\d+)$/);
|
|
3644
|
+
return match ? parseInt(match[1], 10) : void 0;
|
|
3645
|
+
} catch {
|
|
3646
|
+
return void 0;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
function formatInterruptionCard(options) {
|
|
3650
|
+
const lines = [
|
|
3651
|
+
`### \u274C Milestone: Run Interrupted / Failed`,
|
|
3652
|
+
`- **Routine**: \`${options.routine}\``,
|
|
3653
|
+
`- **Status**: Routine execution interrupted or failed (\`${options.status}\`)`,
|
|
3654
|
+
`- **Step**: ${options.step || "Execution halted before normal completion"}`
|
|
3655
|
+
];
|
|
3656
|
+
if (options.logUrl) {
|
|
3657
|
+
lines.push(`- **Action Log**: [View Run Logs](${options.logUrl})`);
|
|
3658
|
+
} else if (options.runner) {
|
|
3659
|
+
lines.push(`- **Runner**: \`${options.runner}\``);
|
|
3660
|
+
}
|
|
3661
|
+
return lines.join("\n");
|
|
3662
|
+
}
|
|
3663
|
+
function tryPostLocalRunMilestone(cwd, issueNumber, cardContent) {
|
|
3664
|
+
try {
|
|
3665
|
+
const tmpFile = path12.join(os2.tmpdir(), `jonah-fleet-milestone-${issueNumber}-${Date.now()}.md`);
|
|
3666
|
+
fs12.writeFileSync(tmpFile, cardContent, "utf8");
|
|
3667
|
+
try {
|
|
3668
|
+
execSync2(`gh issue comment ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
|
|
3669
|
+
cwd,
|
|
3670
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3671
|
+
});
|
|
3672
|
+
} finally {
|
|
3673
|
+
if (fs12.existsSync(tmpFile)) {
|
|
3674
|
+
fs12.unlinkSync(tmpFile);
|
|
3675
|
+
}
|
|
3409
3676
|
}
|
|
3410
|
-
|
|
3677
|
+
} catch {
|
|
3411
3678
|
}
|
|
3412
|
-
|
|
3679
|
+
}
|
|
3680
|
+
function tryReconcileLocalRunIssue(cwd, issueNumber, reportContent, exitCode, hostname, routine) {
|
|
3681
|
+
try {
|
|
3682
|
+
if (exitCode !== 0) {
|
|
3683
|
+
const interruptionCard = formatInterruptionCard({
|
|
3684
|
+
routine: routine || "local-routine",
|
|
3685
|
+
status: `exit code ${exitCode}`,
|
|
3686
|
+
runner: `local (${hostname})`
|
|
3687
|
+
});
|
|
3688
|
+
tryPostLocalRunMilestone(cwd, issueNumber, interruptionCard);
|
|
3689
|
+
}
|
|
3690
|
+
const finalBody = `${reportContent}
|
|
3691
|
+
|
|
3692
|
+
---
|
|
3693
|
+
_Generated by Jonah Fleet local runner on ${hostname}._`;
|
|
3694
|
+
const tmpFile = path12.join(os2.tmpdir(), `jonah-fleet-run-${issueNumber}-${Date.now()}.md`);
|
|
3695
|
+
fs12.writeFileSync(tmpFile, finalBody, "utf8");
|
|
3696
|
+
try {
|
|
3697
|
+
execSync2(`gh issue edit ${issueNumber} --body-file ${JSON.stringify(tmpFile)}`, {
|
|
3698
|
+
cwd,
|
|
3699
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3700
|
+
});
|
|
3701
|
+
if (exitCode === 0) {
|
|
3702
|
+
execSync2(`gh issue edit ${issueNumber} --add-label "status:success" --remove-label "status:running"`, {
|
|
3703
|
+
cwd,
|
|
3704
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3705
|
+
});
|
|
3706
|
+
execSync2(`gh issue close ${issueNumber} --reason completed`, {
|
|
3707
|
+
cwd,
|
|
3708
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3709
|
+
});
|
|
3710
|
+
} else {
|
|
3711
|
+
execSync2(`gh issue edit ${issueNumber} --add-label "status:failure,needs-attention" --remove-label "status:running"`, {
|
|
3712
|
+
cwd,
|
|
3713
|
+
stdio: ["ignore", "ignore", "ignore"]
|
|
3714
|
+
});
|
|
3715
|
+
}
|
|
3716
|
+
} finally {
|
|
3717
|
+
if (fs12.existsSync(tmpFile)) {
|
|
3718
|
+
fs12.unlinkSync(tmpFile);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
} catch {
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
function extractFreshRunReport(executionReportPath, targetReportPath, startTime) {
|
|
3725
|
+
try {
|
|
3726
|
+
if (fs12.existsSync(executionReportPath)) {
|
|
3727
|
+
const stat = fs12.statSync(executionReportPath);
|
|
3728
|
+
if (stat.mtimeMs >= startTime - 1e3) {
|
|
3729
|
+
return fs12.readFileSync(executionReportPath, "utf8");
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
} catch {
|
|
3733
|
+
}
|
|
3734
|
+
try {
|
|
3735
|
+
if (executionReportPath !== targetReportPath && fs12.existsSync(targetReportPath)) {
|
|
3736
|
+
const stat = fs12.statSync(targetReportPath);
|
|
3737
|
+
if (stat.mtimeMs >= startTime - 1e3) {
|
|
3738
|
+
return fs12.readFileSync(targetReportPath, "utf8");
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
} catch {
|
|
3742
|
+
}
|
|
3743
|
+
return null;
|
|
3413
3744
|
}
|
|
3414
3745
|
async function runLocalRoutine(options) {
|
|
3415
3746
|
const targetDir = path12.resolve(options.targetDir);
|
|
3416
3747
|
const routine = options.routine;
|
|
3417
|
-
const model = options.model || "gemini-3.
|
|
3748
|
+
const model = options.model || "gemini-3.8-flash-high";
|
|
3418
3749
|
const printTimeout = options.printTimeout || "30m";
|
|
3419
3750
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3420
3751
|
const hostname = os2.hostname();
|
|
@@ -3435,9 +3766,18 @@ async function runLocalRoutine(options) {
|
|
|
3435
3766
|
worktreePath = worktreeResult.worktreePath;
|
|
3436
3767
|
executionDir = worktreePath;
|
|
3437
3768
|
}
|
|
3769
|
+
let targetTitle = options.title;
|
|
3770
|
+
const baseTarget = options.pr ? `PR #${options.pr}` : options.issue ? `Issue #${options.issue}` : void 0;
|
|
3771
|
+
let targetLabel = baseTarget ? formatTargetLabel(baseTarget, targetTitle) : routine;
|
|
3772
|
+
let dynamicTargetDetected = Boolean(options.pr || options.issue);
|
|
3773
|
+
let routineIssueNumber;
|
|
3774
|
+
if (!options.dryRun) {
|
|
3775
|
+
routineIssueNumber = tryCreateLocalRunIssue(targetDir, routine, timestamp, targetLabel, hostname);
|
|
3776
|
+
}
|
|
3438
3777
|
const prompt = buildRoutinePrompt(executionDir, routine, {
|
|
3439
3778
|
issue: options.issue,
|
|
3440
|
-
pr: options.pr
|
|
3779
|
+
pr: options.pr,
|
|
3780
|
+
routineIssueNumber
|
|
3441
3781
|
});
|
|
3442
3782
|
if (options.dryRun) {
|
|
3443
3783
|
return {
|
|
@@ -3459,6 +3799,9 @@ Timeout: ${printTimeout}`,
|
|
|
3459
3799
|
TARGET_ISSUE: options.issue ? String(options.issue) : "",
|
|
3460
3800
|
PR_NUMBER: options.pr ? String(options.pr) : ""
|
|
3461
3801
|
};
|
|
3802
|
+
if (routineIssueNumber) {
|
|
3803
|
+
childEnv.ROUTINE_ISSUE_NUMBER = String(routineIssueNumber);
|
|
3804
|
+
}
|
|
3462
3805
|
const args = buildAgyArgs(prompt, model, printTimeout);
|
|
3463
3806
|
let output = "";
|
|
3464
3807
|
let finalResponseText = "";
|
|
@@ -3468,13 +3811,41 @@ Timeout: ${printTimeout}`,
|
|
|
3468
3811
|
const logDir = path12.join(targetDir, ".jonah-fleet");
|
|
3469
3812
|
fs12.mkdirSync(logDir, { recursive: true });
|
|
3470
3813
|
const logFilePath = path12.join(logDir, "daemon.log");
|
|
3471
|
-
|
|
3472
|
-
|
|
3814
|
+
const runsDir = path12.join(logDir, "runs");
|
|
3815
|
+
fs12.mkdirSync(runsDir, { recursive: true });
|
|
3816
|
+
const executionReportPath = path12.join(executionDir, ".jonah-fleet", "run-report.md");
|
|
3817
|
+
const targetReportPath = path12.join(targetDir, ".jonah-fleet", "run-report.md");
|
|
3818
|
+
try {
|
|
3819
|
+
if (fs12.existsSync(executionReportPath)) {
|
|
3820
|
+
fs12.unlinkSync(executionReportPath);
|
|
3821
|
+
}
|
|
3822
|
+
} catch {
|
|
3823
|
+
}
|
|
3824
|
+
try {
|
|
3825
|
+
if (fs12.existsSync(targetReportPath)) {
|
|
3826
|
+
fs12.unlinkSync(targetReportPath);
|
|
3827
|
+
}
|
|
3828
|
+
} catch {
|
|
3829
|
+
}
|
|
3473
3830
|
let activePhase = "Starting session...";
|
|
3831
|
+
let lastActionDesc = null;
|
|
3474
3832
|
const spinner = !options.verbose ? new TerminalSpinner() : null;
|
|
3475
3833
|
if (spinner) {
|
|
3476
3834
|
spinner.start(`${targetLabel}: ${activePhase}`);
|
|
3477
3835
|
}
|
|
3836
|
+
if (baseTarget && !targetTitle) {
|
|
3837
|
+
fetchTargetTitleAsync(targetDir, baseTarget).then((fetchedTitle) => {
|
|
3838
|
+
if (fetchedTitle && !targetTitle) {
|
|
3839
|
+
targetTitle = fetchedTitle;
|
|
3840
|
+
targetLabel = formatTargetLabel(baseTarget, targetTitle);
|
|
3841
|
+
options.onTargetDetected?.(targetLabel);
|
|
3842
|
+
if (spinner) {
|
|
3843
|
+
spinner.update(`${targetLabel}: ${lastActionDesc || activePhase}`);
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
}).catch(() => {
|
|
3847
|
+
});
|
|
3848
|
+
}
|
|
3478
3849
|
const cleanup = async () => {
|
|
3479
3850
|
spinner?.stop();
|
|
3480
3851
|
if (worktreePath && !options.keepWorktree) {
|
|
@@ -3496,8 +3867,19 @@ Timeout: ${printTimeout}`,
|
|
|
3496
3867
|
targetLabel = detected;
|
|
3497
3868
|
options.onTargetDetected?.(detected);
|
|
3498
3869
|
if (spinner) {
|
|
3499
|
-
spinner.update(`${targetLabel}: ${activePhase}`);
|
|
3870
|
+
spinner.update(`${targetLabel}: ${lastActionDesc || activePhase}`);
|
|
3500
3871
|
}
|
|
3872
|
+
fetchTargetTitleAsync(executionDir, detected).then((fetchedTitle) => {
|
|
3873
|
+
if (fetchedTitle) {
|
|
3874
|
+
targetTitle = fetchedTitle;
|
|
3875
|
+
targetLabel = formatTargetLabel(detected, fetchedTitle);
|
|
3876
|
+
options.onTargetDetected?.(targetLabel);
|
|
3877
|
+
if (spinner) {
|
|
3878
|
+
spinner.update(`${targetLabel}: ${lastActionDesc || activePhase}`);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
}).catch(() => {
|
|
3882
|
+
});
|
|
3501
3883
|
}
|
|
3502
3884
|
};
|
|
3503
3885
|
const stdoutParser = new LineBufferedStreamParser((line) => {
|
|
@@ -3510,6 +3892,7 @@ Timeout: ${printTimeout}`,
|
|
|
3510
3892
|
const toolParams = su.tool_info?.parameters;
|
|
3511
3893
|
if (su.state === "ACTIVE") {
|
|
3512
3894
|
const actionDesc = formatActionDescription(toolName, toolParams);
|
|
3895
|
+
lastActionDesc = actionDesc;
|
|
3513
3896
|
if (spinner) {
|
|
3514
3897
|
spinner.update(`${targetLabel}: ${actionDesc}`);
|
|
3515
3898
|
}
|
|
@@ -3521,6 +3904,7 @@ Timeout: ${printTimeout}`,
|
|
|
3521
3904
|
if (formatted) console.log(formatted);
|
|
3522
3905
|
}
|
|
3523
3906
|
} else if (su.state === "DONE") {
|
|
3907
|
+
lastActionDesc = null;
|
|
3524
3908
|
if (su.tool_info?.output) {
|
|
3525
3909
|
checkTargetDetection(su.tool_info.output);
|
|
3526
3910
|
}
|
|
@@ -3538,7 +3922,8 @@ Timeout: ${printTimeout}`,
|
|
|
3538
3922
|
accumulatedOutput += su.text_delta;
|
|
3539
3923
|
checkTargetDetection(su.text_delta);
|
|
3540
3924
|
const newPhase = detectActivePhase(su.text_delta, activePhase);
|
|
3541
|
-
if (newPhase !== activePhase) {
|
|
3925
|
+
if (newPhase !== activePhase || lastActionDesc) {
|
|
3926
|
+
lastActionDesc = null;
|
|
3542
3927
|
activePhase = newPhase;
|
|
3543
3928
|
if (spinner) {
|
|
3544
3929
|
spinner.update(`${targetLabel}: ${activePhase}`);
|
|
@@ -3576,6 +3961,7 @@ Timeout: ${printTimeout}`,
|
|
|
3576
3961
|
if (options.verbose) {
|
|
3577
3962
|
console.log(line);
|
|
3578
3963
|
} else if (spinner) {
|
|
3964
|
+
lastActionDesc = null;
|
|
3579
3965
|
const newPhase = detectActivePhase(line, activePhase);
|
|
3580
3966
|
if (newPhase !== activePhase) {
|
|
3581
3967
|
activePhase = newPhase;
|
|
@@ -3589,6 +3975,7 @@ Timeout: ${printTimeout}`,
|
|
|
3589
3975
|
if (options.verbose) {
|
|
3590
3976
|
console.error(pc10.dim(`[stderr] ${line}`));
|
|
3591
3977
|
} else if (spinner) {
|
|
3978
|
+
lastActionDesc = null;
|
|
3592
3979
|
const newPhase = detectActivePhase(line, activePhase);
|
|
3593
3980
|
if (newPhase !== activePhase) {
|
|
3594
3981
|
activePhase = newPhase;
|
|
@@ -3643,10 +4030,68 @@ Timeout: ${printTimeout}`,
|
|
|
3643
4030
|
stdoutParser.flush();
|
|
3644
4031
|
stderrParser.flush();
|
|
3645
4032
|
output = finalResponseText || accumulatedOutput;
|
|
4033
|
+
const durationMs = Date.now() - startTime;
|
|
4034
|
+
const durationSec = Math.round(durationMs / 1e3);
|
|
4035
|
+
let reportContent = extractFreshRunReport(executionReportPath, targetReportPath, startTime) || "";
|
|
4036
|
+
if (!reportContent) {
|
|
4037
|
+
reportContent = `## Run Summary
|
|
4038
|
+
|
|
4039
|
+
| Metric | Value |
|
|
4040
|
+
|---|---|
|
|
4041
|
+
| Routine | \`${routine}\` |
|
|
4042
|
+
| Timestamp | \`${timestamp}\` |
|
|
4043
|
+
| Result | \`${exitCode === 0 ? "SUCCESS" : "FAILURE"}\` |
|
|
4044
|
+
| Exit Code | \`${exitCode}\` |
|
|
4045
|
+
| Host | \`${hostname}\` |
|
|
4046
|
+
| Target | \`${targetLabel}\` |
|
|
4047
|
+
| Duration | \`${durationSec}s\` |
|
|
4048
|
+
`;
|
|
4049
|
+
if (exitCode !== 0 && output.trim()) {
|
|
4050
|
+
const sanitizedOutput = stripAnsi(output.trim()).split("\n").slice(-15).join("\n");
|
|
4051
|
+
reportContent += `
|
|
4052
|
+
### Error Output
|
|
4053
|
+
|
|
4054
|
+
\`\`\`
|
|
4055
|
+
${sanitizedOutput}
|
|
4056
|
+
\`\`\`
|
|
4057
|
+
`;
|
|
4058
|
+
}
|
|
4059
|
+
}
|
|
4060
|
+
try {
|
|
4061
|
+
fs12.writeFileSync(targetReportPath, reportContent, "utf8");
|
|
4062
|
+
} catch {
|
|
4063
|
+
}
|
|
4064
|
+
const runReportFile = path12.join(runsDir, `${routine}-${timestamp}.md`);
|
|
4065
|
+
const runMetaFile = path12.join(runsDir, `${routine}-${timestamp}.json`);
|
|
4066
|
+
try {
|
|
4067
|
+
fs12.writeFileSync(runReportFile, reportContent, "utf8");
|
|
4068
|
+
fs12.writeFileSync(
|
|
4069
|
+
runMetaFile,
|
|
4070
|
+
JSON.stringify(
|
|
4071
|
+
{
|
|
4072
|
+
routine,
|
|
4073
|
+
timestamp,
|
|
4074
|
+
exitCode,
|
|
4075
|
+
success: exitCode === 0,
|
|
4076
|
+
target: targetLabel,
|
|
4077
|
+
issueNumber: routineIssueNumber,
|
|
4078
|
+
durationMs
|
|
4079
|
+
},
|
|
4080
|
+
null,
|
|
4081
|
+
2
|
|
4082
|
+
),
|
|
4083
|
+
"utf8"
|
|
4084
|
+
);
|
|
4085
|
+
} catch {
|
|
4086
|
+
}
|
|
4087
|
+
if (routineIssueNumber) {
|
|
4088
|
+
tryReconcileLocalRunIssue(targetDir, routineIssueNumber, reportContent, exitCode, hostname, routine);
|
|
4089
|
+
}
|
|
3646
4090
|
if (options.showCard !== false && !options.verbose) {
|
|
3647
|
-
const
|
|
3648
|
-
const
|
|
3649
|
-
const
|
|
4091
|
+
const prMatch = targetLabel.match(/PR\s*#?(\d+)/i);
|
|
4092
|
+
const issueMatch = targetLabel.match(/Issue\s*#?(\d+)/i);
|
|
4093
|
+
const effectiveIssue = options.issue || (issueMatch ? issueMatch[1] : void 0);
|
|
4094
|
+
const effectivePR = options.pr || (prMatch ? prMatch[1] : void 0);
|
|
3650
4095
|
if (exitCode === 0) {
|
|
3651
4096
|
console.log(
|
|
3652
4097
|
"\n" + renderSummaryCard({
|
|
@@ -3655,6 +4100,7 @@ Timeout: ${printTimeout}`,
|
|
|
3655
4100
|
repoRoot: targetDir,
|
|
3656
4101
|
issue: effectiveIssue,
|
|
3657
4102
|
pr: effectivePR,
|
|
4103
|
+
title: targetTitle,
|
|
3658
4104
|
durationMs
|
|
3659
4105
|
}) + "\n"
|
|
3660
4106
|
);
|
|
@@ -3676,7 +4122,8 @@ Timeout: ${printTimeout}`,
|
|
|
3676
4122
|
exitCode,
|
|
3677
4123
|
output,
|
|
3678
4124
|
worktreePath,
|
|
3679
|
-
branchName
|
|
4125
|
+
branchName,
|
|
4126
|
+
issueNumber: routineIssueNumber
|
|
3680
4127
|
};
|
|
3681
4128
|
}
|
|
3682
4129
|
|
|
@@ -3751,8 +4198,8 @@ import pc14 from "picocolors";
|
|
|
3751
4198
|
// src/lib/daemon.ts
|
|
3752
4199
|
import fs14 from "fs";
|
|
3753
4200
|
import path14 from "path";
|
|
3754
|
-
import { spawn as spawn2, execFile as
|
|
3755
|
-
import { promisify as
|
|
4201
|
+
import { spawn as spawn2, execFile as execFile4 } from "child_process";
|
|
4202
|
+
import { promisify as promisify4 } from "util";
|
|
3756
4203
|
|
|
3757
4204
|
// src/lib/daemon-keys.ts
|
|
3758
4205
|
import readline2 from "readline";
|
|
@@ -3796,6 +4243,12 @@ var KeyboardController = class {
|
|
|
3796
4243
|
}
|
|
3797
4244
|
resume() {
|
|
3798
4245
|
this.isPaused = false;
|
|
4246
|
+
if (this.listening && this.stdin && typeof this.stdin.resume === "function") {
|
|
4247
|
+
try {
|
|
4248
|
+
this.stdin.resume();
|
|
4249
|
+
} catch {
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
3799
4252
|
}
|
|
3800
4253
|
handleKeypress(str, key) {
|
|
3801
4254
|
if (this.isPaused) return;
|
|
@@ -3928,6 +4381,12 @@ async function promptTargetedInput(promptMessage, options = {}) {
|
|
|
3928
4381
|
} catch {
|
|
3929
4382
|
}
|
|
3930
4383
|
}
|
|
4384
|
+
if (stdin && typeof stdin.resume === "function") {
|
|
4385
|
+
try {
|
|
4386
|
+
stdin.resume();
|
|
4387
|
+
} catch {
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
3931
4390
|
resolve(val);
|
|
3932
4391
|
};
|
|
3933
4392
|
const onRawData = (chunk) => {
|
|
@@ -4129,7 +4588,7 @@ function formatDaemonStatusLine(options = {}) {
|
|
|
4129
4588
|
|
|
4130
4589
|
// src/lib/daemon.ts
|
|
4131
4590
|
import pc13 from "picocolors";
|
|
4132
|
-
var
|
|
4591
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
4133
4592
|
function getDaemonStatePath(repoRoot) {
|
|
4134
4593
|
return path14.join(repoRoot, ".jonah-fleet", "daemon.json");
|
|
4135
4594
|
}
|
|
@@ -4174,7 +4633,7 @@ function filterReviewablePRs(prs) {
|
|
|
4174
4633
|
}
|
|
4175
4634
|
async function getOpenReviewablePRs(repoRoot) {
|
|
4176
4635
|
try {
|
|
4177
|
-
const { stdout } = await
|
|
4636
|
+
const { stdout } = await execFileAsync4(
|
|
4178
4637
|
"gh",
|
|
4179
4638
|
["pr", "list", "--state", "open", "--draft=false", "--json", "number,headRefName,title"],
|
|
4180
4639
|
{ cwd: repoRoot }
|
|
@@ -4366,6 +4825,7 @@ async function runDaemonLoop(repoRoot, options = {}) {
|
|
|
4366
4825
|
let isGracefulStopping = false;
|
|
4367
4826
|
let isWorking = false;
|
|
4368
4827
|
let isPaused = false;
|
|
4828
|
+
let isPrompting = false;
|
|
4369
4829
|
let pendingRoutine = null;
|
|
4370
4830
|
let keyboard;
|
|
4371
4831
|
let tickerInterval;
|
|
@@ -4383,7 +4843,7 @@ async function runDaemonLoop(repoRoot, options = {}) {
|
|
|
4383
4843
|
}
|
|
4384
4844
|
};
|
|
4385
4845
|
const updateTicker = () => {
|
|
4386
|
-
if (isStopping || isWorking || options.verbose || !process.stderr.isTTY) return;
|
|
4846
|
+
if (isStopping || isWorking || isPrompting || options.verbose || !process.stderr.isTTY) return;
|
|
4387
4847
|
const line = formatDaemonStatusLine({
|
|
4388
4848
|
now: Date.now(),
|
|
4389
4849
|
isPaused,
|
|
@@ -4418,6 +4878,7 @@ Stopping local agent daemon...`));
|
|
|
4418
4878
|
if (isStopping || isWorking) return;
|
|
4419
4879
|
try {
|
|
4420
4880
|
isWorking = true;
|
|
4881
|
+
nextReviewCheckTime = Date.now() + reviewIntervalMs;
|
|
4421
4882
|
await drainReviewQueue({
|
|
4422
4883
|
repoRoot,
|
|
4423
4884
|
state,
|
|
@@ -4456,34 +4917,42 @@ Stopping local agent daemon...`));
|
|
|
4456
4917
|
};
|
|
4457
4918
|
const runAutoworkCheck = async () => {
|
|
4458
4919
|
if (isStopping || isWorking || !routines.includes("autowork")) return;
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u23F3 Autowork paused: draining ${pendingPRs} reviewable PR(s) first...`
|
|
4466
|
-
)
|
|
4467
|
-
);
|
|
4468
|
-
await performReviewDrain();
|
|
4469
|
-
const remainingPRs = (await getPRsFn(repoRoot)).length;
|
|
4470
|
-
if (remainingPRs > 0) {
|
|
4920
|
+
try {
|
|
4921
|
+
isWorking = true;
|
|
4922
|
+
nextAutoworkCheckTime = Date.now() + autoworkIntervalMs;
|
|
4923
|
+
if (routines.includes("peer-review")) {
|
|
4924
|
+
const pendingPRs = (await getPRsFn(repoRoot)).length;
|
|
4925
|
+
if (pendingPRs > 0) {
|
|
4471
4926
|
console.log(
|
|
4472
|
-
pc13.
|
|
4927
|
+
pc13.cyan(
|
|
4473
4928
|
`
|
|
4474
|
-
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \
|
|
4929
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u23F3 Autowork paused: draining ${pendingPRs} reviewable PR(s) first...`
|
|
4475
4930
|
)
|
|
4476
4931
|
);
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4932
|
+
await drainReviewQueue({
|
|
4933
|
+
repoRoot,
|
|
4934
|
+
state,
|
|
4935
|
+
options,
|
|
4936
|
+
isStopping: () => isStopping,
|
|
4937
|
+
clearTicker,
|
|
4938
|
+
getPRs: getPRsFn,
|
|
4939
|
+
runRoutine: runRoutineFn
|
|
4940
|
+
});
|
|
4941
|
+
const prs = await getPRsFn(repoRoot);
|
|
4942
|
+
lastOpenPRCount = prs.length;
|
|
4943
|
+
const remainingPRs = (await getPRsFn(repoRoot)).length;
|
|
4944
|
+
if (remainingPRs > 0) {
|
|
4945
|
+
console.log(
|
|
4946
|
+
pc13.yellow(
|
|
4947
|
+
`
|
|
4948
|
+
[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] \u26A0\uFE0F Review backlog still has ${remainingPRs} pending PR(s). Postponing autowork session.`
|
|
4949
|
+
)
|
|
4950
|
+
);
|
|
4951
|
+
return;
|
|
4952
|
+
}
|
|
4480
4953
|
}
|
|
4481
4954
|
}
|
|
4482
|
-
|
|
4483
|
-
state.lastAutoworkCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4484
|
-
writeDaemonState(repoRoot, state);
|
|
4485
|
-
try {
|
|
4486
|
-
isWorking = true;
|
|
4955
|
+
state.lastAutoworkCheckAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4487
4956
|
clearTicker();
|
|
4488
4957
|
state.status = "working";
|
|
4489
4958
|
state.activeRoutine = "autowork";
|
|
@@ -4715,7 +5184,7 @@ Stopping local agent daemon...`));
|
|
|
4715
5184
|
await runAutoworkCheck();
|
|
4716
5185
|
},
|
|
4717
5186
|
onTargetedReview: async () => {
|
|
4718
|
-
if (isStopping || isGracefulStopping) return;
|
|
5187
|
+
if (isStopping || isGracefulStopping || isPrompting) return;
|
|
4719
5188
|
if (isWorking) {
|
|
4720
5189
|
clearTicker();
|
|
4721
5190
|
console.log(
|
|
@@ -4729,12 +5198,18 @@ Stopping local agent daemon...`));
|
|
|
4729
5198
|
}
|
|
4730
5199
|
clearTicker();
|
|
4731
5200
|
keyboard?.pause();
|
|
4732
|
-
|
|
5201
|
+
isPrompting = true;
|
|
5202
|
+
let rawInput = null;
|
|
5203
|
+
try {
|
|
5204
|
+
rawInput = await promptTargetedInput(`
|
|
4733
5205
|
${pc13.cyan("Enter PR # to review (Esc/Enter to cancel):")} `, {
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5206
|
+
stdin: options.stdin || process.stdin,
|
|
5207
|
+
stdout: process.stdout
|
|
5208
|
+
});
|
|
5209
|
+
} finally {
|
|
5210
|
+
isPrompting = false;
|
|
5211
|
+
keyboard?.resume();
|
|
5212
|
+
}
|
|
4738
5213
|
if (!rawInput) {
|
|
4739
5214
|
console.log(pc13.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Targeted review cancelled.
|
|
4740
5215
|
`));
|
|
@@ -4753,7 +5228,7 @@ ${pc13.cyan("Enter PR # to review (Esc/Enter to cancel):")} `, {
|
|
|
4753
5228
|
await runTargetedReview(prNumber);
|
|
4754
5229
|
},
|
|
4755
5230
|
onTargetedAutowork: async () => {
|
|
4756
|
-
if (isStopping || isGracefulStopping) return;
|
|
5231
|
+
if (isStopping || isGracefulStopping || isPrompting) return;
|
|
4757
5232
|
if (isWorking) {
|
|
4758
5233
|
clearTicker();
|
|
4759
5234
|
console.log(
|
|
@@ -4767,12 +5242,18 @@ ${pc13.cyan("Enter PR # to review (Esc/Enter to cancel):")} `, {
|
|
|
4767
5242
|
}
|
|
4768
5243
|
clearTicker();
|
|
4769
5244
|
keyboard?.pause();
|
|
4770
|
-
|
|
5245
|
+
isPrompting = true;
|
|
5246
|
+
let rawInput = null;
|
|
5247
|
+
try {
|
|
5248
|
+
rawInput = await promptTargetedInput(`
|
|
4771
5249
|
${pc13.cyan("Enter Issue # to work (Esc/Enter to cancel):")} `, {
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
5250
|
+
stdin: options.stdin || process.stdin,
|
|
5251
|
+
stdout: process.stdout
|
|
5252
|
+
});
|
|
5253
|
+
} finally {
|
|
5254
|
+
isPrompting = false;
|
|
5255
|
+
keyboard?.resume();
|
|
5256
|
+
}
|
|
4776
5257
|
if (!rawInput) {
|
|
4777
5258
|
console.log(pc13.dim(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Targeted autowork cancelled.
|
|
4778
5259
|
`));
|
|
@@ -4888,20 +5369,26 @@ ${pc13.cyan("Enter Issue # to work (Esc/Enter to cancel):")} `, {
|
|
|
4888
5369
|
await runAutoworkCheck();
|
|
4889
5370
|
}
|
|
4890
5371
|
if (isStopping) return;
|
|
5372
|
+
let isTicking = false;
|
|
4891
5373
|
const tick = async () => {
|
|
4892
|
-
if (isStopping || isGracefulStopping || isWorking) return;
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
if (
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
5374
|
+
if (isStopping || isGracefulStopping || isWorking || isPrompting || isTicking) return;
|
|
5375
|
+
isTicking = true;
|
|
5376
|
+
try {
|
|
5377
|
+
if (!isPaused) {
|
|
5378
|
+
const now = Date.now();
|
|
5379
|
+
if (routines.includes("peer-review") && now >= nextReviewCheckTime) {
|
|
5380
|
+
await performReviewDrain();
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
if (routines.includes("autowork") && now >= nextAutoworkCheckTime) {
|
|
5384
|
+
await runAutoworkCheck();
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
4902
5387
|
}
|
|
5388
|
+
updateTicker();
|
|
5389
|
+
} finally {
|
|
5390
|
+
isTicking = false;
|
|
4903
5391
|
}
|
|
4904
|
-
updateTicker();
|
|
4905
5392
|
};
|
|
4906
5393
|
tickerInterval = setInterval(tick, 1e3);
|
|
4907
5394
|
await new Promise((resolve) => {
|
|
@@ -5017,6 +5504,34 @@ async function runLabels(action = "audit", options = {}) {
|
|
|
5017
5504
|
...DEFAULT_PROTECTED_LABEL_PATTERNS,
|
|
5018
5505
|
...userProtected
|
|
5019
5506
|
];
|
|
5507
|
+
if (action === "provision" || action === "sync") {
|
|
5508
|
+
const result = await provisionLabels({ repo, cwd, executor });
|
|
5509
|
+
if (options.json) {
|
|
5510
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5511
|
+
return;
|
|
5512
|
+
}
|
|
5513
|
+
console.log(pc15.bold(pc15.cyan(`
|
|
5514
|
+
\u{1F3F7}\uFE0F Fleet Label Provisioning for ${repo}
|
|
5515
|
+
`)));
|
|
5516
|
+
if (result.created.length > 0) {
|
|
5517
|
+
console.log(pc15.green(` \u2713 Created ${result.created.length} missing label(s):`));
|
|
5518
|
+
for (const label of result.created) {
|
|
5519
|
+
console.log(` - ${pc15.green(label)}`);
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
if (result.alreadyExists.length > 0) {
|
|
5523
|
+
console.log(pc15.dim(` \u2139 ${result.alreadyExists.length} label(s) already exist.`));
|
|
5524
|
+
}
|
|
5525
|
+
if (result.errors.length > 0) {
|
|
5526
|
+
console.log(pc15.red(`
|
|
5527
|
+
\u274C Failed to create ${result.errors.length} label(s):`));
|
|
5528
|
+
for (const err of result.errors) {
|
|
5529
|
+
console.log(` - ${pc15.red(err.label)}: ${err.error}`);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
console.log();
|
|
5533
|
+
return;
|
|
5534
|
+
}
|
|
5020
5535
|
if (action === "prune") {
|
|
5021
5536
|
const isDryRun = Boolean(options.dryRun);
|
|
5022
5537
|
const result = await pruneLabels({
|
|
@@ -5118,14 +5633,14 @@ async function runLabels(action = "audit", options = {}) {
|
|
|
5118
5633
|
// src/index.ts
|
|
5119
5634
|
var program = new Command();
|
|
5120
5635
|
program.name("jonah-fleet").description("Manage autonomous agent fleet, prompt routines, workflows, and skills").version(FLEET_VERSION);
|
|
5121
|
-
program.command("run <routine>").description("Run a specific prompt routine locally in an isolated git worktree").option("-i, --issue <number>", "Targeted issue number for autowork").option("-p, --pr <number>", "Targeted pull request number for peer-review").option("-m, --model <model>", "LLM model override (defaults to gemini-3.
|
|
5636
|
+
program.command("run <routine>").description("Run a specific prompt routine locally in an isolated git worktree").option("-i, --issue <number>", "Targeted issue number for autowork").option("-p, --pr <number>", "Targeted pull request number for peer-review").option("-m, --model <model>", "LLM model override (defaults to gemini-3.8-flash-high)").option("--timeout <duration>", "CLI execution print timeout (default: 30m)").option("--no-worktree", "Execute directly in current directory without creating a git worktree").option("--keep-worktree", "Preserve the git worktree after routine execution completes").option("-d, --dry-run", "Preview prompt and execution parameters without launching agent").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (routine, options) => {
|
|
5122
5637
|
await runRoutineCommand(routine, options);
|
|
5123
5638
|
});
|
|
5124
5639
|
program.command("daemon [action]").description("Manage background local worker daemon polling for unclaimed issues and pull requests").option("-i, --interval <minutes>", "Legacy global polling interval in minutes (default: 30)").option("--review-interval <minutes>", "Peer Review watchdog cadence in minutes (default: 3)").option("--autowork-interval <minutes>", "Autowork backlog cadence in minutes (default: 30)").option("-r, --routines <list>", "Comma-separated routines to run (default: peer-review,autowork)").option("-m, --model <model>", "LLM model override").option("--foreground", "Run daemon in foreground with live console logs").option("-v, --verbose", "Stream raw agent tokens and logs directly to stdout").action(async (action, options) => {
|
|
5125
5640
|
await runDaemonCommand(action, options);
|
|
5126
5641
|
});
|
|
5127
|
-
program.command("labels [action]").description("Audit
|
|
5128
|
-
const act = action === "prune" || action === "list" || action === "audit" ? action : "audit";
|
|
5642
|
+
program.command("labels [action]").description("Audit, prune, or provision repository labels for autonomous fleet taxonomy (audit | prune | provision)").option("-d, --dry-run", "Preview prunable labels without deleting them", false).option("-y, --yes", "Confirm automatic deletion of prunable labels", false).option("-r, --repo <repo>", "Target GitHub repository (defaults to current)").option("-j, --json", "Output results as JSON", false).action(async (action, options) => {
|
|
5643
|
+
const act = action === "prune" || action === "list" || action === "audit" || action === "provision" || action === "sync" ? action : "audit";
|
|
5129
5644
|
await runLabels(act, options);
|
|
5130
5645
|
});
|
|
5131
5646
|
program.command("init").description("Initialize Jonah Fleet configuration, routines, workflows, and skills in the current repo").option("-p, --preset <preset>", "Preset profile to install (minimal | standard | full)", "standard").option("-f, --force", "Force overwrite existing files", false).option("--stack <stack>", "Override detected tech stack name").option("--package-manager <pm>", "Override package manager (npm, pnpm, yarn, bun, uv, poetry, cargo, go)").option("--test-cmd <cmd>", "Override test execution command").option("--build-cmd <cmd>", "Override build execution command").option("--prune-labels", "Prune unused boilerplate labels on initialization", false).option("--interactive", "Force interactive prompts for stack configuration").option("--no-interactive", "Disable interactive prompts").action(async (options) => {
|