@lubab/madar
Advanced tools
@@ -7,21 +7,21 @@ const OWNER = 'file|module|class|function|method|service|handler|worker|component|controller|repository'; | ||
| const IRREGULAR = new Map('built=build generation=generate got=get getting=get persistence=persist planned=plan planning=plan ran=run running=run setting=set written=write wrote=write'.split(' ').map((pair) => pair.split('='))); | ||
| function canonical(value) { | ||
| const mapped = IRREGULAR.get(value); | ||
| if (mapped) | ||
| return mapped; | ||
| const ing = value.endsWith('ing') ? value.slice(0, -3) : ''; | ||
| const past = value.endsWith('ed') ? value.slice(0, -2) : ''; | ||
| const forms = [value, | ||
| /i(?:es|ed)$/u.test(value) ? `${value.slice(0, -3)}y` : '', | ||
| function canon(v) { | ||
| const m = IRREGULAR.get(v); | ||
| if (m) | ||
| return m; | ||
| const ing = v.endsWith('ing') ? v.slice(0, -3) : ''; | ||
| const past = v.endsWith('ed') ? v.slice(0, -2) : ''; | ||
| const f = [v, | ||
| /i(?:es|ed)$/u.test(v) ? `${v.slice(0, -3)}y` : '', | ||
| ing, ing ? `${ing}e` : '', past, past ? `${past}e` : '', | ||
| value.endsWith('es') ? value.slice(0, -2) : '', | ||
| value.endsWith('s') ? value.slice(0, -1) : '']; | ||
| const action = forms.find((candidate) => ACTIONS.has(candidate)); | ||
| if (action) | ||
| return action; | ||
| if (value.length <= 4) | ||
| return value; | ||
| if (value.endsWith('ies')) | ||
| return `${value.slice(0, -3)}y`; | ||
| return /(?<!s|u|i)s$/u.test(value) ? value.slice(0, -1) : value; | ||
| v.endsWith('es') ? v.slice(0, -2) : '', | ||
| v.endsWith('s') ? v.slice(0, -1) : '']; | ||
| const a = f.find((x) => ACTIONS.has(x)); | ||
| if (a) | ||
| return a; | ||
| if (v.length <= 4) | ||
| return v; | ||
| if (v.endsWith('ies')) | ||
| return `${v.slice(0, -3)}y`; | ||
| return /(?<!s|u|i)s$/u.test(v) ? v.slice(0, -1) : v; | ||
| } | ||
@@ -33,3 +33,3 @@ export function lexicalTokens(value) { | ||
| .toLowerCase(); | ||
| return (raw.match(/[\p{L}\p{N}]+/gu) ?? []).map(canonical); | ||
| return (raw.match(/[\p{L}\p{N}]+/gu) ?? []).map(canon); | ||
| } | ||
@@ -46,9 +46,9 @@ const [FW, LW, EW] = [FLOW, LOCATE, EXPLAIN].map((set) => [...set].join('|')); | ||
| for (const rule of rules) { | ||
| const topic = content(rule.exec(text)?.slice(1).join(' ') ?? '', mode, plain).join(' '); | ||
| if (topic) | ||
| return topic; | ||
| const hit = content(rule.exec(text)?.slice(1).join(' ') ?? '', mode, plain).join(' '); | ||
| if (hit) | ||
| return hit; | ||
| } | ||
| return ''; | ||
| } | ||
| function coordinatedFlow(text) { | ||
| function flow(text) { | ||
| const entry = /\b(?:accept|receive|submit|handle) (.+?)(?= (?:schedule|enqueue|queue|dispatch|research|process|compose|assemble|render|write|save|store|persist)\b)/u | ||
@@ -59,11 +59,11 @@ .exec(text), end = /\b(?:write|save|store|persist) (.+)$/u.exec(text), composed = /\b(compose|assemble|render) (.+?)(?= (?:and )?(?:write|save|store|persist)\b|$)/u | ||
| return undefined; | ||
| const input = content(entry[1], 'workflow'); | ||
| let output = content(composed?.[2] ?? end[1], 'workflow'); | ||
| if (composed && /^(?:model|output|result)$/u.test(output.at(-1) ?? '')) | ||
| output = content(end[1], 'workflow'); | ||
| const first = input[0], raw = output.at(-1), last = /^(?:model|output|result)$/u.test(raw ?? '') ? first : raw; | ||
| const ins = content(entry[1], 'workflow'); | ||
| let outs = content(composed?.[2] ?? end[1], 'workflow'); | ||
| if (composed && /^(?:model|output|result)$/u.test(outs.at(-1) ?? '')) | ||
| outs = content(end[1], 'workflow'); | ||
| const first = ins[0], raw = outs.at(-1), last = /^(?:model|output|result)$/u.test(raw ?? '') ? first : raw; | ||
| if (!first || !last) | ||
| return undefined; | ||
| const handoff = /\b(?:schedule|enqueue|queue|dispatch|publish|emit)\b/u.test(text) | ||
| ? 'schedule' : undefined, stages = [ | ||
| const hop = /\b(?:schedule|enqueue|queue|dispatch|publish|emit)\b/u.test(text) | ||
| ? 'schedule' : undefined, steps = [ | ||
| /\b(?:research|investigate|discover)\b/u.test(text) ? 'research' : '', | ||
@@ -75,8 +75,8 @@ /\b(?:compose|assemble|render|synthesize|merge)\b/u.test(text) | ||
| subject: first === last ? first : `${first} ${last}`, | ||
| entry: `request ${first}`, ...(stages.length ? { stage: stages.join(' ') } : {}), | ||
| ...(handoff ? { handoff } : {}), terminal: 'persistence', | ||
| terms: [...new Set([first, last, ...stages, ...(handoff ? [handoff] : [])])], | ||
| entry: `request ${first}`, ...(steps.length ? { stage: steps.join(' ') } : {}), | ||
| ...(hop ? { handoff: hop } : {}), terminal: 'persistence', | ||
| terms: [...new Set([first, last, ...steps, ...(hop ? [hop] : [])])], | ||
| }; | ||
| } | ||
| function flowSubject(text) { | ||
| function subject(text) { | ||
| const direct = pick(text, 'workflow', [ | ||
@@ -101,3 +101,3 @@ /\bwhat happen when (?:(?:a|an|the) )?(?:user|client|caller) (?:request|submit) (.+)$/, | ||
| } | ||
| const topic = pick(text, 'workflow', [ | ||
| const subj = pick(text, 'workflow', [ | ||
| /^follow (?:\S+ )*?(\S+) from .+ until (?:\S+ )*?(\S+) is/, | ||
@@ -111,5 +111,5 @@ /for (.+?) generate/, | ||
| ]); | ||
| return [topic || content(text, 'workflow').join(' '), []]; | ||
| return [subj || content(text, 'workflow').join(' '), []]; | ||
| } | ||
| function flowBounds(text) { | ||
| function bounds(text, s = false) { | ||
| if (/^follow /.test(text) | ||
@@ -119,5 +119,4 @@ || !(/\b(?:from|via)\b/u.test(text) || /^(?:trace|walk)\b.*\bthrough\b/u | ||
| return {}; | ||
| const read = (rule) => { | ||
| const value = lexicalTokens(rule.exec(text)?.[1] ?? '') | ||
| .filter((token) => !COMMON.has(token)).join(' '); | ||
| const read = (r) => { | ||
| const value = lexicalTokens(r.exec(text)?.[1] ?? '').filter((token) => !COMMON.has(token)).join(' '); | ||
| return value || undefined; | ||
@@ -132,6 +131,6 @@ }; | ||
| : read(/\bthrough (?!to\b)(.+?)(?= to\b| how\b|$)/u)), | ||
| terminal: read(/(?<!end )\b(?:through )?to (.+?)(?= how\b|$)/u)?.replace(/^save (?=report$)/u, ''), | ||
| terminal: read(/(?<!end )\b(?:through )?to (.+?)(?= how\b|$)/u)?.replace(s ? /^(?:save|finished|done) (?=report$)/u : /^save (?=report$)/u, ''), | ||
| }; | ||
| } | ||
| function simpleSubject(text, mode) { | ||
| function simple(text, mode) { | ||
| const locate = mode === 'locate'; | ||
@@ -154,7 +153,8 @@ const rules = locate ? [ | ||
| export function planQuestion(request) { | ||
| const raw = request.question.normalize('NFKC'), text = lexicalTokens(request.question).join(' '), tokens = text.split(' '), names = [...raw.matchAll(/(?<![\p{L}\p{N}_$])([\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)+)/gu)] | ||
| .map((match) => match[1]), ident = /\bwhere\s+(?:is|are|was|were)\s+[`'"]?([\p{L}_$][\p{L}\p{N}_$.-]*)[`'"]?\s+(?:defined|declared|implemented)\b/iu | ||
| .exec(raw)?.[1], ownerQuery = RegExp(`\\b(?:which|what) (?:${OWNER}) (?:${LW})\\b`).test(text); | ||
| const mode = ownerQuery ? 'locate' | ||
| : /\b(?:end to end|what happen when)\b|\bfrom\b.+\b(?:through|via)\b.+\bto\b|\btrace\b.+\bfrom\b.+\bto\b/.test(text) | ||
| const raw = request.question.normalize('NFKC'), text = lexicalTokens(request.question).join(' '), tokens = text.split(' '), refs = [...raw.matchAll(/(?<![\p{L}\p{N}_$])([\p{L}_$][\p{L}\p{N}_$]*(?:\.[\p{L}_$][\p{L}\p{N}_$]*)+)/gu)] | ||
| .map((match) => match[1]), id = /\bwhere\s+(?:is|are|was|were)\s+[`'"]?([\p{L}_$][\p{L}\p{N}_$.-]*)[`'"]?\s+(?:defined|declared|implemented)\b/iu | ||
| .exec(raw)?.[1], owner = RegExp(`\\b(?:which|what) (?:${OWNER}) (?:${LW})\\b`).test(text); | ||
| const mode = owner ? 'locate' | ||
| : /^where (?:is|are|was|were) .+ (?:flow|workflow|pipeline) orchestrated list (?:the )?(?:flow|workflow|pipeline) stage and (?:the )?entrypoint (?:service|handler|worker)$/u.test(text) | ||
| || /\b(?:end to end|what happen when)\b|\bfrom\b.+\b(?:through|via)\b.+\bto\b|\btrace\b.+\bfrom\b.+\bto\b/.test(text) | ||
| || /^follow /.test(text) | ||
@@ -178,21 +178,21 @@ || /^which .+\b(?:save|write)\b/.test(text) | ||
| } | ||
| const coordinated = mode === 'workflow' ? coordinatedFlow(text) : undefined; | ||
| const [topic, ignored] = mode === 'workflow' | ||
| ? coordinated ? [coordinated.subject, []] : flowSubject(text) | ||
| : [names[0] | ||
| ? content(names[0], mode, true).join(' ') | ||
| : mode === 'locate' && ident | ||
| ? content(ident, 'locate', true).join(' ') | ||
| : simpleSubject(text, mode), []]; | ||
| const span = coordinated | ||
| ?? (mode === 'workflow' ? flowBounds(text) : {}); | ||
| if (mode === 'workflow' && names.length) { | ||
| span.stage = names.flatMap(lexicalTokens).join(' '); | ||
| const coord = mode === 'workflow' ? flow(text) : undefined; | ||
| const [subj, omit] = mode === 'workflow' | ||
| ? coord ? [coord.subject, []] : subject(text) | ||
| : [refs[0] | ||
| ? content(refs[0], mode, true).join(' ') | ||
| : mode === 'locate' && id | ||
| ? content(id, 'locate', true).join(' ') | ||
| : simple(text, mode), []]; | ||
| const span = coord ?? (mode === 'workflow' | ||
| ? bounds(text, /(?:^|\s)to\s+(?:(?:a|an|the|its)\s+)?(?:finished|done)\s+report\s*[.?!]*$/iu.test(raw)) : {}); | ||
| if (mode === 'workflow' && refs.length) { | ||
| span.stage = refs.flatMap(lexicalTokens).join(' '); | ||
| } | ||
| const skip = new Set(ignored); | ||
| const terms = new Set((coordinated?.terms ?? tokens).filter((token) => !isNoise(token, mode) && !skip.has(token))); | ||
| lexicalTokens(topic).forEach((token) => terms.add(token)); | ||
| const sorted = [...terms].sort(); | ||
| if (!topic || sorted.length === 0) { | ||
| return { status: 'unsupported', reason: 'missing_subject', terms: sorted }; | ||
| const skip = new Set(omit); | ||
| const keys = new Set((coord?.terms ?? tokens).filter((token) => !isNoise(token, mode) && !skip.has(token))); | ||
| lexicalTokens(subj).forEach((token) => keys.add(token)); | ||
| const list = [...keys].sort(); | ||
| if (!subj || list.length === 0) { | ||
| return { status: 'unsupported', reason: 'missing_subject', terms: list }; | ||
| } | ||
@@ -209,15 +209,15 @@ const words = new Set(tokens); | ||
| .test(text) ? [] | ||
| : sorted.filter((token) => !lexicalTokens(topic).includes(token)); | ||
| : list.filter((token) => !lexicalTokens(subj).includes(token)); | ||
| return { | ||
| status: 'supported', | ||
| plan: { | ||
| intent: mode, subject: topic, terms: sorted, | ||
| intent: mode, subject: subj, terms: list, | ||
| obligations: kinds.map((kind, index) => ({ | ||
| id: `o${index + 1}`, kind, | ||
| target: kind === 'entry' ? span.entry ?? topic | ||
| : kind === 'stage' ? span.stage ?? topic | ||
| : kind === 'handoff' ? span.handoff ?? topic | ||
| : kind === 'terminal' ? span.terminal ?? topic | ||
| target: kind === 'entry' ? span.entry ?? subj | ||
| : kind === 'stage' ? span.stage ?? subj | ||
| : kind === 'handoff' ? span.handoff ?? subj | ||
| : kind === 'terminal' ? span.terminal ?? subj | ||
| : kind === 'behavior' && mode === 'explain' && rest.length | ||
| ? rest.join(' ') : topic, | ||
| ? rest.join(' ') : subj, | ||
| mandatory: true, | ||
@@ -224,0 +224,0 @@ })), |
+1
-1
| { | ||
| "name": "@lubab/madar", | ||
| "version": "0.40.0-beta.6", | ||
| "version": "0.40.0-beta.7", | ||
| "mcpName": "io.github.mohanagy/madar", | ||
@@ -5,0 +5,0 @@ "description": "Give AI coding agents a small, authenticated evidence path through large TypeScript and JavaScript repositories.", |
+2
-2
@@ -15,3 +15,3 @@ # Madar | ||
| [](https://www.npmjs.com/package/@lubab/madar/v/0.40.0-beta.6) | ||
| [](https://www.npmjs.com/package/@lubab/madar/v/0.40.0-beta.7) | ||
| [](https://nodejs.org/) | ||
@@ -23,3 +23,3 @@ [](#local-by-design) | ||
| See [beta.6 changes](https://github.com/mohanagy/madar/blob/next/CHANGELOG.md#0400-beta6---2026-08-02). This corrective manual-test candidate precedes #631 qualification; it makes no comparative performance or retention claim. | ||
| See [beta.7 changes](https://github.com/mohanagy/madar/blob/next/CHANGELOG.md#0400-beta7---2026-08-02). This terminal-language corrective manual-test candidate precedes #631 qualification; it makes no comparative performance or retention claim. | ||
@@ -26,0 +26,0 @@ ## Start in three steps |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
654026
0