Neuer Status-Code [!] für Items mit unbekannter Größe/Aufwand:
Parser:
- STATUS_BY_CODE erweitert: '!' -> {key:'highrisk', name:'High Risk – Aufwand unklar'}
Style:
- .node.st-highrisk: Pastellorange (#FFE5CC) mit orange Rahmen (#F97316)
- .chip.st-highrisk: Gleiche Farben für Agenda-Beispiele
i18n (alle 9 Sprachen):
- DE: "High Risk"
- EN: "high risk"
- ES: "alto riesgo"
- FR: "risque élevé"
- PL: "wysokie ryzyko"
- RU: "высокий риск"
- HI: "उच्च जोखिम"
- ZH: "高风险"
- JA: "高リスク"
Tests:
- parser.test.js aktualisiert: 8 Status-Codes (waren 7)
Damit können Items mit ungeklärtem Aufwand im Lean-Pathfinding-Prozess
gekennzeichnet werden – wichtig für Risk-Flagging in Phase 1.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
67 lines
3.0 KiB
JavaScript
67 lines
3.0 KiB
JavaScript
/* Werkbaum-Parser — headless, ohne DOM/UI nutzbar.
|
||
Setzt docs/SPEC.md §1–§8 um: Zeilenformat, Hierarchie (Einrückung),
|
||
Zerlegungsart (Gate), Status, Größe, Links, Personen-Tags, Kommentare.
|
||
Verhalten ist normativ gegen SPEC — Änderungen zuerst dort dokumentieren. */
|
||
|
||
/* T-Shirt-Größen (SPEC §5), aufsteigend geordnet. */
|
||
export const SIZE_RANK = { XS: 0, S: 1, M: 2, L: 3, XL: 4, XXL: 5 };
|
||
|
||
/* Status-Vokabular (SPEC §4): Checkbox-Code -> {key, name}.
|
||
`name` ist der deutsche Anzeigename (Quellsprache). */
|
||
export const STATUS_BY_CODE = {
|
||
'?': {key:'idee', name:'Idee'},
|
||
' ': {key:'geplant', name:'geplant'},
|
||
'~': {key:'arbeit', name:'in Arbeit'},
|
||
'/': {key:'durchstich', name:'Durchstich – funktionsbereit, Feinarbeiten offen'},
|
||
'x': {key:'fertig', name:'fertig'},
|
||
'^': {key:'prod', name:'in Produktion'},
|
||
'-': {key:'verworfen', name:'verworfen'},
|
||
'!': {key:'highrisk', name:'High Risk – Aufwand unklar'}
|
||
};
|
||
|
||
/* Parst den Notationstext zu { roots, warnings }.
|
||
Jeder Knoten: {label, type:'and'|'or', status, url, size, tags, children, line}.
|
||
Extraktionsreihenfolge (SPEC §1): Kommentar -> Zeichen/Status -> URL -> Größe
|
||
-> Tags -> Label. Hierarchie über Einrückungsbreite (Tab = 2 Leerzeichen);
|
||
Elternknoten ist die nächste vorangehende Zeile mit kleinerer Breite. */
|
||
export function parse(text){
|
||
const virtualRoot = {label:'', type:'and', children:[]};
|
||
const stack = [{node:virtualRoot, width:-1}];
|
||
const warnings = [];
|
||
|
||
text.split('\n').forEach((raw, i) => {
|
||
raw = raw.replace(/%%.*$/, ''); /* %%-Kommentare entfernen (Mermaid-Konvention) */
|
||
if(!raw.trim()) return;
|
||
/* Statusbox tolerant erfassen: irgendein einzelnes Zeichen in [ ] an der
|
||
Statusposition. Gültige Codes -> Status; unbekannte -> Warnung + neutral
|
||
(fehlertolerant: die Zeile geht nicht verloren). */
|
||
const m = raw.match(/^([ \t]*)([-|])?\s*(?:\[([^\]])\]\s*)?(.*)$/);
|
||
const width = m[1].replace(/\t/g,' ').length;
|
||
const type = m[2] === '|' ? 'or' : 'and';
|
||
const boxChar = m[3]; // undefined, wenn keine Statusbox
|
||
|
||
let rest = m[4], url = null, size = null;
|
||
const tags = [];
|
||
rest = rest.replace(/https?:\/\/\S+/i, s => { url = s; return ''; });
|
||
rest = rest.replace(/\((XXL|XS|XL|S|M|L)\)/i, (s, g) => { size = g.toUpperCase(); return ''; });
|
||
rest = rest.replace(/@([\p{L}\p{N}._-]+)/gu, (s, g) => { tags.push(g); return ''; });
|
||
const label = rest.replace(/\s+/g, ' ').trim();
|
||
if(!label) return;
|
||
|
||
let status = null;
|
||
if(boxChar != null){
|
||
status = STATUS_BY_CODE[boxChar.toLowerCase()] || null;
|
||
if(!status) warnings.push({type:'unknownStatus', line:i+1, code:boxChar});
|
||
}
|
||
|
||
while(stack.length > 1 && stack[stack.length-1].width >= width) stack.pop();
|
||
const parent = stack[stack.length-1].node;
|
||
|
||
const node = {label, type, status, url, size, tags, children:[], line:i+1};
|
||
parent.children.push(node);
|
||
stack.push({node, width});
|
||
});
|
||
|
||
return {roots: virtualRoot.children, warnings};
|
||
}
|