astrocode-workflow 0.3.0 → 0.3.2

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 (139) hide show
  1. package/dist/index.js +6 -0
  2. package/dist/shared/metrics.d.ts +66 -0
  3. package/dist/shared/metrics.js +112 -0
  4. package/dist/src/agents/commands.d.ts +9 -0
  5. package/dist/src/agents/commands.js +121 -0
  6. package/dist/src/agents/prompts.d.ts +3 -0
  7. package/dist/src/agents/prompts.js +232 -0
  8. package/dist/src/agents/registry.d.ts +6 -0
  9. package/dist/src/agents/registry.js +242 -0
  10. package/dist/src/agents/types.d.ts +14 -0
  11. package/dist/src/agents/types.js +8 -0
  12. package/dist/src/config/config-handler.d.ts +4 -0
  13. package/dist/src/config/config-handler.js +46 -0
  14. package/dist/src/config/defaults.d.ts +3 -0
  15. package/dist/src/config/defaults.js +3 -0
  16. package/dist/src/config/loader.d.ts +11 -0
  17. package/dist/src/config/loader.js +82 -0
  18. package/dist/src/config/schema.d.ts +194 -0
  19. package/dist/src/config/schema.js +223 -0
  20. package/dist/src/hooks/continuation-enforcer.d.ts +34 -0
  21. package/dist/src/hooks/continuation-enforcer.js +190 -0
  22. package/dist/src/hooks/inject-provider.d.ts +22 -0
  23. package/dist/src/hooks/inject-provider.js +120 -0
  24. package/dist/src/hooks/tool-output-truncator.d.ts +25 -0
  25. package/dist/src/hooks/tool-output-truncator.js +57 -0
  26. package/dist/src/index.d.ts +3 -0
  27. package/dist/src/index.js +308 -0
  28. package/dist/src/shared/deep-merge.d.ts +8 -0
  29. package/dist/src/shared/deep-merge.js +25 -0
  30. package/dist/src/shared/hash.d.ts +1 -0
  31. package/dist/src/shared/hash.js +4 -0
  32. package/dist/src/shared/log.d.ts +7 -0
  33. package/dist/src/shared/log.js +24 -0
  34. package/dist/src/shared/metrics.d.ts +66 -0
  35. package/dist/src/shared/metrics.js +112 -0
  36. package/dist/src/shared/model-tuning.d.ts +9 -0
  37. package/dist/src/shared/model-tuning.js +28 -0
  38. package/dist/src/shared/paths.d.ts +19 -0
  39. package/dist/src/shared/paths.js +64 -0
  40. package/dist/src/shared/text.d.ts +4 -0
  41. package/dist/src/shared/text.js +19 -0
  42. package/dist/src/shared/time.d.ts +1 -0
  43. package/dist/src/shared/time.js +3 -0
  44. package/dist/src/state/adapters/index.d.ts +41 -0
  45. package/dist/src/state/adapters/index.js +115 -0
  46. package/dist/src/state/db.d.ts +16 -0
  47. package/dist/src/state/db.js +225 -0
  48. package/dist/src/state/ids.d.ts +8 -0
  49. package/dist/src/state/ids.js +25 -0
  50. package/dist/src/state/repo-lock.d.ts +3 -0
  51. package/dist/src/state/repo-lock.js +29 -0
  52. package/dist/src/state/schema.d.ts +2 -0
  53. package/dist/src/state/schema.js +251 -0
  54. package/dist/src/state/types.d.ts +71 -0
  55. package/dist/src/state/types.js +1 -0
  56. package/dist/src/tools/artifacts.d.ts +18 -0
  57. package/dist/src/tools/artifacts.js +71 -0
  58. package/dist/src/tools/health.d.ts +8 -0
  59. package/dist/src/tools/health.js +119 -0
  60. package/dist/src/tools/index.d.ts +20 -0
  61. package/dist/src/tools/index.js +94 -0
  62. package/dist/src/tools/init.d.ts +17 -0
  63. package/dist/src/tools/init.js +96 -0
  64. package/dist/src/tools/injects.d.ts +53 -0
  65. package/dist/src/tools/injects.js +325 -0
  66. package/dist/src/tools/metrics.d.ts +7 -0
  67. package/dist/src/tools/metrics.js +61 -0
  68. package/dist/src/tools/repair.d.ts +8 -0
  69. package/dist/src/tools/repair.js +25 -0
  70. package/dist/src/tools/reset.d.ts +8 -0
  71. package/dist/src/tools/reset.js +92 -0
  72. package/dist/src/tools/run.d.ts +13 -0
  73. package/dist/src/tools/run.js +54 -0
  74. package/dist/src/tools/spec.d.ts +12 -0
  75. package/dist/src/tools/spec.js +44 -0
  76. package/dist/src/tools/stage.d.ts +23 -0
  77. package/dist/src/tools/stage.js +371 -0
  78. package/dist/src/tools/status.d.ts +8 -0
  79. package/dist/src/tools/status.js +125 -0
  80. package/dist/src/tools/story.d.ts +23 -0
  81. package/dist/src/tools/story.js +85 -0
  82. package/dist/src/tools/workflow.d.ts +13 -0
  83. package/dist/src/tools/workflow.js +355 -0
  84. package/dist/src/ui/inject.d.ts +12 -0
  85. package/dist/src/ui/inject.js +107 -0
  86. package/dist/src/ui/toasts.d.ts +13 -0
  87. package/dist/src/ui/toasts.js +39 -0
  88. package/dist/src/workflow/artifacts.d.ts +24 -0
  89. package/dist/src/workflow/artifacts.js +45 -0
  90. package/dist/src/workflow/baton.d.ts +72 -0
  91. package/dist/src/workflow/baton.js +166 -0
  92. package/dist/src/workflow/context.d.ts +20 -0
  93. package/dist/src/workflow/context.js +113 -0
  94. package/dist/src/workflow/directives.d.ts +39 -0
  95. package/dist/src/workflow/directives.js +137 -0
  96. package/dist/src/workflow/repair.d.ts +8 -0
  97. package/dist/src/workflow/repair.js +99 -0
  98. package/dist/src/workflow/state-machine.d.ts +86 -0
  99. package/dist/src/workflow/state-machine.js +216 -0
  100. package/dist/src/workflow/story-helpers.d.ts +9 -0
  101. package/dist/src/workflow/story-helpers.js +13 -0
  102. package/dist/state/db.d.ts +1 -0
  103. package/dist/state/db.js +9 -0
  104. package/dist/state/repo-lock.d.ts +3 -0
  105. package/dist/state/repo-lock.js +29 -0
  106. package/dist/test/integration/db-transactions.test.d.ts +1 -0
  107. package/dist/test/integration/db-transactions.test.js +126 -0
  108. package/dist/test/integration/injection-metrics.test.d.ts +1 -0
  109. package/dist/test/integration/injection-metrics.test.js +129 -0
  110. package/dist/tools/health.d.ts +8 -0
  111. package/dist/tools/health.js +119 -0
  112. package/dist/tools/index.js +9 -0
  113. package/dist/tools/metrics.d.ts +7 -0
  114. package/dist/tools/metrics.js +61 -0
  115. package/dist/tools/reset.d.ts +8 -0
  116. package/dist/tools/reset.js +92 -0
  117. package/dist/tools/workflow.js +210 -215
  118. package/dist/ui/inject.d.ts +6 -0
  119. package/dist/ui/inject.js +86 -67
  120. package/dist/workflow/state-machine.d.ts +32 -32
  121. package/dist/workflow/state-machine.js +85 -170
  122. package/package.json +6 -3
  123. package/src/index.ts +8 -0
  124. package/src/shared/metrics.ts +148 -0
  125. package/src/state/db.ts +10 -1
  126. package/src/state/repo-lock.ts +158 -0
  127. package/src/tools/health.ts +128 -0
  128. package/src/tools/index.ts +12 -3
  129. package/src/tools/init.ts +26 -14
  130. package/src/tools/metrics.ts +71 -0
  131. package/src/tools/repair.ts +21 -8
  132. package/src/tools/reset.ts +100 -0
  133. package/src/tools/stage.ts +12 -0
  134. package/src/tools/status.ts +17 -3
  135. package/src/tools/story.ts +41 -15
  136. package/src/tools/workflow.ts +123 -121
  137. package/src/ui/inject.ts +113 -79
  138. package/src/workflow/state-machine.ts +123 -227
  139. package/src/tools/workflow.ts.backup +0 -681
@@ -0,0 +1,129 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { injectChatPrompt } from '../../src/ui/inject';
3
+ import { metrics } from '../../src/shared/metrics';
4
+ describe('Injection Integration Tests', () => {
5
+ beforeEach(() => {
6
+ metrics.clear();
7
+ });
8
+ afterEach(() => {
9
+ metrics.clear();
10
+ });
11
+ it('should record successful injection metrics', async () => {
12
+ const mockCtx = {
13
+ client: {
14
+ session: {
15
+ prompt: vi.fn().mockResolvedValue(undefined)
16
+ }
17
+ },
18
+ sessionID: 'test-session-123'
19
+ };
20
+ await injectChatPrompt({
21
+ ctx: mockCtx,
22
+ sessionId: 'test-session-123',
23
+ text: 'Test message',
24
+ agent: 'TestAgent'
25
+ });
26
+ const injectionStats = metrics.getInjectionStats();
27
+ expect(injectionStats).toBeTruthy();
28
+ expect(injectionStats.total).toBe(1);
29
+ expect(injectionStats.successful).toBe(1);
30
+ expect(injectionStats.failed).toBe(0);
31
+ expect(injectionStats.successRate).toBe(1);
32
+ expect(injectionStats.avgAttempts).toBe(1);
33
+ expect(injectionStats.avgDuration).toBeGreaterThan(0);
34
+ });
35
+ it('should record failed injection metrics', async () => {
36
+ const mockCtx = {
37
+ client: {
38
+ session: {
39
+ prompt: vi.fn().mockRejectedValue(new Error('API unavailable'))
40
+ }
41
+ },
42
+ sessionID: 'test-session-456'
43
+ };
44
+ await expect(injectChatPrompt({
45
+ ctx: mockCtx,
46
+ sessionId: 'test-session-456',
47
+ text: 'Test message that will fail',
48
+ agent: 'TestAgent'
49
+ })).rejects.toThrow('API unavailable');
50
+ const injectionStats = metrics.getInjectionStats();
51
+ expect(injectionStats).toBeTruthy();
52
+ expect(injectionStats.total).toBe(1);
53
+ expect(injectionStats.successful).toBe(0);
54
+ expect(injectionStats.failed).toBe(1);
55
+ expect(injectionStats.successRate).toBe(0);
56
+ });
57
+ it('should handle per-session queuing correctly', async () => {
58
+ const mockCtx1 = {
59
+ client: {
60
+ session: {
61
+ prompt: vi.fn().mockResolvedValue(undefined)
62
+ }
63
+ },
64
+ sessionID: 'session-1'
65
+ };
66
+ const mockCtx2 = {
67
+ client: {
68
+ session: {
69
+ prompt: vi.fn().mockResolvedValue(undefined)
70
+ }
71
+ },
72
+ sessionID: 'session-2'
73
+ };
74
+ // Start multiple injections for different sessions
75
+ const promise1 = injectChatPrompt({
76
+ ctx: mockCtx1,
77
+ sessionId: 'session-1',
78
+ text: 'Message 1',
79
+ agent: 'Agent1'
80
+ });
81
+ const promise2 = injectChatPrompt({
82
+ ctx: mockCtx2,
83
+ sessionId: 'session-2',
84
+ text: 'Message 2',
85
+ agent: 'Agent2'
86
+ });
87
+ const promise3 = injectChatPrompt({
88
+ ctx: mockCtx1,
89
+ sessionId: 'session-1',
90
+ text: 'Message 3',
91
+ agent: 'Agent1'
92
+ });
93
+ await Promise.all([promise1, promise2, promise3]);
94
+ const injectionStats = metrics.getInjectionStats();
95
+ expect(injectionStats).toBeTruthy();
96
+ expect(injectionStats.total).toBe(3);
97
+ expect(injectionStats.successful).toBe(3);
98
+ });
99
+ it('should record errors for permanently failed injections', async () => {
100
+ const mockCtx = {
101
+ client: {
102
+ session: {
103
+ prompt: vi.fn()
104
+ .mockRejectedValueOnce(new Error('Attempt 1'))
105
+ .mockRejectedValueOnce(new Error('Attempt 2'))
106
+ .mockRejectedValueOnce(new Error('Attempt 3'))
107
+ .mockRejectedValueOnce(new Error('Attempt 4'))
108
+ }
109
+ },
110
+ sessionID: 'failing-session'
111
+ };
112
+ await expect(injectChatPrompt({
113
+ ctx: mockCtx,
114
+ sessionId: 'failing-session',
115
+ text: 'This will fail permanently',
116
+ agent: 'FailingAgent'
117
+ })).rejects.toThrow();
118
+ const injectionStats = metrics.getInjectionStats();
119
+ expect(injectionStats).toBeTruthy();
120
+ expect(injectionStats.total).toBe(1);
121
+ expect(injectionStats.successful).toBe(0);
122
+ expect(injectionStats.failed).toBe(1);
123
+ expect(injectionStats.avgAttempts).toBe(4); // Should try 4 times
124
+ // Check that error was recorded
125
+ const systemMetrics = metrics.getMetrics();
126
+ expect(systemMetrics.errors.length).toBeGreaterThan(0);
127
+ expect(systemMetrics.errors.some(e => e.type === 'injection_failure')).toBe(true);
128
+ });
129
+ });
@@ -0,0 +1,8 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ export declare function createAstroHealthTool(opts: {
5
+ ctx: any;
6
+ config: AstrocodeConfig;
7
+ db: SqliteDb;
8
+ }): ToolDefinition;
@@ -0,0 +1,119 @@
1
+ // src/tools/health.ts
2
+ import { tool } from "@opencode-ai/plugin/tool";
3
+ import { getSchemaVersion } from "../state/db";
4
+ import { getActiveRun } from "../workflow/state-machine";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ export function createAstroHealthTool(opts) {
8
+ const { ctx, config, db } = opts;
9
+ return tool({
10
+ description: "Check Astrocode health: DB status, locks, schema, active runs, recent events.",
11
+ args: {},
12
+ execute: async () => {
13
+ const lines = [];
14
+ const repoRoot = ctx.directory || process.cwd();
15
+ const dbPath = config.db?.path || ".astro/astro.db";
16
+ const fullDbPath = path.resolve(repoRoot, dbPath);
17
+ // System info
18
+ lines.push("# Astrocode Health Check");
19
+ lines.push(`- PID: ${process.pid || "unknown"}`);
20
+ lines.push(`- Repo: ${repoRoot}`);
21
+ lines.push(`- DB Path: ${fullDbPath}`);
22
+ // Lock status
23
+ const lockPath = `${repoRoot}/.astro/astro.lock`;
24
+ try {
25
+ if (fs.existsSync(lockPath)) {
26
+ const lockContent = fs.readFileSync(lockPath, "utf8").trim();
27
+ const parts = lockContent.split(" ");
28
+ if (parts.length >= 2) {
29
+ const pid = parseInt(parts[0]);
30
+ const startedAt = parts[1];
31
+ // Check if PID is still running
32
+ try {
33
+ process.kill(pid, 0); // Signal 0 just checks if process exists
34
+ lines.push(`- Lock: HELD by PID ${pid} (started ${startedAt})`);
35
+ }
36
+ catch {
37
+ lines.push(`- Lock: STALE (PID ${pid} not running, started ${startedAt})`);
38
+ lines.push(` → Run: rm "${lockPath}"`);
39
+ }
40
+ }
41
+ else {
42
+ lines.push(`- Lock: MALFORMED (${lockContent})`);
43
+ }
44
+ }
45
+ else {
46
+ lines.push(`- Lock: NONE (no lock file)`);
47
+ }
48
+ }
49
+ catch (e) {
50
+ lines.push(`- Lock: ERROR (${String(e)})`);
51
+ }
52
+ // DB file status
53
+ const dbExists = fs.existsSync(fullDbPath);
54
+ const walExists = fs.existsSync(`${fullDbPath}-wal`);
55
+ const shmExists = fs.existsSync(`${fullDbPath}-shm`);
56
+ lines.push(`- DB Files:`);
57
+ lines.push(` - Main: ${dbExists ? "EXISTS" : "MISSING"}`);
58
+ lines.push(` - WAL: ${walExists ? "EXISTS" : "MISSING"}`);
59
+ lines.push(` - SHM: ${shmExists ? "EXISTS" : "MISSING"}`);
60
+ if (!dbExists) {
61
+ lines.push(`- STATUS: DB MISSING - run astro_init first`);
62
+ return lines.join("\n");
63
+ }
64
+ // Schema version
65
+ try {
66
+ const schemaVersion = getSchemaVersion(db);
67
+ lines.push(`- Schema Version: ${schemaVersion}`);
68
+ }
69
+ catch (e) {
70
+ lines.push(`- Schema Version: ERROR (${String(e)})`);
71
+ lines.push(`- STATUS: DB CORRUPTED`);
72
+ return lines.join("\n");
73
+ }
74
+ // Active run
75
+ try {
76
+ const activeRun = getActiveRun(db);
77
+ if (activeRun) {
78
+ lines.push(`- Active Run: ${activeRun.run_id} (${activeRun.status})`);
79
+ lines.push(` - Story: ${activeRun.story_key}`);
80
+ lines.push(` - Stage: ${activeRun.current_stage_key || "none"}`);
81
+ lines.push(` - Started: ${activeRun.started_at}`);
82
+ }
83
+ else {
84
+ lines.push(`- Active Run: NONE`);
85
+ }
86
+ }
87
+ catch (e) {
88
+ lines.push(`- Active Run: ERROR (${String(e)})`);
89
+ }
90
+ // Recent events
91
+ try {
92
+ const events = db.prepare(`
93
+ SELECT event_id, run_id, stage_key, type, created_at
94
+ FROM events
95
+ ORDER BY created_at DESC
96
+ LIMIT 10
97
+ `).all();
98
+ lines.push(`- Recent Events (${events.length}):`);
99
+ for (const event of events) {
100
+ const stage = event.stage_key ? `/${event.stage_key}` : "";
101
+ lines.push(` - ${event.created_at}: ${event.type} (${event.run_id || "global"}${stage})`);
102
+ }
103
+ }
104
+ catch (e) {
105
+ lines.push(`- Recent Events: ERROR (${String(e)})`);
106
+ }
107
+ // Status summary
108
+ lines.push(``);
109
+ lines.push(`## Status`);
110
+ lines.push(`✅ DB accessible`);
111
+ lines.push(`✅ Schema valid`);
112
+ lines.push(`✅ Lock file checked`);
113
+ if (walExists || shmExists) {
114
+ lines.push(`âš ī¸ WAL/SHM files present - indicates unclean shutdown or active transaction`);
115
+ }
116
+ return lines.join("\n");
117
+ },
118
+ });
119
+ }
@@ -8,6 +8,9 @@ import { createAstroStageStartTool, createAstroStageCompleteTool, createAstroSta
8
8
  import { createAstroArtifactPutTool, createAstroArtifactListTool, createAstroArtifactGetTool } from "./artifacts";
9
9
  import { createAstroInjectPutTool, createAstroInjectListTool, createAstroInjectSearchTool, createAstroInjectGetTool, createAstroInjectEligibleTool, createAstroInjectDebugDueTool } from "./injects";
10
10
  import { createAstroRepairTool } from "./repair";
11
+ import { createAstroHealthTool } from "./health";
12
+ import { createAstroResetTool } from "./reset";
13
+ import { createAstroMetricsTool } from "./metrics";
11
14
  export function createAstroTools(opts) {
12
15
  const { ctx, config, agents, runtime } = opts;
13
16
  const { db } = runtime;
@@ -16,6 +19,9 @@ export function createAstroTools(opts) {
16
19
  // Always available tools (work without database - guaranteed DB-independent)
17
20
  tools.astro_status = createAstroStatusTool({ ctx, config });
18
21
  tools.astro_spec_get = createAstroSpecGetTool({ ctx, config });
22
+ tools.astro_health = createAstroHealthTool({ ctx, config, db });
23
+ tools.astro_reset = createAstroResetTool({ ctx, config, db });
24
+ tools.astro_metrics = createAstroMetricsTool({ ctx, config });
19
25
  // Recovery tool - available even in limited mode to allow DB initialization
20
26
  tools.astro_init = createAstroInitTool({ ctx, config, runtime });
21
27
  // Database-dependent tools
@@ -74,6 +80,9 @@ export function createAstroTools(opts) {
74
80
  ["_astro_inject_eligible", "astro_inject_eligible"],
75
81
  ["_astro_inject_debug_due", "astro_inject_debug_due"],
76
82
  ["_astro_repair", "astro_repair"],
83
+ ["_astro_health", "astro_health"],
84
+ ["_astro_reset", "astro_reset"],
85
+ ["_astro_metrics", "astro_metrics"],
77
86
  ];
78
87
  // Only add aliases for tools that exist
79
88
  for (const [alias, target] of aliases) {
@@ -0,0 +1,7 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ type CreateAstroMetricsToolOptions = {
3
+ ctx: any;
4
+ config: any;
5
+ };
6
+ export declare function createAstroMetricsTool(opts: CreateAstroMetricsToolOptions): ToolDefinition;
7
+ export {};
@@ -0,0 +1,61 @@
1
+ // src/tools/metrics.ts
2
+ import { tool } from "@opencode-ai/plugin/tool";
3
+ import { metrics } from "../shared/metrics";
4
+ export function createAstroMetricsTool(opts) {
5
+ return tool({
6
+ description: "Get performance metrics for Astrocode operations including transaction times, injection success rates, and error statistics.",
7
+ args: {},
8
+ execute: async () => {
9
+ return runMetricsTool();
10
+ },
11
+ });
12
+ }
13
+ function runMetricsTool() {
14
+ const stats = metrics.getMetrics();
15
+ const txStats = metrics.getTransactionStats();
16
+ const injectionStats = metrics.getInjectionStats();
17
+ let output = "# Astrocode Performance Metrics\n\n";
18
+ // Transaction Stats
19
+ if (txStats) {
20
+ output += "## Database Transactions\n\n";
21
+ output += `**Total:** ${txStats.total}\n`;
22
+ output += `**Success Rate:** ${(txStats.successRate * 100).toFixed(1)}% (${txStats.successful}/${txStats.total})\n`;
23
+ output += `**Average Duration:** ${txStats.avgDuration.toFixed(2)}ms\n`;
24
+ output += `**Duration Range:** ${txStats.minDuration}ms - ${txStats.maxDuration}ms\n`;
25
+ output += `**Average Nesting Depth:** ${txStats.avgNestedDepth.toFixed(1)}\n\n`;
26
+ }
27
+ else {
28
+ output += "## Database Transactions\n\nNo transaction data available.\n\n";
29
+ }
30
+ // Injection Stats
31
+ if (injectionStats) {
32
+ output += "## UI Injections\n\n";
33
+ output += `**Total:** ${injectionStats.total}\n`;
34
+ output += `**Success Rate:** ${(injectionStats.successRate * 100).toFixed(1)}% (${injectionStats.successful}/${injectionStats.total})\n`;
35
+ output += `**Average Attempts:** ${injectionStats.avgAttempts.toFixed(1)}\n`;
36
+ output += `**Total Retries:** ${injectionStats.totalRetries}\n`;
37
+ output += `**Average Duration:** ${injectionStats.avgDuration.toFixed(2)}ms\n\n`;
38
+ }
39
+ else {
40
+ output += "## UI Injections\n\nNo injection data available.\n\n";
41
+ }
42
+ // Recent Errors
43
+ if (stats.errors.length > 0) {
44
+ output += "## Recent Errors\n\n";
45
+ const recentErrors = stats.errors.slice(-10); // Last 10 errors
46
+ for (const error of recentErrors) {
47
+ const timestamp = new Date(error.timestamp).toISOString();
48
+ output += `- **[${error.type}]** ${timestamp}: ${error.message}\n`;
49
+ }
50
+ output += "\n";
51
+ }
52
+ else {
53
+ output += "## Recent Errors\n\nNo errors recorded.\n\n";
54
+ }
55
+ // Raw Data Summary
56
+ output += "## Data Summary\n\n";
57
+ output += `**Transactions Tracked:** ${stats.transactions.length}\n`;
58
+ output += `**Injections Tracked:** ${stats.injections.length}\n`;
59
+ output += `**Errors Recorded:** ${stats.errors.length}\n`;
60
+ return output;
61
+ }
@@ -0,0 +1,8 @@
1
+ import { type ToolDefinition } from "@opencode-ai/plugin/tool";
2
+ import type { AstrocodeConfig } from "../config/schema";
3
+ import type { SqliteDb } from "../state/db";
4
+ export declare function createAstroResetTool(opts: {
5
+ ctx: any;
6
+ config: AstrocodeConfig;
7
+ db: SqliteDb;
8
+ }): ToolDefinition;
@@ -0,0 +1,92 @@
1
+ // src/tools/reset.ts
2
+ import { tool } from "@opencode-ai/plugin/tool";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ export function createAstroResetTool(opts) {
6
+ const { ctx, config, db } = opts;
7
+ return tool({
8
+ description: "Reset Astrocode database: safely delete all DB files and WAL/SHM after killing concurrent processes.",
9
+ args: {
10
+ confirm: tool.schema.string().default("").describe("Type 'RESET' to confirm destructive operation"),
11
+ },
12
+ execute: async ({ confirm }) => {
13
+ if (confirm !== "RESET") {
14
+ return [
15
+ "❌ Reset cancelled - confirmation required",
16
+ "",
17
+ "This operation will:",
18
+ "- Delete .astro/astro.db",
19
+ "- Delete .astro/astro.db-wal (if exists)",
20
+ "- Delete .astro/astro.db-shm (if exists)",
21
+ "- Lose all workflow data, stories, runs, artifacts",
22
+ "",
23
+ "To confirm: astro_reset(confirm=\"RESET\")",
24
+ ].join("\n");
25
+ }
26
+ const repoRoot = ctx.directory || process.cwd();
27
+ const dbPath = config.db?.path || ".astro/astro.db";
28
+ const fullDbPath = path.resolve(repoRoot, dbPath);
29
+ const lines = [];
30
+ lines.push("đŸ—‘ī¸ Astrocode Database Reset");
31
+ lines.push(`- Repo: ${repoRoot}`);
32
+ lines.push(`- Target: ${fullDbPath}`);
33
+ // Check for lock file
34
+ const lockPath = `${repoRoot}/.astro/astro.lock`;
35
+ if (fs.existsSync(lockPath)) {
36
+ try {
37
+ const lockContent = fs.readFileSync(lockPath, "utf8").trim();
38
+ const pid = parseInt(lockContent.split(" ")[0]);
39
+ lines.push(`- Lock file found for PID ${pid}`);
40
+ // Try to kill the process
41
+ try {
42
+ process.kill(pid, 'SIGTERM');
43
+ lines.push(`- Sent SIGTERM to PID ${pid}, waiting 2s...`);
44
+ await new Promise(resolve => setTimeout(resolve, 2000));
45
+ }
46
+ catch (e) {
47
+ lines.push(`- Could not kill PID ${pid}: ${String(e)}`);
48
+ }
49
+ }
50
+ catch (e) {
51
+ lines.push(`- Error reading lock file: ${String(e)}`);
52
+ }
53
+ }
54
+ // Delete DB files
55
+ const filesToDelete = [
56
+ fullDbPath,
57
+ `${fullDbPath}-wal`,
58
+ `${fullDbPath}-shm`,
59
+ lockPath,
60
+ ];
61
+ let deletedCount = 0;
62
+ for (const filePath of filesToDelete) {
63
+ try {
64
+ if (fs.existsSync(filePath)) {
65
+ fs.unlinkSync(filePath);
66
+ lines.push(`- Deleted: ${path.relative(repoRoot, filePath)}`);
67
+ deletedCount++;
68
+ }
69
+ else {
70
+ lines.push(`- Skipped: ${path.relative(repoRoot, filePath)} (not found)`);
71
+ }
72
+ }
73
+ catch (e) {
74
+ lines.push(`- Failed to delete ${path.relative(repoRoot, filePath)}: ${String(e)}`);
75
+ }
76
+ }
77
+ lines.push(``);
78
+ if (deletedCount > 0) {
79
+ lines.push(`✅ Reset complete - ${deletedCount} files deleted`);
80
+ lines.push(``);
81
+ lines.push(`Next steps:`);
82
+ lines.push(`1. Run: astro_init`);
83
+ lines.push(`2. Run: astro_status`);
84
+ lines.push(`3. Import your stories and restart workflow`);
85
+ }
86
+ else {
87
+ lines.push(`â„šī¸ No files found to delete`);
88
+ }
89
+ return lines.join("\n");
90
+ },
91
+ });
92
+ }