tewip-ai 0.16.0 → 0.16.2

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
@@ -14,7 +14,23 @@ It works for every stack: Node.js (node:test, Jest, Vitest, Mocha), Python (pyte
14
14
 
15
15
  Formerly *triage-agent*: the repository moved to `hmamut39/tewip`, and GitHub redirects the old address. See [ROADMAP.md](ROADMAP.md) for the plan.
16
16
 
17
- **Status: phases 1 to 4 are built, Phase 5 (auto-fix) is in progress, and phases 6 and 7 (test plans and verified test writing) are built.** Those phases are collection, rule-based diagnosis, LLM diagnosis with a real-data eval, multi-role views, and planning and writing tests in the project's own framework. It runs live in GitHub CI and answers questions from AI assistants through MCP. On fresh real Gutenberg CI failures, with the pipeline frozen before labelling, accuracy is 93.4%; the caveats are in [eval/README.md](eval/README.md).
17
+ **Status: phases 1 to 14 are built; the two auto-fix phases (5 and 8) are the ones still in progress.** Tewip collects failures from any framework, diagnoses them with rules first and a model only for what rules cannot settle, and turns that into work for each role: a CI comment and merge gate, test plans and verified tests, a flake dossier, a PR-risk review, security scanning, tickets in Jira, Rally, GitHub Issues, Azure Boards or Linear, checks generated from a Figma design, specifications written for people, and - opt in, and only when proved - fixes to test or application code. It runs live in GitHub CI and answers questions from AI assistants through MCP. On fresh real Gutenberg CI failures, with the pipeline frozen before labelling, accuracy is 93.4%; across six real datasets it ranges from 86.1% to 99.7%, and the caveats are in [eval/README.md](eval/README.md).
18
+
19
+ ## What it costs
20
+
21
+ Most failures never reach a model. On 3,556 real CI failures from six public projects, the
22
+ deterministic rules answered **75.1%** on their own - and on the labelled ones they answered
23
+ correctly **1,979 times out of 2,012 (98.4%)**. A rule-answered failure costs nothing, explains
24
+ itself, and gives the same answer every time.
25
+
26
+ Only the remaining quarter is sent to a model, so triaging **1,000 real failures costs about
27
+ $1.12** at gpt-5.4-mini's published price. That is measured, not estimated: three real uncached
28
+ calls on held-out failures billed 3,624 prompt and 499 completion tokens each. A real week costs
29
+ less, because a failure that repeats is answered from cache, and a failure someone has corrected
30
+ never goes to the model again.
31
+
32
+ Tewip prints what every command costs, caps its own spend, and works with no key at all - the rules
33
+ run on their own and everything they cannot settle is left as UNKNOWN rather than guessed.
18
34
 
19
35
  ## Use it in your project
20
36
 
@@ -0,0 +1,58 @@
1
+ /** The line is checking the value rather than creating anything with it. */
2
+ const ASSERTING = /\bexpect\s*\(|\.\s*should\s*\(|\bassert(?:Equals?|That|ion)?\b|\btoBe\w*\s*\(|\bto(?:Have|Contain)\w*\s*\(/;
3
+ /**
4
+ * The line is making or sending something: this is data, and making it unique is the fix.
5
+ * The object-property form matters most and was missing at first - real test data is written
6
+ * `name: "Secondi"` and `employee_name="Nguyen Van A"` far more often than it is assigned to its
7
+ * own variable, so without it four real creation sites were misread as assertions.
8
+ */
9
+ const CREATING = /\b(?:const|let|var)\s+[\w$]+\s*=|[\w$]+\s*[:=]\s*['"`]|\b(?:create|insert|add|register|signup|signUp|post|put|save|seed|make|new)\w*\s*\(|\.\s*(?:fill|type|values|send)\s*\(/;
10
+ /**
11
+ * The value is naming an element, not creating a record. `getByRole('button', { name: 'Submit' })`
12
+ * reads exactly like an object property to the pattern above, and rewriting it would point the test
13
+ * at an element that does not exist - breaking the test rather than fixing the collision. A unit
14
+ * test caught this before any real data did.
15
+ */
16
+ const A_SELECTOR = /\bgetBy\w+\s*\(|\bfindBy\w+\s*\(|\blocator\s*\(|\bcy\.get\s*\(|querySelector|\bBy\.\w+\s*\(|\bselector\b/i;
17
+ /**
18
+ * Picks the one occurrence it is safe to make unique, given the whole file.
19
+ */
20
+ export function chooseDataSite(source, sites, value) {
21
+ const asserting = sites.filter((s) => ASSERTING.test(lineOf(source, s)));
22
+ const creating = sites.filter((s) => {
23
+ const line = lineOf(source, s);
24
+ return CREATING.test(line) && !ASSERTING.test(line) && !A_SELECTOR.test(line);
25
+ });
26
+ if (creating.length === 0) {
27
+ return {
28
+ site: null,
29
+ reason: 'value-only-asserted',
30
+ why: sites.length === 1
31
+ ? `"${value}" appears only in ${lineOf(source, sites[0]).trim().slice(0, 60)}, where the test is checking it rather than creating anything. Making that unique would change what the test asserts instead of fixing the collision.`
32
+ : `"${value}" is written ${sites.length} times in the test and none of them creates anything: they are assertions or arguments Tewip cannot tell apart. A person should decide which one is the data.`,
33
+ };
34
+ }
35
+ if (creating.length > 1) {
36
+ return {
37
+ site: null,
38
+ reason: 'value-ambiguous',
39
+ why: `"${value}" is created in ${creating.length} places (lines ${creating.map((s) => s.line).join(', ')}). Making only one unique would break the link between them, so this needs a person.`,
40
+ };
41
+ }
42
+ if (asserting.length > 0) {
43
+ // The commonest real shape of all: `name: "Secondi"` on one line and
44
+ // `expect(body.data.name).toBe("Secondi")` a few lines down. Making the data unique without
45
+ // rewriting the assertion turns a flaky test into a failing one, and rewriting both is a bigger
46
+ // change than this fix is allowed to make.
47
+ return {
48
+ site: null,
49
+ reason: 'value-also-asserted',
50
+ why: `"${value}" is created on line ${creating[0].line} and also checked on line ${asserting[0].line}. Making the data unique would leave the assertion looking for the old value, so the two have to change together - which is a person's call, not this fix's.`,
51
+ };
52
+ }
53
+ return {
54
+ site: creating[0],
55
+ why: sites.length === 1 ? `"${value}" is written once in the test, where the record is created` : `"${value}" is written ${sites.length} times; line ${creating[0].line} is the one that creates the record`,
56
+ };
57
+ }
58
+ const lineOf = (source, site) => source.split('\n')[site.line - 1] ?? '';
@@ -8,6 +8,7 @@
8
8
  // Same sandbox as the other fixes: test files only, never application code, and the file goes back
9
9
  // exactly as it was unless a candidate is accepted.
10
10
  import { relative, resolve, sep } from 'node:path';
11
+ import { chooseDataSite } from './data-site.js';
11
12
  import { applyDataFix, collision, dataFixCandidates, findLiteral, languageOf } from './data.js';
12
13
  /** Two passes in a row: the second run is what proves the value is really unique. */
13
14
  export const REQUIRED_PASSES = 2;
@@ -34,7 +35,13 @@ export async function attemptDataFix({ record, diagnosis, action, specFile }, de
34
35
  const sites = findLiteral(source, found.value);
35
36
  if (sites.length === 0)
36
37
  return stop('value-not-in-test', `"${found.value}" is not written in ${specFile}: it comes from a fixture or a factory, which a person should make unique`);
37
- const site = sites[0];
38
+ // Not simply the first occurrence: a colliding value is often also a value the test checks, and
39
+ // rewriting that would change what the test asserts while leaving the collision in place.
40
+ const choice = chooseDataSite(source, sites, found.value);
41
+ if (!choice.site)
42
+ return stop(choice.reason, `refused: ${choice.why}`);
43
+ const site = choice.site;
44
+ result.log.push(choice.why);
38
45
  result.line = site.line;
39
46
  result.original = site.text;
40
47
  const candidates = dataFixCandidates(found.value, languageOf(specFile));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "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.",
5
5
  "keywords": [
6
6
  "test-failures",