tewip-ai 0.70.3 → 0.70.5

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.
@@ -112,15 +112,15 @@ export function handleRpc(cmd, body) {
112
112
  }
113
113
  const choice = chooseSkill(message);
114
114
  if (choice.kind === 'ask') {
115
- record(cmd.root, { method, outcome: 'asked-back', why: choice.why });
115
+ record(cmd.root, { http: 'POST', path: JSONRPC_PATH, method, outcome: 'asked-back', why: choice.why });
116
116
  return { status: 200, payload: { jsonrpc: '2.0', id, result: needsInput(askWhich(choice.why)) } };
117
117
  }
118
118
  if (choice.kind === 'unknown') {
119
- record(cmd.root, { method, outcome: 'unknown-skill', asked: choice.asked });
119
+ record(cmd.root, { http: 'POST', path: JSONRPC_PATH, method, outcome: 'unknown-skill', asked: choice.asked });
120
120
  return { status: 200, payload: { jsonrpc: '2.0', id, result: needsInput(askWhich(`"${choice.asked}" is not a skill Tewip has`)) } };
121
121
  }
122
122
  const answered = runSkill(cmd.root, choice.skill, []);
123
- record(cmd.root, { method, outcome: 'answered', skill: choice.skill.id, verb: choice.skill.verb, failed: answered.failed });
123
+ record(cmd.root, { http: 'POST', path: JSONRPC_PATH, method, outcome: 'answered', skill: choice.skill.id, verb: choice.skill.verb, failed: answered.failed });
124
124
  return { status: 200, payload: { jsonrpc: '2.0', id, result: completedTask(answered.text, answered.failed) } };
125
125
  }
126
126
  /** True of an address that only this machine can reach. */
@@ -130,11 +130,26 @@ export function serveA2A(cmd) {
130
130
  const url = `http://${cmd.host}:${cmd.port}${JSONRPC_PATH}`;
131
131
  const server = createServer((req, res) => {
132
132
  const path = (req.url ?? '').split('?')[0];
133
+ // What reaches the port, not only what gets answered.
134
+ //
135
+ // `record` was called from the three places inside the answering path, so the log held
136
+ // successful answers and nothing else. Measured by sending four requests and finding one: a POST
137
+ // to the wrong path, a JSON-RPC call for a method this agent does not have, and a malformed body
138
+ // all left **no trace at all**. On a port with no authentication those are the entries worth
139
+ // keeping - a peer probing for methods is exactly what an operator needs to see - and the file's
140
+ // own comment promises "a record of what another agent asked for and what it was told". A
141
+ // rejected request is still something another agent asked for.
142
+ //
143
+ // The request's body is never recorded, only what it asked for and how it was answered, because
144
+ // a body from another agent can contain anything.
145
+ const seen = (outcome, extra = {}) => record(root, { http: req.method ?? '', path, outcome, ...extra });
133
146
  if (req.method === 'GET' && path === CARD_PATH) {
147
+ seen('card-read');
134
148
  send(res, 200, agentCard(url, cmd.tewipRoot));
135
149
  return;
136
150
  }
137
151
  if (req.method !== 'POST' || path !== JSONRPC_PATH) {
152
+ seen('no-such-endpoint');
138
153
  send(res, 404, { error: `nothing here. The card is at ${CARD_PATH} and calls go to ${JSONRPC_PATH}` });
139
154
  return;
140
155
  }
@@ -145,13 +160,23 @@ export function serveA2A(cmd) {
145
160
  parsed = JSON.parse(raw);
146
161
  }
147
162
  catch {
163
+ seen('body-not-json');
148
164
  send(res, 400, rpcError(null, INVALID_PARAMS, 'the body is not JSON'));
149
165
  return;
150
166
  }
151
167
  const { status, payload } = handleRpc({ ...cmd, root }, parsed);
168
+ // A JSON-RPC error rides in a 200 response - the transport worked, the call did not - so the
169
+ // HTTP status cannot be the test. Checking it missed exactly the entry worth having: a peer
170
+ // asking for a method this agent does not implement.
171
+ const rpc = payload ?? {};
172
+ if (rpc.error)
173
+ seen('refused', { why: rpc.error.message ?? '' });
152
174
  send(res, status, payload);
153
175
  })
154
- .catch((error) => send(res, 413, rpcError(null, INVALID_PARAMS, error.message)));
176
+ .catch((error) => {
177
+ seen('body-rejected', { why: error.message });
178
+ send(res, 413, rpcError(null, INVALID_PARAMS, error.message));
179
+ });
155
180
  });
156
181
  server.listen(cmd.port, cmd.host, () => {
157
182
  console.log(`Tewip is answering other agents about ${root}`);
@@ -7,6 +7,7 @@
7
7
  import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
8
8
  import { dirname, join, resolve } from 'node:path';
9
9
  import { HistoryIndex } from '../history.js';
10
+ import { loadCorrections } from '../corrections.js';
10
11
  import { buildItems } from '../../outlets/items.js';
11
12
  import { digestView } from '../../outlets/views/digest.js';
12
13
  import { qualityView } from '../../outlets/views/quality.js';
@@ -36,9 +37,13 @@ export async function runNote(cmd) {
36
37
  continue;
37
38
  totalRuns += runs.length;
38
39
  totalFailures += records.length;
39
- // The diagnoses were made when each failure happened; nothing is judged again here.
40
+ // The diagnoses were made when each failure happened; nothing is judged again here - except
41
+ // where a person overruled one. This note is the report a lead reads, and it was printing
42
+ // "Diagnosis: LOCATOR_DRIFT, confidence 0.95, source: rules" about a test somebody had corrected
43
+ // to TIMING, because the corrections were never loaded here at all.
40
44
  const index = new HistoryIndex(records, runs);
41
- const items = await buildItems(records, index, { dataset: `history: ${name}`, llm: null, reuseStoredDiagnoses: true });
45
+ const where = join(cmd.root, '.tewip', 'history', name);
46
+ const items = await buildItems(records, index, { dataset: `history: ${name}`, llm: null, reuseStoredDiagnoses: true, corrections: loadCorrections(where) });
42
47
  parts.push(`## ${name}`, '', `${runs.length} run(s), ${records.length} failure(s).`, '');
43
48
  if (items.length > 0) {
44
49
  parts.push(digestView(items, runs, `history: ${name}`).markdown, '', qualityView(items, `history: ${name}`).markdown, '');
@@ -4,6 +4,7 @@ import { extractLocator } from '../engine/error-parse.js';
4
4
  import { decide } from '../engine/decide.js';
5
5
  import { lastFailedAttempts } from '../engine/dataset.js';
6
6
  import { failingCodeOf } from '../engine/llm/context.js';
7
+ import { applyCorrection, correctionFor } from '../engine/corrections.js';
7
8
  import { diagnosePipeline } from '../engine/pipeline.js';
8
9
  import { truncate } from '../engine/signature.js';
9
10
  import { designDebt } from './design-debt.js';
@@ -47,7 +48,24 @@ export async function buildItems(records, index, { dataset, llm, reuseStoredDiag
47
48
  const stored = reuseStoredDiagnoses ? cases.map((c) => c.diagnosis) : cases.map(() => undefined);
48
49
  const missing = cases.filter((_, i) => !stored[i]);
49
50
  const fresh = await diagnosePipeline(missing, index, llm, { corrections });
50
- const diagnoses = stored.map((d) => d ?? fresh.shift());
51
+ // A correction outranks a stored diagnosis, which it did not.
52
+ //
53
+ // `diagnosePipeline` applies corrections, but only to the failures in `missing` - the ones with no
54
+ // stored diagnosis. `explain` stores one for every failure it diagnoses, so on any history a team
55
+ // actually keeps, **every** failure has one and a correction reached none of them. Field-tested:
56
+ // three real corrections of real diagnoses, `learn` reported the pattern perfectly, and the weekly
57
+ // note still said "Diagnosis: LOCATOR_DRIFT, confidence 0.95, source: rules" about a test a person
58
+ // had overruled to TIMING - directly against what `correct` had just promised in print, "Tewip will
59
+ // answer with your decision the next time that test fails the same way, and say it was yours."
60
+ //
61
+ // This is the whole hierarchy of the product: what the team decided beats what the tool decided.
62
+ // Fixing it here fixes every caller at once, which matters because three of them reuse stored
63
+ // diagnoses and two of those did not even pass the corrections along.
64
+ const diagnoses = cases.map((record, i) => {
65
+ const own = stored[i] ?? fresh.shift();
66
+ const correction = corrections.length > 0 ? correctionFor(record, corrections) : null;
67
+ return correction ? applyCorrection(own, correction) : own;
68
+ });
51
69
  const failedAttempts = new Map();
52
70
  for (const r of records)
53
71
  failedAttempts.set(`${r.runId} ${r.testId}`, [...(failedAttempts.get(`${r.runId} ${r.testId}`) ?? []), r]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tewip-ai",
3
- "version": "0.70.3",
3
+ "version": "0.70.5",
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": [