@orkestrel/scaffold 0.0.40 → 0.0.42

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 (40) hide show
  1. package/dist/bin/main.js +8 -1
  2. package/dist/bin/main.js.map +1 -1
  3. package/dist/host/AGENTS.md +2 -2
  4. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
  5. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +3 -3
  6. package/dist/host/agents/skills/orkestrel-align-packages/references/integration.md +1 -1
  7. package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +7 -2
  8. package/dist/host/claude/agents/builder.md +2 -2
  9. package/dist/host/claude/agents/orkestrel.md +48 -48
  10. package/dist/host/claude/rules/application.md +6 -4
  11. package/dist/host/claude/rules/architecture.md +2 -0
  12. package/dist/host/claude/rules/documentation.md +6 -0
  13. package/dist/host/claude/rules/tests.md +11 -1
  14. package/dist/host/claude/rules/typescript.md +15 -1
  15. package/dist/host/claude/rules/workspace.md +43 -13
  16. package/dist/host/claude/rules/writing.md +125 -0
  17. package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +10 -1
  18. package/dist/host/claude/skills/orkestrel-align-packages/SKILL.md +1 -1
  19. package/dist/host/claude/skills/orkestrel-build-application/SKILL.md +1 -1
  20. package/dist/host/claude/skills/orkestrel-harden-package/SKILL.md +1 -1
  21. package/dist/host/configs/policy.ts +185 -0
  22. package/dist/host/dotfiles/oxlintrc.json +13 -1
  23. package/dist/host/dotfiles/prettierignore +3 -0
  24. package/dist/host/guides/scaffold.md +23 -10
  25. package/dist/host/manifest.json +11 -7
  26. package/dist/host/scripts/codex.sh +0 -0
  27. package/dist/host/scripts/cursor.sh +0 -0
  28. package/dist/host/scripts/deps.sh +0 -0
  29. package/dist/host/scripts/ollama.sh +0 -0
  30. package/dist/host/tests/config.test.ts +217 -4
  31. package/dist/host/tests/policy.test.ts +82 -0
  32. package/dist/host/tests/setupPolicy.ts +863 -21
  33. package/dist/src/core/index.cjs +97 -22
  34. package/dist/src/core/index.cjs.map +1 -1
  35. package/dist/src/core/index.d.cts +45 -29
  36. package/dist/src/core/index.d.ts +45 -29
  37. package/dist/src/core/index.js +96 -22
  38. package/dist/src/core/index.js.map +1 -1
  39. package/package.json +7 -7
  40. package/dist/host/agents/skills/orkestrel-build-application/references/application.md +0 -129
@@ -129,6 +129,7 @@ var HOST_PATHS = Object.freeze([
129
129
  "tests/policy.test.ts",
130
130
  "tests/config.test.ts",
131
131
  "configs/helpers.ts",
132
+ "configs/policy.ts",
132
133
  ".editorconfig",
133
134
  ".gitattributes",
134
135
  ".gitignore",
@@ -234,16 +235,16 @@ var NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
234
235
  */
235
236
  var DEPENDENCY_NAME_PATTERN = /^@orkestrel\/[a-z][a-z0-9-]*$/;
236
237
  /**
237
- * The development extra name syntax: any valid npm package name.
238
+ * The package name syntax for a dependency this package does not publish.
238
239
  *
239
240
  * @remarks
240
- * Wider than a runtime dependency name on purpose. An extra is manifest content
241
- * and never reaches a path, so it may carry any scope or no scope at all. The
242
- * single optional `/` is still fixed to the one scope boundary, and neither `..`
243
- * nor a backslash is admitted, so the shape cannot express a traversal even
244
- * though it accepts far more names.
241
+ * A foreign package is one this package does not publish, so its name reaches
242
+ * no path. It may carry any scope or no scope at all. Each name segment begins
243
+ * with an alphanumeric character after an optional leading `@`, and the name
244
+ * carries at most one `/`. No segment can therefore be `..`, and no backslash
245
+ * is admitted, so the shape cannot express a traversal.
245
246
  */
246
- var EXTRA_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
247
+ var FOREIGN_NAME_PATTERN = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
247
248
  /** The exact three-component version syntax a blueprint declares. */
248
249
  var VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
249
250
  /**
@@ -258,6 +259,15 @@ var VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
258
259
  var ORKESTREL_RANGE_PATTERN = /^\^0\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
259
260
  /** The registry-only semver subset accepted for a development extra's range. */
260
261
  var EXTRA_RANGE_PATTERN = /^(?:\^|~)?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?$/;
262
+ /**
263
+ * The exact three-component floor accepted for a foreign peer's range.
264
+ *
265
+ * @remarks
266
+ * This is independent from {@link ENGINES_PATTERN}. An engine floors the Node
267
+ * runtime a workspace supports, while a peer floors a tool the consumer
268
+ * supplies. Either obligation may change without changing the other.
269
+ */
270
+ var FLOOR_RANGE_PATTERN = /^>=(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
261
271
  /** The minimum-Node engine syntax a blueprint declares. */
262
272
  var ENGINES_PATTERN = /^>=(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
263
273
  /** Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. */
@@ -305,7 +315,7 @@ var DEFAULT_ENGINES = `>=${MINIMUM_NODE_VERSION}`;
305
315
  /** The tooling versions scaffold and every generated workspace share. */
306
316
  var BASE_DEV_DEPENDENCIES = Object.freeze({
307
317
  "@orkestrel/guide": "^0.0.12",
308
- "@orkestrel/scaffold": "^0.0.40",
318
+ "@orkestrel/scaffold": "^0.0.41",
309
319
  "@orkestrel/test": "^0.0.6",
310
320
  "@types/node": "^26.2.0",
311
321
  oxfmt: "^0.62.0",
@@ -405,6 +415,7 @@ var CONFIG_TEMPLATES = Object.freeze({
405
415
  `,
406
416
  vite: `import type { {{viteTypes}} } from 'vite'
407
417
  {{imports}}import { defineConfig, mergeConfig } from 'vitest/config'
418
+ import manifest from './package.json' with { type: 'json' }
408
419
  import tsconfig from './tsconfig.json' with { type: 'json' }
409
420
  {{helpers}}{{browsers}}import { fileURLToPath, URL } from 'node:url'
410
421
 
@@ -412,6 +423,18 @@ import tsconfig from './tsconfig.json' with { type: 'json' }
412
423
  return fileURLToPath(new URL(relativePath, import.meta.url))
413
424
  }
414
425
 
426
+ const peerDependencies = 'peerDependencies' in manifest ? manifest.peerDependencies : undefined
427
+ if (
428
+ peerDependencies !== undefined &&
429
+ (typeof peerDependencies !== 'object' ||
430
+ peerDependencies === null ||
431
+ Array.isArray(peerDependencies))
432
+ ) {
433
+ throw new Error('package peerDependencies must be an object')
434
+ }
435
+ export const peers: readonly string[] =
436
+ peerDependencies === undefined ? [] : Object.keys(peerDependencies)
437
+
415
438
  const resolve = {
416
439
  alias: Object.entries(tsconfig.compilerOptions.paths).reduce((aliases, [key, values]) => {
417
440
  const [path] = values
@@ -539,7 +562,13 @@ const resolve = {
539
562
  },
540
563
  outDir: 'dist/bin',
541
564
  target: 'node22',
542
- rolldownOptions: { external: [/^node:/, /^@orkestrel\\//, /^@src\\//] },
565
+ rolldownOptions: {
566
+ external: (id: string) =>
567
+ id.startsWith('node:') ||
568
+ id.startsWith('@orkestrel/') ||
569
+ id.startsWith('@src/') ||
570
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),
571
+ },
543
572
  },
544
573
  test: {
545
574
  name: { label: 'src:bin', color: 'yellow' },
@@ -660,6 +689,24 @@ export function appBrowser(): UserConfig {
660
689
  setupFiles: ['./tests/setup.ts'],
661
690
  environment: 'node',
662
691
  browser: { enabled: false },
692
+ // A config test validates every target wrapper and runs the real linter twice with
693
+ // 15-second child caps, so this budget clears both caps and reports their diagnostics.
694
+ testTimeout: 45_000,
695
+ },
696
+ },
697
+ options ?? {},
698
+ )
699
+ `,
700
+ setup: `export const setup = (options?: UserConfig): UserConfig =>
701
+ mergeConfig(
702
+ {
703
+ resolve,
704
+ test: {
705
+ name: { label: 'setup', color: 'white' },
706
+ include: ['tests/setup*.test.ts'],
707
+ setupFiles: ['./tests/setup.ts'],
708
+ environment: 'node',
709
+ browser: { enabled: false },
663
710
  },
664
711
  },
665
712
  options ?? {},
@@ -895,7 +942,7 @@ export const probe = (options?: UserConfig): UserConfig =>
895
942
  core: `import { defineConfig } from 'vite'
896
943
  import dts from 'vite-plugin-dts'
897
944
  import { environmentBoundary, outputBoundary } from '../helpers.js'
898
- import { srcCore, resolveWorkspacePath } from '../../vite.config.ts'
945
+ import { peers, srcCore, resolveWorkspacePath } from '../../vite.config.ts'
899
946
 
900
947
  export default defineConfig(
901
948
  srcCore({
@@ -923,7 +970,12 @@ export default defineConfig(
923
970
  fileName: (format) => (format === 'es' ? 'index.js' : 'index.cjs'),
924
971
  },
925
972
  outDir: 'dist/src/core',
926
- rolldownOptions: { external: [/^node:/, /^@orkestrel\\//] },
973
+ rolldownOptions: {
974
+ external: (id: string) =>
975
+ id.startsWith('node:') ||
976
+ id.startsWith('@orkestrel/') ||
977
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),
978
+ },
927
979
  },
928
980
  }),
929
981
  )
@@ -1716,6 +1768,7 @@ var isBlueprint = (0, _orkestrel_contract.recordOf)({
1716
1768
  engines: _orkestrel_contract.isString,
1717
1769
  overrides: (0, _orkestrel_contract.andOf)(isCollection, (0, _orkestrel_contract.arrayOf)(isOverride)),
1718
1770
  bin: _orkestrel_contract.isBoolean,
1771
+ setup: _orkestrel_contract.isBoolean,
1719
1772
  guides: _orkestrel_contract.isBoolean,
1720
1773
  distribution: _orkestrel_contract.isBoolean,
1721
1774
  integration: _orkestrel_contract.isBoolean,
@@ -2973,7 +3026,10 @@ function srcToExports(src) {
2973
3026
  * each declared axis adds the set its host needs. A declared extra or peer is
2974
3027
  * applied after those, so a workspace that pins a range for a package the
2975
3028
  * conditional sets also name gets the range it declared. An extra that restates
2976
- * a shared toolchain pin never reaches here, because the gate refuses it.
3029
+ * a shared toolchain pin never reaches here, because the gate refuses it. A peer
3030
+ * that names a development tool already selected keeps that tool's pin, because
3031
+ * the peer's floor states what consumers may supply rather than what this workspace
3032
+ * develops against.
2977
3033
  *
2978
3034
  * A peer is declared here as well as under `peerDependencies`, because a peer is
2979
3035
  * not installed by the workspace that declares it and developing against one
@@ -3006,7 +3062,7 @@ function blueprintToDevDependencies(blueprint) {
3006
3062
  ...blueprint.app.includes("server") ? APP_SERVER_DEV_DEPENDENCIES : {}
3007
3063
  };
3008
3064
  for (const extra of blueprint.extras) merged[extra.name] = extra.range;
3009
- for (const peer of blueprint.peers) merged[peer.name] = peer.range;
3065
+ for (const peer of blueprint.peers) if (!Object.hasOwn(merged, peer.name)) merged[peer.name] = peer.range;
3010
3066
  const own = blueprint.src.length > 0 ? `@orkestrel/${blueprint.name}` : blueprint.name;
3011
3067
  const runtime = new Set(blueprint.dependencies.map((dependency) => dependency.name));
3012
3068
  return Object.fromEntries(Object.entries(merged).filter(([name]) => name !== own && !runtime.has(name)).sort(([left], [right]) => (0, _orkestrel_contract.compareValues)(left, right)));
@@ -3079,6 +3135,7 @@ function blueprintToScripts(blueprint) {
3079
3135
  ...blueprint.app.length > 0 ? ["npm run test:app"] : [],
3080
3136
  "npm run test:policy",
3081
3137
  "npm run test:config",
3138
+ ...blueprint.setup ? ["npm run test:setup"] : [],
3082
3139
  ...blueprint.guides ? ["npm run test:guides"] : [],
3083
3140
  ...blueprint.conformance ? ["npm run test:conformance"] : [],
3084
3141
  ...integrates ? ["npm run test:integration"] : [],
@@ -3099,6 +3156,7 @@ function blueprintToScripts(blueprint) {
3099
3156
  }
3100
3157
  scripts["test:policy"] = `${vitest} --project policy`;
3101
3158
  scripts["test:config"] = `${vitest} --project config`;
3159
+ if (blueprint.setup) scripts["test:setup"] = `${vitest} --project setup`;
3102
3160
  if (blueprint.guides) scripts["test:guides"] = `${vitest} --project guides`;
3103
3161
  if (blueprint.conformance) scripts["test:conformance"] = `${vitest} --project conformance`;
3104
3162
  scripts["test:probe"] = "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe";
@@ -3322,7 +3380,12 @@ function blueprintToRootVite(blueprint) {
3322
3380
  if (blueprint.src.includes("browser")) {
3323
3381
  const core = blueprint.src.includes("core");
3324
3382
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.src.browser, {
3325
- external: core ? "external: (id: string) => id === '@src/core' || id.startsWith('@orkestrel/')," : "external: (id: string) => id.startsWith('@orkestrel/'),",
3383
+ external: core ? `external: (id: string) =>
3384
+ id === '@src/core' ||
3385
+ id.startsWith('@orkestrel/') ||
3386
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),` : `external: (id: string) =>
3387
+ id.startsWith('@orkestrel/') ||
3388
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),`,
3326
3389
  output: core ? " output: { paths: { '@src/core': '../core/index.js' } }," : " output: {},",
3327
3390
  exclude: core ? " exclude: ['tests/src/core/**/*.test.ts'],\n" : "",
3328
3391
  global: blueprint.global ? " globalSetup: ['./tests/setupGlobal.ts'],\n" : ""
@@ -3333,7 +3396,13 @@ function blueprintToRootVite(blueprint) {
3333
3396
  const core = blueprint.src.includes("core");
3334
3397
  factories.push((0, _orkestrel_template.fillTemplate)(CONFIG_TEMPLATES.factories.src.server, {
3335
3398
  external: core ? `external: (id: string) =>
3336
- id === '@src/core' || id.startsWith('node:') || id.startsWith('@orkestrel/'),` : "external: (id: string) => id.startsWith('node:') || id.startsWith('@orkestrel/'),",
3399
+ id === '@src/core' ||
3400
+ id.startsWith('node:') ||
3401
+ id.startsWith('@orkestrel/') ||
3402
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),` : `external: (id: string) =>
3403
+ id.startsWith('node:') ||
3404
+ id.startsWith('@orkestrel/') ||
3405
+ peers.some((peer) => id === peer || id.startsWith(peer + '/')),`,
3337
3406
  output: core ? `\t\t\t\t\toutput: [
3338
3407
  {
3339
3408
  format: 'es',
@@ -3420,6 +3489,10 @@ export function appShowcase(): UserConfig {
3420
3489
  projects.push("policy");
3421
3490
  factories.push(CONFIG_TEMPLATES.factories.config);
3422
3491
  projects.push("config");
3492
+ if (blueprint.setup) {
3493
+ factories.push(CONFIG_TEMPLATES.factories.setup);
3494
+ projects.push("setup");
3495
+ }
3423
3496
  if (blueprint.guides) {
3424
3497
  factories.push(CONFIG_TEMPLATES.factories.guides);
3425
3498
  projects.push("guides");
@@ -4078,11 +4151,11 @@ function planToFindings(plan, current) {
4078
4151
  * range, in list order.
4079
4152
  *
4080
4153
  * @remarks
4081
- * The three declared lists differ only in the two syntaxes they accept, so the
4082
- * rules live here once and each caller supplies its own patterns. A runtime
4083
- * dependency name reaches a path through its guide mirror and is fixed to the
4084
- * `@orkestrel` scope; a development extra never reaches a path and admits any
4085
- * valid npm name.
4154
+ * The declared lists and peer partitions differ only in the two syntaxes they
4155
+ * accept, so the rules live here once and each caller supplies its own patterns.
4156
+ * A runtime dependency name reaches a path through its guide mirror and is
4157
+ * fixed to the `@orkestrel` scope. A foreign peer or development extra reaches
4158
+ * no path and admits any valid npm name.
4086
4159
  *
4087
4160
  * Both patterns must be stateless. A global or sticky pattern carries a
4088
4161
  * `lastIndex` between calls, so it would answer differently for the same input
@@ -4240,7 +4313,7 @@ function blueprintToQuestions(blueprint) {
4240
4313
  message: "showcase projects a browser app, and this workspace declares none, so it emits nothing.",
4241
4314
  blocking: false
4242
4315
  });
4243
- 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));
4316
+ questions.push(...dependenciesToQuestions(blueprint.dependencies, "dependencies", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.peers.filter((peer) => peer.name.startsWith("@orkestrel/")), "peers", DEPENDENCY_NAME_PATTERN, ORKESTREL_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.peers.filter((peer) => !peer.name.startsWith("@orkestrel/")), "peers", FOREIGN_NAME_PATTERN, FLOOR_RANGE_PATTERN), ...dependenciesToQuestions(blueprint.extras, "extras", FOREIGN_NAME_PATTERN, EXTRA_RANGE_PATTERN));
4244
4317
  const lists = [
4245
4318
  ["dependencies", blueprint.dependencies],
4246
4319
  ["peers", blueprint.peers],
@@ -4742,6 +4815,7 @@ function createBlueprint(name, input) {
4742
4815
  engines: input?.engines ?? DEFAULT_ENGINES,
4743
4816
  overrides: input?.overrides ?? [],
4744
4817
  bin: input?.bin ?? false,
4818
+ setup: input?.setup ?? false,
4745
4819
  guides: input?.guides ?? false,
4746
4820
  distribution: input?.distribution ?? false,
4747
4821
  integration: input?.integration ?? false,
@@ -4795,8 +4869,9 @@ exports.DISTRIBUTION_TEST_PATH = DISTRIBUTION_TEST_PATH;
4795
4869
  exports.ENGINES_PATTERN = ENGINES_PATTERN;
4796
4870
  exports.ENVIRONMENTS = ENVIRONMENTS;
4797
4871
  exports.EXECUTABLE_PATHS = EXECUTABLE_PATHS;
4798
- exports.EXTRA_NAME_PATTERN = EXTRA_NAME_PATTERN;
4799
4872
  exports.EXTRA_RANGE_PATTERN = EXTRA_RANGE_PATTERN;
4873
+ exports.FLOOR_RANGE_PATTERN = FLOOR_RANGE_PATTERN;
4874
+ exports.FOREIGN_NAME_PATTERN = FOREIGN_NAME_PATTERN;
4800
4875
  exports.GLOBAL_SETUP_PATH = GLOBAL_SETUP_PATH;
4801
4876
  exports.GROUPS = GROUPS;
4802
4877
  exports.GUIDES_TEST_PATH = GUIDES_TEST_PATH;