@orkestrel/scaffold 0.0.81 → 0.0.83

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 (47) hide show
  1. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +14 -3
  2. package/dist/bin/main.js +295 -13
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/AGENTS.md +5 -3
  5. package/dist/host/agents/orchestration.md +1 -0
  6. package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +2 -2
  7. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +43 -39
  8. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +3 -3
  9. package/dist/host/agents/skills/orkestrel-journey/references/decide.md +10 -9
  10. package/dist/host/agents/skills/orkestrel-journey/references/layer.md +5 -5
  11. package/dist/host/agents/skills/orkestrel-journey/references/recorded.md +77 -0
  12. package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +3 -3
  13. package/dist/host/agents/skills/orkestrel-journey/references/styles.md +9 -9
  14. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +4 -0
  15. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +22 -4
  16. package/dist/host/claude/agents/orkestrel.md +10 -10
  17. package/dist/host/claude/rules/application.md +20 -6
  18. package/dist/host/claude/rules/architecture.md +2 -2
  19. package/dist/host/claude/rules/browser.md +9 -0
  20. package/dist/host/claude/rules/documentation.md +2 -1
  21. package/dist/host/claude/rules/quality.md +5 -0
  22. package/dist/host/claude/rules/styles.md +35 -12
  23. package/dist/host/claude/rules/tests.md +15 -10
  24. package/dist/host/claude/rules/workspace.md +128 -98
  25. package/dist/host/claude/skills/orkestrel-journey/SKILL.md +1 -1
  26. package/dist/host/configs/helpers.ts +157 -9
  27. package/dist/host/configs/policy.ts +64 -61
  28. package/dist/host/dotfiles/oxlintrc.json +132 -16
  29. package/dist/host/dotfiles/prettierignore +1 -1
  30. package/dist/host/guides/README.md +9 -5
  31. package/dist/host/guides/guide.md +4 -1
  32. package/dist/host/guides/scaffold.md +482 -115
  33. package/dist/host/manifest.json +38 -32
  34. package/dist/host/scripts/codex.sh +0 -0
  35. package/dist/host/scripts/cursor.sh +0 -0
  36. package/dist/host/scripts/deps.sh +0 -0
  37. package/dist/host/scripts/ollama.sh +0 -0
  38. package/dist/host/tests/config.test.ts +996 -39
  39. package/dist/host/tests/policy.test.ts +11 -0
  40. package/dist/host/tests/setupPolicy.ts +396 -22
  41. package/dist/src/core/index.cjs +1289 -164
  42. package/dist/src/core/index.cjs.map +1 -1
  43. package/dist/src/core/index.d.cts +313 -35
  44. package/dist/src/core/index.d.ts +313 -35
  45. package/dist/src/core/index.js +1268 -165
  46. package/dist/src/core/index.js.map +1 -1
  47. package/package.json +7 -6
@@ -19,6 +19,9 @@ const RUNTIME_FIELDS = ['dependencies', 'peerDependencies'];
19
19
  const RANGE_FIELDS = [...RUNTIME_FIELDS, 'devDependencies'];
20
20
  const CATALOG = '.claude/agents/orkestrel.md';
21
21
  const OFFLINE_REFUSAL = "'catalog' does not take --offline";
22
+ // The vendored host is staged from this package's checkout, so overwriting it from the host deletes
23
+ // and replaces its own canon.
24
+ const SCAFFOLD = '@orkestrel/scaffold';
22
25
  const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
23
26
  // The checkout runs the `.ts` sources and a built twin under `dist/agents` runs `.js` siblings.
24
27
  const EXTENSION = extname(fileURLToPath(import.meta.url));
@@ -223,10 +226,14 @@ function runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline) {
223
226
  runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'audit', ...offline], runner.target);
224
227
  }
225
228
  }
226
- function runVerify(runner, scaffoldFile, scaffoldPrefix, offline) {
229
+ function runVerify(runner, scaffoldFile, scaffoldPrefix, offline, own) {
227
230
  const audit = runStep(runner, 'verify', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target);
228
231
  if (audit === undefined)
229
232
  return;
233
+ // The audit reads the canonical sources of scaffold's own checkout as foreign paths and exits 1.
234
+ if (own && audit.status === 1) {
235
+ replaceLastStep(runner, 0, 'foreign-path findings are expected on the scaffold checkout');
236
+ }
230
237
  const releases = readReleases(audit.stdout);
231
238
  if (releases === undefined) {
232
239
  replaceLastStep(runner, audit.status === null || audit.status === 0 ? 2 : audit.status, 'the audit printed no releases envelope');
@@ -327,10 +334,14 @@ function runVisit(argv) {
327
334
  if (selected.has('commit') && !failed(runner))
328
335
  runCommit(runner);
329
336
  if (selected.has('overwrite') && !failed(runner)) {
330
- runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline);
337
+ if (name === SCAFFOLD) {
338
+ recordStep(runner, 'overwrite', describeCommand(scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline]), `skipped with its audit: ${SCAFFOLD} is the package whose checkout the vendored host is staged from`);
339
+ }
340
+ else
341
+ runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline);
331
342
  }
332
343
  if (selected.has('verify') && !failed(runner)) {
333
- runVerify(runner, scaffoldFile, scaffoldPrefix, offline);
344
+ runVerify(runner, scaffoldFile, scaffoldPrefix, offline, name === SCAFFOLD);
334
345
  }
335
346
  if (selected.has('install') && !failed(runner))
336
347
  runNpmStep(runner, 'install', ['install']);
package/dist/bin/main.js CHANGED
@@ -1,11 +1,131 @@
1
1
  #!/usr/bin/env node
2
2
  import { align, renderTable, strip, stripControls, width } from "@orkestrel/console";
3
3
  import { attempt, isArray, isError, isInstance, isRecord, isString, parseJSON } from "@orkestrel/contract";
4
- import { BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN, ENVIRONMENTS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, JOURNEY_CONFIG_PATH, MAX_MANIFEST_BYTES, MAX_PATH_LENGTH, SERVICE_SETUP_PATH, SHOWCASE_CONFIG_PATH, SKILLS_CONFIG_PATH, ScaffoldError, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToRootVite, blueprintToScripts, blueprintToTestArtifacts, blueprintToWritableScripts, compareVersions, createBlueprint, extractRangeMajor, extractVersion, isFloorPath, isScaffoldError, manifestToDependencies, manifestToName, nameToGuide, replaceManifestScripts, replacePlanRanges } from "../src/core/index.js";
4
+ import { AXES, BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN, ENVIRONMENTS, FRAMEWORKS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, JOURNEY_CONFIG_PATH, MAX_MANIFEST_BYTES, MAX_PATH_LENGTH, RESERVED_SHEET_NAMES, SERVICE_SETUP_PATH, SHEET_ENTRY_NAME, SHOWCASE_CONFIG_PATH, SHOWCASE_PAGES_PATH, SKILLS_CONFIG_PATH, STYLES_ENTRY_PATH, ScaffoldError, THEMES_BARREL_PATH, THEMES_ENTRY_PATH, blueprintToConfigArtifacts, blueprintToDevDependencies, blueprintToFaces, blueprintToProjects, blueprintToScripts, blueprintToSheets, blueprintToTestArtifacts, blueprintToWritableScripts, compareVersions, createBlueprint, extractRangeMajor, extractVersion, isFloorPath, isScaffoldError, isSheetName, manifestToDependencies, manifestToName, nameToGuide, parseExtension, replaceManifestScripts, replacePlanRanges } from "../src/core/index.js";
5
5
  import { MAX_INVENTORY_PATHS, Materializer, Upstream, filesToHost, isExactCaseFile, isPhysicalDirectory, isWorktree, listFiles, readFileText, readHostFloor, readHostManifest, readSnapshot, resolveContainedPath } from "../src/server/index.js";
6
6
  import { createMarkdown, flattenText, isTableNode } from "@orkestrel/markdown";
7
7
  import { createSession } from "@orkestrel/process/server";
8
+ import { readdirSync } from "node:fs";
9
+ import { normalize } from "node:path";
8
10
  import { parseArgs } from "node:util";
11
+ var package_default = {
12
+ name: "@orkestrel/scaffold",
13
+ version: "0.0.83",
14
+ description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
15
+ keywords: [
16
+ "audit",
17
+ "catalog",
18
+ "new",
19
+ "overwrite",
20
+ "repair"
21
+ ],
22
+ homepage: "https://github.com/orkestrel/scaffold#readme",
23
+ bugs: "https://github.com/orkestrel/scaffold/issues",
24
+ license: "MIT",
25
+ repository: {
26
+ "type": "git",
27
+ "url": "git+https://github.com/orkestrel/scaffold.git"
28
+ },
29
+ bin: { "scaffold": "dist/bin/main.js" },
30
+ files: [
31
+ "dist/src",
32
+ "dist/bin",
33
+ "dist/host",
34
+ "dist/agents",
35
+ "README.md"
36
+ ],
37
+ type: "module",
38
+ sideEffects: ["./src/bin/main.ts", "./dist/bin/main.js"],
39
+ main: "./dist/src/core/index.cjs",
40
+ module: "./dist/src/core/index.js",
41
+ exports: {
42
+ ".": {
43
+ "import": {
44
+ "types": "./dist/src/core/index.d.ts",
45
+ "default": "./dist/src/core/index.js"
46
+ },
47
+ "require": {
48
+ "types": "./dist/src/core/index.d.cts",
49
+ "default": "./dist/src/core/index.cjs"
50
+ }
51
+ },
52
+ "./server": {
53
+ "import": {
54
+ "types": "./dist/src/server/index.d.ts",
55
+ "default": "./dist/src/server/index.js"
56
+ },
57
+ "require": {
58
+ "types": "./dist/src/server/index.d.cts",
59
+ "default": "./dist/src/server/index.cjs"
60
+ }
61
+ },
62
+ "./package.json": "./package.json"
63
+ },
64
+ publishConfig: { "access": "public" },
65
+ scripts: {
66
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
67
+ "copy": "node -e \"const fs=require('node:fs'),p=require('node:path'),a=process.argv[1],b=process.argv[2];fs.mkdirSync(p.dirname(b),{recursive:true});fs.cpSync(a,b,{force:true});console.log('Copied: '+a+' to '+b)\"",
68
+ "scaffold": "node ./dist/bin/main.js",
69
+ "lint": "oxlint --config .oxlintrc.json --fix .",
70
+ "check": "tsc --noEmit --project tsconfig.json && npm run check:src && npm run check:skills",
71
+ "check:src": "npm run check:src:core && npm run check:src:server && npm run check:src:bin",
72
+ "check:src:core": "tsc --noEmit -p configs/src/tsconfig.core.json",
73
+ "check:src:server": "tsc --noEmit -p configs/src/tsconfig.server.json",
74
+ "check:src:bin": "tsc --noEmit -p configs/src/tsconfig.bin.json",
75
+ "check:skills": "tsc --noEmit -p configs/agents/tsconfig.skills.json",
76
+ "format": "oxfmt --config .oxfmtrc.json --write .",
77
+ "format:check": "oxfmt --config .oxfmtrc.json --check .",
78
+ "lint:check": "oxlint --config .oxlintrc.json --deny-warnings .",
79
+ "test": "npm run test:src:core && npm run test:src:server && npm run test:src:bin && npm run test:policy && npm run test:config && npm run test:setup && npm run test:skills && npm run test:guides",
80
+ "test:src:core": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:core",
81
+ "test:src:server": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:server",
82
+ "test:src:bin": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:bin",
83
+ "test:policy": "vitest run --config vite.config.ts --no-cache --reporter=dot --project policy",
84
+ "test:config": "vitest run --config vite.config.ts --no-cache --reporter=dot --project config",
85
+ "test:guides": "node --experimental-strip-types tests/guides.test.ts",
86
+ "test:skills": "vitest run --config vite.config.ts --no-cache --reporter=dot --project skills",
87
+ "test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
88
+ "test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
89
+ "test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
90
+ "test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup",
91
+ "build": "npm run clean && npm run build:src && npm run build:host && npm run build:skills && npm run build:inventory",
92
+ "build:src": "npm run build:src:core && npm run build:src:server && npm run build:src:bin",
93
+ "build:src:core": "vite build --config configs/src/vite.core.config.ts && npm run copy dist/src/core/index.d.ts dist/src/core/index.d.cts",
94
+ "build:src:server": "vite build --config configs/src/vite.server.config.ts && npm run copy dist/src/server/index.d.ts dist/src/server/index.d.cts",
95
+ "build:src:bin": "vite build --config configs/src/vite.bin.config.ts",
96
+ "build:host": "node -e \"import('./dist/src/server/index.js').then((m)=>{const n=m.stageHost(process.cwd(),'dist/host',{report: (message) => console.error(message)}).length;console.log('build-host: staged '+n+' file(s) into dist/host')})\"",
97
+ "build:inventory": "node -e \"import('./dist/src/server/index.js').then((m)=>{const p=process.argv[1]??'host.json',n=m.stageInventory(process.cwd(),p).entries.length;console.log('build-inventory: staged '+n+' file(s) into '+p)})\"",
98
+ "build:skills": "tsc -p configs/agents/tsconfig.skills.json --noEmit false --allowImportingTsExtensions false --rewriteRelativeImportExtensions --declaration false --sourceMap false --rootDir .agents/skills --outDir dist/agents/skills && npm run copy .agents/templates/brief.md dist/agents/templates/brief.md",
99
+ "prepack": "npm run build",
100
+ "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release"
101
+ },
102
+ dependencies: {
103
+ "@orkestrel/console": "^0.0.15",
104
+ "@orkestrel/contract": "^0.0.19",
105
+ "@orkestrel/emitter": "^0.0.11",
106
+ "@orkestrel/markdown": "^0.0.17",
107
+ "@orkestrel/process": "^0.0.14",
108
+ "@orkestrel/template": "^0.0.9"
109
+ },
110
+ devDependencies: {
111
+ "@microsoft/api-extractor": "^7.59.3",
112
+ "@orkestrel/browser": "^0.0.20",
113
+ "@orkestrel/guide": "^0.0.22",
114
+ "@orkestrel/html": "^0.0.12",
115
+ "@orkestrel/probe": "^0.0.19",
116
+ "@orkestrel/test": "^0.0.24",
117
+ "@types/node": "^26.6.3",
118
+ "@vitest/browser-playwright": "^4.1.11",
119
+ "oxfmt": "^0.71.0",
120
+ "oxlint": "^1.86.0",
121
+ "playwright": "^1.63.0",
122
+ "typescript": "^6.0.3",
123
+ "vite": "^8.3.2",
124
+ "vitest": "^4.1.11"
125
+ },
126
+ engines: { "node": ">=22.18.0" }
127
+ };
128
+ //#endregion
9
129
  //#region src/bin/constants.ts
10
130
  /**
11
131
  * Names the command the executable installs as.
@@ -17,6 +137,15 @@ import { parseArgs } from "node:util";
17
137
  */
18
138
  var EXECUTABLE_NAME = "scaffold";
19
139
  /**
140
+ * Names the package this executable ships in, as its manifest declares it.
141
+ *
142
+ * @remarks
143
+ * A target declaring this name is the checkout the vendored host is staged
144
+ * from, so `repair` and `overwrite` refuse it: a write from the host would
145
+ * replace that checkout's canon with the copy staged from it.
146
+ */
147
+ var PACKAGE_NAME = package_default.name;
148
+ /**
20
149
  * Lists the {@link Verb} values in usage order, frozen.
21
150
  *
22
151
  * @remarks
@@ -82,6 +211,13 @@ var COMMAND_OPTIONS = Object.freeze({
82
211
  src: Object.freeze({ type: "string" }),
83
212
  app: Object.freeze({ type: "string" }),
84
213
  bin: Object.freeze({ type: "boolean" }),
214
+ styles: Object.freeze({ type: "boolean" }),
215
+ themes: Object.freeze({ type: "boolean" }),
216
+ showcase: Object.freeze({ type: "boolean" }),
217
+ extend: Object.freeze({
218
+ type: "string",
219
+ multiple: true
220
+ }),
85
221
  deps: Object.freeze({ type: "string" }),
86
222
  groups: Object.freeze({ type: "string" }),
87
223
  all: Object.freeze({ type: "boolean" }),
@@ -106,6 +242,10 @@ var OPTION_SUMMARY = Object.freeze({
106
242
  "--src <list>": "the published library environments to build: core, browser, server",
107
243
  "--app <list>": "the private application environments to build: core, browser, server",
108
244
  "--bin": "scaffold a command-line executable at src/bin/main.ts",
245
+ "--styles": "select the base styles surface",
246
+ "--themes": "select themes; requires --styles",
247
+ "--showcase": "select the showcase; requires --app browser",
248
+ "--extend <list>": "select surface:name extensions on the selected surfaces",
109
249
  "--deps <list>": "the @orkestrel/* packages the workspace depends on",
110
250
  "--groups <list>": "the artifact groups to cover; every group when absent",
111
251
  "--all": "fetch a guide for every package the organization publishes, not the declared ones alone",
@@ -131,6 +271,10 @@ var VERB_OPTIONS = Object.freeze({
131
271
  "--src <list>",
132
272
  "--app <list>",
133
273
  "--bin",
274
+ "--styles",
275
+ "--themes",
276
+ "--showcase",
277
+ "--extend <list>",
134
278
  "--deps <list>",
135
279
  "--offline",
136
280
  "--from <path>",
@@ -383,6 +527,9 @@ function argvToCommand(argv) {
383
527
  const target = isString(values.target) ? values.target : void 0;
384
528
  const json = values.json === true;
385
529
  const [from] = paths;
530
+ const extensions = isArray(values.extend) ? values.extend.filter(isString) : [];
531
+ if (extensions.length > 1) throw new UsageError("'new' takes --extend once; combine entries in one list.");
532
+ const [extension] = extensions;
386
533
  const location = target === void 0 ? {} : { target };
387
534
  const source = from === void 0 ? {} : { from };
388
535
  const selection = isString(values.groups) ? { groups: values.groups } : {};
@@ -398,6 +545,10 @@ function argvToCommand(argv) {
398
545
  ...src === void 0 ? {} : { src },
399
546
  ...app === void 0 ? {} : { app },
400
547
  ...bin ? { bin } : {},
548
+ ...values.styles === true ? { styles: true } : {},
549
+ ...values.themes === true ? { themes: true } : {},
550
+ ...values.showcase === true ? { showcase: true } : {},
551
+ ...extension === void 0 ? {} : { extensions: extension },
401
552
  ...offline ? { offline } : {},
402
553
  ...dependencies === void 0 ? {} : { dependencies }
403
554
  };
@@ -888,6 +1039,7 @@ function releasesToPins(releases, declared) {
888
1039
  * Reads the literal Vitest projects, configuration paths, and npm run scripts a shell command names.
889
1040
  *
890
1041
  * @param script - The manifest script text to read.
1042
+ * @param wrappers - Planned wrapper paths mapped to the face projects they run.
891
1043
  * @returns The named projects, configurations, and scripts, or `undefined` when the command
892
1044
  * cannot be read literally.
893
1045
  *
@@ -906,7 +1058,7 @@ function releasesToPins(releases, declared) {
906
1058
  * // { projects: ['src:core'], configs: [], scripts: [] }
907
1059
  * ```
908
1060
  */
909
- function scriptToInvocations(script) {
1061
+ function scriptToInvocations(script, wrappers = /* @__PURE__ */ new Map()) {
910
1062
  const tokens = [];
911
1063
  let value = "";
912
1064
  let resolved = true;
@@ -983,9 +1135,19 @@ function scriptToInvocations(script) {
983
1135
  const projects = [];
984
1136
  const configs = [];
985
1137
  const scripts = [];
1138
+ const configurations = new Map([...wrappers].map(([path, project]) => [normalize(path), project]));
1139
+ let testing = false;
986
1140
  for (let index = 0; index < tokens.length; index += 1) {
987
1141
  const token = tokens[index];
988
1142
  if (token === void 0) return void 0;
1143
+ if (index === 0 || [
1144
+ "&&",
1145
+ "||",
1146
+ ";",
1147
+ "|",
1148
+ "&",
1149
+ "("
1150
+ ].includes(tokens[index - 1]?.value ?? "")) testing = token.resolved && token.value === "vitest" && tokens[index + 1]?.value === "run";
989
1151
  if (token.value === "npm" && tokens[index + 1]?.value === "run") {
990
1152
  const name = tokens[index + 2];
991
1153
  if (name === void 0 || !name.resolved || name.value.length === 0 || [
@@ -1012,14 +1174,20 @@ function scriptToInvocations(script) {
1012
1174
  "(",
1013
1175
  ")"
1014
1176
  ].includes(project.value)) return void 0;
1015
- (token.value === "--project" ? projects : configs).push(project.value);
1177
+ const names = token.value === "--project" ? projects : configs;
1178
+ names.push(project.value);
1179
+ const face = testing && names === configs ? configurations.get(normalize(project.value)) : void 0;
1180
+ if (face !== void 0) projects.push(face);
1016
1181
  index += 1;
1017
1182
  continue;
1018
1183
  }
1019
1184
  if (token.value.startsWith("--project=") || token.value.startsWith("--config=")) {
1020
1185
  const project = token.value.slice(token.value.indexOf("=") + 1);
1021
1186
  if (!token.resolved || project.length === 0) return void 0;
1022
- (token.value.startsWith("--project=") ? projects : configs).push(project);
1187
+ const names = token.value.startsWith("--project=") ? projects : configs;
1188
+ names.push(project);
1189
+ const face = testing && names === configs ? configurations.get(normalize(project)) : void 0;
1190
+ if (face !== void 0) projects.push(face);
1023
1191
  continue;
1024
1192
  }
1025
1193
  if (!token.resolved && /--project|--config/.test(token.value)) return void 0;
@@ -1055,6 +1223,96 @@ function targetToEnvironments(target, axis) {
1055
1223
  });
1056
1224
  }
1057
1225
  /**
1226
+ * Reads the styles, themes, and showcase markers a target physically holds.
1227
+ * @param target - The target directory.
1228
+ * @returns The independent structural facts, false when their markers are absent.
1229
+ */
1230
+ function targetToFacts(target) {
1231
+ const styles = resolveContainedPath(target, STYLES_ENTRY_PATH);
1232
+ const themes = resolveContainedPath(target, THEMES_ENTRY_PATH);
1233
+ const barrel = resolveContainedPath(target, THEMES_BARREL_PATH);
1234
+ const wrapper = resolveContainedPath(target, SHOWCASE_CONFIG_PATH);
1235
+ const pages = resolveContainedPath(target, SHOWCASE_PAGES_PATH);
1236
+ return {
1237
+ styles: styles !== void 0 && isExactCaseFile(styles),
1238
+ themes: themes !== void 0 && isExactCaseFile(themes) && barrel !== void 0 && isExactCaseFile(barrel),
1239
+ showcase: wrapper !== void 0 && isExactCaseFile(wrapper) || pages !== void 0 && isPhysicalDirectory(pages)
1240
+ };
1241
+ }
1242
+ /**
1243
+ * Reads browser faces and named sheet marker pairs from a target.
1244
+ * @param target - The target directory.
1245
+ * @returns Browser extensions in framework order followed by sheets in code-unit order.
1246
+ * @throws `ScaffoldError` when sheet markers name an invalid or case-colliding face, or the directory cannot be read.
1247
+ */
1248
+ function targetToExtensions(target) {
1249
+ const extensions = [];
1250
+ for (const name of FRAMEWORKS) {
1251
+ const axes = AXES.filter((axis) => {
1252
+ const path = resolveContainedPath(target, `${axis}/${name}`);
1253
+ return path !== void 0 && isPhysicalDirectory(path);
1254
+ });
1255
+ if (axes.length > 0) extensions.push({
1256
+ surface: "browser",
1257
+ name,
1258
+ axes
1259
+ });
1260
+ }
1261
+ const source = resolveContainedPath(target, "src");
1262
+ if (source === void 0 || !isPhysicalDirectory(source)) return extensions;
1263
+ const entries = readdirSync(source, { withFileTypes: true });
1264
+ if (entries.length > MAX_INVENTORY_PATHS) throw new ScaffoldError("TARGET", "The source directory exceeds the inventory limit.");
1265
+ const names = [];
1266
+ const seen = /* @__PURE__ */ new Set();
1267
+ for (const entry of entries) {
1268
+ if (!entry.isDirectory() || RESERVED_SHEET_NAMES.includes(entry.name)) continue;
1269
+ const sheet = resolveContainedPath(target, `src/${entry.name}/index.scss`);
1270
+ const marker = resolveContainedPath(target, `src/${entry.name}/${SHEET_ENTRY_NAME}`);
1271
+ if (sheet === void 0 || marker === void 0 || !isExactCaseFile(sheet) || !isExactCaseFile(marker)) continue;
1272
+ const folded = entry.name.toLowerCase();
1273
+ if (seen.has(folded)) throw new ScaffoldError("TARGET", `Case-colliding sheet face '${entry.name}'.`);
1274
+ seen.add(folded);
1275
+ names.push(entry.name);
1276
+ }
1277
+ for (const name of names.sort()) {
1278
+ if (!isSheetName(name)) throw new ScaffoldError("TARGET", `Invalid sheet face '${name}'.`);
1279
+ extensions.push({
1280
+ surface: "styles",
1281
+ name
1282
+ });
1283
+ }
1284
+ return extensions;
1285
+ }
1286
+ /**
1287
+ * Reads a creation extension list and assigns its selected browser axes.
1288
+ * @param selection - The comma-separated `surface:name` entries, or no selection.
1289
+ * @param axes - The axes selecting a browser environment.
1290
+ * @param styles - If `true`, admits stylesheet extensions; if `false`, refuses them.
1291
+ * @returns The validated extensions in selection order.
1292
+ * @throws `UsageError` when an entry is malformed, repeated, unsupported, or lacks its surface.
1293
+ */
1294
+ function selectionToExtensions(selection, axes, styles) {
1295
+ if (selection === void 0) return [];
1296
+ const extensions = [];
1297
+ const seen = /* @__PURE__ */ new Set();
1298
+ for (const entry of selection.split(",")) {
1299
+ const extension = parseExtension(entry);
1300
+ if (extension === void 0 || seen.has(entry)) throw new UsageError(`'--extend' refuses '${entry}': use distinct supported surface:name entries.`);
1301
+ seen.add(entry);
1302
+ if (extension.surface === "browser") {
1303
+ if (axes.length === 0) throw new UsageError("A browser extension requires browser in --src or --app.");
1304
+ extensions.push({
1305
+ ...extension,
1306
+ axes
1307
+ });
1308
+ } else {
1309
+ if (!styles) throw new UsageError("A styles extension requires --styles.");
1310
+ extensions.push(extension);
1311
+ }
1312
+ }
1313
+ return extensions;
1314
+ }
1315
+ /**
1058
1316
  * Reads the packages a target's catalog table listed before this run.
1059
1317
  *
1060
1318
  * @param target - The target or raw checkout directory, or a verified value host.
@@ -1396,9 +1654,17 @@ var CLI = class {
1396
1654
  async #create(command) {
1397
1655
  const target = command.target ?? command.name;
1398
1656
  const app = selectionToEnvironments(command.app, "app");
1657
+ const src = selectionToEnvironments(command.src, "src");
1658
+ if (command.themes === true && command.styles !== true) throw new UsageError("--themes requires --styles.");
1659
+ if (command.showcase === true && !app.includes("browser")) throw new UsageError("--showcase requires --app browser.");
1660
+ const axes = AXES.filter((axis) => (axis === "src" ? src : app).includes("browser"));
1399
1661
  const blueprint = createBlueprint(command.name, {
1400
- src: selectionToEnvironments(command.src, "src"),
1662
+ src,
1401
1663
  app,
1664
+ extensions: selectionToExtensions(command.extensions, axes, command.styles === true),
1665
+ styles: command.styles === true,
1666
+ themes: command.themes === true,
1667
+ showcase: command.showcase === true,
1402
1668
  journey: app.includes("browser"),
1403
1669
  bin: command.bin === true,
1404
1670
  setup: [],
@@ -1900,16 +2166,17 @@ var CLI = class {
1900
2166
  const conformance = resolveContainedPath(target, CONFORMANCE_TEST_PATH);
1901
2167
  const service = resolveContainedPath(target, SERVICE_SETUP_PATH);
1902
2168
  const global = resolveContainedPath(target, GLOBAL_SETUP_PATH);
1903
- const showcase = resolveContainedPath(target, SHOWCASE_CONFIG_PATH);
1904
2169
  const journey = resolveContainedPath(target, JOURNEY_CONFIG_PATH);
1905
2170
  const skills = resolveContainedPath(target, SKILLS_CONFIG_PATH);
1906
2171
  const setup = /* @__PURE__ */ new Set();
1907
2172
  if (tests !== void 0) for (const path of listFiles(tests)) {
1908
2173
  if (path.includes("/") || !path.startsWith("setup") || !path.endsWith(".test.ts")) continue;
1909
2174
  const proof = resolveContainedPath(tests, path);
1910
- if (proof !== void 0 && isExactCaseFile(proof)) setup.add(path === "setupBrowser.test.ts" ? "browser" : "node");
2175
+ if (proof !== void 0 && isExactCaseFile(proof)) setup.add(path === "setupBrowser.test.ts" || path === "setupStyles.test.ts" ? "browser" : "node");
1911
2176
  }
1912
2177
  return createBlueprint(declared.slice(declared.lastIndexOf("/") + 1), {
2178
+ ...targetToFacts(target),
2179
+ extensions: targetToExtensions(target),
1913
2180
  src: targetToEnvironments(target, "src"),
1914
2181
  app: targetToEnvironments(target, "app"),
1915
2182
  dependencies: manifestToDependencies(manifest).runtime,
@@ -1920,7 +2187,6 @@ var CLI = class {
1920
2187
  conformance: conformance !== void 0 && isExactCaseFile(conformance),
1921
2188
  service: service !== void 0 && isExactCaseFile(service),
1922
2189
  global: global !== void 0 && isExactCaseFile(global),
1923
- showcase: showcase !== void 0 && isExactCaseFile(showcase),
1924
2190
  journey: journey !== void 0 && isExactCaseFile(journey),
1925
2191
  skills: skills !== void 0 && isExactCaseFile(skills)
1926
2192
  });
@@ -1929,7 +2195,7 @@ var CLI = class {
1929
2195
  const text = this.#manifest(target);
1930
2196
  const writable = writing ? manifestToWritableScripts(text, blueprint) : blueprintToWritableScripts(blueprint);
1931
2197
  const manifest = replaceManifestScripts(text, writable) ?? text;
1932
- const planned = blueprintToRootVite(blueprint);
2198
+ const planned = new Set(blueprintToProjects(blueprint));
1933
2199
  const parsed = parseJSON(manifest);
1934
2200
  const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
1935
2201
  const gates = !isRecord(parsed) || parsed.private !== true ? ["test", "prepublishOnly"] : ["test"];
@@ -1943,7 +2209,7 @@ var CLI = class {
1943
2209
  unresolved = true;
1944
2210
  continue;
1945
2211
  }
1946
- for (const project of invoked.projects) if (!planned.includes(`name: { label: '${project}',`)) absent.add(project);
2212
+ for (const project of invoked.projects) if (!planned.has(project)) absent.add(project);
1947
2213
  }
1948
2214
  const projects = [...absent].sort();
1949
2215
  if (unresolved) return {
@@ -1991,6 +2257,14 @@ var CLI = class {
1991
2257
  if (command !== void 0) expectedLines.set(project, `"${direct}": ${JSON.stringify(command)},`);
1992
2258
  }
1993
2259
  }
2260
+ const faces = /* @__PURE__ */ new Set([...blueprintToSheets(blueprint).map((name) => `src:${name}`), ...blueprintToFaces(blueprint).flatMap(({ name, axes }) => axes.map((axis) => `${axis}:${name}`))]);
2261
+ const wrappers = /* @__PURE__ */ new Map();
2262
+ for (const artifact of blueprintToConfigArtifacts(blueprint)) {
2263
+ const match = /^configs\/(src|app)\/vite\.([^.]+)\.config\.ts$/u.exec(artifact.path);
2264
+ if (match === null) continue;
2265
+ const project = `${match[1]}:${match[2]}`;
2266
+ if (faces.has(project)) wrappers.set(artifact.path, project);
2267
+ }
1994
2268
  const reachable = /* @__PURE__ */ new Set();
1995
2269
  const chains = /* @__PURE__ */ new Map();
1996
2270
  for (const gate of gates) {
@@ -2003,7 +2277,7 @@ var CLI = class {
2003
2277
  visited.add(name);
2004
2278
  const script = scripts[name];
2005
2279
  if (!isString(script)) continue;
2006
- const invoked = scriptToInvocations(script);
2280
+ const invoked = scriptToInvocations(script, wrappers);
2007
2281
  if (invoked === void 0) {
2008
2282
  unresolved = true;
2009
2283
  continue;
@@ -2102,7 +2376,7 @@ var CLI = class {
2102
2376
  if (proofs.has(`${path.slice(0, -3)}.test.ts`)) return false;
2103
2377
  if (resolveContainedPath(tests, path) === void 0) return false;
2104
2378
  const content = (readFileText(tests, path) ?? "").trim();
2105
- return content !== "" && content !== (seeds.get(`tests/${path}`) ?? "");
2379
+ return /^export /m.test(content) && content !== (seeds.get(`tests/${path}`) ?? "");
2106
2380
  }).sort();
2107
2381
  if (modules.length === 0) return void 0;
2108
2382
  const single = modules.length === 1;
@@ -2118,6 +2392,13 @@ var CLI = class {
2118
2392
  }
2119
2393
  #targetQuestions(target, blueprint, groups, writing = false) {
2120
2394
  const questions = [];
2395
+ const browser = resolveContainedPath(target, "app/browser");
2396
+ if (browser !== void 0 && listFiles(browser).some((path) => path.endsWith(".vue"))) questions.push({
2397
+ field: "extensions",
2398
+ message: "Move Vue components from app/browser to app/vue before regenerating this workspace.",
2399
+ blocking: writing,
2400
+ groups: []
2401
+ });
2121
2402
  if (!writing) {
2122
2403
  const script = this.#scriptQuestion(target, blueprint);
2123
2404
  if (script !== void 0) questions.push(script);
@@ -2130,7 +2411,7 @@ var CLI = class {
2130
2411
  const setup = this.#setupQuestion(target, blueprint);
2131
2412
  if (setup !== void 0) questions.push(setup);
2132
2413
  }
2133
- return questions.filter((question) => groups === void 0 || question.groups.some((group) => groups.includes(group))).map(({ field, message, blocking, candidates }) => ({
2414
+ return questions.filter((question) => question.blocking || groups === void 0 || question.groups.some((group) => groups.includes(group))).map(({ field, message, blocking, candidates }) => ({
2134
2415
  field,
2135
2416
  message,
2136
2417
  blocking,
@@ -2145,6 +2426,7 @@ var CLI = class {
2145
2426
  };
2146
2427
  }
2147
2428
  #assertTarget(target, blueprint, groups) {
2429
+ if (manifestToName(this.#manifest(target)) === PACKAGE_NAME) throw new ScaffoldError("TARGET", `The target at ${target} is the ${PACKAGE_NAME} package, whose canon the vendored host is staged from. Run repair and overwrite against a workspace that consumes it.`, { target });
2148
2430
  const questions = this.#targetQuestions(target, blueprint, groups, true);
2149
2431
  if (questions.length === 0) return;
2150
2432
  throw new ScaffoldError("TARGET", questions.map((question) => question.message).join(" "), { target });