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.
Files changed (2) hide show
  1. package/dist/index.js +207 -4
  2. 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
- exitCode = await runTick(settings, options);
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") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.6.0-develop.293",
3
+ "version": "0.6.0-develop.296",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {