astrocode-workflow 0.1.58 → 0.2.0
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/README.md +243 -11
- package/dist/agents/prompts.d.ts +1 -0
- package/dist/agents/prompts.js +159 -0
- package/dist/agents/registry.js +11 -1
- package/dist/config/loader.js +34 -0
- package/dist/config/schema.d.ts +7 -1
- package/dist/config/schema.js +2 -0
- package/dist/hooks/continuation-enforcer.d.ts +9 -1
- package/dist/hooks/continuation-enforcer.js +2 -1
- package/dist/hooks/inject-provider.d.ts +9 -1
- package/dist/hooks/inject-provider.js +2 -1
- package/dist/hooks/tool-output-truncator.d.ts +9 -1
- package/dist/hooks/tool-output-truncator.js +2 -1
- package/dist/index.js +228 -45
- package/dist/state/adapters/index.d.ts +4 -2
- package/dist/state/adapters/index.js +23 -27
- package/dist/state/db.d.ts +6 -8
- package/dist/state/db.js +106 -45
- package/dist/tools/index.d.ts +13 -3
- package/dist/tools/index.js +14 -31
- package/dist/tools/init.d.ts +10 -1
- package/dist/tools/init.js +73 -18
- package/dist/tools/injects.js +90 -26
- package/dist/tools/spec.d.ts +0 -1
- package/dist/tools/spec.js +4 -1
- package/dist/tools/status.d.ts +1 -1
- package/dist/tools/status.js +70 -52
- package/dist/tools/workflow.js +2 -2
- package/dist/ui/inject.d.ts +16 -2
- package/dist/ui/inject.js +104 -33
- package/dist/workflow/directives.d.ts +2 -0
- package/dist/workflow/directives.js +34 -19
- package/dist/workflow/state-machine.d.ts +46 -3
- package/dist/workflow/state-machine.js +249 -92
- package/package.json +1 -1
- package/src/agents/prompts.ts +160 -0
- package/src/agents/registry.ts +16 -1
- package/src/config/loader.ts +39 -4
- package/src/config/schema.ts +3 -0
- package/src/hooks/continuation-enforcer.ts +9 -2
- package/src/hooks/inject-provider.ts +9 -2
- package/src/hooks/tool-output-truncator.ts +9 -2
- package/src/index.ts +260 -56
- package/src/state/adapters/index.ts +21 -26
- package/src/state/db.ts +114 -58
- package/src/tools/index.ts +29 -31
- package/src/tools/init.ts +91 -22
- package/src/tools/injects.ts +147 -53
- package/src/tools/spec.ts +6 -2
- package/src/tools/status.ts +71 -55
- package/src/tools/workflow.ts +3 -3
- package/src/ui/inject.ts +115 -41
- package/src/workflow/directives.ts +103 -75
- package/src/workflow/state-machine.ts +327 -109
package/src/state/db.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// src/state/db.ts
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { SCHEMA_SQL, SCHEMA_VERSION } from "./schema";
|
|
@@ -13,22 +14,34 @@ function ensureParentDir(filePath: string) {
|
|
|
13
14
|
fs.mkdirSync(dir, { recursive: true });
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
function tableExists(db: SqliteDb, tableName: string): boolean {
|
|
18
|
+
try {
|
|
19
|
+
const row = db
|
|
20
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?")
|
|
21
|
+
.get(tableName) as { name?: string } | undefined;
|
|
22
|
+
return row?.name === tableName;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
export function openSqlite(dbPath: string, opts?: { busyTimeoutMs?: number }): SqliteDb {
|
|
17
29
|
ensureParentDir(dbPath);
|
|
18
30
|
const adapter = createDatabaseAdapter();
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
if (!adapter.isAvailable()) {
|
|
32
|
+
throw new Error("No SQLite driver available (adapter unavailable).");
|
|
33
|
+
}
|
|
34
|
+
return adapter.open(dbPath, opts);
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
export function configurePragmas(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
temp_store?: "DEFAULT" | "MEMORY" | "FILE";
|
|
37
|
+
export function configurePragmas(db: SqliteDb, pragmas: Record<string, any>) {
|
|
38
|
+
if (!pragmas || typeof pragmas !== "object") return;
|
|
39
|
+
|
|
40
|
+
const known = new Set(["journal_mode", "synchronous", "foreign_keys", "temp_store"]);
|
|
41
|
+
for (const k of Object.keys(pragmas)) {
|
|
42
|
+
if (!known.has(k)) warn(`[Astrocode] Unknown pragma ignored: ${k}`);
|
|
30
43
|
}
|
|
31
|
-
|
|
44
|
+
|
|
32
45
|
if (pragmas.journal_mode) db.pragma(`journal_mode = ${pragmas.journal_mode}`);
|
|
33
46
|
if (pragmas.synchronous) db.pragma(`synchronous = ${pragmas.synchronous}`);
|
|
34
47
|
if (typeof pragmas.foreign_keys === "boolean") db.pragma(`foreign_keys = ${pragmas.foreign_keys ? "ON" : "OFF"}`);
|
|
@@ -36,10 +49,12 @@ export function configurePragmas(
|
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
/** BEGIN IMMEDIATE transaction helper. */
|
|
39
|
-
export function withTx<T>(db: SqliteDb, fn: () => T): T {
|
|
52
|
+
export function withTx<T>(db: SqliteDb, fn: () => T, opts?: { require?: boolean }): T {
|
|
40
53
|
const adapter = createDatabaseAdapter();
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
const available = adapter.isAvailable();
|
|
55
|
+
|
|
56
|
+
if (!available) {
|
|
57
|
+
if (opts?.require) throw new Error("Database adapter unavailable; transaction required.");
|
|
43
58
|
return fn();
|
|
44
59
|
}
|
|
45
60
|
|
|
@@ -58,61 +73,102 @@ export function withTx<T>(db: SqliteDb, fn: () => T): T {
|
|
|
58
73
|
}
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
export function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
export function getSchemaVersion(db: SqliteDb): number {
|
|
77
|
+
try {
|
|
78
|
+
const row = db
|
|
79
|
+
.prepare("SELECT schema_version FROM repo_state WHERE id = 1")
|
|
80
|
+
.get() as { schema_version?: number } | undefined;
|
|
81
|
+
return row?.schema_version ?? 0;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
84
|
+
if (msg.includes("no such table")) return 0;
|
|
85
|
+
throw new Error(`Failed to read schema version: ${msg}`);
|
|
66
86
|
}
|
|
87
|
+
}
|
|
67
88
|
|
|
89
|
+
function migrateStageRunsCreatedAt(db: SqliteDb) {
|
|
90
|
+
// v2 requires stage_runs.created_at to be meaningful; never write ''.
|
|
91
|
+
// This migration is best-effort and does not assume constraint rewrite support.
|
|
68
92
|
try {
|
|
69
|
-
db.
|
|
93
|
+
const cols = db.prepare("PRAGMA table_info(stage_runs)").all() as Array<{ name: string }>;
|
|
94
|
+
const hasCreatedAt = cols.some((c) => c.name === "created_at");
|
|
95
|
+
if (hasCreatedAt) return;
|
|
70
96
|
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const columns = db.prepare("PRAGMA table_info(stage_runs)").all() as { name: string }[];
|
|
75
|
-
const hasCreatedAt = columns.some(col => col.name === 'created_at');
|
|
76
|
-
if (!hasCreatedAt) {
|
|
77
|
-
db.exec("ALTER TABLE stage_runs ADD COLUMN created_at TEXT NOT NULL DEFAULT ''");
|
|
78
|
-
info("[Astrocode] Added created_at column to stage_runs table");
|
|
79
|
-
}
|
|
80
|
-
} catch (e) {
|
|
81
|
-
// Column might already exist or table doesn't exist, ignore
|
|
82
|
-
}
|
|
97
|
+
// Add nullable column first (avoid poison defaults).
|
|
98
|
+
db.exec("ALTER TABLE stage_runs ADD COLUMN created_at TEXT");
|
|
83
99
|
|
|
84
|
-
const
|
|
100
|
+
const now = nowISO();
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
// Backfill using best available timestamp fields, falling back to now.
|
|
103
|
+
db.prepare(`
|
|
104
|
+
UPDATE stage_runs
|
|
105
|
+
SET created_at =
|
|
106
|
+
COALESCE(
|
|
107
|
+
NULLIF(created_at, ''),
|
|
108
|
+
NULLIF(updated_at, ''),
|
|
109
|
+
NULLIF(started_at, ''),
|
|
110
|
+
?
|
|
111
|
+
)
|
|
112
|
+
WHERE created_at IS NULL OR created_at = ''
|
|
113
|
+
`).run(now);
|
|
95
114
|
|
|
96
|
-
|
|
115
|
+
info("[Astrocode] Added created_at column to stage_runs table (backfilled)");
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// Ignore: table may not exist yet, or migration already applied.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
97
120
|
|
|
98
|
-
|
|
121
|
+
export function ensureSchema(db: SqliteDb, opts?: { allowAutoMigrate?: boolean; silent?: boolean }) {
|
|
122
|
+
try {
|
|
123
|
+
return withTx(
|
|
124
|
+
db,
|
|
125
|
+
() => {
|
|
126
|
+
db.exec(SCHEMA_SQL);
|
|
99
127
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
-
}
|
|
128
|
+
// Deterministic assertions: if these fail, bootstrap is broken.
|
|
129
|
+
if (!tableExists(db, "repo_state")) throw new Error("Schema missing required table repo_state after SCHEMA_SQL");
|
|
130
|
+
if (!tableExists(db, "story_keyseq")) throw new Error("Schema missing required table story_keyseq after SCHEMA_SQL");
|
|
105
131
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
// Ensure required singleton rows exist.
|
|
133
|
+
db.prepare("INSERT OR IGNORE INTO story_keyseq (id, next_story_num) VALUES (1, 1)").run();
|
|
134
|
+
|
|
135
|
+
// Migrations for existing DBs.
|
|
136
|
+
migrateStageRunsCreatedAt(db);
|
|
137
|
+
|
|
138
|
+
const row = db
|
|
139
|
+
.prepare("SELECT schema_version FROM repo_state WHERE id = 1")
|
|
140
|
+
.get() as { schema_version?: number } | undefined;
|
|
141
|
+
|
|
142
|
+
if (!row) {
|
|
143
|
+
const now = nowISO();
|
|
144
|
+
db.prepare("INSERT INTO repo_state (id, schema_version, created_at, updated_at) VALUES (1, ?, ?, ?)").run(
|
|
145
|
+
SCHEMA_VERSION,
|
|
146
|
+
now,
|
|
147
|
+
now
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const currentVersion = row.schema_version ?? 0;
|
|
153
|
+
|
|
154
|
+
if (currentVersion === SCHEMA_VERSION) return;
|
|
155
|
+
|
|
156
|
+
if (currentVersion > SCHEMA_VERSION) {
|
|
157
|
+
// Newer schema - no action (policy enforcement elsewhere).
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (currentVersion < SCHEMA_VERSION) {
|
|
162
|
+
if (opts?.allowAutoMigrate ?? true) {
|
|
163
|
+
db.prepare("UPDATE repo_state SET schema_version = ?, updated_at = ? WHERE id = 1").run(SCHEMA_VERSION, nowISO());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{ require: true }
|
|
168
|
+
);
|
|
115
169
|
} catch (e) {
|
|
116
|
-
|
|
170
|
+
if (opts?.silent) return;
|
|
171
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
172
|
+
throw new Error(`Schema initialization failed: ${msg}`);
|
|
117
173
|
}
|
|
118
174
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -15,22 +15,40 @@ import { createAstroRepairTool } from "./repair";
|
|
|
15
15
|
|
|
16
16
|
import { AgentConfig } from "@opencode-ai/sdk";
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
type RuntimeState = {
|
|
19
|
+
db: SqliteDb | null;
|
|
20
|
+
limitedMode: boolean;
|
|
21
|
+
limitedModeReason: null | { code: "db_init_failed"|"schema_too_old"|"schema_downgrade"|"schema_migration_failed"; details: any };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type CreateAstroToolsOptions = {
|
|
25
|
+
ctx: any;
|
|
26
|
+
config: AstrocodeConfig;
|
|
27
|
+
agents?: Record<string, AgentConfig>;
|
|
28
|
+
runtime: RuntimeState;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function createAstroTools(opts: CreateAstroToolsOptions): Record<string, ToolDefinition> {
|
|
32
|
+
const { ctx, config, agents, runtime } = opts;
|
|
33
|
+
const { db } = runtime;
|
|
34
|
+
const hasDatabase = db !== null; // Source of truth: DB availability
|
|
21
35
|
|
|
22
36
|
const tools: Record<string, ToolDefinition> = {};
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
// Always available tools (work without database - guaranteed DB-independent)
|
|
39
|
+
tools.astro_status = createAstroStatusTool({ ctx, config });
|
|
40
|
+
tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
|
|
26
41
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
tools.astro_spec_get = createAstroSpecGetTool({ ctx, config, db });
|
|
42
|
+
// Recovery tool - available even in limited mode to allow DB initialization
|
|
43
|
+
tools.astro_init = createAstroInitTool({ ctx, config, runtime });
|
|
30
44
|
|
|
31
45
|
// Database-dependent tools
|
|
32
46
|
if (hasDatabase) {
|
|
33
|
-
|
|
47
|
+
// Ensure agents are available for workflow tools that require them
|
|
48
|
+
if (!agents) {
|
|
49
|
+
throw new Error("astro_workflow_proceed requires agents to be provided in normal mode.");
|
|
50
|
+
}
|
|
51
|
+
|
|
34
52
|
tools.astro_story_queue = createAstroStoryQueueTool({ ctx, config, db });
|
|
35
53
|
tools.astro_story_approve = createAstroStoryApproveTool({ ctx, config, db });
|
|
36
54
|
tools.astro_story_board = createAstroStoryBoardTool({ ctx, config, db });
|
|
@@ -53,28 +71,6 @@ export function createAstroTools(opts: { ctx: any; config: AstrocodeConfig; db:
|
|
|
53
71
|
tools.astro_inject_eligible = createAstroInjectEligibleTool({ ctx, config, db });
|
|
54
72
|
tools.astro_inject_debug_due = createAstroInjectDebugDueTool({ ctx, config, db });
|
|
55
73
|
tools.astro_repair = createAstroRepairTool({ ctx, config, db });
|
|
56
|
-
} else {
|
|
57
|
-
// Limited mode tools - provide helpful messages instead of failing
|
|
58
|
-
tools.astro_init = {
|
|
59
|
-
description: "Initialize Astrocode (requires database - currently unavailable)",
|
|
60
|
-
args: {},
|
|
61
|
-
execute: async () => "❌ Database not available. Astrocode is running in limited mode."
|
|
62
|
-
};
|
|
63
|
-
tools.astro_story_queue = {
|
|
64
|
-
description: "Queue a story (requires database - currently unavailable)",
|
|
65
|
-
args: {},
|
|
66
|
-
execute: async () => "❌ Database not available. Astrocode is running in limited mode."
|
|
67
|
-
};
|
|
68
|
-
tools.astro_spec_set = {
|
|
69
|
-
description: "Set project spec (requires database for hash tracking - currently unavailable)",
|
|
70
|
-
args: {},
|
|
71
|
-
execute: async () => "❌ Database not available. Astrocode is running in limited mode."
|
|
72
|
-
};
|
|
73
|
-
tools.astro_workflow_proceed = {
|
|
74
|
-
description: "Advance workflow (requires database - currently unavailable)",
|
|
75
|
-
args: {},
|
|
76
|
-
execute: async () => "❌ Database not available. Astrocode is running in limited mode."
|
|
77
|
-
};
|
|
78
74
|
}
|
|
79
75
|
|
|
80
76
|
// Create aliases for backward compatibility
|
|
@@ -101,6 +97,8 @@ export function createAstroTools(opts: { ctx: any; config: AstrocodeConfig; db:
|
|
|
101
97
|
["_astro_inject_list", "astro_inject_list"],
|
|
102
98
|
["_astro_inject_search", "astro_inject_search"],
|
|
103
99
|
["_astro_inject_get", "astro_inject_get"],
|
|
100
|
+
["_astro_inject_eligible", "astro_inject_eligible"],
|
|
101
|
+
["_astro_inject_debug_due", "astro_inject_debug_due"],
|
|
104
102
|
["_astro_repair", "astro_repair"],
|
|
105
103
|
];
|
|
106
104
|
|
package/src/tools/init.ts
CHANGED
|
@@ -3,13 +3,22 @@ import path from "node:path";
|
|
|
3
3
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool";
|
|
4
4
|
import type { AstrocodeConfig } from "../config/schema";
|
|
5
5
|
import type { SqliteDb } from "../state/db";
|
|
6
|
-
import { ensureSchema } from "../state/db";
|
|
6
|
+
import { ensureSchema, openSqlite, configurePragmas } from "../state/db";
|
|
7
7
|
import { getAstroPaths, ensureAstroDirs } from "../shared/paths";
|
|
8
8
|
import { nowISO } from "../shared/time";
|
|
9
9
|
import { sha256Hex } from "../shared/hash";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
type RuntimeState = {
|
|
12
|
+
db: SqliteDb | null;
|
|
13
|
+
limitedMode: boolean;
|
|
14
|
+
limitedModeReason: null | {
|
|
15
|
+
code: "db_init_failed" | "schema_too_old" | "schema_downgrade" | "schema_migration_failed";
|
|
16
|
+
details: any;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function createAstroInitTool(opts: { ctx: any; config: AstrocodeConfig; runtime: RuntimeState }): ToolDefinition {
|
|
21
|
+
const { ctx, config, runtime } = opts;
|
|
13
22
|
|
|
14
23
|
return tool({
|
|
15
24
|
description:
|
|
@@ -23,28 +32,88 @@ export function createAstroInitTool(opts: { ctx: any; config: AstrocodeConfig; d
|
|
|
23
32
|
const paths = getAstroPaths(repoRoot, config.db.path);
|
|
24
33
|
ensureAstroDirs(paths);
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
const hadDbAlready = !!runtime.db;
|
|
36
|
+
let db: SqliteDb | null = runtime.db;
|
|
37
|
+
let publishedToRuntime = false;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
if (!db) {
|
|
41
|
+
try {
|
|
42
|
+
db = openSqlite(paths.dbPath, { busyTimeoutMs: config.db.busy_timeout_ms });
|
|
43
|
+
configurePragmas(db, config.db.pragmas);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
46
|
+
throw new Error(
|
|
47
|
+
`❌ Failed to open database at ${paths.dbPath}: ${msg}. Install a SQLite driver (better-sqlite3/bun:sqlite) and check file permissions.`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Source-of-truth for schema + repo_state invariants.
|
|
53
|
+
ensureSchema(db, { allowAutoMigrate: config.db.allow_auto_migrate });
|
|
54
|
+
|
|
55
|
+
// Postcondition: repo_state must exist after ensureSchema.
|
|
56
|
+
try {
|
|
57
|
+
db.prepare("SELECT schema_version FROM repo_state WHERE id = 1").get();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
60
|
+
throw new Error(`❌ Schema initialization incomplete: repo_state missing/unreadable after ensureSchema (${msg})`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (ensure_spec) {
|
|
64
|
+
if (!fs.existsSync(paths.specPath)) {
|
|
65
|
+
fs.writeFileSync(paths.specPath, spec_placeholder);
|
|
66
|
+
}
|
|
27
67
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
fs.writeFileSync(paths.specPath, spec_placeholder);
|
|
31
|
-
const specHash = sha256Hex(spec_placeholder);
|
|
68
|
+
const content = fs.readFileSync(paths.specPath, "utf8");
|
|
69
|
+
const specHash = sha256Hex(content);
|
|
32
70
|
const now = nowISO();
|
|
33
|
-
|
|
71
|
+
|
|
72
|
+
// Update hash; fail with a clear error if schema is mismatched.
|
|
73
|
+
try {
|
|
74
|
+
db.prepare(
|
|
75
|
+
`
|
|
76
|
+
UPDATE repo_state
|
|
77
|
+
SET spec_hash_after = ?, updated_at = ?
|
|
78
|
+
WHERE id = 1
|
|
79
|
+
`
|
|
80
|
+
).run(specHash, now);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
83
|
+
throw new Error(`❌ Failed to update repo_state spec hash (schema mismatch?): ${msg}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Best-effort: if we recovered DB in-process, publish it so the harness can use it immediately.
|
|
88
|
+
// (If your harness reads runtime at creation-time only, this still helps future tool calls.)
|
|
89
|
+
if (!hadDbAlready && db) {
|
|
90
|
+
runtime.db = db;
|
|
91
|
+
runtime.limitedMode = false;
|
|
92
|
+
runtime.limitedModeReason = null;
|
|
93
|
+
publishedToRuntime = true;
|
|
34
94
|
}
|
|
35
|
-
}
|
|
36
95
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
96
|
+
const stat = db.prepare("SELECT schema_version, created_at, updated_at FROM repo_state WHERE id=1").get() as any;
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
`✅ Astrocode initialized.`,
|
|
100
|
+
``,
|
|
101
|
+
`- Repo: ${repoRoot}`,
|
|
102
|
+
`- DB: ${path.relative(repoRoot, paths.dbPath)} (schema_version=${stat?.schema_version ?? "?"})`,
|
|
103
|
+
`- Spec: ${path.relative(repoRoot, paths.specPath)}`,
|
|
104
|
+
``,
|
|
105
|
+
publishedToRuntime
|
|
106
|
+
? `Next: run /astro-status. (DB recovered in-process.)`
|
|
107
|
+
: `Next: restart the agent/runtime if Astrocode is still in Limited Mode, then run /astro-status.`,
|
|
108
|
+
].join("\n");
|
|
109
|
+
} finally {
|
|
110
|
+
// Only close if this tool opened it AND we did not publish it for ongoing use.
|
|
111
|
+
if (!hadDbAlready && !publishedToRuntime && db && typeof db.close === "function") {
|
|
112
|
+
try {
|
|
113
|
+
db.close();
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
48
117
|
},
|
|
49
118
|
});
|
|
50
|
-
}
|
|
119
|
+
}
|