@sigloch/contracts 10.12.0 → 10.14.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.
@@ -40,24 +40,27 @@ export declare const TraceType: z.ZodEnum<{
40
40
  }>;
41
41
  export type TraceType = z.infer<typeof TraceType>;
42
42
  /**
43
- * REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
43
+ * REQ kind — GENAU EIN Wert je REQ, aus zwei (CR-SM-366).
44
44
  *
45
- * CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
46
- * Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
47
- * ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
48
- * als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
49
- * haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
50
- * bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
51
- * gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
52
- * Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
45
+ * `functional` -> erfuellt von FUNC. `non-functional` -> erfuellt von MOD (lokales Budget), SYS
46
+ * (Systemebene) oder FCHAIN (Ende-zu-Ende entlang der Kette). Ohne kinds kein Erfueller (BQ-07,
47
+ * `satisfiesPredicate`). Ein REQ, das beides ist, ist falsch zerlegt -> teilen; die
48
+ * Teilmengen-Semantik der where-Praedikate laesst es von niemandem erfuellen.
49
+ *
50
+ * Entfallen (Major, CR-SM-366):
51
+ * - `precondition` / `postcondition`: die Vorbedingung steckt im Eingangs-FLOW der Kette, die
52
+ * Nachbedingung ist das UC-Ziel — beides prueft R-21 (Integrationstest je Kette). Ihre Leser
53
+ * UC-05/06 sind seit CR-SM-357 gestrichen.
54
+ * - `risk` / `mitigation`: die FMEA-ROLLE einer Anforderung, keine Art — `REQ.attributes.role`
55
+ * (CR-SM-365), satisfy-neutral.
56
+ * Ein Altwert wird nicht still verworfen: `normalizeReqKinds` laesst ihn stehen, das Schema
57
+ * lehnt ihn ab, und R-18 meldet jede satisfy-Kante darauf als `kinds-mismatch` mit `declared`.
58
+ *
59
+ * Vorher CR-SM-266 B: `negative` entfiel (ein Verbots-REQ ist `functional`).
53
60
  */
54
61
  export declare const ReqKind: z.ZodEnum<{
55
62
  functional: "functional";
56
63
  "non-functional": "non-functional";
57
- risk: "risk";
58
- mitigation: "mitigation";
59
- precondition: "precondition";
60
- postcondition: "postcondition";
61
64
  }>;
62
65
  export type ReqKind = z.infer<typeof ReqKind>;
63
66
  /**
@@ -222,6 +225,32 @@ type Attributes = Record<string, unknown> | undefined;
222
225
  export declare function readTestRefs(attributes: Attributes): BindingRead<TestRefs>;
223
226
  /** Der EINE Leser von `attributes.realRef` (CR-SM-360). */
224
227
  export declare function readRealRef(attributes: Attributes): BindingRead<RealRef>;
228
+ /**
229
+ * CR-SM-364: die Kopplung eines Uebergangs — `FLOW.attributes.sync`. Fehlend heisst UNBEKANNT,
230
+ * nie stillschweigend `sync`: eine Kettenkennzahl (synchrone Tiefe, Engstellen-Grad) weist eine
231
+ * unmarkierte Kette als „nicht bewertbar" aus, statt sie ueber eine Annahme zu beurteilen.
232
+ */
233
+ export declare const FlowSync: z.ZodEnum<{
234
+ sync: "sync";
235
+ async: "async";
236
+ }>;
237
+ export type FlowSync = z.infer<typeof FlowSync>;
238
+ /** Der EINE Leser von `FLOW.attributes.sync` (CR-SM-364) — ungueltig ist nicht fehlend. */
239
+ export declare function readFlowSync(attributes: Attributes): BindingRead<FlowSync>;
240
+ /**
241
+ * CR-SM-365: die ROLLE einer Anforderung im FMEA-Sinn — `REQ.attributes.role`. `risk` und
242
+ * `mitigation` waren bis hierher `kinds`-Werte und damit satisfy-relevant (MOD/SYS erfuellten
243
+ * sie). Sie sind aber keine ART der Anforderung, sondern ihre Motivation: eine Gegenmassnahme ist
244
+ * funktional oder nicht-funktional wie jede andere REQ. Die Rolle ist satisfy-neutral — die
245
+ * Grammatik (`isValidTrace`) liest sie nicht; FM-01..03 lesen NUR sie.
246
+ */
247
+ export declare const ReqRole: z.ZodEnum<{
248
+ risk: "risk";
249
+ mitigation: "mitigation";
250
+ }>;
251
+ export type ReqRole = z.infer<typeof ReqRole>;
252
+ /** Der EINE Leser von `REQ.attributes.role` (CR-SM-365) — ungueltig ist nicht fehlend. */
253
+ export declare function readReqRole(attributes: Attributes): BindingRead<ReqRole>;
225
254
  /**
226
255
  * The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
227
256
  * skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
@@ -314,10 +343,6 @@ export declare const OntologyElement: z.ZodObject<{
314
343
  kinds: z.ZodOptional<z.ZodArray<z.ZodEnum<{
315
344
  functional: "functional";
316
345
  "non-functional": "non-functional";
317
- risk: "risk";
318
- mitigation: "mitigation";
319
- precondition: "precondition";
320
- postcondition: "postcondition";
321
346
  }>>>;
322
347
  attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
323
348
  status: z.ZodDefault<z.ZodEnum<{
@@ -380,10 +405,6 @@ export declare const OntologyGraph: z.ZodObject<{
380
405
  kinds: z.ZodOptional<z.ZodArray<z.ZodEnum<{
381
406
  functional: "functional";
382
407
  "non-functional": "non-functional";
383
- risk: "risk";
384
- mitigation: "mitigation";
385
- precondition: "precondition";
386
- postcondition: "postcondition";
387
408
  }>>>;
388
409
  attributes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
389
410
  status: z.ZodDefault<z.ZodEnum<{
@@ -25,21 +25,25 @@ export const TraceType = z.enum([
25
25
  'compose', 'io', 'satisfy', 'verify', 'allocate', 'relation',
26
26
  ]);
27
27
  /**
28
- * REQ kind — 6 values aligned with SysML 2.0 + FMEA (CR-180).
28
+ * REQ kind — GENAU EIN Wert je REQ, aus zwei (CR-SM-366).
29
29
  *
30
- * CR-SM-266 B: `negative` ENTFAELLT. Ein Verbots-REQ ("das System tut X nie") ist auch eine
31
- * Anforderung — die Implementierung muss Regeln abfragen oder Sicherungen einbauen, und das
32
- * ist `functional`. Der Wert hatte NULL Leser (kein Regel-, Gate- oder View-Konsument; anders
33
- * als risk/mitigation → FM-01..03, pre/postcondition → UC-05/06, non-functional → NFR-01) und
34
- * haette durch die where-Praedikate am satisfy-Pattern per AUSLASSUNG erstmals Wirkung
35
- * bekommen: er stand in keiner der vier Listen und waere damit nur noch per FCHAIN erfuellbar
36
- * gewesen. Die sechs verbleibenden Werte partitionieren die where-Listen VOLLSTAENDIG — kein
37
- * Wert ohne Zuordnung, keine Kante, die durch ein Loch in der Aufzaehlung faellt.
30
+ * `functional` -> erfuellt von FUNC. `non-functional` -> erfuellt von MOD (lokales Budget), SYS
31
+ * (Systemebene) oder FCHAIN (Ende-zu-Ende entlang der Kette). Ohne kinds kein Erfueller (BQ-07,
32
+ * `satisfiesPredicate`). Ein REQ, das beides ist, ist falsch zerlegt -> teilen; die
33
+ * Teilmengen-Semantik der where-Praedikate laesst es von niemandem erfuellen.
34
+ *
35
+ * Entfallen (Major, CR-SM-366):
36
+ * - `precondition` / `postcondition`: die Vorbedingung steckt im Eingangs-FLOW der Kette, die
37
+ * Nachbedingung ist das UC-Ziel — beides prueft R-21 (Integrationstest je Kette). Ihre Leser
38
+ * UC-05/06 sind seit CR-SM-357 gestrichen.
39
+ * - `risk` / `mitigation`: die FMEA-ROLLE einer Anforderung, keine Art — `REQ.attributes.role`
40
+ * (CR-SM-365), satisfy-neutral.
41
+ * Ein Altwert wird nicht still verworfen: `normalizeReqKinds` laesst ihn stehen, das Schema
42
+ * lehnt ihn ab, und R-18 meldet jede satisfy-Kante darauf als `kinds-mismatch` mit `declared`.
43
+ *
44
+ * Vorher CR-SM-266 B: `negative` entfiel (ein Verbots-REQ ist `functional`).
38
45
  */
39
- export const ReqKind = z.enum([
40
- 'functional', 'non-functional', 'risk',
41
- 'mitigation', 'precondition', 'postcondition',
42
- ]);
46
+ export const ReqKind = z.enum(['functional', 'non-functional']);
43
47
  /**
44
48
  * `kinds` in der Form, die WIRKLICH auf Platte liegt — als Liste.
45
49
  *
@@ -183,6 +187,36 @@ export function readRealRef(attributes) {
183
187
  const r = RealRefSchema.safeParse(raw);
184
188
  return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
185
189
  }
190
+ /**
191
+ * CR-SM-364: die Kopplung eines Uebergangs — `FLOW.attributes.sync`. Fehlend heisst UNBEKANNT,
192
+ * nie stillschweigend `sync`: eine Kettenkennzahl (synchrone Tiefe, Engstellen-Grad) weist eine
193
+ * unmarkierte Kette als „nicht bewertbar" aus, statt sie ueber eine Annahme zu beurteilen.
194
+ */
195
+ export const FlowSync = z.enum(['sync', 'async']);
196
+ /** Der EINE Leser von `FLOW.attributes.sync` (CR-SM-364) — ungueltig ist nicht fehlend. */
197
+ export function readFlowSync(attributes) {
198
+ const raw = attributes?.sync;
199
+ if (raw === undefined || raw === null)
200
+ return { state: 'absent' };
201
+ const r = FlowSync.safeParse(raw);
202
+ return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
203
+ }
204
+ /**
205
+ * CR-SM-365: die ROLLE einer Anforderung im FMEA-Sinn — `REQ.attributes.role`. `risk` und
206
+ * `mitigation` waren bis hierher `kinds`-Werte und damit satisfy-relevant (MOD/SYS erfuellten
207
+ * sie). Sie sind aber keine ART der Anforderung, sondern ihre Motivation: eine Gegenmassnahme ist
208
+ * funktional oder nicht-funktional wie jede andere REQ. Die Rolle ist satisfy-neutral — die
209
+ * Grammatik (`isValidTrace`) liest sie nicht; FM-01..03 lesen NUR sie.
210
+ */
211
+ export const ReqRole = z.enum(['risk', 'mitigation']);
212
+ /** Der EINE Leser von `REQ.attributes.role` (CR-SM-365) — ungueltig ist nicht fehlend. */
213
+ export function readReqRole(attributes) {
214
+ const raw = attributes?.role;
215
+ if (raw === undefined || raw === null)
216
+ return { state: 'absent' };
217
+ const r = ReqRole.safeParse(raw);
218
+ return r.success ? { state: 'bound', value: r.data } : { state: 'invalid', error: r.error.message };
219
+ }
186
220
  /**
187
221
  * The five judgment-work artifact ids (CR-GC-221's creation keys) that graphcode's
188
222
  * skills produce and that a phase gate checks for presence+freshness (CR-SM-227).
@@ -263,7 +297,7 @@ export const OntologyElement = z.object({
263
297
  description: z.string(),
264
298
  /** INCOSE TIAD verification method — only for TEST elements (CR-057). */
265
299
  method: VerificationMethod.optional(),
266
- /** REQ kinds: multi-valued classification (CR-180). Only for REQ elements. */
300
+ /** REQ kinds: exactly ONE of functional|non-functional (CR-SM-366); a list for the on-disk form. Only for REQ elements. */
267
301
  kinds: z.array(ReqKind).optional(),
268
302
  attributes: z.record(z.string(), z.unknown()).optional(),
269
303
  status: z.enum(['draft', 'reviewed', 'open', 'done']).default('draft'),
@@ -335,6 +369,7 @@ export const ELEMENT_ATTRIBUTES = {
335
369
  { key: 'concept', type: 'boolean', description: 'Concept-only TEST: no run artifact yet; exempt from the R-19 testRefs-binding requirement (CR-GC-205)' },
336
370
  ],
337
371
  REQ: [
372
+ { key: 'role', type: 'enum', enumValues: ReqRole.options, description: "FMEA role: 'risk' | 'mitigation' — satisfy-neutral, read by FM-01..03 via readReqRole. A mitigation is functional or non-functional like any REQ; the role is its motivation, not its kind (CR-SM-365)" },
338
373
  { key: 'severity', type: 'number', description: 'FMEA severity (1-10)' },
339
374
  { key: 'occurrence', type: 'number', description: 'FMEA occurrence (1-10)' },
340
375
  { key: 'detection', type: 'number', description: 'FMEA detection (1-10)' },
@@ -355,10 +390,12 @@ export const ELEMENT_ATTRIBUTES = {
355
390
  { key: 'status', type: 'enum', enumValues: ['open', 'done'], description: 'CR completion status' },
356
391
  { key: 'rationale', type: 'string', description: 'Reason for the change' },
357
392
  { key: 'spike', type: 'boolean', description: 'CR is a spike/exploration' },
393
+ { key: 'architectureOnly', type: 'boolean', description: 'Done CR without code change (model/architecture only): exempt from the CR-R02 commitRef requirement (CR-SM-386)' },
358
394
  ],
395
+ // CR-SM-364: genau ein Realisierungsattribut. `protocol`/`qos` entfielen — freie Strings, in
396
+ // 0 von 186 Familien-FLOWs gesetzt, ohne Leser. Abweichende Kopplung je Verbraucher ⇒ FLOW teilen.
359
397
  FLOW: [
360
- { key: 'protocol', type: 'string', description: 'ICD protocol (e.g. REST, gRPC)' },
361
- { key: 'qos', type: 'string', description: 'Quality of Service level' },
398
+ { key: 'sync', type: 'enum', enumValues: FlowSync.options, description: "Coupling of the transition: 'sync' | 'async'. Missing = unknown, never an implicit 'sync' — read with readFlowSync (CR-SM-364)" },
362
399
  ],
363
400
  MOD: [
364
401
  { key: 'path', type: 'string', description: 'Source file or glob the module owns, e.g. src/harness.ts — anchors the MOD<->file mapping for graph<->code conformance (CR-GC-205)' },
@@ -0,0 +1 @@
1
+ export declare function hasPlaceholder(text: string): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * placeholder.ts — the one definition of "this text still carries a placeholder" (CR-SM-383).
3
+ *
4
+ * UC-04 and BQ-07 each had their own pattern, both case-insensitive: `todo` matched the domain
5
+ * word "Todo" ("Benutzer fuegt einen neuen Todo-Eintrag hinzu"), and the finding could not be
6
+ * resolved by any rewording that kept the subject. Markers are written in capitals by
7
+ * convention; only the spelled-out phrases are matched regardless of case.
8
+ */
9
+ const MARKER_RE = /\b(TBD|TBR|TODO|FIXME|XXX)\b/;
10
+ const PHRASE_RE = /\b(placeholder|to be determined)\b|needs\s.*review/i;
11
+ export function hasPlaceholder(text) {
12
+ return MARKER_RE.test(text) || PHRASE_RE.test(text);
13
+ }
@@ -1,4 +1,5 @@
1
1
  import { normalizeReqKinds } from './ontology.js';
2
+ import { hasPlaceholder } from './placeholder.js';
2
3
  // ---------------------------------------------------------------------------
3
4
  // Weasel words list (BQ-01)
4
5
  // ---------------------------------------------------------------------------
@@ -205,7 +206,6 @@ export function bq06Conforming(graph) {
205
206
  // ---------------------------------------------------------------------------
206
207
  // BQ-07 Complete — REQ must have meaningful description and no placeholders
207
208
  // ---------------------------------------------------------------------------
208
- const placeholderPattern = /\b(TBD|TBR|TODO|placeholder|to be determined)\b|needs\s.*review/i;
209
209
  const BQ07_MIN_DESC_LENGTH = 20;
210
210
  export function bq07Complete(graph) {
211
211
  // CR-SM-266 REQ-X06: `kinds` ist Pflicht am REQ — hier als DRITTER Grund derselben Regel,
@@ -220,7 +220,7 @@ export function bq07Complete(graph) {
220
220
  // Angabe ist ein Vollstaendigkeitssignal und deshalb hier/warning — dieselbe Trennung, die
221
221
  // CR-SM-262 fuer RC-06 begruendet hat.
222
222
  const isIncomplete = (req) => descriptionOf(req).trim().length < BQ07_MIN_DESC_LENGTH ||
223
- placeholderPattern.test(descriptionOf(req)) ||
223
+ hasPlaceholder(descriptionOf(req)) ||
224
224
  normalizeReqKinds(req.kinds).length === 0;
225
225
  return reqElements(graph)
226
226
  .filter(isIncomplete)
@@ -230,7 +230,7 @@ export function bq07Complete(graph) {
230
230
  if (desc.trim().length < BQ07_MIN_DESC_LENGTH) {
231
231
  reasons.push(`description too short (${desc.trim().length}/${BQ07_MIN_DESC_LENGTH} chars)`);
232
232
  }
233
- if (placeholderPattern.test(desc)) {
233
+ if (hasPlaceholder(desc)) {
234
234
  reasons.push('contains placeholder');
235
235
  }
236
236
  if (normalizeReqKinds(req.kinds).length === 0) {
@@ -170,6 +170,20 @@ export type PhaseGateType = z.infer<typeof PhaseGate>;
170
170
  * single-owner convention as RULE_TO_DIMENSION.
171
171
  */
172
172
  export declare const RULE_TO_PHASE: Record<string, PhaseGateType>;
173
+ /**
174
+ * CR-SM-343 — die VORBEDINGUNG einer Regel: der Elementtyp, von dem der Graph mindestens ein
175
+ * Element tragen muss, damit die Regel ueberhaupt ausgewertet ist.
176
+ *
177
+ * Der Nenner einer Dimension ist `Σ Grundgesamtheit` ueber ihre Regeln (`domain`). Eine Regel,
178
+ * die mangels Gegenstand schweigt, truege dort ihre ganze Grundgesamtheit bei und liefert null
179
+ * Verstoesse — der Score STIEGE, weil nicht geprueft wurde. CR-R05 fragt nach einem Bauauftrag;
180
+ * ein Graph ohne einen einzigen CR (`cr: docs`) hat keinen Plan im Graphen, also ist die Frage
181
+ * dort NICHT GESTELLT, nicht bestanden. Wer einen Anteil bildet, fragt `ruleApplies` und laesst
182
+ * eine nicht anwendbare Regel aus Zaehler UND Nenner.
183
+ */
184
+ export declare const RULE_PRECONDITION: Readonly<Record<string, string>>;
185
+ /** Ist die Regel an einem Graphen mit diesen Typ-Zaehlungen ausgewertet? (CR-SM-343) */
186
+ export declare function ruleApplies(ruleId: string, countByType: Readonly<Record<string, number>>): boolean;
173
187
  /**
174
188
  * Die Eigentuemer-Spalte (CR-SM-350, Entscheidung 2026-09-22): welcher TASK eine Regel bearbeitet.
175
189
  *
@@ -152,6 +152,9 @@ export const RULE_TO_DIMENSION = {
152
152
  'BQ-01': 'req', 'BQ-02': 'req', 'BQ-04': 'req',
153
153
  'BQ-06': 'req', 'BQ-07': 'req',
154
154
  'RD-01': 'req', 'RD-02': 'req',
155
+ // CR-SM-343: CR-R05 (Blatt-REQ ohne Bauauftrag) — ein Mangel der Anforderungsdeckung, nicht
156
+ // der CR-Hygiene; zaehlt nur, wenn der Graph ueberhaupt CR-Knoten traegt (RULE_PRECONDITION).
157
+ 'CR-R05': 'req',
155
158
  // RD-04 is decomposition *breadth* — an architecture concern, not a requirement one
156
159
  'RD-04': 'arch',
157
160
  // CR-SM-311: die Untergrenze derselben Breite — ebenfalls Architektur
@@ -171,6 +174,7 @@ export const RULE_TO_DIMENSION = {
171
174
  'R-15': 'uc', 'R-16': 'uc', 'R-17': 'uc',
172
175
  // FC-04 (CR-SM-226): FCHAIN actor-bounded (trigger+consumer) — same dimension as FC-01..03.
173
176
  'FC-02': 'uc', 'FC-03': 'uc', 'FC-04': 'uc',
177
+ 'FC-05': 'uc', // CR-SM-363: Zusammenhang der Kette — dieselbe Dimension wie FC-04
174
178
  // arch
175
179
  'R-02': 'arch', 'R-10': 'arch', 'R-12': 'arch',
176
180
  // alloc
@@ -262,7 +266,7 @@ export const RULE_TO_PHASE = {
262
266
  'R-02': 'PDR', 'R-08': 'PDR', 'R-10': 'PDR', 'R-12': 'PDR', 'R-18': 'PDR',
263
267
  'R-04': 'PDR', 'R-22': 'PDR', 'R-23': 'PDR',
264
268
  'R-15': 'PDR',
265
- 'FC-03': 'PDR', 'FC-04': 'PDR',
269
+ 'FC-03': 'PDR', 'FC-04': 'PDR', 'FC-05': 'PDR',
266
270
  // CR-GC-366: Anschluss des Funktionsbaus — gehoert an dasselbe Gate wie R-15/IO-01,
267
271
  // die auf derselben Kette aufsetzen.
268
272
  'R-30': 'PDR', 'R-31': 'PDR',
@@ -282,7 +286,27 @@ export const RULE_TO_PHASE = {
282
286
  'VR-01': 'TRR',
283
287
  'FM-03': 'TRR',
284
288
  'CR-R02': 'TRR',
289
+ 'CR-R05': 'TRR', // CR-SM-343: dieselbe Phase wie AF-05 (Implementierungsplan)
285
290
  };
291
+ /**
292
+ * CR-SM-343 — die VORBEDINGUNG einer Regel: der Elementtyp, von dem der Graph mindestens ein
293
+ * Element tragen muss, damit die Regel ueberhaupt ausgewertet ist.
294
+ *
295
+ * Der Nenner einer Dimension ist `Σ Grundgesamtheit` ueber ihre Regeln (`domain`). Eine Regel,
296
+ * die mangels Gegenstand schweigt, truege dort ihre ganze Grundgesamtheit bei und liefert null
297
+ * Verstoesse — der Score STIEGE, weil nicht geprueft wurde. CR-R05 fragt nach einem Bauauftrag;
298
+ * ein Graph ohne einen einzigen CR (`cr: docs`) hat keinen Plan im Graphen, also ist die Frage
299
+ * dort NICHT GESTELLT, nicht bestanden. Wer einen Anteil bildet, fragt `ruleApplies` und laesst
300
+ * eine nicht anwendbare Regel aus Zaehler UND Nenner.
301
+ */
302
+ export const RULE_PRECONDITION = {
303
+ 'CR-R05': 'CR',
304
+ };
305
+ /** Ist die Regel an einem Graphen mit diesen Typ-Zaehlungen ausgewertet? (CR-SM-343) */
306
+ export function ruleApplies(ruleId, countByType) {
307
+ const required = RULE_PRECONDITION[ruleId];
308
+ return required === undefined || (countByType[required] ?? 0) > 0;
309
+ }
286
310
  /**
287
311
  * Die Eigentuemer-Spalte (CR-SM-350, Entscheidung 2026-09-22): welcher TASK eine Regel bearbeitet.
288
312
  *
@@ -302,10 +326,13 @@ export const TASK_OWNED_RULES = {
302
326
  trade: ['TR-01'], // CR-SM-355: Trade-Entscheidung als CR
303
327
  irr: ['IR-01'], // CR-SM-355: Annahmen-Review promoviert zu CR
304
328
  fmea: ['FM-01', 'FM-02', 'FM-03'],
305
- plan: ['MS-01', 'MS-02', 'MS-03', 'CR-R01', 'CR-R02', 'CR-R03'],
329
+ // CR-SM-377: CR-R05 (Blatt-REQ ohne Bauauftrag, CR-SM-343) fragt nach einem CR — die Antwort
330
+ // schreibt der Plan-Task (se-plan), nicht die Generierungsschleife. FC-05 bleibt Kern: die
331
+ // Kette zu schliessen ist Spezifikationsarbeit wie FC-04.
332
+ plan: ['MS-01', 'MS-02', 'MS-03', 'CR-R01', 'CR-R02', 'CR-R03', 'CR-R05'],
306
333
  // Annahme (CR-SM-350): BQ bekommt einen eigenen Task, vorerst ohne automatischen Eintrittspunkt.
307
334
  anforderungsqualitaet: ['BQ-01', 'BQ-02', 'BQ-04', 'BQ-06', 'BQ-07'],
308
- realisierung: ['R-19', 'R-20', 'R-26', 'R-29', 'R-32', 'VR-01', 'RC-01', 'RC-02', 'RC-03', 'RC-04', 'RC-05', 'RC-06', 'RC-07', 'RC-08', 'RC-09'],
335
+ realisierung: ['R-19', 'R-20', 'R-26', 'R-29', 'R-32', 'VR-01', 'RC-01', 'RC-02', 'RC-03', 'RC-04', 'RC-05', 'RC-06', 'RC-07', 'RC-08', 'RC-09', 'RC-10'],
309
336
  };
310
337
  /**
311
338
  * Der Eintrittspunkt eines Tasks im Kern: die Regel, die "dieses Artefakt fehlt" sagt. `null` = der
@@ -31,12 +31,18 @@
31
31
  * umformulierter Satz darf keinen Major ausloesen, deshalb steht im Golden-File nur die
32
32
  * Schluesselmenge, nicht die Prosa.
33
33
  *
34
- * Sprache: Englisch, wie die Regelbeschreibungen selbst. 18,6 KB reine Strings — der
35
- * `./browser`-Eintrag bleibt transitiv `node:`-frei (CR-SM-230), Strings ziehen nichts nach.
34
+ * Sprache: Englisch, wie die Regelbeschreibungen selbst — ausser `vorschlag`, der Satz, den ein Nutzer
35
+ * seinem Agenten schickt (CR-SM-384): Deutsch, weil er in der Sprache des Nutzers steht, nicht in der
36
+ * des Katalogs. Bis CR-SM-384 lag er als `VORSCHLAG_REGEL` in graphcode, ein paralleler Pfad zu
37
+ * `plain` und `fix_hint` mit derselben Driftgefahr wie oben. Reine Strings — der `./browser`-Eintrag
38
+ * bleibt transitiv `node:`-frei (CR-SM-230), Strings ziehen nichts nach.
36
39
  *
37
40
  * @author andreas@siglochconsulting
38
41
  */
39
- /** Ein verfasster Hilfeeintrag: die zwei Klartext-Schichten, dazu ein Kopier-Prompt, wo einer passt. */
42
+ /**
43
+ * Ein verfasster Hilfeeintrag: die zwei Klartext-Schichten, dazu ein Kopier-Prompt, wo einer passt,
44
+ * und — fuer die Regeln des Kern-Fokus — der Vorschlag an den Nutzer.
45
+ */
40
46
  export interface RuleHelpEntry {
41
47
  /** Schicht 0 — ohne SE-Jargon; endet mit der EINEN einfachen Handlung. */
42
48
  readonly plain: string;
@@ -44,6 +50,14 @@ export interface RuleHelpEntry {
44
50
  readonly se: string;
45
51
  /** Schicht 2 — ein kopierbarer Prompt (`se:*`-Skill oder MCP-Aufruf), wo einer passt. */
46
52
  readonly prompt?: string;
53
+ /**
54
+ * Schicht 3 (CR-SM-384) — der naechste Schritt als Bitte des NUTZERS an seinen Agenten: ein deutscher
55
+ * Satz, Imperativ, `{n}` = die Namen der Fund-Elemente. Keine Werkzeugnamen, keine Regel-IDs, keine
56
+ * Fix-Vorlage — der Nutzer entscheidet, der Agent fuehrt aus (graphcode CR-GC-729/730). Genau an den
57
+ * Regeln, die den Kern-Fokus stellen koennen: Task `kern`, Schwere nicht `info`, kein Eintrittspunkt
58
+ * einer Analyse (die haben ihren eigenen Satz beim Konsumenten). Beidseitig erzwungen im Test.
59
+ */
60
+ readonly vorschlag?: string;
47
61
  }
48
62
  /**
49
63
  * Je Regel-ID der Hilfeeintrag — Katalogregeln (`ALL_RULE_DEFS`) UND Conformance-Regeln