tewip-ai 0.70.6 → 0.70.8
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.
|
@@ -62,19 +62,52 @@ ${noModelHelp()}`);
|
|
|
62
62
|
const fileName = framework.fileFor(scenario.id);
|
|
63
63
|
const file = join(framework.testDir, fileName);
|
|
64
64
|
if (existsSync(file) || (cmd.moveTo && existsSync(join(cmd.moveTo, fileName)))) {
|
|
65
|
-
|
|
65
|
+
// Every file this command writes carries "tewip" in its name, so one already sitting here is
|
|
66
|
+
// one of its own - an unverified draft from a run that did not finish, not a test the team
|
|
67
|
+
// wrote. Calling that "left untouched" sounds like a decision and hides that the file is in
|
|
68
|
+
// the test folder, where the suite is running it.
|
|
69
|
+
//
|
|
70
|
+
// Matched case-insensitively on the whole name rather than as a prefix, because only the
|
|
71
|
+
// JavaScript frameworks produce `tewip-ls-002.spec.ts`: pytest gives `test_tewip_ls_002.py`
|
|
72
|
+
// and JUnit `TewipLs002Test.java`, and a prefix check would have handed Python and Java teams
|
|
73
|
+
// the misleading message this fix exists to remove.
|
|
74
|
+
const mine = /tewip/i.test(fileName);
|
|
75
|
+
results.push({
|
|
76
|
+
id: scenario.id,
|
|
77
|
+
title: scenario.title,
|
|
78
|
+
file,
|
|
79
|
+
outcome: {
|
|
80
|
+
kind: 'rejected',
|
|
81
|
+
reason: mine
|
|
82
|
+
? `${fileName} is already here from a run that did not finish, and was never verified. It is in your test folder, so your suite is running it: delete it, or move it to .tewip/drafts/, then ask again.`
|
|
83
|
+
: `${fileName} already exists and was written by somebody else; left untouched`,
|
|
84
|
+
repairs: 0,
|
|
85
|
+
passedReliably: false,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
66
88
|
continue;
|
|
67
89
|
}
|
|
68
90
|
console.error(` ${scenario.id} ${scenario.title}`);
|
|
69
|
-
//
|
|
91
|
+
// A draft that was never verified must not stay in the project, however the run ended.
|
|
92
|
+
//
|
|
93
|
+
// SIGINT and SIGTERM were covered; nothing else was. A run of this interrupted another way -
|
|
94
|
+
// a closed pipe, an uncaught throw, a kill - left the draft in the **test folder**, where the
|
|
95
|
+
// project's own suite then runs it. Seen for real: a two-scenario run was cut short by a broken
|
|
96
|
+
// shell command, the suite went from green to red, and the next `write` reported the leftover as
|
|
97
|
+
// "already exists; left untouched", which reads like a decision rather than a casualty.
|
|
98
|
+
//
|
|
99
|
+
// `renameSync` is synchronous, so an `exit` handler can still do it: that covers every way out.
|
|
100
|
+
const rescue = () => {
|
|
101
|
+
if (!existsSync(file))
|
|
102
|
+
return;
|
|
103
|
+
mkdirSync(drafts, { recursive: true });
|
|
104
|
+
renameSync(file, join(drafts, fileName));
|
|
105
|
+
};
|
|
70
106
|
const leaveNothing = () => {
|
|
71
|
-
|
|
72
|
-
mkdirSync(drafts, { recursive: true });
|
|
73
|
-
renameSync(file, join(drafts, fileName));
|
|
74
|
-
}
|
|
107
|
+
rescue();
|
|
75
108
|
process.exit(130);
|
|
76
109
|
};
|
|
77
|
-
process.once('SIGINT', leaveNothing).once('SIGTERM', leaveNothing);
|
|
110
|
+
process.once('SIGINT', leaveNothing).once('SIGTERM', leaveNothing).once('exit', rescue);
|
|
78
111
|
let outcome;
|
|
79
112
|
try {
|
|
80
113
|
({ outcome } = await writeAndVerify(model, { framework, fileName, pages, feature: saved.feature, code, houseRules: guidanceFor(loadStandards(cmd.root), fileName) }, scenario, file, { runs: cmd.runs, repairs: cmd.repairs, mutateSource: cmd.mutateSource, log: (l) => console.error(l) }));
|
|
@@ -83,7 +116,7 @@ ${noModelHelp()}`);
|
|
|
83
116
|
outcome = { kind: 'rejected', reason: `stopped: ${error.message}`, repairs: 0, passedReliably: false };
|
|
84
117
|
}
|
|
85
118
|
finally {
|
|
86
|
-
process.off('SIGINT', leaveNothing).off('SIGTERM', leaveNothing);
|
|
119
|
+
process.off('SIGINT', leaveNothing).off('SIGTERM', leaveNothing).off('exit', rescue);
|
|
87
120
|
}
|
|
88
121
|
// Some runners take minutes per run (Angular rebuilds the app for every `ng test`): say how long
|
|
89
122
|
// the rest will take, so a slow run is not mistaken for a stuck one.
|
package/dist/engine/spec/spec.js
CHANGED
|
@@ -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
|
|
68
|
-
*
|
|
69
|
-
* the
|
|
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
|
|
81
|
-
|
|
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.
|
|
3
|
+
"version": "0.70.8",
|
|
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": [
|