tewip-ai 0.70.7 → 0.70.9

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.
@@ -11,6 +11,7 @@ import { datasetFiles, readJsonl, readTriageRecords } from '../../reporters/stor
11
11
  import { alreadyTried, cannotAct, planFor } from './act.js';
12
12
  import { decide } from './policy.js';
13
13
  import { CANNOT_FAIL } from '../vouch/vouch.js';
14
+ import { applyCorrection, correctionFor, loadCorrections } from '../corrections.js';
14
15
  import { vouchAll } from '../vouch/vouch-command.js';
15
16
  const words = (text) => (text.toLowerCase().match(/[a-z][a-z0-9]{2,}/g) ?? []).filter((w) => !['the', 'and', 'for', 'this', 'that', 'with', 'keep', 'green', 'sprint', 'flow', 'test', 'tests'].includes(w));
16
17
  /** Does this test or scenario belong to the goal? Deterministic, so a person can predict it. */
@@ -25,6 +26,7 @@ export function observe(root, goal, only) {
25
26
  const dir = join(root, '.tewip', 'history');
26
27
  const runs = [];
27
28
  const records = [];
29
+ const corrections = [];
28
30
  if (existsSync(dir)) {
29
31
  for (const project of only ? [only] : readdirSync(dir)) {
30
32
  const where = join(dir, project);
@@ -32,16 +34,34 @@ export function observe(root, goal, only) {
32
34
  continue;
33
35
  runs.push(...readJsonl(datasetFiles(where).runs));
34
36
  records.push(...readTriageRecords(datasetFiles(where).triage));
37
+ corrections.push(...loadCorrections(where));
35
38
  }
36
39
  }
37
40
  runs.sort((a, b) => (a.finishedAt > b.finishedAt ? 1 : -1));
38
41
  const newest = runs.at(-1);
39
42
  const wanted = words(goal);
43
+ // A person's correction decides the category, as it does everywhere else.
44
+ //
45
+ // This read `r.diagnosis` straight off the record, so a correction never reached the decision.
46
+ // Field-tested: a person marked a failure REAL_BUG, and `pursue` went on calling it LOCATOR_DRIFT
47
+ // and concluding "the goal is failing and Tewip cannot say why". That matters more here than in a
48
+ // report, because this is the command that **acts** - and its own documented order is "a real bug
49
+ // first... it will not touch a real bug", a safety rule that depends on recognising one. The
50
+ // strongest possible signal that something is a real bug is a person saying so.
51
+ //
52
+ // Phase 74 fixed this inside `buildItems` for every command that goes through it. This one does
53
+ // not, so it applies them itself.
40
54
  const failing = records
41
55
  .filter((r) => r.runId === newest?.runId && about(wanted, r.testId))
42
56
  .map((r) => {
43
- const d = r.diagnosis;
44
- return { testId: r.testId, category: String(d?.category ?? 'UNKNOWN'), confidence: Number(d?.confidence ?? 0) };
57
+ const correction = corrections.length > 0 ? correctionFor(r, corrections) : null;
58
+ const own = r.diagnosis;
59
+ const d = correction && own ? applyCorrection(own, correction) : own;
60
+ return {
61
+ testId: r.testId,
62
+ category: String((correction?.category ?? d?.category) ?? 'UNKNOWN'),
63
+ confidence: Number(d?.confidence ?? (correction ? 1 : 0)),
64
+ };
45
65
  });
46
66
  // A test that failed in more than one earlier run and is not failing now has come and gone.
47
67
  const failedBefore = new Map();
@@ -1,14 +1,14 @@
1
1
  export const SPEC_PROMPT_VERSION = 'spec-1';
2
- const SYSTEM = `You are Tewip's specification writer, a senior product engineer who works from the code.
3
-
4
- Write an epic, its user stories, and acceptance criteria for the feature described. Use only the facts given: the feature description, the change, the pages of the running app, the tests that already exist, the source files read, and this project's failure history.
5
-
6
- Rules:
7
- - Every statement about what the system ALREADY does must be backed by a quote: evidence is a file path from the facts, then ": ", then a piece of that file copied character for character (a line of code, a test name, a comment). If you cannot quote it, do not claim it - put the uncertainty in open_questions.
8
- - Acceptance criteria are Given / When / Then, each one checkable by a person or a test. No criterion may say "works correctly", "is fast" or "is user-friendly": say the observable thing.
9
- - A story is one user-visible outcome, written as "As a <role>, I want <outcome> so that <benefit>". Split anything larger.
10
- - Prefer what the failure history and the existing tests show is risky or already broken.
11
- - Never write estimates, story points, dates, budgets, market claims or business cases. You cannot check those, so they do not belong here.
2
+ const SYSTEM = `You are Tewip's specification writer, a senior product engineer who works from the code.
3
+
4
+ Write an epic, its user stories, and acceptance criteria for the feature described. Use only the facts given: the feature description, the change, the pages of the running app, the tests that already exist, the source files read, and this project's failure history.
5
+
6
+ Rules:
7
+ - Every statement about what the system ALREADY does must be backed by a quote: evidence is a file path from the facts, then ": ", then a piece of that file copied character for character (a line of code, a test name, a comment). If you cannot quote it, do not claim it - put the uncertainty in open_questions.
8
+ - Acceptance criteria are Given / When / Then, each one checkable by a person or a test. No criterion may say "works correctly", "is fast" or "is user-friendly": say the observable thing.
9
+ - A story is one user-visible outcome, written as "As a <role>, I want <outcome> so that <benefit>". Split anything larger.
10
+ - Prefer what the failure history and the existing tests show is risky or already broken.
11
+ - Never write estimates, story points, dates, budgets, market claims or business cases. You cannot check those, so they do not belong here.
12
12
  - out_of_scope is what this epic deliberately does not do. open_questions are the decisions a person must make.`;
13
13
  const CRITERION = {
14
14
  type: 'object',
@@ -64,9 +64,29 @@ function readable(facts) {
64
64
  return [...(facts.testSources ?? []), ...(facts.codeSources ?? [])].map((s) => ({ file: s.file, content: s.content }));
65
65
  }
66
66
  /**
67
- * A quote is real when the file is one Tewip read and the distinctive parts of the quote are in it.
68
- * Whitespace and quote style differ between a model's copy and the file, so the comparison is on
69
- * the words and symbols, not on the exact spacing.
67
+ * A quote is real when the file is one Tewip read and the quote appears in it **as one piece**.
68
+ *
69
+ * This used to collect the quote's "distinctive parts" - runs of `[A-Za-z0-9_$.]{3,}` - and require
70
+ * each to appear somewhere in the file, in any order. Three fabrications got through a real check
71
+ * against a real file, and the worst was this, offered as evidence that a 20% discount cap is
72
+ * enforced:
73
+ *
74
+ * const rate = Math.min(percent, 20) / 100; // the file says 100, not 20
75
+ *
76
+ * Every part of that - `const`, `rate`, `Math.min`, `percent`, `100` - is in the file. **`20` is
77
+ * not a part at all**, because `{3,}` cannot see a two-character number. So the one token that
78
+ * carried the entire meaning was the one token not checked, and a reader of "What the code already
79
+ * does - each line is quoted from a file Tewip read" would have concluded the bug was fixed.
80
+ *
81
+ * The comment that used to sit here saw the risk and named it exactly - *"a quote that is mostly
82
+ * real but wrong where it matters... 'slice(0, 50)' and 'slice(0, 10000)' differ by one word"* -
83
+ * and `50` was never a word it could compare. A line assembled from real words in an order the file
84
+ * has never contained got through for the same reason.
85
+ *
86
+ * So the quote has to be in the file contiguously. Whitespace is squashed and quote characters are
87
+ * normalised, because those are the two differences between a model's copy and the file that carry
88
+ * no meaning; nothing else is forgiven. A claim that fails this becomes an open question, which is
89
+ * the right place for it.
70
90
  */
71
91
  export function quoteHolds(evidence, sources) {
72
92
  const at = evidence.indexOf(': ');
@@ -77,13 +97,8 @@ export function quoteHolds(evidence, sources) {
77
97
  const source = sources.find((s) => s.file === file || s.file.endsWith(`/${file}`) || file.endsWith(s.file));
78
98
  if (!source || quote.length < 3)
79
99
  return false;
80
- const parts = quote.match(/[A-Za-z0-9_$.]{3,}/g) ?? [];
81
- if (parts.length === 0)
82
- return false;
83
- const body = source.content;
84
- // Every distinctive part must be in the file. Anything less lets a quote that is mostly real but
85
- // wrong where it matters through - "slice(0, 50)" and "slice(0, 10000)" differ by one word.
86
- return parts.every((part) => body.includes(part));
100
+ const squash = (text) => text.replace(/["'`]/g, '"').replace(/\s+/g, ' ').trim();
101
+ return squash(source.content).includes(squash(quote));
87
102
  }
88
103
  /**
89
104
  * Checks every claim about what already exists, and demotes the ones Tewip cannot quote into open
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.70.7",
3
+ "version": "0.70.9",
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": [