tewip-ai 0.57.0 → 0.58.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.
@@ -46,11 +46,16 @@ const DJANGO = [
46
46
  instead: 'for a large table, work in batches with atomic = False on the migration and make it resumable. For a small one this is fine - Tewip cannot see how many rows there are',
47
47
  },
48
48
  {
49
- find: /AddIndex\(|AddConstraint\(/i,
49
+ // The model is named, for two reasons. A finding that says "adds an index or constraint" without
50
+ // saying to what is most of the way to useless, and `createdHere` - which stops an index on a
51
+ // table created moments earlier from counting as a lock - matches on the table name, so with no
52
+ // name it could never suppress anything here. Django writes it `AddIndex(model_name="order", ...)`.
53
+ find: /(?:AddIndex|AddConstraint)\s*\([\s\S]{0,120}?model_name\s*=\s*["']([\w]+)["']/i,
50
54
  unless: /AddIndexConcurrently|AddConstraintNotValid|atomic\s*=\s*False/,
51
55
  risk: 'locks-writes',
52
- what: () => 'adds an index or constraint, which Django builds with a lock unless told otherwise',
56
+ what: (m) => `adds an index or constraint to ${m[1]}, which Django builds with a lock unless told otherwise`,
53
57
  instead: 'AddIndexConcurrently / AddConstraintNotValid from django.contrib.postgres.operations, with atomic = False on the migration',
58
+ table: (m) => m[1],
54
59
  },
55
60
  ];
56
61
  /** Rails migrations. */
@@ -4,7 +4,8 @@
4
4
  // across a whole checkout rather than across a diff and that file went past the 300-line rule.
5
5
  // Keeping it in one place is the point: the alternative was a copy of these patterns in a script,
6
6
  // and two definitions of "is this a migration" drift the moment one of them is fixed.
7
- import { readdirSync, statSync } from 'node:fs';
7
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
8
+ import { dialectOf } from './frameworks.js';
8
9
  import { join, relative } from 'node:path';
9
10
  // A directory called `migration/` says what is probably in it, not what every file in it is. This
10
11
  // matched *anything* underneath one, so Keycloak's `docs/guides/migration/index.adoc` and its 113
@@ -70,8 +71,29 @@ export function migrationsUnder(root, limit = 20_000) {
70
71
  continue;
71
72
  }
72
73
  const rel = relative(root, full).replaceAll(String.fromCharCode(92), '/');
73
- if (SCHEMA_FILE.test(rel) || MIGRATION.test(rel) || NAMED_MIGRATION.test(rel))
74
- found.push(rel);
74
+ if (!SCHEMA_FILE.test(rel) && !MIGRATION.test(rel) && !NAMED_MIGRATION.test(rel))
75
+ continue;
76
+ // And it has to look like one inside. Phase 44 stopped a directory called `migration/` from
77
+ // claiming every file under it by extension; discourse shows that was not enough. `lib/migration/
78
+ // helpers.rb` is a library module, `table_dropper.rb` is a class, and
79
+ // `migrations/converters/config/locales/migrations.en.yml` is a locale file - all three were
80
+ // selected, none could be read, and all three were reported as migrations Tewip could not read.
81
+ // 550 of discourse's 3,095, which is a "could not read" list that is mostly library code.
82
+ //
83
+ // `dialectOf` is the one definition of what a migration is written in, so it decides here too:
84
+ // a file whose dialect is unknown is not an unreadable migration, it is not a migration.
85
+ if (info.size > 2_000_000)
86
+ continue;
87
+ let text = '';
88
+ try {
89
+ text = readFileSync(full, 'utf8');
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ if (dialectOf(rel, text) === 'unknown')
95
+ continue;
96
+ found.push(rel);
75
97
  }
76
98
  };
77
99
  walk(root, 0);
@@ -46,6 +46,11 @@ function createdHere(text) {
46
46
  // EF Core puts the name on the line after the call, so these have to reach across a newline.
47
47
  const FRAMEWORK_CREATES = [
48
48
  /migrationBuilder\s*\.\s*CreateTable\s*\([\s\S]{0,80}?name\s*:\s*["']([\w$.]+)["']/gi,
49
+ // Django names the model rather than the table, and so does its AddIndex, so the two match each
50
+ // other. Saleor never exercises this - Django convention puts CreateModel and AddIndex in
51
+ // separate migrations - but every other dialect here has its create form registered and this
52
+ // one did not, which is the kind of gap that waits for a project with different habits.
53
+ /migrations\s*\.\s*CreateModel\s*\([\s\S]{0,60}?name\s*=\s*["']([\w]+)["']/gi,
49
54
  /queryInterface\s*\.\s*createTable\s*\(\s*["']([\w$.]+)["']/gi,
50
55
  /knex\s*\.\s*schema\s*\.\s*createTable\s*\(\s*["']([\w$.]+)["']/gi,
51
56
  /Schema\s*::\s*create\s*\(\s*["']([\w$.]+)["']/gi,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.57.0",
3
+ "version": "0.58.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": [