tewip-ai 0.69.5 → 0.69.7

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.
@@ -157,6 +157,19 @@ const envServerError = {
157
157
  * Declines when the server failed during the test: the wrong value may come from the
158
158
  * backend (ENV) or from a test that provokes errors on purpose, which only the LLM layer,
159
159
  * seeing the whole trace, can tell apart (rules-3).
160
+ *
161
+ * **Do not remove the history requirement.** Phase 64 found that `tewip bugs` files almost nothing
162
+ * from one CI run - 1 ticket out of 1,964 real failures - because every test in a single run has
163
+ * `runs < 2` and this rule declines. The proposed fix was to let a plain value mismatch stand on its
164
+ * own: "expected 4, received 3 needs no model" is an obvious-sounding argument, and it was measured
165
+ * in Phase 65 over **2,767 hand-labelled real failures from nine projects**:
166
+ *
167
+ * 293 value mismatches -> TIMING 265 (90.4%), REAL_BUG 26 (8.9%), ENV 2 (0.7%)
168
+ *
169
+ * Per project it is 0%, 0%, 0%, 4%, 5%, 6%, 7%, 11%, 39%. **A flaky test fails its assertion too -
170
+ * that is what failing looks like.** The mismatch is not the signal; the history around it is, which
171
+ * is what the streak conditions below are reading and what one CI run cannot supply. Re-derive with
172
+ * `node eval/mismatch-precision.ts` before changing any of this.
160
173
  */
161
174
  const realBugAssertion = {
162
175
  id: 'real-bug-assertion',
@@ -21,20 +21,44 @@ export function testFor(scenario, attempted) {
21
21
  const id = clean(scenario.id);
22
22
  return id.length >= 4 ? (attempted.find((t) => clean(t).includes(id)) ?? '') : '';
23
23
  }
24
+ /**
25
+ * Why no test could be named - and what that does and does not establish.
26
+ *
27
+ * "no test in this project can be named for it, so nothing is checking it" was printed whatever the
28
+ * reason, including when **no run had been kept here at all**. Field-tested on this repository, which
29
+ * has 1,095 passing tests and a kept history belonging to a different project: all twelve scenarios
30
+ * of a real plan came back "nothing is checking it". Tewip was not looking at this project's tests,
31
+ * because it only ever sees the tests a kept run names, and it said so as though it had looked.
32
+ *
33
+ * The distinction matters because the two cases call for opposite actions: write a test, or keep
34
+ * your runs so the ones you have can be seen.
35
+ */
36
+ function notCoveredBecause(runs, knownTests) {
37
+ if (runs.length === 0) {
38
+ return 'no run has been kept for this project, so Tewip has no list of its tests to match against - this is not a claim that nothing checks it. Run `tewip explain <report>` once and ask again';
39
+ }
40
+ return `no test among the ${knownTests} that the ${runs.length} kept run(s) name matches it, so nothing Tewip can see is checking it`;
41
+ }
24
42
  /** Joins one plan to what the runs actually say. Nothing here is inferred from anything else. */
25
43
  export function tracePlan(plan, runs, records) {
26
44
  const newest = runs.at(-1);
27
45
  const attempted = newest ? Object.keys(newest.attempts ?? {}) : [];
28
46
  const everAttempted = new Set(runs.flatMap((r) => Object.keys(r.attempts ?? {})));
47
+ // A scenario's own id was matched against the **newest run only**, so a test that ran last week
48
+ // and not today could not be found at all - and the answer for it was "no test in this project can
49
+ // be named for it, so nothing is checking it", which is a different and much stronger claim than
50
+ // "it did not run today". The branch below that exists to say exactly that ("ran before but not in
51
+ // the last run") was unreachable for every scenario the plan did not name a test for.
52
+ const everyTest = [...everAttempted];
29
53
  const failures = new Map();
30
54
  for (const record of records)
31
55
  if (record.runId === newest?.runId)
32
56
  failures.set(record.testId, record);
33
57
  const scenarios = plan.scenarios.map((scenario) => {
34
- const test = testFor(scenario, attempted);
58
+ const test = testFor(scenario, everyTest);
35
59
  const base = { id: scenario.id, title: scenario.title, priority: scenario.priority ?? '', test, runId: newest?.runId ?? '', when: newest?.finishedAt ?? '' };
36
60
  if (!test) {
37
- return { ...base, standing: 'not-covered', diagnosis: '', why: 'no test in this project can be named for it, so nothing is checking it' };
61
+ return { ...base, standing: 'not-covered', diagnosis: '', why: notCoveredBecause(runs, everAttempted.size) };
38
62
  }
39
63
  if (!everAttempted.has(test)) {
40
64
  return { ...base, standing: 'never-run', runId: '', when: '', diagnosis: '', why: `${test} is named as the cover, but no run kept here has attempted it` };
@@ -60,8 +60,13 @@ export function whyNothingWasFiled(items, unsure) {
60
60
  return [
61
61
  `${items.length} failure(s). ${unsure.length} of them Tewip reads as a real bug, and none of those reaches the ${CONFIDENT_ENOUGH.toFixed(2)} confidence it requires before opening a ticket in somebody's name - the highest is ${highest.toFixed(2)}.`,
62
62
  byModel > 0
63
- ? `${byModel === unsure.length ? 'All' : `${byModel}`} of them came from the AI layer, which is capped at 0.45 on purpose: a model's unsupported word is not enough to file on. A person's correction on the failure in CI, or a rule that agrees, is what would make it enough.`
63
+ ? `${byModel === unsure.length ? 'All' : `${byModel}`} of them came from the AI layer, which is capped at 0.45 on purpose: a model's unsupported word is not enough to file on.`
64
64
  : '',
65
+ // What a CI user can actually do about it. The rules decline a value mismatch without a stable
66
+ // failing streak, and on one run there is no streak to read - which is not over-caution:
67
+ // measured over 2,767 labelled real failures, a value mismatch is TIMING 90% of the time and a
68
+ // real bug 9% of the time (`node eval/mismatch-precision.ts`). History is what separates them.
69
+ 'What would change this: history. The rules will not call a wrong value a bug without a stable failing streak behind it, because a flaky test produces the same wrong value - on real labelled data a value mismatch is a flaky test nine times out of ten. Keep this project\'s runs (`--name <project>`, or let the Action keep them) and the same failure in a second and third run is what the rules read. A person\'s correction on the failure in CI settles it immediately.',
65
70
  `Nothing was filed. These are listed in \`tewip explain\` with their evidence${kinds.length > 0 ? `, alongside ${kinds.join(', ')} failures that are deliberately never filed` : ''}.`,
66
71
  ]
67
72
  .filter(Boolean)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.69.5",
3
+ "version": "0.69.7",
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": [