@orkestrel/scaffold 0.0.21 → 0.0.23

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 (68) hide show
  1. package/dist/bin/scaffold.js +371 -140
  2. package/dist/bin/scaffold.js.map +1 -1
  3. package/dist/host/AGENTS.md +61 -42
  4. package/dist/host/CLAUDE.md +39 -408
  5. package/dist/host/agents/orchestration.md +454 -0
  6. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +25 -7
  7. package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +2 -2
  8. package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +42 -31
  9. package/dist/host/agents/skills/orkestrel-build-application/references/application.md +129 -0
  10. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +3 -3
  11. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +2 -2
  12. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +167 -0
  13. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +98 -0
  14. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +148 -0
  15. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +4 -2
  16. package/dist/host/agents/skills/orkestrel-harden-package/references/centralization.md +5 -5
  17. package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +2 -0
  18. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +11 -6
  19. package/dist/host/claude/agents/analyst.md +59 -0
  20. package/dist/host/claude/agents/application.md +1 -1
  21. package/dist/host/claude/agents/builder.md +1 -1
  22. package/dist/host/claude/agents/checker.md +5 -1
  23. package/dist/host/claude/agents/codex.md +120 -94
  24. package/dist/host/claude/agents/grok.md +44 -32
  25. package/dist/host/claude/agents/implementer.md +1 -1
  26. package/dist/host/claude/agents/orkestrel.md +5 -5
  27. package/dist/host/claude/agents/planner.md +9 -3
  28. package/dist/host/claude/agents/researcher.md +6 -5
  29. package/dist/host/claude/agents/reviewer.md +12 -6
  30. package/dist/host/claude/agents/scout.md +7 -4
  31. package/dist/host/claude/agents/verifier.md +1 -1
  32. package/dist/host/claude/rules/application.md +17 -16
  33. package/dist/host/claude/rules/architecture.md +41 -22
  34. package/dist/host/claude/rules/documentation.md +2 -1
  35. package/dist/host/claude/rules/names.md +7 -6
  36. package/dist/host/claude/rules/quality.md +65 -18
  37. package/dist/host/claude/rules/styles.md +1 -1
  38. package/dist/host/claude/rules/tests.md +12 -6
  39. package/dist/host/claude/rules/typescript.md +1 -1
  40. package/dist/host/claude/rules/workspace.md +6 -3
  41. package/dist/host/claude/settings.json +2 -0
  42. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +10 -0
  43. package/dist/host/codex/agents/analyst.toml +13 -4
  44. package/dist/host/codex/agents/checker.toml +1 -1
  45. package/dist/host/codex/agents/grok.toml +2 -1
  46. package/dist/host/codex/agents/opus.toml +1 -1
  47. package/dist/host/codex/agents/planner.toml +1 -1
  48. package/dist/host/codex/agents/researcher.toml +2 -2
  49. package/dist/host/codex/agents/reviewer.toml +1 -1
  50. package/dist/host/codex/agents/scout.toml +2 -2
  51. package/dist/host/codex/config.toml +23 -70
  52. package/dist/host/cursor/rules/orchestration.mdc +33 -0
  53. package/dist/host/guides/src/scaffold.md +339 -125
  54. package/dist/host/manifest.json +47 -1
  55. package/dist/host/tests/setupPolicy.ts +210 -3
  56. package/dist/src/core/index.cjs +477 -69
  57. package/dist/src/core/index.cjs.map +1 -1
  58. package/dist/src/core/index.d.cts +132 -43
  59. package/dist/src/core/index.d.ts +132 -43
  60. package/dist/src/core/index.js +474 -70
  61. package/dist/src/core/index.js.map +1 -1
  62. package/dist/src/server/index.cjs +83 -33
  63. package/dist/src/server/index.cjs.map +1 -1
  64. package/dist/src/server/index.d.cts +43 -27
  65. package/dist/src/server/index.d.ts +43 -27
  66. package/dist/src/server/index.js +84 -35
  67. package/dist/src/server/index.js.map +1 -1
  68. package/package.json +4 -4
@@ -256,7 +256,7 @@ function isManifestEntry(value) {
256
256
  * Narrow a value to one exact complete vendored-host manifest.
257
257
  *
258
258
  * @param value - The candidate manifest value.
259
- * @returns `true` only for an exact `{ entries, roots }` record with safe paths.
259
+ * @returns `true` only for an exact `{ entries, roots, digest }` record with safe paths.
260
260
  */
261
261
  function isHostManifest(value) {
262
262
  const result = (0, _orkestrel_contract.attempt)(() => {
@@ -264,7 +264,8 @@ function isHostManifest(value) {
264
264
  const keys = Reflect.ownKeys(value);
265
265
  const entries = Reflect.getOwnPropertyDescriptor(value, "entries");
266
266
  const roots = Reflect.getOwnPropertyDescriptor(value, "roots");
267
- return keys.length === 2 && keys.every((key) => key === "entries" || key === "roots") && entries !== void 0 && Reflect.has(entries, "value") && roots !== void 0 && Reflect.has(roots, "value") && (0, _src_core.isDenseDataArray)(entries.value, 4096, isManifestEntry) && (0, _src_core.isDenseDataArray)(roots.value, 4096, isPortablePath);
267
+ const digest = Reflect.getOwnPropertyDescriptor(value, "digest");
268
+ return keys.length === 3 && keys.every((key) => key === "entries" || key === "roots" || key === "digest") && entries !== void 0 && Reflect.has(entries, "value") && roots !== void 0 && Reflect.has(roots, "value") && digest !== void 0 && Reflect.has(digest, "value") && (0, _src_core.isDenseDataArray)(entries.value, 4096, isManifestEntry) && (0, _src_core.isDenseDataArray)(roots.value, 4096, isPortablePath) && typeof digest.value === "string" && WRITE_DIGEST_PATTERN.test(digest.value);
268
269
  });
269
270
  return result.success && result.value;
270
271
  }
@@ -939,6 +940,19 @@ function digestText(value) {
939
940
  return (0, node_crypto.createHash)("sha256").update(value, "utf8").digest("hex");
940
941
  }
941
942
  /**
943
+ * Digest the exact declared membership of a vendored host manifest.
944
+ *
945
+ * @param entries - The ordered file membership declarations.
946
+ * @param roots - The ordered directory membership declarations.
947
+ * @returns A SHA-256 digest independent of the manifest's stored digest field.
948
+ */
949
+ function digestHostManifest(entries, roots) {
950
+ return digestText((0, _src_core.stableStringify)({
951
+ entries,
952
+ roots
953
+ }));
954
+ }
955
+ /**
942
956
  * Render the local pointer written when an upstream dependency guide is not vendored yet.
943
957
  *
944
958
  * @param source - The canonical `guides/src/<name>.md` source path.
@@ -1772,9 +1786,10 @@ function selectOrkestrelEntries(value) {
1772
1786
  * and the name satisfies `isWorkspaceName`; every other name is a coded
1773
1787
  * `TARGET` failure. `src` is derived from `src/<environment>/` and `app` from
1774
1788
  * `app/<environment>/`; a target with no environment on either axis is also a coded
1775
- * `TARGET` failure. The `bin`, `integration`, and `service` facts probe physical
1776
- * directories at `src/bin`, `tests/integration`, and `tests/service`, respectively,
1777
- * while `global` probes the physical exact-case `tests/setupGlobal.ts` file and `showcase`
1789
+ * `TARGET` failure. The `bin` and `integration` facts probe physical
1790
+ * directories at `src/bin` and `tests/integration`. `services` is the sorted
1791
+ * set of direct vendor directories under `tests/service` that contain a test
1792
+ * at any depth, while `global` probes the physical exact-case `tests/setupGlobal.ts` file and `showcase`
1778
1793
  * probes the physical exact-case `configs/app/vite.showcase.config.ts` regular file; none is
1779
1794
  * inferred from the workspace name. `dependencies` /
1780
1795
  * `peers` are the `@orkestrel/`-prefixed entries of `manifest.dependencies` /
@@ -1794,8 +1809,8 @@ function selectOrkestrelEntries(value) {
1794
1809
  * @throws `ScaffoldError('TARGET', …)` when `target`'s manifest is unreadable
1795
1810
  * (via `readManifest`), is not valid JSON, its name is unsafe for its
1796
1811
  * publication mode, or `target` carries no source or application environment.
1797
- * @throws `ScaffoldError('TARGET', …)` when `tests/service` exists without the
1798
- * physical companion files `tests/setupService.ts` and `scripts/service.sh`.
1812
+ * @throws `ScaffoldError('INVALID', …)` when service tests use the retired flat
1813
+ * layout, a vendor directory has no test, or a service companion is missing.
1799
1814
  *
1800
1815
  * @example
1801
1816
  * ```ts
@@ -1835,22 +1850,46 @@ function deriveBlueprint(target) {
1835
1850
  const engines = typeof rawNode === "string" ? rawNode : _src_core.DEFAULT_ENGINES;
1836
1851
  const bin = isRealDirectory((0, node_path.join)(target, "src", "bin"));
1837
1852
  const integration = isRealDirectory((0, node_path.join)(target, "tests", "integration"));
1838
- const service = isRealDirectory((0, node_path.join)(target, "tests", "service"));
1853
+ const serviceRoot = (0, node_path.join)(target, "tests", "service");
1854
+ const services = [];
1855
+ if ((0, node_fs.existsSync)(serviceRoot)) {
1856
+ if (!isRealDirectory(serviceRoot)) throw new _src_core.ScaffoldError("INVALID", `Service root at ${serviceRoot} must be a physical directory`, {
1857
+ target,
1858
+ path: "tests/service"
1859
+ });
1860
+ const serviceEntries = (0, node_fs.readdirSync)(serviceRoot, { withFileTypes: true });
1861
+ const flat = serviceEntries.filter((entry) => entry.name.endsWith(".test.ts") && !entry.isDirectory()).map((entry) => entry.name).sort();
1862
+ if (flat.length > 0) throw new _src_core.ScaffoldError("INVALID", `Flat service tests are not supported; move ${flat.join(", ")} into tests/service/<vendor>/`, {
1863
+ target,
1864
+ paths: flat.map((path) => `tests/service/${path}`)
1865
+ });
1866
+ for (const entry of serviceEntries) {
1867
+ const vendor = (0, node_path.join)(serviceRoot, entry.name);
1868
+ if (!isRealDirectory(vendor)) continue;
1869
+ if (!(0, _src_core.isWorkspaceName)(entry.name)) throw new _src_core.ScaffoldError("INVALID", `Service vendor directory "${entry.name}" must use a bounded lowercase name`, {
1870
+ target,
1871
+ path: `tests/service/${entry.name}`
1872
+ });
1873
+ if (listFiles(vendor).filter((path) => path.endsWith(".test.ts")).length === 0) throw new _src_core.ScaffoldError("INVALID", `Service vendor "${entry.name}" contains no test; add tests/service/${entry.name}/**/*.test.ts or remove the directory`, {
1874
+ target,
1875
+ service: entry.name
1876
+ });
1877
+ const setup = `tests/service/${entry.name}/setup.ts`;
1878
+ if (!isRealFile((0, node_path.join)(target, setup))) throw new _src_core.ScaffoldError("INVALID", `Service vendor "${entry.name}" is missing ${setup}; add a readiness module that probes and warms the vendor and throws when unavailable`, {
1879
+ target,
1880
+ service: entry.name,
1881
+ missing: setup
1882
+ });
1883
+ services.push(entry.name);
1884
+ }
1885
+ services.sort();
1886
+ }
1839
1887
  const showcaseRoot = (0, node_path.join)(target, (0, node_path.dirname)(_src_core.SHOWCASE_CONFIG_PATH));
1840
1888
  const showcaseEntries = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readdirSync)(showcaseRoot));
1841
1889
  const showcase = showcaseEntries.success && showcaseEntries.value.includes((0, node_path.basename)(_src_core.SHOWCASE_CONFIG_PATH)) && isRealFile((0, node_path.join)(target, _src_core.SHOWCASE_CONFIG_PATH));
1842
1890
  const tests = (0, node_path.join)(target, (0, node_path.dirname)(_src_core.GLOBAL_SETUP_PATH));
1843
1891
  const entries = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readdirSync)(tests));
1844
1892
  const global = entries.success && entries.value.includes((0, node_path.basename)(_src_core.GLOBAL_SETUP_PATH)) && isRealFile((0, node_path.join)(target, _src_core.GLOBAL_SETUP_PATH));
1845
- if (service) {
1846
- const missing = [];
1847
- if (!isRealFile((0, node_path.join)(target, "tests", "setupService.ts"))) missing.push("tests/setupService.ts");
1848
- if (!isRealFile((0, node_path.join)(target, _src_core.SERVICE_SCRIPT_PATH))) missing.push(_src_core.SERVICE_SCRIPT_PATH);
1849
- if (missing.length > 0) throw new _src_core.ScaffoldError("TARGET", `Service tests under ${target} are missing ${missing.join(" and ")}`, {
1850
- target,
1851
- missing
1852
- });
1853
- }
1854
1893
  const rawDependencies = (0, _src_core.ownDataValue)(parsed, "dependencies");
1855
1894
  const rawPeerDependencies = (0, _src_core.ownDataValue)(parsed, "peerDependencies");
1856
1895
  const dependencies = selectOrkestrelEntries(rawDependencies).map(([depName, range]) => ({
@@ -1879,7 +1918,7 @@ function deriveBlueprint(target) {
1879
1918
  extras: [],
1880
1919
  bin,
1881
1920
  integration,
1882
- service,
1921
+ services,
1883
1922
  global,
1884
1923
  showcase
1885
1924
  }))));
@@ -1902,7 +1941,7 @@ function deriveBlueprint(target) {
1902
1941
  overrides: [],
1903
1942
  bin,
1904
1943
  integration,
1905
- service,
1944
+ services,
1906
1945
  global,
1907
1946
  showcase
1908
1947
  });
@@ -2026,8 +2065,9 @@ function readManifest(target) {
2026
2065
  * `manifest.json` — the raw-repo-root fallback (`Materializer` then maps
2027
2066
  * an artifact's `source` to `host` 1:1, no vendored staging indirection).
2028
2067
  * @throws `ScaffoldError('TARGET', …)` when `manifest.json` exists but is
2029
- * unreadable, malformed, collision-prone, root-incomplete, or does not map
2030
- * bijectively and case-exactly onto real contained storage files.
2068
+ * unreadable, malformed, membership-corrupted, collision-prone,
2069
+ * root-incomplete, or does not map bijectively and case-exactly onto real
2070
+ * contained storage files.
2031
2071
  *
2032
2072
  * @example
2033
2073
  * ```ts
@@ -2051,6 +2091,10 @@ function readHostManifest(host) {
2051
2091
  host,
2052
2092
  full
2053
2093
  });
2094
+ if (manifest.digest !== digestHostManifest(manifest.entries, manifest.roots)) throw new _src_core.ScaffoldError("TARGET", `Host manifest membership at ${full} is corrupted`, {
2095
+ host,
2096
+ full
2097
+ });
2054
2098
  const destinationConflict = (0, _src_core.findFileConflict)(manifest.entries.map((entry) => entry.destination));
2055
2099
  if (destinationConflict !== void 0) throw new _src_core.ScaffoldError("TARGET", `Host manifest destination collision between "${destinationConflict[0]}" and "${destinationConflict[1]}"`, {
2056
2100
  host,
@@ -2527,7 +2571,8 @@ function stageHost(root, out, paths = _src_core.HOST_PATHS) {
2527
2571
  if (rootConflict !== void 0) throw new _src_core.ScaffoldError("TARGET", `Directory collision between "${rootConflict[0]}" and "${rootConflict[1]}"`, { paths: rootConflict });
2528
2572
  const manifest = {
2529
2573
  entries,
2530
- roots: uniqueRoots
2574
+ roots: uniqueRoots,
2575
+ digest: digestHostManifest(entries, uniqueRoots)
2531
2576
  };
2532
2577
  const parent = (0, node_path.dirname)((0, node_path.resolve)(out));
2533
2578
  const name = (0, node_path.basename)((0, node_path.resolve)(out));
@@ -2626,6 +2671,7 @@ function stageHost(root, out, paths = _src_core.HOST_PATHS) {
2626
2671
  * {
2627
2672
  * entries: [{ storage: 'pkg.tmpl', destination: 'package.json', executable: false }],
2628
2673
  * roots: [],
2674
+ * digest: '3b52f19450237ea4cfb39bc7a77b1e5094b52a8ff1fc9c02fd68b13ad088aa0f',
2629
2675
  * },
2630
2676
  * 'package.json',
2631
2677
  * './dist/host',
@@ -2738,7 +2784,8 @@ function hydratePlan(plan, host) {
2738
2784
  });
2739
2785
  if (entries.length === 0) {
2740
2786
  if (source.startsWith("guides/src/") && source.endsWith(".md")) {
2741
- artifacts.push(artifact);
2787
+ const { hex: _hex, ...presenceOwned } = artifact;
2788
+ artifacts.push(presenceOwned);
2742
2789
  continue;
2743
2790
  }
2744
2791
  throw new _src_core.ScaffoldError("TARGET", `Host manifest does not declare ${source}`, {
@@ -3081,9 +3128,10 @@ function catalogPackages(roots, limit = MAX_HOST_ENTRIES) {
3081
3128
  * (`ScaffoldError('TARGET', …)`), then byte-copies each `host` artifact from
3082
3129
  * the `host` root and writes each `template` / `computed` artifact's rendered
3083
3130
  * `content`, failing fast on any write error (`ScaffoldError('WRITE', …)`).
3084
- * `repair` is into-existing: it skips the vacancy check and writes ONLY the
3085
- * `missing` / `stale` artifacts an `Audit` names, leaving `aligned` ones
3086
- * untouched. Hydrated directory-shaped host entries are expanded into
3131
+ * `repair` is into-existing: it skips the vacancy check and writes `missing`
3132
+ * artifacts an `Audit` names. `stale` artifacts are report-only unless its
3133
+ * `replace` switch is true, because byte replacement discards local changes.
3134
+ * Hydrated directory-shaped host entries are expanded into
3087
3135
  * file-shaped artifacts, so canonical skills and agent configuration are
3088
3136
  * audited and repaired file by file. `prune` deletes stale files under
3089
3137
  * `target/.claude/agents/`, `target/.codex/agents/`, and `target/scripts/`
@@ -3114,9 +3162,10 @@ function catalogPackages(roots, limit = MAX_HOST_ENTRIES) {
3114
3162
  * source is always staged by `stageHost`, so a non-guide zero-match means a
3115
3163
  * corrupted or truncated `manifest.json`, not an intentionally-unvendored
3116
3164
  * artifact. For a guide pointer, a short stub file is written at the
3117
- * destination and reported exactly like a successful copy, mirroring the
3118
- * READ path (`hydratePlan` leaves such artifacts `content`-undefined, so
3119
- * `diffPlan` audits them by PRESENCE only). For every OTHER zero-match, the
3165
+ * destination and reported exactly like a successful copy. The READ path
3166
+ * leaves that intentional pointer without canonical bytes, so `diffPlan`
3167
+ * treats it as presence-owned rather than inventing a byte match.
3168
+ * For every OTHER zero-match, the
3120
3169
  * fail-closed `ScaffoldError('TARGET', …)` is thrown — degrading an
3121
3170
  * unscoped zero-match would otherwise let a corrupted manifest silently stub
3122
3171
  * an unrecoverable artifact (e.g. `AGENTS.md` — `pull` only ever fetches
@@ -3190,16 +3239,16 @@ var Materializer = class {
3190
3239
  this.#emitter.emit("done", result);
3191
3240
  return result;
3192
3241
  }
3193
- repair(plan, audit, target) {
3242
+ repair(plan, audit, target, replace = false) {
3194
3243
  this.#ensureAlive();
3195
3244
  const context = this.#prepare(plan, target, plan, false);
3196
3245
  if (!(0, _src_core.hasOnlyDataProperties)(audit) || !(0, _src_core.hasValidAuditBytes)(audit)) throw new _src_core.ScaffoldError("INVALID", "repair requires a bounded audit snapshot");
3197
3246
  const preview = (0, _orkestrel_contract.attempt)(() => structuredClone(audit));
3198
3247
  if (!preview.success || !Array.isArray(preview.value.findings)) throw new _src_core.ScaffoldError("INVALID", "repair requires a structurally valid audit", { ...preview.success ? {} : { error: preview.error } });
3199
- const drifted = new Set(preview.value.findings.filter((finding) => finding.drift === "missing" || finding.drift === "stale").map((finding) => finding.path));
3248
+ const selectedPaths = new Set(preview.value.findings.filter((finding) => finding.drift === "missing" || replace === true && finding.drift === "stale").map((finding) => finding.path));
3200
3249
  const selected = {
3201
3250
  ...context,
3202
- artifacts: context.artifacts.filter((artifact) => drifted.has(artifact.path))
3251
+ artifacts: context.artifacts.filter((artifact) => selectedPaths.has(artifact.path))
3203
3252
  };
3204
3253
  const prepared = this.#prepare(selected, target, context);
3205
3254
  const preparedByPath = new Map(prepared.artifacts.map((artifact) => [artifact.path, artifact]));
@@ -3223,7 +3272,7 @@ var Materializer = class {
3223
3272
  throw new _src_core.ScaffoldError("INVALID", `Repair precondition is missing at ${artifact.path}`, { path: artifact.path });
3224
3273
  });
3225
3274
  const skipped = [];
3226
- for (const artifact of context.artifacts) if (!drifted.has(artifact.path)) skipped.push(artifact.path);
3275
+ for (const artifact of context.artifacts) if (!selectedPaths.has(artifact.path)) skipped.push(artifact.path);
3227
3276
  const { written, copied } = this.#apply(prepared, target, preconditions);
3228
3277
  const result = {
3229
3278
  target,
@@ -4190,6 +4239,7 @@ exports.createWriteDirectory = createWriteDirectory;
4190
4239
  exports.deriveBlueprint = deriveBlueprint;
4191
4240
  exports.digestFile = digestFile;
4192
4241
  exports.digestHex = digestHex;
4242
+ exports.digestHostManifest = digestHostManifest;
4193
4243
  exports.digestText = digestText;
4194
4244
  exports.discardWriteTransaction = discardWriteTransaction;
4195
4245
  exports.discoverPackages = discoverPackages;