@orkestrel/scaffold 0.0.25 → 0.0.26

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.
@@ -140,6 +140,25 @@ var HOST_PATHS = Object.freeze([
140
140
  "guides/scaffold.md"
141
141
  ]);
142
142
  /**
143
+ * The vendored paths a target receives with its executable bit set, frozen.
144
+ *
145
+ * @remarks
146
+ * Declared rather than read from the staging host's filesystem, because that
147
+ * reading is not portable: Windows carries no executable bit, so a host staged
148
+ * there reports every file non-executable and every target receives hooks it
149
+ * cannot run. Declaring the set here makes one checkout stage one manifest on
150
+ * every host.
151
+ *
152
+ * Every entry is also a {@link HOST_PATHS} member or sits beneath one. A file
153
+ * that must run when a target invokes it belongs here the moment it is vendored.
154
+ */
155
+ var EXECUTABLE_PATHS = Object.freeze([
156
+ "scripts/codex.sh",
157
+ "scripts/cursor.sh",
158
+ "scripts/deps.sh",
159
+ "scripts/ollama.sh"
160
+ ]);
161
+ /**
143
162
  * The path prefixes whose contents instruct or wire an agent, frozen.
144
163
  *
145
164
  * @remarks
@@ -227,7 +246,7 @@ var ENGINES_PATTERN = /^>=(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
227
246
  var HEX_PATTERN = /^(?:[0-9a-f]{2})*$/;
228
247
  /** Unicode controls, formatting controls, and line and paragraph separators rejected in text. */
229
248
  var CONTROL_CHARACTER_PATTERN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
230
- /** Visible characters a portable path and a Markdown path cell both forbid. */
249
+ /** Visible characters a target-relative path and a Markdown path cell both forbid. */
231
250
  var INVALID_PATH_CHARACTER_PATTERN = /[<>:"|?*\\]/;
232
251
  /**
233
252
  * Maximum bare workspace name length.
@@ -245,6 +264,8 @@ var MAX_RANGE_LENGTH = 2048;
245
264
  var MAX_PATH_LENGTH = 32767;
246
265
  /** Maximum items accepted in one public collection. */
247
266
  var MAX_COLLECTION_ITEMS = 1e3;
267
+ /** Maximum findings one audit can produce from a bounded plan and snapshot. */
268
+ var MAX_AUDIT_FINDINGS = MAX_COLLECTION_ITEMS * 2;
248
269
  /** Maximum bytes accepted for one artifact. */
249
270
  var MAX_ARTIFACT_BYTES = 5242880;
250
271
  /** Maximum length of the hexadecimal string carrying one artifact's bytes. */
@@ -263,7 +284,7 @@ var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
263
284
  var BASE_DEV_DEPENDENCIES = Object.freeze({
264
285
  "@microsoft/api-extractor": "^7.58.12",
265
286
  "@orkestrel/guide": "^0.0.9",
266
- "@orkestrel/scaffold": "^0.0.25",
287
+ "@orkestrel/scaffold": "^0.0.26",
267
288
  "@types/node": "^26.2.0",
268
289
  oxfmt: "^0.62.0",
269
290
  oxlint: "^1.77.0",
@@ -356,12 +377,10 @@ var CONFIG_TEMPLATES = Object.freeze({
356
377
  "exclude": ["node_modules", "dist", "tmp"]
357
378
  }
358
379
  `,
359
- vite: `import type { UserConfig } from 'vite'
360
- {{imports}}
361
- import { defineConfig, mergeConfig } from 'vitest/config'
380
+ vite: `import type { {{viteTypes}} } from 'vite'
381
+ {{imports}}import { defineConfig, mergeConfig } from 'vitest/config'
362
382
  import tsconfig from './tsconfig.json' with { type: 'json' }
363
- import { environmentBoundary, outputBoundary } from './configs/helpers.js'
364
- import { lstatSync, readdirSync, realpathSync } from 'node:fs'
383
+ {{helpers}}import { lstatSync, readdirSync, realpathSync } from 'node:fs'
365
384
  import { basename, join, parse, relative, resolve as resolvePath, sep } from 'node:path'
366
385
  import { fileURLToPath, URL } from 'node:url'
367
386
 
@@ -553,14 +572,9 @@ const resolve = {
553
572
  `,
554
573
  browser: `function applicationBrowser(showcase: boolean): UserConfig {
555
574
  const output = showcase ? 'dist/showcase' : 'dist/app/browser'
556
- return {
575
+ {{showcasePlugins}} return {
557
576
  resolve,
558
- plugins: [
559
- outputBoundary(output),
560
- environmentBoundary('app/browser'),
561
- vue(),
562
- {{showcasePlugin}} ],
563
- root: resolveWorkspacePath('app/browser'),
577
+ {{plugins}} root: resolveWorkspacePath('app/browser'),
564
578
  publicDir: false,
565
579
  build: {
566
580
  {{showcaseBuild}} emptyOutDir: true,
@@ -943,9 +957,13 @@ export default defineConfig(appShowcase())
943
957
  *
944
958
  * @remarks
945
959
  * Builders in `compilers.ts` fill every varying span through
946
- * `@orkestrel/template`. Empty barrels and setup modules are intentional: the
947
- * generated workspace starts with no sample domain API, while each selected
948
- * Vitest project gets a real test that proves its barrel has no starter exports.
960
+ * `@orkestrel/template`. Empty barrels, entries, and setup modules are
961
+ * intentional: the generated workspace starts with no sample domain API, while
962
+ * each selected Vitest project gets a real test that proves its barrel has no
963
+ * starter exports. An entry starts empty for the same reason its barrel does,
964
+ * and because the vendored lint config refuses an unassigned import outside a
965
+ * stylesheet, so a starter `import './index.js'` would fail the workspace's own
966
+ * `lint:check` on the day it is written.
949
967
  *
950
968
  * @example
951
969
  * ```ts
@@ -957,8 +975,6 @@ export default defineConfig(appShowcase())
957
975
  var ARTIFACT_TEMPLATES = Object.freeze({
958
976
  source: Object.freeze({
959
977
  empty: "",
960
- main: `import './index.js'
961
- `,
962
978
  browser: `<!doctype html>
963
979
  <html lang="en">
964
980
  <head>
@@ -1143,16 +1159,20 @@ printf '%s\\n' \\
1143
1159
  * The one error this package throws, carrying the coded reason it was raised.
1144
1160
  *
1145
1161
  * @remarks
1146
- * A throw is reserved for a caller the package cannot serve: `INVALID` for
1147
- * off-contract input, `DESTROYED` for any call made after teardown, `TARGET`
1148
- * for a destination that is not what the caller's observation said it was,
1149
- * `WRITE` for a mutation that could not be completed, and `FETCH` for an
1150
- * upstream read that produced no answer the caller can be given.
1151
- *
1152
- * A refused blueprint is not one of these. The gate fails closed and returns
1153
- * the questions that closed it, and `BLOCKED` is the code those stage records
1154
- * carry, so a caller reads a refusal from the value it asked for rather than
1155
- * from a stack.
1162
+ * Each code names one cause: `INVALID` for off-contract input, `DESTROYED` for
1163
+ * any call made after teardown, `TARGET` for a destination that is not what the
1164
+ * caller's observation said it was, `WRITE` for a mutation that could not be
1165
+ * completed, `FETCH` for an upstream read that produced no answer the caller can
1166
+ * be given, and `BLOCKED` for a refused blueprint.
1167
+ *
1168
+ * `BLOCKED` covers both refusals a blueprint can meet, because they are one fact
1169
+ * this blueprint will not be built and the questions say which. The compiler
1170
+ * answers its refusal rather than throwing it: the gate fails closed, returns the
1171
+ * questions that closed it, and records `BLOCKED` on its stage, so a caller reads
1172
+ * that refusal from the value it asked for. A verb that creates a workspace
1173
+ * throws it, because it chose the shape and has nothing to hand back. A blocking
1174
+ * question closed the gate; a non-blocking one is a shape this package can
1175
+ * describe and declines to create.
1156
1176
  *
1157
1177
  * `context` carries whatever the raising site can say about the failure. It is
1158
1178
  * `unknown` because nothing narrows it usefully at the catch site; read it for
@@ -1206,19 +1226,20 @@ function isScaffoldError(value) {
1206
1226
  //#endregion
1207
1227
  //#region src/core/validators.ts
1208
1228
  /**
1209
- * Narrow a value to a portable target-relative path.
1229
+ * Narrow a value to a logical target-relative path.
1210
1230
  *
1211
1231
  * @param value - The candidate path.
1212
1232
  * @returns `true` for a bounded relative path with no traversal, empty segment,
1213
- * control character, or non-portable visible character.
1233
+ * control character, or reserved syntax character.
1214
1234
  *
1215
1235
  * @remarks
1216
1236
  * Every path this package reads or writes passes here, so one law covers a
1217
1237
  * planned artifact, an override target, an audit finding, a guide mirror, and a
1218
1238
  * snapshot key. Rejecting `..`, a leading `/`, and a backslash at the guard is
1219
1239
  * what stops a caller-supplied path from naming a destination outside the
1220
- * target, and rejecting the non-portable visible characters is what keeps a
1221
- * generated workspace checkable out on every supported filesystem.
1240
+ * target. Host-location validation is a separate server boundary: this guard
1241
+ * does not reject a device spelling, a trailing dot or space, or a segment that
1242
+ * exceeds a host filesystem's byte ceiling.
1222
1243
  *
1223
1244
  * @example
1224
1245
  * ```ts
@@ -1496,20 +1517,34 @@ var isQuestion = (0, _orkestrel_contract.recordOf)({
1496
1517
  * @remarks
1497
1518
  * `observed` is required exactly where the mutation it precedes is held to it,
1498
1519
  * absent where the destination had no bytes to record, and optional where the
1499
- * comparison may not have been made.
1520
+ * comparison may not have been made. Planned findings require `ownership`;
1521
+ * foreign findings forbid it because no artifact was planned for their path.
1522
+ *
1523
+ * That is the whole claim. This guard proves the shape a reader may destructure
1524
+ * and nothing about whether the verdict is one an audit could have reached: the
1525
+ * correlation between `ownership`, `drift`, and `observed` belongs to
1526
+ * {@link inferDrift}, and it is re-derived at the verb that acts on the finding.
1500
1527
  */
1501
1528
  var isFinding = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.recordOf)({
1502
1529
  path: isPath,
1503
1530
  group: isGroup,
1504
- drift: (0, _orkestrel_contract.literalOf)("stale", "foreign"),
1531
+ ownership: (0, _orkestrel_contract.literalOf)("content", "presence", "birth"),
1532
+ drift: (0, _orkestrel_contract.literalOf)("stale"),
1533
+ observed: isHex
1534
+ }), (0, _orkestrel_contract.recordOf)({
1535
+ path: isPath,
1536
+ group: isGroup,
1537
+ drift: (0, _orkestrel_contract.literalOf)("foreign"),
1505
1538
  observed: isHex
1506
1539
  }), (0, _orkestrel_contract.recordOf)({
1507
1540
  path: isPath,
1508
1541
  group: isGroup,
1542
+ ownership: (0, _orkestrel_contract.literalOf)("content", "presence", "birth"),
1509
1543
  drift: (0, _orkestrel_contract.literalOf)("missing")
1510
1544
  }), (0, _orkestrel_contract.recordOf)({
1511
1545
  path: isPath,
1512
1546
  group: isGroup,
1547
+ ownership: (0, _orkestrel_contract.literalOf)("content", "presence", "birth"),
1513
1548
  drift: (0, _orkestrel_contract.literalOf)("aligned"),
1514
1549
  observed: isHex
1515
1550
  }, ["observed"]));
@@ -1518,10 +1553,11 @@ var isFinding = (0, _orkestrel_contract.unionOf)((0, _orkestrel_contract.recordO
1518
1553
  *
1519
1554
  * @remarks
1520
1555
  * An audit reaches the writer and the destructive verb, so it is guarded as
1521
- * strictly as the plan beside it.
1556
+ * strictly as the plan beside it. Findings use the sum of the two producer
1557
+ * bounds: one per planned artifact, then one per unplanned snapshot path.
1522
1558
  */
1523
1559
  var isAudit = (0, _orkestrel_contract.recordOf)({
1524
- findings: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isFinding)),
1560
+ findings: (0, _orkestrel_contract.andOf)((value) => (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isArray)(value) && value.length <= 2e3), (0, _orkestrel_contract.arrayOf)(isFinding)),
1525
1561
  questions: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isQuestion))
1526
1562
  });
1527
1563
  /**
@@ -2111,6 +2147,23 @@ function inferDrift(artifact, observed) {
2111
2147
  return observed === artifactToHex(artifact) ? "aligned" : "stale";
2112
2148
  }
2113
2149
  /**
2150
+ * Test whether {@link inferDrift} could have produced a finding for an ownership.
2151
+ *
2152
+ * @param ownership - What scaffold claims at the planned path.
2153
+ * @param finding - The audit verdict to test.
2154
+ * @returns Whether the ownership and verdict are reachable through {@link inferDrift}.
2155
+ *
2156
+ * @remarks
2157
+ * This predicate keeps the comparison law beside the reachability law it
2158
+ * restates. A mutation uses it so a refusal can distinguish an impossible
2159
+ * verdict from a target that genuinely moved after its audit.
2160
+ */
2161
+ function matchesDriftReachability(ownership, finding) {
2162
+ if (finding.drift === "aligned") return ownership === "birth" || finding.observed !== void 0;
2163
+ if (finding.drift === "missing") return ownership !== "birth";
2164
+ return finding.drift === "stale" && ownership === "content";
2165
+ }
2166
+ /**
2114
2167
  * Project a plan into its tally by artifact origin.
2115
2168
  *
2116
2169
  * @param plan - The plan to summarize.
@@ -2538,10 +2591,9 @@ function srcToExports(src) {
2538
2591
  *
2539
2592
  * @example
2540
2593
  * ```ts
2541
- * import type { Blueprint } from '@orkestrel/scaffold'
2542
- * import { blueprintToDevDependencies } from '@orkestrel/scaffold'
2594
+ * import { blueprintToDevDependencies, createBlueprint } from '@orkestrel/scaffold'
2543
2595
  *
2544
- * declare const blueprint: Blueprint
2596
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2545
2597
  *
2546
2598
  * blueprintToDevDependencies(blueprint).typescript // the shared TypeScript pin
2547
2599
  * ```
@@ -2552,7 +2604,7 @@ function blueprintToDevDependencies(blueprint) {
2552
2604
  ...blueprint.src.includes("browser") ? SOURCE_BROWSER_DEV_DEPENDENCIES : {},
2553
2605
  ...blueprint.app.length > 0 ? APP_DEV_DEPENDENCIES : {},
2554
2606
  ...blueprint.app.includes("browser") ? APP_BROWSER_DEV_DEPENDENCIES : {},
2555
- ...blueprint.showcase ? SHOWCASE_DEV_DEPENDENCIES : {},
2607
+ ...blueprint.showcase && blueprint.app.includes("browser") ? SHOWCASE_DEV_DEPENDENCIES : {},
2556
2608
  ...blueprint.app.includes("server") ? APP_SERVER_DEV_DEPENDENCIES : {}
2557
2609
  };
2558
2610
  for (const extra of blueprint.extras) merged[extra.name] = extra.range;
@@ -2581,10 +2633,9 @@ function blueprintToDevDependencies(blueprint) {
2581
2633
  *
2582
2634
  * @example
2583
2635
  * ```ts
2584
- * import type { Blueprint } from '@orkestrel/scaffold'
2585
- * import { blueprintToScripts } from '@orkestrel/scaffold'
2636
+ * import { blueprintToScripts, createBlueprint } from '@orkestrel/scaffold'
2586
2637
  *
2587
- * declare const blueprint: Blueprint
2638
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2588
2639
  *
2589
2640
  * blueprintToScripts(blueprint)['format:check'] // 'oxfmt --config .oxfmtrc.json --check .'
2590
2641
  * ```
@@ -2700,10 +2751,9 @@ function blueprintToScripts(blueprint) {
2700
2751
  *
2701
2752
  * @example
2702
2753
  * ```ts
2703
- * import type { Blueprint } from '@orkestrel/scaffold'
2704
- * import { blueprintToManifest } from '@orkestrel/scaffold'
2754
+ * import { blueprintToManifest, createBlueprint } from '@orkestrel/scaffold'
2705
2755
  *
2706
- * declare const blueprint: Blueprint
2756
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2707
2757
  *
2708
2758
  * blueprintToManifest(blueprint).endsWith('}\n') // true
2709
2759
  * ```
@@ -2779,12 +2829,11 @@ function blueprintToManifest(blueprint) {
2779
2829
  *
2780
2830
  * @example
2781
2831
  * ```ts
2782
- * import type { Blueprint } from '@orkestrel/scaffold'
2783
- * import { blueprintToMachinery } from '@orkestrel/scaffold'
2832
+ * import { blueprintToMachinery, createBlueprint } from '@orkestrel/scaffold'
2784
2833
  *
2785
- * declare const blueprint: Blueprint
2834
+ * const blueprint = createBlueprint('router', { app: ['browser'] })
2786
2835
  *
2787
- * blueprintToMachinery(blueprint).vue // true when the app declares browser
2836
+ * blueprintToMachinery(blueprint).vue // true
2788
2837
  * ```
2789
2838
  */
2790
2839
  function blueprintToMachinery(blueprint) {
@@ -2804,10 +2853,9 @@ function blueprintToMachinery(blueprint) {
2804
2853
  *
2805
2854
  * @example
2806
2855
  * ```ts
2807
- * import type { Blueprint } from '@orkestrel/scaffold'
2808
- * import { blueprintToRootTsconfig } from '@orkestrel/scaffold'
2856
+ * import { blueprintToRootTsconfig, createBlueprint } from '@orkestrel/scaffold'
2809
2857
  *
2810
- * declare const blueprint: Blueprint
2858
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2811
2859
  *
2812
2860
  * blueprintToRootTsconfig(blueprint).startsWith('{') // true
2813
2861
  * ```
@@ -2832,10 +2880,9 @@ function blueprintToRootTsconfig(blueprint) {
2832
2880
  *
2833
2881
  * @example
2834
2882
  * ```ts
2835
- * import type { Blueprint } from '@orkestrel/scaffold'
2836
- * import { blueprintToRootVite } from '@orkestrel/scaffold'
2883
+ * import { blueprintToRootVite, createBlueprint } from '@orkestrel/scaffold'
2837
2884
  *
2838
- * declare const blueprint: Blueprint
2885
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2839
2886
  *
2840
2887
  * blueprintToRootVite(blueprint).includes('defineConfig') // true
2841
2888
  * ```
@@ -2865,8 +2912,7 @@ function blueprintToRootVite(blueprint) {
2865
2912
  const core = blueprint.src.includes("core");
2866
2913
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.src.server, {
2867
2914
  external: core ? `external: (id: string) =>
2868
- id === '@src/core' || id.startsWith('node:') || id.startsWith('@orkestrel/'),` : `external: (id: string) =>
2869
- id.startsWith('node:') || id.startsWith('@orkestrel/'),`,
2915
+ id === '@src/core' || id.startsWith('node:') || id.startsWith('@orkestrel/'),` : "external: (id: string) => id.startsWith('node:') || id.startsWith('@orkestrel/'),",
2870
2916
  output: core ? `\t\t\t\t\toutput: [
2871
2917
  {
2872
2918
  format: 'es',
@@ -2892,28 +2938,35 @@ function blueprintToRootVite(blueprint) {
2892
2938
  projects.push("appCore");
2893
2939
  }
2894
2940
  if (blueprint.app.includes("browser")) {
2895
- const showcasePlugin = machinery.showcase ? `\t\t\t...(showcase
2896
- ? [
2897
- viteSingleFile({
2898
- removeViteModuleLoader: true,
2899
- useRecommendedBuildConfig: true,
2900
- }),
2901
- {
2902
- name: 'orkestrel-showcase-html',
2903
- transformIndexHtml: {
2904
- order: 'post',
2905
- handler(html) {
2906
- const stamp = new Date().toISOString()
2907
- return html.replace(
2908
- '</head>',
2909
- '\t\t<meta name="build-id" content="' + stamp + '" />\\n\t</head>',
2910
- )
2911
- },
2912
- },
2941
+ const showcasePlugins = machinery.showcase ? ` const showcasePlugins: PluginOption[] = showcase
2942
+ ? [
2943
+ viteSingleFile({
2944
+ removeViteModuleLoader: true,
2945
+ useRecommendedBuildConfig: true,
2946
+ }),
2947
+ {
2948
+ name: 'orkestrel-showcase-html',
2949
+ transformIndexHtml: {
2950
+ order: 'post',
2951
+ handler(html) {
2952
+ const stamp = new Date().toISOString()
2953
+ return html.replace(
2954
+ '</head>',
2955
+ '\t\t<meta name="build-id" content="' + stamp + '" />\\n\t</head>',
2956
+ )
2913
2957
  },
2914
- ]
2915
- : []),
2958
+ },
2959
+ },
2960
+ ]
2961
+ : []
2916
2962
  ` : "";
2963
+ const plugins = machinery.showcase ? `\t\tplugins: [
2964
+ outputBoundary(output),
2965
+ environmentBoundary('app/browser'),
2966
+ vue(),
2967
+ ...showcasePlugins,
2968
+ ],
2969
+ ` : " plugins: [outputBoundary(output), environmentBoundary('app/browser'), vue()],\n";
2917
2970
  const showcaseBuild = machinery.showcase ? `\t\t\t...(showcase
2918
2971
  ? {
2919
2972
  cssMinify: 'lightningcss',
@@ -2932,11 +2985,12 @@ export function appShowcase(...options: never[]): UserConfig {
2932
2985
  }
2933
2986
  ` : "";
2934
2987
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.app.browser, {
2935
- showcasePlugin,
2988
+ plugins,
2989
+ showcasePlugins,
2936
2990
  showcaseBuild,
2937
2991
  showcaseFactory
2938
2992
  }));
2939
- projects.push("appBrowser");
2993
+ projects.push("appBrowser()");
2940
2994
  }
2941
2995
  if (blueprint.app.includes("server")) {
2942
2996
  factories.push(CONFIG_TEMPLATES.factories.app.server);
@@ -2957,9 +3011,13 @@ export function appShowcase(...options: never[]): UserConfig {
2957
3011
  const projectRows = `\t\tprojects: [
2958
3012
  ${projects.map((project) => `\t\t\t${project},`).join("\n")}
2959
3013
  \t],`;
3014
+ const body = `${factories.join("\n")}\n`;
3015
+ const boundaries = ["environmentBoundary", "outputBoundary"].filter((boundary) => body.includes(boundary));
2960
3016
  return (0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.root.vite, {
3017
+ viteTypes: machinery.showcase ? "PluginOption, UserConfig" : "UserConfig",
2961
3018
  imports: imports.length === 0 ? "" : `${imports.join("\n")}\n`,
2962
- factories: `${factories.join("\n")}\n`,
3019
+ helpers: boundaries.length === 0 ? "" : `import { ${boundaries.join(", ")} } from './configs/helpers.js'\n`,
3020
+ factories: body,
2963
3021
  projects: projectRows
2964
3022
  });
2965
3023
  }
@@ -2971,10 +3029,9 @@ ${projects.map((project) => `\t\t\t${project},`).join("\n")}
2971
3029
  *
2972
3030
  * @example
2973
3031
  * ```ts
2974
- * import type { Blueprint } from '@orkestrel/scaffold'
2975
- * import { blueprintToConfigArtifacts } from '@orkestrel/scaffold'
3032
+ * import { blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
2976
3033
  *
2977
- * declare const blueprint: Blueprint
3034
+ * const blueprint = createBlueprint('router', { src: ['core'] })
2978
3035
  *
2979
3036
  * blueprintToConfigArtifacts(blueprint)[0]?.path // 'tsconfig.json'
2980
3037
  * ```
@@ -3000,7 +3057,8 @@ function blueprintToConfigArtifacts(blueprint) {
3000
3057
  else if (path === "configs/src/tsconfig.browser.json") content = CONFIG_TEMPLATES.tsconfigs.src.browser;
3001
3058
  else if (path === "configs/src/vite.server.config.ts") {
3002
3059
  const packageName = serializeTypeScriptString(`@orkestrel/${blueprint.name}`);
3003
- const replacement = packageName.length <= 36 ? `\t\t\t\t\t\t? content.replaceAll(/(?:\\.\\.\\/)+core\\/index\\.ts/g, ${packageName})` : [
3060
+ const joined = `\t\t\t\t\t\t? content.replaceAll(/(?:\\.\\.\\/)+core\\/index\\.ts/g, ${packageName})`;
3061
+ const replacement = joined.replaceAll(" ", " ").length <= 100 ? joined : [
3004
3062
  " ? content.replaceAll(",
3005
3063
  " /(?:\\.\\.\\/)+core\\/index\\.ts/g,",
3006
3064
  `\t\t\t\t\t\t\t\t${packageName},`,
@@ -3065,7 +3123,7 @@ ${paths.join("\n")}
3065
3123
  content
3066
3124
  });
3067
3125
  }
3068
- if (blueprint.showcase) artifacts.push({
3126
+ if (blueprint.showcase && blueprint.app.includes("browser")) artifacts.push({
3069
3127
  path: SHOWCASE_CONFIG_PATH,
3070
3128
  group: "configs",
3071
3129
  ownership: "content",
@@ -3082,16 +3140,18 @@ ${paths.join("\n")}
3082
3140
  * @returns Empty published barrels, selected application entries, and the optional bin entry.
3083
3141
  *
3084
3142
  * @remarks
3085
- * The barrels and bin entry intentionally export nothing. A generated sample
3086
- * entity is too easy to mistake for package implementation, so the scaffold
3087
- * establishes only the selected environment boundaries.
3143
+ * The barrels and every runtime entry intentionally hold nothing. A generated
3144
+ * sample entity is too easy to mistake for package implementation, so the
3145
+ * scaffold establishes only the selected environment boundaries. An application
3146
+ * entry is empty for the same reason the bin entry is, and because the vendored
3147
+ * lint config refuses an unassigned import outside a stylesheet, so the entry
3148
+ * cannot start by importing its barrel for effect either.
3088
3149
  *
3089
3150
  * @example
3090
3151
  * ```ts
3091
- * import type { Blueprint } from '@orkestrel/scaffold'
3092
- * import { blueprintToSourceArtifacts } from '@orkestrel/scaffold'
3152
+ * import { blueprintToSourceArtifacts, createBlueprint } from '@orkestrel/scaffold'
3093
3153
  *
3094
- * declare const blueprint: Blueprint
3154
+ * const blueprint = createBlueprint('router', { src: ['core'] })
3095
3155
  *
3096
3156
  * blueprintToSourceArtifacts(blueprint).every(({ group }) => group === 'source') // true
3097
3157
  * ```
@@ -3121,7 +3181,7 @@ function blueprintToSourceArtifacts(blueprint) {
3121
3181
  ownership: "birth",
3122
3182
  origin: "template",
3123
3183
  environment,
3124
- content: ARTIFACT_TEMPLATES.source.main
3184
+ content: ARTIFACT_TEMPLATES.source.empty
3125
3185
  }, {
3126
3186
  path: "app/browser/index.html",
3127
3187
  group: "source",
@@ -3136,7 +3196,7 @@ function blueprintToSourceArtifacts(blueprint) {
3136
3196
  ownership: "birth",
3137
3197
  origin: "template",
3138
3198
  environment,
3139
- content: ARTIFACT_TEMPLATES.source.main
3199
+ content: ARTIFACT_TEMPLATES.source.empty
3140
3200
  });
3141
3201
  }
3142
3202
  if (blueprint.bin) artifacts.push({
@@ -3164,10 +3224,9 @@ function blueprintToSourceArtifacts(blueprint) {
3164
3224
  *
3165
3225
  * @example
3166
3226
  * ```ts
3167
- * import type { Blueprint } from '@orkestrel/scaffold'
3168
- * import { blueprintToTestArtifacts } from '@orkestrel/scaffold'
3227
+ * import { blueprintToTestArtifacts, createBlueprint } from '@orkestrel/scaffold'
3169
3228
  *
3170
- * declare const blueprint: Blueprint
3229
+ * const blueprint = createBlueprint('router', { src: ['core'] })
3171
3230
  *
3172
3231
  * blueprintToTestArtifacts(blueprint)[0]?.path // 'tests/setup.ts'
3173
3232
  * ```
@@ -3377,10 +3436,9 @@ function nameToHostArtifacts(name) {
3377
3436
  *
3378
3437
  * @example
3379
3438
  * ```ts
3380
- * import type { Artifact } from '@orkestrel/scaffold'
3381
- * import { applyOverrides } from '@orkestrel/scaffold'
3439
+ * import { applyOverrides, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
3382
3440
  *
3383
- * declare const artifacts: readonly Artifact[]
3441
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
3384
3442
  *
3385
3443
  * applyOverrides(artifacts, [{ path: 'README.md', content: '# Title\n' }])
3386
3444
  * ```
@@ -3418,12 +3476,11 @@ function applyOverrides(artifacts, overrides) {
3418
3476
  *
3419
3477
  * @example
3420
3478
  * ```ts
3421
- * import type { Plan } from '@orkestrel/scaffold'
3422
- * import { planToHash } from '@orkestrel/scaffold'
3479
+ * import { createBlueprint, createCompiler, planToHash } from '@orkestrel/scaffold'
3423
3480
  *
3424
- * declare const plan: Plan
3481
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
3425
3482
  *
3426
- * planToHash(plan)?.length // 16
3483
+ * plan === undefined ? undefined : planToHash(plan)?.length // 16
3427
3484
  * ```
3428
3485
  */
3429
3486
  function planToHash(plan) {
@@ -3441,13 +3498,16 @@ function planToHash(plan) {
3441
3498
  * @param artifact - The planned artifact.
3442
3499
  * @param observed - The destination's exact bytes as hexadecimal; absent when
3443
3500
  * the destination holds no file.
3444
- * @returns The finding, carrying `observed` exactly where bytes were read.
3501
+ * @returns The finding, carrying the artifact's ownership and `observed`
3502
+ * exactly where bytes were read.
3445
3503
  *
3446
3504
  * @remarks
3447
3505
  * The comparison itself is {@link inferDrift}'s, so ownership decides it here
3448
3506
  * exactly as it does everywhere else. This adds only the shape: a missing
3449
3507
  * destination has no bytes to record, and every other verdict records the bytes
3450
3508
  * it was given, which is the precondition the mutation that follows is held to.
3509
+ * Ownership is copied rather than inferred from drift because aligned findings
3510
+ * span all three ownership tiers.
3451
3511
  *
3452
3512
  * `foreign` is not answerable here, because it describes a path no artifact was
3453
3513
  * planned for.
@@ -3459,29 +3519,34 @@ function planToHash(plan) {
3459
3519
  * artifactToFinding(
3460
3520
  * { path: 'README.md', group: 'docs', ownership: 'content', origin: 'computed', content: 'hi\n' },
3461
3521
  * '6279650a',
3462
- * ) // { path: 'README.md', group: 'docs', drift: 'stale', observed: '6279650a' }
3522
+ * ) // { path: 'README.md', group: 'docs', ownership: 'content', drift: 'stale', observed: '6279650a' }
3463
3523
  * ```
3464
3524
  */
3465
3525
  function artifactToFinding(artifact, observed) {
3466
3526
  const path = artifact.path;
3467
3527
  const group = artifact.group;
3528
+ const ownership = artifact.ownership;
3468
3529
  if (observed === void 0) return inferDrift(artifact) === "aligned" ? {
3469
3530
  path,
3470
3531
  group,
3532
+ ownership,
3471
3533
  drift: "aligned"
3472
3534
  } : {
3473
3535
  path,
3474
3536
  group,
3537
+ ownership,
3475
3538
  drift: "missing"
3476
3539
  };
3477
3540
  return inferDrift(artifact, observed) === "stale" ? {
3478
3541
  path,
3479
3542
  group,
3543
+ ownership,
3480
3544
  drift: "stale",
3481
3545
  observed
3482
3546
  } : {
3483
3547
  path,
3484
3548
  group,
3549
+ ownership,
3485
3550
  drift: "aligned",
3486
3551
  observed
3487
3552
  };
@@ -3505,12 +3570,11 @@ function artifactToFinding(artifact, observed) {
3505
3570
  *
3506
3571
  * @example
3507
3572
  * ```ts
3508
- * import type { Plan } from '@orkestrel/scaffold'
3509
- * import { planToFindings } from '@orkestrel/scaffold'
3573
+ * import { createBlueprint, createCompiler, planToFindings } from '@orkestrel/scaffold'
3510
3574
  *
3511
- * declare const plan: Plan
3575
+ * const { plan } = createCompiler().compile(createBlueprint('router', { src: ['core'] }))
3512
3576
  *
3513
- * planToFindings(plan, { 'AGENTS.md': '68690a' })
3577
+ * plan === undefined ? [] : planToFindings(plan, { 'AGENTS.md': '68690a' })
3514
3578
  * ```
3515
3579
  */
3516
3580
  function planToFindings(plan, current) {
@@ -3594,8 +3658,8 @@ function dependenciesToQuestions(dependencies, field, name, range) {
3594
3658
  * Measure a blueprint against every law its own fields decide.
3595
3659
  *
3596
3660
  * @param blueprint - The workspace specification.
3597
- * @returns One blocking question per rejected field, in blueprint field order,
3598
- * with the rules that span several fields last.
3661
+ * @returns One question per rejected field, in blueprint field order, with the
3662
+ * rules that span several fields last.
3599
3663
  *
3600
3664
  * @remarks
3601
3665
  * Only the laws a blueprint answers alone are here. The structural record and
@@ -3606,19 +3670,25 @@ function dependenciesToQuestions(dependencies, field, name, range) {
3606
3670
  * a drafted plan belong to {@link artifactsToQuestions} and
3607
3671
  * {@link overridesToQuestions}.
3608
3672
  *
3609
- * Every question is blocking, because each one describes a workspace this
3610
- * package cannot generate rather than one it can generate imperfectly. An
3611
- * environment question carries `ENVIRONMENTS` as its candidates, so a caller
3612
- * reads the accepted values from the refusal instead of from the documentation.
3673
+ * A question blocks when it describes a workspace this package cannot generate.
3674
+ * Three do not, because each describes a workspace it can describe honestly and
3675
+ * should not create: a published axis of several environments without core, whose
3676
+ * manifest names a core build the workspace never runs, and a structural flag
3677
+ * whose required axis is absent, which emits nothing. Blocking those closed the
3678
+ * gate for every verb, and the verbs that read an existing workspace need the
3679
+ * plan the gate refused. The caller that chooses the shape refuses the advisory;
3680
+ * the callers that read one report it.
3681
+ *
3682
+ * An environment question carries `ENVIRONMENTS` as its candidates, so a caller
3683
+ * reads the accepted values from the question instead of from the documentation.
3613
3684
  *
3614
3685
  * @example
3615
3686
  * ```ts
3616
- * import type { Blueprint } from '@orkestrel/scaffold'
3617
- * import { blueprintToQuestions } from '@orkestrel/scaffold'
3687
+ * import { blueprintToQuestions, createBlueprint } from '@orkestrel/scaffold'
3618
3688
  *
3619
- * declare const blueprint: Blueprint
3689
+ * const blueprint = createBlueprint('router', { src: ['core'] })
3620
3690
  *
3621
- * blueprintToQuestions(blueprint).length === 0 // true when the gate passes
3691
+ * blueprintToQuestions(blueprint).length === 0 // true
3622
3692
  * ```
3623
3693
  */
3624
3694
  function blueprintToQuestions(blueprint) {
@@ -3644,6 +3714,12 @@ function blueprintToQuestions(blueprint) {
3644
3714
  blocking: true,
3645
3715
  candidates: ENVIRONMENTS
3646
3716
  });
3717
+ if (blueprint.src.length > 1 && !blueprint.src.includes("core")) questions.push({
3718
+ field: "src",
3719
+ message: "Several published environments put core at the package root, so this manifest names a core build the workspace never runs. Declare core on src, or publish one environment.",
3720
+ blocking: false,
3721
+ candidates: ENVIRONMENTS
3722
+ });
3647
3723
  for (const environment of ENVIRONMENTS) {
3648
3724
  if (blueprint.src.filter((declared) => declared === environment).length > 1) questions.push({
3649
3725
  field: "src",
@@ -3658,11 +3734,10 @@ function blueprintToQuestions(blueprint) {
3658
3734
  candidates: ENVIRONMENTS
3659
3735
  });
3660
3736
  }
3661
- if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
3662
- field: "showcase",
3663
- message: "A showcase projects the browser application, which app does not declare.",
3664
- blocking: true,
3665
- candidates: ENVIRONMENTS
3737
+ if (blueprint.integration && blueprint.src.length === 0) questions.push({
3738
+ field: "integration",
3739
+ message: "integration projects a published src, and this workspace declares none, so it emits nothing.",
3740
+ blocking: false
3666
3741
  });
3667
3742
  const services = /* @__PURE__ */ new Set();
3668
3743
  for (const service of blueprint.services) {
@@ -3678,6 +3753,11 @@ function blueprintToQuestions(blueprint) {
3678
3753
  });
3679
3754
  services.add(service);
3680
3755
  }
3756
+ if (blueprint.showcase && !blueprint.app.includes("browser")) questions.push({
3757
+ field: "showcase",
3758
+ message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
3759
+ blocking: false
3760
+ });
3681
3761
  questions.push(...dependenciesToQuestions(blueprint.dependencies, "dependencies", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.peers, "peers", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.extras, "extras", EXTRA_NAME_PATTERN, EXTRA_RANGE_PATTERN));
3682
3762
  const lists = [
3683
3763
  ["dependencies", blueprint.dependencies],
@@ -3726,12 +3806,11 @@ function blueprintToQuestions(blueprint) {
3726
3806
  *
3727
3807
  * @example
3728
3808
  * ```ts
3729
- * import type { Artifact } from '@orkestrel/scaffold'
3730
- * import { artifactsToQuestions } from '@orkestrel/scaffold'
3809
+ * import { artifactsToQuestions, blueprintToConfigArtifacts, createBlueprint } from '@orkestrel/scaffold'
3731
3810
  *
3732
- * declare const artifacts: readonly Artifact[]
3811
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
3733
3812
  *
3734
- * artifactsToQuestions(artifacts).length === 0 // true when the draft is sound
3813
+ * artifactsToQuestions(artifacts).length === 0 // true
3735
3814
  * ```
3736
3815
  */
3737
3816
  function artifactsToQuestions(artifacts) {
@@ -3783,10 +3862,9 @@ function artifactsToQuestions(artifacts) {
3783
3862
  *
3784
3863
  * @example
3785
3864
  * ```ts
3786
- * import type { Artifact } from '@orkestrel/scaffold'
3787
- * import { overridesToQuestions } from '@orkestrel/scaffold'
3865
+ * import { blueprintToConfigArtifacts, createBlueprint, overridesToQuestions } from '@orkestrel/scaffold'
3788
3866
  *
3789
- * declare const artifacts: readonly Artifact[]
3867
+ * const artifacts = blueprintToConfigArtifacts(createBlueprint('router', { src: ['core'] }))
3790
3868
  *
3791
3869
  * overridesToQuestions([{ path: 'package.json', content: '{}\n' }], artifacts).length // 1
3792
3870
  * ```
@@ -3913,12 +3991,19 @@ var Compiler = class {
3913
3991
  * carries no plan, then `compile` with the whole outcome either way, so an
3914
3992
  * observer reads every compile from one event and the refusals from the other.
3915
3993
  *
3994
+ * A plan says the blueprint can be built. It does not say the blueprint should
3995
+ * be created, and the questions beside it are what this compiler could not
3996
+ * settle. A caller creating a fresh workspace answers them first and writes
3997
+ * nothing while any remains, which is the rule the `new` verb applies; a caller
3998
+ * describing or repairing an existing target carries them through instead.
3999
+ * Nothing downstream repeats that check, because only the caller knows which of
4000
+ * the two it is.
4001
+ *
3916
4002
  * @example
3917
4003
  * ```ts
3918
- * import type { Blueprint } from '@orkestrel/scaffold'
3919
- * import { createCompiler } from '@orkestrel/scaffold'
4004
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
3920
4005
  *
3921
- * declare const blueprint: Blueprint
4006
+ * const blueprint = createBlueprint('router', { src: ['core'] })
3922
4007
  *
3923
4008
  * createCompiler().compile(blueprint, ['manifest']).plan?.artifacts.length // 1
3924
4009
  * ```
@@ -3946,14 +4031,19 @@ var Compiler = class {
3946
4031
  * reports no findings and carries the questions instead. Emits `block` in that
3947
4032
  * case, then `audit` with the verdict either way.
3948
4033
  *
4034
+ * Ownership decides each verdict, not absence. A birth-owned path is never
4035
+ * compared and reads `aligned` against a target holding nothing, while a
4036
+ * content-owned path with no bytes to read is `missing`. An empty snapshot
4037
+ * therefore produces both verdicts rather than one.
4038
+ *
3949
4039
  * @example
3950
4040
  * ```ts
3951
- * import type { Blueprint } from '@orkestrel/scaffold'
3952
- * import { createCompiler } from '@orkestrel/scaffold'
4041
+ * import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
3953
4042
  *
3954
- * declare const blueprint: Blueprint
4043
+ * const blueprint = createBlueprint('router', { src: ['core'] })
3955
4044
  *
3956
- * createCompiler().audit(blueprint, {}).findings.every(({ drift }) => drift === 'missing') // true
4045
+ * createCompiler().audit(blueprint, {}, ['manifest']).findings[0]?.drift // 'aligned'
4046
+ * createCompiler().audit(blueprint, {}, ['configs']).findings[0]?.drift // 'missing'
3957
4047
  * ```
3958
4048
  */
3959
4049
  audit(blueprint, current, groups) {
@@ -3991,7 +4081,10 @@ var Compiler = class {
3991
4081
  }
3992
4082
  #scaffold(blueprint, groups) {
3993
4083
  const stages = [];
3994
- const artifacts = this.#draft(blueprint, groups);
4084
+ const draft = this.#draft(blueprint);
4085
+ const selected = [];
4086
+ for (const group of groups) for (const artifact of draft) if (artifact.group === group) selected.push(artifact);
4087
+ const artifacts = applyOverrides(selected, blueprint.overrides);
3995
4088
  stages.push({
3996
4089
  stage: "draft",
3997
4090
  input: {
@@ -4000,7 +4093,7 @@ var Compiler = class {
4000
4093
  },
4001
4094
  output: artifacts
4002
4095
  });
4003
- const questions = this.#gate(blueprint, artifacts);
4096
+ const questions = this.#gate(blueprint, draft, artifacts);
4004
4097
  const blocking = questions.filter((question) => question.blocking);
4005
4098
  if (blocking.length > 0) {
4006
4099
  stages.push({
@@ -4069,8 +4162,8 @@ var Compiler = class {
4069
4162
  stages
4070
4163
  };
4071
4164
  }
4072
- #draft(blueprint, groups) {
4073
- const drafted = [
4165
+ #draft(blueprint) {
4166
+ return [
4074
4167
  {
4075
4168
  path: "package.json",
4076
4169
  group: "manifest",
@@ -4086,14 +4179,11 @@ var Compiler = class {
4086
4179
  ...blueprintToOrchestrationArtifacts(blueprint),
4087
4180
  ...nameToHostArtifacts(blueprint.name)
4088
4181
  ];
4089
- const covered = [];
4090
- for (const group of groups) for (const artifact of drafted) if (artifact.group === group) covered.push(artifact);
4091
- return applyOverrides(covered, blueprint.overrides);
4092
4182
  }
4093
- #gate(blueprint, artifacts) {
4183
+ #gate(blueprint, draft, artifacts) {
4094
4184
  return [
4095
4185
  ...blueprintToQuestions(blueprint),
4096
- ...overridesToQuestions(blueprint.overrides, artifacts),
4186
+ ...overridesToQuestions(blueprint.overrides, draft),
4097
4187
  ...artifactsToQuestions(artifacts)
4098
4188
  ];
4099
4189
  }
@@ -4215,6 +4305,7 @@ exports.DEFAULT_VERSION = DEFAULT_VERSION;
4215
4305
  exports.DEPENDENCY_NAME_PATTERN = DEPENDENCY_NAME_PATTERN;
4216
4306
  exports.ENGINES_PATTERN = ENGINES_PATTERN;
4217
4307
  exports.ENVIRONMENTS = ENVIRONMENTS;
4308
+ exports.EXECUTABLE_PATHS = EXECUTABLE_PATHS;
4218
4309
  exports.EXTRA_NAME_PATTERN = EXTRA_NAME_PATTERN;
4219
4310
  exports.EXTRA_RANGE_PATTERN = EXTRA_RANGE_PATTERN;
4220
4311
  exports.GLOBAL_SETUP_PATH = GLOBAL_SETUP_PATH;
@@ -4226,6 +4317,7 @@ exports.INTEGRATION_TEST_PATH = INTEGRATION_TEST_PATH;
4226
4317
  exports.INVALID_PATH_CHARACTER_PATTERN = INVALID_PATH_CHARACTER_PATTERN;
4227
4318
  exports.MAX_ARTIFACT_BYTES = MAX_ARTIFACT_BYTES;
4228
4319
  exports.MAX_ARTIFACT_HEX_LENGTH = MAX_ARTIFACT_HEX_LENGTH;
4320
+ exports.MAX_AUDIT_FINDINGS = MAX_AUDIT_FINDINGS;
4229
4321
  exports.MAX_COLLECTION_ITEMS = MAX_COLLECTION_ITEMS;
4230
4322
  exports.MAX_DEPENDENCY_NAME_LENGTH = MAX_DEPENDENCY_NAME_LENGTH;
4231
4323
  exports.MAX_MANIFEST_BYTES = MAX_MANIFEST_BYTES;
@@ -4298,6 +4390,7 @@ exports.isScaffoldError = isScaffoldError;
4298
4390
  exports.isSnapshot = isSnapshot;
4299
4391
  exports.manifestToDependencies = manifestToDependencies;
4300
4392
  exports.manifestToName = manifestToName;
4393
+ exports.matchesDriftReachability = matchesDriftReachability;
4301
4394
  exports.matchesEngines = matchesEngines;
4302
4395
  exports.matchesOrchestrationPath = matchesOrchestrationPath;
4303
4396
  exports.matchesRange = matchesRange;