You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Score: +3 touches 10+ files, +2 new system/module from scratch, +2 complex lifecycle/error state logic, +2 multi-package changes.
1. Context
Problem: Night Watch has provider execution, fallback, queueing, feedback, and action-trigger policy spread across bash scripts, CLI commands, and server routes; this makes cross-cutting behavior hard to reuse consistently across executor, reviewer, QA, audit, and merger.
packages/cli/src/commands/merge.ts — merger command entry and CI policy wiring
packages/server/src/routes/action.routes.ts — server action gates and manual trigger spawning
packages/server/src/routes/feedback.routes.ts — feedback outcome and augmentation API
Current Behavior:
Fallback already exists for executor rate limits in scripts/night-watch-cron.sh: a 429 can trigger configured preset/model fallback, emits rate_limit_fallback=1, and records rate_limited when exhausted.
Provider selection already flows through jobProviders, resolveJobProvider, resolvePreset, and buildBaseEnvVars.
Global queue behavior already exists in packages/core/src/utils/job-queue.ts and is activated from bash scripts with NW_QUEUE_ENABLED.
Feedback already records outcomes in CLI code and exposes summaries/patterns through server routes.
Action routes already gate some manual triggers with lock checks and bypass queueing for manual UI actions via NW_QUEUE_ENABLED=0.
Integration Points Checklist:
**How will this feature be reached?**-[x] Entry point identified: CLI job commands (`run`, `review`, `qa`, `audit`, `merge`) and server action routes (`/api/actions/*`)
-[x] Caller file identified: `packages/cli/src/commands/shared/env-builder.ts` exports middleware env/context; job commands invoke lifecycle hooks before/after script execution; `packages/server/src/routes/action.routes.ts` invokes action gate hooks before spawning commands
-[x] Registration/wiring needed: export middleware from `packages/core/src/index.ts`; register built-in middleware from CLI command helpers; wire server action gate calls into `spawnAction`**Is this user-facing?**-[x] NO → Internal/background architecture improvement. It is triggered by existing CLI commands, cron scripts, server action routes, and provider runs. No new UI is required in this PRD.
**Full user flow:**1. User or cron triggers an existing Night Watch job, such as `night-watch run`, `night-watch review`, `night-watch qa`, `night-watch audit`, or `night-watch merge`.
2. The existing CLI command loads config, resolves the job provider, builds env vars, and enters the provider/action middleware runner.
3. Middleware runs typed hooks for action gates, prompt/context injection, secret redaction, provider attempt tracing, failure classification, fallback planning, and policy enforcement.
4. Existing scripts still perform provider execution during early phases; fallback behavior is first observed and then centralized without changing user-facing behavior.
5. User sees the same command/API behavior as before, plus more consistent logs, session outcomes, and feedback patterns.
2. Solution
Approach:
Add a small typed lifecycle middleware layer in @night-watch/core, inspired by Google Genkit's middleware idea of composable execution hooks, but with no Genkit dependency and no adoption of Genkit runtime concepts.
Treat existing fallback as behavior to preserve and centralize, not as a new feature. The middleware first observes current fallback markers, then moves classification and fallback decision logic into typed core helpers while bash remains a compatibility execution layer.
Provide two middleware surfaces: provider lifecycle hooks for AI provider attempts and action gate hooks for manual/server-triggered job actions.
Reuse current architecture: JOB_REGISTRY for job metadata, config normalization for policy inputs, job-queue.ts for queue context, feedback repositories for prompt augmentation/outcome storage, and env-builder.ts for provider env assembly.
Keep scope internal and typed: no external plugin marketplace, no Genkit package, no user-authored arbitrary middleware loading in phase one.
Compatibility: bash scripts remain callable; existing env vars and result markers remain stable until replacement phases prove equivalent behavior.
Security: secret redaction is centralized before traces, logs, metadata, and failure signatures are persisted.
Data Changes: None in the initial implementation. Use existing session outcome and feedback tables. If trace detail later outgrows session outcome metadata, create a separate PRD for trace storage.
3. Sequence Flow
sequenceDiagram
participant U as User/Cron
participant CLI as CLI Command
participant MW as Middleware Runner
participant Script as Existing Bash Script
participant FB as Feedback/Outcome Store
participant API as Server Action Route
alt Server-triggered action
U->>API: POST /api/actions/run
API->>MW: runActionGates(action, jobType, projectDir)
alt Denied
MW-->>API: deny(reason, statusCode)
API-->>U: 409/403 with reason
else Allowed
MW-->>API: allow(extraEnv)
API->>CLI: spawn night-watch command
end
else CLI/cron-triggered job
U->>CLI: night-watch run/review/qa/audit/merge
end
CLI->>MW: beforeRun(context)
MW-->>CLI: prompt additions, redaction rules, trace id
CLI->>Script: execute existing script with NW_* env
Script-->>CLI: exitCode + NIGHT_WATCH_RESULT markers
CLI->>MW: afterAttempt(result)
MW->>MW: classify failure / detect rate limit / decide fallback metadata
CLI->>FB: recordJobOutcome(redacted metadata)
FB-->>CLI: stored outcome + feedback analysis
CLI-->>U: existing command/API behavior
Loading
4. Execution Phases
Phase 1: Typed Middleware Core — "Provider/action runs can pass through typed hooks without changing behavior"
should fail open when an observability hook throws
run continues and trace warning is attached
Verification Plan:
Unit Tests:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/runner.test.ts
Type Check:
yarn workspace @night-watch/core verify
Evidence Required:
Hook ordering test passes
Action gate fail-closed test passes
Core package typecheck passes
User Verification:
Action: Run yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/runner.test.ts
Expected: Tests pass and no existing CLI behavior changes because the core runner is exported but not yet wired into job commands.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 1 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
current provider env keys from providerEnv and preset envVars
Telegram webhook tokens/chat IDs before trace metadata is persisted
Ensure prompt augmentation uses existing buildProjectFeedbackPromptBlock and respects current feedback config.
Update recordJobOutcome to accept optional middleware trace/classification metadata, merge it into outcome metadata, and avoid storing unredacted fields.
Do not add new UI or API fields in this phase; use existing metadata storage only.
should store middleware classification without raw secrets
session outcome metadata includes category and excludes token values
Verification Plan:
Unit Tests:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/built-ins.test.ts
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/shared/feedback.test.ts
Type Check:
yarn workspace @night-watch/core verify
yarn workspace @jonit-dev/night-watch-cli verify
Evidence Required:
Redaction tests prove raw secret values are not stored
Classification tests preserve current rate-limit category behavior
Feedback prompt injection remains config-gated
User Verification:
Action: Run the listed tests with a fake provider env containing ANTHROPIC_API_KEY=test-secret.
Expected: Test output passes and stored metadata contains [REDACTED], never test-secret.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 2 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
Phase 3: CLI Provider Lifecycle Wiring — "Executor, reviewer, QA, audit, and merger enter middleware around existing script execution"
Files (max 5):
packages/cli/src/commands/shared/provider-lifecycle.ts — shared helper to create context, run hooks, execute script, classify result, record outcome
packages/cli/src/commands/run.ts — delegate executor lifecycle metadata and outcome recording to shared helper while preserving env vars
packages/cli/src/commands/review.ts — wire reviewer through shared lifecycle helper
packages/cli/src/commands/qa.ts — wire QA through shared lifecycle helper
appends prompt/context additions only when the command has a prompt surface
executes the existing script using current executeScriptWithOutput
parses NIGHT_WATCH_RESULT markers with existing parseScriptResult
runs afterProviderAttempt, classifyFailure, and afterRun
calls recordJobOutcome with redacted middleware metadata
Keep existing env var behavior stable, especially:
NW_FALLBACK_ON_RATE_LIMIT
NW_CLAUDE_PRIMARY_MODEL_ID
NW_CLAUDE_SECONDARY_MODEL_ID
NW_FALLBACK_*_PRESET_*
NW_QUEUE_*
Ensure fallback remains implemented by existing script behavior in this phase; middleware observes and classifies it, but does not replace it yet.
Apply shared lifecycle helper to run, review, and qa commands first.
Add a follow-up task in this PRD phase notes for audit/merger wiring if their command shape requires a second small patch; do not exceed the five-file phase limit.
should continue when optional trace middleware throws
script execution still occurs
Verification Plan:
Unit Tests:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/provider-lifecycle.test.ts
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/run.test.ts packages/cli/src/__tests__/commands/qa.test.ts
Script Smoke Tests:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "rate-limit fallback"
Type Check:
yarn workspace @jonit-dev/night-watch-cli verify
Evidence Required:
Existing executor fallback smoke tests still pass
Middleware metadata is recorded without changing script markers
run, review, and qa command tests pass
User Verification:
Action: Run the existing rate-limit fallback smoke test.
Expected: Executor still emits rate_limit_fallback=1 on successful fallback and still emits rate_limited when fallback is disabled or exhausted.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 3 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
Phase 4: Action Gate Middleware — "Server and manual triggers use shared typed policy checks before spawning jobs"
Files (max 5):
packages/core/src/middleware/action-gates.ts — built-in gates for locks, paused jobs, queue bypass policy, job registry validation, and provider policy
packages/core/src/__tests__/middleware/action-gates.test.ts — unit tests for gates
packages/server/src/routes/action.routes.ts — invoke shared gates from spawnAction
packages/server/src/__tests__/server/action-routes.test.ts — API tests for denied/allowed actions
should spawn registry-driven job route when action gate allows
response { started: true, pid } is unchanged
Verification Plan:
Unit Tests:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/action-gates.test.ts
API Tests:
yarn workspace @night-watch/server test -- packages/server/src/__tests__/server/action-routes.test.ts
Type Check:
yarn workspace @night-watch/core verify
yarn workspace @night-watch/server verify
API Proof:
curl -X POST http://localhost:3000/api/actions/run \
-H "Content-Type: application/json" \
-d '{}'# Expected when no executor lock exists: {"started":true,"pid":<number>}# Expected when executor lock is active: HTTP 409 with existing "Executor is already running" shape
Evidence Required:
Action API behavior remains backward compatible
Gate logic is shared instead of duplicated in server routes
Registry-driven actions continue to work
User Verification:
Action: Trigger POST /api/actions/run while executor lock is active, then again after clearing the lock.
Expected: Active lock returns 409; no lock starts the existing command with no UI contract change.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 4 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
Phase 5: Centralize Fallback Policy Without Changing Fallback Behavior — "Existing rate-limit fallback decisions are represented as typed policy outcomes"
Files (max 5):
packages/core/src/middleware/fallback-policy.ts — typed fallback decision helpers based on current config and provider attempt classification
packages/core/src/__tests__/middleware/fallback-policy.test.ts — preserve current fallback decision matrix
packages/cli/src/commands/run.ts — use fallback policy to build existing fallback env vars and trace policy decisions
packages/cli/src/__tests__/commands/run.test.ts — verify existing env and marker behavior remains stable
packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts — update/add fallback smoke assertions only if needed
Implementation:
Add resolveFallbackPolicy(config, providerContext) that returns:
enabled
primaryPreset / secondaryPreset
primaryModel / secondaryModel
reasonIfUnavailable
preservesExistingScriptFallback: true
Use the policy in run.ts to populate the same env vars currently used by scripts/night-watch-cron.sh.
Do not move provider re-execution out of bash in this phase. The script remains responsible for actual retry/fallback execution.
Add trace metadata that explains whether fallback was configured, attempted, skipped, or missing configuration.
Preserve these existing behaviors exactly:
fallback only triggers on detected rate limit in executor script
fallbackOnRateLimit=false disables fallback
primary preset takes precedence over model fallback
secondary preset/model is tried only when primary fallback is rate-limited
executor should trigger native Claude fallback and include rate_limit_fallback marker when proxy returns 429
existing smoke test remains green
Verification Plan:
Unit Tests:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/fallback-policy.test.ts
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/run.test.ts
Smoke Tests:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "fallback"
Type Check:
yarn workspace @night-watch/core verify
yarn workspace @jonit-dev/night-watch-cli verify
Evidence Required:
Existing fallback tests are unchanged or only updated to inspect typed policy metadata
No PRD text or release note describes fallback as a new feature
Bash fallback execution remains stable while policy construction is centralized
User Verification:
Action: Run fallback smoke tests for enabled, disabled, secondary, and missing-configuration cases.
Expected: All existing result markers and outcomes match pre-middleware behavior.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 5 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
Phase 6: Complete Job Coverage and Documentation — "All provider-backed job types share lifecycle hooks with clear internal docs"
Files (max 5):
packages/cli/src/commands/audit.ts — wire audit through shared provider lifecycle helper
packages/cli/src/commands/merge.ts — wire merger action/policy tracing where provider or repair actions run
packages/cli/src/__tests__/commands/merge.test.ts — verify merger action policy tracing does not alter merge decisions
docs/architecture/provider-action-middleware.md — internal architecture note with hook contracts and non-goals
Implementation:
Wire audit command through the shared lifecycle helper, preserving audit-specific max runtime, branch patterns, reports, and fallback-on-rate-limit behavior already present in scripts/night-watch-audit-cron.sh.
Wire merger through action/policy tracing for merge orchestration and AI repair paths, preserving ciPolicy=fallback-local behavior as a merger CI policy, not provider rate-limit fallback.
Document hook contract rules:
built-ins only for now
no Genkit dependency
no marketplace/plugin loading
no arbitrary project-provided hook execution
action gates fail closed; observability hooks fail open
Document integration points for future provider execution migration out of bash, but keep that out of scope.
Tests Required:
Test File
Test Name
Assertion
packages/cli/src/__tests__/commands/audit.test.ts
should preserve audit env vars when lifecycle middleware is enabled
audit command env remains compatible with script
packages/cli/src/__tests__/commands/audit.test.ts
should classify audit rate limits without changing retry behavior
outcome classification is rate-limited and script controls retry
packages/cli/src/__tests__/commands/merge.test.ts
should preserve fallback-local CI policy behavior
local check fallback still controls merge eligibility
packages/cli/src/__tests__/commands/merge.test.ts
should record merger policy trace without raw command secrets
metadata is redacted
Verification Plan:
Unit Tests:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/audit.test.ts packages/cli/src/__tests__/commands/merge.test.ts
Smoke Tests:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "merger"
Full Package Verification:
yarn verify
Evidence Required:
Executor, reviewer, QA, audit, and merger have lifecycle integration
Merger CI fallback-local behavior is not conflated with provider rate-limit fallback
Architecture docs list non-goals and future migration points
User Verification:
Action: Run yarn verify and targeted audit/merger tests.
Expected: All checks pass, with no user-facing command or API behavior changes except improved internal metadata consistency.
Checkpoint: Spawn prd-work-reviewer with prompt: Review checkpoint for phase 6 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.
5. Checkpoint Protocol
After each phase:
Run the phase's unit/API/smoke tests.
Run the listed package type checks.
Spawn the automated checkpoint reviewer:
Use Task tool with:
- subagent_type: "prd-work-reviewer"
- prompt: "Review checkpoint for phase [N] of PRD at docs/prds/provider-action-middleware.md"
Continue to the next phase only when the checkpoint reviewer reports PASS.
Manual checkpoints are not required because this PRD is internal/background architecture work with no UI changes. API proof for action routes is included in Phase 4.
6. Verification Strategy
Unit Tests:
Middleware runner hook ordering and error semantics
Built-in redaction, trace metadata, feedback prompt injection, and failure classification
Fallback policy decision matrix
Action gates for locks, paused jobs, registry validation, and queue bypass behavior
Integration Tests:
CLI command tests for run, review, qa, audit, and merge
Server action route tests for allowed/denied actions
Feedback outcome tests ensuring middleware metadata is stored redacted
Script Smoke Tests:
Existing executor fallback tests in packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts
Existing merger local fallback tests to prove CI fallback policy remains separate from provider fallback
Queue-related smoke tests if lifecycle metadata touches NW_QUEUE_*
API Proof:
curl -X POST http://localhost:3000/api/actions/run \
-H "Content-Type: application/json" \
-d '{}'# Expected when allowed: {"started":true,"pid":<number>}# Expected when denied by active lock: HTTP 409 and existing lock error message shape
Final Verification Commands:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/provider-lifecycle.test.ts
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/run.test.ts packages/cli/src/__tests__/commands/qa.test.ts packages/cli/src/__tests__/commands/audit.test.ts packages/cli/src/__tests__/commands/merge.test.ts
yarn workspace @night-watch/server test -- packages/server/src/__tests__/server/action-routes.test.ts
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "fallback"
yarn verify
Evidence Required:
Current rate-limit fallback smoke tests pass without behavioral drift
Metadata/traces never persist raw provider secrets
Action routes preserve current response shapes
All provider-backed job types are wired through shared lifecycle helpers
yarn verify passes
7. Acceptance Criteria
All phases complete.
All specified tests pass.
yarn verify passes.
All automated checkpoint reviews pass.
Middleware is reachable from existing entry points and no hook module is orphaned.
Fallback is documented and implemented as existing behavior that has been centralized/refactored, not as a new feature.
No Genkit dependency is added to any package.json.
No broad plugin marketplace, arbitrary project hook loading, or user-authored middleware execution is introduced.
Provider lifecycle hooks cover executor, reviewer, QA, audit, and merger.
Action gate hooks cover server-triggered job actions and preserve current lock/queue behavior.
Prompt/context injection reuses the existing feedback augmentation system.
Secret redaction is applied before storing trace metadata, failure signatures, or outcome metadata.
Failure classification distinguishes at minimum rate_limited, network, context_exhausted, timeout, policy_denied, and uncategorized.
Merger fallback-local CI behavior remains separate from provider rate-limit fallback.
PRD: Provider/Action Lifecycle Middleware
Complexity: 9 → HIGH mode
Score: +3 touches 10+ files, +2 new system/module from scratch, +2 complex lifecycle/error state logic, +2 multi-package changes.
1. Context
Problem: Night Watch has provider execution, fallback, queueing, feedback, and action-trigger policy spread across bash scripts, CLI commands, and server routes; this makes cross-cutting behavior hard to reuse consistently across executor, reviewer, QA, audit, and merger.
Files Analyzed:
scripts/night-watch-cron.sh— executor provider execution, retry, existing rate-limit fallback, result markersscripts/night-watch-helpers.sh— provider command construction, rate-limit detection, notification helperspackages/core/src/config.ts— config loading withfallbackOnRateLimit, fallback models,jobProviders,feedback,queuepackages/core/src/config-normalize.ts— existing fallback config normalizationpackages/core/src/config-env.ts— existing environment overrides for fallback settingspackages/core/src/constants.ts— fallback, provider, queue, feedback defaultspackages/core/src/types.ts— provider, feedback, session outcome, job type typespackages/core/src/jobs/job-registry.ts— single source of truth for job metadata across executor/reviewer/QA/audit/mergerpackages/core/src/utils/job-queue.ts— global queue and provider-aware dispatch utilitiespackages/core/src/feedback/outcome-parser.ts— failure classification and outcome parsingpackages/core/src/feedback/prompt-augmenter.ts— feedback-driven prompt/context injectionpackages/cli/src/commands/shared/env-builder.ts— shared provider env construction and queue env wiringpackages/cli/src/commands/shared/feedback.ts— session outcome recording and feedback analysispackages/cli/src/commands/run.ts— executor env construction including existing rate-limit fallback wiringpackages/cli/src/commands/review.ts— reviewer provider command entrypackages/cli/src/commands/qa.ts— QA provider command entrypackages/cli/src/commands/audit.ts— audit provider command entrypackages/cli/src/commands/merge.ts— merger command entry and CI policy wiringpackages/server/src/routes/action.routes.ts— server action gates and manual trigger spawningpackages/server/src/routes/feedback.routes.ts— feedback outcome and augmentation APICurrent Behavior:
scripts/night-watch-cron.sh: a 429 can trigger configured preset/model fallback, emitsrate_limit_fallback=1, and recordsrate_limitedwhen exhausted.fallbackOnRateLimit,primaryFallbackModel,secondaryFallbackModel,primaryFallbackPreset,secondaryFallbackPreset.jobProviders,resolveJobProvider,resolvePreset, andbuildBaseEnvVars.packages/core/src/utils/job-queue.tsand is activated from bash scripts withNW_QUEUE_ENABLED.NW_QUEUE_ENABLED=0.Integration Points Checklist:
2. Solution
Approach:
@night-watch/core, inspired by Google Genkit's middleware idea of composable execution hooks, but with no Genkit dependency and no adoption of Genkit runtime concepts.JOB_REGISTRYfor job metadata, config normalization for policy inputs,job-queue.tsfor queue context, feedback repositories for prompt augmentation/outcome storage, andenv-builder.tsfor provider env assembly.Architecture Diagram:
flowchart TD Trigger[cron / CLI / server action] --> Gate[Action gate middleware] Gate --> Config[loadConfig + JOB_REGISTRY] Config --> Context[Build ProviderActionContext] Context --> Prompt[Prompt/context middleware] Prompt --> Provider[Existing script/provider execution] Provider --> Classifier[Failure classification middleware] Classifier --> Fallback[Existing fallback policy, centralized over phases] Fallback --> Trace[Run trace + redaction middleware] Trace --> Feedback[Session outcome + feedback analysis]Key Decisions:
JOB_REGISTRY,resolveJobProvider,resolvePreset,buildBaseEnvVars,job-queue,recordJobOutcome,buildProjectFeedbackPromptBlock,analyzeFeedbackOutcome,parseScriptResult.Data Changes: None in the initial implementation. Use existing session outcome and feedback tables. If trace detail later outgrows session outcome metadata, create a separate PRD for trace storage.
3. Sequence Flow
sequenceDiagram participant U as User/Cron participant CLI as CLI Command participant MW as Middleware Runner participant Script as Existing Bash Script participant FB as Feedback/Outcome Store participant API as Server Action Route alt Server-triggered action U->>API: POST /api/actions/run API->>MW: runActionGates(action, jobType, projectDir) alt Denied MW-->>API: deny(reason, statusCode) API-->>U: 409/403 with reason else Allowed MW-->>API: allow(extraEnv) API->>CLI: spawn night-watch command end else CLI/cron-triggered job U->>CLI: night-watch run/review/qa/audit/merge end CLI->>MW: beforeRun(context) MW-->>CLI: prompt additions, redaction rules, trace id CLI->>Script: execute existing script with NW_* env Script-->>CLI: exitCode + NIGHT_WATCH_RESULT markers CLI->>MW: afterAttempt(result) MW->>MW: classify failure / detect rate limit / decide fallback metadata CLI->>FB: recordJobOutcome(redacted metadata) FB-->>CLI: stored outcome + feedback analysis CLI-->>U: existing command/API behavior4. Execution Phases
Phase 1: Typed Middleware Core — "Provider/action runs can pass through typed hooks without changing behavior"
Files (max 5):
packages/core/src/middleware/types.ts— define lifecycle context, hook result, failure classification, trace event, action gate typespackages/core/src/middleware/runner.ts— implement ordered hook runner with fail-open/fail-closed semanticspackages/core/src/middleware/index.ts— export middleware public API for corepackages/core/src/index.ts— export new middleware modulepackages/core/src/__tests__/middleware/runner.test.ts— prove hook ordering, context mutation, and error handlingImplementation:
ProviderActionContextwithjobType,projectDir,providerKey,providerCommand,providerLabel,config,traceId,source(cron|cli|server-action), and optionalscriptResult.ProviderLifecycleMiddlewarehooks:beforeRun(context)beforeProviderAttempt(context)afterProviderAttempt(context, result)classifyFailure(context, result)afterRun(context, result)ActionGateMiddlewarehook:beforeAction(context)returningallow,deny, ormodifyEnv.uncategorizedon hook error@night-watch/core.Tests Required:
packages/core/src/__tests__/middleware/runner.test.tsshould run provider lifecycle hooks in registration orderpackages/core/src/__tests__/middleware/runner.test.tsshould preserve context changes from beforeRun hookspackages/core/src/__tests__/middleware/runner.test.tsshould fail closed when an action gate throwspackages/core/src/__tests__/middleware/runner.test.tsshould fail open when an observability hook throwsVerification Plan:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/runner.test.tsyarn workspace @night-watch/core verifyUser Verification:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/runner.test.tsCheckpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 1 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.Phase 2: Built-In Context, Trace, and Redaction Middleware — "Existing job commands emit consistent redacted lifecycle metadata"
Files (max 5):
packages/core/src/middleware/built-ins.ts— implement built-in trace, secret redaction, prompt augmentation, and failure classification middlewarepackages/core/src/middleware/redaction.ts— central redaction helpers for env, logs, metadata, provider command detailspackages/core/src/__tests__/middleware/built-ins.test.ts— unit coverage for built-inspackages/cli/src/commands/shared/feedback.ts— include middleware classification and redacted trace metadata in recorded outcomespackages/cli/src/__tests__/commands/shared/feedback.test.ts— verify redacted metadata is storedImplementation:
createDefaultProviderMiddleware(config)returning built-ins for:TOKEN,KEY,SECRET,PASSWORD,AUTHproviderEnvand presetenvVarsbuildProjectFeedbackPromptBlockand respects current feedback config.recordJobOutcometo accept optional middleware trace/classification metadata, merge it into outcome metadata, and avoid storing unredacted fields.Tests Required:
packages/core/src/__tests__/middleware/built-ins.test.tsshould redact provider secrets from trace metadata[REDACTED]packages/core/src/__tests__/middleware/built-ins.test.tsshould classify rate limit failures when output contains 429rate_limitedpackages/core/src/__tests__/middleware/built-ins.test.tsshould build feedback prompt context when active augmentations existpackages/cli/src/__tests__/commands/shared/feedback.test.tsshould store middleware classification without raw secretsVerification Plan:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/built-ins.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/shared/feedback.test.tsyarn workspace @night-watch/core verifyyarn workspace @jonit-dev/night-watch-cli verifyUser Verification:
ANTHROPIC_API_KEY=test-secret.[REDACTED], nevertest-secret.Checkpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 2 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.Phase 3: CLI Provider Lifecycle Wiring — "Executor, reviewer, QA, audit, and merger enter middleware around existing script execution"
Files (max 5):
packages/cli/src/commands/shared/provider-lifecycle.ts— shared helper to create context, run hooks, execute script, classify result, record outcomepackages/cli/src/commands/run.ts— delegate executor lifecycle metadata and outcome recording to shared helper while preserving env varspackages/cli/src/commands/review.ts— wire reviewer through shared lifecycle helperpackages/cli/src/commands/qa.ts— wire QA through shared lifecycle helperpackages/cli/src/__tests__/commands/provider-lifecycle.test.ts— shared lifecycle command testsImplementation:
runJobWithProviderLifecycle(input)that:ProviderActionContextbeforeRunexecuteScriptWithOutputNIGHT_WATCH_RESULTmarkers with existingparseScriptResultafterProviderAttempt,classifyFailure, andafterRunrecordJobOutcomewith redacted middleware metadataNW_FALLBACK_ON_RATE_LIMITNW_CLAUDE_PRIMARY_MODEL_IDNW_CLAUDE_SECONDARY_MODEL_IDNW_FALLBACK_*_PRESET_*NW_QUEUE_*run,review, andqacommands first.Tests Required:
packages/cli/src/__tests__/commands/provider-lifecycle.test.tsshould preserve executor fallback env vars when lifecycle is enabledpackages/cli/src/__tests__/commands/provider-lifecycle.test.tsshould record rate_limit_fallback marker as middleware trace metadatapackages/cli/src/__tests__/commands/provider-lifecycle.test.tsshould still skip QA notifications for queued resultsshouldSendQaNotification('queued')behavior remains falsepackages/cli/src/__tests__/commands/provider-lifecycle.test.tsshould continue when optional trace middleware throwsVerification Plan:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/provider-lifecycle.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/run.test.ts packages/cli/src/__tests__/commands/qa.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "rate-limit fallback"yarn workspace @jonit-dev/night-watch-cli verifyrun,review, andqacommand tests passUser Verification:
rate_limit_fallback=1on successful fallback and still emitsrate_limitedwhen fallback is disabled or exhausted.Checkpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 3 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.Phase 4: Action Gate Middleware — "Server and manual triggers use shared typed policy checks before spawning jobs"
Files (max 5):
packages/core/src/middleware/action-gates.ts— built-in gates for locks, paused jobs, queue bypass policy, job registry validation, and provider policypackages/core/src/__tests__/middleware/action-gates.test.ts— unit tests for gatespackages/server/src/routes/action.routes.ts— invoke shared gates fromspawnActionpackages/server/src/__tests__/server/action-routes.test.ts— API tests for denied/allowed actionspackages/cli/src/commands/shared/env-builder.ts— expose action-gate env additions consistentlyImplementation:
JOB_REGISTRYspawnActionwith shared gate calls while preserving response status and response shape.NW_QUEUE_ENABLED=0for current manual actions unless a gate returns a deliberate env modification.JOB_REGISTRY.Tests Required:
packages/core/src/__tests__/middleware/action-gates.test.tsshould deny executor action when executor lock is running409packages/core/src/__tests__/middleware/action-gates.test.tsshould allow registered job action when no lock is runningpackages/core/src/__tests__/middleware/action-gates.test.tsshould redact errors from denied action gate responsespackages/server/src/__tests__/server/action-routes.test.tsshould return 409 when run action is already runningpackages/server/src/__tests__/server/action-routes.test.tsshould spawn registry-driven job route when action gate allows{ started: true, pid }is unchangedVerification Plan:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/action-gates.test.tsyarn workspace @night-watch/server test -- packages/server/src/__tests__/server/action-routes.test.tsyarn workspace @night-watch/core verifyyarn workspace @night-watch/server verifyUser Verification:
POST /api/actions/runwhile executor lock is active, then again after clearing the lock.Checkpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 4 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.Phase 5: Centralize Fallback Policy Without Changing Fallback Behavior — "Existing rate-limit fallback decisions are represented as typed policy outcomes"
Files (max 5):
packages/core/src/middleware/fallback-policy.ts— typed fallback decision helpers based on current config and provider attempt classificationpackages/core/src/__tests__/middleware/fallback-policy.test.ts— preserve current fallback decision matrixpackages/cli/src/commands/run.ts— use fallback policy to build existing fallback env vars and trace policy decisionspackages/cli/src/__tests__/commands/run.test.ts— verify existing env and marker behavior remains stablepackages/cli/src/__tests__/scripts/core-flow-smoke.test.ts— update/add fallback smoke assertions only if neededImplementation:
resolveFallbackPolicy(config, providerContext)that returns:enabledprimaryPreset/secondaryPresetprimaryModel/secondaryModelreasonIfUnavailablepreservesExistingScriptFallback: truerun.tsto populate the same env vars currently used byscripts/night-watch-cron.sh.fallbackOnRateLimit=falsedisables fallbackfallback_unconfigured=1rate_limit_fallback=1Tests Required:
packages/core/src/__tests__/middleware/fallback-policy.test.tsshould resolve disabled fallback when fallbackOnRateLimit is falsepackages/core/src/__tests__/middleware/fallback-policy.test.tsshould prefer primary fallback preset over model fallbackpackages/core/src/__tests__/middleware/fallback-policy.test.tsshould report missing fallback configuration when no preset or model existsreasonIfUnavailableis setpackages/cli/src/__tests__/commands/run.test.tsshould export the same fallback env vars from policypackages/cli/src/__tests__/scripts/core-flow-smoke.test.tsexecutor should trigger native Claude fallback and include rate_limit_fallback marker when proxy returns 429Verification Plan:
yarn workspace @night-watch/core test -- packages/core/src/__tests__/middleware/fallback-policy.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/run.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "fallback"yarn workspace @night-watch/core verifyyarn workspace @jonit-dev/night-watch-cli verifyUser Verification:
Checkpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 5 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.Phase 6: Complete Job Coverage and Documentation — "All provider-backed job types share lifecycle hooks with clear internal docs"
Files (max 5):
packages/cli/src/commands/audit.ts— wire audit through shared provider lifecycle helperpackages/cli/src/commands/merge.ts— wire merger action/policy tracing where provider or repair actions runpackages/cli/src/__tests__/commands/audit.test.ts— verify audit env/outcome behavior remains stablepackages/cli/src/__tests__/commands/merge.test.ts— verify merger action policy tracing does not alter merge decisionsdocs/architecture/provider-action-middleware.md— internal architecture note with hook contracts and non-goalsImplementation:
scripts/night-watch-audit-cron.sh.ciPolicy=fallback-localbehavior as a merger CI policy, not provider rate-limit fallback.Tests Required:
packages/cli/src/__tests__/commands/audit.test.tsshould preserve audit env vars when lifecycle middleware is enabledpackages/cli/src/__tests__/commands/audit.test.tsshould classify audit rate limits without changing retry behaviorpackages/cli/src/__tests__/commands/merge.test.tsshould preserve fallback-local CI policy behaviorpackages/cli/src/__tests__/commands/merge.test.tsshould record merger policy trace without raw command secretsVerification Plan:
yarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/commands/audit.test.ts packages/cli/src/__tests__/commands/merge.test.tsyarn workspace @jonit-dev/night-watch-cli test -- packages/cli/src/__tests__/scripts/core-flow-smoke.test.ts -t "merger"yarn verifyUser Verification:
yarn verifyand targeted audit/merger tests.Checkpoint: Spawn
prd-work-reviewerwith prompt:Review checkpoint for phase 6 of PRD at docs/prds/provider-action-middleware.md. Continue only after PASS.5. Checkpoint Protocol
After each phase:
Manual checkpoints are not required because this PRD is internal/background architecture work with no UI changes. API proof for action routes is included in Phase 4.
6. Verification Strategy
Unit Tests:
Integration Tests:
run,review,qa,audit, andmergeScript Smoke Tests:
packages/cli/src/__tests__/scripts/core-flow-smoke.test.tsNW_QUEUE_*API Proof:
Final Verification Commands:
Evidence Required:
yarn verifypasses7. Acceptance Criteria
yarn verifypasses.package.json.rate_limited,network,context_exhausted,timeout,policy_denied, anduncategorized.fallback-localCI behavior remains separate from provider rate-limit fallback.