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
- const SCHEMA_FILE = /\.(sql)$|(^|\/)(migrations?|db\/migrate)\/.*\.(py|rb|js|ts)$/i;
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
- if (seen >= limit)
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
- const inMigrations = /(^|\/)(migrations?|db\/migrate|changelog)\//i.test(rel);
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 line = body
219
+ const lines = body
220
220
  .split(String.fromCharCode(10))
221
221
  .map((l) => l.trim())
222
- .find((l) => l !== '' && l !== '{' && l !== '}');
223
- return (line ?? '').slice(0, 120);
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.49.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": [