@crewx/shared 0.0.6-rc.22 → 0.0.6-rc.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewx/shared",
3
- "version": "0.0.6-rc.22",
3
+ "version": "0.0.6-rc.24",
4
4
  "main": "skill-tracer.js",
5
5
  "exports": {
6
6
  ".": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "description": "Shared utilities for CrewX built-in packages",
30
30
  "dependencies": {
31
- "@crewx/sdk": "0.9.0-rc.107"
31
+ "@crewx/sdk": "0.9.0-rc.109"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^20.0.0",
package/skill-tracer.js CHANGED
@@ -24,9 +24,7 @@ const { generateId } = require('@crewx/sdk');
24
24
  * crewx.db 경로 찾기
25
25
  */
26
26
  function getDbPath() {
27
- if (process.env.CREWX_TRACES_DB || process.env.CREWX_DB) {
28
- return process.env.CREWX_TRACES_DB || process.env.CREWX_DB;
29
- }
27
+ if (process.env.CREWX_DB) return process.env.CREWX_DB;
30
28
 
31
29
  return path.join(os.homedir(), '.crewx', 'crewx.db');
32
30
  }
@@ -35,141 +33,67 @@ function getDbPath() {
35
33
  const getTracesDbPath = getDbPath;
36
34
 
37
35
  /**
38
- * crewx.db 연결 테이블 확인
39
- * Returns null if better-sqlite3 is unavailable or DB cannot be opened.
36
+ * Open an already initialized crewx.db for trace writes.
37
+ * This function never creates a database, directory, or schema.
38
+ * Returns null if better-sqlite3 is unavailable, the database is missing, or
39
+ * the existing file does not contain the tracing tables used by this module.
40
40
  */
41
41
  function getDb() {
42
42
  if (!Database) return null;
43
43
 
44
44
  const dbPath = getDbPath();
45
- const dir = path.dirname(dbPath);
46
-
47
- if (!fs.existsSync(dir)) {
48
- fs.mkdirSync(dir, { recursive: true });
49
- }
45
+ if (!fs.existsSync(dbPath)) return null;
50
46
 
51
- const db = new Database(dbPath);
52
- db.exec('PRAGMA journal_mode = WAL');
53
- db.exec('PRAGMA busy_timeout = 5000');
54
- db.exec('PRAGMA foreign_keys = ON');
55
-
56
- db.exec(`
57
- CREATE TABLE IF NOT EXISTS tasks (
58
- id TEXT PRIMARY KEY,
59
- agent_id TEXT NOT NULL,
60
- user_id TEXT,
61
- prompt TEXT NOT NULL,
62
- mode TEXT NOT NULL DEFAULT 'execute',
63
- status TEXT NOT NULL DEFAULT 'running',
64
- result TEXT,
65
- error TEXT,
66
- started_at TEXT NOT NULL,
67
- completed_at TEXT,
68
- duration_ms INTEGER,
69
- metadata TEXT,
70
- project_id TEXT,
71
- project_name TEXT
72
- )
73
- `);
74
-
75
- db.exec(`
76
- CREATE TABLE IF NOT EXISTS spans (
77
- id TEXT PRIMARY KEY,
78
- task_id TEXT,
79
- parent_span_id TEXT,
80
- name TEXT NOT NULL,
81
- kind TEXT NOT NULL DEFAULT 'internal',
82
- status TEXT NOT NULL DEFAULT 'ok',
83
- started_at TEXT NOT NULL,
84
- completed_at TEXT,
85
- duration_ms INTEGER,
86
- input TEXT,
87
- output TEXT,
88
- error TEXT,
89
- attributes TEXT,
90
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL,
91
- FOREIGN KEY (parent_span_id) REFERENCES spans(id) ON DELETE SET NULL
92
- )
93
- `);
94
-
95
- ensureProjectColumns(db);
96
- ensureSpansTaskIdNullable(db);
97
-
98
- return db;
99
- }
100
-
101
- function ensureProjectColumns(db) {
47
+ let db;
102
48
  try {
103
- const columns = db.prepare('PRAGMA table_info(tasks)').all().map((col) => col.name);
104
- if (!columns.includes('project_id')) {
105
- db.exec(`ALTER TABLE tasks ADD COLUMN project_id TEXT`);
106
- }
107
- if (!columns.includes('project_name')) {
108
- db.exec(`ALTER TABLE tasks ADD COLUMN project_name TEXT`);
109
- }
110
- db.exec(`CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id)`);
111
- } catch {
112
- // Best-effort; ignore failures
113
- }
114
- }
49
+ db = new Database(dbPath, { fileMustExist: true });
50
+ db.exec('PRAGMA journal_mode = WAL');
51
+ db.exec('PRAGMA busy_timeout = 5000');
52
+ db.exec('PRAGMA foreign_keys = ON');
115
53
 
116
- function ensureSpansTaskIdNullable(db) {
117
- try {
118
- const columns = db.prepare('PRAGMA table_info(spans)').all();
119
- const taskIdColumn = columns.find((col) => col.name === 'task_id');
120
- if (!taskIdColumn || taskIdColumn.notnull === 0) {
121
- return;
54
+ if (!hasTraceSchema(db)) {
55
+ db.close();
56
+ return null;
122
57
  }
123
58
 
124
- db.exec('PRAGMA foreign_keys = OFF');
125
- db.exec('BEGIN');
126
- db.exec(`
127
- CREATE TABLE spans_backup (
128
- id TEXT PRIMARY KEY,
129
- task_id TEXT,
130
- parent_span_id TEXT,
131
- name TEXT NOT NULL,
132
- kind TEXT NOT NULL DEFAULT 'internal',
133
- status TEXT NOT NULL DEFAULT 'ok',
134
- started_at TEXT NOT NULL,
135
- completed_at TEXT,
136
- duration_ms INTEGER,
137
- input TEXT,
138
- output TEXT,
139
- error TEXT,
140
- attributes TEXT,
141
- FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE SET NULL,
142
- FOREIGN KEY (parent_span_id) REFERENCES spans_backup(id) ON DELETE SET NULL
143
- )
144
- `);
145
- db.exec(`
146
- INSERT INTO spans_backup (
147
- id, task_id, parent_span_id, name, kind, status, started_at,
148
- completed_at, duration_ms, input, output, error, attributes
149
- )
150
- SELECT
151
- id, task_id, parent_span_id, name, kind, status, started_at,
152
- completed_at, duration_ms, input, output, error, attributes
153
- FROM spans
154
- `);
155
- db.exec('DROP TABLE spans');
156
- db.exec('ALTER TABLE spans_backup RENAME TO spans');
157
- db.exec('COMMIT');
158
- db.exec('PRAGMA foreign_keys = ON');
159
- } catch {
59
+ return db;
60
+ } catch (error) {
160
61
  try {
161
- db.exec('ROLLBACK');
62
+ db?.close();
162
63
  } catch {
163
- // Best-effort rollback
164
- }
165
- try {
166
- db.exec('PRAGMA foreign_keys = ON');
167
- } catch {
168
- // Best-effort; ignore failures
64
+ // Best-effort cleanup after an open or pragma failure.
169
65
  }
66
+ throw error;
170
67
  }
171
68
  }
172
69
 
70
+ function hasTraceSchema(db) {
71
+ const requiredColumns = {
72
+ tasks: ['id'],
73
+ spans: [
74
+ 'id',
75
+ 'task_id',
76
+ 'name',
77
+ 'kind',
78
+ 'status',
79
+ 'started_at',
80
+ 'completed_at',
81
+ 'duration_ms',
82
+ 'input',
83
+ 'output',
84
+ 'error',
85
+ 'attributes',
86
+ ],
87
+ };
88
+
89
+ return Object.entries(requiredColumns).every(([tableName, columns]) => {
90
+ const availableColumns = new Set(
91
+ db.prepare(`PRAGMA table_info(${tableName})`).all().map((column) => column.name),
92
+ );
93
+ return columns.every((column) => availableColumns.has(column));
94
+ });
95
+ }
96
+
173
97
  function resolveProjectContext() {
174
98
  const projectPath = path.resolve(process.cwd());
175
99
  return {
@@ -6,6 +6,36 @@ import Database from 'better-sqlite3';
6
6
 
7
7
  const SPAN_ID_PATTERN = /^spn_[A-Za-z0-9]{8}$/;
8
8
 
9
+ function createTracerDb(dbPath: string): void {
10
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
11
+ const db = new Database(dbPath);
12
+ db.exec(`
13
+ CREATE TABLE tasks (
14
+ id TEXT PRIMARY KEY,
15
+ agent_id TEXT,
16
+ prompt TEXT,
17
+ status TEXT,
18
+ started_at TEXT
19
+ );
20
+ CREATE TABLE spans (
21
+ id TEXT PRIMARY KEY,
22
+ task_id TEXT,
23
+ parent_span_id TEXT,
24
+ name TEXT NOT NULL,
25
+ kind TEXT NOT NULL DEFAULT 'internal',
26
+ status TEXT NOT NULL DEFAULT 'ok',
27
+ started_at TEXT NOT NULL,
28
+ completed_at TEXT,
29
+ duration_ms INTEGER,
30
+ input TEXT,
31
+ output TEXT,
32
+ error TEXT,
33
+ attributes TEXT
34
+ );
35
+ `);
36
+ db.close();
37
+ }
38
+
9
39
  describe('shared/skill-tracer', () => {
10
40
  const sharedDir = path.resolve(__dirname, '..');
11
41
 
@@ -48,6 +78,7 @@ describe('shared/skill-tracer', () => {
48
78
 
49
79
  process.env.CREWX_DB = dbPath;
50
80
  delete process.env.CREWX_TASK_ID;
81
+ createTracerDb(dbPath);
51
82
 
52
83
  try {
53
84
  const trace = tracer.trace('memory', 'index core_sqa');
@@ -69,6 +100,39 @@ describe('shared/skill-tracer', () => {
69
100
  fs.rmSync(tempDir, { recursive: true, force: true });
70
101
  }
71
102
  });
103
+
104
+ it('does not create a missing target database and no-ops without throwing', () => {
105
+ const tracer = require(path.join(sharedDir, 'skill-tracer.js'));
106
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'skill-tracer-missing-'));
107
+ const dbPath = path.join(tempDir, 'nested', 'crewx.db');
108
+ const previousDbPath = process.env.CREWX_DB;
109
+ const previousTaskId = process.env.CREWX_TASK_ID;
110
+
111
+ process.env.CREWX_DB = dbPath;
112
+ delete process.env.CREWX_TASK_ID;
113
+
114
+ try {
115
+ expect(() => {
116
+ const trace = tracer.trace('memory', 'index core_sqa');
117
+ expect(trace._skipped).toBe(true);
118
+ trace.ok('ignored');
119
+ }).not.toThrow();
120
+ expect(fs.existsSync(dbPath)).toBe(false);
121
+ expect(fs.existsSync(path.dirname(dbPath))).toBe(false);
122
+ } finally {
123
+ if (previousDbPath === undefined) {
124
+ delete process.env.CREWX_DB;
125
+ } else {
126
+ process.env.CREWX_DB = previousDbPath;
127
+ }
128
+ if (previousTaskId === undefined) {
129
+ delete process.env.CREWX_TASK_ID;
130
+ } else {
131
+ process.env.CREWX_TASK_ID = previousTaskId;
132
+ }
133
+ fs.rmSync(tempDir, { recursive: true, force: true });
134
+ }
135
+ });
72
136
  });
73
137
 
74
138
  describe('shared/skill-tracer output capture', () => {
@@ -85,6 +149,7 @@ describe('shared/skill-tracer output capture', () => {
85
149
  prevTaskId = process.env.CREWX_TASK_ID;
86
150
  process.env.CREWX_DB = dbPath;
87
151
  delete process.env.CREWX_TASK_ID;
152
+ createTracerDb(dbPath);
88
153
  });
89
154
 
90
155
  afterEach(() => {