diff --git a/.snyk b/.snyk
index 78207903f..4ce123d5c 100644
--- a/.snyk
+++ b/.snyk
@@ -47,11 +47,6 @@ ignore:
reason: 'Transitive dependency in @azure/functions; upgrade path has type errors'
expires: '2026-07-28T00:00:00.000Z'
created: '2026-01-15T11:04:00.000Z'
- 'SNYK-JS-YAUZL-15467445':
- - '* > yauzl@<3.2.1':
- reason: 'Transitive dependency in @mongodb-memory-server; not exploitable in current usage.'
- expires: '2026-03-26T00:00:00.000Z'
- created: '2026-03-12T12:35:00.000Z'
'SNYK-JS-UUID-16133035':
- '* > uuid@8.3.2':
reason: 'Transitive dependency in Docusaurus and Azurite dev-only stacks; no compatible fix path available yet from upstream.'
@@ -71,11 +66,6 @@ ignore:
reason: 'Mongoose 8.22.1 has TypeScript constraint errors in type definitions (types/inferrawdoctype.d.ts, types/inferschematype.d.ts) that break compilation. Patch attempts failed due to external library type incompatibilities. Risk is Low: requires control of query field names and values.'
expires: '2026-11-07T00:00:00.000Z'
created: '2026-05-07T09:00:00.000Z'
- 'SNYK-JS-BABELPLUGINTRANSFORMMODULESSYSTEMJS-16624576':
- - '* > @babel/plugin-transform-modules-systemjs@7.28.5':
- reason: 'Transitive dependency in Docusaurus; not exploitable in current usage.'
- expires: '2026-06-28T00:00:00.000Z'
- created: '2026-05-11T10:00:00.000Z'
'SNYK-JS-AI-16734889':
- '@docusaurus/preset-classic@3.10.1 > * > ai@5.0.105':
reason: 'Transitive dependency in Docusaurus docsearch; Snyk reports no fixed upgrade or patch available.'
@@ -101,11 +91,6 @@ ignore:
reason: 'Transitive dependency in Docusaurus CSS optimization/build tooling; Snyk reports no fixed upgrade or patch available. Not exploitable at runtime because docs CSS is repository-controlled and processed at build time.'
expires: '2026-09-18T00:00:00.000Z'
created: '2026-06-08T00:00:00.000Z'
- 'SNYK-JS-SHELLQUOTE-17457810':
- - '* > shell-quote@1.8.4':
- reason: 'Transitive dependency in @docusaurus/core via webpack-dev-server > launch-editor; fixed in shell-quote@1.9.0 but no direct upgrade path available. Dev-time only; not exploitable in current usage.'
- expires: '2026-12-31T00:00:00.000Z'
- created: '2026-06-25T00:00:00.000Z'
'SNYK-JS-IMAGESIZE-17295814':
- '* > image-size@<=2.0.2':
reason: 'Transitive dependency in vitest@4.1.6; not exploitable in current usage.'
diff --git a/apps/server-oauth2-mock/src/index.ts b/apps/server-oauth2-mock/src/index.ts
index a29ab59c6..e594c99c5 100644
--- a/apps/server-oauth2-mock/src/index.ts
+++ b/apps/server-oauth2-mock/src/index.ts
@@ -1,6 +1,6 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
-import { createMockOAuth2Manager, type MockOAuth2PortalConfig, normalizeBaseUrl, discoverPortalConfigs, type PortalOidcConfig, ensurePortInUrl, createFileUserStore } from '@cellix/server-oauth2-mock-seedwork';
+import { createFileUserStore, createMockOAuth2Manager, discoverPortalConfigs, ensurePortInUrl, type MockOAuth2PortalConfig, normalizeBaseUrl, type PortalOidcConfig } from '@cellix/server-oauth2-mock-seedwork';
import { setupEnvironment } from './setup-environment.ts';
setupEnvironment();
diff --git a/apps/ui-community/mock-oidc.users.json b/apps/ui-community/mock-oidc.users.json
index ade0b4f42..f5518cba9 100644
--- a/apps/ui-community/mock-oidc.users.json
+++ b/apps/ui-community/mock-oidc.users.json
@@ -3,7 +3,7 @@
"username": "test@example.com",
"sub": "00000000-0000-4000-8000-000000000001",
"password": "password",
- "oidcConfigName": "end-user",
+ "oidcConfigName": "end-user",
"claims": {
"email": "test@example.com",
"given_name": "Test",
@@ -15,7 +15,7 @@
"username": "john.doe@example.com",
"sub": "00000000-0000-4000-8000-000000000002",
"password": "password",
- "oidcConfigName": "end-user",
+ "oidcConfigName": "end-user",
"claims": {
"email": "john.doe@example.com",
"given_name": "John",
@@ -27,7 +27,7 @@
"username": "owner@test.example",
"sub": "aaaaaaaa-bbbb-1ccc-9ddd-eeeeeeeeee01",
"password": "password",
- "oidcConfigName": "end-user",
+ "oidcConfigName": "end-user",
"claims": {
"email": "owner@test.example",
"given_name": "Test",
diff --git a/apps/ui-staff/mock-oidc.users.json b/apps/ui-staff/mock-oidc.users.json
index 6b635dfb4..1c753ea7c 100644
--- a/apps/ui-staff/mock-oidc.users.json
+++ b/apps/ui-staff/mock-oidc.users.json
@@ -3,7 +3,7 @@
"username": "staff@ownercommunity.onmicrosoft.com",
"sub": "10000000-0000-4000-8000-000000000001",
"password": "password",
- "oidcConfigName": "staff-user",
+ "oidcConfigName": "staff-user",
"claims": {
"email": "staff@ownercommunity.onmicrosoft.com",
"given_name": "Staff",
diff --git a/biome.json b/biome.json
index 9da5b4793..2970dcc8b 100644
--- a/biome.json
+++ b/biome.json
@@ -9,7 +9,7 @@
"!**/dist",
"!**/build",
"!**/deploy",
- "!**/target",
+ "!**/target",
"!**/node_modules",
"!**/coverage",
"!**/.turbo",
diff --git a/build-pipeline/scripts/verify.ts b/build-pipeline/scripts/verify.ts
new file mode 100644
index 000000000..e630e08b0
--- /dev/null
+++ b/build-pipeline/scripts/verify.ts
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+///
+
+import { architectureTests, coverageMerge, e2eTests, knipCheck, pnpmAudit, pnpmScript, snykCodeScan, snykDependencyScan, sonarPullRequestAnalysis, sonarQualityGate, verificationSequence } from '@cellix/local-dev/silent-runners';
+
+const snykOrgArgs = ['--org=cellixjs', '--remote-repo-url=https://github.com/CellixJs/cellixjs'];
+
+const cellixVerify = verificationSequence
+ .addStep(pnpmScript('format:check'))
+ .addStep(architectureTests())
+ .addStep(coverageMerge())
+ .addStep(e2eTests())
+ .addStep(knipCheck())
+ .addStep(pnpmAudit({ auditLevel: 'high', dependencyType: 'prod', name: 'audit:prod' }))
+ .addStep(pnpmAudit({ auditLevel: 'critical', dependencyType: 'dev', name: 'audit:dev' }))
+ .addStep(
+ snykDependencyScan({
+ args: [...snykOrgArgs, '--policy-path=.snyk', '--file=package.json', '--severity-threshold=high'],
+ }),
+ )
+ .addStep(
+ snykCodeScan({
+ args: snykOrgArgs,
+ }),
+ )
+ .addStep(sonarPullRequestAnalysis())
+ .addStep(sonarQualityGate());
+
+function runVerifyCommand(): void {
+ const result = cellixVerify.run();
+ if (result.status === 0) {
+ process.stdout.write('verify passed\n');
+ }
+ process.exitCode = result.status;
+}
+
+runVerifyCommand();
diff --git a/knip.json b/knip.json
index c96fff069..d30847b3b 100644
--- a/knip.json
+++ b/knip.json
@@ -1,6 +1,10 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"workspaces": {
+ ".": {
+ "entry": ["build-pipeline/scripts/verify.ts"],
+ "project": ["build-pipeline/scripts/**/*.ts"]
+ },
"apps/api": {
"entry": ["src/index.ts", "start-*.mjs"],
"project": ["src/**/*.ts", "*.mjs"],
@@ -82,7 +86,7 @@
"entry": ["cucumber.js", "src/world.ts", "src/step-definitions/index.ts", "src/shared/abilities/index.ts"],
"project": ["src/**/*.ts"],
"ignoreBinaries": ["report"],
- "ignoreUnresolved": ["progress-bar"]
+ "ignoreUnresolved": ["progress-bar"]
},
"packages/ocom-verification/acceptance-ui": {
"entry": ["cucumber.js", "src/world.ts", "src/step-definitions/index.ts"],
@@ -93,7 +97,7 @@
"packages/ocom-verification/e2e-tests": {
"entry": ["cucumber.js", "src/world.ts", "src/contexts/**/step-definitions/**/*.steps.ts", "src/shared/environment/**/*.ts", "src/shared/abilities/**/*.ts"],
"project": ["src/**/*.ts"],
- "ignoreUnresolved": ["progress-bar"]
+ "ignoreUnresolved": ["progress-bar"]
},
"apps/server-oauth2-mock": {
"entry": ["src/index.ts"],
diff --git a/package.json b/package.json
index 3314071b1..0b6a2538b 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
"sonar:pr": "export PR_NUMBER=$(node build-pipeline/scripts/get-pr-number.cjs) && sonar-scanner -Dsonar.pullrequest.key=$PR_NUMBER -Dsonar.pullrequest.branch=$(git branch --show-current) -Dsonar.pullrequest.base=main",
"sonar:pr-windows": "for /f %i in ('node build-pipeline/scripts/get-pr-number.cjs') do set PR_NUMBER=%i && sonar-scanner -Dsonar.pullrequest.key=%PR_NUMBER% -Dsonar.pullrequest.branch=%BRANCH_NAME% -Dsonar.pullrequest.base=main",
"check-sonar": "node build-pipeline/scripts/check-sonar-quality-gate.cjs",
- "verify": "pnpm run format:check && pnpm run test:arch && pnpm run test:coverage:merge && pnpm run test:e2e:worktree && pnpm run knip && pnpm run audit && pnpm run snyk",
+ "verify": "node --conditions=source build-pipeline/scripts/verify.ts",
"knip": "knip",
"snyk": "pnpm run snyk:test && pnpm run snyk:code",
"snyk:report": "pnpm run snyk:monitor && pnpm run snyk:code:report",
@@ -73,6 +73,7 @@
"@ant-design/cli": "^6.3.5",
"@biomejs/biome": "2.4.10",
"@cellix/graphql-codegen": "workspace:*",
+ "@cellix/local-dev": "workspace:*",
"@graphql-codegen/cli": "^5.0.7",
"@graphql-codegen/introspection": "^4.0.3",
"@graphql-codegen/typed-document-node": "^5.1.2",
diff --git a/packages/cellix/local-dev/.gitignore b/packages/cellix/local-dev/.gitignore
new file mode 100644
index 000000000..c925c21d5
--- /dev/null
+++ b/packages/cellix/local-dev/.gitignore
@@ -0,0 +1,2 @@
+/dist
+/node_modules
diff --git a/packages/cellix/local-dev/README.md b/packages/cellix/local-dev/README.md
new file mode 100644
index 000000000..c0873af1d
--- /dev/null
+++ b/packages/cellix/local-dev/README.md
@@ -0,0 +1,119 @@
+# @cellix/local-dev
+
+Local developer wrappers for Cellix verification commands.
+
+This package is intentionally small on this branch. Other pull requests add
+additional local-development modules to the same package; this change owns only
+the silent command runner export and its portable tool wrappers.
+
+## Install
+
+In this monorepo, consumers use the workspace package directly:
+
+```json
+{
+ "devDependencies": {
+ "@cellix/local-dev": "workspace:*"
+ }
+}
+```
+
+## Silent Runners
+
+`runSilentCommand` captures stdout and stderr while a command is running. If the
+command succeeds, nothing is printed. If it fails, whatever the command wrote to
+stdout and stderr is replayed before the failing status is returned.
+
+```js
+import { runSilentCommand } from '@cellix/local-dev/silent-runners';
+
+const result = runSilentCommand({
+ command: 'snyk',
+ args: ['test', '--all-projects'],
+});
+
+process.exitCode = result.status;
+```
+
+Use `runSilentCommandSequence` when a wrapper needs to run several commands in
+order. Steps are silent by default; mark a step with `output: 'inherit'` only
+when its live output is part of the intended consumer experience.
+
+```js
+import { runSilentCommandSequence } from '@cellix/local-dev/silent-runners';
+
+const result = runSilentCommandSequence({
+ steps: [
+ { name: 'format:check', command: 'pnpm', args: ['run', 'format:check'] },
+ { name: 'test:e2e', command: 'pnpm', args: ['run', 'test:e2e'] },
+ ],
+});
+
+process.exitCode = result.status;
+```
+
+For reusable verification workflows, use the fluent sequence builder:
+
+```js
+import { pnpmScript, verificationSequence } from '@cellix/local-dev/silent-runners';
+
+const verify = verificationSequence
+ .addStep(pnpmScript('format:check'))
+ .addStep(pnpmScript('test'));
+
+const result = verify.run();
+process.exitCode = result.status;
+```
+
+Prefer the named tool wrappers when a command has a known CLI shape:
+
+```js
+import { knipCheck, pnpmAudit, runSilentCommandSequence, snykCodeScan, snykDependencyScan } from '@cellix/local-dev/silent-runners';
+
+const result = runSilentCommandSequence({
+ steps: [
+ knipCheck(),
+ pnpmAudit({ auditLevel: 'high', dependencyType: 'prod' }),
+ snykDependencyScan({ args: ['--all-projects', '--org=my-org'] }),
+ snykCodeScan({ args: ['--org=my-org'] }),
+ ],
+});
+```
+
+## Public API
+
+Exports are available from `@cellix/local-dev` and
+`@cellix/local-dev/silent-runners`:
+
+- `runSilentCommand`
+- `runSilentCommandSequence`
+- `CommandOutputMode`
+- `CommandSequenceStep`
+- `architectureTests`
+- `coverageMerge`
+- `e2eTests`
+- `knipCheck`
+- `livePnpmScript`
+- `pnpmAudit`
+- `pnpmScript`
+- `SilentCommandOptions`
+- `SilentCommandResult`
+- `SilentCommandSequenceOptions`
+- `SilentCommandSequenceResult`
+- `SilentRunnerSpawnSync`
+- `SilentRunnerStreams`
+- `snykCodeScan`
+- `snykDependencyScan`
+- `snykIacScan`
+- `sonarPullRequestAnalysis`
+- `sonarQualityGate`
+- `VerificationSequence`
+- `VerificationSequenceOptions`
+- `verificationSequence`
+
+## Notes
+
+- Tool wrappers encode reusable CLI shape; scripts still own project-specific arguments such as org names, paths, and CI policy.
+- Success is silent; failure replays whatever the command wrote to stdout/stderr.
+- Captured output defaults to 64 MiB and can be adjusted with `maxBuffer` for a command, sequence, or individual sequence step.
+- Commands run without shell interpolation. Pass the executable as `command` and arguments as `args`.
diff --git a/packages/cellix/local-dev/manifest.md b/packages/cellix/local-dev/manifest.md
new file mode 100644
index 000000000..ddc6b4e24
--- /dev/null
+++ b/packages/cellix/local-dev/manifest.md
@@ -0,0 +1,75 @@
+# manifest.md - @cellix/local-dev
+
+## Purpose
+
+Provide local developer wrappers for Cellix commands that should be quiet on
+success and diagnostic on failure.
+
+## Scope
+
+This branch owns only the silent verification runner surface and portable tool
+wrappers. Other local-dev modules from related pull requests will share this
+package after the branches are merged.
+
+## Non-goals
+
+- Project-specific Snyk, Edgescan, or scanner policy such as org names,
+ repository URLs, token handling, or report publishing
+- Worktree ports, app dev-server runners, URL transforms, or settings sync
+
+## Public API shape
+
+Published entrypoints:
+
+- `@cellix/local-dev`
+- `@cellix/local-dev/silent-runners`
+
+Root entrypoint exports:
+
+- `runSilentCommand(options)`
+- `runSilentCommandSequence(options)`
+- `VerificationSequence`
+- `verificationSequence`
+- tool-wrapper builders such as `knipCheck()`, `pnpmAudit(options)`,
+ `snykDependencyScan(options)`, `snykCodeScan(options)`, and
+ `sonarPullRequestAnalysis(options)`
+
+## Core concepts
+
+- Silent verification runners capture external command output, emit nothing on
+ success, and replay whatever the command wrote to stdout/stderr on failure.
+- Silent command sequences run ordered verification steps, defaulting each step
+ to silent output while allowing explicit passthrough where live output is
+ intentionally part of the command contract.
+- Fluent verification sequences provide a reusable `.addStep(...).run(...)`
+ object API over the same ordered, stop-on-failure behavior.
+- Tool wrappers encode portable CLI shapes; root or app scripts own
+ project-specific arguments.
+- Commands are spawned without shell interpolation.
+
+## Package boundaries
+
+- Do not encode project-specific Snyk, Edgescan, or scanner policy here.
+- Keep this branch scoped to the silent runner module so it can merge cleanly
+ with other `@cellix/local-dev` modular exports.
+
+## Dependencies / relationships
+
+- Downstream consumers in this monorepo: the root `verify` script.
+
+## Testing strategy
+
+- Public-entrypoint tests prove success output is suppressed, failure output is
+ replayed, and the root verify sequence uses the expected tool wrappers while
+ remaining fully silent on success.
+
+## Documentation obligations
+
+- Keep README.md consumer-facing and focused on silent runners until the broader
+ local-dev package exports merge.
+- Keep TSDoc aligned on the public runner API.
+
+## Release-readiness standards
+
+- Package build and package tests must pass.
+- Root verification scripts should preserve their existing command arguments.
diff --git a/packages/cellix/local-dev/package.json b/packages/cellix/local-dev/package.json
new file mode 100644
index 000000000..b54978a76
--- /dev/null
+++ b/packages/cellix/local-dev/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "@cellix/local-dev",
+ "version": "1.0.0",
+ "description": "Local developer wrappers for Cellix verification commands",
+ "type": "module",
+ "files": [
+ "dist",
+ "src"
+ ],
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "source": "./src/index.ts",
+ "default": "./dist/index.js"
+ },
+ "./silent-runners": {
+ "types": "./dist/silent-runners/index.d.ts",
+ "source": "./src/silent-runners/index.ts",
+ "default": "./dist/silent-runners/index.js"
+ }
+ },
+ "scripts": {
+ "prebuild": "pnpm run lint",
+ "build": "tsgo --build",
+ "clean": "rimraf dist",
+ "lint": "biome lint",
+ "format": "biome format --write",
+ "format:check": "biome format .",
+ "test": "vitest run --silent --reporter=dot",
+ "test:coverage": "vitest run --coverage --silent --reporter=dot",
+ "test:watch": "vitest"
+ },
+ "devDependencies": {
+ "@cellix/config-typescript": "workspace:*",
+ "@cellix/config-vitest": "workspace:*",
+ "@types/node": "catalog:",
+ "@vitest/coverage-istanbul": "catalog:",
+ "rimraf": "catalog:",
+ "typescript": "catalog:",
+ "vitest": "catalog:"
+ }
+}
diff --git a/packages/cellix/local-dev/src/index.test.ts b/packages/cellix/local-dev/src/index.test.ts
new file mode 100644
index 000000000..b0b3827fb
--- /dev/null
+++ b/packages/cellix/local-dev/src/index.test.ts
@@ -0,0 +1,300 @@
+import type { SpawnSyncOptionsWithStringEncoding, SpawnSyncReturns } from 'node:child_process';
+import {
+ architectureTests,
+ coverageMerge,
+ e2eTests,
+ knipCheck,
+ pnpmAudit,
+ pnpmScript,
+ runSilentCommand,
+ runSilentCommandSequence,
+ type SilentRunnerSpawnSync,
+ snykCodeScan,
+ snykDependencyScan,
+ sonarPullRequestAnalysis,
+ sonarQualityGate,
+ verificationSequence,
+} from '@cellix/local-dev/silent-runners';
+import { describe, expect, it } from 'vitest';
+
+function createVerifySpawn(options: { failStep?: string } = {}) {
+ const calls: Array<{ args: string[]; command: string; stdio: SpawnSyncOptionsWithStringEncoding['stdio'] }> = [];
+ const spawn = (command: string, args: string[], spawnOptions: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns => {
+ const stepName = getStepName(command, args);
+ calls.push({
+ args,
+ command,
+ stdio: spawnOptions.stdio,
+ });
+
+ return {
+ output: [`${stepName} stdout`, '', ''],
+ pid: 123,
+ signal: null,
+ status: options.failStep === stepName ? 9 : 0,
+ stderr: `${stepName} stderr\n`,
+ stdout: `${stepName} stdout\n`,
+ };
+ };
+
+ return { calls, spawn };
+}
+
+function getStepName(command: string, args: string[]): string {
+ if (command === 'pnpm' && args[0] === 'run' && args[1]) {
+ return args[1];
+ }
+
+ if (command === 'pnpm' && args[0] === 'audit') {
+ return args.includes('--prod') ? 'audit:prod' : 'audit:dev';
+ }
+
+ if (command === 'pnpm' && args[0] === 'exec' && args[1] === 'knip') {
+ return 'knip';
+ }
+
+ if (command === 'pnpm' && args[0] === 'exec' && args[1] === 'snyk' && args[2] === 'test') {
+ return 'snyk:test';
+ }
+
+ if (command === 'pnpm' && args[0] === 'exec' && args[1] === 'snyk' && args[2] === 'code' && args[3] === 'test') {
+ return 'snyk:code';
+ }
+
+ return command;
+}
+
+describe('@cellix/local-dev/silent-runners', () => {
+ it('keeps successful local verification commands silent', () => {
+ const output = {
+ stderr: '',
+ stdout: '',
+ };
+ const spawn: SilentRunnerSpawnSync = (command, args, options) => {
+ expect(command).toBe('snyk');
+ expect(args).toEqual(['test']);
+ expect(options.maxBuffer).toBe(64 * 1024 * 1024);
+ expect(options.stdio).toBe('pipe');
+ return {
+ output: ['tool output', '', ''],
+ pid: 123,
+ signal: null,
+ status: 0,
+ stderr: 'hidden success stderr',
+ stdout: 'hidden success stdout',
+ };
+ };
+
+ const result = runSilentCommand({
+ args: ['test'],
+ command: 'snyk',
+ spawn,
+ streams: {
+ stderr: {
+ write: (chunk: string) => {
+ output.stderr += chunk;
+ return true;
+ },
+ },
+ stdout: {
+ write: (chunk: string) => {
+ output.stdout += chunk;
+ return true;
+ },
+ },
+ },
+ });
+
+ expect(result).toMatchObject({
+ status: 0,
+ stderr: 'hidden success stderr',
+ stdout: 'hidden success stdout',
+ });
+ expect(output).toEqual({
+ stderr: '',
+ stdout: '',
+ });
+ });
+
+ it('replays local verification command output when a command fails', () => {
+ const output = {
+ stderr: '',
+ stdout: '',
+ };
+ const spawn: SilentRunnerSpawnSync = () => ({
+ output: ['tool output', '', ''],
+ pid: 123,
+ signal: null,
+ status: 2,
+ stderr: 'failure stderr',
+ stdout: 'failure stdout',
+ });
+
+ const result = runSilentCommand({
+ args: ['code', 'test'],
+ command: 'snyk',
+ spawn,
+ streams: {
+ stderr: {
+ write: (chunk: string) => {
+ output.stderr += chunk;
+ return true;
+ },
+ },
+ stdout: {
+ write: (chunk: string) => {
+ output.stdout += chunk;
+ return true;
+ },
+ },
+ },
+ });
+
+ expect(result.status).toBe(2);
+ expect(output).toEqual({
+ stderr: '\nCommand failed (exit 2): snyk code test\n\nfailure stderr',
+ stdout: 'failure stdout',
+ });
+ });
+
+ it('allows callers to configure the captured output buffer', () => {
+ const observedMaxBuffers: Array = [];
+ const spawn: SilentRunnerSpawnSync = (_command, _args, options) => {
+ observedMaxBuffers.push(options.maxBuffer);
+ return {
+ output: ['', '', ''],
+ pid: 123,
+ signal: null,
+ status: 0,
+ stderr: '',
+ stdout: '',
+ };
+ };
+
+ runSilentCommand({
+ command: 'large-output-tool',
+ maxBuffer: 128 * 1024 * 1024,
+ spawn,
+ });
+ runSilentCommandSequence({
+ maxBuffer: 96 * 1024 * 1024,
+ spawn,
+ steps: [
+ { command: 'first-tool', name: 'first-tool' },
+ { command: 'second-tool', maxBuffer: 160 * 1024 * 1024, name: 'second-tool' },
+ ],
+ });
+
+ expect(observedMaxBuffers).toEqual([128 * 1024 * 1024, 96 * 1024 * 1024, 160 * 1024 * 1024]);
+ });
+
+ it('keeps the successful verify sequence fully silent', () => {
+ const { calls, spawn } = createVerifySpawn();
+ const output = { stderr: '', stdout: '' };
+ const steps = [
+ pnpmScript('format:check'),
+ architectureTests(),
+ coverageMerge(),
+ e2eTests(),
+ knipCheck(),
+ pnpmAudit({ auditLevel: 'high', dependencyType: 'prod', name: 'audit:prod' }),
+ pnpmAudit({ auditLevel: 'critical', dependencyType: 'dev', name: 'audit:dev' }),
+ snykDependencyScan({ args: ['--all-projects'] }),
+ snykCodeScan(),
+ sonarPullRequestAnalysis(),
+ sonarQualityGate(),
+ ];
+
+ const result = runSilentCommandSequence({
+ spawn,
+ steps,
+ streams: {
+ stderr: {
+ write: (chunk: string) => {
+ output.stderr += chunk;
+ return true;
+ },
+ },
+ stdout: {
+ write: (chunk: string) => {
+ output.stdout += chunk;
+ return true;
+ },
+ },
+ },
+ });
+
+ expect(result.status).toBe(0);
+ expect(output).toEqual({ stderr: '', stdout: '' });
+ expect(calls.map((call) => getStepName(call.command, call.args))).toEqual(steps.map((step) => step.name));
+ expect(calls).toEqual(
+ steps.map((step) => ({
+ args: step.args ?? [],
+ command: step.command,
+ stdio: 'pipe',
+ })),
+ );
+ });
+
+ it('replays output from the failing silent verify step and stops before later work', () => {
+ const { calls, spawn } = createVerifySpawn({ failStep: 'snyk:code' });
+ const output = { stderr: '', stdout: '' };
+ const steps = [
+ pnpmScript('format:check'),
+ architectureTests(),
+ coverageMerge(),
+ e2eTests(),
+ knipCheck(),
+ pnpmAudit({ auditLevel: 'high', dependencyType: 'prod', name: 'audit:prod' }),
+ pnpmAudit({ auditLevel: 'critical', dependencyType: 'dev', name: 'audit:dev' }),
+ snykDependencyScan({ args: ['--all-projects'] }),
+ snykCodeScan(),
+ sonarPullRequestAnalysis(),
+ sonarQualityGate(),
+ ];
+
+ const result = runSilentCommandSequence({
+ spawn,
+ steps,
+ streams: {
+ stderr: {
+ write: (chunk: string) => {
+ output.stderr += chunk;
+ return true;
+ },
+ },
+ stdout: {
+ write: (chunk: string) => {
+ output.stdout += chunk;
+ return true;
+ },
+ },
+ },
+ });
+
+ expect(result.status).toBe(9);
+ expect(result.step.name).toBe('snyk:code');
+ expect(output).toEqual({
+ stderr: '\nCommand failed (exit 9): pnpm exec snyk code test\n\nsnyk:code stderr\n',
+ stdout: 'snyk:code stdout\n',
+ });
+ expect(calls.map((call) => getStepName(call.command, call.args))).toEqual(['format:check', 'test:arch', 'test:coverage:merge', 'test:e2e', 'knip', 'audit:prod', 'audit:dev', 'snyk:test', 'snyk:code']);
+ });
+
+ it('builds and runs reusable verification sequences with a fluent addStep API', () => {
+ const { calls, spawn } = createVerifySpawn();
+ const sequence = verificationSequence.addStep(pnpmScript('format:check')).addStep(architectureTests());
+
+ const result = sequence.run({ spawn });
+
+ expect(result.status).toBe(0);
+ expect(calls.map((call) => getStepName(call.command, call.args))).toEqual(['format:check', 'test:arch']);
+ });
+
+ it('keeps the shared verification sequence immutable', () => {
+ const configuredSequence = verificationSequence.addStep(pnpmScript('format:check'));
+
+ expect(() => verificationSequence.run()).toThrow('runSilentCommandSequence requires at least one step');
+ expect(configuredSequence).not.toBe(verificationSequence);
+ });
+});
diff --git a/packages/cellix/local-dev/src/index.ts b/packages/cellix/local-dev/src/index.ts
new file mode 100644
index 000000000..2a937b8c2
--- /dev/null
+++ b/packages/cellix/local-dev/src/index.ts
@@ -0,0 +1,31 @@
+export {
+ architectureTests,
+ type CommandOutputMode,
+ type CommandSequenceStep,
+ coverageMerge,
+ e2eTests,
+ knipCheck,
+ livePnpmScript,
+ type PnpmAuditOptions,
+ type PnpmScriptOptions,
+ pnpmAudit,
+ pnpmScript,
+ runSilentCommand,
+ runSilentCommandSequence,
+ type SilentCommandOptions,
+ type SilentCommandResult,
+ type SilentCommandSequenceOptions,
+ type SilentCommandSequenceResult,
+ type SilentRunnerSpawnSync,
+ type SilentRunnerStreams,
+ type SnykScanOptions,
+ type SonarScriptOptions,
+ snykCodeScan,
+ snykDependencyScan,
+ snykIacScan,
+ sonarPullRequestAnalysis,
+ sonarQualityGate,
+ VerificationSequence,
+ type VerificationSequenceOptions,
+ verificationSequence,
+} from './silent-runners/index.ts';
diff --git a/packages/cellix/local-dev/src/silent-runners/index.ts b/packages/cellix/local-dev/src/silent-runners/index.ts
new file mode 100644
index 000000000..5b5685b5a
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/index.ts
@@ -0,0 +1,326 @@
+import { type SpawnSyncOptionsWithStringEncoding, type SpawnSyncReturns, spawnSync } from 'node:child_process';
+
+const DEFAULT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
+
+export { knipCheck } from './knip.ts';
+export { livePnpmScript, type PnpmScriptOptions, pnpmScript } from './pnpm.ts';
+export { type PnpmAuditOptions, pnpmAudit } from './pnpm-audit.ts';
+export { type SnykScanOptions, snykCodeScan, snykDependencyScan, snykIacScan } from './snyk.ts';
+export { type SonarScriptOptions, sonarPullRequestAnalysis, sonarQualityGate } from './sonar.ts';
+export { architectureTests, coverageMerge, e2eTests } from './test-runners.ts';
+
+/**
+ * Spawn function used by silent command runners.
+ *
+ * Consumers usually rely on the default `child_process.spawnSync`; tests and
+ * advanced wrappers can inject this to observe command execution without
+ * running the external tool.
+ */
+export type SilentRunnerSpawnSync = (command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding) => SpawnSyncReturns;
+
+/**
+ * Writable stream surface used when replaying captured output after a command
+ * fails.
+ */
+export interface SilentRunnerStreams {
+ /** Stream that receives captured stdout when the command fails. */
+ stdout?: Pick;
+ /** Stream that receives captured stderr when the command fails. */
+ stderr?: Pick;
+}
+
+/**
+ * Options for running an external command silently until failure.
+ */
+export interface SilentCommandOptions {
+ /** Executable name. */
+ command: string;
+ /** Command-line arguments passed without shell interpolation. */
+ args?: string[];
+ /** Working directory for the command. Defaults to the current process cwd. */
+ cwd?: string;
+ /** Environment to pass to the command. Defaults to the current process env. */
+ env?: NodeJS.ProcessEnv;
+ /** Maximum captured output in bytes. Defaults to 64 MiB. */
+ maxBuffer?: number;
+ /** Process spawner used by tests and advanced consumers. Defaults to `child_process.spawnSync`. */
+ spawn?: SilentRunnerSpawnSync;
+ /** Streams used when replaying captured failure output. Defaults to `process.stdout` and `process.stderr`. */
+ streams?: SilentRunnerStreams;
+}
+
+/**
+ * How a command in a verification sequence should handle output.
+ */
+export type CommandOutputMode = 'silent' | 'inherit';
+
+/**
+ * One command in a verification sequence.
+ */
+export interface CommandSequenceStep {
+ /** Human-readable step name for tests and orchestration logs. */
+ name: string;
+ /** Executable name. */
+ command: string;
+ /** Command-line arguments passed without shell interpolation. */
+ args?: string[];
+ /** Output policy for this step. Defaults to `silent`. */
+ output?: CommandOutputMode;
+ /** Maximum captured output in bytes for this step. */
+ maxBuffer?: number;
+}
+
+/**
+ * Options for running a verification sequence.
+ */
+export interface SilentCommandSequenceOptions {
+ /** Steps to run in order until one fails. */
+ steps: CommandSequenceStep[];
+ /** Working directory for each command. Defaults to the current process cwd. */
+ cwd?: string;
+ /** Environment to pass to each command. Defaults to the current process env. */
+ env?: NodeJS.ProcessEnv;
+ /** Default maximum captured output in bytes for silent steps. Defaults to 64 MiB. */
+ maxBuffer?: number;
+ /** Process spawner used by tests and advanced consumers. Defaults to `child_process.spawnSync`. */
+ spawn?: SilentRunnerSpawnSync;
+ /** Streams used when replaying captured failure output. Defaults to `process.stdout` and `process.stderr`. */
+ streams?: SilentRunnerStreams;
+}
+
+interface CommandSequenceContext {
+ cwd: string | undefined;
+ env: NodeJS.ProcessEnv | undefined;
+ maxBuffer: number | undefined;
+ spawn: SilentRunnerSpawnSync | undefined;
+ streams: SilentRunnerStreams | undefined;
+}
+
+/**
+ * Result returned by a silent command runner.
+ */
+export interface SilentCommandResult {
+ /** Exit status reported by the child process. */
+ status: number;
+ /** Signal reported by the child process, when it was terminated by a signal. */
+ signal: NodeJS.Signals | null;
+ /** Captured stdout. This is replayed only when the command fails. */
+ stdout: string;
+ /** Captured stderr. This is replayed only when the command fails. */
+ stderr: string;
+ /** Spawn error, such as an executable that could not be found. */
+ error?: Error;
+}
+
+/**
+ * Result returned by a command sequence.
+ */
+export interface SilentCommandSequenceResult extends SilentCommandResult {
+ /** Step that produced the returned status. */
+ step: CommandSequenceStep;
+}
+
+export type VerificationSequenceOptions = Omit;
+
+/**
+ * Runs a command with stdout and stderr captured instead of inherited.
+ *
+ * Successful commands produce no terminal output. Failed commands replay their
+ * captured stdout and stderr so local verification stays quiet when healthy and
+ * still shows whatever the command wrote when something breaks.
+ *
+ * @param options - Command, arguments, process context, and optional test seams.
+ * @returns The child-process result with captured output and normalized status.
+ *
+ * @example
+ * ```ts
+ * import { runSilentCommand } from '@cellix/local-dev/silent-runners';
+ *
+ * const result = runSilentCommand({
+ * command: 'snyk',
+ * args: ['test', '--all-projects'],
+ * });
+ *
+ * process.exitCode = result.status;
+ * ```
+ */
+export function runSilentCommand(options: SilentCommandOptions): SilentCommandResult {
+ const {
+ args = [],
+ command,
+ cwd,
+ env,
+ maxBuffer = DEFAULT_MAX_BUFFER_BYTES,
+ spawn = spawnCommandSync,
+ streams = {
+ stderr: process.stderr,
+ stdout: process.stdout,
+ },
+ } = options;
+ const result = spawn(command, args, {
+ cwd,
+ encoding: 'utf8',
+ env,
+ maxBuffer,
+ stdio: 'pipe',
+ });
+ const normalizedResult = toSilentCommandResult(result);
+
+ if (normalizedResult.status !== 0) {
+ replayFailureOutput(normalizedResult, streams, command, args);
+ }
+
+ return normalizedResult;
+}
+
+/**
+ * Runs commands in order, stopping at the first failure.
+ *
+ * Steps default to silent output. Use `output: 'inherit'` only when live output
+ * is part of the command's intended consumer experience.
+ *
+ * @param options - Sequence steps and optional process context.
+ * @returns The final successful step result or the first failing step result.
+ */
+export function runSilentCommandSequence(options: SilentCommandSequenceOptions): SilentCommandSequenceResult {
+ const { cwd, env, maxBuffer, spawn, steps, streams } = options;
+
+ let lastResult: SilentCommandSequenceResult | undefined;
+
+ for (const step of steps) {
+ const context: CommandSequenceContext = { cwd, env, maxBuffer, spawn, streams };
+ const result = step.output === 'inherit' ? runInheritedCommand(buildCommandOptions(step, context)) : runSilentCommand(buildCommandOptions(step, context));
+ lastResult = {
+ ...result,
+ step,
+ };
+
+ if (result.status !== 0) {
+ return lastResult;
+ }
+ }
+
+ if (!lastResult) {
+ throw new Error('runSilentCommandSequence requires at least one step');
+ }
+
+ return lastResult;
+}
+
+/**
+ * Fluent builder for an ordered sequence of verification commands.
+ *
+ * Steps execute in insertion order and stop at the first failure. The sequence
+ * can be configured once with shared process options and run repeatedly.
+ *
+ * @example
+ * ```ts
+ * const verify = verificationSequence
+ * .addStep(pnpmScript('format:check'))
+ * .addStep(pnpmScript('test'));
+ *
+ * const result = verify.run();
+ * process.exitCode = result.status;
+ * ```
+ */
+export class VerificationSequence {
+ readonly #options: VerificationSequenceOptions;
+ readonly #steps: CommandSequenceStep[];
+
+ constructor(options: VerificationSequenceOptions = {}, steps: CommandSequenceStep[] = []) {
+ this.#options = options;
+ this.#steps = steps;
+ }
+
+ /**
+ * Appends a command to the sequence.
+ *
+ * @param step - Command descriptor to execute after previously added steps.
+ * @returns A new sequence containing the appended step.
+ */
+ addStep(step: CommandSequenceStep): VerificationSequence {
+ return new VerificationSequence(this.#options, [...this.#steps, step]);
+ }
+
+ /**
+ * Runs the configured steps in insertion order.
+ *
+ * Per-run options override options supplied when the sequence was created.
+ *
+ * @param options - Optional process context and test seams for this run.
+ * @returns The final successful result or the first failing step result.
+ * @throws When the sequence has no steps.
+ */
+ run(options: VerificationSequenceOptions = {}): SilentCommandSequenceResult {
+ return runSilentCommandSequence({
+ ...this.#options,
+ ...options,
+ steps: [...this.#steps],
+ });
+ }
+}
+
+/**
+ * Empty reusable verification sequence for fluent workflow definitions.
+ *
+ * Adding a step returns a new sequence, so this shared object remains empty and
+ * can safely be reused by multiple consumers.
+ */
+export const verificationSequence = new VerificationSequence();
+
+function runInheritedCommand(options: SilentCommandOptions): SilentCommandResult {
+ const { args = [], command, cwd, env, spawn = spawnCommandSync } = options;
+ return toSilentCommandResult(
+ spawn(command, args, {
+ cwd,
+ encoding: 'utf8',
+ env,
+ stdio: 'inherit',
+ }),
+ );
+}
+
+function buildCommandOptions(step: CommandSequenceStep, options: CommandSequenceContext): SilentCommandOptions {
+ const maxBuffer = step.maxBuffer ?? options.maxBuffer;
+
+ return {
+ ...(step.args ? { args: step.args } : {}),
+ ...(options.cwd ? { cwd: options.cwd } : {}),
+ ...(options.env ? { env: options.env } : {}),
+ ...(maxBuffer !== undefined ? { maxBuffer } : {}),
+ ...(options.spawn ? { spawn: options.spawn } : {}),
+ ...(options.streams ? { streams: options.streams } : {}),
+ command: step.command,
+ };
+}
+
+function toSilentCommandResult(result: SpawnSyncReturns): SilentCommandResult {
+ return {
+ ...(result.error ? { error: result.error } : {}),
+ signal: result.signal,
+ status: result.status ?? 1,
+ stderr: result.stderr ?? '',
+ stdout: result.stdout ?? '',
+ };
+}
+
+function spawnCommandSync(command: string, args: string[], options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns {
+ return spawnSync(command, args, {
+ ...options,
+ encoding: 'utf8',
+ });
+}
+
+function replayFailureOutput(result: SilentCommandResult, streams: SilentRunnerStreams, command: string, args: string[]): void {
+ const commandLine = [command, ...args].join(' ');
+ streams.stderr?.write(`\nCommand failed (exit ${result.status}): ${commandLine}\n\n`);
+ if (result.stdout) {
+ streams.stdout?.write(result.stdout);
+ }
+ if (result.stderr) {
+ streams.stderr?.write(result.stderr);
+ }
+ if (result.error) {
+ streams.stderr?.write(`${result.error.message}\n`);
+ }
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/knip.ts b/packages/cellix/local-dev/src/silent-runners/knip.ts
new file mode 100644
index 000000000..32e96651f
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/knip.ts
@@ -0,0 +1,12 @@
+import type { CommandSequenceStep } from './index.ts';
+
+/**
+ * Runs Knip unused-code/dependency analysis.
+ */
+export function knipCheck(): CommandSequenceStep {
+ return {
+ args: ['exec', 'knip'],
+ command: 'pnpm',
+ name: 'knip',
+ };
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/pnpm-audit.ts b/packages/cellix/local-dev/src/silent-runners/pnpm-audit.ts
new file mode 100644
index 000000000..68ed5e50e
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/pnpm-audit.ts
@@ -0,0 +1,18 @@
+import type { CommandSequenceStep } from './index.ts';
+
+export interface PnpmAuditOptions {
+ auditLevel: 'low' | 'moderate' | 'high' | 'critical';
+ dependencyType: 'prod' | 'dev';
+ name?: string;
+}
+
+/**
+ * Runs `pnpm audit` for one dependency class and audit threshold.
+ */
+export function pnpmAudit(options: PnpmAuditOptions): CommandSequenceStep {
+ return {
+ args: ['audit', `--audit-level=${options.auditLevel}`, `--${options.dependencyType}`],
+ command: 'pnpm',
+ name: options.name ?? `audit:${options.dependencyType}`,
+ };
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/pnpm.ts b/packages/cellix/local-dev/src/silent-runners/pnpm.ts
new file mode 100644
index 000000000..d73c8881f
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/pnpm.ts
@@ -0,0 +1,31 @@
+import type { CommandOutputMode, CommandSequenceStep } from './index.ts';
+
+export interface PnpmScriptOptions {
+ name?: string;
+ output?: CommandOutputMode;
+}
+
+/**
+ * Runs a package-manager script.
+ *
+ * Use this for project-owned scripts whose internals should remain delegated to
+ * the consuming repository.
+ */
+export function pnpmScript(script: string, options: PnpmScriptOptions = {}): CommandSequenceStep {
+ return {
+ args: ['run', script],
+ command: 'pnpm',
+ name: options.name ?? script,
+ ...(options.output ? { output: options.output } : {}),
+ };
+}
+
+/**
+ * Runs a package-manager script with live output.
+ */
+export function livePnpmScript(script: string, options: Omit = {}): CommandSequenceStep {
+ return pnpmScript(script, {
+ ...options,
+ output: 'inherit',
+ });
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/snyk.ts b/packages/cellix/local-dev/src/silent-runners/snyk.ts
new file mode 100644
index 000000000..e19c00805
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/snyk.ts
@@ -0,0 +1,39 @@
+import type { CommandSequenceStep } from './index.ts';
+
+export interface SnykScanOptions {
+ args?: string[];
+ name?: string;
+}
+
+/**
+ * Runs Snyk dependency scanning (`snyk test`).
+ */
+export function snykDependencyScan(options: SnykScanOptions = {}): CommandSequenceStep {
+ return {
+ args: ['exec', 'snyk', 'test', ...(options.args ?? [])],
+ command: 'pnpm',
+ name: options.name ?? 'snyk:test',
+ };
+}
+
+/**
+ * Runs Snyk code scanning (`snyk code test`).
+ */
+export function snykCodeScan(options: SnykScanOptions = {}): CommandSequenceStep {
+ return {
+ args: ['exec', 'snyk', 'code', 'test', ...(options.args ?? [])],
+ command: 'pnpm',
+ name: options.name ?? 'snyk:code',
+ };
+}
+
+/**
+ * Runs Snyk IaC scanning (`snyk iac test`).
+ */
+export function snykIacScan(targets: string[] = [], options: SnykScanOptions = {}): CommandSequenceStep {
+ return {
+ args: ['exec', 'snyk', 'iac', 'test', ...targets, ...(options.args ?? [])],
+ command: 'pnpm',
+ name: options.name ?? 'snyk:iac',
+ };
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/sonar.ts b/packages/cellix/local-dev/src/silent-runners/sonar.ts
new file mode 100644
index 000000000..ac6cdc64d
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/sonar.ts
@@ -0,0 +1,27 @@
+import type { CommandSequenceStep } from './index.ts';
+import { pnpmScript } from './pnpm.ts';
+
+export interface SonarScriptOptions {
+ script?: string;
+}
+
+/**
+ * Runs a repository-owned Sonar pull-request analysis script.
+ *
+ * The PR-number and branch-resolution policy is intentionally delegated to the
+ * consuming repository because those details vary by CI provider.
+ */
+export function sonarPullRequestAnalysis(options: SonarScriptOptions = {}): CommandSequenceStep {
+ return pnpmScript(options.script ?? 'sonar:pr', {
+ name: 'sonar:pr',
+ });
+}
+
+/**
+ * Runs a repository-owned Sonar quality-gate script.
+ */
+export function sonarQualityGate(options: SonarScriptOptions = {}): CommandSequenceStep {
+ return pnpmScript(options.script ?? 'check-sonar', {
+ name: 'check-sonar',
+ });
+}
diff --git a/packages/cellix/local-dev/src/silent-runners/test-runners.ts b/packages/cellix/local-dev/src/silent-runners/test-runners.ts
new file mode 100644
index 000000000..0699f2c95
--- /dev/null
+++ b/packages/cellix/local-dev/src/silent-runners/test-runners.ts
@@ -0,0 +1,23 @@
+import type { CommandSequenceStep } from './index.ts';
+import { pnpmScript } from './pnpm.ts';
+
+/**
+ * Runs the repository's architecture test script silently.
+ */
+export function architectureTests(script = 'test:arch'): CommandSequenceStep {
+ return pnpmScript(script);
+}
+
+/**
+ * Runs the repository's coverage-and-merge script silently.
+ */
+export function coverageMerge(script = 'test:coverage:merge'): CommandSequenceStep {
+ return pnpmScript(script);
+}
+
+/**
+ * Runs the repository's e2e test script silently until failure.
+ */
+export function e2eTests(script = 'test:e2e'): CommandSequenceStep {
+ return pnpmScript(script);
+}
diff --git a/packages/cellix/local-dev/tsconfig.json b/packages/cellix/local-dev/tsconfig.json
new file mode 100644
index 000000000..3b79c6fd1
--- /dev/null
+++ b/packages/cellix/local-dev/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "@cellix/config-typescript/node",
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "dist",
+ "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo"
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/cellix/local-dev/tsconfig.vitest.json b/packages/cellix/local-dev/tsconfig.vitest.json
new file mode 100644
index 000000000..50c446b6b
--- /dev/null
+++ b/packages/cellix/local-dev/tsconfig.vitest.json
@@ -0,0 +1,11 @@
+{
+ "extends": ["./tsconfig.json", "@cellix/config-typescript/vitest"],
+ "compilerOptions": {
+ "rootDir": "../../..",
+ "paths": {
+ "@cellix/local-dev": ["./src/index.ts"],
+ "@cellix/local-dev/silent-runners": ["./src/silent-runners/index.ts"]
+ }
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/cellix/local-dev/vitest.config.ts b/packages/cellix/local-dev/vitest.config.ts
new file mode 100644
index 000000000..629142ed9
--- /dev/null
+++ b/packages/cellix/local-dev/vitest.config.ts
@@ -0,0 +1,19 @@
+import { join } from 'node:path';
+import { getDirnameFromImportMetaUrl, nodeConfig } from '@cellix/config-vitest';
+import { mergeConfig } from 'vitest/config';
+
+const dirname = getDirnameFromImportMetaUrl(import.meta.url);
+
+export default mergeConfig(nodeConfig, {
+ test: {
+ typecheck: {
+ tsconfig: './tsconfig.vitest.json',
+ },
+ },
+ resolve: {
+ alias: {
+ '@cellix/local-dev/silent-runners': join(dirname, 'src/silent-runners/index.ts'),
+ '@cellix/local-dev': join(dirname, 'src/index.ts'),
+ },
+ },
+});
diff --git a/packages/cellix/serenity-framework/src/formatters/agent-formatter.ts b/packages/cellix/serenity-framework/src/formatters/agent-formatter.ts
index c06a3b36c..f41105253 100644
--- a/packages/cellix/serenity-framework/src/formatters/agent-formatter.ts
+++ b/packages/cellix/serenity-framework/src/formatters/agent-formatter.ts
@@ -104,6 +104,10 @@ export default class AgentFormatter extends Formatter {
}
private onTestRunFinished(testRunFinished: TestRunFinished): void {
+ if (this.issueCount === 0) {
+ return;
+ }
+
this.log('\n--- (Agent) Results ---\n');
const parts = Object.entries(this.statusCounts).map(([status, count]) => `${status}: ${count}`);
@@ -114,6 +118,6 @@ export default class AgentFormatter extends Formatter {
this.log(`Duration: ${ms}ms\n`);
}
- this.log(this.issueCount === 0 ? 'All scenarios passed.\n' : `Issues: ${this.issueCount}\n`);
+ this.log(`Issues: ${this.issueCount}\n`);
}
}
diff --git a/packages/cellix/server-oauth2-mock-seedwork/tests/file-user-store.test.ts b/packages/cellix/server-oauth2-mock-seedwork/tests/file-user-store.test.ts
index 04772575f..f6fdee1ca 100644
--- a/packages/cellix/server-oauth2-mock-seedwork/tests/file-user-store.test.ts
+++ b/packages/cellix/server-oauth2-mock-seedwork/tests/file-user-store.test.ts
@@ -126,9 +126,7 @@ describe('file user store oidcConfigName filtering', () => {
it('users without oidcConfigName are visible to all scoped stores', async () => {
if (!tmp) throw new Error('tmp not created');
- writeUsers(tmp, 'mock-oidc.users.json', [
- { username: 'shared@example.com', sub: 'sub-shared' },
- ]);
+ writeUsers(tmp, 'mock-oidc.users.json', [{ username: 'shared@example.com', sub: 'sub-shared' }]);
const endStore = createFileUserStore(tmp, 'end-user');
const staffStore = createFileUserStore(tmp, 'staff-user');
@@ -191,15 +189,7 @@ describe('file user store oidcConfigName filtering', () => {
expect(users).toHaveLength(1);
expect(users[0]?.username).toBe('good@example.com');
expect(warnSpy.length).toBeGreaterThan(0);
- expect(
- warnSpy.some((args) =>
- (args as unknown[]).some(
- (arg) =>
- typeof arg === 'string' &&
- arg.includes('"oidcConfigName" must be a string'),
- ),
- ),
- ).toBe(true);
+ expect(warnSpy.some((args) => (args as unknown[]).some((arg) => typeof arg === 'string' && arg.includes('"oidcConfigName" must be a string')))).toBe(true);
} finally {
console.warn = origWarn;
}
diff --git a/packages/cellix/server-oauth2-mock-seedwork/tests/portal-discovery.test.ts b/packages/cellix/server-oauth2-mock-seedwork/tests/portal-discovery.test.ts
index d364915c8..565ece8ac 100644
--- a/packages/cellix/server-oauth2-mock-seedwork/tests/portal-discovery.test.ts
+++ b/packages/cellix/server-oauth2-mock-seedwork/tests/portal-discovery.test.ts
@@ -569,11 +569,7 @@ describe('discoverPortalConfigs', () => {
envVars: { clientId: 'VITE_APP_PORTAL_STAFF_CLIENTID', redirectUri: 'VITE_APP_PORTAL_STAFF_REDIRECT' },
},
]);
- writeEnv(
- tmp,
- 'ui-portal/.env',
- 'VITE_APP_PORTAL_END_CLIENTID=end-cid\nVITE_APP_PORTAL_END_REDIRECT=https://portal/end/cb\nVITE_APP_PORTAL_STAFF_CLIENTID=staff-cid\nVITE_APP_PORTAL_STAFF_REDIRECT=https://portal/staff/cb\n',
- );
+ writeEnv(tmp, 'ui-portal/.env', 'VITE_APP_PORTAL_END_CLIENTID=end-cid\nVITE_APP_PORTAL_END_REDIRECT=https://portal/end/cb\nVITE_APP_PORTAL_STAFF_CLIENTID=staff-cid\nVITE_APP_PORTAL_STAFF_REDIRECT=https://portal/staff/cb\n');
const portals = discoverPortalConfigs(tmp);
expect(portals).toHaveLength(2);
diff --git a/packages/cellix/service-queue-storage/src/index.ts b/packages/cellix/service-queue-storage/src/index.ts
index 583824e3f..1e24249e1 100644
--- a/packages/cellix/service-queue-storage/src/index.ts
+++ b/packages/cellix/service-queue-storage/src/index.ts
@@ -1,7 +1,9 @@
-export type { InboundQueueDefinition, LoggingFieldSpec, OutboundQueueDefinition, QueueDefinition, QueueLoggingConfig, QueueMessage, QueueStorageConfig, QueueTriggerMetadata } from './interfaces.ts';
+export type { FromSchema, JSONSchema } from 'json-schema-to-ts';
export { defineQueue } from './define-queue.ts';
-export { $payload, payloadFields, resolveLoggingFields } from './logging-fields.ts';
+export type { InboundQueueDefinition, LoggingFieldSpec, OutboundQueueDefinition, QueueDefinition, QueueLoggingConfig, QueueMessage, QueueStorageConfig, QueueTriggerMetadata } from './interfaces.ts';
+export type { QueueServiceLifecycle, QueueServiceLogging } from './internal-queue-storage-service.ts';
export type { IQueueMessageLogger, MessageLogEnvelope, QueueMessageLogBlobStorage } from './logging.ts';
+export { $payload, payloadFields, resolveLoggingFields } from './logging-fields.ts';
export type { QueueConsumerContext } from './queue-consumer.ts';
export type { QueueProducerContext } from './queue-producer.ts';
export type {
@@ -14,5 +16,3 @@ export type {
RegisteredQueueService,
} from './register-queues.ts';
export { createRegisteredQueueService, deriveProvisionQueues, registerQueues } from './register-queues.ts';
-export type { QueueServiceLifecycle, QueueServiceLogging } from './internal-queue-storage-service.ts';
-export type { FromSchema, JSONSchema } from 'json-schema-to-ts';
diff --git a/packages/ocom-verification/acceptance-api/src/mock-application-services.ts b/packages/ocom-verification/acceptance-api/src/mock-application-services.ts
index 308365adb..7b162160a 100644
--- a/packages/ocom-verification/acceptance-api/src/mock-application-services.ts
+++ b/packages/ocom-verification/acceptance-api/src/mock-application-services.ts
@@ -4,8 +4,8 @@ import type { ApiContextSpec } from '@ocom/context-spec';
import { Persistence } from '@ocom/persistence';
import type { ServiceApolloServer } from '@ocom/service-apollo-server';
import type { BlobAddress, BlobStorageOperations, ClientUploadOperations, ListBlobsRequest, UploadTextBlobRequest } from '@ocom/service-blob-storage';
-import type { EndUserUpdatePayload, QueueStorageOperations } from '@ocom/service-queue-storage';
import type { ServiceMongoose } from '@ocom/service-mongoose';
+import type { EndUserUpdatePayload, QueueStorageOperations } from '@ocom/service-queue-storage';
import type { TokenValidation, TokenValidationResult } from '@ocom/service-token-validation';
import { actors } from '@ocom-verification/verification-shared/test-data';
diff --git a/packages/ocom-verification/acceptance-ui/src/contexts/community/step-definitions/create-community.steps.tsx b/packages/ocom-verification/acceptance-ui/src/contexts/community/step-definitions/create-community.steps.tsx
index be435d792..831e08c3b 100644
--- a/packages/ocom-verification/acceptance-ui/src/contexts/community/step-definitions/create-community.steps.tsx
+++ b/packages/ocom-verification/acceptance-ui/src/contexts/community/step-definitions/create-community.steps.tsx
@@ -16,11 +16,7 @@ Given('{word} is an authenticated community owner', async (actorName: string) =>
const actor = actorCalled(actorName);
const onSave = async (values: { name: string }): Promise => {
- await actor.attemptsTo(
- notes().set('formSubmitted', true),
- notes().set('communityName', values.name ?? ''),
- notes().set('communityCreationQueued', true),
- );
+ await actor.attemptsTo(notes().set('formSubmitted', true), notes().set('communityName', values.name ?? ''), notes().set('communityCreationQueued', true));
};
await actor.attemptsTo(
diff --git a/packages/ocom-verification/e2e-tests/src/contexts/community/step-definitions/create-community.steps.ts b/packages/ocom-verification/e2e-tests/src/contexts/community/step-definitions/create-community.steps.ts
index 300450fec..97e7876fd 100644
--- a/packages/ocom-verification/e2e-tests/src/contexts/community/step-definitions/create-community.steps.ts
+++ b/packages/ocom-verification/e2e-tests/src/contexts/community/step-definitions/create-community.steps.ts
@@ -4,12 +4,12 @@ import { type DataTable, Given, Then, When } from '@cucumber/cucumber';
import { actors } from '@ocom-verification/verification-shared/test-data';
import { actorCalled, notes } from '@serenity-js/core';
import { LogInWithOAuth2 } from '../../../shared/abilities/oauth2-login.ts';
+import { clearKnownQueueMessages, waitForCommunityCreationQueueMessage } from '../../../shared/support/queue-storage.ts';
import type { CommunityE2ENotes } from '../notes/community-notes.ts';
import { CommunityCreatedFlag } from '../questions/community-created-flag.ts';
import { CommunityErrorMessage } from '../questions/community-error-message.ts';
import { CommunityName } from '../questions/community-name.ts';
import { CreateCommunity } from '../tasks/create-community.ts';
-import { clearKnownQueueMessages, waitForCommunityCreationQueueMessage } from '../../../shared/support/queue-storage.ts';
let lastActorName = actors.CommunityOwner.name;
diff --git a/packages/ocom-verification/e2e-tests/src/shared/support/queue-storage.ts b/packages/ocom-verification/e2e-tests/src/shared/support/queue-storage.ts
index 24464c766..96159eab8 100644
--- a/packages/ocom-verification/e2e-tests/src/shared/support/queue-storage.ts
+++ b/packages/ocom-verification/e2e-tests/src/shared/support/queue-storage.ts
@@ -1,4 +1,4 @@
-import { ServiceQueueStorage, type CommunityCreationPayload } from '@ocom/service-queue-storage';
+import { type CommunityCreationPayload, ServiceQueueStorage } from '@ocom/service-queue-storage';
const communityCreationQueueName = 'community-creation';
let queueStorageService: ServiceQueueStorage | undefined;
@@ -56,9 +56,7 @@ export async function waitForCommunityCreationQueueMessage(expected: { community
while (Date.now() < deadline) {
const matchingMessage = (await service.peekAtCommunityCreationQueue(32)).find(
(message) =>
- message.payload.name === expected.name &&
- (expected.createdBy === undefined || message.payload.createdBy === expected.createdBy) &&
- (expected.communityId == null || message.payload.communityId === expected.communityId),
+ message.payload.name === expected.name && (expected.createdBy === undefined || message.payload.createdBy === expected.createdBy) && (expected.communityId == null || message.payload.communityId === expected.communityId),
)?.payload;
if (matchingMessage) {
diff --git a/packages/ocom/persistence/src/datasources/domain/community/community/community.domain-adapter.ts b/packages/ocom/persistence/src/datasources/domain/community/community/community.domain-adapter.ts
index bdc5d2536..2a238d9f6 100644
--- a/packages/ocom/persistence/src/datasources/domain/community/community/community.domain-adapter.ts
+++ b/packages/ocom/persistence/src/datasources/domain/community/community/community.domain-adapter.ts
@@ -1,9 +1,8 @@
import { MongooseSeedwork } from '@cellix/mongoose-seedwork';
-import { Domain } from '@ocom/domain';
-
-import { EndUserDomainAdapter } from '../../user/end-user/end-user.domain-adapter.ts';
import type { Community } from '@ocom/data-sources-mongoose-models/community';
import type { EndUser } from '@ocom/data-sources-mongoose-models/user/end-user';
+import { Domain } from '@ocom/domain';
+import { EndUserDomainAdapter } from '../../user/end-user/end-user.domain-adapter.ts';
export class CommunityConverter extends MongooseSeedwork.MongoTypeConverter> {
constructor() {
diff --git a/packages/ocom/service-queue-storage/src/schemas/inbound/end-user-update.ts b/packages/ocom/service-queue-storage/src/schemas/inbound/end-user-update.ts
index 7dd6f8bad..7753f436c 100644
--- a/packages/ocom/service-queue-storage/src/schemas/inbound/end-user-update.ts
+++ b/packages/ocom/service-queue-storage/src/schemas/inbound/end-user-update.ts
@@ -1,5 +1,5 @@
import { defineQueue } from '@cellix/service-queue-storage';
-import { schema as endUserUpdateSchema, type Schema as EndUserUpdatePayload } from './end-user-update.schema.generated.ts';
+import { type Schema as EndUserUpdatePayload, schema as endUserUpdateSchema } from './end-user-update.schema.generated.ts';
export type { EndUserUpdatePayload };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cf48ef53c..29e3cc045 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -129,9 +129,11 @@ overrides:
jiti: 2.6.1
rollup: ^4.59.0
'@ant-design/pro-layout>path-to-regexp': ^8.4.0
- brace-expansion@1.1.12: 1.1.13
- brace-expansion@5.0.4: 5.0.6
- brace-expansion@5.0.5: 5.0.6
+ brace-expansion@1.1.12: 5.0.7
+ brace-expansion@1.1.13: 5.0.7
+ brace-expansion@5.0.4: 5.0.7
+ brace-expansion@5.0.5: 5.0.7
+ brace-expansion@5.0.6: 5.0.7
diff@4.0.2: 4.0.4
'@protobufjs/codegen': 2.0.5
'@protobufjs/utf8': 1.1.1
@@ -166,11 +168,11 @@ overrides:
'@babel/plugin-transform-modules-systemjs': 7.29.4
'@babel/core': ^7.29.6
js-yaml@4.1.1: 4.2.0
- shell-quote@<1.8.4: 1.8.4
+ shell-quote@<1.9.0: 1.9.0
'@opentelemetry/exporter-prometheus@0.57.2': 0.217.0
'@opentelemetry/core@2.7.1': 2.8.0
ws: 8.21.0
- shell-quote: 1.8.4
+ shell-quote: 1.9.0
'@grpc/grpc-js': '>=1.14.4'
form-data: ^4.0.6
webpack-dev-server>http-proxy-middleware: 3.0.7
@@ -208,6 +210,9 @@ importers:
'@cellix/graphql-codegen':
specifier: workspace:*
version: link:packages/cellix/graphql-codegen
+ '@cellix/local-dev':
+ specifier: workspace:*
+ version: link:packages/cellix/local-dev
'@graphql-codegen/cli':
specifier: ^5.0.7
version: 5.0.7(@parcel/watcher@2.5.1)(@types/node@22.19.15)(graphql@16.12.0)(supports-color@8.1.1)(typescript@6.0.3)
@@ -910,6 +915,30 @@ importers:
specifier: 'catalog:'
version: 4.1.8(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-istanbul@4.1.8)(happy-dom@20.10.1)(jsdom@26.1.0)(vite@8.0.16(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
+ packages/cellix/local-dev:
+ devDependencies:
+ '@cellix/config-typescript':
+ specifier: workspace:*
+ version: link:../config-typescript
+ '@cellix/config-vitest':
+ specifier: workspace:*
+ version: link:../config-vitest
+ '@types/node':
+ specifier: 'catalog:'
+ version: 22.19.15
+ '@vitest/coverage-istanbul':
+ specifier: 'catalog:'
+ version: 4.1.8(vitest@4.1.8)
+ rimraf:
+ specifier: 'catalog:'
+ version: 6.0.1
+ typescript:
+ specifier: 'catalog:'
+ version: 6.0.3
+ vitest:
+ specifier: 'catalog:'
+ version: 4.1.8(@opentelemetry/api@1.9.0)(@types/node@22.19.15)(@vitest/browser-playwright@4.1.8)(@vitest/coverage-istanbul@4.1.8)(happy-dom@20.10.1)(jsdom@26.1.0)(vite@8.0.16(@types/node@22.19.15)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))
+
packages/cellix/mongoose-seedwork:
dependencies:
'@cellix/domain-seedwork':
@@ -7818,14 +7847,11 @@ packages:
resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==}
engines: {node: '>=14.16'}
- brace-expansion@1.1.13:
- resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
-
brace-expansion@2.0.3:
resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==}
- brace-expansion@5.0.6:
- resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
+ brace-expansion@5.0.7:
+ resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
engines: {node: 18 || 20 || >=22}
braces@3.0.3:
@@ -8258,9 +8284,6 @@ packages:
compute-scroll-into-view@3.1.1:
resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==}
- concat-map@0.0.1:
- resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-
concurrently@9.2.1:
resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==}
engines: {node: '>=18'}
@@ -12659,8 +12682,8 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- shell-quote@1.8.4:
- resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==}
+ shell-quote@1.9.0:
+ resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
engines: {node: '>= 0.4'}
shimmer@1.2.1:
@@ -17067,7 +17090,7 @@ snapshots:
listr2: 4.0.5
log-symbols: 4.1.0
micromatch: 4.0.8
- shell-quote: 1.8.4
+ shell-quote: 1.9.0
string-env-interpolation: 1.0.1
ts-log: 2.2.7
tslib: 2.8.1
@@ -20606,16 +20629,11 @@ snapshots:
widest-line: 4.0.1
wrap-ansi: 8.1.0
- brace-expansion@1.1.13:
- dependencies:
- balanced-match: 1.0.2
- concat-map: 0.0.1
-
brace-expansion@2.0.3:
dependencies:
balanced-match: 1.0.2
- brace-expansion@5.0.6:
+ brace-expansion@5.0.7:
dependencies:
balanced-match: 4.0.4
@@ -21072,13 +21090,11 @@ snapshots:
compute-scroll-into-view@3.1.1: {}
- concat-map@0.0.1: {}
-
concurrently@9.2.1:
dependencies:
chalk: 4.1.2
rxjs: 7.8.2
- shell-quote: 1.8.4
+ shell-quote: 1.9.0
supports-color: 8.1.1
tree-kill: 1.2.2
yargs: 17.7.2
@@ -23368,7 +23384,7 @@ snapshots:
launch-editor@2.14.1:
dependencies:
picocolors: 1.1.1
- shell-quote: 1.8.4
+ shell-quote: 1.9.0
less@4.4.2:
dependencies:
@@ -24189,11 +24205,11 @@ snapshots:
minimatch@10.2.4:
dependencies:
- brace-expansion: 5.0.6
+ brace-expansion: 5.0.7
minimatch@3.1.5:
dependencies:
- brace-expansion: 1.1.13
+ brace-expansion: 5.0.7
minimatch@9.0.9:
dependencies:
@@ -26359,7 +26375,7 @@ snapshots:
shebang-regex@3.0.0: {}
- shell-quote@1.8.4: {}
+ shell-quote@1.9.0: {}
shimmer@1.2.1: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index a5e8c4715..50ba38468 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -80,9 +80,11 @@ overrides:
jiti: 2.6.1
rollup: ^4.59.0
'@ant-design/pro-layout>path-to-regexp': ^8.4.0
- 'brace-expansion@1.1.12': 1.1.13
- 'brace-expansion@5.0.4': 5.0.6
- 'brace-expansion@5.0.5': 5.0.6
+ 'brace-expansion@1.1.12': 5.0.7
+ 'brace-expansion@1.1.13': 5.0.7
+ 'brace-expansion@5.0.4': 5.0.7
+ 'brace-expansion@5.0.5': 5.0.7
+ 'brace-expansion@5.0.6': 5.0.7
'diff@4.0.2': 4.0.4
'@protobufjs/codegen': 2.0.5
'@protobufjs/utf8': 1.1.1
@@ -117,11 +119,11 @@ overrides:
'@babel/plugin-transform-modules-systemjs': 7.29.4
'@babel/core': ^7.29.6
'js-yaml@4.1.1': 4.2.0
- 'shell-quote@<1.8.4': 1.8.4
+ 'shell-quote@<1.9.0': 1.9.0
'@opentelemetry/exporter-prometheus@0.57.2': 0.217.0
'@opentelemetry/core@2.7.1': 2.8.0
ws: 8.21.0
- shell-quote: 1.8.4
+ shell-quote: 1.9.0
'@grpc/grpc-js': '>=1.14.4'
form-data: ^4.0.6
'webpack-dev-server>http-proxy-middleware': 3.0.7
diff --git a/readme.md b/readme.md
index 345a2fcb3..05d5d6d10 100644
--- a/readme.md
+++ b/readme.md
@@ -169,6 +169,7 @@ The local mock OpenID Connect provider used for UI development is configured per
}
```
+
The envVars values are environment variable names (from the UI app's `.env` file) that the server resolves to actual values at startup.
Per-developer overrides
@@ -231,6 +232,7 @@ CellixJS integrates [Agent Skills](https://agentskills.io) to provide AI coding
The managed skill set lives in [`.agents/skills/`](.agents/skills/) and is mirrored for Copilot discovery through symlinks in [`.github/skills/`](.github/skills/). Only skills relevant to normal work in this repo are committed.
+
**Current managed skills:**
- **madr-enforcement**: Enforces ADR-defined architectural standards across the codebase
- **turborepo**: Guides monorepo task graph, caching, and pipeline work
diff --git a/turbo.json b/turbo.json
index 3a716dd69..e214fb9fc 100644
--- a/turbo.json
+++ b/turbo.json
@@ -34,7 +34,7 @@
"test": {
"description": "Runs the default test suite for each package",
"dependsOn": ["^build", "@cellix/config-vitest#build"],
- "inputs": ["$TURBO_DEFAULT$", "**/*.test.ts", "**/*.stories.tsx", "!coverage/**", "!target/**", "!dist/**", "!build/**", "!deploy/**"]
+ "inputs": ["$TURBO_DEFAULT$", "**/*.test.ts", "**/*.stories.tsx", "!coverage/**", "!target/**", "!dist/**", "!build/**", "!deploy/**"]
},
"test:arch": {
"description": "Runs architecture unit tests for each package",