@sigloch/se-engine 1.6.0 → 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.
- package/dist/fix-templates.js +159 -0
- package/dist/metrics.d.ts +1 -1
- package/dist/metrics.js +6 -1
- package/package.json +1 -1
package/dist/fix-templates.js
CHANGED
|
@@ -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/dist/metrics.d.ts
CHANGED
|
@@ -49,6 +49,6 @@ export { projectLayer, weightNodes, ARCH_TYPES, type MetricLayer } from './layer
|
|
|
49
49
|
export { CLASS_MAP, classOf, classifyAll, classificationStats, type RuleClass, type Classification, type ClassifiedRule, type ClassificationStats, } from './rule-classify.js';
|
|
50
50
|
export { applyRule, markEmptyDelta, type ApplyResult, type DeltaMark } from './rule-apply.js';
|
|
51
51
|
export { suggestEdits, type Suggestion, type SuggestOptions } from './suggest.js';
|
|
52
|
-
export { steerScore, beatsBySteer, STEER_RULES, EPS_AUGMENT, type SteerScore, type SteerCandidate } from './steer.js';
|
|
52
|
+
export { steerScore, steerTerms, beatsBySteer, STEER_RULES, EPS_AUGMENT, type SteerScore, type SteerTerm, type SteerCandidate, } from './steer.js';
|
|
53
53
|
export { fixFor, mentionedElements, FIX_TEMPLATES, mergeCandidates, applyMergeEdit, contractSimilarity, MERGE_OPERATOR_ID, MERGE_SIMILARITY_THRESHOLD, type SuggestedEdit, } from './fix-templates.js';
|
|
54
54
|
export { buildAdjacency, betweenness, maxBetweenness, detectCommunities, modularityOf, modularityQ, redundancyDensity, intraEdgeFraction, components, componentSizes, sourceSinkPaths, type Adjacency, } from './topology.js';
|
package/dist/metrics.js
CHANGED
|
@@ -94,6 +94,11 @@ export { CLASS_MAP, classOf, classifyAll, classificationStats, } from './rule-cl
|
|
|
94
94
|
export { applyRule, markEmptyDelta } from './rule-apply.js';
|
|
95
95
|
export { suggestEdits } from './suggest.js';
|
|
96
96
|
// CR-SM-292: der Ranking-Score. `targetFor` ist mit dem ℝ⁶-Ranking ersatzlos entfallen.
|
|
97
|
-
|
|
97
|
+
// CR-SM-340: `steerTerms`/`SteerTerm` sind hier nachgetragen. CR-SM-337 hat sie gebaut, damit
|
|
98
|
+
// der Host den Steuerungsraum EXPORTIEREN kann, statt die Normierung ein zweites Mal zu
|
|
99
|
+
// schreiben — und genau diese Zeile hat sie nicht durchgereicht. Die Funktion war da, die Tuer
|
|
100
|
+
// nicht; `package.json` kennt nur `"."`, ein Tiefimport ist also versperrt. Gemessen an
|
|
101
|
+
// graphcode: `import { steerTerms } from '@sigloch/se-engine'` warf SyntaxError.
|
|
102
|
+
export { steerScore, steerTerms, beatsBySteer, STEER_RULES, EPS_AUGMENT, } from './steer.js';
|
|
98
103
|
export { fixFor, mentionedElements, FIX_TEMPLATES, mergeCandidates, applyMergeEdit, contractSimilarity, MERGE_OPERATOR_ID, MERGE_SIMILARITY_THRESHOLD, } from './fix-templates.js';
|
|
99
104
|
export { buildAdjacency, betweenness, maxBetweenness, detectCommunities, modularityOf, modularityQ, redundancyDensity, intraEdgeFraction, components, componentSizes, sourceSinkPaths, } from './topology.js';
|