@grackle-ai/database 0.73.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.
Files changed (78) hide show
  1. package/README.md +34 -0
  2. package/dist/credential-providers.d.ts +41 -0
  3. package/dist/credential-providers.d.ts.map +1 -0
  4. package/dist/credential-providers.js +97 -0
  5. package/dist/credential-providers.js.map +1 -0
  6. package/dist/crypto.d.ts +5 -0
  7. package/dist/crypto.d.ts.map +1 -0
  8. package/dist/crypto.js +75 -0
  9. package/dist/crypto.js.map +1 -0
  10. package/dist/db-seed.d.ts +16 -0
  11. package/dist/db-seed.d.ts.map +1 -0
  12. package/dist/db-seed.js +176 -0
  13. package/dist/db-seed.js.map +1 -0
  14. package/dist/db.d.ts +43 -0
  15. package/dist/db.d.ts.map +1 -0
  16. package/dist/db.js +478 -0
  17. package/dist/db.js.map +1 -0
  18. package/dist/env-registry.d.ts +29 -0
  19. package/dist/env-registry.d.ts.map +1 -0
  20. package/dist/env-registry.js +78 -0
  21. package/dist/env-registry.js.map +1 -0
  22. package/dist/event-store.d.ts +19 -0
  23. package/dist/event-store.d.ts.map +1 -0
  24. package/dist/event-store.js +16 -0
  25. package/dist/event-store.js.map +1 -0
  26. package/dist/finding-store.d.ts +7 -0
  27. package/dist/finding-store.d.ts.map +1 -0
  28. package/dist/finding-store.js +45 -0
  29. package/dist/finding-store.js.map +1 -0
  30. package/dist/index.d.ts +32 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +31 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/json-helpers.d.ts +7 -0
  35. package/dist/json-helpers.d.ts.map +1 -0
  36. package/dist/json-helpers.js +22 -0
  37. package/dist/json-helpers.js.map +1 -0
  38. package/dist/paths.d.ts +7 -0
  39. package/dist/paths.d.ts.map +1 -0
  40. package/dist/paths.js +12 -0
  41. package/dist/paths.js.map +1 -0
  42. package/dist/persona-store.d.ts +15 -0
  43. package/dist/persona-store.d.ts.map +1 -0
  44. package/dist/persona-store.js +57 -0
  45. package/dist/persona-store.js.map +1 -0
  46. package/dist/schema.d.ts +1784 -0
  47. package/dist/schema.d.ts.map +1 -0
  48. package/dist/schema.js +152 -0
  49. package/dist/schema.js.map +1 -0
  50. package/dist/session-store.d.ts +57 -0
  51. package/dist/session-store.d.ts.map +1 -0
  52. package/dist/session-store.js +204 -0
  53. package/dist/session-store.js.map +1 -0
  54. package/dist/settings-store.d.ts +14 -0
  55. package/dist/settings-store.d.ts.map +1 -0
  56. package/dist/settings-store.js +30 -0
  57. package/dist/settings-store.js.map +1 -0
  58. package/dist/task-store.d.ts +71 -0
  59. package/dist/task-store.d.ts.map +1 -0
  60. package/dist/task-store.js +301 -0
  61. package/dist/task-store.js.map +1 -0
  62. package/dist/test-db.d.ts +13 -0
  63. package/dist/test-db.d.ts.map +1 -0
  64. package/dist/test-db.js +14 -0
  65. package/dist/test-db.js.map +1 -0
  66. package/dist/token-store.d.ts +25 -0
  67. package/dist/token-store.d.ts.map +1 -0
  68. package/dist/token-store.js +58 -0
  69. package/dist/token-store.js.map +1 -0
  70. package/dist/utils/slugify.d.ts +20 -0
  71. package/dist/utils/slugify.d.ts.map +1 -0
  72. package/dist/utils/slugify.js +22 -0
  73. package/dist/utils/slugify.js.map +1 -0
  74. package/dist/workspace-store.d.ts +29 -0
  75. package/dist/workspace-store.d.ts.map +1 -0
  76. package/dist/workspace-store.js +74 -0
  77. package/dist/workspace-store.js.map +1 -0
  78. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @grackle-ai/database
2
+
3
+ SQLite persistence layer for Grackle — schema definitions, store modules, migrations, and encrypted token storage.
4
+
5
+ ## What it provides
6
+
7
+ - **Schema** — Drizzle ORM table definitions for environments, sessions, tasks, workspaces, findings, personas, settings, tokens, and domain events
8
+ - **Stores** — Focused CRUD modules for each entity (e.g., `sessionStore`, `taskStore`, `workspaceStore`)
9
+ - **Migrations** — Idempotent schema migrations that run on every startup
10
+ - **Seeding** — Application-level defaults (personas, root task, settings backfills)
11
+ - **Crypto** — AES-256-GCM encryption for token storage
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { openDatabase, initDatabase, seedDatabase, sqlite, sessionStore } from "@grackle-ai/database";
17
+
18
+ // Initialize at startup
19
+ openDatabase();
20
+ initDatabase();
21
+ seedDatabase(sqlite!);
22
+
23
+ // Use stores
24
+ const session = sessionStore.getSession("session-123");
25
+ ```
26
+
27
+ ## Requirements
28
+
29
+ - Node.js >= 22
30
+ - `better-sqlite3` native module (built at install time)
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,41 @@
1
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
+ import * as schema from "./schema.js";
3
+ /** Configuration for which credential providers are enabled. */
4
+ export interface CredentialProviderConfig {
5
+ claude: "off" | "subscription" | "api_key";
6
+ github: "off" | "on";
7
+ copilot: "off" | "on";
8
+ codex: "off" | "on";
9
+ goose: "off" | "on";
10
+ }
11
+ /** Valid provider names. */
12
+ export declare const VALID_PROVIDERS: readonly string[];
13
+ /** Valid values for the Claude provider. */
14
+ export declare const VALID_CLAUDE_VALUES: ReadonlySet<string>;
15
+ /** Valid values for toggle-style providers (github, copilot, codex). */
16
+ export declare const VALID_TOGGLE_VALUES: ReadonlySet<string>;
17
+ /** Drizzle database instance type used by credential-provider functions. */
18
+ export type DatabaseInstance = BetterSQLite3Database<typeof schema>;
19
+ /**
20
+ * Parse a raw JSON string into a validated {@link CredentialProviderConfig}.
21
+ * Invalid or missing fields fall back to {@link DEFAULT_CONFIG} values.
22
+ * Throws if the JSON is syntactically invalid — callers decide how to handle the error.
23
+ * Non-object values (e.g. `"null"`, `"42"`) are treated as empty and fall back to defaults.
24
+ */
25
+ export declare function parseCredentialProviderConfig(rawJson: string): CredentialProviderConfig;
26
+ /**
27
+ * Read the current credential provider configuration from the database.
28
+ * @param database - Optional Drizzle instance; defaults to the module-level db.
29
+ */
30
+ export declare function getCredentialProviders(database?: DatabaseInstance): CredentialProviderConfig;
31
+ /**
32
+ * Validate that a value is a well-formed credential provider config.
33
+ * Returns true if all fields have valid values.
34
+ */
35
+ export declare function isValidCredentialProviderConfig(value: unknown): value is CredentialProviderConfig;
36
+ /**
37
+ * Persist credential provider configuration to the database.
38
+ * @param database - Optional Drizzle instance; defaults to the module-level db.
39
+ */
40
+ export declare function setCredentialProviders(config: CredentialProviderConfig, database?: DatabaseInstance): void;
41
+ //# sourceMappingURL=credential-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-providers.d.ts","sourceRoot":"","sources":["../src/credential-providers.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAExE,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAItC,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,KAAK,GAAG,cAAc,GAAG,SAAS,CAAC;IAC3C,MAAM,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,KAAK,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAcD,4BAA4B;AAC5B,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAAsD,CAAC;AAEpG,4CAA4C;AAC5C,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAA+C,CAAC;AAEpG,wEAAwE;AACxE,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAA0B,CAAC;AAE/E,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,CAAC,OAAO,MAAM,CAAC,CAAC;AAIpE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,wBAAwB,CAUvF;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,wBAAwB,CAkB5F;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,wBAAwB,CAYjG;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAU1G"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Configurable credential providers — manages which credential providers
3
+ * are enabled and persists the configuration to the database.
4
+ *
5
+ * The token bundle builder that reads `process.env` / disk lives in
6
+ * `@grackle-ai/server` to keep this module a pure persistence layer.
7
+ */
8
+ import { eq } from "drizzle-orm";
9
+ import db from "./db.js";
10
+ import * as schema from "./schema.js";
11
+ /** Settings table key for credential provider configuration. */
12
+ const SETTINGS_KEY = "credential_providers";
13
+ /** Default configuration — all providers off. */
14
+ const DEFAULT_CONFIG = {
15
+ claude: "off",
16
+ github: "off",
17
+ copilot: "off",
18
+ codex: "off",
19
+ goose: "off",
20
+ };
21
+ /** Valid provider names. */
22
+ export const VALID_PROVIDERS = ["claude", "github", "copilot", "codex", "goose"];
23
+ /** Valid values for the Claude provider. */
24
+ export const VALID_CLAUDE_VALUES = new Set(["off", "subscription", "api_key"]);
25
+ /** Valid values for toggle-style providers (github, copilot, codex). */
26
+ export const VALID_TOGGLE_VALUES = new Set(["off", "on"]);
27
+ // ─── Read / Write ──────────────────────────────────────────
28
+ /**
29
+ * Parse a raw JSON string into a validated {@link CredentialProviderConfig}.
30
+ * Invalid or missing fields fall back to {@link DEFAULT_CONFIG} values.
31
+ * Throws if the JSON is syntactically invalid — callers decide how to handle the error.
32
+ * Non-object values (e.g. `"null"`, `"42"`) are treated as empty and fall back to defaults.
33
+ */
34
+ export function parseCredentialProviderConfig(rawJson) {
35
+ const raw = JSON.parse(rawJson);
36
+ const parsed = (typeof raw === "object" && raw !== null ? raw : {});
37
+ return {
38
+ claude: VALID_CLAUDE_VALUES.has(parsed.claude ?? "") ? parsed.claude : DEFAULT_CONFIG.claude,
39
+ github: VALID_TOGGLE_VALUES.has(parsed.github ?? "") ? parsed.github : DEFAULT_CONFIG.github,
40
+ copilot: VALID_TOGGLE_VALUES.has(parsed.copilot ?? "") ? parsed.copilot : DEFAULT_CONFIG.copilot,
41
+ codex: VALID_TOGGLE_VALUES.has(parsed.codex ?? "") ? parsed.codex : DEFAULT_CONFIG.codex,
42
+ goose: VALID_TOGGLE_VALUES.has(parsed.goose ?? "") ? parsed.goose : DEFAULT_CONFIG.goose,
43
+ };
44
+ }
45
+ /**
46
+ * Read the current credential provider configuration from the database.
47
+ * @param database - Optional Drizzle instance; defaults to the module-level db.
48
+ */
49
+ export function getCredentialProviders(database) {
50
+ const conn = database ?? db;
51
+ const row = conn
52
+ .select()
53
+ .from(schema.settings)
54
+ .where(eq(schema.settings.key, SETTINGS_KEY))
55
+ .get();
56
+ if (!row) {
57
+ return { ...DEFAULT_CONFIG };
58
+ }
59
+ try {
60
+ return parseCredentialProviderConfig(row.value);
61
+ }
62
+ catch {
63
+ process.stderr.write("Invalid credential_providers setting; returning defaults\n");
64
+ return { ...DEFAULT_CONFIG };
65
+ }
66
+ }
67
+ /**
68
+ * Validate that a value is a well-formed credential provider config.
69
+ * Returns true if all fields have valid values.
70
+ */
71
+ export function isValidCredentialProviderConfig(value) {
72
+ if (typeof value !== "object" || value === null) {
73
+ return false;
74
+ }
75
+ const v = value;
76
+ return (VALID_CLAUDE_VALUES.has(v.claude) &&
77
+ VALID_TOGGLE_VALUES.has(v.github) &&
78
+ VALID_TOGGLE_VALUES.has(v.copilot) &&
79
+ VALID_TOGGLE_VALUES.has(v.codex) &&
80
+ VALID_TOGGLE_VALUES.has(v.goose));
81
+ }
82
+ /**
83
+ * Persist credential provider configuration to the database.
84
+ * @param database - Optional Drizzle instance; defaults to the module-level db.
85
+ */
86
+ export function setCredentialProviders(config, database) {
87
+ const conn = database ?? db;
88
+ const value = JSON.stringify(config);
89
+ conn.insert(schema.settings)
90
+ .values({ key: SETTINGS_KEY, value })
91
+ .onConflictDoUpdate({
92
+ target: schema.settings.key,
93
+ set: { value },
94
+ })
95
+ .run();
96
+ }
97
+ //# sourceMappingURL=credential-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-providers.js","sourceRoot":"","sources":["../src/credential-providers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Encrypt a plaintext string using AES-256-GCM with a PBKDF2-derived key. */
2
+ export declare function encrypt(plaintext: string): string;
3
+ /** Decrypt an AES-256-GCM ciphertext string produced by {@link encrypt}. */
4
+ export declare function decrypt(ciphertext: string): string;
5
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AA6DA,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAWlD"}
package/dist/crypto.js ADDED
@@ -0,0 +1,75 @@
1
+ import { randomBytes, createCipheriv, createDecipheriv, pbkdf2Sync } from "node:crypto";
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { grackleHome } from "./paths.js";
5
+ const ALGORITHM = "aes-256-gcm";
6
+ const IV_LENGTH = 12;
7
+ const TAG_LENGTH = 16;
8
+ const SALT_LENGTH = 16;
9
+ const KEY_LENGTH = 32;
10
+ const ITERATIONS = 100_000;
11
+ const MASTER_KEY_BYTE_LENGTH = 32;
12
+ const MASTER_KEY_FILENAME = "master-key";
13
+ /**
14
+ * Load or generate the master key for token encryption. Priority:
15
+ * 1. `GRACKLE_MASTER_KEY` env var
16
+ * 2. Persisted random key at `$GRACKLE_HOME/.grackle/master-key`
17
+ * 3. Generate and persist a new random key
18
+ */
19
+ function loadMasterKey() {
20
+ if (process.env.GRACKLE_MASTER_KEY) {
21
+ return process.env.GRACKLE_MASTER_KEY;
22
+ }
23
+ const keyPath = join(grackleHome, MASTER_KEY_FILENAME);
24
+ if (existsSync(keyPath)) {
25
+ const key = readFileSync(keyPath, "utf8").trim();
26
+ if (key.length > 0) {
27
+ return key;
28
+ }
29
+ }
30
+ // Generate and persist a random key
31
+ const key = randomBytes(MASTER_KEY_BYTE_LENGTH).toString("hex");
32
+ mkdirSync(grackleHome, { recursive: true });
33
+ writeFileSync(keyPath, key + "\n", { mode: 0o600 });
34
+ try {
35
+ chmodSync(keyPath, 0o600);
36
+ }
37
+ catch { /* Windows may not support this */ }
38
+ // Informational only — logged to stdout to avoid Rush interpreting stderr as a build warning.
39
+ // eslint-disable-next-line no-console
40
+ console.log("Generated new master key for token encryption. Set GRACKLE_MASTER_KEY env var for explicit control.");
41
+ return key;
42
+ }
43
+ let cachedMasterKey = undefined;
44
+ function getMasterKey() {
45
+ if (!cachedMasterKey) {
46
+ cachedMasterKey = loadMasterKey();
47
+ }
48
+ return cachedMasterKey;
49
+ }
50
+ function deriveKey(salt) {
51
+ return pbkdf2Sync(getMasterKey(), salt, ITERATIONS, KEY_LENGTH, "sha256");
52
+ }
53
+ /** Encrypt a plaintext string using AES-256-GCM with a PBKDF2-derived key. */
54
+ export function encrypt(plaintext) {
55
+ const salt = randomBytes(SALT_LENGTH);
56
+ const key = deriveKey(salt);
57
+ const iv = randomBytes(IV_LENGTH);
58
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
59
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
60
+ const tag = cipher.getAuthTag();
61
+ // Format: salt:iv:tag:ciphertext (all base64)
62
+ return [salt, iv, tag, encrypted].map((b) => b.toString("base64")).join(":");
63
+ }
64
+ /** Decrypt an AES-256-GCM ciphertext string produced by {@link encrypt}. */
65
+ export function decrypt(ciphertext) {
66
+ const parts = ciphertext.split(":");
67
+ if (parts.length !== 4)
68
+ throw new Error("Invalid encrypted format");
69
+ const [salt, iv, tag, encrypted] = parts.map((p) => Buffer.from(p, "base64"));
70
+ const key = deriveKey(salt);
71
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: TAG_LENGTH });
72
+ decipher.setAuthTag(tag);
73
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
74
+ }
75
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Application-level database seeding — creates default personas, root task,
3
+ * and backfills settings for fresh installs and upgrades.
4
+ *
5
+ * Separated from {@link initDatabase} (which owns schema migrations) so that
6
+ * the persistence layer stays free of business/domain knowledge.
7
+ */
8
+ import type Database from "better-sqlite3";
9
+ /**
10
+ * Seed the database with application-level defaults.
11
+ * Call once at startup after {@link initDatabase} has applied schema migrations.
12
+ *
13
+ * @param conn - The raw better-sqlite3 connection to seed.
14
+ */
15
+ export declare function seedDatabase(conn: InstanceType<typeof Database>): void;
16
+ //# sourceMappingURL=db-seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-seed.d.ts","sourceRoot":"","sources":["../src/db-seed.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,GAAG,IAAI,CAsLtE"}
@@ -0,0 +1,176 @@
1
+ import { SYSTEM_PERSONA_ID, ROOT_TASK_ID } from "@grackle-ai/common";
2
+ /**
3
+ * Seed the database with application-level defaults.
4
+ * Call once at startup after {@link initDatabase} has applied schema migrations.
5
+ *
6
+ * @param conn - The raw better-sqlite3 connection to seed.
7
+ */
8
+ export function seedDatabase(conn) {
9
+ // Capture persona count BEFORE any seed inserts so we can distinguish
10
+ // fresh installs from upgrades in the onboarding backfill below.
11
+ const personaCount = conn
12
+ .prepare("SELECT COUNT(*) as cnt FROM personas")
13
+ .get();
14
+ // Seed: create default "Claude Code" persona if no personas exist
15
+ if (personaCount.cnt === 0) {
16
+ conn.exec(`
17
+ INSERT INTO personas (id, name, description, system_prompt, runtime, model, max_turns)
18
+ VALUES (
19
+ 'claude-code',
20
+ 'Claude Code',
21
+ 'Default agent persona using Claude Code runtime',
22
+ '',
23
+ 'claude-code',
24
+ 'sonnet',
25
+ 0
26
+ )
27
+ `);
28
+ conn.exec(`
29
+ INSERT OR IGNORE INTO settings (key, value)
30
+ VALUES ('default_persona_id', 'claude-code')
31
+ `);
32
+ }
33
+ // Migration: update the seed persona with the completion checklist and clearer name.
34
+ // Guards: only run when the system_prompt is still empty and name is still "Claude Code"
35
+ // so we don't overwrite user customizations.
36
+ conn.exec(`
37
+ UPDATE personas SET system_prompt = 'When you have finished implementing the task, you MUST complete ALL steps below in order. Do NOT stop early or go to "waiting for input" until every step is done.
38
+
39
+ ### Phase 1: Implement & Test
40
+ 1. **Implement** the task requirements.
41
+ 2. **Write tests**: Write unit tests, integration tests, or E2E specs as appropriate. Every implementation MUST include tests unless the change is purely cosmetic or untestable (state why if skipping).
42
+ 3. **Build**: Run the repository''s build command and fix any errors.
43
+ 4. **Run tests**: Run relevant tests and ensure they pass.
44
+ 5. **Manual test**: If the change affects UI, visually verify. If it affects CLI or API, run the commands manually. State explicitly if skipping and why.
45
+
46
+ ### Phase 2: Create PR
47
+ 6. **Sync with main**: Fetch and merge the main branch. If merge conflicts arise, resolve them, stage, and commit the merge. NEVER rebase.
48
+ 7. **Rebuild after merge**: If the merge brought in new commits, rebuild to catch integration conflicts.
49
+ 8. **Commit**: Stage your changed files and create a descriptive git commit. Use a conventional commit message (e.g., fix: ..., feat: ...).
50
+ 9. **Push**: Push your branch to the remote.
51
+ 10. **Create PR**: Create a pull request that links back to the issue (e.g., "Closes #ISSUE").
52
+
53
+ ### Phase 3: PR Readiness (you MUST complete this — do NOT skip)
54
+ After creating the PR, you must ensure it is ready to merge.
55
+
56
+ 11. **Check for merge conflicts**: Verify the PR has no merge conflicts. If it does, fetch and merge the main branch, resolve conflicts, rebuild, commit, and push.
57
+ 12. **Wait for CI**: Wait for all CI checks to complete. If any check fails, read the logs, fix the issue, commit, push, and repeat.
58
+ 13. **Address code review comments**: Check for automated code review comments. For each unresolved comment: read the suggestion, fix the code or dismiss with an explanation, reply to the comment, and resolve the thread. After fixing, commit, push, and check again. Repeat until all review threads are resolved.
59
+ 14. **Post finding**: Use finding_post to summarize what you did and any key decisions.
60
+
61
+ IMPORTANT: The PR is the deliverable, but a PR with failing CI or unresolved review comments is NOT done. You MUST complete Phase 3. Do NOT go to "waiting for input" until CI is green AND all review threads are resolved.'
62
+ WHERE id = 'claude-code' AND system_prompt = '' AND name = 'Claude Code'
63
+ `);
64
+ conn.exec(`
65
+ UPDATE personas
66
+ SET name = 'Software Engineer',
67
+ description = 'Default agent persona for software engineering tasks'
68
+ WHERE id = 'claude-code'
69
+ AND name = 'Claude Code'
70
+ AND NOT EXISTS (
71
+ SELECT 1 FROM personas
72
+ WHERE name = 'Software Engineer'
73
+ AND id != 'claude-code'
74
+ )
75
+ `);
76
+ // Seed: ensure a System persona exists with the canonical SYSTEM_PERSONA_ID.
77
+ // Copies runtime + model from the seed persona so the FRE choice propagates.
78
+ // Handles name collisions: if a user-created persona named "System" already
79
+ // exists under a different id, reassign it to SYSTEM_PERSONA_ID.
80
+ {
81
+ const existingSystemById = conn
82
+ .prepare("SELECT id FROM personas WHERE id = ?")
83
+ .get(SYSTEM_PERSONA_ID);
84
+ if (!existingSystemById) {
85
+ const seedRow = conn
86
+ .prepare("SELECT runtime, model FROM personas WHERE id = 'claude-code'")
87
+ .get();
88
+ const systemRuntime = seedRow?.runtime || "claude-code";
89
+ const systemModel = seedRow?.model || "sonnet";
90
+ const existingSystemByName = conn
91
+ .prepare("SELECT id FROM personas WHERE name = 'System'")
92
+ .get();
93
+ if (existingSystemByName && existingSystemByName.id !== SYSTEM_PERSONA_ID) {
94
+ // Reassign existing "System" persona to the canonical id and update
95
+ // all stored references atomically so a crash can't leave dangling refs.
96
+ const reassignSystemPersona = conn.transaction((oldId) => {
97
+ conn.prepare("UPDATE personas SET id = ? WHERE id = ?").run(SYSTEM_PERSONA_ID, oldId);
98
+ conn.prepare("UPDATE settings SET value = ? WHERE key = 'default_persona_id' AND value = ?").run(SYSTEM_PERSONA_ID, oldId);
99
+ conn.prepare("UPDATE sessions SET persona_id = ? WHERE persona_id = ?").run(SYSTEM_PERSONA_ID, oldId);
100
+ conn.prepare("UPDATE tasks SET default_persona_id = ? WHERE default_persona_id = ?").run(SYSTEM_PERSONA_ID, oldId);
101
+ conn.prepare("UPDATE workspaces SET default_persona_id = ? WHERE default_persona_id = ?").run(SYSTEM_PERSONA_ID, oldId);
102
+ });
103
+ reassignSystemPersona(existingSystemByName.id);
104
+ }
105
+ else if (!existingSystemByName) {
106
+ conn
107
+ .prepare(`
108
+ INSERT INTO personas (id, name, description, system_prompt, runtime, model, max_turns, type)
109
+ VALUES (?, 'System', 'Central orchestrator persona', ?, ?, ?, 0, 'agent')
110
+ `)
111
+ .run(SYSTEM_PERSONA_ID, [
112
+ "You are the System — the central orchestrator for Grackle, an agent kernel that manages AI coding agents.",
113
+ "",
114
+ "You help the user coordinate work across their development environments. You can:",
115
+ "- Answer questions and have conversations",
116
+ "- Help plan and break down work into tasks",
117
+ "- Create and manage workspaces (project containers tied to environments)",
118
+ "- Create, assign, and monitor tasks executed by AI coding agents",
119
+ "- Share and query findings (knowledge shared between agents)",
120
+ "",
121
+ "When the user describes work to be done:",
122
+ "1. Help them think through the approach",
123
+ "2. Break complex work into discrete, well-scoped tasks",
124
+ "3. Create tasks with clear titles and descriptions that an AI agent can execute independently",
125
+ "4. Start tasks on appropriate environments",
126
+ "5. Monitor progress and report results",
127
+ "",
128
+ "You are always available for conversation. Think of yourself as the user's AI project manager — you coordinate the agents, track progress, and ensure work is organized effectively.",
129
+ "",
130
+ "Keep responses concise and action-oriented. When the user wants something done, bias toward creating and starting tasks rather than lengthy discussion.",
131
+ ].join("\n"), systemRuntime, systemModel);
132
+ }
133
+ }
134
+ }
135
+ // Seed: create root task (well-known "system" task) if it doesn't exist.
136
+ conn
137
+ .prepare(`
138
+ INSERT OR IGNORE INTO tasks (id, workspace_id, title, description, status, branch, parent_task_id, depth, can_decompose, default_persona_id)
139
+ VALUES (?, NULL, 'System', '', 'not_started', 'system', '', 0, 1, ?)
140
+ `)
141
+ .run(ROOT_TASK_ID, SYSTEM_PERSONA_ID);
142
+ // Backfill: ensure default_persona_id setting exists for upgrades.
143
+ // Existing installations may have personas but no default_persona_id setting,
144
+ // which would cause resolvePersona() to fail when no persona is explicitly specified.
145
+ const existingDefault = conn
146
+ .prepare("SELECT value FROM settings WHERE key = 'default_persona_id'")
147
+ .get();
148
+ if (!existingDefault) {
149
+ // Prefer the seed persona 'claude-code' if it exists; otherwise fall back
150
+ // to the first persona alphabetically.
151
+ const fallback = (conn.prepare("SELECT id FROM personas WHERE id = 'claude-code'").get() ??
152
+ conn.prepare("SELECT id FROM personas ORDER BY name LIMIT 1").get());
153
+ if (fallback) {
154
+ conn
155
+ .prepare("INSERT OR IGNORE INTO settings (key, value) VALUES ('default_persona_id', ?)")
156
+ .run(fallback.id);
157
+ }
158
+ }
159
+ // Backfill: ensure onboarding_completed setting exists.
160
+ // Fresh installs (no pre-existing environments or personas) get "false" to trigger
161
+ // the setup wizard. Upgrades (pre-existing data) get "true" to skip it.
162
+ // personaCount was captured before the seed insert, so it reflects user-created personas.
163
+ const existingOnboarding = conn
164
+ .prepare("SELECT value FROM settings WHERE key = 'onboarding_completed'")
165
+ .get();
166
+ if (!existingOnboarding) {
167
+ const environmentCount = conn
168
+ .prepare("SELECT COUNT(*) as cnt FROM environments")
169
+ .get();
170
+ const isFreshInstall = environmentCount.cnt === 0 && personaCount.cnt === 0;
171
+ conn
172
+ .prepare("INSERT OR IGNORE INTO settings (key, value) VALUES ('onboarding_completed', ?)")
173
+ .run(isFreshInstall ? "false" : "true");
174
+ }
175
+ }
176
+ //# sourceMappingURL=db-seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-seed.js","sourceRoot":"","sources":["../src/db-seed.ts"],"names":[],"mappings":"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"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import Database from "better-sqlite3";
2
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
3
+ import * as schema from "./schema.js";
4
+ /** Error collected from a migration step that uses try-catch for idempotency. */
5
+ export interface MigrationError {
6
+ name: string;
7
+ error: unknown;
8
+ }
9
+ /** Result returned by {@link initDatabase}. */
10
+ export interface InitDatabaseResult {
11
+ migrationErrors: MigrationError[];
12
+ }
13
+ /** Raw better-sqlite3 instance. Available after {@link openDatabase} has been called. */
14
+ declare let sqlite: InstanceType<typeof Database> | undefined;
15
+ /**
16
+ * Drizzle ORM instance wrapping the SQLite database.
17
+ * Available after {@link openDatabase} has been called.
18
+ * Exported as the default export via ESM live binding so that store modules
19
+ * that do `import db from "./db.js"` see the initialized value after startup.
20
+ */
21
+ declare let db: BetterSQLite3Database<typeof schema> & {
22
+ $client: InstanceType<typeof Database>;
23
+ };
24
+ /**
25
+ * Open the SQLite database and initialize the Drizzle ORM instance.
26
+ * Call once at startup before using `db` or `sqlite`.
27
+ * If already initialized, returns silently.
28
+ *
29
+ * @param dbPath - Optional path to the database file. Defaults to `~/.grackle/grackle.db`.
30
+ */
31
+ export declare function openDatabase(dbPath?: string): void;
32
+ /**
33
+ * Initialize all database tables and run migrations.
34
+ * Call once at startup after {@link openDatabase}, or pass an in-memory
35
+ * SQLite instance for testing.
36
+ *
37
+ * @param sqliteOverride - Optional SQLite instance to use instead of the module-level one.
38
+ * @returns Collected migration errors from idempotent try-catch steps.
39
+ */
40
+ export declare function initDatabase(sqliteOverride?: InstanceType<typeof Database>): InitDatabaseResult;
41
+ export { sqlite };
42
+ export { db as default };
43
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAKxE,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+CAA+C;AAC/C,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,cAAc,EAAE,CAAC;CACnC;AAED,yFAAyF;AACzF,QAAA,IAAI,MAAM,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,GAAG,SAAS,CAAC;AAEtD;;;;;GAKG;AACH,QAAA,IAAI,EAAE,EAAG,qBAAqB,CAAC,OAAO,MAAM,CAAC,GAAG;IAC9C,OAAO,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;CACxC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAsElD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,cAAc,CAAC,EAAE,YAAY,CAAC,OAAO,QAAQ,CAAC,GAAG,kBAAkB,CAkd/F;AAED,OAAO,EAAE,MAAM,EAAE,CAAC;AAClB,OAAO,EAAE,EAAE,IAAI,OAAO,EAAE,CAAC"}