astrocode-workflow 0.3.5 → 0.4.1

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.
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
2
3
  import type { AstrocodeConfig } from "../config/schema";
3
4
  import type { SqliteDb } from "../state/db";
@@ -5,28 +6,64 @@ import { withTx } from "../state/db";
5
6
  import { repairState, formatRepairReport } from "../workflow/repair";
6
7
  import { putArtifact } from "../workflow/artifacts";
7
8
  import { nowISO } from "../shared/time";
9
+ import { getLockStatus, tryRemoveStaleLock } from "../state/repo-lock";
8
10
 
9
11
 
10
12
  export function createAstroRepairTool(opts: { ctx: any; config: AstrocodeConfig; db: SqliteDb }): ToolDefinition {
11
13
  const { ctx, config, db } = opts;
12
14
 
13
15
  return tool({
14
- description: "Repair Astrocode invariants and recover from inconsistent DB state. Writes a repair report artifact.",
16
+ description: "Repair Astrocode invariants and recover from inconsistent DB state. Also checks and repairs lock files. Writes a repair report artifact.",
15
17
  args: {
16
18
  write_report_artifact: tool.schema.boolean().default(true),
19
+ repair_lock: tool.schema.boolean().default(true).describe("Attempt to remove stale/dead lock files"),
17
20
  },
18
- execute: async ({ write_report_artifact }) => {
21
+ execute: async ({ write_report_artifact, repair_lock }) => {
19
22
  const repoRoot = ctx.directory as string;
23
+ const lockPath = path.join(repoRoot, ".astro", "astro.lock");
24
+
25
+ // First, check and repair lock if requested
26
+ const lockLines: string[] = [];
27
+ const lockStatus = getLockStatus(lockPath);
28
+
29
+ if (lockStatus.exists) {
30
+ lockLines.push("## Lock Status");
31
+ lockLines.push(`- Lock found: ${lockPath}`);
32
+ lockLines.push(`- PID: ${lockStatus.pid} (${lockStatus.pidAlive ? 'alive' : 'dead'})`);
33
+ lockLines.push(`- Age: ${lockStatus.ageMs ? Math.floor(lockStatus.ageMs / 1000) : '?'}s`);
34
+ lockLines.push(`- Status: ${lockStatus.isStale ? 'stale' : 'fresh'}`);
35
+
36
+ if (repair_lock) {
37
+ const result = tryRemoveStaleLock(lockPath);
38
+ if (result.removed) {
39
+ lockLines.push(`- **Removed**: ${result.reason}`);
40
+ } else {
41
+ lockLines.push(`- **Not removed**: ${result.reason}`);
42
+ }
43
+ } else {
44
+ if (!lockStatus.pidAlive || lockStatus.isStale) {
45
+ lockLines.push(`- **Recommendation**: Run with repair_lock=true to remove this ${!lockStatus.pidAlive ? 'dead' : 'stale'} lock`);
46
+ }
47
+ }
48
+ lockLines.push("");
49
+ }
50
+
51
+ // Then repair database state
20
52
  const report = withTx(db, () => repairState(db, config));
21
- const md = formatRepairReport(report);
53
+ const dbMd = formatRepairReport(report);
54
+
55
+ // Combine lock and DB repair
56
+ const fullMd = lockLines.length > 0
57
+ ? `# Astrocode Repair Report\n\n${lockLines.join("\n")}\n${dbMd.replace(/^# Astrocode repair report\n*/i, "")}`
58
+ : dbMd;
22
59
 
23
60
  if (write_report_artifact) {
24
61
  const rel = `.astro/repair/repair_${nowISO().replace(/[:.]/g, "-")}.md`;
25
- const a = putArtifact({ repoRoot, db, run_id: null, stage_key: null, type: "log", rel_path: rel, content: md, meta: { kind: "repair" } });
26
- return md + `\n\nReport saved: ${rel} (artifact=${a.artifact_id})`;
62
+ const a = putArtifact({ repoRoot, db, run_id: null, stage_key: null, type: "log", rel_path: rel, content: fullMd, meta: { kind: "repair" } });
63
+ return fullMd + `\n\nReport saved: ${rel} (artifact=${a.artifact_id})`;
27
64
  }
28
65
 
29
- return md;
66
+ return fullMd;
30
67
  },
31
68
  });
32
69
  }
@@ -199,6 +199,7 @@ export function createAstroWorkflowProceedTool(opts: { ctx: any; config: Astroco
199
199
  repoRoot,
200
200
  sessionId,
201
201
  owner: "astro_workflow_proceed",
202
+ advisory: true, // Advisory mode: warn instead of blocking on lock contention
202
203
  fn: async () => {
203
204
  const steps = Math.min(max_steps, config.workflow.loop_max_steps_hard_cap);
204
205
 
@@ -72,11 +72,11 @@ export function repairState(db: SqliteDb, config: AstrocodeConfig): RepairReport
72
72
  if (stageRuns.length < pipeline.length) {
73
73
  const existingKeys = new Set(stageRuns.map((s) => s.stage_key));
74
74
  const insert = db.prepare(
75
- "INSERT INTO stage_runs (stage_run_id, run_id, stage_key, stage_index, status, updated_at) VALUES (?, ?, ?, ?, 'pending', ?)"
75
+ "INSERT INTO stage_runs (stage_run_id, run_id, stage_key, stage_index, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'pending', ?, ?)"
76
76
  );
77
77
  pipeline.forEach((key, idx) => {
78
78
  if (!existingKeys.has(key as any)) {
79
- insert.run(newStageRunId(), active.run_id, key, idx, now);
79
+ insert.run(newStageRunId(), active.run_id, key, idx, now, now);
80
80
  push(report, `Inserted missing stage_run ${key} for run ${active.run_id}`);
81
81
  }
82
82
  });