@hasna/loops 0.3.10 → 0.3.11

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/cli/index.js CHANGED
@@ -860,31 +860,23 @@ class Store {
860
860
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
861
861
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
862
862
  `);
863
- try {
864
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
865
- } catch {}
866
- try {
867
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
868
- } catch {}
869
- try {
870
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
871
- } catch {}
872
- try {
873
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
874
- } catch {}
875
- try {
876
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
877
- } catch {}
878
- try {
879
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
880
- } catch {}
881
- try {
882
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
883
- } catch {}
863
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
864
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
867
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
868
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
869
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
885
871
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
886
872
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
887
873
  }
874
+ addColumnIfMissing(table, column, definition) {
875
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
876
+ if (columns.some((c) => c.name === column))
877
+ return;
878
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
879
+ }
888
880
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
889
881
  if (!opts.daemonLeaseId)
890
882
  return;
@@ -4338,7 +4330,7 @@ function runDoctor(store) {
4338
4330
  // package.json
4339
4331
  var package_default = {
4340
4332
  name: "@hasna/loops",
4341
- version: "0.3.10",
4333
+ version: "0.3.11",
4342
4334
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4343
4335
  type: "module",
4344
4336
  main: "dist/index.js",
@@ -860,31 +860,23 @@ class Store {
860
860
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
861
861
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
862
862
  `);
863
- try {
864
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
865
- } catch {}
866
- try {
867
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
868
- } catch {}
869
- try {
870
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
871
- } catch {}
872
- try {
873
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
874
- } catch {}
875
- try {
876
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
877
- } catch {}
878
- try {
879
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
880
- } catch {}
881
- try {
882
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
883
- } catch {}
863
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
864
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
867
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
868
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
869
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
885
871
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
886
872
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
887
873
  }
874
+ addColumnIfMissing(table, column, definition) {
875
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
876
+ if (columns.some((c) => c.name === column))
877
+ return;
878
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
879
+ }
888
880
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
889
881
  if (!opts.daemonLeaseId)
890
882
  return;
@@ -4138,7 +4130,7 @@ function enableStartup(result) {
4138
4130
  // package.json
4139
4131
  var package_default = {
4140
4132
  name: "@hasna/loops",
4141
- version: "0.3.10",
4133
+ version: "0.3.11",
4142
4134
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4143
4135
  type: "module",
4144
4136
  main: "dist/index.js",
package/dist/index.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
@@ -58,6 +58,14 @@ export declare class Store {
58
58
  private db;
59
59
  constructor(path?: string);
60
60
  private migrate;
61
+ /**
62
+ * Add a column only if it does not already exist. Idempotent — avoids the
63
+ * "duplicate column name" error that SQLite logs (via libsqlite3, before any
64
+ * JS try/catch) when re-running an additive migration on a database that has
65
+ * already been upgraded. Table/column/definition come from hardcoded literals
66
+ * in {@link migrate}, never user input, so interpolation here is safe.
67
+ */
68
+ private addColumnIfMissing;
61
69
  private assertDaemonLeaseFence;
62
70
  createLoop(input: CreateLoopInput, from?: Date): Loop;
63
71
  getLoop(id: string): Loop | undefined;
package/dist/lib/store.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
package/dist/sdk/index.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",