@sightmap/sightmap 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,18 +1,30 @@
1
1
  # @sightmap/sightmap
2
2
 
3
- Library and CLI for the [Sightmap spec](https://github.com/sightmap/spec).
3
+ Library and CLI for the Sightmap spec.
4
+
5
+ - **Spec & docs:** [sightmap.org](https://sightmap.org) · [github.com/sightmap/spec](https://github.com/sightmap/spec)
6
+ - **Package:** [`@sightmap/sightmap`](https://www.npmjs.com/package/@sightmap/sightmap) on npm
7
+ - **Source:** [github.com/sightmap/sightmap-js](https://github.com/sightmap/sightmap-js)
4
8
 
5
9
  A Sightmap is a YAML description of a web app's views, components, and API requests — the runtime semantics agents need to navigate and interact with the app. This package gives a coding agent a reliable, machine-readable toolkit for curating the `.sightmap/` directory in a customer's repo.
6
10
 
11
+ ```bash
12
+ npm install -D @sightmap/sightmap
13
+ ```
14
+
7
15
  ## CLI
8
16
 
9
17
  ```bash
10
18
  sightmap validate [path] # default path: .sightmap
11
19
  sightmap lint [path] [--strict] [--rules <list>]
20
+ sightmap check [path] [--stdin] [--level schema|quality] # combined validate + lint
12
21
  sightmap match <url> [path] [--method <m>] [--require-view]
13
22
  sightmap explain <query> [path] [--by name|path] [--type view|component|request] [--require-hit]
23
+ sightmap check-conventions [path] # default path: .
14
24
  ```
15
25
 
26
+ `check` powers the WI-2 advisory hooks: `--stdin` reads YAML from stdin and validates it as a virtual `<stdin>` source. `--level schema` skips lint rules; the default `quality` runs both.
27
+
16
28
  Every command supports `--json` for machine-readable output and `--cwd <dir>` for invocation from outside the project.
17
29
 
18
30
  ### Examples
@@ -52,6 +64,17 @@ $ sightmap explain 'app/(tabs)/index.tsx'
52
64
  component ArcCard matchedAs=path src=app/(tabs)/index.tsx
53
65
  ```
54
66
 
67
+ ### `sightmap check-conventions [path]`
68
+
69
+ Validate that the repo at `<path>` (default `.`) follows the [filename conventions](https://github.com/sightmap/spec/blob/main/docs/repo-conventions.md) for SEPs (`seps/NNNN-{slug}.md`) and conformance fixtures (`conformance/NNN-{slug}.fixture/`). Exit code `0` if clean, `1` if any violations. Pass `--json` for machine-readable output.
70
+
71
+ ```bash
72
+ sightmap check-conventions .
73
+ sightmap check-conventions /path/to/repo --json
74
+ ```
75
+
76
+ Unlike `validate`/`lint`/`match`/`explain`, this command takes a *repo root*, not a `.sightmap/` directory. It only inspects `seps/` and `conformance/`; everything else (including `spec/`, `docs/`, and top-level project files) is ignored.
77
+
55
78
  ### Exit codes
56
79
 
57
80
  - `0` — success
@@ -103,6 +126,8 @@ const issues = await lint(sightmap, { root: process.cwd() });
103
126
 
104
127
  `parse()` throws on invalid input; `validate()` returns a non-throwing `Result`. **Downstream packages should consume `validate()`,** not `parse()`. `parse()` is sugar for end-user code that knows the input is valid.
105
128
 
129
+ For the canonical spec semantics — schema fields, route glob syntax, scope rules, and the design rationale — see [sightmap.org](https://sightmap.org) and [`spec/v1/schema.md`](https://github.com/sightmap/spec/blob/main/spec/v1/schema.md) in the spec repo.
130
+
106
131
  ### Diagnostics
107
132
 
108
133
  Lint and validate share a `Diagnostic` shape:
@@ -133,6 +158,10 @@ Codes are stable, kebab-case, and exported as constants:
133
158
  | `route-shadowing` | warning | `lint` |
134
159
  | `unknown-source` | warning | `lint` |
135
160
  | `selector-syntax` | warning | `lint` |
161
+ | `convention.sep-filename` | error | `check-conventions` |
162
+ | `convention.fixture-dirname` | error | `check-conventions` |
163
+ | `convention.invalid-slug` | error | `check-conventions` |
164
+ | `convention.unexpected-file` | error | `check-conventions` |
136
165
 
137
166
  Renames require an SEP. Future SDK ports (Python, Go) MUST emit identical codes for the same conditions.
138
167
 
@@ -0,0 +1,253 @@
1
+ /**
2
+ * File-level memory entries — surfaced whenever any definition from this file is active.
3
+ */
4
+ type Memory = string[];
5
+ /**
6
+ * Freeform notes surfaced to agents at runtime in the [Guide] section.
7
+ */
8
+ type Memory1 = string[];
9
+ /**
10
+ * A CSS selector, or a list of alternative selectors tried in order.
11
+ */
12
+ type Selector = string | [string, ...string[]];
13
+ /**
14
+ * A sightmap file: one YAML document under .sightmap/ describing views, components, requests, and memory entries. See https://github.com/sightmap/spec/blob/main/spec/v1/schema.md for the human-readable reference.
15
+ */
16
+ interface SightmapV1 {
17
+ /**
18
+ * Must be 1 for files that conform to this version of the spec.
19
+ */
20
+ version: 1;
21
+ memory?: Memory;
22
+ views?: View[];
23
+ /**
24
+ * Global components — matched against every view.
25
+ */
26
+ components?: Component[];
27
+ /**
28
+ * Global requests — matched against every view.
29
+ */
30
+ requests?: Request[];
31
+ }
32
+ interface View {
33
+ name: string;
34
+ /**
35
+ * Glob pattern matched against the URL pathname. * matches one segment, ** matches any depth.
36
+ */
37
+ route: string;
38
+ description?: string;
39
+ source?: string;
40
+ memory?: Memory1;
41
+ components?: Component[];
42
+ requests?: Request[];
43
+ }
44
+ interface Component {
45
+ name: string;
46
+ selector: Selector;
47
+ source?: string;
48
+ description?: string;
49
+ memory?: Memory1;
50
+ /**
51
+ * Nested components. Selectors are scoped to the parent's matched subtree.
52
+ */
53
+ children?: Component[];
54
+ }
55
+ interface Request {
56
+ name: string;
57
+ /**
58
+ * Glob pattern. Express-style :param segments normalize to *.
59
+ */
60
+ route: string;
61
+ /**
62
+ * HTTP method filter. Case-insensitive; commonly GET, POST, PUT, PATCH, DELETE.
63
+ */
64
+ method?: string;
65
+ description?: string;
66
+ source?: string;
67
+ request?: Payload;
68
+ response?: Payload;
69
+ headers?: string[];
70
+ memory?: Memory1;
71
+ }
72
+ interface Payload {
73
+ fields?: Field[];
74
+ }
75
+ interface Field {
76
+ name: string;
77
+ type?: string;
78
+ description?: string;
79
+ }
80
+
81
+ type Severity = "error" | "warning" | "info";
82
+ interface Diagnostic {
83
+ severity: Severity;
84
+ code: string;
85
+ message: string;
86
+ file?: string;
87
+ path?: string;
88
+ loc?: {
89
+ line: number;
90
+ column: number;
91
+ };
92
+ source?: string;
93
+ }
94
+ declare const PARSE_ERROR = "parse-error";
95
+ declare const SCHEMA_VALIDATION_FAILED = "schema-validation-failed";
96
+ declare const UNKNOWN_VERSION = "unknown-version";
97
+ declare const MERGE_COLLISION_VIEW = "merge-collision-view";
98
+ declare const MERGE_COLLISION_COMPONENT = "merge-collision-component";
99
+ declare const DUPLICATE_VIEW_NAME = "duplicate-view-name";
100
+ declare const DUPLICATE_ROUTE = "duplicate-route";
101
+ declare const ROUTE_SHADOWING = "route-shadowing";
102
+ declare const UNKNOWN_SOURCE = "unknown-source";
103
+ declare const SELECTOR_SYNTAX = "selector-syntax";
104
+
105
+ /**
106
+ * A single-file fragment after parsing and selector normalization.
107
+ * Branded to prevent querying an unmerged fragment.
108
+ */
109
+ interface SightmapFragment extends SightmapV1 {
110
+ readonly __brand: "SightmapFragment";
111
+ readonly __sourceFile?: string;
112
+ }
113
+ /**
114
+ * A merged, queryable sightmap produced by `merge()` or `loadDirectory()`.
115
+ */
116
+ interface Sightmap {
117
+ readonly version: 1;
118
+ readonly views: readonly View[];
119
+ readonly globalComponents: readonly Component[];
120
+ readonly globalRequests: readonly Request[];
121
+ readonly fileMemory: readonly {
122
+ memory: string[];
123
+ sourceFile: string;
124
+ }[];
125
+ /**
126
+ * Diagnostics produced during merge (e.g. collisions, duplicate names across files).
127
+ * Lint diagnostics are returned separately by `lint()`.
128
+ */
129
+ readonly diagnostics: readonly Diagnostic[];
130
+ readonly __brand: "Sightmap";
131
+ }
132
+ interface ResolvedView {
133
+ name: string;
134
+ route: string;
135
+ source?: string;
136
+ description?: string;
137
+ memory: string[];
138
+ definedIn: {
139
+ file: string;
140
+ line?: number;
141
+ };
142
+ }
143
+ interface ResolvedComponent {
144
+ name: string;
145
+ selector: string[];
146
+ source?: string;
147
+ description?: string;
148
+ memory: string[];
149
+ parentChain: string[];
150
+ scope: "global" | "view-scoped";
151
+ /** Present iff `scope === "view-scoped"`. */
152
+ scopedToView?: string;
153
+ definedIn: {
154
+ file: string;
155
+ line?: number;
156
+ };
157
+ }
158
+ interface ResolvedRequest {
159
+ name: string;
160
+ route: string;
161
+ method?: string;
162
+ source?: string;
163
+ description?: string;
164
+ request?: {
165
+ fields: readonly Field[];
166
+ };
167
+ response?: {
168
+ fields: readonly Field[];
169
+ };
170
+ headers?: readonly string[];
171
+ memory: string[];
172
+ definedIn: {
173
+ file: string;
174
+ line?: number;
175
+ };
176
+ }
177
+ interface MatchResult {
178
+ view: ResolvedView | null;
179
+ components: ResolvedComponent[];
180
+ requests: ResolvedRequest[];
181
+ memory: string[];
182
+ }
183
+ type ExplainMatchedAs = "name" | "path" | "name-and-path";
184
+ /**
185
+ * Discriminated union: narrowing on `type` automatically narrows `entry`.
186
+ * if (hit.type === "view") { hit.entry.route; } // no cast needed
187
+ */
188
+ type ExplainHit = {
189
+ type: "view";
190
+ matchedAs: ExplainMatchedAs;
191
+ entry: ResolvedView;
192
+ } | {
193
+ type: "component";
194
+ matchedAs: ExplainMatchedAs;
195
+ entry: ResolvedComponent;
196
+ } | {
197
+ type: "request";
198
+ matchedAs: ExplainMatchedAs;
199
+ entry: ResolvedRequest;
200
+ };
201
+ interface ExplainResult {
202
+ query: string;
203
+ hits: ExplainHit[];
204
+ }
205
+ /** Result returned by validate() — never throws. */
206
+ type ValidateResult = {
207
+ ok: true;
208
+ value: SightmapFragment;
209
+ } | {
210
+ ok: false;
211
+ diagnostics: readonly Diagnostic[];
212
+ };
213
+
214
+ interface ParseOptions {
215
+ /** Optional source file path; recorded on the fragment for canonical-order merging. */
216
+ sourceFile?: string;
217
+ }
218
+ /**
219
+ * Parse a single sightmap file (YAML string or pre-parsed object).
220
+ * Throws on parse error, missing version, or version mismatch.
221
+ * Normalizes `selector: string` to `selector: [string]` everywhere.
222
+ */
223
+ declare function parse(input: string | object, opts?: ParseOptions): SightmapFragment;
224
+
225
+ /**
226
+ * Merge fragments into a queryable Sightmap.
227
+ *
228
+ * Canonical order: fragments are sorted by `__sourceFile` (code-point order; locale-independent
229
+ * for cross-environment determinism). Fragments without a `__sourceFile` sort first, and ES2019
230
+ * sort stability preserves their input order. Within each fragment, declaration order is
231
+ * preserved. The merged collection's order = (sourceFile order, then declaration order).
232
+ *
233
+ * Duplicate view names produce `merge-collision-view` warnings; duplicate global component
234
+ * names produce `merge-collision-component`. The first occurrence wins.
235
+ *
236
+ * Returned arrays are fresh, but element objects (View/Component/Request) are shared with
237
+ * the input fragments — callers must not mutate them.
238
+ */
239
+ declare function merge(fragments: SightmapFragment[]): Sightmap;
240
+
241
+ interface MatchOptions {
242
+ url: string;
243
+ method?: string;
244
+ }
245
+ declare function match(sightmap: Sightmap, opts: MatchOptions): MatchResult;
246
+
247
+ interface ExplainOptions {
248
+ by?: "name" | "path";
249
+ type?: "view" | "component" | "request";
250
+ }
251
+ declare function explain(sightmap: Sightmap, query: string, opts?: ExplainOptions): ExplainResult;
252
+
253
+ export { type Component as C, type Diagnostic as D, type ExplainHit as E, type Field as F, MERGE_COLLISION_COMPONENT as M, PARSE_ERROR as P, ROUTE_SHADOWING as R, type Sightmap as S, UNKNOWN_SOURCE as U, type ValidateResult as V, type SightmapV1 as a, DUPLICATE_ROUTE as b, DUPLICATE_VIEW_NAME as c, type ExplainMatchedAs as d, type ExplainResult as e, MERGE_COLLISION_VIEW as f, type MatchResult as g, type Request as h, type ResolvedComponent as i, type ResolvedRequest as j, type ResolvedView as k, SCHEMA_VALIDATION_FAILED as l, SELECTOR_SYNTAX as m, type Severity as n, type SightmapFragment as o, UNKNOWN_VERSION as p, type View as q, explain as r, match as s, merge as t, parse as u };
@@ -0,0 +1 @@
1
+ export { E as ExplainHit, d as ExplainMatchedAs, e as ExplainResult, g as MatchResult, i as ResolvedComponent, j as ResolvedRequest, k as ResolvedView, S as Sightmap, o as SightmapFragment, r as explain, s as match, t as merge, u as parse } from './browser-ChD_xQt8.js';
@@ -0,0 +1,353 @@
1
+ // src/parse.ts
2
+ import yaml from "js-yaml";
3
+ function parse(input, opts = {}) {
4
+ let doc;
5
+ if (typeof input === "string") {
6
+ try {
7
+ doc = yaml.load(input);
8
+ } catch (err) {
9
+ const msg = err instanceof Error ? err.message : String(err);
10
+ throw new Error(`YAML parse error: ${msg}`);
11
+ }
12
+ } else {
13
+ doc = { ...input };
14
+ }
15
+ if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
16
+ throw new Error("Expected sightmap document to be an object at the root");
17
+ }
18
+ const obj = doc;
19
+ if (obj["version"] === void 0) {
20
+ throw new Error("Missing required `version` field");
21
+ }
22
+ if (obj["version"] !== 1) {
23
+ throw new Error(`Unsupported version: ${String(obj["version"])} (expected 1)`);
24
+ }
25
+ if (Array.isArray(obj["components"])) {
26
+ obj["components"] = obj["components"].map(normalizeComponent);
27
+ }
28
+ if (Array.isArray(obj["views"])) {
29
+ obj["views"] = obj["views"].map((v) => {
30
+ const out = { ...v };
31
+ if (Array.isArray(out["components"])) {
32
+ out["components"] = out["components"].map(normalizeComponent);
33
+ }
34
+ return out;
35
+ });
36
+ }
37
+ const fragment = {
38
+ ...obj,
39
+ __brand: "SightmapFragment",
40
+ ...opts.sourceFile !== void 0 ? { __sourceFile: opts.sourceFile } : {}
41
+ };
42
+ return fragment;
43
+ }
44
+ function normalizeComponent(c) {
45
+ if (c["selectors"] !== void 0 && c.selector === void 0) {
46
+ const name = c.name ?? "(unnamed)";
47
+ throw new Error(
48
+ `Component "${name}": use \`selector\` (singular), not \`selectors\` (plural). \`selector\` accepts either a single string or an array of strings.`
49
+ );
50
+ }
51
+ const sel = c.selector;
52
+ const normalized = {
53
+ ...c,
54
+ selector: typeof sel === "string" ? [sel] : sel
55
+ };
56
+ if (Array.isArray(c.children)) {
57
+ normalized.children = c.children.map(normalizeComponent);
58
+ }
59
+ return normalized;
60
+ }
61
+
62
+ // src/diagnostics.ts
63
+ var MERGE_COLLISION_VIEW = "merge-collision-view";
64
+ var MERGE_COLLISION_COMPONENT = "merge-collision-component";
65
+
66
+ // src/merge.ts
67
+ function merge(fragments) {
68
+ const sorted = [...fragments].sort((a, b) => {
69
+ const aFile = a.__sourceFile ?? "";
70
+ const bFile = b.__sourceFile ?? "";
71
+ return aFile < bFile ? -1 : aFile > bFile ? 1 : 0;
72
+ });
73
+ const views = [];
74
+ const globalComponents = [];
75
+ const globalRequests = [];
76
+ const fileMemory = [];
77
+ const diagnostics = [];
78
+ const seenViewNames = /* @__PURE__ */ new Map();
79
+ const seenComponentNames = /* @__PURE__ */ new Map();
80
+ for (const f of sorted) {
81
+ const file = f.__sourceFile ?? "<unknown>";
82
+ for (const v of f.views ?? []) {
83
+ const prev = seenViewNames.get(v.name);
84
+ if (prev !== void 0) {
85
+ diagnostics.push({
86
+ severity: "warning",
87
+ code: MERGE_COLLISION_VIEW,
88
+ message: `View name "${v.name}" defined in both "${prev}" and "${file}"; first occurrence wins.`,
89
+ file
90
+ });
91
+ } else {
92
+ seenViewNames.set(v.name, file);
93
+ }
94
+ views.push(v);
95
+ }
96
+ for (const c of f.components ?? []) {
97
+ const prev = seenComponentNames.get(c.name);
98
+ if (prev !== void 0) {
99
+ diagnostics.push({
100
+ severity: "warning",
101
+ code: MERGE_COLLISION_COMPONENT,
102
+ message: `Component name "${c.name}" defined in both "${prev}" and "${file}"; first occurrence wins.`,
103
+ file
104
+ });
105
+ } else {
106
+ seenComponentNames.set(c.name, file);
107
+ }
108
+ globalComponents.push(c);
109
+ }
110
+ for (const r of f.requests ?? []) {
111
+ globalRequests.push(r);
112
+ }
113
+ if (Array.isArray(f.memory) && f.memory.length > 0) {
114
+ fileMemory.push({ memory: [...f.memory], sourceFile: file });
115
+ }
116
+ }
117
+ return {
118
+ version: 1,
119
+ views,
120
+ globalComponents,
121
+ globalRequests,
122
+ fileMemory,
123
+ diagnostics,
124
+ __brand: "Sightmap"
125
+ };
126
+ }
127
+
128
+ // src/routeMatch.ts
129
+ function canonicalizeUrl(input) {
130
+ let s = input;
131
+ const protoMatch = /^[a-z][a-z0-9+.-]*:\/\/[^/]*/i.exec(s);
132
+ if (protoMatch) {
133
+ s = s.slice(protoMatch[0].length) || "/";
134
+ }
135
+ const hash = s.indexOf("#");
136
+ if (hash !== -1) s = s.slice(0, hash);
137
+ const q = s.indexOf("?");
138
+ if (q !== -1) s = s.slice(0, q);
139
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
140
+ return s;
141
+ }
142
+ function routeMatch(pattern, url) {
143
+ const p = normalizePattern(pattern);
144
+ const u = canonicalizeUrl(url);
145
+ const patternSegs = splitSegments(p);
146
+ const urlSegs = splitSegments(u);
147
+ return matchSegs(patternSegs, urlSegs);
148
+ }
149
+ function normalizePattern(p) {
150
+ let s = p.split("/").map((seg) => seg.startsWith(":") ? "*" : seg).join("/");
151
+ if (s.length > 1 && s.endsWith("/")) s = s.slice(0, -1);
152
+ return s;
153
+ }
154
+ function splitSegments(path) {
155
+ if (path === "/" || path === "") return [];
156
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
157
+ return trimmed.split("/");
158
+ }
159
+ function matchSegs(pattern, url) {
160
+ if (pattern.length === 0 && url.length === 0) return true;
161
+ if (pattern.length === 0) return false;
162
+ const head = pattern[0];
163
+ const rest = pattern.slice(1);
164
+ if (head === "**") {
165
+ if (rest.length === 0) return true;
166
+ for (let i = 0; i <= url.length; i++) {
167
+ if (matchSegs(rest, url.slice(i))) return true;
168
+ }
169
+ return false;
170
+ }
171
+ if (url.length === 0) return false;
172
+ if (head === "*" || head === url[0]) {
173
+ return matchSegs(rest, url.slice(1));
174
+ }
175
+ return false;
176
+ }
177
+
178
+ // src/resolver.ts
179
+ function resolveByUrl(sightmap, url, method) {
180
+ let matchedView = null;
181
+ for (const v of sightmap.views) {
182
+ if (routeMatch(v.route, url)) {
183
+ matchedView = v;
184
+ break;
185
+ }
186
+ }
187
+ const components = [];
188
+ for (const c of sightmap.globalComponents) {
189
+ components.push(...flattenComponent(c, [], "global", void 0));
190
+ }
191
+ if (matchedView !== null && Array.isArray(matchedView.components)) {
192
+ for (const c of matchedView.components) {
193
+ components.push(...flattenComponent(c, [], "view-scoped", matchedView.name));
194
+ }
195
+ }
196
+ const requests = [];
197
+ const requestPool = [
198
+ ...sightmap.globalRequests,
199
+ ...matchedView?.requests ?? []
200
+ ];
201
+ for (const req of requestPool) {
202
+ if (!routeMatch(req.route, url)) continue;
203
+ if (method !== void 0 && req.method !== void 0 && req.method !== method) continue;
204
+ requests.push(toResolvedRequest(req));
205
+ }
206
+ const memory = [];
207
+ for (const fm of sightmap.fileMemory) memory.push(...fm.memory);
208
+ if (matchedView?.memory) memory.push(...matchedView.memory);
209
+ return {
210
+ view: matchedView !== null ? toResolvedView(matchedView) : null,
211
+ components,
212
+ requests,
213
+ memory
214
+ };
215
+ }
216
+ function resolveByName(sightmap, name) {
217
+ const hits = [];
218
+ for (const v of sightmap.views) {
219
+ if (v.name === name) hits.push({ type: "view", matchedAs: "name", entry: toResolvedView(v) });
220
+ }
221
+ for (const c of sightmap.globalComponents) {
222
+ for (const rc of flattenComponent(c, [], "global", void 0)) {
223
+ if (rc.name === name) hits.push({ type: "component", matchedAs: "name", entry: rc });
224
+ }
225
+ }
226
+ for (const v of sightmap.views) {
227
+ for (const c of v.components ?? []) {
228
+ for (const rc of flattenComponent(c, [], "view-scoped", v.name)) {
229
+ if (rc.name === name) hits.push({ type: "component", matchedAs: "name", entry: rc });
230
+ }
231
+ }
232
+ }
233
+ for (const r of sightmap.globalRequests) {
234
+ if (r.name === name) hits.push({ type: "request", matchedAs: "name", entry: toResolvedRequest(r) });
235
+ }
236
+ for (const v of sightmap.views) {
237
+ for (const r of v.requests ?? []) {
238
+ if (r.name === name) hits.push({ type: "request", matchedAs: "name", entry: toResolvedRequest(r) });
239
+ }
240
+ }
241
+ return hits;
242
+ }
243
+ function resolveBySourcePath(sightmap, path) {
244
+ const hits = [];
245
+ for (const v of sightmap.views) {
246
+ if (v.source === path) hits.push({ type: "view", matchedAs: "path", entry: toResolvedView(v) });
247
+ }
248
+ for (const c of sightmap.globalComponents) {
249
+ for (const rc of flattenComponent(c, [], "global", void 0)) {
250
+ if (rc.source === path) hits.push({ type: "component", matchedAs: "path", entry: rc });
251
+ }
252
+ }
253
+ for (const v of sightmap.views) {
254
+ for (const c of v.components ?? []) {
255
+ for (const rc of flattenComponent(c, [], "view-scoped", v.name)) {
256
+ if (rc.source === path) hits.push({ type: "component", matchedAs: "path", entry: rc });
257
+ }
258
+ }
259
+ }
260
+ for (const r of sightmap.globalRequests) {
261
+ if (r.source === path) hits.push({ type: "request", matchedAs: "path", entry: toResolvedRequest(r) });
262
+ }
263
+ return hits;
264
+ }
265
+ function flattenComponent(c, parentChain, scope, scopedToView) {
266
+ const out = [];
267
+ const selector = Array.isArray(c.selector) ? c.selector : [c.selector];
268
+ out.push({
269
+ name: c.name,
270
+ selector,
271
+ ...c.source !== void 0 ? { source: c.source } : {},
272
+ ...c.description !== void 0 ? { description: c.description } : {},
273
+ memory: c.memory ? [...c.memory] : [],
274
+ parentChain: [...parentChain],
275
+ scope,
276
+ ...scopedToView !== void 0 ? { scopedToView } : {},
277
+ definedIn: { file: "<unknown>" }
278
+ });
279
+ for (const child of c.children ?? []) {
280
+ out.push(...flattenComponent(child, [...parentChain, c.name], scope, scopedToView));
281
+ }
282
+ return out;
283
+ }
284
+ function toResolvedView(v) {
285
+ return {
286
+ name: v.name,
287
+ route: v.route,
288
+ ...v.source !== void 0 ? { source: v.source } : {},
289
+ ...v.description !== void 0 ? { description: v.description } : {},
290
+ memory: v.memory ? [...v.memory] : [],
291
+ definedIn: { file: "<unknown>" }
292
+ };
293
+ }
294
+ function toResolvedRequest(r) {
295
+ return {
296
+ name: r.name,
297
+ route: r.route,
298
+ ...r.method !== void 0 ? { method: r.method } : {},
299
+ ...r.source !== void 0 ? { source: r.source } : {},
300
+ ...r.description !== void 0 ? { description: r.description } : {},
301
+ ...r.request !== void 0 ? { request: { fields: r.request.fields ?? [] } } : {},
302
+ ...r.response !== void 0 ? { response: { fields: r.response.fields ?? [] } } : {},
303
+ ...r.headers !== void 0 ? { headers: r.headers } : {},
304
+ memory: r.memory ? [...r.memory] : [],
305
+ definedIn: { file: "<unknown>" }
306
+ };
307
+ }
308
+
309
+ // src/match.ts
310
+ function match(sightmap, opts) {
311
+ return resolveByUrl(sightmap, opts.url, opts.method);
312
+ }
313
+
314
+ // src/explain.ts
315
+ function explain(sightmap, query, opts = {}) {
316
+ const byName = opts.by === "path" ? [] : resolveByName(sightmap, query);
317
+ const byPath = opts.by === "name" ? [] : resolveBySourcePath(sightmap, query);
318
+ const key = (h) => `${h.type}:${h.entry.name}`;
319
+ const namedKeys = new Set(byName.map(key));
320
+ const pathKeys = new Set(byPath.map(key));
321
+ const merged = [];
322
+ for (const h of byName) {
323
+ if (pathKeys.has(key(h))) {
324
+ merged.push(withMatchedAs(h, "name-and-path"));
325
+ } else {
326
+ merged.push(h);
327
+ }
328
+ }
329
+ for (const h of byPath) {
330
+ if (!namedKeys.has(key(h))) {
331
+ merged.push(h);
332
+ }
333
+ }
334
+ const filtered = opts.type !== void 0 ? merged.filter((h) => h.type === opts.type) : merged;
335
+ return { query, hits: filtered };
336
+ }
337
+ function withMatchedAs(h, matchedAs) {
338
+ switch (h.type) {
339
+ case "view":
340
+ return { type: "view", matchedAs, entry: h.entry };
341
+ case "component":
342
+ return { type: "component", matchedAs, entry: h.entry };
343
+ case "request":
344
+ return { type: "request", matchedAs, entry: h.entry };
345
+ }
346
+ }
347
+ export {
348
+ explain,
349
+ match,
350
+ merge,
351
+ parse
352
+ };
353
+ //# sourceMappingURL=browser.js.map