@ifc-lite/cli 0.27.0 → 0.28.1

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 (46) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/bcf.js +1 -1
  3. package/dist/commands/bcf.js.map +1 -1
  4. package/dist/commands/delivery-checks.d.ts +56 -0
  5. package/dist/commands/delivery-checks.d.ts.map +1 -0
  6. package/dist/commands/delivery-checks.js +222 -0
  7. package/dist/commands/delivery-checks.js.map +1 -0
  8. package/dist/commands/delivery-recipe.d.ts +23 -0
  9. package/dist/commands/delivery-recipe.d.ts.map +1 -0
  10. package/dist/commands/delivery-recipe.js +104 -0
  11. package/dist/commands/delivery-recipe.js.map +1 -0
  12. package/dist/commands/delivery-report.d.ts +51 -0
  13. package/dist/commands/delivery-report.d.ts.map +1 -0
  14. package/dist/commands/delivery-report.js +114 -0
  15. package/dist/commands/delivery-report.js.map +1 -0
  16. package/dist/commands/delivery.d.ts +2 -0
  17. package/dist/commands/delivery.d.ts.map +1 -0
  18. package/dist/commands/delivery.js +127 -0
  19. package/dist/commands/delivery.js.map +1 -0
  20. package/dist/commands/export-rust-formats.d.ts +11 -0
  21. package/dist/commands/export-rust-formats.d.ts.map +1 -0
  22. package/dist/commands/export-rust-formats.js +161 -0
  23. package/dist/commands/export-rust-formats.js.map +1 -0
  24. package/dist/commands/export.d.ts.map +1 -1
  25. package/dist/commands/export.js +31 -145
  26. package/dist/commands/export.js.map +1 -1
  27. package/dist/commands/extract-entities.d.ts +10 -17
  28. package/dist/commands/extract-entities.d.ts.map +1 -1
  29. package/dist/commands/extract-entities.js +34 -23
  30. package/dist/commands/extract-entities.js.map +1 -1
  31. package/dist/commands/gym.js +2 -2
  32. package/dist/commands/mutate.d.ts +16 -4
  33. package/dist/commands/mutate.d.ts.map +1 -1
  34. package/dist/commands/mutate.js +38 -46
  35. package/dist/commands/mutate.js.map +1 -1
  36. package/dist/commands/step-args.d.ts +114 -0
  37. package/dist/commands/step-args.d.ts.map +1 -0
  38. package/dist/commands/step-args.js +299 -0
  39. package/dist/commands/step-args.js.map +1 -0
  40. package/dist/commands/subset-relations.d.ts +124 -0
  41. package/dist/commands/subset-relations.d.ts.map +1 -0
  42. package/dist/commands/subset-relations.js +273 -0
  43. package/dist/commands/subset-relations.js.map +1 -0
  44. package/dist/index.js +7 -1
  45. package/dist/index.js.map +1 -1
  46. package/package.json +14 -14
package/README.md CHANGED
@@ -48,7 +48,7 @@ Global flags: `--json`, `--out <file>`, `--verbose`, `--quiet`, `--debug`, `--lo
48
48
 
49
49
  ## gym
50
50
 
51
- `ifc-lite gym` is a prototype reset/step/reward environment API over the existing headless checks: the skeleton of an RLVR environment for buildings (see [`docs/vision/moonshots-tech.md`](../../docs/vision/moonshots-tech.md) M2 and [`docs/vision/moonshots-execution-plan.md`](../../docs/vision/moonshots-execution-plan.md) B0.4). It wraps a model - either a fixed file (`--model`) or a procedurally generated World Gym episode (`--seed`) - and lets an agent apply data-mutation ops, scoring each step against the same schema/clash/ids checks the `validate`, `clash`, and `ids` commands already run.
51
+ `ifc-lite gym` is a prototype reset/step/reward environment API over the existing headless checks: the skeleton of an RLVR environment for buildings (see [`tools/world-gym/README.md`](../../tools/world-gym/README.md) M2 and [`tools/world-gym/benchmark/BENCHMARK.md`](../../tools/world-gym/benchmark/BENCHMARK.md) B0.4). It wraps a model - either a fixed file (`--model`) or a procedurally generated World Gym episode (`--seed`) - and lets an agent apply data-mutation ops, scoring each step against the same schema/clash/ids checks the `validate`, `clash`, and `ids` commands already run.
52
52
 
53
53
  ```bash
54
54
  ifc-lite gym --model model.ifc --checks schema,clash
@@ -21,7 +21,7 @@ export async function bcfCommand(args) {
21
21
  const author = getFlag(args, '--author') ?? 'cli@ifc-lite.com';
22
22
  const outPath = getFlag(args, '--out');
23
23
  if (!title)
24
- fatal('Usage: ifc-lite bcf create --title "Issue" [--description "..."] [--author email] --out file.bcf');
24
+ fatal('Usage: ifc-lite bcf create --title "Missing door" [--description "..."] [--author email] --out file.bcf');
25
25
  if (!outPath)
26
26
  fatal('--out is required for BCF creation');
27
27
  const project = await bcf.createProject({ name: 'CLI Project' });
@@ -1 +1 @@
1
- {"version":3,"file":"bcf.js","sourceRoot":"","sources":["../../src/commands/bcf.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bcf.js","sourceRoot":"","sources":["../../src/commands/bcf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { type IfcDataStore } from '@ifc-lite/parser';
2
+ import { type ValidationIssue } from './validate.js';
3
+ export type CheckStatus = 'pass' | 'fail' | 'error';
4
+ export interface LoadedModel {
5
+ path: string;
6
+ /** SHA-256 of the exact bytes read from disk (post ifcZIP unwrap: the STEP text actually checked). */
7
+ sha256: string;
8
+ store: IfcDataStore;
9
+ }
10
+ export interface ModelLoadError {
11
+ path: string;
12
+ error: string;
13
+ }
14
+ /** Load one model file for delivery checking, never throwing and never exiting the process. */
15
+ export declare function loadModelForDelivery(path: string): Promise<LoadedModel | ModelLoadError>;
16
+ export interface StructuralCheckResult {
17
+ type: 'structural';
18
+ model: string;
19
+ status: CheckStatus;
20
+ errorCount: number;
21
+ warningCount: number;
22
+ infoCount: number;
23
+ issues: ValidationIssue[];
24
+ error?: string;
25
+ }
26
+ /** Run the same structural rules `ifc-lite validate` runs. `fail` iff at least one error-severity issue was found. */
27
+ export declare function runStructuralCheck(modelPath: string, store: IfcDataStore): StructuralCheckResult;
28
+ export interface IdsCheckResult {
29
+ type: 'ids';
30
+ model: string;
31
+ source: string;
32
+ status: CheckStatus;
33
+ totalSpecifications?: number;
34
+ passedSpecifications?: number;
35
+ failedSpecifications?: number;
36
+ notApplicableSpecifications?: number;
37
+ totalEntities?: number;
38
+ passedEntities?: number;
39
+ failedEntities?: number;
40
+ error?: string;
41
+ }
42
+ /**
43
+ * Run one IDS rule file against one already-loaded model.
44
+ *
45
+ * `status` is `error` when the IDS file itself could not be read/parsed, or
46
+ * when it declares zero specifications (nothing was actually evaluated —
47
+ * reporting that as `pass` would hide an empty ruleset behind a green
48
+ * check). Otherwise `fail` iff at least one specification failed; a
49
+ * specification whose applicability matched no entities counts as neither
50
+ * pass nor fail (`notApplicableSpecifications`) — if EVERY specification
51
+ * lands there (nothing was definitively satisfied OR violated), the check
52
+ * reports `error` rather than a `pass` that would rest on zero actual
53
+ * evidence.
54
+ */
55
+ export declare function runIdsCheck(modelPath: string, store: IfcDataStore, idsPath: string): Promise<IdsCheckResult>;
56
+ //# sourceMappingURL=delivery-checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-checks.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-checks.ts"],"names":[],"mappings":"AA6BA,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGlF,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE9E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,sGAAsG;IACtG,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+FAA+F;AAC/F,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,cAAc,CAAC,CAyC9F;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sHAAsH;AACtH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,qBAAqB,CAchG;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+GD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA6ClH"}
@@ -0,0 +1,222 @@
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
+ * Model loading and per-check evaluation for `ifc-lite delivery`.
6
+ *
7
+ * Three outcomes exist for every check, never just two:
8
+ * - `pass` — the check ran and found nothing to report.
9
+ * - `fail` — the check ran and found a violation.
10
+ * - `error` — the check could NOT be run (unreadable model, unreadable or
11
+ * unparsable IDS file, an IDS document declaring zero
12
+ * specifications). An unevaluable check must never be folded
13
+ * into `pass`: a delivery report that silently skips a rule
14
+ * and still reads as clean is worse than no report.
15
+ *
16
+ * `loadModelForDelivery` deliberately does NOT reuse `loader.ts`'s
17
+ * `loadIfcFile`/`loadIfcBytes`: those call `process.exit(1)` directly on an
18
+ * empty, non-STEP, or unparsable file, which is correct for a single-file
19
+ * command but would kill the whole `delivery` process on the SECOND model in
20
+ * a multi-model recipe just because the first one was bad. This loader
21
+ * performs the same validation (STEP signature, ifcZIP unwrap, parse) but
22
+ * returns a `{ error }` result instead of exiting, so one unreadable model
23
+ * becomes one `error` entry in the report — never a crashed run and never a
24
+ * silently-passing verdict.
25
+ */
26
+ import { readFile } from 'node:fs/promises';
27
+ import { createHash } from 'node:crypto';
28
+ import { IfcParser, unwrapIfcZipView } from '@ifc-lite/parser';
29
+ import { createDataAccessor } from '@ifc-lite/ids/bridge';
30
+ import { IDSNamespace } from '@ifc-lite/sdk';
31
+ import { computeValidationIssues } from './validate.js';
32
+ /** Load one model file for delivery checking, never throwing and never exiting the process. */
33
+ export async function loadModelForDelivery(path) {
34
+ let bytes;
35
+ try {
36
+ bytes = await readFile(path);
37
+ }
38
+ catch (err) {
39
+ return { path, error: `could not be read: ${err.message}` };
40
+ }
41
+ if (bytes.byteLength === 0) {
42
+ return { path, error: 'is empty (0 bytes)' };
43
+ }
44
+ try {
45
+ bytes = new Uint8Array(await unwrapIfcZipView(bytes));
46
+ }
47
+ catch (err) {
48
+ return { path, error: `could not be unwrapped: ${err.message}` };
49
+ }
50
+ const headerSnippet = new TextDecoder('latin1').decode(bytes.subarray(0, Math.min(bytes.byteLength, 256)));
51
+ if (!headerSnippet.includes('ISO-10303-21')) {
52
+ return { path, error: 'is not a valid IFC/STEP file' };
53
+ }
54
+ const sha256 = createHash('sha256').update(bytes).digest('hex');
55
+ const parser = new IfcParser();
56
+ const origLog = console.log;
57
+ const origWarn = console.warn;
58
+ console.log = () => { };
59
+ console.warn = () => { };
60
+ try {
61
+ const arrayBuffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
62
+ const store = await parser.parseColumnar(arrayBuffer, {});
63
+ store.fileSize = bytes.byteLength;
64
+ return { path, sha256, store };
65
+ }
66
+ catch (err) {
67
+ return { path, error: `could not be parsed: ${err.message}` };
68
+ }
69
+ finally {
70
+ console.log = origLog;
71
+ console.warn = origWarn;
72
+ }
73
+ }
74
+ /** Run the same structural rules `ifc-lite validate` runs. `fail` iff at least one error-severity issue was found. */
75
+ export function runStructuralCheck(modelPath, store) {
76
+ const issues = computeValidationIssues(store);
77
+ const errorCount = issues.filter(i => i.severity === 'error').length;
78
+ const warningCount = issues.filter(i => i.severity === 'warning').length;
79
+ const infoCount = issues.filter(i => i.severity === 'info').length;
80
+ return {
81
+ type: 'structural',
82
+ model: modelPath,
83
+ status: errorCount > 0 ? 'fail' : 'pass',
84
+ errorCount,
85
+ warningCount,
86
+ infoCount,
87
+ issues,
88
+ };
89
+ }
90
+ /**
91
+ * A specification counts as genuinely evaluated only when its cardinality
92
+ * bound could actually have been violated by SOME entity count, or it
93
+ * checked at least one entity's requirements. `minOccurs="0"` with no
94
+ * finite upper bound (the IDS "optional, if present" cardinality) is
95
+ * satisfied by zero matches for every possible model — no entity count
96
+ * could ever fail it — so treating that as `pass` reports success for a
97
+ * rule that could not possibly have failed and checked nothing. That is
98
+ * distinct from a `maxOccurs="0"` prohibition ("must not exist"), which a
99
+ * nonzero applicable count WOULD have violated, so zero matches there is
100
+ * real evidence, not a vacuous result — `@ifc-lite/ids`'s own corpus fixture
101
+ * `pass-prohibited_specifications_passes_if_the_applicability_does_not_matches.ids`
102
+ * exercises exactly this and must keep reading `pass`.
103
+ */
104
+ function isVacuousSpecification(spec) {
105
+ if (spec.status === 'not_applicable')
106
+ return true;
107
+ const c = spec.cardinalityResult;
108
+ if (!c || c.actualCount !== 0)
109
+ return false;
110
+ return c.minExpected === 0 && (c.maxExpected === undefined || c.maxExpected === 'unbounded');
111
+ }
112
+ /**
113
+ * Summarize a real `IDSValidationReport` for the delivery checker.
114
+ *
115
+ * This intentionally does NOT call `bim.ids.summarize()` (`@ifc-lite/sdk`)
116
+ * for the specification-level bucketing: that helper defers entirely to
117
+ * the validator's own per-specification `status`, and the validator marks
118
+ * a `minOccurs="0"`/unbounded-`maxOccurs` specification `pass` whenever it
119
+ * matches zero entities (see `isVacuousSpecification` above) — correct IDS
120
+ * semantics for an "optional" clause, but exactly the vacuous-check defect
121
+ * a delivery checker must not fold into `pass`. Entity counts ARE summed
122
+ * directly here for the same reason `bim.ids.summarize()` would need
123
+ * `includePassingEntities: true` to get them right: that option controls
124
+ * whether PASSING entities are even present in `entityResults`, and this
125
+ * file used to call `validate()` with it `false`, which is harmless for the
126
+ * raw validator's own `summary.totalEntitiesChecked` (computed from each
127
+ * specification's `passedCount`/`failedCount`, not from `entityResults`)
128
+ * but would silently undercount any per-entity tally derived from
129
+ * `entityResults` directly, `bim.ids.summarize()` included.
130
+ */
131
+ function summarizeIdsReport(report) {
132
+ let passedSpecifications = 0;
133
+ let failedSpecifications = 0;
134
+ let notApplicableSpecifications = 0;
135
+ let totalEntities = 0;
136
+ let passedEntities = 0;
137
+ let failedEntities = 0;
138
+ for (const spec of report.specificationResults) {
139
+ for (const entity of spec.entityResults) {
140
+ totalEntities++;
141
+ if (entity.passed)
142
+ passedEntities++;
143
+ else
144
+ failedEntities++;
145
+ }
146
+ if (spec.status === 'fail') {
147
+ failedSpecifications++;
148
+ }
149
+ else if (isVacuousSpecification(spec)) {
150
+ notApplicableSpecifications++;
151
+ }
152
+ else {
153
+ passedSpecifications++;
154
+ }
155
+ }
156
+ return {
157
+ totalSpecifications: report.specificationResults.length,
158
+ passedSpecifications,
159
+ failedSpecifications,
160
+ notApplicableSpecifications,
161
+ totalEntities,
162
+ passedEntities,
163
+ failedEntities,
164
+ };
165
+ }
166
+ /**
167
+ * Run one IDS rule file against one already-loaded model.
168
+ *
169
+ * `status` is `error` when the IDS file itself could not be read/parsed, or
170
+ * when it declares zero specifications (nothing was actually evaluated —
171
+ * reporting that as `pass` would hide an empty ruleset behind a green
172
+ * check). Otherwise `fail` iff at least one specification failed; a
173
+ * specification whose applicability matched no entities counts as neither
174
+ * pass nor fail (`notApplicableSpecifications`) — if EVERY specification
175
+ * lands there (nothing was definitively satisfied OR violated), the check
176
+ * reports `error` rather than a `pass` that would rest on zero actual
177
+ * evidence.
178
+ */
179
+ export async function runIdsCheck(modelPath, store, idsPath) {
180
+ const base = { type: 'ids', model: modelPath, source: idsPath };
181
+ let idsContent;
182
+ try {
183
+ idsContent = await readFile(idsPath, 'utf-8');
184
+ }
185
+ catch (err) {
186
+ return { ...base, status: 'error', error: `could not be read: ${err.message}` };
187
+ }
188
+ const ids = new IDSNamespace();
189
+ let idsDoc;
190
+ try {
191
+ idsDoc = await ids.parse(idsContent);
192
+ }
193
+ catch (err) {
194
+ return { ...base, status: 'error', error: `could not be parsed: ${err.message}` };
195
+ }
196
+ const accessor = createDataAccessor(store);
197
+ let report;
198
+ try {
199
+ report = (await ids.validate(idsDoc, {
200
+ accessor,
201
+ modelInfo: { schemaVersion: store.schemaVersion },
202
+ locale: 'en',
203
+ includePassingEntities: true,
204
+ }));
205
+ }
206
+ catch (err) {
207
+ return { ...base, status: 'error', error: `validation failed: ${err.message}` };
208
+ }
209
+ const s = summarizeIdsReport(report);
210
+ if (s.totalSpecifications === 0) {
211
+ return { ...base, status: 'error', error: 'declares zero specifications', ...s };
212
+ }
213
+ const evaluated = s.passedSpecifications + s.failedSpecifications;
214
+ const status = s.failedSpecifications > 0 ? 'fail' : evaluated > 0 ? 'pass' : 'error';
215
+ return {
216
+ ...base,
217
+ status,
218
+ ...s,
219
+ ...(status === 'error' ? { error: 'every specification was not-applicable — nothing was evaluated' } : {}),
220
+ };
221
+ }
222
+ //# sourceMappingURL=delivery-checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-checks.js","sourceRoot":"","sources":["../../src/commands/delivery-checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /** The recipe fields as declared in the JSON file (paths not yet resolved). */
2
+ export interface DeliveryRecipe {
3
+ models: string[];
4
+ structural: boolean;
5
+ ids: string[];
6
+ }
7
+ /** A recipe with every `models`/`ids` path resolved to an absolute path. */
8
+ export interface ResolvedDeliveryRecipe extends DeliveryRecipe {
9
+ /** Absolute path to the recipe file itself. */
10
+ recipePath: string;
11
+ /** `models`, resolved to absolute paths, in declared order. */
12
+ resolvedModels: string[];
13
+ /** `ids`, resolved to absolute paths, in declared order. */
14
+ resolvedIds: string[];
15
+ }
16
+ /**
17
+ * Load and validate a delivery recipe, resolving every declared path
18
+ * relative to the recipe file's own directory. Every failure mode is a
19
+ * `fatal(...)` with a specific reason — never a silently empty or
20
+ * partially-applied recipe.
21
+ */
22
+ export declare function loadDeliveryRecipe(path: string): Promise<ResolvedDeliveryRecipe>;
23
+ //# sourceMappingURL=delivery-recipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-recipe.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-recipe.ts"],"names":[],"mappings":"AAuCA,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,EAAE,MAAM,EAAE,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,4DAA4D;IAC5D,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAQD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiEtF"}
@@ -0,0 +1,104 @@
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
+ * The saved recipe `ifc-lite delivery` runs.
6
+ *
7
+ * A recipe is a plain JSON file naming one or more model files and the
8
+ * checks to run against every one of them — `structural` (the same rules
9
+ * `ifc-lite validate` runs) and/or one or more `ids` rule files (the same
10
+ * validator `ifc-lite ids` runs). It exists so a delivery check is a single,
11
+ * versioned, reviewable artifact a team commits and re-runs, rather than a
12
+ * remembered shell invocation.
13
+ *
14
+ * ```json
15
+ * {
16
+ * "models": ["model.ifc"],
17
+ * "structural": true,
18
+ * "ids": ["door-rules.ids"]
19
+ * }
20
+ * ```
21
+ *
22
+ * Every `models`/`ids` path is resolved relative to the DIRECTORY CONTAINING
23
+ * THE RECIPE FILE, not the process's current working directory — a recipe
24
+ * committed alongside its fixtures stays runnable from anywhere.
25
+ *
26
+ * A malformed recipe is a `fatal(...)`, matching every other saved-definition
27
+ * loader in this CLI (`schedule-spec.ts`): not valid JSON, not a JSON object,
28
+ * an unrecognised field, a field of the wrong type, an empty/missing
29
+ * `models` list, or declaring NEITHER `structural: true` NOR a non-empty
30
+ * `ids` list (a recipe with zero applicable checks can never produce a
31
+ * meaningful delivery verdict, so it is rejected up front rather than
32
+ * silently reporting an empty "pass").
33
+ */
34
+ import { readFile } from 'node:fs/promises';
35
+ import { dirname, resolve } from 'node:path';
36
+ import { fatal } from '../output.js';
37
+ const RECIPE_FIELDS = ['models', 'structural', 'ids'];
38
+ function isStringArray(value) {
39
+ return Array.isArray(value) && value.every(v => typeof v === 'string');
40
+ }
41
+ /**
42
+ * Load and validate a delivery recipe, resolving every declared path
43
+ * relative to the recipe file's own directory. Every failure mode is a
44
+ * `fatal(...)` with a specific reason — never a silently empty or
45
+ * partially-applied recipe.
46
+ */
47
+ export async function loadDeliveryRecipe(path) {
48
+ let content;
49
+ try {
50
+ content = await readFile(path, 'utf-8');
51
+ }
52
+ catch (err) {
53
+ fatal(`Recipe "${path}" could not be read: ${err.message}`);
54
+ }
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(content);
58
+ }
59
+ catch (err) {
60
+ fatal(`Recipe "${path}" is not valid JSON: ${err.message}`);
61
+ }
62
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
63
+ fatal(`Recipe "${path}" must be a JSON object with "models" and at least one of "structural"/"ids", got ${Array.isArray(parsed) ? 'an array' : typeof parsed}.`);
64
+ }
65
+ const obj = parsed;
66
+ const unknownKeys = Object.keys(obj).filter(k => !RECIPE_FIELDS.includes(k));
67
+ if (unknownKeys.length > 0) {
68
+ fatal(`Recipe "${path}" has unrecognised field(s): ${unknownKeys.join(', ')}. Valid fields: ${RECIPE_FIELDS.join(', ')}.`);
69
+ }
70
+ if (!('models' in obj) || !isStringArray(obj.models) || obj.models.length === 0) {
71
+ fatal(`Recipe "${path}": "models" must be a non-empty array of file paths.`);
72
+ }
73
+ const models = obj.models;
74
+ if ('structural' in obj && typeof obj.structural !== 'boolean') {
75
+ fatal(`Recipe "${path}": "structural" must be a boolean, got ${typeof obj.structural}.`);
76
+ }
77
+ const structural = obj.structural ?? false;
78
+ if ('ids' in obj && !isStringArray(obj.ids)) {
79
+ fatal(`Recipe "${path}": "ids" must be an array of file paths.`);
80
+ }
81
+ const ids = obj.ids ?? [];
82
+ if (!structural && ids.length === 0) {
83
+ fatal(`Recipe "${path}" declares no applicable checks: set "structural": true and/or a non-empty "ids" list. A zero-check recipe can never produce a delivery verdict.`);
84
+ }
85
+ // Duplicate model/ids entries are rejected rather than silently
86
+ // deduplicated or run twice under one collapsed key: a repeated path is
87
+ // almost certainly a copy-paste mistake in the recipe, and either
88
+ // response (silent dedup, or a doubled report entry) would misrepresent
89
+ // what the recipe actually says it checks.
90
+ const dupModels = models.filter((m, i) => models.indexOf(m) !== i);
91
+ if (dupModels.length > 0) {
92
+ fatal(`Recipe "${path}": "models" lists the same path more than once: ${[...new Set(dupModels)].join(', ')}.`);
93
+ }
94
+ const dupIds = ids.filter((m, i) => ids.indexOf(m) !== i);
95
+ if (dupIds.length > 0) {
96
+ fatal(`Recipe "${path}": "ids" lists the same path more than once: ${[...new Set(dupIds)].join(', ')}.`);
97
+ }
98
+ const recipePath = resolve(path);
99
+ const baseDir = dirname(recipePath);
100
+ const resolvedModels = models.map(m => resolve(baseDir, m));
101
+ const resolvedIds = ids.map(i => resolve(baseDir, i));
102
+ return { recipePath, models, structural, ids, resolvedModels, resolvedIds };
103
+ }
104
+ //# sourceMappingURL=delivery-recipe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-recipe.js","sourceRoot":"","sources":["../../src/commands/delivery-recipe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Consolidated report assembly and HTML rendering for `ifc-lite delivery`.
3
+ *
4
+ * Determinism contract: `buildDeliveryReport` never reads the wall clock,
5
+ * never iterates a `Map`/object in insertion-order-dependent ways, and never
6
+ * keys a result by a derived string (model path, IDS file name, check type)
7
+ * — every check is one entry in a flat, declared-order ARRAY. Two collisions
8
+ * this codebase has actually shipped (a subtotal silently overwritten in
9
+ * `schedule`'s output, a duplicate `--columns` header collapsing a row in
10
+ * JSON) both came from keying a result object by a name that two distinct
11
+ * inputs could share; an array keeps every check's own model/type/source
12
+ * fields as data instead of forcing them into a Map/object key that could
13
+ * collide. The result: the SAME recipe run twice, with no model or IDS file
14
+ * changed on disk, produces byte-identical JSON (`tool.version` and the
15
+ * `sha256` fingerprints are the only fields that could ever legitimately
16
+ * differ between two runs, and neither depends on wall-clock time or
17
+ * iteration order).
18
+ */
19
+ import type { StructuralCheckResult, IdsCheckResult } from './delivery-checks.js';
20
+ import type { ResolvedDeliveryRecipe } from './delivery-recipe.js';
21
+ export type DeliveryCheckResult = StructuralCheckResult | IdsCheckResult;
22
+ export interface DeliveryModelEntry {
23
+ /** Path exactly as declared in the recipe (relative, for a readable report). */
24
+ path: string;
25
+ /** SHA-256 of the model's bytes, or undefined when the model could not be loaded. */
26
+ sha256?: string;
27
+ /** Set when the model could not be loaded; every check for this model then reports `error`. */
28
+ loadError?: string;
29
+ }
30
+ export interface DeliveryReport {
31
+ tool: {
32
+ name: 'ifc-lite';
33
+ version: string;
34
+ };
35
+ recipe: string;
36
+ models: DeliveryModelEntry[];
37
+ checks: DeliveryCheckResult[];
38
+ /** `pass` iff every check in `checks` is `pass` and `checks` is non-empty. */
39
+ verdict: 'pass' | 'fail';
40
+ summary: {
41
+ total: number;
42
+ pass: number;
43
+ fail: number;
44
+ error: number;
45
+ };
46
+ }
47
+ /** Build the consolidated report from a resolved recipe, its per-model load outcomes, and every check result. */
48
+ export declare function buildDeliveryReport(recipe: ResolvedDeliveryRecipe, version: string, models: DeliveryModelEntry[], checks: DeliveryCheckResult[]): DeliveryReport;
49
+ /** Render the report as a standalone HTML document — no external assets, safe to open from disk. */
50
+ export declare function renderDeliveryHtml(report: DeliveryReport): string;
51
+ //# sourceMappingURL=delivery-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery-report.d.ts","sourceRoot":"","sources":["../../src/commands/delivery-report.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAe,MAAM,sBAAsB,CAAC;AAC/F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG,cAAc,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC7B,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,iHAAiH;AACjH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,EAAE,EAC5B,MAAM,EAAE,mBAAmB,EAAE,GAC5B,cAAc,CAchB;AA+BD,oGAAoG;AACpG,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAoEjE"}
@@ -0,0 +1,114 @@
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
+ /** Build the consolidated report from a resolved recipe, its per-model load outcomes, and every check result. */
5
+ export function buildDeliveryReport(recipe, version, models, checks) {
6
+ const counts = { total: checks.length, pass: 0, fail: 0, error: 0 };
7
+ for (const c of checks)
8
+ counts[c.status]++;
9
+ const verdict = checks.length > 0 && counts.fail === 0 && counts.error === 0 ? 'pass' : 'fail';
10
+ return {
11
+ tool: { name: 'ifc-lite', version },
12
+ recipe: recipe.recipePath,
13
+ models,
14
+ checks,
15
+ verdict,
16
+ summary: counts,
17
+ };
18
+ }
19
+ function escapeHtml(value) {
20
+ return value
21
+ .replace(/&/g, '&amp;')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;')
24
+ .replace(/"/g, '&quot;')
25
+ .replace(/'/g, '&#39;');
26
+ }
27
+ function statusBadge(status) {
28
+ const label = status.toUpperCase();
29
+ return `<span class="badge badge-${status}">${label}</span>`;
30
+ }
31
+ function describeCheck(check) {
32
+ if (check.type === 'structural') {
33
+ return `${check.errorCount} error(s), ${check.warningCount} warning(s), ${check.infoCount} info`;
34
+ }
35
+ const parts = [];
36
+ if (check.totalSpecifications !== undefined) {
37
+ parts.push(`${check.passedSpecifications ?? 0}/${check.totalSpecifications} specification(s) passed`);
38
+ }
39
+ if (check.failedEntities !== undefined && check.totalEntities !== undefined) {
40
+ parts.push(`${check.failedEntities}/${check.totalEntities} entities failed`);
41
+ }
42
+ if (check.error)
43
+ parts.push(check.error);
44
+ return parts.join(' — ') || (check.error ?? '');
45
+ }
46
+ /** Render the report as a standalone HTML document — no external assets, safe to open from disk. */
47
+ export function renderDeliveryHtml(report) {
48
+ const rows = report.checks
49
+ .map(check => {
50
+ const source = check.type === 'structural' ? 'structural' : check.source;
51
+ return ` <tr class="row-${check.status}">
52
+ <td>${escapeHtml(check.model)}</td>
53
+ <td>${escapeHtml(check.type)}</td>
54
+ <td>${escapeHtml(source)}</td>
55
+ <td>${statusBadge(check.status)}</td>
56
+ <td>${escapeHtml(describeCheck(check))}</td>
57
+ </tr>`;
58
+ })
59
+ .join('\n');
60
+ const modelRows = report.models
61
+ .map(m => ` <tr>
62
+ <td>${escapeHtml(m.path)}</td>
63
+ <td><code>${escapeHtml(m.sha256 ?? '')}</code></td>
64
+ <td>${m.loadError ? `<span class="badge badge-error">ERROR</span> ${escapeHtml(m.loadError)}` : 'OK'}</td>
65
+ </tr>`)
66
+ .join('\n');
67
+ return `<!doctype html>
68
+ <html lang="en">
69
+ <head>
70
+ <meta charset="utf-8">
71
+ <title>IFC Delivery Check Report</title>
72
+ <style>
73
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 2rem; color: #1a1a1a; }
74
+ h1 { font-size: 1.4rem; }
75
+ .verdict { font-size: 1.1rem; font-weight: 600; padding: 0.5rem 1rem; border-radius: 4px; display: inline-block; }
76
+ .verdict-pass { background: #e6f4ea; color: #1e7e34; }
77
+ .verdict-fail { background: #fce8e6; color: #c5221f; }
78
+ table { border-collapse: collapse; width: 100%; margin-top: 1rem; }
79
+ th, td { border: 1px solid #ddd; padding: 0.4rem 0.6rem; text-align: left; font-size: 0.9rem; }
80
+ th { background: #f5f5f5; }
81
+ .badge { display: inline-block; padding: 0.1rem 0.5rem; border-radius: 3px; font-size: 0.75rem; font-weight: 600; }
82
+ .badge-pass { background: #e6f4ea; color: #1e7e34; }
83
+ .badge-fail { background: #fce8e6; color: #c5221f; }
84
+ .badge-error { background: #fef7e0; color: #8a6d00; }
85
+ .row-error td { background: #fffdf5; }
86
+ </style>
87
+ </head>
88
+ <body>
89
+ <h1>IFC Delivery Check Report</h1>
90
+ <p><strong>Tool:</strong> ${escapeHtml(report.tool.name)} ${escapeHtml(report.tool.version)}<br>
91
+ <strong>Recipe:</strong> <code>${escapeHtml(report.recipe)}</code></p>
92
+ <p class="verdict verdict-${report.verdict}">Verdict: ${report.verdict.toUpperCase()}</p>
93
+ <p>${report.summary.pass} pass, ${report.summary.fail} fail, ${report.summary.error} error (of ${report.summary.total} check(s))</p>
94
+
95
+ <h2>Models</h2>
96
+ <table>
97
+ <thead><tr><th>Path</th><th>SHA-256</th><th>Status</th></tr></thead>
98
+ <tbody>
99
+ ${modelRows}
100
+ </tbody>
101
+ </table>
102
+
103
+ <h2>Checks</h2>
104
+ <table>
105
+ <thead><tr><th>Model</th><th>Type</th><th>Source</th><th>Status</th><th>Evidence</th></tr></thead>
106
+ <tbody>
107
+ ${rows}
108
+ </tbody>
109
+ </table>
110
+ </body>
111
+ </html>
112
+ `;
113
+ }
114
+ //# sourceMappingURL=delivery-report.js.map