@mknrt/autotests-overkill 1.2.0 → 1.2.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.
package/README.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  `Autotests Overkill` is a standalone AI-assisted testing platform for `autotests2`, `caseplatform-web`, optional `fis_platform` backend context, and CI artifacts. It is designed for Codex-style work: inspect existing coverage, extract frontend/backend contracts and runtime logic, generate draft specs, triage failures, and narrow rerun scope without silently mutating consumer repositories.
4
4
 
5
+ Runtime requirement:
6
+
7
+ - Node.js 22.5+ in both Windows and WSL environments
8
+ - No native database addon is required; the knowledge store runs on the built-in `node:sqlite` module so the same package install works from either environment
9
+
5
10
  ## What v1.1 adds
6
11
 
7
12
  - Legacy widget contract extraction for files such as `dateRangeInput_control.js` and `dateRangeInput_singleton.js`
@@ -87,6 +92,11 @@ The platform reads `overkill.config.json` with these keys:
87
92
 
88
93
  The default path is local and artifacts-first. Missing ReportPortal or GitLab credentials do not break tools; the platform keeps using local stats, screenshots, videos, and captured API payloads.
89
94
 
95
+ ## Troubleshooting
96
+
97
+ - If MCP or indexing fails with `ERR_UNKNOWN_BUILTIN_MODULE` for `node:sqlite`, your Node.js version is too old. Upgrade to Node.js 22.5 or newer in that environment.
98
+ - If startup fails with `database is locked`, another process is already using the same cache database. Stop the competing process or configure a different `cacheDir`.
99
+
90
100
  ## Backend integration
91
101
 
92
102
  Backend indexing is discovery-first and evidence-driven, not hardcoded. The first bridge is an `ApiDiscoveryCatalog` built from `autotests2` support code such as request wrappers, endpoint constants, `cy.request`, `fetch`, `XMLHttpRequest`, and API capture helpers. Selected high-signal families become `api-interaction` documents with `normalizedPaths`, `consumerSignalIds`, `evidencePaths`, `confidence`, and `whySelected`.
@@ -1,5 +1,6 @@
1
1
  import { createRuntimeAppContext } from '../appContext.js';
2
2
  import { refreshKnowledge } from '../indexer/refreshPipeline.js';
3
+ import { formatRuntimeStartupError } from './runtimeStartupError.js';
3
4
  import { isDirectExecution } from './shared.js';
4
5
  export async function runIndexKnowledge() {
5
6
  const context = await createRuntimeAppContext();
@@ -7,5 +8,11 @@ export async function runIndexKnowledge() {
7
8
  console.log(JSON.stringify(result, null, 2));
8
9
  }
9
10
  if (isDirectExecution(import.meta.url)) {
10
- await runIndexKnowledge();
11
+ try {
12
+ await runIndexKnowledge();
13
+ }
14
+ catch (error) {
15
+ console.error(formatRuntimeStartupError('index', error));
16
+ process.exitCode = 1;
17
+ }
11
18
  }
@@ -1,10 +1,17 @@
1
1
  import { createRuntimeAppContext } from '../appContext.js';
2
2
  import { runMcpServer } from '../mcp/server.js';
3
+ import { formatRuntimeStartupError } from './runtimeStartupError.js';
3
4
  import { isDirectExecution } from './shared.js';
4
5
  export async function startMcpServer() {
5
6
  const context = await createRuntimeAppContext();
6
7
  await runMcpServer(context);
7
8
  }
8
9
  if (isDirectExecution(import.meta.url)) {
9
- await startMcpServer();
10
+ try {
11
+ await startMcpServer();
12
+ }
13
+ catch (error) {
14
+ console.error(formatRuntimeStartupError('mcp', error));
15
+ process.exitCode = 1;
16
+ }
10
17
  }
@@ -0,0 +1,3 @@
1
+ type StartupCommand = 'index' | 'mcp';
2
+ export declare function formatRuntimeStartupError(command: StartupCommand, error: unknown): string;
3
+ export {};
@@ -0,0 +1,60 @@
1
+ function asError(error) {
2
+ if (error instanceof Error) {
3
+ return error;
4
+ }
5
+ return new Error(String(error));
6
+ }
7
+ function detectStartupError(error) {
8
+ const code = 'code' in error ? String(error.code) : '';
9
+ const message = `${error.message}\n${error.stack ?? ''}`;
10
+ if (code === 'ERR_UNKNOWN_BUILTIN_MODULE'
11
+ && message.includes('node:sqlite')) {
12
+ return {
13
+ kind: 'unsupported-node-version',
14
+ summary: 'The current Node.js runtime does not provide the built-in node:sqlite module.',
15
+ recoverySteps: [
16
+ 'Upgrade Node.js to version 22.5 or newer in the environment where the server will run.',
17
+ 'After upgrading Node.js, reinstall dependencies in that same environment.',
18
+ ],
19
+ };
20
+ }
21
+ if (code === 'SQLITE_BUSY' || message.includes('database is locked')) {
22
+ return {
23
+ kind: 'sqlite-busy',
24
+ summary: 'The knowledge database is locked by another process.',
25
+ recoverySteps: [
26
+ 'Stop other autotests-overkill indexing or MCP processes that use the same cache directory.',
27
+ 'If you need concurrent sessions, point them to different `cacheDir` values in `overkill.config.json`.',
28
+ ],
29
+ };
30
+ }
31
+ return {
32
+ kind: 'unknown',
33
+ summary: error.message,
34
+ recoverySteps: [],
35
+ };
36
+ }
37
+ export function formatRuntimeStartupError(command, error) {
38
+ const normalized = asError(error);
39
+ const details = detectStartupError(normalized);
40
+ const lines = [
41
+ `Failed to start autotests-overkill ${command}.`,
42
+ details.summary,
43
+ ];
44
+ if (details.kind !== 'unknown') {
45
+ lines.push('');
46
+ lines.push('How to fix:');
47
+ for (const step of details.recoverySteps) {
48
+ lines.push(`- ${step}`);
49
+ }
50
+ }
51
+ lines.push('');
52
+ lines.push(`cwd: ${process.cwd()}`);
53
+ lines.push(`node: ${process.version}`);
54
+ if (normalized.stack) {
55
+ lines.push('');
56
+ lines.push('Original error:');
57
+ lines.push(normalized.stack);
58
+ }
59
+ return lines.join('\n');
60
+ }
@@ -1,3 +1,4 @@
1
+ import crypto from 'node:crypto';
1
2
  export function extractBackendContracts(input) {
2
3
  const documents = input.sources.flatMap((source) => extractRecordsFromSource(source));
3
4
  return {
@@ -26,7 +27,14 @@ function createRecord(source, httpMethod, endpointPath, body) {
26
27
  ].filter((value) => Boolean(value));
27
28
  const ruleHints = extractRuleHints(body);
28
29
  return {
29
- id: `fis-platform:${source.matchedFamily}:${httpMethod ?? 'RESOURCE'}:${source.path}:${resolvedEndpointPath}`,
30
+ id: [
31
+ 'fis-platform',
32
+ source.matchedFamily,
33
+ httpMethod ?? 'RESOURCE',
34
+ source.path,
35
+ resolvedEndpointPath,
36
+ crypto.createHash('sha1').update(body).digest('hex').slice(0, 12),
37
+ ].join(':'),
30
38
  title: `${httpMethod ? `${httpMethod} ` : ''}${resolvedEndpointPath}`,
31
39
  path: source.path,
32
40
  body,
@@ -96,6 +96,7 @@ export async function refreshKnowledge(context) {
96
96
  }
97
97
  for (const record of apiInteractions.documents) {
98
98
  documents.push(documentFrom({
99
+ id: record.id,
99
100
  sourceKind: record.sourceKind,
100
101
  repoKind: record.repoKind,
101
102
  path: record.path,
@@ -112,6 +113,7 @@ export async function refreshKnowledge(context) {
112
113
  snapshotStore.write('backend-contracts', backendContracts);
113
114
  for (const record of backendContracts.documents) {
114
115
  documents.push(documentFrom({
116
+ id: record.id,
115
117
  sourceKind: record.sourceKind,
116
118
  repoKind: record.repoKind,
117
119
  path: record.path,
@@ -201,7 +203,12 @@ function repoState(root) {
201
203
  }
202
204
  function documentFrom(input) {
203
205
  return {
204
- id: crypto.createHash('sha1').update(`${input.sourceKind}:${input.path}`).digest('hex'),
206
+ id: input.id ?? crypto.createHash('sha1').update([
207
+ input.repoKind,
208
+ input.sourceKind,
209
+ input.path,
210
+ input.title,
211
+ ].join(':')).digest('hex'),
205
212
  sourceKind: input.sourceKind,
206
213
  repoKind: input.repoKind,
207
214
  path: input.path,
@@ -1,3 +1,3 @@
1
- import Database from 'better-sqlite3';
2
- export type KnowledgeDatabase = Database.Database;
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export type KnowledgeDatabase = DatabaseSync;
3
3
  export declare function createKnowledgeDatabase(filePath: string): KnowledgeDatabase;
@@ -1,8 +1,15 @@
1
- import Database from 'better-sqlite3';
1
+ import { DatabaseSync } from 'node:sqlite';
2
2
  import { baseMigrations } from './migrations.js';
3
3
  export function createKnowledgeDatabase(filePath) {
4
- const db = new Database(filePath);
5
- db.pragma('journal_mode = WAL');
4
+ const db = new DatabaseSync(filePath);
5
+ // This database is a rebuildable cache, so we optimize for fast refreshes over crash durability.
6
+ db.exec(`
7
+ PRAGMA journal_mode = MEMORY;
8
+ PRAGMA synchronous = OFF;
9
+ PRAGMA temp_store = MEMORY;
10
+ PRAGMA cache_size = -20000;
11
+ PRAGMA busy_timeout = 5000;
12
+ `);
6
13
  db.exec(baseMigrations);
7
14
  return db;
8
15
  }
@@ -1,7 +1,11 @@
1
+ const INSERT_DOCUMENT_SQL = `
2
+ INSERT INTO documents (id, source_kind, repo_kind, path, title, body, metadata_json, updated_at)
3
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
4
+ `;
5
+ const INSERT_FTS_SQL = 'INSERT INTO documents_fts (id, path, title, body) VALUES (?, ?, ?, ?)';
1
6
  export function upsertDocuments(db, documents) {
2
7
  const upsertDocument = db.prepare(`
3
- INSERT INTO documents (id, source_kind, repo_kind, path, title, body, metadata_json, updated_at)
4
- VALUES (@id, @sourceKind, @repoKind, @path, @title, @body, @metadataJson, @updatedAt)
8
+ ${INSERT_DOCUMENT_SQL}
5
9
  ON CONFLICT(id) DO UPDATE SET
6
10
  source_kind = excluded.source_kind,
7
11
  repo_kind = excluded.repo_kind,
@@ -12,35 +16,32 @@ export function upsertDocuments(db, documents) {
12
16
  updated_at = excluded.updated_at
13
17
  `);
14
18
  const deleteFts = db.prepare('DELETE FROM documents_fts WHERE id = ?');
15
- const insertFts = db.prepare('INSERT INTO documents_fts (id, path, title, body) VALUES (?, ?, ?, ?)');
16
- const transaction = db.transaction((records) => {
17
- for (const record of records) {
18
- upsertDocument.run(record);
19
+ const insertFts = db.prepare(INSERT_FTS_SQL);
20
+ runInTransaction(db, () => {
21
+ for (const record of documents) {
22
+ upsertDocument.run(record.id, record.sourceKind, record.repoKind, record.path, record.title, record.body, record.metadataJson, record.updatedAt);
19
23
  deleteFts.run(record.id);
20
24
  insertFts.run(record.id, record.path, record.title, record.body);
21
25
  }
22
26
  });
23
- transaction(documents);
24
27
  }
25
28
  export function syncDocumentsByRepoKinds(db, documents, repoKinds) {
26
- upsertDocuments(db, documents);
27
29
  const normalizedRepoKinds = [...new Set(repoKinds)];
28
- const selectIds = db.prepare('SELECT id FROM documents WHERE repo_kind = ?');
29
- const deleteDocument = db.prepare('DELETE FROM documents WHERE id = ?');
30
- const deleteFts = db.prepare('DELETE FROM documents_fts WHERE id = ?');
31
- const transaction = db.transaction(() => {
32
- for (const repoKind of normalizedRepoKinds) {
33
- const desiredIds = new Set(documents.filter((record) => record.repoKind === repoKind).map((record) => record.id));
34
- const existingIds = selectIds.all(repoKind);
35
- for (const row of existingIds) {
36
- if (!desiredIds.has(row.id)) {
37
- deleteDocument.run(row.id);
38
- deleteFts.run(row.id);
39
- }
40
- }
30
+ const insertDocument = db.prepare(INSERT_DOCUMENT_SQL);
31
+ const insertFts = db.prepare(INSERT_FTS_SQL);
32
+ runInTransaction(db, () => {
33
+ if (normalizedRepoKinds.length > 0) {
34
+ const placeholders = normalizedRepoKinds.map(() => '?').join(', ');
35
+ db.prepare(`DELETE FROM documents_fts WHERE id IN (SELECT id FROM documents WHERE repo_kind IN (${placeholders}))`)
36
+ .run(...normalizedRepoKinds);
37
+ db.prepare(`DELETE FROM documents WHERE repo_kind IN (${placeholders})`)
38
+ .run(...normalizedRepoKinds);
39
+ }
40
+ for (const record of documents) {
41
+ insertDocument.run(record.id, record.sourceKind, record.repoKind, record.path, record.title, record.body, record.metadataJson, record.updatedAt);
42
+ insertFts.run(record.id, record.path, record.title, record.body);
41
43
  }
42
44
  });
43
- transaction();
44
45
  }
45
46
  export function countDocuments(db) {
46
47
  const row = db.prepare('SELECT COUNT(*) AS count FROM documents').get();
@@ -76,3 +77,14 @@ function normalizeFtsQuery(query) {
76
77
  .map((token) => `"${token.replaceAll('"', '""')}"`)
77
78
  .join(' OR ');
78
79
  }
80
+ function runInTransaction(db, operation) {
81
+ db.exec('BEGIN');
82
+ try {
83
+ operation();
84
+ db.exec('COMMIT');
85
+ }
86
+ catch (error) {
87
+ db.exec('ROLLBACK');
88
+ throw error;
89
+ }
90
+ }
@@ -6,7 +6,7 @@
6
6
 
7
7
  **Architecture:** The repository will be a TypeScript Node.js workspace with a thin MCP server over focused domain services. Data flows through configurable connectors into a SQLite + FTS knowledge store plus JSON snapshots; each MCP tool is a small orchestration layer over reusable analyzers, not a monolithic “smart tool”. Skills and prompt templates sit on top of tool contracts, while integration docs and sample consumer config keep `autotests2` as a consumer, not a host.
8
8
 
9
- **Tech Stack:** Node.js, TypeScript, `@modelcontextprotocol/server`, `zod`, `better-sqlite3`, SQLite FTS5, `fast-glob`, `typescript`, `tsx`, `vitest`
9
+ **Tech Stack:** Node.js, TypeScript, `@modelcontextprotocol/server`, `zod`, built-in `node:sqlite`, SQLite FTS5, `fast-glob`, `typescript`, `tsx`, `vitest`
10
10
 
11
11
  ---
12
12
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **Architecture:** Extend the existing `connectors -> extractors -> SQLite FTS -> domain -> MCP tools` pipeline with one explicit bridge object, `ApiDiscoveryCatalog`, plus two narrow knowledge layers: `api-interaction` and `backend-contract`. Build the catalog from `autotests2` support code first, pass it into `fis_platform` backend discovery explicitly, then store only evidence-backed documents with confidence metadata. Runtime API captures stay on-demand triage evidence in iteration 1; persistent runtime observation indexing is deferred until static interaction indexing proves useful.
8
8
 
9
- **Tech Stack:** TypeScript, Node.js, Zod, better-sqlite3/FTS5, Vitest, MCP SDK, Java source parsing via regex/structured text extraction.
9
+ **Tech Stack:** TypeScript, Node.js, Zod, built-in node:sqlite/FTS5, Vitest, MCP SDK, Java source parsing via regex/structured text extraction.
10
10
 
11
11
  ---
12
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mknrt/autotests-overkill",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "AI-assisted testing intelligence platform for autotests2, caseplatform-web, and CI artifacts",
@@ -15,7 +15,7 @@
15
15
  "url": "git+https://github.com/iamknrt/autotests-overkill.git"
16
16
  },
17
17
  "engines": {
18
- "node": ">=20.0.0"
18
+ "node": ">=22.5.0"
19
19
  },
20
20
  "bin": {
21
21
  "autotests-overkill": "./bin/autotests-overkill.js"
@@ -61,12 +61,10 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@modelcontextprotocol/sdk": "^1.13.0",
64
- "better-sqlite3": "^12.6.2",
65
64
  "fast-glob": "^3.3.3",
66
65
  "zod": "^3.24.1"
67
66
  },
68
67
  "devDependencies": {
69
- "@types/better-sqlite3": "^7.6.12",
70
68
  "@types/node": "^22.15.3",
71
69
  "tsx": "^4.19.4",
72
70
  "typescript": "^5.8.3",