@milehimikey/em 1.9.1 → 1.10.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.
Files changed (77) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +1 -1
  2. package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
  3. package/.claude/skills/event-modeling-design/SKILL.md +1 -1
  4. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  5. package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
  6. package/.claude/skills/event-modeling-review/SKILL.md +1 -1
  7. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +46 -1
  8. package/README.md +8 -1
  9. package/dist/cli/ciInit.d.ts +10 -3
  10. package/dist/cli/ciInit.js +22 -14
  11. package/dist/cli/ciInit.js.map +1 -1
  12. package/dist/cli/systemInputs.d.ts +34 -0
  13. package/dist/cli/systemInputs.js +129 -0
  14. package/dist/cli/systemInputs.js.map +1 -0
  15. package/dist/cli.js +220 -5
  16. package/dist/cli.js.map +1 -1
  17. package/dist/emit/json.d.ts +16 -1
  18. package/dist/emit/json.js +36 -1
  19. package/dist/emit/json.js.map +1 -1
  20. package/dist/emit/queryJson.d.ts +6 -0
  21. package/dist/emit/queryJson.js +29 -0
  22. package/dist/emit/queryJson.js.map +1 -0
  23. package/dist/emit/systemJson.d.ts +8 -0
  24. package/dist/emit/systemJson.js +38 -0
  25. package/dist/emit/systemJson.js.map +1 -0
  26. package/dist/mcp/server.d.ts +1 -1
  27. package/dist/mcp/server.js +185 -6
  28. package/dist/mcp/server.js.map +1 -1
  29. package/dist/model/edges.d.ts +13 -1
  30. package/dist/model/edges.js +43 -15
  31. package/dist/model/edges.js.map +1 -1
  32. package/dist/model/model.d.ts +2 -0
  33. package/dist/model/model.js +1 -0
  34. package/dist/model/model.js.map +1 -1
  35. package/dist/model/qualifiedRef.d.ts +47 -0
  36. package/dist/model/qualifiedRef.js +100 -0
  37. package/dist/model/qualifiedRef.js.map +1 -0
  38. package/dist/model/queryIndex.d.ts +50 -0
  39. package/dist/model/queryIndex.js +111 -0
  40. package/dist/model/queryIndex.js.map +1 -0
  41. package/dist/model/rules.d.ts +69 -0
  42. package/dist/model/rules.js +82 -0
  43. package/dist/model/rules.js.map +1 -1
  44. package/dist/model/sliceDocIndex.d.ts +30 -0
  45. package/dist/model/sliceDocIndex.js +110 -0
  46. package/dist/model/sliceDocIndex.js.map +1 -0
  47. package/dist/model/validate.js +4 -11
  48. package/dist/model/validate.js.map +1 -1
  49. package/dist/parser/ast.d.ts +4 -0
  50. package/dist/parser/parser.d.ts +2 -0
  51. package/dist/parser/parser.js +8 -2
  52. package/dist/parser/parser.js.map +1 -1
  53. package/dist/query/format.d.ts +8 -0
  54. package/dist/query/format.js +80 -0
  55. package/dist/query/format.js.map +1 -0
  56. package/dist/query/pipeline.d.ts +17 -0
  57. package/dist/query/pipeline.js +38 -0
  58. package/dist/query/pipeline.js.map +1 -0
  59. package/dist/query/system.d.ts +60 -0
  60. package/dist/query/system.js +87 -0
  61. package/dist/query/system.js.map +1 -0
  62. package/dist/query/verbs.d.ts +100 -0
  63. package/dist/query/verbs.js +306 -0
  64. package/dist/query/verbs.js.map +1 -0
  65. package/dist/render/drawEdges.js +8 -4
  66. package/dist/render/drawEdges.js.map +1 -1
  67. package/dist/render/sliceDiagram.js +1 -0
  68. package/dist/render/sliceDiagram.js.map +1 -1
  69. package/dist/system/manifest.d.ts +45 -0
  70. package/dist/system/manifest.js +178 -0
  71. package/dist/system/manifest.js.map +1 -0
  72. package/dist/system/verify.d.ts +88 -0
  73. package/dist/system/verify.js +263 -0
  74. package/dist/system/verify.js.map +1 -0
  75. package/dist/util/slug.js +16 -10
  76. package/dist/util/slug.js.map +1 -1
  77. package/package.json +10 -1
@@ -0,0 +1,8 @@
1
+ import { ConsumerEntry, ProducerEntry, ClosureEntry, SliceQueryEntry, InvariantQueryEntry, FieldQueryEntry, PathQueryEntry } from "./verbs.js";
2
+ export declare function formatConsumers(results: ConsumerEntry[]): string;
3
+ export declare function formatProducers(results: ProducerEntry[]): string;
4
+ export declare function formatClosure(results: ClosureEntry[]): string;
5
+ export declare function formatSlices(results: SliceQueryEntry[]): string;
6
+ export declare function formatInvariant(results: InvariantQueryEntry[]): string;
7
+ export declare function formatField(results: FieldQueryEntry[]): string;
8
+ export declare function formatPath(results: PathQueryEntry[]): string;
@@ -0,0 +1,80 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Human-text formatting for `em query`'s eight verbs (the default output — `--json` bypasses
3
+ // this entirely and prints `emit/queryJson.ts`'s document instead). An empty `results` array
4
+ // always prints a bare `(none)` line and still exits 0 — a query with a legitimately empty
5
+ // answer (an event nobody consumes, two elements with no path between them) is not an error.
6
+ function tagList(label, items) {
7
+ return items.length > 0 ? ` ${label}=[${items.join(", ")}]` : "";
8
+ }
9
+ export function formatConsumers(results) {
10
+ if (results.length === 0)
11
+ return "(none)";
12
+ return results.map((r) => `${r.ref} (${r.kind}) in slice "${r.sliceName}" [${r.sliceKey}] — via ${r.via}`).join("\n");
13
+ }
14
+ export function formatProducers(results) {
15
+ if (results.length === 0)
16
+ return "(none)";
17
+ const lines = [];
18
+ for (const r of results) {
19
+ lines.push(`${r.ref} (${r.kind}) in slice "${r.sliceName}" [${r.sliceKey}] — via ${r.via}`);
20
+ for (const t of r.uiTriggers)
21
+ lines.push(` triggered by ui: ${t.ref} "${t.name}"`);
22
+ }
23
+ return lines.join("\n");
24
+ }
25
+ export function formatClosure(results) {
26
+ if (results.length === 0)
27
+ return "(none)";
28
+ return results.map((r) => `depth ${r.depth} ${r.ref} (${r.kind}) via ${r.via} — slice "${r.sliceName}" [${r.sliceKey}]`).join("\n");
29
+ }
30
+ export function formatSlices(results) {
31
+ if (results.length === 0)
32
+ return "(none)";
33
+ return results
34
+ .map((r) => `${r.ref} "${r.name}" pattern=${r.pattern} status=${r.status ?? "no-doc"}` +
35
+ tagList("personas", r.personas) +
36
+ tagList("contexts", r.contexts) +
37
+ tagList("tags", r.tags))
38
+ .join("\n");
39
+ }
40
+ export function formatInvariant(results) {
41
+ if (results.length === 0)
42
+ return "(none)";
43
+ const r = results[0];
44
+ const lines = [
45
+ `${r.id} declared in slice "${r.sliceName}" [${r.sliceRef}]`,
46
+ ` doc: ${r.docPath ?? "(no doc)"} status: ${r.status ?? "(none)"}`,
47
+ ];
48
+ if (r.citations !== null) {
49
+ if (r.citations.length === 0)
50
+ lines.push(" citations: (none)");
51
+ else {
52
+ lines.push(" citations:");
53
+ for (const c of r.citations)
54
+ lines.push(` ${c.file}:${c.line}`);
55
+ }
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+ export function formatField(results) {
60
+ if (results.length === 0)
61
+ return "(none)";
62
+ const r = results[0];
63
+ const renamed = tagList("renamedFrom", r.renamedFrom ?? []);
64
+ const elementRenamed = tagList("elementRenamedFrom", r.elementRenamedFrom ?? []);
65
+ return `${r.elementRef}.${r.name}: ${r.type ?? "(untyped)"} tag=${r.tag} assigned=${r.assigned}${renamed}${elementRenamed}`;
66
+ }
67
+ export function formatPath(results) {
68
+ if (results.length === 0)
69
+ return "(none)";
70
+ const r = results[0];
71
+ if (r.length === 0)
72
+ return r.refs[0];
73
+ const parts = [r.refs[0]];
74
+ for (let i = 0; i < r.edgeKinds.length; i++) {
75
+ parts.push(`--[${r.edgeKinds[i]}]-->`);
76
+ parts.push(r.refs[i + 1]);
77
+ }
78
+ return parts.join(" ");
79
+ }
80
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/query/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ import { Diagnostic } from "../model/validate.js";
4
+ import { ModelIndex } from "../model/queryIndex.js";
5
+ export interface QueryCompileResult {
6
+ model: NormalizedModel;
7
+ refs: RefsResult;
8
+ diagnostics: Diagnostic[];
9
+ index: ModelIndex;
10
+ }
11
+ /** Compile one `.em` source for query use: parse + normalize + refs + validate (grid-free, see
12
+ * header) + the ModelIndex every verb queries. `baseDir` is the `.em` file's own directory —
13
+ * same doc-resolution convention every other command uses (passed straight through to
14
+ * `buildModelIndex`'s slice-doc join). Throws `ParseError` on a syntax error, same as
15
+ * `pipeline.ts`'s `compile()` — callers (cli.ts's compileFile-style wrapper, the MCP tool)
16
+ * catch it the same way. */
17
+ export declare function compileForQuery(source: string, baseDir: string): QueryCompileResult;
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em query`'s sub-pipeline (MIL-168): `parse -> normalize -> computeRefs -> buildIndex`,
3
+ // skipping `pipeline.ts`'s own `layout()` (the dense per-cell Grid allocation — rows x slice
4
+ // columns) and `emitDot()` entirely. Neither is query work: nothing here renders, and
5
+ // `layout()`'s only diagnostic contribution `validate()` reads is `grid.collisions` (row/col
6
+ // placement clashes — a rendering-only concern, `src/model/validate.ts` greps clean for any
7
+ // other `grid.` use). `EMPTY_GRID` below hands `validate()` a real, empty `Grid` shape instead
8
+ // of computing one, so query pays zero layout cost while every OTHER validate.ts diagnostic
9
+ // (unresolved refs, dangling views, tag/field completeness, ...) still runs at full strength.
10
+ //
11
+ // validate() itself is deliberately still called, one explicit deviation from this ticket's
12
+ // design note (which named it alongside layout/DOT as work to skip): the CLI/MCP contract
13
+ // this ticket also specifies is "refuse (exit 1) if a model has compile errors," the same
14
+ // `hasErrors(diagnostics)` gate `em export`/`em status`/every other JSON surface uses — dropping
15
+ // validate() would mean `em query` silently traverses a model with unresolved references
16
+ // instead of refusing, a behavior change from every sibling command with no upside once
17
+ // layout's own cost is already zero. See this ticket's report for the full tradeoff.
18
+ import { parse } from "../parser/parser.js";
19
+ import { normalize } from "../model/model.js";
20
+ import { computeRefs } from "../model/refs.js";
21
+ import { validate } from "../model/validate.js";
22
+ import { buildModelIndex } from "../model/queryIndex.js";
23
+ const EMPTY_GRID = { rows: [], cols: 0, sliceNames: [], cells: [], rowIndexByKey: new Map(), collisions: [] };
24
+ /** Compile one `.em` source for query use: parse + normalize + refs + validate (grid-free, see
25
+ * header) + the ModelIndex every verb queries. `baseDir` is the `.em` file's own directory —
26
+ * same doc-resolution convention every other command uses (passed straight through to
27
+ * `buildModelIndex`'s slice-doc join). Throws `ParseError` on a syntax error, same as
28
+ * `pipeline.ts`'s `compile()` — callers (cli.ts's compileFile-style wrapper, the MCP tool)
29
+ * catch it the same way. */
30
+ export function compileForQuery(source, baseDir) {
31
+ const ast = parse(source);
32
+ const model = normalize(ast);
33
+ const refs = computeRefs(model);
34
+ const diagnostics = [...validate(model, EMPTY_GRID, refs), ...refs.diagnostics];
35
+ const index = buildModelIndex(model, refs, baseDir);
36
+ return { model, refs, diagnostics, index };
37
+ }
38
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/query/pipeline.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,0FAA0F;AAC1F,6FAA6F;AAC7F,sFAAsF;AACtF,6FAA6F;AAC7F,4FAA4F;AAC5F,+FAA+F;AAC/F,4FAA4F;AAC5F,8FAA8F;AAC9F,EAAE;AACF,4FAA4F;AAC5F,0FAA0F;AAC1F,0FAA0F;AAC1F,iGAAiG;AACjG,yFAAyF;AACzF,wFAAwF;AACxF,qFAAqF;AAErF,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAmB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAc,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAc,MAAM,wBAAwB,CAAC;AAGrE,MAAM,UAAU,GAAS,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,GAAG,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;AASpH;;;;;6BAK6B;AAC7B,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,OAAe;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,WAAW,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ import { ModelIndex } from "../model/queryIndex.js";
4
+ import type { Diagnostic } from "../model/validate.js";
5
+ export interface QueryModelEntry {
6
+ file: string;
7
+ modelKey: string;
8
+ model: NormalizedModel;
9
+ refs: RefsResult;
10
+ index: ModelIndex;
11
+ }
12
+ export interface QuerySystem {
13
+ entries: QueryModelEntry[];
14
+ /** True once more than one file was given — the CLI/MCP layer's signal to qualify every ref
15
+ * in its output (`qualifyRef` below), and the signal `resolveElement`'s ambiguity rule uses
16
+ * ("unqualified + multi-model + ambiguous" -> list qualified candidates, per this ticket's
17
+ * design note). */
18
+ multiModel: boolean;
19
+ /** `duplicate-model-key` warnings from keying the entries (MIL-193, `computeModelKeys()`):
20
+ * two input models deriving the same key, the later one suffixed `~2`/`~3`, … in file-list
21
+ * order. Empty for a single-model system. The CLI prints these to stderr like any other
22
+ * compile warning; the JSON document is unaffected (MCP parity holds). */
23
+ diagnostics: Diagnostic[];
24
+ }
25
+ export declare function buildQuerySystem(entries: Array<{
26
+ file: string;
27
+ model: NormalizedModel;
28
+ refs: RefsResult;
29
+ index: ModelIndex;
30
+ }>): QuerySystem;
31
+ /** `<modelKey>:<ref>` when the system spans more than one model, else the bare ref — the one
32
+ * place every query verb decides whether to qualify a ref for output, so the WHEN can't drift
33
+ * between verbs (the HOW is `formatQualifiedRef()`'s). */
34
+ export declare function qualifyRef(system: QuerySystem, modelKey: string, ref: string): string;
35
+ export interface ResolvedElement {
36
+ modelKey: string;
37
+ file: string;
38
+ entry: QueryModelEntry;
39
+ ref: string;
40
+ qualifiedRef: string;
41
+ elementKind: string;
42
+ elementName: string;
43
+ }
44
+ export type ResolveResult = {
45
+ ok: true;
46
+ match: ResolvedElement;
47
+ } | {
48
+ ok: false;
49
+ error: string;
50
+ };
51
+ /** Resolve one user-supplied element reference — a stable ref, a `<modelKey>:`-qualified ref, or
52
+ * a bare display name — against every model in `system` (or just the named model, when
53
+ * qualified). A bare name/ref matching more than one element across the searched model(s) is
54
+ * always an error listing every candidate as a fully-qualified ref (`<modelKey>:<ref>`, even
55
+ * when `system` is single-model — a candidate list must be unambiguous on its own) — resolution
56
+ * never guesses. "Resolution is per-model" (MIL-168's spec): a bare name is looked up in each
57
+ * candidate model's OWN `byName` index, never merged across models before matching. A repeated
58
+ * read model's later instances (`view X again`) don't count as separate name matches — see
59
+ * the bare-name branch below. */
60
+ export declare function resolveElement(system: QuerySystem, raw: string): ResolveResult;
@@ -0,0 +1,87 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em query`'s multi-model addressing (MIL-168): a `QuerySystem` is one or more compiled
3
+ // models, each keyed by a short, stable `modelKey`. Refs (`<sliceKey>/<kind>.<slug>`) are, and
4
+ // remain, model-UNQUALIFIED — `computeRefs()`'s contract is untouched. For a query answer that
5
+ // spans more than one input file, that's ambiguous on its own (two models can easily mint the
6
+ // same slice/element ref independently), so `em query` addresses cross-model results with the
7
+ // qualified form `<modelKey>:<sliceKey>/<kind>.<slug>` — accepted on input alongside a bare
8
+ // ref/display name, and used to disambiguate/print results whenever more than one file was given.
9
+ //
10
+ // Query was the first surface to need this (MIL-168) and originally derived `modelKey` from the
11
+ // input filename. Since MIL-193 the key, the grammar, and the parse/format live in
12
+ // model/qualifiedRef.ts — the ONE cross-model addressing scheme every em surface shares
13
+ // (`em export`'s `model.key`, MIL-194's seam manifest, em-portal deep links): the key is the
14
+ // kebab-slug of the declared `model "Name"` (basename fallback when none is declared), so a
15
+ // query result's qualified ref is the same string an export consumer would mint for the same
16
+ // element. This module only decides WHEN to qualify (more than one file given) and how a
17
+ // user-supplied ref resolves; it never spells the grammar itself.
18
+ import { normalizeName } from "../model/model.js";
19
+ import { computeModelKeys, formatQualifiedRef, parseQualifiedRef } from "../model/qualifiedRef.js";
20
+ export function buildQuerySystem(entries) {
21
+ const { keys, diagnostics } = computeModelKeys(entries);
22
+ return {
23
+ entries: entries.map((e, i) => ({ file: e.file, modelKey: keys[i], model: e.model, refs: e.refs, index: e.index })),
24
+ multiModel: entries.length > 1,
25
+ diagnostics,
26
+ };
27
+ }
28
+ /** `<modelKey>:<ref>` when the system spans more than one model, else the bare ref — the one
29
+ * place every query verb decides whether to qualify a ref for output, so the WHEN can't drift
30
+ * between verbs (the HOW is `formatQualifiedRef()`'s). */
31
+ export function qualifyRef(system, modelKey, ref) {
32
+ return system.multiModel ? formatQualifiedRef(modelKey, ref) : ref;
33
+ }
34
+ const REF_SHAPE_RE = /\//;
35
+ /** Resolve one user-supplied element reference — a stable ref, a `<modelKey>:`-qualified ref, or
36
+ * a bare display name — against every model in `system` (or just the named model, when
37
+ * qualified). A bare name/ref matching more than one element across the searched model(s) is
38
+ * always an error listing every candidate as a fully-qualified ref (`<modelKey>:<ref>`, even
39
+ * when `system` is single-model — a candidate list must be unambiguous on its own) — resolution
40
+ * never guesses. "Resolution is per-model" (MIL-168's spec): a bare name is looked up in each
41
+ * candidate model's OWN `byName` index, never merged across models before matching. A repeated
42
+ * read model's later instances (`view X again`) don't count as separate name matches — see
43
+ * the bare-name branch below. */
44
+ export function resolveElement(system, raw) {
45
+ // `parseQualifiedRef` only splits when the prefix has the model-key SHAPE; a prefix that
46
+ // isn't one of THIS system's keys is then left in place — a display name may legitimately
47
+ // contain a colon — so the search runs over every model; if nothing matches, the miss names
48
+ // the unrecognised prefix (a typo'd qualifier is the likely cause).
49
+ const parsed = parseQualifiedRef(raw);
50
+ const known = parsed.modelKey !== null && system.entries.some((e) => e.modelKey === parsed.modelKey);
51
+ const modelKey = known ? parsed.modelKey : undefined;
52
+ const rest = known ? parsed.ref : raw;
53
+ const candidates = modelKey ? system.entries.filter((e) => e.modelKey === modelKey) : system.entries;
54
+ const matches = [];
55
+ for (const entry of candidates) {
56
+ // A slash makes the input look like a ref; when no ref matches, fall through to the
57
+ // display-name lookup — a name may itself contain a slash ("Approve/Reject Screen").
58
+ const byRef = REF_SHAPE_RE.test(rest) ? entry.index.byRef.get(rest) : undefined;
59
+ if (byRef) {
60
+ const ref = entry.refs.refById.get(byRef.id);
61
+ matches.push({ modelKey: entry.modelKey, file: entry.file, entry, ref, qualifiedRef: formatQualifiedRef(entry.modelKey, ref), elementKind: byRef.kind, elementName: byRef.name });
62
+ }
63
+ else {
64
+ // A repeated read model (`view X again`) has one name and several instances; the bare
65
+ // name means the read model, which resolves to its FIRST instance (`logicalId`) — every
66
+ // closure/path verb then follows the instance chain from there (verbs.ts's header). A
67
+ // later instance stays addressable by its own ref.
68
+ const bucket = (entry.model.byName.get(normalizeName(rest)) ?? []).filter((el) => el.logicalId === el.id);
69
+ for (const el of bucket) {
70
+ const ref = entry.refs.refById.get(el.id);
71
+ matches.push({ modelKey: entry.modelKey, file: entry.file, entry, ref, qualifiedRef: formatQualifiedRef(entry.modelKey, ref), elementKind: el.kind, elementName: el.name });
72
+ }
73
+ }
74
+ }
75
+ if (matches.length === 0) {
76
+ const hint = parsed.modelKey !== null && !known
77
+ ? ` ("${parsed.modelKey}" is not a model key — input models are: ${system.entries.map((e) => e.modelKey).join(", ")})`
78
+ : "";
79
+ return { ok: false, error: `em query: no element matches "${raw}"${hint}` };
80
+ }
81
+ if (matches.length > 1) {
82
+ const candidateRefs = matches.map((m) => m.qualifiedRef).join(", ");
83
+ return { ok: false, error: `em query: "${raw}" is ambiguous — matches ${matches.length} elements: ${candidateRefs}` };
84
+ }
85
+ return { ok: true, match: matches[0] };
86
+ }
87
+ //# sourceMappingURL=system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/query/system.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ import { QuerySystem } from "./system.js";
2
+ import { Citation } from "../cli/coverage.js";
3
+ export interface ElementSummary {
4
+ ref: string;
5
+ kind: string;
6
+ name: string;
7
+ sliceKey: string;
8
+ sliceName: string;
9
+ }
10
+ export type VerbResult<T> = {
11
+ ok: true;
12
+ results: T[];
13
+ } | {
14
+ ok: false;
15
+ error: string;
16
+ };
17
+ export interface ConsumerEntry extends ElementSummary {
18
+ via: string;
19
+ }
20
+ /** Views/reactions (and anything wired by an explicit `arrow`) consuming the named event — its
21
+ * direct out-neighbors in the legal-connection graph, each with the slice it lives in. */
22
+ export declare function queryConsumers(system: QuerySystem, eventRef: string): VerbResult<ConsumerEntry>;
23
+ export interface ProducerEntry extends ElementSummary {
24
+ via: string;
25
+ uiTriggers: ElementSummary[];
26
+ }
27
+ /** Commands producing the named event, each with its own slice and (MIL-168 spec: "+ slices, ui
28
+ * triggers") the `ui` elements that trigger that command in turn. */
29
+ export declare function queryProducers(system: QuerySystem, eventRef: string): VerbResult<ProducerEntry>;
30
+ export interface ClosureEntry extends ElementSummary {
31
+ depth: number;
32
+ via: string;
33
+ }
34
+ /** Transitive closure along legal edges (direction: element -> whatever it feeds) — impact
35
+ * analysis: "what breaks if this changes." */
36
+ export declare function queryDownstream(system: QuerySystem, ofRef: string, depth?: number): VerbResult<ClosureEntry>;
37
+ /** Transitive closure against legal-edge direction — "what feeds this." */
38
+ export declare function queryUpstream(system: QuerySystem, ofRef: string, depth?: number): VerbResult<ClosureEntry>;
39
+ export interface SliceQueryEntry {
40
+ ref: string;
41
+ name: string;
42
+ index: number;
43
+ pattern: string;
44
+ status: string | null;
45
+ personas: string[];
46
+ contexts: string[];
47
+ tags: string[];
48
+ }
49
+ export interface SliceFilters {
50
+ pattern?: string;
51
+ status?: string;
52
+ context?: string;
53
+ persona?: string;
54
+ tag?: string;
55
+ }
56
+ /** The filtered slice list — every filter AND-combines. `pattern`/`status` match the slice's
57
+ * own single value exactly (case-insensitive); `context`/`persona`/`tag` match when ANY
58
+ * element in the slice carries that context/persona/tag key. */
59
+ export declare function querySlices(system: QuerySystem, filters: SliceFilters): VerbResult<SliceQueryEntry>;
60
+ export interface InvariantQueryEntry {
61
+ id: string;
62
+ sliceRef: string;
63
+ sliceName: string;
64
+ docPath: string | null;
65
+ status: string | null;
66
+ citations: Citation[] | null;
67
+ }
68
+ /** The declaring slice + doc facts for one `INV-*` id, and (with `testsDir`) every test file
69
+ * citing it — a single-ID `scanTestCitations()` call, the same word-boundary citation scan
70
+ * `em coverage` uses for every ID in the model, reused rather than reimplemented. An id
71
+ * matching more than one input model is the same "unqualified + multi-model + ambiguous" case
72
+ * every other verb refuses on, listing qualified candidates. */
73
+ export declare function queryInvariant(system: QuerySystem, id: string, testsDir?: string): VerbResult<InvariantQueryEntry>;
74
+ export interface FieldQueryEntry {
75
+ elementRef: string;
76
+ name: string;
77
+ type: string | null;
78
+ tag: boolean;
79
+ assigned: boolean;
80
+ renamedFrom: string[] | null;
81
+ /** The owning element's own `renamed from` chain (event/command only, MIL-68) — provenance
82
+ * has two axes: the field's prior names, and the prior names of the element it lives on. */
83
+ elementRenamedFrom: string[] | null;
84
+ }
85
+ /** One field's facts on the named element — type, identity-tag/system-assigned markers, its
86
+ * `renamed from` chain, and the owning element's. An element that resolves but carries no
87
+ * field of that name is a legitimate empty result (`results: []`), not an error — same "none,
88
+ * exit 0" contract as every other verb's empty answer. */
89
+ export declare function queryField(system: QuerySystem, ofRef: string, name: string): VerbResult<FieldQueryEntry>;
90
+ export interface PathQueryEntry {
91
+ refs: string[];
92
+ edgeKinds: string[];
93
+ length: number;
94
+ }
95
+ /** Shortest directed path from `fromRef` to `toRef` through the legal-connection graph (BFS,
96
+ * unweighted — first-found is shortest by construction; a `view-instance` hop costs nothing
97
+ * and isn't counted in `length`). Endpoints resolving to different models is a legitimate
98
+ * empty result (edges never cross model files — see system.ts's header) rather than an
99
+ * error. */
100
+ export declare function queryPath(system: QuerySystem, fromRef: string, toRef: string): VerbResult<PathQueryEntry>;