tewip-ai 0.49.0 → 0.50.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.
|
@@ -13,8 +13,12 @@ import { dirname, resolve } from 'node:path';
|
|
|
13
13
|
import { changeSummary } from '../quality/grounding.js';
|
|
14
14
|
import { dialectOf, forwardOnly, irreversible, readFramework } from './frameworks.js';
|
|
15
15
|
import { readSql } from './statements.js';
|
|
16
|
-
const MIGRATION = /(^|\/)(migrations?|db\/migrate|prisma\/migrations|alembic|flyway|sql)\//i;
|
|
17
|
-
|
|
16
|
+
const MIGRATION = /(^|\/)(migrations?|migration|db\/migrate|prisma\/migrations|alembic|flyway|changelogs?|sql)\//i;
|
|
17
|
+
// A changelog is often not in a directory that says so. OpenMRS keeps 44 Liquibase changelogs in
|
|
18
|
+
// `api/src/main/resources/` named `liquibase-*.xml`, and the path filter saw none of them - so a
|
|
19
|
+
// real healthcare system with 44 migration files was told it had none. The name counts too.
|
|
20
|
+
const NAMED_MIGRATION = /(^|\/)(liquibase|changelog)[\w.-]*\.(xml|ya?ml|json)$/i;
|
|
21
|
+
const SCHEMA_FILE = /\.(sql)$|(^|\/)(migrations?|migration|db\/migrate)\/.*\.(py|rb|js|ts|php|cs)$/i;
|
|
18
22
|
/** The schema files in this change. */
|
|
19
23
|
export function schemaFiles(cmd) {
|
|
20
24
|
const named = cmd.files !== undefined && cmd.files.length > 0;
|
|
@@ -24,7 +28,7 @@ export function schemaFiles(cmd) {
|
|
|
24
28
|
// Asking for a file by name overrides the path filter: the filter exists to find migrations in
|
|
25
29
|
// a diff of a hundred files, not to argue with somebody who pointed at one. A TypeORM migration
|
|
26
30
|
// sitting outside a migrations/ directory was being answered with "no migration in this change".
|
|
27
|
-
.filter((f) => (named || SCHEMA_FILE.test(f) || MIGRATION.test(f)) && existsSync(resolve(cmd.root, f)));
|
|
31
|
+
.filter((f) => (named || SCHEMA_FILE.test(f) || MIGRATION.test(f) || NAMED_MIGRATION.test(f)) && existsSync(resolve(cmd.root, f)));
|
|
28
32
|
}
|
|
29
33
|
/**
|
|
30
34
|
* Tables this migration creates itself.
|
|
@@ -37,6 +37,7 @@ export function lookAround(root, limit = 4000) {
|
|
|
37
37
|
hasWorkflows: existsSync(join(root, '.github', 'workflows')),
|
|
38
38
|
hasStandards: existsSync(join(root, '.tewip', 'standards')),
|
|
39
39
|
filesSeen: false,
|
|
40
|
+
stoppedEarly: false,
|
|
40
41
|
};
|
|
41
42
|
let seen = 0;
|
|
42
43
|
// A queue rather than recursion: every directory at one depth is read before any at the next.
|
|
@@ -53,8 +54,13 @@ export function lookAround(root, limit = 4000) {
|
|
|
53
54
|
continue;
|
|
54
55
|
}
|
|
55
56
|
for (const entry of entries) {
|
|
56
|
-
|
|
57
|
+
// Out of budget with entries still unread in *this* directory. Checking only the queue was
|
|
58
|
+
// not enough: a project whose files sit in one flat directory empties the queue before the
|
|
59
|
+
// budget runs out, so the look stopped short and did not know it.
|
|
60
|
+
if (seen >= limit) {
|
|
61
|
+
found.stoppedEarly = true;
|
|
57
62
|
break;
|
|
63
|
+
}
|
|
58
64
|
if (SKIP.has(entry) || (entry.startsWith('.') && entry !== '.github'))
|
|
59
65
|
continue;
|
|
60
66
|
const full = join(dir, entry);
|
|
@@ -78,7 +84,10 @@ export function lookAround(root, limit = 4000) {
|
|
|
78
84
|
found.perfReports.push(rel);
|
|
79
85
|
else if (/(k6|lighthouse|summary|lhr)[\w.-]*\.json$/i.test(entry) && info.size < 8_000_000)
|
|
80
86
|
found.perfReports.push(rel);
|
|
81
|
-
|
|
87
|
+
// A changelog is often not in a directory that says so: OpenMRS keeps 44 Liquibase files in
|
|
88
|
+
// `api/src/main/resources/` named `liquibase-*.xml`. The name counts as well as the path.
|
|
89
|
+
const inMigrations = /(^|\/)(migrations?|migration|db\/migrate|changelogs?)\//i.test(rel) ||
|
|
90
|
+
/(^|\/)(liquibase|changelog)[\w.-]*\.(xml|ya?ml|json)$/i.test(rel);
|
|
82
91
|
// Reading a file is only worth it for the two questions a name cannot answer: is this a
|
|
83
92
|
// test, and is this a migration. Both are cheap, and both are often wrong from the path.
|
|
84
93
|
const language = languageOf(entry);
|
|
@@ -101,6 +110,8 @@ export function lookAround(root, limit = 4000) {
|
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
}
|
|
113
|
+
if (seen >= limit && queue.length > 0)
|
|
114
|
+
found.stoppedEarly = true;
|
|
104
115
|
return found;
|
|
105
116
|
}
|
|
106
117
|
const total = (counts) => [...counts.values()].reduce((a, b) => a + b, 0);
|
|
@@ -139,19 +150,19 @@ export function suggest(found) {
|
|
|
139
150
|
if (tests > 0) {
|
|
140
151
|
out.push({
|
|
141
152
|
command: 'tewip vouch',
|
|
142
|
-
because: `${tests} test ${tests === 1 ? 'file' : 'files'} here (${named(found.tests)}). This reads them and names the ones that cannot fail - no model, a second or two.`,
|
|
153
|
+
because: `${found.stoppedEarly ? 'At least ' : ''}${tests} test ${tests === 1 ? 'file' : 'files'} here (${named(found.tests)}). This reads them and names the ones that cannot fail - no model, a second or two.`,
|
|
143
154
|
});
|
|
144
155
|
}
|
|
145
156
|
if (migrations > 0) {
|
|
146
157
|
out.push({
|
|
147
158
|
command: 'tewip schema --diff main...HEAD',
|
|
148
|
-
because: `${migrations} ${migrations === 1 ? 'migration' : 'migrations'} here (${named(found.migrations)}). This says what a change would do to a live database before it runs.`,
|
|
159
|
+
because: `${found.stoppedEarly ? 'At least ' : ''}${migrations} ${migrations === 1 ? 'migration' : 'migrations'} here (${named(found.migrations)}). This says what a change would do to a live database before it runs.`,
|
|
149
160
|
});
|
|
150
161
|
}
|
|
151
162
|
if (found.markup > 0) {
|
|
152
163
|
out.push({
|
|
153
164
|
command: 'tewip access',
|
|
154
|
-
because: `${found.markup} markup ${found.markup === 1 ? 'file' : 'files'} here. This names who cannot use the interface, against the WCAG criterion a regulator would cite.`,
|
|
165
|
+
because: `${found.stoppedEarly ? 'At least ' : ''}${found.markup} markup ${found.markup === 1 ? 'file' : 'files'} here. This names who cannot use the interface, against the WCAG criterion a regulator would cite.`,
|
|
155
166
|
});
|
|
156
167
|
}
|
|
157
168
|
for (const report of found.perfReports.slice(0, 1)) {
|
|
@@ -187,6 +198,11 @@ export function renderStart(found, root) {
|
|
|
187
198
|
}
|
|
188
199
|
lines.push('## What it will not do here', '');
|
|
189
200
|
const cannot = [];
|
|
201
|
+
if (found.stoppedEarly) {
|
|
202
|
+
cannot.push('This project is larger than Tewip looks at from here, so the counts above are **at least** ' +
|
|
203
|
+
'that many rather than exactly. The commands themselves read everything; only this first ' +
|
|
204
|
+
'glance is bounded, so that it stays instant.');
|
|
205
|
+
}
|
|
190
206
|
if (total(found.tests) === 0)
|
|
191
207
|
cannot.push('No test files were found, so nothing can be said about whether your tests could fail or would catch a bug.');
|
|
192
208
|
if (total(found.migrations) === 0)
|
|
@@ -216,9 +216,15 @@ const stripLines = (body, lines) => body
|
|
|
216
216
|
.join(String.fromCharCode(10));
|
|
217
217
|
/** The first line that does something, for the report to quote. */
|
|
218
218
|
function firstRealLine(body) {
|
|
219
|
-
const
|
|
219
|
+
const lines = body
|
|
220
220
|
.split(String.fromCharCode(10))
|
|
221
221
|
.map((l) => l.trim())
|
|
222
|
-
.
|
|
223
|
-
|
|
222
|
+
.filter((l) => l !== '' && l !== '{' && l !== '}');
|
|
223
|
+
// A comment is not the evidence. Saleor's tests are written `# given` / `# when / then`, so the
|
|
224
|
+
// quoted evidence for 166 real findings was the single character "#", which tells a reader
|
|
225
|
+
// nothing about the test it is accusing. The line that does something is the evidence.
|
|
226
|
+
// Nor is a line with nothing in it but punctuation: a lone quote closing a docstring, a stray
|
|
227
|
+
// bracket. Evidence has to be something a reader can recognise as the test doing work.
|
|
228
|
+
const code = lines.find((l) => !/^(#|\/\/|\/\*|\*|"""|'''|--)/.test(l) && /[A-Za-z0-9]/.test(l));
|
|
229
|
+
return (code ?? lines[0] ?? '').slice(0, 120);
|
|
224
230
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tewip-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"mcpName": "io.github.hmamut39/tewip",
|
|
5
5
|
"description": "Tewip finds why each failing test failed - real bug, flaky test, environment, test data or changed selector - for any stack, from the command line, CI, or your editor.",
|
|
6
6
|
"keywords": [
|