@xemahq/repo-build-tooling 0.8.1 → 0.8.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xemahq/repo-build-tooling",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Dev-time build tooling shared by every Xema repository. Ships as plain ESM with zero dependencies so the published artifact is the reviewed source.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Neuralchowder Inc. <developer@xema.dev> (https://xema.dev)",
package/src/readiness.mjs CHANGED
@@ -439,10 +439,18 @@ export function packagesOwningPaths(root, relPaths, script = 'test') {
439
439
  * REPO-WIDE, labelled, because skipping would drop a proof.
440
440
  * - `inapplicable`— nothing declares it. Genuinely nothing to run.
441
441
  */
442
- export function affectedStagePlan({ base, paths, packages, rootDeclares }) {
443
- if (!base) return { mode: 'workspace', viaRoot: rootDeclares };
442
+ export function affectedStagePlan({ base, paths, packages, rootDeclares, membersDeclare }) {
443
+ // REPO-WIDE means WHAT CI RUNS, and every CI in this fleet runs the stage as
444
+ // `pnpm -r --if-present run <script>` — per member, never the root script.
445
+ // Measured 2026-09-20 in xema-kernel-sdk: the root `lint` is `eslint .`,
446
+ // which also lints `tooling/**` and re-lints every package under the ROOT
447
+ // config, and reports 139 errors CI has never seen. Running it here made
448
+ // readiness a SECOND lint authority that disagreed with the first. The root
449
+ // script is used only where no member declares the stage at all.
450
+ if (!base) return { mode: 'workspace', viaRoot: rootDeclares && !membersDeclare };
444
451
  if (packages.length > 0) return { mode: 'affected', packages };
445
- if (paths.length > 0 && rootDeclares) return { mode: 'root' };
452
+ if (paths.length > 0 && rootDeclares && !membersDeclare) return { mode: 'root' };
453
+ if (paths.length > 0 && membersDeclare) return { mode: 'workspace', viaRoot: false };
446
454
  return { mode: 'inapplicable' };
447
455
  }
448
456
 
@@ -455,6 +463,7 @@ function runAffectedStage({ id, script, why }) {
455
463
  paths,
456
464
  packages,
457
465
  rootDeclares: Boolean(scripts[script]),
466
+ membersDeclare: servicesWith(script).length > 0,
458
467
  });
459
468
  switch (plan.mode) {
460
469
  case 'workspace':
@@ -482,10 +491,9 @@ function runAffectedStage({ id, script, why }) {
482
491
  return;
483
492
  case 'root':
484
493
  // FALLBACK, and it is the difference between "inapplicable" and "not
485
- // checked". Some repositories own a stage only at the root — `lint:
486
- // eslint .` covers every package without any of them declaring `lint`.
487
- // Skipping there would silently drop a real proof, so the root script
488
- // runs, LABELLED as repo-wide rather than affected.
494
+ // checked": a repository that owns a stage ONLY at the root (no member
495
+ // declares it). Skipping there would silently drop a real proof, so the
496
+ // root script runs, LABELLED as repo-wide rather than affected.
489
497
  console.log(
490
498
  ` ${paths.length} changed path(s) since ${base.slice(0, 9)}; no changed` +
491
499
  ` package declares \`${script}\`, but the ROOT does — running it\n` +
@@ -218,28 +218,40 @@ test('an affected stage with NO branch point runs REPO-WIDE — it never skips',
218
218
  // The defect this pins: a scratch export or an archive has no origin/develop,
219
219
  // and the stage used to SKIP and let the run end "converged — safe to push"
220
220
  // with typecheck, lint and tests all unexecuted. Green over nothing.
221
+ // Members declare it: CI's `pnpm -r` form, even when the root ALSO has a
222
+ // script — the root's is a second authority (kernel-sdk's `eslint .`).
221
223
  assert.deepEqual(
222
- affectedStagePlan({ base: undefined, paths: [], packages: [], rootDeclares: true }),
224
+ affectedStagePlan({ base: undefined, paths: [], packages: [], rootDeclares: true, membersDeclare: true }),
225
+ { mode: 'workspace', viaRoot: false },
226
+ );
227
+ // Only the root declares it: the root script is the only place it lives.
228
+ assert.deepEqual(
229
+ affectedStagePlan({ base: undefined, paths: [], packages: [], rootDeclares: true, membersDeclare: false }),
223
230
  { mode: 'workspace', viaRoot: true },
224
231
  );
225
232
  assert.deepEqual(
226
- affectedStagePlan({ base: undefined, paths: [], packages: [], rootDeclares: false }),
233
+ affectedStagePlan({ base: undefined, paths: [], packages: [], rootDeclares: false, membersDeclare: false }),
227
234
  { mode: 'workspace', viaRoot: false },
228
235
  );
229
236
  // A base with owning packages is the ordinary affected run.
230
237
  assert.deepEqual(
231
- affectedStagePlan({ base: 'abc', paths: ['a/x.ts'], packages: ['a'], rootDeclares: true }),
238
+ affectedStagePlan({ base: 'abc', paths: ['a/x.ts'], packages: ['a'], rootDeclares: true, membersDeclare: true }),
232
239
  { mode: 'affected', packages: ['a'] },
233
240
  );
234
241
  // A base where only the ROOT declares the script runs the root, labelled.
235
242
  assert.deepEqual(
236
- affectedStagePlan({ base: 'abc', paths: ['README.md'], packages: [], rootDeclares: true }),
243
+ affectedStagePlan({ base: 'abc', paths: ['README.md'], packages: [], rootDeclares: true, membersDeclare: false }),
237
244
  { mode: 'root' },
238
245
  );
246
+ // A root-only path change where members own the stage: CI's form, not the root's.
247
+ assert.deepEqual(
248
+ affectedStagePlan({ base: 'abc', paths: ['README.md'], packages: [], rootDeclares: true, membersDeclare: true }),
249
+ { mode: 'workspace', viaRoot: false },
250
+ );
239
251
  // Nothing declares it: the only outcome that runs nothing, and it needs a
240
252
  // base to be trusted — without one it is the workspace run above.
241
253
  assert.deepEqual(
242
- affectedStagePlan({ base: 'abc', paths: ['README.md'], packages: [], rootDeclares: false }),
254
+ affectedStagePlan({ base: 'abc', paths: ['README.md'], packages: [], rootDeclares: false, membersDeclare: false }),
243
255
  { mode: 'inapplicable' },
244
256
  );
245
257
  });