@sigloch/se-engine 1.6.1 → 1.7.0

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.
@@ -183,10 +183,161 @@ function edgeToMentioned(opts) {
183
183
  return null;
184
184
  };
185
185
  }
186
+ /**
187
+ * Kante zu einem Ziel aus `context.candidate_targets` — der zweite Kandidatenweg (CR-SM-342).
188
+ *
189
+ * `edgeToMentioned` sucht im ELEMENTTEXT. Das traegt, wo der Text das Ziel nennt (CR-Text nennt
190
+ * seine FUNC), und traegt nicht, wo die Regel den Kandidatenkreis ohnehin schon mitliefert.
191
+ * Gemessen an einem vollen Spezifikationslauf: R-02 (86 Befunde), RD-01 (71), R-31 (42) und
192
+ * R-30 (40) tragen ihre `candidate_targets` zu 100 % — 239 Befunde, fuer die ein Template
193
+ * nichts SUCHEN muss. Es gab nur keins.
194
+ *
195
+ * Die Kandidaten kommen aus `toCandidates` und sind bereits nach Token-Ueberlappung mit dem
196
+ * Fund-Element sortiert: der erste, der alle Riegel passiert, gewinnt. Die Riegel sind
197
+ * dieselben wie im Textweg, in derselben Reihenfolge — eine Vorschlagsquelle mehr, kein
198
+ * zweiter Legalitaetsbegriff.
199
+ *
200
+ * WARUM DER KINDS-RIEGEL HIER TRAEGT, nicht nur formal: fuer `satisfy` FUNC→REQ laesst das
201
+ * Muster nur {functional, postcondition, precondition} zu. Am selben Modell gemessen sind
202
+ * 42 von 80 REQ nicht-funktional — eine Vorlage, die blind den erstbesten Kandidaten nimmt,
203
+ * haette in 53 % der Faelle die Kante vorgeschlagen, die R-18 sofort abweist. Das war im Lauf
204
+ * bereits die groesste Ablehnungsklasse (39 von 118 Fehlern); sie zu automatisieren haette
205
+ * den Waste nicht gesenkt, sondern vervielfacht.
206
+ */
207
+ function edgeToCandidate(opts) {
208
+ return (v, g) => {
209
+ const cands = v.context?.candidate_targets;
210
+ if (!Array.isArray(cands) || cands.length === 0)
211
+ return null;
212
+ const el = byId(g, v.element_id);
213
+ if (!el)
214
+ return null;
215
+ for (const cand of cands) {
216
+ const other = byId(g, cand.id);
217
+ if (!other || other.id === el.id)
218
+ continue;
219
+ if (opts.types && !opts.types.includes(other.type))
220
+ continue;
221
+ const source = opts.direction === 'out' ? el : other;
222
+ const target = opts.direction === 'out' ? other : el;
223
+ if (hasTrace(g, source.id, target.id, opts.traceType))
224
+ continue;
225
+ if (!isValidTrace({
226
+ source: source.type, target: target.type, type: opts.traceType,
227
+ sourceKinds: source.kinds, targetKinds: target.kinds,
228
+ }))
229
+ continue;
230
+ const retire = retireFor(g, source, target, opts.traceType);
231
+ if (retire === 'blocked')
232
+ continue;
233
+ const file = retire ? realFileOf(source) : undefined;
234
+ return {
235
+ op: 'add-trace',
236
+ source: source.id,
237
+ target: target.id,
238
+ type: opts.traceType,
239
+ rationale: `${other.id} ist der bestplatzierte Kandidat des Befunds und die Kante ` +
240
+ `${source.type} -${opts.traceType}-> ${target.type} ist zulaessig`,
241
+ ...(retire ? { retire } : {}),
242
+ ...(retire && file ? { codeImpact: { file, targetModule: target.id } } : {}),
243
+ };
244
+ }
245
+ return null;
246
+ };
247
+ }
248
+ /**
249
+ * Die abgewiesene Kante reparieren — UMHAENGEN, nicht anhaengen (CR-SM-342).
250
+ *
251
+ * Eine illegale Kante verschwindet nicht dadurch, dass eine legale danebengelegt wird: der
252
+ * Befund bliebe stehen. Deshalb traegt jeder Vorschlag hier ein `retire` auf genau die
253
+ * beanstandete Kante — Anwendung als EIN Batch [delete, add], wie CR-GC-435 es fuer die
254
+ * Kardinalitaet schon eingefuehrt hat.
255
+ *
256
+ * NUR EINDEUTIGE FAELLE. Nennt der Grund mehr als eine Alternative, ist die Wahl eine
257
+ * Entscheidung und kein Zug — dann `null`, und der Fund bleibt Fund-Ebene. Das ist dieselbe
258
+ * Linie wie R-23s fehlender `uniqueFallback`: lieber keine Empfehlung als eine geratene.
259
+ *
260
+ * Der kinds-Fall hat eine Ausnahme, die KEINE Raterei ist: schlaegt `satisfy` einer FUNC an
261
+ * einem nicht-funktionalen REQ fehl und ist MOD eine zulaessige Quelle, dann ist das Modul
262
+ * der FUNC der Traeger — `FUNC -allocate-> MOD` steht im Graphen und macht die Wahl
263
+ * deterministisch. Gemessen war das die groesste Einzelklasse des Laufs (39 von 118).
264
+ */
265
+ function rejectedEdgeFix(rej, v, g) {
266
+ if (!rej)
267
+ return null;
268
+ const el = byId(g, v.element_id);
269
+ if (!el)
270
+ return null;
271
+ // Quelle und Ziel stehen BEIDE am Befund (CR-SM-342): `element_id` ist die Quelle,
272
+ // `target_id` das Ziel. Die Kante im Graphen zu suchen waere falsch — bei einem dryRun,
273
+ // der sie gerade abgelehnt hat, steht sie im Arbeitsgraphen gar nicht.
274
+ const target = byId(g, rej.target_id);
275
+ if (!target)
276
+ return null;
277
+ const retire = {
278
+ source: el.id,
279
+ target: target.id,
280
+ type: rej.trace_type,
281
+ rationale: `${el.id} -${rej.trace_type}-> ${target.id} ist unzulaessig (${rej.reason}) und muss weichen`,
282
+ };
283
+ if (rej.reason === 'no-pattern') {
284
+ if (rej.candidate_trace_types.length !== 1)
285
+ return null;
286
+ const typ = rej.candidate_trace_types[0];
287
+ if (hasTrace(g, el.id, target.id, typ))
288
+ return null;
289
+ return {
290
+ op: 'add-trace', source: el.id, target: target.id, type: typ,
291
+ rationale: `${rej.source_type} → ${rej.target_type} kennt genau einen Kantentyp: ${typ}`,
292
+ retire,
293
+ };
294
+ }
295
+ // kinds-mismatch / kinds-undeclared: die Kante ist richtig, die QUELLE ist es nicht.
296
+ if (rej.candidate_sources.length === 0)
297
+ return null;
298
+ const traeger = g.traces
299
+ .filter((t) => t.source === el.id && t.type === 'allocate')
300
+ .map((t) => byId(g, t.target))
301
+ .filter((m) => !!m && rej.candidate_sources.includes(m.type));
302
+ if (traeger.length !== 1)
303
+ return null;
304
+ const neu = traeger[0];
305
+ if (hasTrace(g, neu.id, target.id, rej.trace_type))
306
+ return null;
307
+ if (!isValidTrace({
308
+ source: neu.type, target: target.type, type: rej.trace_type,
309
+ sourceKinds: neu.kinds, targetKinds: target.kinds,
310
+ }))
311
+ return null;
312
+ return {
313
+ op: 'add-trace', source: neu.id, target: target.id, type: rej.trace_type,
314
+ rationale: `${target.id} traegt ${rej.field} {${(rej.declared ?? []).join(', ')}} — das Muster ` +
315
+ `${rej.source_type} → ${rej.target_type} laesst das nicht zu, ${neu.id} (${neu.type}) schon, ` +
316
+ `und ${el.id} ist genau dorthin alloziert`,
317
+ retire,
318
+ };
319
+ }
186
320
  export const FIX_TEMPLATES = {
187
321
  // CR-SM-295: SCHEMA gehoert zum Umfang — ein CR, der nur einen Datenvertrag aendert,
188
322
  // ist ein vollstaendiger CR. Die Liste ist woertlich SCOPE_TYPES aus cr-quality-rules.
189
323
  'CR-R01': relationToMentioned(['UC', 'REQ', 'FUNC', 'MOD', 'SCHEMA']),
324
+ // --- Kandidaten-Operatoren (CR-SM-342) -------------------------------------
325
+ // Die vier teuersten Operator-Regeln des gemessenen Laufs. `CLASS_MAP` fuehrte sie
326
+ // laengst als mechanisch loesbar, ihre Befunde tragen ihre Kandidaten zu 100 %, und
327
+ // keine hatte eine Vorlage: 239 Befunde ohne ausfuehrbare Empfehlung.
328
+ // FUNC ohne satisfy → das bestplatzierte REQ, dessen kinds das Muster zulaesst.
329
+ // Der kinds-Riegel ist hier der ganze Punkt, nicht Formsache (s. edgeToCandidate).
330
+ 'R-02': edgeToCandidate({ traceType: 'satisfy', direction: 'out', types: ['REQ'] }),
331
+ // REQ ohne Aufloesung → dieselbe satisfy-Kante, aber der Fund sitzt am REQ:
332
+ // die Kandidaten sind die Quellen (FUNC/MOD/…), also laeuft die Kante herein.
333
+ 'RD-01': edgeToCandidate({ traceType: 'satisfy', direction: 'in' }),
334
+ // FUNC ohne io → der FLOW, den der Befund nennt. Richtung aus der Kante selbst:
335
+ // `isValidTrace` kennt beide Muster (FUNC→FLOW und FLOW→FUNC), und 'out' probiert
336
+ // FUNC→FLOW zuerst; passt nur die Gegenrichtung, faellt der Kandidat durch und der
337
+ // naechste kommt dran — kein Raten ueber die Richtung.
338
+ 'R-31': edgeToCandidate({ traceType: 'io', direction: 'out', types: ['FLOW'] }),
339
+ // FUNC ohne Wirkkette → compose von der FCHAIN herein (einziges legales Muster).
340
+ 'R-30': edgeToCandidate({ traceType: 'compose', direction: 'in', types: ['FCHAIN'] }),
190
341
  // --- Architektur-Operatoren (CR-SM-241) ------------------------------------
191
342
  // Ohne sie hat auf `layer: 'arch'` — graphcodes DEFAULT-Messebene — jeder
192
343
  // Template-Edit Δm = 0, weil CR/MS/UC gar nicht im Teilgraphen liegen.
@@ -223,6 +374,14 @@ export const FIX_TEMPLATES = {
223
374
  * durchgesetzte Untergrenze dazu, traegt dieses Template sie ohne Aenderung.
224
375
  */
225
376
  'R-18': (v, g) => {
377
+ // CR-SM-342: R-18 hat vier Beine, und bis hierher bediente das Template genau eins.
378
+ // Gemessen an einem vollen Lauf: 48 von 56 Befunden kamen aus der KANTEN-GRAMMATIK und
379
+ // fielen in der Zeile darueber heraus, weil sie kein `candidate_targets` tragen — die
380
+ // Regel mit den meisten Ablehnungen war die einzige ohne Empfehlung. Seit CR-SM-341
381
+ // traegt sie ihren Grund als `context.trace_rejection`; hier wird er bedient.
382
+ const rej = v.context?.trace_rejection;
383
+ if (rej)
384
+ return rejectedEdgeFix(rej, v, g);
226
385
  if (!Array.isArray(v.context?.candidate_targets))
227
386
  return null;
228
387
  const el = byId(g, v.element_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sigloch/se-engine",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",