automata-cli 0.6.0-develop.293 → 0.6.0-develop.296
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/dist/index.js +207 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3782,6 +3782,165 @@ function runRepoHygiene(options, now = /* @__PURE__ */ new Date()) {
|
|
|
3782
3782
|
return { rescue, base, prunes: outcomes, degraded };
|
|
3783
3783
|
}
|
|
3784
3784
|
|
|
3785
|
+
// src/run/operationLog.ts
|
|
3786
|
+
import {
|
|
3787
|
+
accessSync,
|
|
3788
|
+
appendFileSync,
|
|
3789
|
+
constants,
|
|
3790
|
+
readFileSync as readFileSync4,
|
|
3791
|
+
renameSync as renameSync2,
|
|
3792
|
+
unlinkSync as unlinkSync2,
|
|
3793
|
+
writeFileSync as writeFileSync2
|
|
3794
|
+
} from "fs";
|
|
3795
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3796
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
3797
|
+
var EXECUTION_LOG_FILE = "automata-execution.log";
|
|
3798
|
+
var WORK_LOG_FILE = "automata-work.log";
|
|
3799
|
+
var MAX_EXECUTION_LINES = 1e3;
|
|
3800
|
+
var WORK_RECORD_MAX_AGE_DAYS = 30;
|
|
3801
|
+
var MAX_DETAIL_LENGTH = 200;
|
|
3802
|
+
var OUTCOMES = [
|
|
3803
|
+
"answered",
|
|
3804
|
+
"answered-no-reply",
|
|
3805
|
+
"skipped",
|
|
3806
|
+
"failed",
|
|
3807
|
+
"deferred"
|
|
3808
|
+
];
|
|
3809
|
+
function operationLogDirectory() {
|
|
3810
|
+
return dirname2(process.cwd());
|
|
3811
|
+
}
|
|
3812
|
+
function repoField(repo) {
|
|
3813
|
+
if (repo === null) return "-";
|
|
3814
|
+
const flat = oneLine(repo);
|
|
3815
|
+
return flat.length === 0 ? "-" : flat;
|
|
3816
|
+
}
|
|
3817
|
+
function formatExecutionLine(tick) {
|
|
3818
|
+
const counts = {
|
|
3819
|
+
answered: 0,
|
|
3820
|
+
"answered-no-reply": 0,
|
|
3821
|
+
skipped: 0,
|
|
3822
|
+
failed: 0,
|
|
3823
|
+
deferred: 0
|
|
3824
|
+
};
|
|
3825
|
+
let runs = 0;
|
|
3826
|
+
for (const item of tick.items) {
|
|
3827
|
+
counts[item.outcome]++;
|
|
3828
|
+
if (item.ranExecutor) runs++;
|
|
3829
|
+
}
|
|
3830
|
+
const fields = [
|
|
3831
|
+
tick.timestamp.toISOString(),
|
|
3832
|
+
tick.command,
|
|
3833
|
+
`repo=${repoField(tick.repo)}`,
|
|
3834
|
+
`items=${String(tick.items.length)}`,
|
|
3835
|
+
...OUTCOMES.map((outcome) => `${outcome}=${String(counts[outcome])}`),
|
|
3836
|
+
`runs=${String(runs)}`,
|
|
3837
|
+
`exit=${String(tick.exitCode)}`,
|
|
3838
|
+
`dur=${(tick.durationMs / 1e3).toFixed(1)}s`
|
|
3839
|
+
];
|
|
3840
|
+
if (tick.note !== void 0 && tick.note.length > 0) fields.push(`note=${tick.note}`);
|
|
3841
|
+
return fields.join(" ") + "\n";
|
|
3842
|
+
}
|
|
3843
|
+
function oneLine(value) {
|
|
3844
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3845
|
+
}
|
|
3846
|
+
function briefDetail(detail) {
|
|
3847
|
+
const flat = oneLine(detail);
|
|
3848
|
+
return flat.length <= MAX_DETAIL_LENGTH ? flat : `${flat.slice(0, MAX_DETAIL_LENGTH)}\u2026`;
|
|
3849
|
+
}
|
|
3850
|
+
function describeItemExecution(item) {
|
|
3851
|
+
if (item.executor === void 0) return "";
|
|
3852
|
+
const model = item.model === void 0 ? "" : ` model=${oneLine(item.model)}`;
|
|
3853
|
+
const effort = item.effort === void 0 ? "" : ` effort=${oneLine(item.effort)}`;
|
|
3854
|
+
return ` [${oneLine(item.executor)}${model}${effort}]`;
|
|
3855
|
+
}
|
|
3856
|
+
function formatWorkRecord(tick) {
|
|
3857
|
+
const ran = tick.items.filter((item) => item.ranExecutor);
|
|
3858
|
+
if (ran.length === 0) return null;
|
|
3859
|
+
const header = `=== ${tick.timestamp.toISOString()} ${repoField(tick.repo)} ===
|
|
3860
|
+
`;
|
|
3861
|
+
const lines = ran.map(
|
|
3862
|
+
(item) => `#${String(item.issue)} ${item.turn === null ? "-" : oneLine(item.turn)} ${item.outcome}${describeItemExecution(item)} \u2014 ${briefDetail(item.detail)}
|
|
3863
|
+
`
|
|
3864
|
+
);
|
|
3865
|
+
return header + lines.join("") + "\n";
|
|
3866
|
+
}
|
|
3867
|
+
function trimToLastLines(content, max) {
|
|
3868
|
+
const lines = content.split("\n");
|
|
3869
|
+
if (lines.at(-1) === "") lines.pop();
|
|
3870
|
+
if (lines.length <= max) return content;
|
|
3871
|
+
return lines.slice(lines.length - max).join("\n") + "\n";
|
|
3872
|
+
}
|
|
3873
|
+
var RECORD_HEADER = /^=== (\S+) /;
|
|
3874
|
+
function pruneOldRecords(content, now, maxAgeMs) {
|
|
3875
|
+
if (content.length === 0) return content;
|
|
3876
|
+
const cutoff = now.getTime() - maxAgeMs;
|
|
3877
|
+
const lines = content.split("\n");
|
|
3878
|
+
if (lines.at(-1) === "") lines.pop();
|
|
3879
|
+
const groups = [];
|
|
3880
|
+
let current = { header: null, lines: [] };
|
|
3881
|
+
for (const line of lines) {
|
|
3882
|
+
const header = RECORD_HEADER.exec(line);
|
|
3883
|
+
if (header === null) {
|
|
3884
|
+
current.lines.push(line);
|
|
3885
|
+
continue;
|
|
3886
|
+
}
|
|
3887
|
+
groups.push(current);
|
|
3888
|
+
current = { header: header[1], lines: [line] };
|
|
3889
|
+
}
|
|
3890
|
+
groups.push(current);
|
|
3891
|
+
return groups.filter((group) => {
|
|
3892
|
+
if (group.lines.length === 0) return false;
|
|
3893
|
+
if (group.header === null) return true;
|
|
3894
|
+
const parsed = Date.parse(group.header);
|
|
3895
|
+
return Number.isNaN(parsed) || parsed >= cutoff;
|
|
3896
|
+
}).map((group) => group.lines.join("\n") + "\n").join("");
|
|
3897
|
+
}
|
|
3898
|
+
function appendWithRetention(dir, file, content, retain) {
|
|
3899
|
+
const target = join3(dir, file);
|
|
3900
|
+
appendFileSync(target, content, "utf8");
|
|
3901
|
+
const existing = readFileSync4(target, "utf8");
|
|
3902
|
+
const retained = retain(existing);
|
|
3903
|
+
if (retained === existing) return;
|
|
3904
|
+
const temp = `${target}.${randomUUID2()}.tmp`;
|
|
3905
|
+
try {
|
|
3906
|
+
writeFileSync2(temp, retained, "utf8");
|
|
3907
|
+
renameSync2(temp, target);
|
|
3908
|
+
} finally {
|
|
3909
|
+
try {
|
|
3910
|
+
unlinkSync2(temp);
|
|
3911
|
+
} catch {
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function recordTick(tick, dir = operationLogDirectory()) {
|
|
3916
|
+
try {
|
|
3917
|
+
accessSync(dir, constants.W_OK);
|
|
3918
|
+
} catch {
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
try {
|
|
3922
|
+
appendWithRetention(
|
|
3923
|
+
dir,
|
|
3924
|
+
EXECUTION_LOG_FILE,
|
|
3925
|
+
formatExecutionLine(tick),
|
|
3926
|
+
(existing) => trimToLastLines(existing, MAX_EXECUTION_LINES)
|
|
3927
|
+
);
|
|
3928
|
+
} catch {
|
|
3929
|
+
}
|
|
3930
|
+
try {
|
|
3931
|
+
const record = formatWorkRecord(tick);
|
|
3932
|
+
if (record === null) return;
|
|
3933
|
+
const maxAgeMs = WORK_RECORD_MAX_AGE_DAYS * 24 * 60 * 60 * 1e3;
|
|
3934
|
+
appendWithRetention(
|
|
3935
|
+
dir,
|
|
3936
|
+
WORK_LOG_FILE,
|
|
3937
|
+
record,
|
|
3938
|
+
(existing) => pruneOldRecords(existing, tick.timestamp, maxAgeMs)
|
|
3939
|
+
);
|
|
3940
|
+
} catch {
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3785
3944
|
// src/commands/doWork.ts
|
|
3786
3945
|
var inFlightMarker = null;
|
|
3787
3946
|
function out(message) {
|
|
@@ -3790,9 +3949,15 @@ function out(message) {
|
|
|
3790
3949
|
function progress(message) {
|
|
3791
3950
|
process.stderr.write(message);
|
|
3792
3951
|
}
|
|
3952
|
+
var loggableInvocation = null;
|
|
3793
3953
|
function fail(message) {
|
|
3794
3954
|
process.stderr.write(`Error: ${message}
|
|
3795
3955
|
`);
|
|
3956
|
+
if (loggableInvocation !== null) {
|
|
3957
|
+
const { startedAt } = loggableInvocation;
|
|
3958
|
+
loggableInvocation = null;
|
|
3959
|
+
logTick([], 1, startedAt, "config-error");
|
|
3960
|
+
}
|
|
3796
3961
|
process.exit(1);
|
|
3797
3962
|
}
|
|
3798
3963
|
function parsePositiveInt(value, label) {
|
|
@@ -4450,15 +4615,19 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
4450
4615
|
"--dry-run",
|
|
4451
4616
|
"Print the work plan, plus a summary and the exact command that would be launched for each item, and exit without changing anything"
|
|
4452
4617
|
).option("--json", "Emit the work plan and outcomes as JSON on stdout").option("--silent", "Suppress step-by-step Claude output; show only the final summary").action(async (options) => {
|
|
4618
|
+
const startedAt = Date.now();
|
|
4619
|
+
if (options.dryRun !== true) loggableInvocation = { startedAt };
|
|
4453
4620
|
const settings = resolveSettings(options);
|
|
4454
4621
|
if (options.dryRun === true) {
|
|
4455
|
-
const exitCode2 = await runTick(settings, options);
|
|
4622
|
+
const { exitCode: exitCode2 } = await runTick(settings, options);
|
|
4456
4623
|
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
4457
4624
|
return;
|
|
4458
4625
|
}
|
|
4626
|
+
loggableInvocation = null;
|
|
4459
4627
|
const lock = acquireRunLock("do-work", settings.lockStaleMinutes);
|
|
4460
4628
|
if (!lock.ok) {
|
|
4461
4629
|
const exitCode2 = reportLockHeld(lock, settings, options);
|
|
4630
|
+
logTick([], exitCode2, startedAt, "lock-held");
|
|
4462
4631
|
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
4463
4632
|
return;
|
|
4464
4633
|
}
|
|
@@ -4483,8 +4652,11 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
4483
4652
|
process.once("SIGINT", onSignal);
|
|
4484
4653
|
process.once("SIGTERM", onSignal);
|
|
4485
4654
|
let exitCode;
|
|
4655
|
+
let reports = [];
|
|
4486
4656
|
try {
|
|
4487
|
-
|
|
4657
|
+
const result = await runTick(settings, options);
|
|
4658
|
+
exitCode = result.exitCode;
|
|
4659
|
+
reports = result.reports;
|
|
4488
4660
|
} catch (err) {
|
|
4489
4661
|
process.stderr.write(`Error: ${err.message}
|
|
4490
4662
|
`);
|
|
@@ -4494,8 +4666,39 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
4494
4666
|
process.removeListener("SIGINT", onSignal);
|
|
4495
4667
|
process.removeListener("SIGTERM", onSignal);
|
|
4496
4668
|
}
|
|
4669
|
+
logTick(reports, exitCode, startedAt);
|
|
4497
4670
|
if (exitCode !== 0) process.exit(exitCode);
|
|
4498
4671
|
});
|
|
4672
|
+
function toTickLogItem(report) {
|
|
4673
|
+
return {
|
|
4674
|
+
issue: report.issue,
|
|
4675
|
+
turn: report.turn,
|
|
4676
|
+
outcome: report.outcome,
|
|
4677
|
+
detail: report.detail,
|
|
4678
|
+
ranExecutor: report.ranExecutor ?? false,
|
|
4679
|
+
executor: report.execution?.executor,
|
|
4680
|
+
model: report.execution?.model,
|
|
4681
|
+
effort: report.execution?.effort
|
|
4682
|
+
};
|
|
4683
|
+
}
|
|
4684
|
+
function logTick(reports, exitCode, startedAt, note) {
|
|
4685
|
+
let repo;
|
|
4686
|
+
try {
|
|
4687
|
+
const slug = getRepoSlug();
|
|
4688
|
+
repo = `${slug.owner}/${slug.repo}`;
|
|
4689
|
+
} catch {
|
|
4690
|
+
repo = null;
|
|
4691
|
+
}
|
|
4692
|
+
recordTick({
|
|
4693
|
+
command: "do-work",
|
|
4694
|
+
repo,
|
|
4695
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
4696
|
+
durationMs: Date.now() - startedAt,
|
|
4697
|
+
exitCode,
|
|
4698
|
+
note,
|
|
4699
|
+
items: reports.map(toTickLogItem)
|
|
4700
|
+
});
|
|
4701
|
+
}
|
|
4499
4702
|
function reportLockHeld(lock, settings, options) {
|
|
4500
4703
|
const held = lock.heldBy;
|
|
4501
4704
|
const sentence = `Another automata instance is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}, command ${held.command}). Doing nothing.
|
|
@@ -4553,7 +4756,7 @@ ${describePlan(decisions)}`;
|
|
|
4553
4756
|
}
|
|
4554
4757
|
if (options.dryRun) {
|
|
4555
4758
|
reportDryRun(items, decisions, settings, options, hygiene);
|
|
4556
|
-
return 0;
|
|
4759
|
+
return { exitCode: 0, reports: [] };
|
|
4557
4760
|
}
|
|
4558
4761
|
const reports = [];
|
|
4559
4762
|
const deferred = [];
|
|
@@ -4612,7 +4815,7 @@ ${describePlan(decisions)}`;
|
|
|
4612
4815
|
summarizeHygiene(hygiene);
|
|
4613
4816
|
summarize(reports);
|
|
4614
4817
|
}
|
|
4615
|
-
return exitCode;
|
|
4818
|
+
return { exitCode, reports };
|
|
4616
4819
|
}
|
|
4617
4820
|
function toRunJson(entry) {
|
|
4618
4821
|
if (entry.kind === "refused") {
|