@orkestrel/scaffold 0.0.25 → 0.0.27

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.
@@ -46,10 +46,9 @@ export declare interface AppDefinition {
46
46
  *
47
47
  * @example
48
48
  * ```ts
49
- * import type { Artifact } from '@orkestrel/scaffold'
50
- * import { applyOverrides } from '@orkestrel/scaffold'
49
+ * import { applyOverrides, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
51
50
  *
52
- * declare const artifacts: readonly Artifact[]
51
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
53
52
  *
54
53
  * applyOverrides(artifacts, [{ path: 'README.md', content: '# Title\n' }])
55
54
  * ```
@@ -71,9 +70,13 @@ export declare type Artifact = HostArtifact | HydratedArtifact | ContentArtifact
71
70
  *
72
71
  * @remarks
73
72
  * Builders in `compilers.ts` fill every varying span through
74
- * `@orkestrel/template`. Empty barrels and setup modules are intentional: the
75
- * generated workspace starts with no sample domain API, while each selected
76
- * Vitest project gets a real test that proves its barrel has no starter exports.
73
+ * `@orkestrel/template`. Empty barrels, entries, and setup modules are
74
+ * intentional: the generated workspace starts with no sample domain API, while
75
+ * each selected Vitest project gets a real test that proves its barrel has no
76
+ * starter exports. An entry starts empty for the same reason its barrel does,
77
+ * and because the vendored lint config refuses an unassigned import outside a
78
+ * stylesheet, so a starter `import './index.js'` would fail the workspace's own
79
+ * `lint:check` on the day it is written.
77
80
  *
78
81
  * @example
79
82
  * ```ts
@@ -85,7 +88,6 @@ export declare type Artifact = HostArtifact | HydratedArtifact | ContentArtifact
85
88
  export declare const ARTIFACT_TEMPLATES: Readonly<{
86
89
  source: Readonly<{
87
90
  empty: "";
88
- main: "import './index.js'\n";
89
91
  browser: "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n\t\t<title>Application</title>\n\t</head>\n\t<body>\n\t\t<script type=\"module\" src=\"/main.ts\"></script>\n\t</body>\n</html>\n";
90
92
  }>;
91
93
  tests: Readonly<{
@@ -133,12 +135,11 @@ export declare interface ArtifactBase {
133
135
  *
134
136
  * @example
135
137
  * ```ts
136
- * import type { Artifact } from '@orkestrel/scaffold'
137
- * import { artifactsToQuestions } from '@orkestrel/scaffold'
138
+ * import { artifactsToQuestions, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
138
139
  *
139
- * declare const artifacts: readonly Artifact[]
140
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
140
141
  *
141
- * artifactsToQuestions(artifacts).length === 0 // true when the draft is sound
142
+ * artifactsToQuestions(artifacts).length === 0 // true
142
143
  * ```
143
144
  */
144
145
  export declare function artifactsToQuestions(artifacts: readonly Artifact[]): readonly Question[];
@@ -149,13 +150,16 @@ export declare function artifactsToQuestions(artifacts: readonly Artifact[]): re
149
150
  * @param artifact - The planned artifact.
150
151
  * @param observed - The destination's exact bytes as hexadecimal; absent when
151
152
  * the destination holds no file.
152
- * @returns The finding, carrying `observed` exactly where bytes were read.
153
+ * @returns The finding, carrying the artifact's ownership and `observed`
154
+ * exactly where bytes were read.
153
155
  *
154
156
  * @remarks
155
157
  * The comparison itself is {@link inferDrift}'s, so ownership decides it here
156
158
  * exactly as it does everywhere else. This adds only the shape: a missing
157
159
  * destination has no bytes to record, and every other verdict records the bytes
158
160
  * it was given, which is the precondition the mutation that follows is held to.
161
+ * Ownership is copied rather than inferred from drift because aligned findings
162
+ * span all three ownership tiers.
159
163
  *
160
164
  * `foreign` is not answerable here, because it describes a path no artifact was
161
165
  * planned for.
@@ -167,7 +171,7 @@ export declare function artifactsToQuestions(artifacts: readonly Artifact[]): re
167
171
  * artifactToFinding(
168
172
  * { path: 'README.md', group: 'docs', ownership: 'content', origin: 'computed', content: 'hi\n' },
169
173
  * '6279650a',
170
- * ) // { path: 'README.md', group: 'docs', drift: 'stale', observed: '6279650a' }
174
+ * ) // { path: 'README.md', group: 'docs', ownership: 'content', drift: 'stale', observed: '6279650a' }
171
175
  * ```
172
176
  */
173
177
  export declare function artifactToFinding(artifact: Artifact, observed?: string): Finding;
@@ -205,7 +209,8 @@ export declare function artifactToHex(artifact: Artifact): string | undefined;
205
209
  * @remarks
206
210
  * A blocking question means the gate refused the blueprint, so `findings` is
207
211
  * empty and says nothing about the target. Tallies are not stored: count
208
- * `findings` by `drift`.
212
+ * `findings` by `drift` or `ownership`. The finding bound is the sum of the
213
+ * separately bounded plan artifacts and snapshot paths.
209
214
  */
210
215
  export declare interface Audit {
211
216
  readonly findings: readonly Finding[];
@@ -233,7 +238,11 @@ export declare const BIN_ENTRY_PATH = "src/bin/main.ts";
233
238
  * `bin`, `integration`, `services`, `global`, and `showcase` are structural
234
239
  * facts: each is set only when the workspace physically ships the directory or
235
240
  * exact-case file that defines it, never because of the workspace's name and
236
- * never because a sibling fact is set.
241
+ * never because a sibling fact is set. An axis-dependent fact projects nothing
242
+ * when its required axis is absent: `integration` projects only a published
243
+ * `src`, and `showcase` projects only a browser `app`. The gate answers that case
244
+ * with a non-blocking question, so a caller that set the flag learns it emitted
245
+ * nothing and the compile still completes.
237
246
  */
238
247
  export declare interface Blueprint {
239
248
  readonly name: string;
@@ -262,10 +271,9 @@ export declare interface Blueprint {
262
271
  *
263
272
  * @example
264
273
  * ```ts
265
- * import type { Blueprint } from '@orkestrel/scaffold'
266
- * import { blueprintToConfigArtifacts } from '@orkestrel/scaffold'
274
+ * import { blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
267
275
  *
268
- * declare const blueprint: Blueprint
276
+ * const blueprint = createBlueprint('router', { src: ['core'] })
269
277
  *
270
278
  * blueprintToConfigArtifacts(blueprint)[0]?.path // 'tsconfig.json'
271
279
  * ```
@@ -298,10 +306,9 @@ export declare function blueprintToConfigArtifacts(blueprint: Blueprint): readon
298
306
  *
299
307
  * @example
300
308
  * ```ts
301
- * import type { Blueprint } from '@orkestrel/scaffold'
302
- * import { blueprintToDevDependencies } from '@orkestrel/scaffold'
309
+ * import { blueprintToDevDependencies, createBlueprint } from '@orkestrel/scaffold'
303
310
  *
304
- * declare const blueprint: Blueprint
311
+ * const blueprint = createBlueprint('router', { src: ['core'] })
305
312
  *
306
313
  * blueprintToDevDependencies(blueprint).typescript // the shared TypeScript pin
307
314
  * ```
@@ -344,12 +351,11 @@ export declare function blueprintToGuideArtifacts(blueprint: Blueprint): readonl
344
351
  *
345
352
  * @example
346
353
  * ```ts
347
- * import type { Blueprint } from '@orkestrel/scaffold'
348
- * import { blueprintToMachinery } from '@orkestrel/scaffold'
354
+ * import { blueprintToMachinery, createBlueprint } from '@orkestrel/scaffold'
349
355
  *
350
- * declare const blueprint: Blueprint
356
+ * const blueprint = createBlueprint('router', { app: ['browser'] })
351
357
  *
352
- * blueprintToMachinery(blueprint).vue // true when the app declares browser
358
+ * blueprintToMachinery(blueprint).vue // true
353
359
  * ```
354
360
  */
355
361
  export declare function blueprintToMachinery(blueprint: Blueprint): ViteMachinery;
@@ -382,10 +388,9 @@ export declare function blueprintToMachinery(blueprint: Blueprint): ViteMachiner
382
388
  *
383
389
  * @example
384
390
  * ```ts
385
- * import type { Blueprint } from '@orkestrel/scaffold'
386
- * import { blueprintToManifest } from '@orkestrel/scaffold'
391
+ * import { blueprintToManifest, createBlueprint } from '@orkestrel/scaffold'
387
392
  *
388
- * declare const blueprint: Blueprint
393
+ * const blueprint = createBlueprint('router', { src: ['core'] })
389
394
  *
390
395
  * blueprintToManifest(blueprint).endsWith('}\n') // true
391
396
  * ```
@@ -409,8 +414,8 @@ export declare function blueprintToOrchestrationArtifacts(blueprint: Blueprint):
409
414
  * Measure a blueprint against every law its own fields decide.
410
415
  *
411
416
  * @param blueprint - The workspace specification.
412
- * @returns One blocking question per rejected field, in blueprint field order,
413
- * with the rules that span several fields last.
417
+ * @returns One question per rejected field, in blueprint field order, with the
418
+ * rules that span several fields last.
414
419
  *
415
420
  * @remarks
416
421
  * Only the laws a blueprint answers alone are here. The structural record and
@@ -421,19 +426,25 @@ export declare function blueprintToOrchestrationArtifacts(blueprint: Blueprint):
421
426
  * a drafted plan belong to {@link artifactsToQuestions} and
422
427
  * {@link overridesToQuestions}.
423
428
  *
424
- * Every question is blocking, because each one describes a workspace this
425
- * package cannot generate rather than one it can generate imperfectly. An
426
- * environment question carries `ENVIRONMENTS` as its candidates, so a caller
427
- * reads the accepted values from the refusal instead of from the documentation.
429
+ * A question blocks when it describes a workspace this package cannot generate.
430
+ * Three do not, because each describes a workspace it can describe honestly and
431
+ * should not create: a published axis of several environments without core, whose
432
+ * manifest names a core build the workspace never runs, and a structural flag
433
+ * whose required axis is absent, which emits nothing. Blocking those closed the
434
+ * gate for every verb, and the verbs that read an existing workspace need the
435
+ * plan the gate refused. The caller that chooses the shape refuses the advisory;
436
+ * the callers that read one report it.
437
+ *
438
+ * An environment question carries `ENVIRONMENTS` as its candidates, so a caller
439
+ * reads the accepted values from the question instead of from the documentation.
428
440
  *
429
441
  * @example
430
442
  * ```ts
431
- * import type { Blueprint } from '@orkestrel/scaffold'
432
- * import { blueprintToQuestions } from '@orkestrel/scaffold'
443
+ * import { blueprintToQuestions, createBlueprint } from '@orkestrel/scaffold'
433
444
  *
434
- * declare const blueprint: Blueprint
445
+ * const blueprint = createBlueprint('router', { src: ['core'] })
435
446
  *
436
- * blueprintToQuestions(blueprint).length === 0 // true when the gate passes
447
+ * blueprintToQuestions(blueprint).length === 0 // true
437
448
  * ```
438
449
  */
439
450
  export declare function blueprintToQuestions(blueprint: Blueprint): readonly Question[];
@@ -446,10 +457,9 @@ export declare function blueprintToQuestions(blueprint: Blueprint): readonly Que
446
457
  *
447
458
  * @example
448
459
  * ```ts
449
- * import type { Blueprint } from '@orkestrel/scaffold'
450
- * import { blueprintToRootTsconfig } from '@orkestrel/scaffold'
460
+ * import { blueprintToRootTsconfig, createBlueprint } from '@orkestrel/scaffold'
451
461
  *
452
- * declare const blueprint: Blueprint
462
+ * const blueprint = createBlueprint('router', { src: ['core'] })
453
463
  *
454
464
  * blueprintToRootTsconfig(blueprint).startsWith('{') // true
455
465
  * ```
@@ -469,10 +479,9 @@ export declare function blueprintToRootTsconfig(blueprint: Blueprint): string;
469
479
  *
470
480
  * @example
471
481
  * ```ts
472
- * import type { Blueprint } from '@orkestrel/scaffold'
473
- * import { blueprintToRootVite } from '@orkestrel/scaffold'
482
+ * import { blueprintToRootVite, createBlueprint } from '@orkestrel/scaffold'
474
483
  *
475
- * declare const blueprint: Blueprint
484
+ * const blueprint = createBlueprint('router', { src: ['core'] })
476
485
  *
477
486
  * blueprintToRootVite(blueprint).includes('defineConfig') // true
478
487
  * ```
@@ -499,10 +508,9 @@ export declare function blueprintToRootVite(blueprint: Blueprint): string;
499
508
  *
500
509
  * @example
501
510
  * ```ts
502
- * import type { Blueprint } from '@orkestrel/scaffold'
503
- * import { blueprintToScripts } from '@orkestrel/scaffold'
511
+ * import { blueprintToScripts, createBlueprint } from '@orkestrel/scaffold'
504
512
  *
505
- * declare const blueprint: Blueprint
513
+ * const blueprint = createBlueprint('router', { src: ['core'] })
506
514
  *
507
515
  * blueprintToScripts(blueprint)['format:check'] // 'oxfmt --config .oxfmtrc.json --check .'
508
516
  * ```
@@ -516,16 +524,18 @@ export declare function blueprintToScripts(blueprint: Blueprint): Readonly<Recor
516
524
  * @returns Empty published barrels, selected application entries, and the optional bin entry.
517
525
  *
518
526
  * @remarks
519
- * The barrels and bin entry intentionally export nothing. A generated sample
520
- * entity is too easy to mistake for package implementation, so the scaffold
521
- * establishes only the selected environment boundaries.
527
+ * The barrels and every runtime entry intentionally hold nothing. A generated
528
+ * sample entity is too easy to mistake for package implementation, so the
529
+ * scaffold establishes only the selected environment boundaries. An application
530
+ * entry is empty for the same reason the bin entry is, and because the vendored
531
+ * lint config refuses an unassigned import outside a stylesheet, so the entry
532
+ * cannot start by importing its barrel for effect either.
522
533
  *
523
534
  * @example
524
535
  * ```ts
525
- * import type { Blueprint } from '@orkestrel/scaffold'
526
- * import { blueprintToSourceArtifacts } from '@orkestrel/scaffold'
536
+ * import { blueprintToSourceArtifacts, createBlueprint } from '@orkestrel/scaffold'
527
537
  *
528
- * declare const blueprint: Blueprint
538
+ * const blueprint = createBlueprint('router', { src: ['core'] })
529
539
  *
530
540
  * blueprintToSourceArtifacts(blueprint).every(({ group }) => group === 'source') // true
531
541
  * ```
@@ -548,10 +558,9 @@ export declare function blueprintToSourceArtifacts(blueprint: Blueprint): readon
548
558
  *
549
559
  * @example
550
560
  * ```ts
551
- * import type { Blueprint } from '@orkestrel/scaffold'
552
- * import { blueprintToTestArtifacts } from '@orkestrel/scaffold'
561
+ * import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
553
562
  *
554
- * declare const blueprint: Blueprint
563
+ * const blueprint = createBlueprint('router', { src: ['core'] })
555
564
  *
556
565
  * blueprintToTestArtifacts(blueprint)[0]?.path // 'tests/setup.ts'
557
566
  * ```
@@ -600,19 +609,61 @@ export declare const CATALOG_AGENT_PATH = ".claude/agents/orkestrel.md";
600
609
  * still a row: dropping it would hide a package the organization publishes
601
610
  * behind one failed request, and inventing a version would state something
602
611
  * upstream never said.
612
+ *
613
+ * `dependencies` are the RUNTIME edges the published version declares, which is
614
+ * what a publish order is computed over: a runtime bump obliges every dependent
615
+ * to re-pin and republish, while a development bump obliges nothing beyond the
616
+ * repository that declares it. No layer is recorded here, because a layer is a
617
+ * deterministic function of these edges across the whole catalog — a stored one
618
+ * could only disagree with the rows it was derived from. Read it through
619
+ * {@link catalogToLayers}.
603
620
  */
604
621
  export declare type CatalogEntry = {
605
622
  readonly name: string;
606
623
  readonly lookup: 'found';
607
624
  readonly version: string;
625
+ readonly dependencies: readonly Dependency[];
608
626
  readonly note?: never;
609
627
  } | {
610
628
  readonly name: string;
611
629
  readonly lookup: 'missing' | 'failed';
612
630
  readonly note: string;
613
631
  readonly version?: never;
632
+ readonly dependencies?: never;
614
633
  };
615
634
 
635
+ /**
636
+ * Project a catalog into the layers it publishes in.
637
+ *
638
+ * @param entries - The catalog rows to order.
639
+ * @returns One layer per round, each holding the names publishable together,
640
+ * sorted within the layer; a name whose edges never resolve is omitted.
641
+ *
642
+ * @remarks
643
+ * A layer is a deterministic function of the catalog's own edges, so it is
644
+ * computed here rather than stored on a row that could disagree with them.
645
+ * Only RUNTIME edges between catalogued packages count: a development
646
+ * dependency reaches no consumer, so it constrains nothing about publish order,
647
+ * and an edge leaving the fleet is a package this catalog does not publish.
648
+ *
649
+ * The order matters because these packages are `0.0.x`, where a caret pins one
650
+ * exact release. Publishing a dependent before its dependency leaves the
651
+ * dependent pinned to the older release, and two ranges that disagree install
652
+ * two copies of one package that the compiler reads as two distinct types.
653
+ *
654
+ * A cycle cannot be published in rounds, so its members are omitted rather than
655
+ * placed in an order that would be wrong. An absent name is the report: compare
656
+ * the returned names against the catalog to find one.
657
+ *
658
+ * @example
659
+ * ```ts
660
+ * import { catalogToLayers } from '@orkestrel/scaffold'
661
+ *
662
+ * catalogToLayers(entries)[0] // the names that depend on nothing in the fleet
663
+ * ```
664
+ */
665
+ export declare function catalogToLayers(entries: readonly CatalogEntry[]): ReadonlyArray<readonly string[]>;
666
+
616
667
  /**
617
668
  * Snapshot an untrusted value into exact JSON data the caller owns.
618
669
  *
@@ -759,12 +810,19 @@ export declare class Compiler implements CompilerInterface {
759
810
  * carries no plan, then `compile` with the whole outcome either way, so an
760
811
  * observer reads every compile from one event and the refusals from the other.
761
812
  *
813
+ * A plan says the blueprint can be built. It does not say the blueprint should
814
+ * be created, and the questions beside it are what this compiler could not
815
+ * settle. A caller creating a fresh workspace answers them first and writes
816
+ * nothing while any remains, which is the rule the `new` verb applies; a caller
817
+ * describing or repairing an existing target carries them through instead.
818
+ * Nothing downstream repeats that check, because only the caller knows which of
819
+ * the two it is.
820
+ *
762
821
  * @example
763
822
  * ```ts
764
- * import type { Blueprint } from '@orkestrel/scaffold'
765
- * import { createCompiler } from '@orkestrel/scaffold'
823
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
766
824
  *
767
- * declare const blueprint: Blueprint
825
+ * const blueprint = createBlueprint('router', { src: ['core'] })
768
826
  *
769
827
  * createCompiler().compile(blueprint, ['manifest']).plan?.artifacts.length // 1
770
828
  * ```
@@ -785,14 +843,19 @@ export declare class Compiler implements CompilerInterface {
785
843
  * reports no findings and carries the questions instead. Emits `block` in that
786
844
  * case, then `audit` with the verdict either way.
787
845
  *
846
+ * Ownership decides each verdict, not absence. A birth-owned path is never
847
+ * compared and reads `aligned` against a target holding nothing, while a
848
+ * content-owned path with no bytes to read is `missing`. An empty snapshot
849
+ * therefore produces both verdicts rather than one.
850
+ *
788
851
  * @example
789
852
  * ```ts
790
- * import type { Blueprint } from '@orkestrel/scaffold'
791
- * import { createCompiler } from '@orkestrel/scaffold'
853
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
792
854
  *
793
- * declare const blueprint: Blueprint
855
+ * const blueprint = createBlueprint('router', { src: ['core'] })
794
856
  *
795
- * createCompiler().audit(blueprint, {}).findings.every(({ drift }) => drift === 'missing') // true
857
+ * createCompiler().audit(blueprint, {}, ['manifest']).findings[0]?.drift // 'aligned'
858
+ * createCompiler().audit(blueprint, {}, ['configs']).findings[0]?.drift // 'missing'
796
859
  * ```
797
860
  */
798
861
  audit(blueprint: Blueprint, current: Snapshot, groups?: readonly Group[]): Audit;
@@ -941,7 +1004,7 @@ export declare class Compiler implements CompilerInterface {
941
1004
  export declare const CONFIG_TEMPLATES: Readonly<{
942
1005
  root: Readonly<{
943
1006
  tsconfig: "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"allowImportingTsExtensions\": true,\n\t\t\"lib\": [\"ESNext\", \"DOM\", \"DOM.Iterable\"],\n\t\t\"types\": [\"node\", \"vite/client\", \"vitest/globals\"],\n\t\t\"moduleDetection\": \"force\",\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": true,\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"noUncheckedSideEffectImports\": true,\n\t\t\"exactOptionalPropertyTypes\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"forceConsistentCasingInFileNames\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"noEmit\": true,\n\t\t\"paths\": {\n{{paths}}\n\t\t}\n\t},\n\t\"exclude\": [\"node_modules\", \"dist\", \"tmp\"]\n}\n";
944
- vite: "import type { UserConfig } from 'vite'\n{{imports}}\nimport { defineConfig, mergeConfig } from 'vitest/config'\nimport tsconfig from './tsconfig.json' with { type: 'json' }\nimport { environmentBoundary, outputBoundary } from './configs/helpers.js'\nimport { lstatSync, readdirSync, realpathSync } from 'node:fs'\nimport { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'\nimport { fileURLToPath, URL } from 'node:url'\n\nexport function resolveWorkspacePath(relativePath: string): string {\n\treturn fileURLToPath(new URL(relativePath, import.meta.url))\n}\n\n// A generated root config must classify its own fixed proof without importing\n// package source, so the exact-case check stays self-contained over Node APIs.\nfunction isExactCaseFile(path: string): boolean {\n\tconst full = resolvePath(path)\n\ttry {\n\t\tconst status = lstatSync(full)\n\t\tif (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1) return false\n\t\tconst root = parse(full).root\n\t\tconst segments = relative(root, full).split(sep)\n\t\tlet parent = root\n\t\tfor (const segment of segments) {\n\t\t\ttry {\n\t\t\t\tif (!readdirSync(parent).includes(segment)) return false\n\t\t\t} catch {\n\t\t\t\tif (basename(realpathSync.native(join(parent, segment))) !== segment) return false\n\t\t\t}\n\t\t\tparent = join(parent, segment)\n\t\t}\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst resolve = {\n\talias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {\n\t\tconst [path] = values\n\t\tif (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')\n\t\treturn Object.assign(aliases, { [key]: resolveWorkspacePath(path) })\n\t}, {}),\n}\n\n{{factories}}export default defineConfig({\n\tresolve,\n\ttest: {\n{{projects}}\n\t},\n})\n";
1007
+ vite: "import type { {{viteTypes}} } from 'vite'\n{{imports}}import { defineConfig, mergeConfig } from 'vitest/config'\nimport tsconfig from './tsconfig.json' with { type: 'json' }\n{{helpers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'\nimport { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'\nimport { fileURLToPath, URL } from 'node:url'\n\nexport function resolveWorkspacePath(relativePath: string): string {\n\treturn fileURLToPath(new URL(relativePath, import.meta.url))\n}\n\n// A generated root config must classify its own fixed proof without importing\n// package source, so the exact-case check stays self-contained over Node APIs.\nfunction isExactCaseFile(path: string): boolean {\n\tconst full = resolvePath(path)\n\ttry {\n\t\tconst status = lstatSync(full)\n\t\tif (!status.isFile() || status.isSymbolicLink() || status.nlink !== 1) return false\n\t\tconst root = parse(full).root\n\t\tconst segments = relative(root, full).split(sep)\n\t\tlet parent = root\n\t\tfor (const segment of segments) {\n\t\t\ttry {\n\t\t\t\tif (!readdirSync(parent).includes(segment)) return false\n\t\t\t} catch {\n\t\t\t\tif (basename(realpathSync.native(join(parent, segment))) !== segment) return false\n\t\t\t}\n\t\t\tparent = join(parent, segment)\n\t\t}\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nconst resolve = {\n\talias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {\n\t\tconst [path] = values\n\t\tif (path === undefined) throw new Error('tsconfig path alias ' + key + ' has no target')\n\t\treturn Object.assign(aliases, { [key]: resolveWorkspacePath(path) })\n\t}, {}),\n}\n\n{{factories}}export default defineConfig({\n\tresolve,\n\ttest: {\n{{projects}}\n\t},\n})\n";
945
1008
  }>;
946
1009
  factories: Readonly<{
947
1010
  src: Readonly<{
@@ -952,7 +1015,7 @@ export declare class Compiler implements CompilerInterface {
952
1015
  }>;
953
1016
  app: Readonly<{
954
1017
  core: "export const appCore = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [environmentBoundary('app/core')],\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:core', color: 'cyan' },\n\t\t\t\tinclude: ['tests/app/core/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
955
- browser: "function applicationBrowser(showcase: boolean): UserConfig {\n\tconst output = showcase ? 'dist/showcase' : 'dist/app/browser'\n\treturn {\n\t\tresolve,\n\t\tplugins: [\n\t\t\toutputBoundary(output),\n\t\t\tenvironmentBoundary('app/browser'),\n\t\t\tvue(),\n{{showcasePlugin}}\t\t],\n\t\troot: resolveWorkspacePath('app/browser'),\n\t\tpublicDir: false,\n\t\tbuild: {\n{{showcaseBuild}}\t\t\temptyOutDir: true,\n\t\t\toutDir: resolveWorkspacePath(output),\n\t\t\trolldownOptions: { input: resolveWorkspacePath('app/browser/index.html') },\n\t\t},\n\t\ttest: {\n\t\t\tname: { label: 'app:browser', color: 'blue' },\n\t\t\troot: resolveWorkspacePath('.'),\n\t\t\tdir: resolveWorkspacePath('.'),\n\t\t\tinclude: ['tests/app/browser/**/*.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n\t\t\tbrowser: {\n\t\t\t\tenabled: true,\n\t\t\t\tprovider: playwright(),\n\t\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t\t},\n\t\t\tfileParallelism: false,\n\t\t},\n\t}\n}\n\nexport function appBrowser(...options: never[]): UserConfig {\n\tif (options.length > 0) throw new Error('Browser configuration overrides are not permitted')\n\treturn applicationBrowser(false)\n}\n{{showcaseFactory}}";
1018
+ browser: "function applicationBrowser(showcase: boolean): UserConfig {\n\tconst output = showcase ? 'dist/showcase' : 'dist/app/browser'\n{{showcasePlugins}}\treturn {\n\t\tresolve,\n{{plugins}}\t\troot: resolveWorkspacePath('app/browser'),\n\t\tpublicDir: false,\n\t\tbuild: {\n{{showcaseBuild}}\t\t\temptyOutDir: true,\n\t\t\toutDir: resolveWorkspacePath(output),\n\t\t\trolldownOptions: { input: resolveWorkspacePath('app/browser/index.html') },\n\t\t},\n\t\ttest: {\n\t\t\tname: { label: 'app:browser', color: 'blue' },\n\t\t\troot: resolveWorkspacePath('.'),\n\t\t\tdir: resolveWorkspacePath('.'),\n\t\t\tinclude: ['tests/app/browser/**/*.test.ts'],\n\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'],\n\t\t\tbrowser: {\n\t\t\t\tenabled: true,\n\t\t\t\tprovider: playwright(),\n\t\t\t\tinstances: [{ browser: 'chromium', headless: true }],\n\t\t\t},\n\t\t\tfileParallelism: false,\n\t\t},\n\t}\n}\n\nexport function appBrowser(...options: never[]): UserConfig {\n\tif (options.length > 0) throw new Error('Browser configuration overrides are not permitted')\n\treturn applicationBrowser(false)\n}\n{{showcaseFactory}}";
956
1019
  server: "export const appServer = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\tpublicDir: false,\n\t\t\tplugins: [outputBoundary('dist/app/server'), environmentBoundary('app/server')],\n\t\t\tbuild: {\n\t\t\t\temptyOutDir: true,\n\t\t\t\tlib: {\n\t\t\t\t\tentry: resolveWorkspacePath('app/server/main.ts'),\n\t\t\t\t\tformats: ['cjs'],\n\t\t\t\t\tfileName: () => 'main.cjs',\n\t\t\t\t},\n\t\t\t\toutDir: resolveWorkspacePath('dist/app/server'),\n\t\t\t\ttarget: 'node22',\n\t\t\t\trolldownOptions: { external: (id: string) => id.startsWith('node:') },\n\t\t\t},\n\t\t\ttest: {\n\t\t\t\tname: { label: 'app:server', color: 'green' },\n\t\t\t\tinclude: ['tests/app/server/**/*.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts', './tests/setupServer.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
957
1020
  }>;
958
1021
  policy: "export const policy = (options?: UserConfig): UserConfig =>\n\tmergeConfig(\n\t\t{\n\t\t\tresolve,\n\t\t\ttest: {\n\t\t\t\tname: { label: 'policy', color: 'white' },\n\t\t\t\tinclude: ['tests/policy.test.ts'],\n\t\t\t\tsetupFiles: ['./tests/setup.ts'],\n\t\t\t\tenvironment: 'node',\n\t\t\t\tbrowser: { enabled: false },\n\t\t\t},\n\t\t},\n\t\toptions ?? {},\n\t)\n";
@@ -1171,6 +1234,21 @@ export declare class Compiler implements CompilerInterface {
1171
1234
  */
1172
1235
  export declare const ENVIRONMENTS: readonly Environment[];
1173
1236
 
1237
+ /**
1238
+ * The vendored paths a target receives with its executable bit set, frozen.
1239
+ *
1240
+ * @remarks
1241
+ * Declared rather than read from the staging host's filesystem, because that
1242
+ * reading is not portable: Windows carries no executable bit, so a host staged
1243
+ * there reports every file non-executable and every target receives hooks it
1244
+ * cannot run. Declaring the set here makes one checkout stage one manifest on
1245
+ * every host.
1246
+ *
1247
+ * Every entry is also a {@link HOST_PATHS} member or sits beneath one. A file
1248
+ * that must run when a target invokes it belongs here the moment it is vendored.
1249
+ */
1250
+ export declare const EXECUTABLE_PATHS: readonly string[];
1251
+
1174
1252
  /**
1175
1253
  * The development extra name syntax: any valid npm package name.
1176
1254
  *
@@ -1220,21 +1298,45 @@ export declare class Compiler implements CompilerInterface {
1220
1298
  * the audit showed is the one thing the destructive verb must never do. A
1221
1299
  * missing destination has no bytes to record. An aligned one may have gone
1222
1300
  * uncompared, which is what a birth-owned path always does, so it records
1223
- * bytes only where they were actually read.
1301
+ * bytes only where they were actually read. Every planned finding carries its
1302
+ * artifact's {@link Ownership}, so a consumer reads what scaffold claims at the
1303
+ * path from the finding itself. A foreign finding has no planned artifact and
1304
+ * therefore no ownership.
1305
+ *
1306
+ * Which combinations of `ownership`, `drift`, and `observed` a real audit
1307
+ * produces is {@link inferDrift}'s law, not this type's. A birth-owned path is
1308
+ * never compared and is always aligned, a presence-owned path compares existence
1309
+ * only, and bytes are recorded only where they were read. This shape therefore
1310
+ * admits a combination that law never produces — a birth-owned path reported
1311
+ * stale is the plainest one — and it admits it deliberately: restating the
1312
+ * comparison's case analysis here would be a second copy of it, able to disagree
1313
+ * with the one that decides. What closes the gap is the writer: `repair` and
1314
+ * `remove` re-derive every verdict themselves and refuse a caller's audit that
1315
+ * does not match, so a verdict the comparison could not have reached is refused
1316
+ * rather than acted on.
1224
1317
  */
1225
1318
  export declare type Finding = {
1226
1319
  readonly path: string;
1227
1320
  readonly group: Group;
1228
- readonly drift: 'stale' | 'foreign';
1321
+ readonly ownership: Ownership;
1322
+ readonly drift: 'stale';
1229
1323
  readonly observed: string;
1230
1324
  } | {
1231
1325
  readonly path: string;
1232
1326
  readonly group: Group;
1327
+ readonly ownership?: never;
1328
+ readonly drift: 'foreign';
1329
+ readonly observed: string;
1330
+ } | {
1331
+ readonly path: string;
1332
+ readonly group: Group;
1333
+ readonly ownership: Ownership;
1233
1334
  readonly drift: 'missing';
1234
1335
  readonly observed?: never;
1235
1336
  } | {
1236
1337
  readonly path: string;
1237
1338
  readonly group: Group;
1339
+ readonly ownership: Ownership;
1238
1340
  readonly drift: 'aligned';
1239
1341
  readonly observed?: string;
1240
1342
  };
@@ -1381,7 +1483,7 @@ export declare class Compiler implements CompilerInterface {
1381
1483
  /** The installed-package proof whose presence makes a workspace `integration`. */
1382
1484
  export declare const INTEGRATION_TEST_PATH = "tests/integration.test.ts";
1383
1485
 
1384
- /** Visible characters a portable path and a Markdown path cell both forbid. */
1486
+ /** Visible characters a target-relative path and a Markdown path cell both forbid. */
1385
1487
  export declare const INVALID_PATH_CHARACTER_PATTERN: RegExp;
1386
1488
 
1387
1489
  /**
@@ -1408,7 +1510,8 @@ export declare class Compiler implements CompilerInterface {
1408
1510
  *
1409
1511
  * @remarks
1410
1512
  * An audit reaches the writer and the destructive verb, so it is guarded as
1411
- * strictly as the plan beside it.
1513
+ * strictly as the plan beside it. Findings use the sum of the two producer
1514
+ * bounds: one per planned artifact, then one per unplanned snapshot path.
1412
1515
  */
1413
1516
  export declare const isAudit: Guard<Audit>;
1414
1517
 
@@ -1552,7 +1655,13 @@ export declare class Compiler implements CompilerInterface {
1552
1655
  * @remarks
1553
1656
  * `observed` is required exactly where the mutation it precedes is held to it,
1554
1657
  * absent where the destination had no bytes to record, and optional where the
1555
- * comparison may not have been made.
1658
+ * comparison may not have been made. Planned findings require `ownership`;
1659
+ * foreign findings forbid it because no artifact was planned for their path.
1660
+ *
1661
+ * That is the whole claim. This guard proves the shape a reader may destructure
1662
+ * and nothing about whether the verdict is one an audit could have reached: the
1663
+ * correlation between `ownership`, `drift`, and `observed` belongs to
1664
+ * {@link inferDrift}, and it is re-derived at the verb that acts on the finding.
1556
1665
  */
1557
1666
  export declare const isFinding: Guard<Finding>;
1558
1667
 
@@ -1617,19 +1726,20 @@ export declare class Compiler implements CompilerInterface {
1617
1726
  export declare const isOverride: Guard<Override>;
1618
1727
 
1619
1728
  /**
1620
- * Narrow a value to a portable target-relative path.
1729
+ * Narrow a value to a logical target-relative path.
1621
1730
  *
1622
1731
  * @param value - The candidate path.
1623
1732
  * @returns `true` for a bounded relative path with no traversal, empty segment,
1624
- * control character, or non-portable visible character.
1733
+ * control character, or reserved syntax character.
1625
1734
  *
1626
1735
  * @remarks
1627
1736
  * Every path this package reads or writes passes here, so one law covers a
1628
1737
  * planned artifact, an override target, an audit finding, a guide mirror, and a
1629
1738
  * snapshot key. Rejecting `..`, a leading `/`, and a backslash at the guard is
1630
1739
  * what stops a caller-supplied path from naming a destination outside the
1631
- * target, and rejecting the non-portable visible characters is what keeps a
1632
- * generated workspace checkable out on every supported filesystem.
1740
+ * target. Host-location validation is a separate server boundary: this guard
1741
+ * does not reject a device spelling, a trailing dot or space, or a segment that
1742
+ * exceeds a host filesystem's byte ceiling.
1633
1743
  *
1634
1744
  * @example
1635
1745
  * ```ts
@@ -1763,6 +1873,20 @@ export declare class Compiler implements CompilerInterface {
1763
1873
  */
1764
1874
  export declare function manifestToName(manifest: string): string | undefined;
1765
1875
 
1876
+ /**
1877
+ * Test whether {@link inferDrift} could have produced a finding for an ownership.
1878
+ *
1879
+ * @param ownership - What scaffold claims at the planned path.
1880
+ * @param finding - The audit verdict to test.
1881
+ * @returns Whether the ownership and verdict are reachable through {@link inferDrift}.
1882
+ *
1883
+ * @remarks
1884
+ * This predicate keeps the comparison law beside the reachability law it
1885
+ * restates. A mutation uses it so a refusal can distinguish an impossible
1886
+ * verdict from a target that genuinely moved after its audit.
1887
+ */
1888
+ export declare function matchesDriftReachability(ownership: Ownership, finding: Finding): boolean;
1889
+
1766
1890
  /**
1767
1891
  * Test whether a declared engines floor is at or above the supported minimum.
1768
1892
  *
@@ -1861,6 +1985,9 @@ export declare class Compiler implements CompilerInterface {
1861
1985
  /** Maximum length of the hexadecimal string carrying one artifact's bytes. */
1862
1986
  export declare const MAX_ARTIFACT_HEX_LENGTH: number;
1863
1987
 
1988
+ /** Maximum findings one audit can produce from a bounded plan and snapshot. */
1989
+ export declare const MAX_AUDIT_FINDINGS: number;
1990
+
1864
1991
  /** Maximum items accepted in one public collection. */
1865
1992
  export declare const MAX_COLLECTION_ITEMS = 1000;
1866
1993
 
@@ -2024,9 +2151,10 @@ export declare class Compiler implements CompilerInterface {
2024
2151
  *
2025
2152
  * @remarks
2026
2153
  * `content` replaces the rendered artifact at `path` and never partially
2027
- * merges it. An override that matches no planned artifact, that targets a
2028
- * host-origin artifact, or that targets the manifest is a blocking question
2029
- * rather than a silent no-op.
2154
+ * merges it. Legality is measured against every artifact the blueprint drafts,
2155
+ * before a compile narrows the returned groups. An override that matches none
2156
+ * of those artifacts, that targets a host-origin artifact, or that targets the
2157
+ * manifest is a blocking question rather than a silent no-op.
2030
2158
  */
2031
2159
  export declare interface Override {
2032
2160
  readonly path: string;
@@ -2051,10 +2179,9 @@ export declare class Compiler implements CompilerInterface {
2051
2179
  *
2052
2180
  * @example
2053
2181
  * ```ts
2054
- * import type { Artifact } from '@orkestrel/scaffold'
2055
- * import { overridesToQuestions } from '@orkestrel/scaffold'
2182
+ * import { blueprintToConfigArtifacts, createBlueprint, overridesToQuestions } from '@orkestrel/scaffold'
2056
2183
  *
2057
- * declare const artifacts: readonly Artifact[]
2184
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
2058
2185
  *
2059
2186
  * overridesToQuestions([{ path: 'package.json', content: '{}\n' }], artifacts).length // 1
2060
2187
  * ```
@@ -2224,12 +2351,11 @@ export declare class Compiler implements CompilerInterface {
2224
2351
  *
2225
2352
  * @example
2226
2353
  * ```ts
2227
- * import type { Plan } from '@orkestrel/scaffold'
2228
- * import { planToFindings } from '@orkestrel/scaffold'
2354
+ * import { createBlueprint, createCompiler, planToFindings } from '@orkestrel/scaffold'
2229
2355
  *
2230
- * declare const plan: Plan
2356
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
2231
2357
  *
2232
- * planToFindings(plan, { 'AGENTS.md': '68690a' })
2358
+ * plan === undefined ? [] : planToFindings(plan, { 'AGENTS.md': '68690a' })
2233
2359
  * ```
2234
2360
  */
2235
2361
  export declare function planToFindings(plan: Plan, current: Snapshot): readonly Finding[];
@@ -2255,12 +2381,11 @@ export declare class Compiler implements CompilerInterface {
2255
2381
  *
2256
2382
  * @example
2257
2383
  * ```ts
2258
- * import type { Plan } from '@orkestrel/scaffold'
2259
- * import { planToHash } from '@orkestrel/scaffold'
2384
+ * import { createBlueprint, createCompiler, planToHash } from '@orkestrel/scaffold'
2260
2385
  *
2261
- * declare const plan: Plan
2386
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
2262
2387
  *
2263
- * planToHash(plan)?.length // 16
2388
+ * plan === undefined ? undefined : planToHash(plan)?.length // 16
2264
2389
  * ```
2265
2390
  */
2266
2391
  export declare function planToHash(plan: Plan): string | undefined;
@@ -2331,16 +2456,20 @@ export declare class Compiler implements CompilerInterface {
2331
2456
  * The one error this package throws, carrying the coded reason it was raised.
2332
2457
  *
2333
2458
  * @remarks
2334
- * A throw is reserved for a caller the package cannot serve: `INVALID` for
2335
- * off-contract input, `DESTROYED` for any call made after teardown, `TARGET`
2336
- * for a destination that is not what the caller's observation said it was,
2337
- * `WRITE` for a mutation that could not be completed, and `FETCH` for an
2338
- * upstream read that produced no answer the caller can be given.
2339
- *
2340
- * A refused blueprint is not one of these. The gate fails closed and returns
2341
- * the questions that closed it, and `BLOCKED` is the code those stage records
2342
- * carry, so a caller reads a refusal from the value it asked for rather than
2343
- * from a stack.
2459
+ * Each code names one cause: `INVALID` for off-contract input, `DESTROYED` for
2460
+ * any call made after teardown, `TARGET` for a destination that is not what the
2461
+ * caller's observation said it was, `WRITE` for a mutation that could not be
2462
+ * completed, `FETCH` for an upstream read that produced no answer the caller can
2463
+ * be given, and `BLOCKED` for a refused blueprint.
2464
+ *
2465
+ * `BLOCKED` covers both refusals a blueprint can meet, because they are one fact
2466
+ * this blueprint will not be built and the questions say which. The compiler
2467
+ * answers its refusal rather than throwing it: the gate fails closed, returns the
2468
+ * questions that closed it, and records `BLOCKED` on its stage, so a caller reads
2469
+ * that refusal from the value it asked for. A verb that creates a workspace
2470
+ * throws it, because it chose the shape and has nothing to hand back. A blocking
2471
+ * question closed the gate; a non-blocking one is a shape this package can
2472
+ * describe and declines to create.
2344
2473
  *
2345
2474
  * `context` carries whatever the raising site can say about the failure. It is
2346
2475
  * `unknown` because nothing narrows it usefully at the catch site; read it for