@ifc-lite/cli 0.19.0 → 0.21.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.
@@ -0,0 +1,324 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * `ifc-lite gym (--model <file.ifc> | --seed <n>) [--checks schema,clash,ids] [--ids <rules.xml>]`
6
+ *
7
+ * A reset/step/reward environment loop over the existing headless checks:
8
+ * the skeleton of an RLVR environment for buildings (docs/vision/moonshots-tech.md
9
+ * M2, docs/vision/moonshots-execution-plan.md B0.4). The environment wraps a
10
+ * model - either a fixed file (`--model`) or a procedurally generated World
11
+ * Gym episode (`--seed`, see `gym/episode.ts`) - and lets an agent apply
12
+ * data-mutation ops, scoring each step against the same schema/clash/ids
13
+ * checks the `validate`, `clash`, and `ids` commands already run.
14
+ * Geometry-creating ops are out of scope for v0, see "op vocabulary gaps"
15
+ * below.
16
+ *
17
+ * Protocol (newline-delimited JSON on stdout, one JSON command per stdin line):
18
+ *
19
+ * -> (on start) {"type":"reset","observation":{...},"channels":{...}}
20
+ * <- {"type":"step","ops":[{"op":"setProperty","expressId":42,"psetName":"Pset_WallCommon","propName":"IsExternal","value":true}]}
21
+ * -> {"type":"reward","channels":{...},"done":false}
22
+ * <- {"type":"reset"}
23
+ * -> {"type":"reset","observation":{...},"channels":{...}}
24
+ * <- {"type":"close"}
25
+ * -> (process exits 0, no reply line)
26
+ *
27
+ * Malformed input never crashes the process: it replies with a structured
28
+ * {"type":"error","message":"..."} line and keeps reading. A `step` batch is
29
+ * ATOMIC: it either fully applies (one `reward` line) or leaves the session
30
+ * exactly as it was (one `error` line) - a malformed op mid-batch never
31
+ * leaves earlier ops of the same batch applied.
32
+ *
33
+ * Episode factory (B2.2): instead of `--model`, `--seed <n>` generates a
34
+ * World Gym benchmark model in-process (tools/world-gym/generator.mjs,
35
+ * dynamically imported from the repo checkout - the published npm package
36
+ * does not ship the generator, so `--seed` fails there with a clear error
37
+ * while `--model` keeps working). `[--family frame|office|auto]` pins the
38
+ * family; corruption follows the benchmark's deterministic Bernoulli draw at
39
+ * the spec's corrupt rate (tools/world-gym/benchmark/splits.mjs) unless
40
+ * `--corrupt` / `--no-corrupt` forces it or `--corrupt-rate <p>` overrides
41
+ * the rate (forcing and a rate are mutually exclusive). Mid-session,
42
+ * `{"type":"reset","seed":8}` (plus optional "family"/"corrupt"/"corruptRate"
43
+ * fields) swaps to a fresh generated episode, so an RL consumer can stream
44
+ * the whole benchmark through one gym process without touching generator
45
+ * internals. Generated-episode reset lines carry an extra `episode` field:
46
+ * {seed, family, corrupted}. (The `corrupted` flag is deliberately exposed:
47
+ * the gym is the TRAINING surface; benchmark ground truth is regenerable
48
+ * from the seed anyway - see tools/world-gym/benchmark/BENCHMARK.md.)
49
+ *
50
+ * Determinism: the same model plus the same op sequence must yield
51
+ * byte-identical reward lines - see `gym/channels.ts` for the sorting and
52
+ * rounding contract. Reward shaping: every channel's `score` is in [0, 1]
53
+ * with higher-is-better (the clash channel scores 1 for clash-free and
54
+ * decreases as the clash count grows; the raw count is `totalClashes`).
55
+ *
56
+ * Mutation surface: v0 supports `setProperty` / `setAttribute` /
57
+ * `deleteProperty`, mirroring `bim.mutate`'s method names exactly (see
58
+ * `gym/ops.ts`). Ops are applied via `MutablePropertyView` + `StepExporter`
59
+ * (the same classes `ifc-lite mutate` uses) rather than through `bim.mutate`
60
+ * itself, because `HeadlessBackend.mutate` (packages/cli/src/headless-backend.ts)
61
+ * is currently a no-op stub: `bim.mutate.setProperty()` silently does
62
+ * nothing in headless mode. Wiring that backend up to the same
63
+ * MutablePropertyView this file drives is a mechanical follow-up, not a
64
+ * redesign, since the op vocabulary already matches.
65
+ *
66
+ * Each step re-exports the accumulated mutation overlay to STEP text and
67
+ * re-parses it into a fresh `IfcDataStore` before running checks. This is
68
+ * deliberate, not a perf shortcut we forgot to remove: `ids`/`clash`/the
69
+ * schema checks all read directly from an `IfcDataStore` (via
70
+ * `createDataAccessor(store)` / `EntityNode(store, id)`), with no
71
+ * mutation-overlay awareness, so a live overlay would silently be invisible
72
+ * to every check. Re-parsing after export guarantees the checks see exactly
73
+ * what a human re-opening the mutated file would see.
74
+ *
75
+ * Op vocabulary gaps (v0, tracked for a follow-up bet, not fixed here):
76
+ * - Ops target entities by `expressId`, not `GlobalId`. Stable within one
77
+ * gym session (StepExporter never renumbers existing entities), but a
78
+ * GlobalId-keyed op format would be more robust for agents that only
79
+ * ever see a model's IFC-standard identifiers.
80
+ * - `observation.bounds` is always `null`: no geometry pass runs on
81
+ * `reset`, and even the `clash` channel's mesh pass never feeds a
82
+ * bounding box back into the observation.
83
+ * - No entity-creation ops (new walls/slabs/etc. via `packages/create`) and
84
+ * no episode-termination signal (`done` is always `false`); both need a
85
+ * real reward-shaping design, not just wiring.
86
+ * - `bim.mutate.setProperty/setAttribute/deleteProperty` are no-ops on
87
+ * `HeadlessBackend` (see above); this file works around that directly
88
+ * rather than fixing the backend, to keep this bet's diff scoped to one
89
+ * new command.
90
+ */
91
+ import { createInterface } from 'node:readline';
92
+ import { readFile } from 'node:fs/promises';
93
+ import { basename } from 'node:path';
94
+ import { loadIfcFile, loadIfcBytes } from '../loader.js';
95
+ import { getFlag, fatal } from '../output.js';
96
+ import { extractPropertiesOnDemand } from '@ifc-lite/parser';
97
+ import { MutablePropertyView } from '@ifc-lite/mutations';
98
+ import { StepExporter } from '@ifc-lite/export';
99
+ import { GeometryProcessor } from '@ifc-lite/geometry';
100
+ import { IDSNamespace } from '@ifc-lite/sdk';
101
+ import { KNOWN_CHECKS, computeSchemaChannel, computeClashChannel, computeIdsChannel, computeObservation, } from './gym/channels.js';
102
+ import { parseOp, applyOp } from './gym/ops.js';
103
+ import { generateEpisode, parseSeed, parseCorruptRate, } from './gym/episode.js';
104
+ const USAGE = 'Usage: ifc-lite gym (--model <file.ifc> | --seed <n> [--family frame|office|auto] [--corrupt|--no-corrupt|--corrupt-rate <p>]) [--checks schema,clash,ids] [--ids <rules.xml>] [--locale en|de|fr]';
105
+ const SUPPORTED_LOCALES = ['en', 'de', 'fr'];
106
+ function parseChecks(raw, idsPath) {
107
+ if (raw === undefined) {
108
+ return idsPath ? new Set(['schema', 'clash', 'ids']) : new Set(['schema', 'clash']);
109
+ }
110
+ const result = new Set();
111
+ for (const part of raw.split(',').map(s => s.trim()).filter(Boolean)) {
112
+ if (!KNOWN_CHECKS.includes(part)) {
113
+ fatal(`Unknown check "${part}" in --checks (supported: ${KNOWN_CHECKS.join(', ')})`);
114
+ }
115
+ result.add(part);
116
+ }
117
+ return result;
118
+ }
119
+ function parseLocale(raw) {
120
+ if (raw === undefined)
121
+ return 'en';
122
+ if (!SUPPORTED_LOCALES.includes(raw)) {
123
+ fatal(`Unsupported --locale "${raw}" (supported: ${SUPPORTED_LOCALES.join(', ')})`);
124
+ }
125
+ return raw;
126
+ }
127
+ export async function gymCommand(args, io = {}) {
128
+ const modelPath = getFlag(args, '--model');
129
+ const seedFlag = getFlag(args, '--seed');
130
+ if (!modelPath && seedFlag === undefined)
131
+ fatal(USAGE);
132
+ if (modelPath && seedFlag !== undefined)
133
+ fatal(`--model and --seed are mutually exclusive\n${USAGE}`);
134
+ const idsPath = getFlag(args, '--ids');
135
+ const checks = parseChecks(getFlag(args, '--checks'), idsPath);
136
+ const locale = parseLocale(getFlag(args, '--locale'));
137
+ const output = io.output ?? process.stdout;
138
+ const input = io.input ?? process.stdin;
139
+ function send(msg) {
140
+ output.write(`${JSON.stringify(msg)}\n`);
141
+ }
142
+ let modelId;
143
+ let originalStore;
144
+ let episode = null;
145
+ async function loadEpisode(spec) {
146
+ const { model, episode: info } = await generateEpisode(spec);
147
+ originalStore = await loadIfcBytes(new TextEncoder().encode(model.content), `gym-seed-${info.seed}.ifc`);
148
+ episode = info;
149
+ modelId = `gym-seed-${info.seed}.ifc`;
150
+ }
151
+ if (modelPath) {
152
+ modelId = basename(modelPath);
153
+ originalStore = await loadIfcFile(modelPath);
154
+ }
155
+ else {
156
+ const forceCorrupt = args.includes('--corrupt') ? true : args.includes('--no-corrupt') ? false : undefined;
157
+ const corruptRateFlag = getFlag(args, '--corrupt-rate');
158
+ if (forceCorrupt !== undefined && corruptRateFlag !== undefined) {
159
+ fatal(`--corrupt/--no-corrupt and --corrupt-rate are mutually exclusive\n${USAGE}`);
160
+ }
161
+ await loadEpisode({
162
+ seed: parseSeed(seedFlag, '--seed'),
163
+ family: getFlag(args, '--family') ?? 'auto',
164
+ forceCorrupt,
165
+ corruptRate: corruptRateFlag !== undefined ? parseCorruptRate(corruptRateFlag, '--corrupt-rate') : undefined,
166
+ });
167
+ }
168
+ const ids = new IDSNamespace();
169
+ let idsDoc = null;
170
+ if (idsPath) {
171
+ const idsXml = await readFile(idsPath, 'utf-8');
172
+ idsDoc = await ids.parse(idsXml);
173
+ }
174
+ // Lazily initialised: wasm geometry init is only worth paying for when
175
+ // the "clash" channel is actually requested.
176
+ let processor = null;
177
+ async function getProcessor() {
178
+ if (!processor) {
179
+ // Assign only after init() succeeds: caching the instance before a
180
+ // failed init would hand every later call an uninitialized processor
181
+ // instead of retrying (the init throw itself surfaces as a structured
182
+ // error line on the step that requested the clash channel).
183
+ const p = new GeometryProcessor();
184
+ await p.init();
185
+ processor = p;
186
+ }
187
+ return processor;
188
+ }
189
+ function createMutationView() {
190
+ const view = new MutablePropertyView(null, 'default');
191
+ view.setOnDemandExtractor((entityId) => extractPropertiesOnDemand(originalStore, entityId));
192
+ return view;
193
+ }
194
+ /**
195
+ * The committed op journal: every op of every successfully rewarded step,
196
+ * in order. `step` batches are atomic - a failing batch (malformed op,
197
+ * export/parse failure, channel failure) must leave the session exactly as
198
+ * it was - so the journal is the single source of truth and the view is
199
+ * rebuilt from it whenever a batch fails partway through.
200
+ */
201
+ let journal = [];
202
+ let mutationView = createMutationView();
203
+ function rebuildViewFromJournal() {
204
+ mutationView = createMutationView();
205
+ for (const op of journal)
206
+ applyOp(mutationView, op);
207
+ }
208
+ /**
209
+ * Materialise the current mutation overlay into a fresh, independently
210
+ * parsed store. See the module doc for why re-export + re-parse is
211
+ * required rather than reading through the overlay directly.
212
+ */
213
+ async function materializeStore() {
214
+ const schema = (originalStore.schemaVersion ?? 'IFC4');
215
+ const exporter = new StepExporter(originalStore, mutationView);
216
+ const result = exporter.export({ schema, applyMutations: true });
217
+ return loadIfcBytes(result.content, modelId);
218
+ }
219
+ async function computeChannels(store) {
220
+ const channels = {};
221
+ // Fixed canonical key order regardless of --checks argument order, so
222
+ // two runs requesting the same set in a different order still produce
223
+ // byte-identical JSON.
224
+ if (checks.has('schema'))
225
+ channels.schema = computeSchemaChannel(store);
226
+ if (checks.has('clash'))
227
+ channels.clash = await computeClashChannel(store, await getProcessor(), modelId);
228
+ if (checks.has('ids'))
229
+ channels.ids = await computeIdsChannel(store, ids, idsDoc, locale);
230
+ return channels;
231
+ }
232
+ /** Apply a step batch atomically: on ANY failure, restore the pre-batch state. */
233
+ async function stepBatch(rawOps) {
234
+ // Phase 1: validate the whole batch before anything is applied.
235
+ const ops = rawOps.map(raw => parseOp(raw));
236
+ // Phase 2: apply + materialize + score; roll back to the journal on failure.
237
+ try {
238
+ for (const op of ops)
239
+ applyOp(mutationView, op);
240
+ const store = await materializeStore();
241
+ const channels = await computeChannels(store);
242
+ journal.push(...ops);
243
+ return channels;
244
+ }
245
+ catch (err) {
246
+ rebuildViewFromJournal();
247
+ throw err;
248
+ }
249
+ }
250
+ async function emitReset() {
251
+ journal = [];
252
+ mutationView = createMutationView();
253
+ const observation = computeObservation(originalStore);
254
+ const channels = await computeChannels(originalStore);
255
+ // `episode` only exists for generated episodes; --model resets keep the
256
+ // exact v0 payload shape for backward compatibility.
257
+ send(episode ? { type: 'reset', episode, observation, channels } : { type: 'reset', observation, channels });
258
+ }
259
+ await emitReset();
260
+ const rl = createInterface({ input, terminal: false, crlfDelay: Infinity });
261
+ // A broken pipe on stdout or a stdin failure must end the loop cleanly,
262
+ // not take the process down with an uncaught 'error' emitter event.
263
+ const onStreamError = () => {
264
+ rl.close();
265
+ };
266
+ input.on('error', onStreamError);
267
+ output.on('error', onStreamError);
268
+ for await (const line of rl) {
269
+ const trimmed = line.trim();
270
+ if (!trimmed)
271
+ continue;
272
+ let msg;
273
+ try {
274
+ msg = JSON.parse(trimmed);
275
+ }
276
+ catch (err) {
277
+ send({ type: 'error', message: `Malformed JSON: ${err.message}` });
278
+ continue;
279
+ }
280
+ const type = typeof msg === 'object' && msg !== null ? msg.type : undefined;
281
+ try {
282
+ if (type === 'step') {
283
+ const ops = msg.ops;
284
+ if (!Array.isArray(ops))
285
+ throw new Error('"step" command needs an "ops" array');
286
+ const channels = await stepBatch(ops);
287
+ send({ type: 'reward', channels, done: false });
288
+ }
289
+ else if (type === 'reset') {
290
+ const m = msg;
291
+ if (m.seed !== undefined) {
292
+ // New generated episode over the same protocol (episode factory).
293
+ if (m.family !== undefined && typeof m.family !== 'string') {
294
+ throw new Error('reset field "family" must be a string (frame|office|auto)');
295
+ }
296
+ if (m.corrupt !== undefined && typeof m.corrupt !== 'boolean') {
297
+ throw new Error('reset field "corrupt" must be a boolean');
298
+ }
299
+ if (m.corrupt !== undefined && m.corruptRate !== undefined) {
300
+ throw new Error('reset fields "corrupt" and "corruptRate" are mutually exclusive');
301
+ }
302
+ await loadEpisode({
303
+ seed: parseSeed(m.seed, 'reset field "seed"'),
304
+ family: m.family ?? 'auto',
305
+ forceCorrupt: m.corrupt,
306
+ corruptRate: m.corruptRate !== undefined ? parseCorruptRate(m.corruptRate, 'reset field "corruptRate"') : undefined,
307
+ });
308
+ }
309
+ await emitReset();
310
+ }
311
+ else if (type === 'close') {
312
+ rl.close();
313
+ return;
314
+ }
315
+ else {
316
+ send({ type: 'error', message: `Unknown command type: ${JSON.stringify(type ?? null)}` });
317
+ }
318
+ }
319
+ catch (err) {
320
+ send({ type: 'error', message: err.message });
321
+ }
322
+ }
323
+ }
324
+ //# sourceMappingURL=gym.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gym.js","sourceRoot":"","sources":["../../src/commands/gym.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function simplifyCommand(args: string[]): Promise<void>;
2
+ //# sourceMappingURL=simplify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simplify.d.ts","sourceRoot":"","sources":["../../src/commands/simplify.ts"],"names":[],"mappings":"AAoBA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFnE"}
@@ -0,0 +1,102 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ /**
5
+ * ifc-lite simplify <file.ifc> --out light.ifc [--level 1..5] [--ids 1,2,3] [--json]
6
+ *
7
+ * Demesher (dev/testing surface for the DemeshSession SDK API): simplify
8
+ * element meshes — levels 1-4 drop enclosed cavities and decimate to
9
+ * 0.5/0.25/0.10/0.03 of the triangle count, level 5 collapses each element
10
+ * to its bounding box — and write a lighter IFC where each simplified
11
+ * element's representation is replaced by an IfcTriangulatedFaceSet.
12
+ * `--level` defaults to 1; without --ids every element that produced a mesh
13
+ * is simplified.
14
+ */
15
+ import { readFile, writeFile } from 'node:fs/promises';
16
+ import { getFlag, hasFlag, fatal, printJson } from '../output.js';
17
+ import { DemeshSession } from '@ifc-lite/export';
18
+ export async function simplifyCommand(args) {
19
+ const filePath = args.find((a) => !a.startsWith('-'));
20
+ if (!filePath) {
21
+ fatal('Usage: ifc-lite simplify <file.ifc> --out light.ifc [--level 1..5] [--ids 1,2,3] [--json]');
22
+ }
23
+ const levelStr = getFlag(args, '--level');
24
+ const outPath = getFlag(args, '--out');
25
+ const idsStr = getFlag(args, '--ids');
26
+ const jsonOutput = hasFlag(args, '--json');
27
+ const level = Number(levelStr ?? '1');
28
+ if (!Number.isInteger(level) || level < 1 || level > 5) {
29
+ fatal('--level must be an integer 1..5 (1-4 = decimation tiers, 5 = bounding box; default 1)');
30
+ }
31
+ if (!outPath)
32
+ fatal('--out is required. Specify output file path.');
33
+ const source = new Uint8Array(await readFile(filePath));
34
+ const session = new DemeshSession(source);
35
+ try {
36
+ let ids;
37
+ if (idsStr) {
38
+ // Strict: every token must be a positive decimal integer. parseInt
39
+ // would silently accept '12foo' as 12, and filtering bad tokens away
40
+ // would silently simplify a different selection than the user typed.
41
+ const tokens = idsStr.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
42
+ if (tokens.length === 0 || tokens.some((s) => !/^\d+$/.test(s) || Number(s) < 1 || !Number.isSafeInteger(Number(s)))) {
43
+ fatal('--ids must be a comma-separated list of positive integer express ids');
44
+ }
45
+ ids = tokens.map((s) => Number(s));
46
+ }
47
+ else {
48
+ // Every element that produced geometry, heaviest first.
49
+ ids = (await session.heaviest(Number.MAX_SAFE_INTEGER)).map((e) => e.expressId);
50
+ if (ids.length === 0)
51
+ fatal('No element meshes produced from this file.');
52
+ }
53
+ const simplified = await session.simplify(ids, level);
54
+ if (simplified.elements.length === 0) {
55
+ fatal(`No elements could be simplified (${simplified.skipped.length} skipped).`);
56
+ }
57
+ const exported = await session.exportIfc();
58
+ // The export can still skip elements the mesh pass simplified (missing
59
+ // representation attribute, no geometric context, ...). The exported
60
+ // report is the authority on what the written IFC actually contains —
61
+ // and when NOTHING was replaced there is no lighter file to write.
62
+ const replaced = exported.report.replaced.length;
63
+ if (replaced === 0) {
64
+ fatal(`Simplified ${simplified.elements.length} meshes, but none could be applied to the IFC ` +
65
+ `(${exported.report.skipped.length} skipped at export); not writing ${outPath}.`);
66
+ }
67
+ await writeFile(outPath, exported.bytes);
68
+ const trisBefore = simplified.elements.reduce((s, e) => s + e.trisBefore, 0);
69
+ const trisAfter = simplified.elements.reduce((s, e) => s + e.trisAfter, 0);
70
+ if (jsonOutput) {
71
+ printJson({
72
+ level,
73
+ simplified: simplified.elements.length,
74
+ replaced,
75
+ skipped: simplified.skipped,
76
+ exportSkipped: exported.report.skipped,
77
+ trianglesBefore: trisBefore,
78
+ trianglesAfter: trisAfter,
79
+ cavitiesDropped: simplified.elements.reduce((s, e) => s + e.cavitiesDropped, 0),
80
+ prunedEntities: exported.report.prunedEntityCount,
81
+ strippedOpenings: exported.report.strippedOpeningCount,
82
+ upconverted: exported.upconverted,
83
+ bytesBefore: exported.bytesBefore,
84
+ bytesAfter: exported.bytesAfter,
85
+ output: outPath,
86
+ });
87
+ }
88
+ else {
89
+ process.stderr.write(`Simplified ${simplified.elements.length} meshes at level ${level} ` +
90
+ `(${simplified.skipped.length} skipped), ${replaced} representations replaced` +
91
+ `${exported.report.skipped.length > 0 ? ` (${exported.report.skipped.length} skipped at export)` : ''}\n` +
92
+ `Triangles: ${trisBefore} -> ${trisAfter}\n` +
93
+ `File size: ${exported.bytesBefore} -> ${exported.bytesAfter} bytes` +
94
+ `${exported.upconverted ? ' (upconverted IFC2X3 -> IFC4)' : ''}\n` +
95
+ `Written to ${outPath}\n`);
96
+ }
97
+ }
98
+ finally {
99
+ session.destroy();
100
+ }
101
+ }
102
+ //# sourceMappingURL=simplify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simplify.js","sourceRoot":"","sources":["../../src/commands/simplify.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,42 @@
1
+ import { type IfcDataStore } from '@ifc-lite/parser';
2
+ export interface ValidationIssue {
3
+ severity: 'error' | 'warning' | 'info';
4
+ rule: string;
5
+ message: string;
6
+ /** Referencing entity's expressId (set by per-reference rules like reference-integrity). */
7
+ entityId?: number;
8
+ /** Zero-based top-level attribute slot of the offending value within the referencing entity. */
9
+ attributeIndex?: number;
10
+ /** The referenced expressId that does not exist in the file. */
11
+ target?: number;
12
+ }
13
+ /** One `#N` reference whose target expressId does not exist in the file. */
14
+ interface DanglingReference {
15
+ /** expressId of the entity containing the reference. */
16
+ entityId: number;
17
+ /** STEP type name of the referencing entity (UPPERCASE, as scanned). */
18
+ entityType: string;
19
+ /** Zero-based top-level attribute slot the reference sits in (nested refs report their containing slot). */
20
+ attributeIndex: number;
21
+ /** The missing expressId being referenced. */
22
+ target: number;
23
+ }
24
+ /**
25
+ * Walk every indexed entity record and collect `#N` references whose target
26
+ * does not exist in the file (rule `reference-integrity`). Uses the parsed
27
+ * entity index for both iteration and existence checks; entities held in the
28
+ * deferred index (lazily-parsed property entities) count as existing and are
29
+ * scanned too.
30
+ */
31
+ export declare function collectDanglingReferences(store: IfcDataStore): DanglingReference[];
32
+ /**
33
+ * Run the structural validation checks (required entities, storeys, GlobalId
34
+ * uniqueness, naming, schema version, quantity completeness, reference
35
+ * integrity) against an already-parsed store. Pulled out of
36
+ * {@link validateCommand} so other consumers (tests, harnesses) reuse the
37
+ * exact same rules instead of re-implementing them.
38
+ */
39
+ export declare function computeValidationIssues(store: IfcDataStore): ValidationIssue[];
1
40
  export declare function validateCommand(args: string[]): Promise<void>;
41
+ export {};
2
42
  //# sourceMappingURL=validate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AA0BA,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgInE"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAgC,KAAK,YAAY,EAAkB,MAAM,kBAAkB,CAAC;AAEnG,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,4EAA4E;AAC5E,UAAU,iBAAiB;IACzB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,4GAA4G;IAC5G,cAAc,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAuFD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,YAAY,GAAG,iBAAiB,EAAE,CAmBlF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,EAAE,CA2H9E;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCnE"}