astrocode-workflow 0.0.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.
Files changed (133) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +85 -0
  3. package/dist/agents/commands.d.ts +9 -0
  4. package/dist/agents/commands.js +121 -0
  5. package/dist/agents/prompts.d.ts +2 -0
  6. package/dist/agents/prompts.js +27 -0
  7. package/dist/agents/registry.d.ts +6 -0
  8. package/dist/agents/registry.js +223 -0
  9. package/dist/agents/types.d.ts +14 -0
  10. package/dist/agents/types.js +8 -0
  11. package/dist/config/config-handler.d.ts +4 -0
  12. package/dist/config/config-handler.js +46 -0
  13. package/dist/config/defaults.d.ts +3 -0
  14. package/dist/config/defaults.js +3 -0
  15. package/dist/config/loader.d.ts +11 -0
  16. package/dist/config/loader.js +48 -0
  17. package/dist/config/schema.d.ts +176 -0
  18. package/dist/config/schema.js +198 -0
  19. package/dist/hooks/continuation-enforcer.d.ts +26 -0
  20. package/dist/hooks/continuation-enforcer.js +166 -0
  21. package/dist/hooks/tool-output-truncator.d.ts +17 -0
  22. package/dist/hooks/tool-output-truncator.js +56 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +108 -0
  25. package/dist/shared/deep-merge.d.ts +8 -0
  26. package/dist/shared/deep-merge.js +25 -0
  27. package/dist/shared/hash.d.ts +1 -0
  28. package/dist/shared/hash.js +4 -0
  29. package/dist/shared/log.d.ts +7 -0
  30. package/dist/shared/log.js +24 -0
  31. package/dist/shared/model-tuning.d.ts +9 -0
  32. package/dist/shared/model-tuning.js +28 -0
  33. package/dist/shared/paths.d.ts +19 -0
  34. package/dist/shared/paths.js +51 -0
  35. package/dist/shared/text.d.ts +4 -0
  36. package/dist/shared/text.js +19 -0
  37. package/dist/shared/time.d.ts +1 -0
  38. package/dist/shared/time.js +3 -0
  39. package/dist/state/adapters/index.d.ts +39 -0
  40. package/dist/state/adapters/index.js +119 -0
  41. package/dist/state/db.d.ts +17 -0
  42. package/dist/state/db.js +83 -0
  43. package/dist/state/ids.d.ts +8 -0
  44. package/dist/state/ids.js +25 -0
  45. package/dist/state/schema.d.ts +2 -0
  46. package/dist/state/schema.js +247 -0
  47. package/dist/state/types.d.ts +70 -0
  48. package/dist/state/types.js +1 -0
  49. package/dist/tools/artifacts.d.ts +18 -0
  50. package/dist/tools/artifacts.js +71 -0
  51. package/dist/tools/index.d.ts +8 -0
  52. package/dist/tools/index.js +100 -0
  53. package/dist/tools/init.d.ts +8 -0
  54. package/dist/tools/init.js +41 -0
  55. package/dist/tools/injects.d.ts +23 -0
  56. package/dist/tools/injects.js +99 -0
  57. package/dist/tools/repair.d.ts +8 -0
  58. package/dist/tools/repair.js +25 -0
  59. package/dist/tools/run.d.ts +13 -0
  60. package/dist/tools/run.js +54 -0
  61. package/dist/tools/spec.d.ts +13 -0
  62. package/dist/tools/spec.js +41 -0
  63. package/dist/tools/stage.d.ts +23 -0
  64. package/dist/tools/stage.js +284 -0
  65. package/dist/tools/status.d.ts +8 -0
  66. package/dist/tools/status.js +107 -0
  67. package/dist/tools/story.d.ts +23 -0
  68. package/dist/tools/story.js +85 -0
  69. package/dist/tools/workflow.d.ts +8 -0
  70. package/dist/tools/workflow.js +197 -0
  71. package/dist/ui/inject.d.ts +5 -0
  72. package/dist/ui/inject.js +9 -0
  73. package/dist/ui/toasts.d.ts +13 -0
  74. package/dist/ui/toasts.js +39 -0
  75. package/dist/workflow/artifacts.d.ts +24 -0
  76. package/dist/workflow/artifacts.js +45 -0
  77. package/dist/workflow/baton.d.ts +66 -0
  78. package/dist/workflow/baton.js +101 -0
  79. package/dist/workflow/context.d.ts +12 -0
  80. package/dist/workflow/context.js +67 -0
  81. package/dist/workflow/directives.d.ts +37 -0
  82. package/dist/workflow/directives.js +111 -0
  83. package/dist/workflow/repair.d.ts +8 -0
  84. package/dist/workflow/repair.js +99 -0
  85. package/dist/workflow/state-machine.d.ts +43 -0
  86. package/dist/workflow/state-machine.js +127 -0
  87. package/dist/workflow/story-helpers.d.ts +9 -0
  88. package/dist/workflow/story-helpers.js +13 -0
  89. package/package.json +32 -0
  90. package/src/agents/commands.ts +137 -0
  91. package/src/agents/prompts.ts +28 -0
  92. package/src/agents/registry.ts +310 -0
  93. package/src/agents/types.ts +31 -0
  94. package/src/config/config-handler.ts +48 -0
  95. package/src/config/defaults.ts +4 -0
  96. package/src/config/loader.ts +55 -0
  97. package/src/config/schema.ts +236 -0
  98. package/src/hooks/continuation-enforcer.ts +217 -0
  99. package/src/hooks/tool-output-truncator.ts +82 -0
  100. package/src/index.ts +131 -0
  101. package/src/shared/deep-merge.ts +28 -0
  102. package/src/shared/hash.ts +5 -0
  103. package/src/shared/log.ts +30 -0
  104. package/src/shared/model-tuning.ts +48 -0
  105. package/src/shared/paths.ts +70 -0
  106. package/src/shared/text.ts +20 -0
  107. package/src/shared/time.ts +3 -0
  108. package/src/shims.node.d.ts +20 -0
  109. package/src/state/adapters/index.ts +155 -0
  110. package/src/state/db.ts +105 -0
  111. package/src/state/ids.ts +33 -0
  112. package/src/state/schema.ts +249 -0
  113. package/src/state/types.ts +76 -0
  114. package/src/tools/artifacts.ts +83 -0
  115. package/src/tools/index.ts +111 -0
  116. package/src/tools/init.ts +50 -0
  117. package/src/tools/injects.ts +108 -0
  118. package/src/tools/repair.ts +31 -0
  119. package/src/tools/run.ts +62 -0
  120. package/src/tools/spec.ts +50 -0
  121. package/src/tools/stage.ts +361 -0
  122. package/src/tools/status.ts +119 -0
  123. package/src/tools/story.ts +106 -0
  124. package/src/tools/workflow.ts +241 -0
  125. package/src/ui/inject.ts +13 -0
  126. package/src/ui/toasts.ts +48 -0
  127. package/src/workflow/artifacts.ts +69 -0
  128. package/src/workflow/baton.ts +141 -0
  129. package/src/workflow/context.ts +86 -0
  130. package/src/workflow/directives.ts +170 -0
  131. package/src/workflow/repair.ts +138 -0
  132. package/src/workflow/state-machine.ts +194 -0
  133. package/src/workflow/story-helpers.ts +18 -0
@@ -0,0 +1,30 @@
1
+ export type LogLevel = "debug" | "info" | "warn" | "error";
2
+
3
+ let CURRENT_LEVEL: LogLevel = (process.env.ASTRO_LOG_LEVEL as LogLevel) ?? "info";
4
+
5
+ const ORDER: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40 };
6
+
7
+ export function setLogLevel(level: LogLevel) {
8
+ CURRENT_LEVEL = level;
9
+ }
10
+
11
+ function shouldLog(level: LogLevel): boolean {
12
+ return ORDER[level] >= ORDER[CURRENT_LEVEL];
13
+ }
14
+
15
+ export function log(level: LogLevel, message: string, meta?: unknown) {
16
+ if (!shouldLog(level)) return;
17
+ const prefix = `[astrocode:${level}]`;
18
+ if (meta === undefined) {
19
+ // eslint-disable-next-line no-console
20
+ console.log(prefix, message);
21
+ return;
22
+ }
23
+ // eslint-disable-next-line no-console
24
+ console.log(prefix, message, meta);
25
+ }
26
+
27
+ export const debug = (msg: string, meta?: unknown) => log("debug", msg, meta);
28
+ export const info = (msg: string, meta?: unknown) => log("info", msg, meta);
29
+ export const warn = (msg: string, meta?: unknown) => log("warn", msg, meta);
30
+ export const error = (msg: string, meta?: unknown) => log("error", msg, meta);
@@ -0,0 +1,48 @@
1
+ import type { AgentConfig } from "@opencode-ai/sdk";
2
+ import { isGptModel } from "../agents/types";
3
+
4
+ export type AstroCognitionPreset =
5
+ | "orchestrator"
6
+ | "frame"
7
+ | "plan"
8
+ | "spec"
9
+ | "implement"
10
+ | "review"
11
+ | "verify"
12
+ | "close"
13
+ | "utility";
14
+
15
+ /**
16
+ * OMO-style: if GPT model => reasoningEffort/textVerbosity.
17
+ * Otherwise => Anthropic-style thinking budget.
18
+ *
19
+ * Not all providers honor these fields; safe to include.
20
+ */
21
+ export function applyModelTuning(base: AgentConfig, preset: AstroCognitionPreset): AgentConfig {
22
+ const model = base.model ?? "";
23
+ if (!model) return base;
24
+
25
+ if (isGptModel(model)) {
26
+ const reasoningEffort =
27
+ preset === "orchestrator" ? "medium"
28
+ : preset === "implement" ? "high"
29
+ : "medium";
30
+
31
+ const textVerbosity =
32
+ preset === "orchestrator" ? "low"
33
+ : preset === "review" ? "high"
34
+ : "medium";
35
+
36
+ const { thinking, ...rest } = base as any;
37
+ return { ...rest, reasoningEffort, textVerbosity } as AgentConfig;
38
+ }
39
+
40
+ const budgetTokens =
41
+ preset === "implement" ? 32_000
42
+ : preset === "review" ? 24_000
43
+ : preset === "orchestrator" ? 16_000
44
+ : 12_000;
45
+
46
+ const { reasoningEffort, textVerbosity, ...rest } = base as any;
47
+ return { ...rest, thinking: { type: "enabled", budgetTokens } } as AgentConfig;
48
+ }
@@ -0,0 +1,70 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+
4
+ /** Normalize to posix-like separators for DB paths. */
5
+ export function toPosix(p: string): string {
6
+ return p.split(path.sep).join("/");
7
+ }
8
+
9
+ export function ensureDir(p: string) {
10
+ fs.mkdirSync(p, { recursive: true });
11
+ }
12
+
13
+ export function joinRepo(root: string, ...parts: string[]): string {
14
+ return path.join(root, ...parts);
15
+ }
16
+
17
+ export type AstroPaths = {
18
+ repoRoot: string;
19
+ astroRoot: string;
20
+ dbPath: string;
21
+ runsDir: string;
22
+ specPath: string;
23
+ toolOutputDir: string;
24
+ configPathPreferred: string;
25
+ configPathFallback: string;
26
+ };
27
+
28
+ export function getAstroPaths(repoRoot: string, dbPathOverride?: string): AstroPaths {
29
+ const astroRoot = joinRepo(repoRoot, ".astro");
30
+ const runsDir = joinRepo(repoRoot, ".astro", "runs");
31
+ const dbPath = dbPathOverride ? joinRepo(repoRoot, dbPathOverride) : joinRepo(repoRoot, ".astro", "astro.db");
32
+ const specPath = joinRepo(repoRoot, ".astro", "spec.md");
33
+ const toolOutputDir = joinRepo(repoRoot, ".astro", "tool_output");
34
+ return {
35
+ repoRoot,
36
+ astroRoot,
37
+ dbPath,
38
+ runsDir,
39
+ specPath,
40
+ toolOutputDir,
41
+ configPathPreferred: joinRepo(repoRoot, ".astro", "astrocode.config.jsonc"),
42
+ configPathFallback: joinRepo(repoRoot, "astrocode.config.jsonc"),
43
+ };
44
+ }
45
+
46
+ export function ensureAstroDirs(paths: AstroPaths) {
47
+ ensureDir(paths.astroRoot);
48
+ ensureDir(paths.runsDir);
49
+ ensureDir(paths.toolOutputDir);
50
+ }
51
+
52
+ export function runDir(paths: AstroPaths, runId: string): string {
53
+ return joinRepo(paths.repoRoot, ".astro", "runs", runId);
54
+ }
55
+
56
+ export function stageDir(paths: AstroPaths, runId: string, stageKey: string): string {
57
+ return joinRepo(paths.repoRoot, ".astro", "runs", runId, stageKey);
58
+ }
59
+
60
+ export function assertInsideAstro(repoRoot: string, filePath: string) {
61
+ const absRepo = path.resolve(repoRoot);
62
+ const abs = path.resolve(filePath);
63
+ const astroRoot = path.resolve(path.join(repoRoot, ".astro"));
64
+ if (!abs.startsWith(astroRoot + path.sep) && abs !== astroRoot) {
65
+ throw new Error(`Refusing to write outside .astro: ${filePath}`);
66
+ }
67
+ if (!abs.startsWith(absRepo + path.sep) && abs !== absRepo) {
68
+ throw new Error(`Refusing to write outside repo root: ${filePath}`);
69
+ }
70
+ }
@@ -0,0 +1,20 @@
1
+ export function normalizeNewlines(s: string): string {
2
+ return s.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
3
+ }
4
+
5
+ export function clampLines(md: string, maxLines: number): string {
6
+ const lines = normalizeNewlines(md).split("\n");
7
+ if (lines.length <= maxLines) return md.trimEnd();
8
+ return lines.slice(0, maxLines).join("\n").trimEnd() + "\n…";
9
+ }
10
+
11
+ export function clampChars(s: string, maxChars: number): string {
12
+ if (s.length <= maxChars) return s;
13
+ return s.slice(0, maxChars) + "\n…(truncated)";
14
+ }
15
+
16
+ export function stripCodeFences(md: string): string {
17
+ // Light helper: remove surrounding triple backticks if present
18
+ const m = md.match(/^```[a-zA-Z0-9_-]*\n([\s\S]*)\n```\s*$/);
19
+ return m ? m[1] : md;
20
+ }
@@ -0,0 +1,3 @@
1
+ export function nowISO(): string {
2
+ return new Date().toISOString();
3
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ Minimal Node.js type shims so this package can compile in environments
3
+ where @types/node is not installed (e.g., offline installs).
4
+
5
+ Keep this file tiny on purpose: only add declarations that are actually
6
+ referenced in the codebase.
7
+ */
8
+
9
+ declare var require: any;
10
+
11
+ declare var process: {
12
+ env: Record<string, string | undefined>;
13
+ cwd?: () => string;
14
+ };
15
+
16
+ declare var __dirname: string;
17
+
18
+ declare class Buffer {
19
+ static from(input: any, encoding?: string): Buffer;
20
+ }
@@ -0,0 +1,155 @@
1
+ import { info, warn } from "../../shared/log";
2
+
3
+ export interface DatabaseConnection {
4
+ pragma(sql: string): void;
5
+ exec(sql: string): void;
6
+ prepare(sql: string): Statement;
7
+ close(): void;
8
+ }
9
+
10
+ export interface Statement {
11
+ run(...params: any[]): { changes: number; lastInsertRowid: any };
12
+ get(...params: any[]): any;
13
+ all(...params: any[]): any[];
14
+ }
15
+
16
+ export interface DatabaseAdapter {
17
+ isAvailable(): boolean;
18
+ open(path: string, opts?: { busyTimeoutMs?: number }): DatabaseConnection;
19
+ }
20
+
21
+ // Mock adapter for when no database is available
22
+ export class MockDatabaseAdapter implements DatabaseAdapter {
23
+ isAvailable(): boolean {
24
+ return true; // Mock is always available
25
+ }
26
+
27
+ open(): DatabaseConnection {
28
+ warn("Using mock database - no persistence available");
29
+ return {
30
+ pragma: () => {},
31
+ exec: () => {},
32
+ prepare: () => ({
33
+ run: () => ({ changes: 0, lastInsertRowid: null }),
34
+ get: () => null,
35
+ all: () => []
36
+ }),
37
+ close: () => {}
38
+ };
39
+ }
40
+ }
41
+
42
+ // Bun SQLite adapter - singleton pattern to avoid multiple initializations
43
+ let bunDatabase: any = null;
44
+ let bunDatabaseInitialized = false;
45
+
46
+ function initializeBunDatabase(): any {
47
+ if (bunDatabaseInitialized) {
48
+ return bunDatabase;
49
+ }
50
+
51
+ bunDatabaseInitialized = true;
52
+
53
+ try {
54
+ const isBun = typeof (globalThis as any).Bun !== 'undefined';
55
+ if (isBun) {
56
+ const { Database } = require('bun:sqlite');
57
+ bunDatabase = Database;
58
+ }
59
+ } catch (e) {
60
+ // bun:sqlite not available
61
+ }
62
+
63
+ return bunDatabase;
64
+ }
65
+
66
+ // Bun SQLite adapter
67
+ export class BunSqliteAdapter implements DatabaseAdapter {
68
+ isAvailable(): boolean {
69
+ return !!initializeBunDatabase();
70
+ }
71
+
72
+ open(path: string, opts?: { busyTimeoutMs?: number }): DatabaseConnection {
73
+ const Database = initializeBunDatabase();
74
+ if (!Database) {
75
+ throw new Error("bun:sqlite not available");
76
+ }
77
+
78
+ const db = new Database(path);
79
+
80
+ // Configure database
81
+ if (opts?.busyTimeoutMs) {
82
+ db.exec(`PRAGMA busy_timeout = ${opts.busyTimeoutMs}`);
83
+ }
84
+
85
+ return {
86
+ pragma: (sql: string) => db.exec(`PRAGMA ${sql}`),
87
+ exec: (sql: string) => db.exec(sql),
88
+ prepare: (sql: string) => {
89
+ const stmt = db.prepare(sql);
90
+ return {
91
+ run: (...params: any[]) => stmt.run(...params),
92
+ get: (...params: any[]) => stmt.get(...params),
93
+ all: (...params: any[]) => stmt.all(...params)
94
+ };
95
+ },
96
+ close: () => db.close()
97
+ };
98
+ }
99
+ }
100
+
101
+ // Better SQLite3 adapter
102
+ export class BetterSqliteAdapter implements DatabaseAdapter {
103
+ private Database: any = null;
104
+
105
+ constructor() {
106
+ try {
107
+ this.Database = require("better-sqlite3");
108
+ } catch (e) {
109
+ // better-sqlite3 not available
110
+ }
111
+ }
112
+
113
+ isAvailable(): boolean {
114
+ return !!this.Database;
115
+ }
116
+
117
+ open(path: string, opts?: { busyTimeoutMs?: number }): DatabaseConnection {
118
+ if (!this.Database) {
119
+ throw new Error("better-sqlite3 not available");
120
+ }
121
+
122
+ const db = new this.Database(path);
123
+
124
+ // Set busy timeout if specified
125
+ if (opts?.busyTimeoutMs) {
126
+ db.pragma(`busy_timeout = ${opts.busyTimeoutMs}`);
127
+ }
128
+
129
+ return {
130
+ pragma: (sql: string) => db.pragma(sql.replace('PRAGMA ', '')),
131
+ exec: (sql: string) => db.exec(sql),
132
+ prepare: (sql: string) => db.prepare(sql),
133
+ close: () => db.close()
134
+ };
135
+ }
136
+ }
137
+
138
+ // Factory function to create the appropriate adapter
139
+ export function createDatabaseAdapter(): DatabaseAdapter {
140
+ const isBun = typeof (globalThis as any).Bun !== 'undefined';
141
+
142
+ if (isBun) {
143
+ const adapter = new BunSqliteAdapter();
144
+ if (adapter.isAvailable()) {
145
+ return adapter;
146
+ }
147
+ throw new Error("Bun runtime detected but bun:sqlite not available");
148
+ } else {
149
+ const adapter = new BetterSqliteAdapter();
150
+ if (adapter.isAvailable()) {
151
+ return adapter;
152
+ }
153
+ throw new Error("Node.js runtime detected but better-sqlite3 not available");
154
+ }
155
+ }
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { SCHEMA_SQL, SCHEMA_VERSION } from "./schema";
4
+ import { nowISO } from "../shared/time";
5
+ import { info, warn } from "../shared/log";
6
+ import { createDatabaseAdapter, DatabaseConnection } from "./adapters";
7
+
8
+ export type SqliteDb = DatabaseConnection;
9
+
10
+ /** Ensure directory exists for a file path. */
11
+ function ensureParentDir(filePath: string) {
12
+ const dir = path.dirname(filePath);
13
+ fs.mkdirSync(dir, { recursive: true });
14
+ }
15
+
16
+ export function openSqlite(dbPath: string, opts?: { busyTimeoutMs?: number }): SqliteDb {
17
+ ensureParentDir(dbPath);
18
+ const adapter = createDatabaseAdapter();
19
+ const db = adapter.open(dbPath, opts);
20
+ return db;
21
+ }
22
+
23
+ export function configurePragmas(
24
+ db: SqliteDb,
25
+ pragmas: {
26
+ journal_mode?: "WAL" | "DELETE";
27
+ synchronous?: "NORMAL" | "FULL" | "OFF";
28
+ foreign_keys?: boolean;
29
+ temp_store?: "DEFAULT" | "MEMORY" | "FILE";
30
+ }
31
+ ) {
32
+ if (pragmas.journal_mode) db.pragma(`journal_mode = ${pragmas.journal_mode}`);
33
+ if (pragmas.synchronous) db.pragma(`synchronous = ${pragmas.synchronous}`);
34
+ if (typeof pragmas.foreign_keys === "boolean") db.pragma(`foreign_keys = ${pragmas.foreign_keys ? "ON" : "OFF"}`);
35
+ if (pragmas.temp_store) db.pragma(`temp_store = ${pragmas.temp_store}`);
36
+ }
37
+
38
+ /** BEGIN IMMEDIATE transaction helper. */
39
+ export function withTx<T>(db: SqliteDb, fn: () => T): T {
40
+ const adapter = createDatabaseAdapter();
41
+ if (!adapter.isAvailable()) {
42
+ // No database available, just run the function
43
+ return fn();
44
+ }
45
+
46
+ db.exec("BEGIN IMMEDIATE");
47
+ try {
48
+ const out = fn();
49
+ db.exec("COMMIT");
50
+ return out;
51
+ } catch (e) {
52
+ try {
53
+ db.exec("ROLLBACK");
54
+ } catch {
55
+ // ignore
56
+ }
57
+ throw e;
58
+ }
59
+ }
60
+
61
+ export function ensureSchema(db: SqliteDb, opts?: { allowAutoMigrate?: boolean; failOnDowngrade?: boolean }) {
62
+ const adapter = createDatabaseAdapter();
63
+ if (!adapter.isAvailable()) {
64
+ // Silent skip for mock adapter
65
+ return;
66
+ }
67
+
68
+ try {
69
+ db.exec(SCHEMA_SQL);
70
+
71
+ const row = db.prepare("SELECT schema_version FROM repo_state WHERE id = 1").get() as { schema_version?: number } | undefined;
72
+
73
+ if (!row) {
74
+ const now = nowISO();
75
+ db.prepare(
76
+ "INSERT INTO repo_state (id, schema_version, created_at, updated_at) VALUES (1, ?, ?, ?)"
77
+ ).run(SCHEMA_VERSION, now, now);
78
+ // Initialize story key seq
79
+ db.prepare("INSERT OR IGNORE INTO story_keyseq (id, next_story_num) VALUES (1, 1)").run();
80
+ return;
81
+ }
82
+
83
+ const currentVersion = row.schema_version ?? 0;
84
+
85
+ if (currentVersion === SCHEMA_VERSION) return;
86
+
87
+ if (currentVersion > SCHEMA_VERSION && (opts?.failOnDowngrade ?? true)) {
88
+ throw new Error(
89
+ `Astrocode DB schema_version ${currentVersion} is newer than this plugin (${SCHEMA_VERSION}). Refusing to downgrade.`
90
+ );
91
+ }
92
+
93
+ if (currentVersion < SCHEMA_VERSION) {
94
+ if (!(opts?.allowAutoMigrate ?? true)) {
95
+ throw new Error(
96
+ `Astrocode DB schema_version ${currentVersion} is older than required (${SCHEMA_VERSION}). Auto-migrate disabled.`
97
+ );
98
+ }
99
+ // Additive schema: SCHEMA_SQL already created new tables/indexes if missing.
100
+ db.prepare("UPDATE repo_state SET schema_version = ?, updated_at = ? WHERE id = 1").run(SCHEMA_VERSION, nowISO());
101
+ }
102
+ } catch (e) {
103
+ // Schema operations might fail on mock adapter, silently ignore
104
+ }
105
+ }
@@ -0,0 +1,33 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ export function newId(prefix: string): string {
4
+ return `${prefix}_${randomUUID()}`;
5
+ }
6
+
7
+ export function newRunId(): string {
8
+ return newId("run");
9
+ }
10
+
11
+ export function newStageRunId(): string {
12
+ return newId("stage");
13
+ }
14
+
15
+ export function newArtifactId(): string {
16
+ return newId("art");
17
+ }
18
+
19
+ export function newToolRunId(): string {
20
+ return newId("tool");
21
+ }
22
+
23
+ export function newEventId(): string {
24
+ return newId("evt");
25
+ }
26
+
27
+ export function newSnapshotId(): string {
28
+ return newId("snap");
29
+ }
30
+
31
+ export function newBatchId(): string {
32
+ return newId("batch");
33
+ }