@yemi33/minions 0.1.2447 → 0.1.2449
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +52 -6
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* orchestration (SETUP → DRAFT → EXECUTE) can be re-used verbatim.
|
|
7
7
|
*
|
|
8
8
|
* This module is deliberately PURE in the same sense validateSpec is pure:
|
|
9
|
-
* the only I/O it performs is reading the PRD
|
|
10
|
-
* archive fallback) and, optionally, a guide
|
|
11
|
-
* prd/guides/. No git, no network, no dispatch — every
|
|
12
|
-
* decision is deferred to the SETUP phase. That keeps it
|
|
13
|
-
* unit-testable against fixture PRDs in an isolated MINIONS_TEST_DIR.
|
|
9
|
+
* the only I/O it performs is reading the PRD (from the SQL PRD store, with a
|
|
10
|
+
* legacy prd/ + prd/archive/ filesystem fallback) and, optionally, a guide
|
|
11
|
+
* markdown file under prd/guides/. No git, no network, no dispatch — every
|
|
12
|
+
* live target/worktree decision is deferred to the SETUP phase. That keeps it
|
|
13
|
+
* trivially unit-testable against fixture PRDs in an isolated MINIONS_TEST_DIR.
|
|
14
14
|
*
|
|
15
15
|
* Public API:
|
|
16
16
|
* buildQaSessionSpecFromPrd(prdInput, opts) -> { spec, warnings, provenance }
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
|
-
const shared = require('
|
|
31
|
-
const qaSessions = require('./
|
|
30
|
+
const shared = require('../core/shared');
|
|
31
|
+
const qaSessions = require('./sessions');
|
|
32
32
|
|
|
33
33
|
const { LIMITS, VALID_MODES, validateSpec } = qaSessions;
|
|
34
34
|
|
|
@@ -68,6 +68,19 @@ class QaFromPrdError extends Error {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Classify a caught error as a builder rejection WITHOUT `instanceof`.
|
|
73
|
+
*
|
|
74
|
+
* This module is deliberately re-required per isolated MINIONS_TEST_DIR (see
|
|
75
|
+
* test/_helpers.js ISOLATED_MODULES) because it captures `shared`, so two live
|
|
76
|
+
* copies of the class can exist at once. An `instanceof` check across that
|
|
77
|
+
* boundary silently downgrades a typed 400 into a 500, which is exactly the
|
|
78
|
+
* bug this predicate exists to prevent. Callers in other modules MUST use it.
|
|
79
|
+
*/
|
|
80
|
+
function isQaFromPrdError(err) {
|
|
81
|
+
return !!err && err.name === 'QaFromPrdError' && typeof err.reason === 'string';
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
function _isNonEmptyString(v) {
|
|
72
85
|
return typeof v === 'string' && v.trim().length > 0;
|
|
73
86
|
}
|
|
@@ -95,9 +108,17 @@ function _guidesDir() {
|
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
/**
|
|
98
|
-
* Resolve prdInput to { prd, prdFile }. When given a string, read the
|
|
99
|
-
*
|
|
100
|
-
*
|
|
111
|
+
* Resolve prdInput to { prd, prdFile }. When given a string, read the PRD from
|
|
112
|
+
* the authoritative SQL store (live bucket, then the archive bucket), falling
|
|
113
|
+
* back to legacy on-disk prd/<file> / prd/archive/<file> JSON. When given an
|
|
114
|
+
* object, use it verbatim (prdFile stays null → no guide lookup is possible).
|
|
115
|
+
*
|
|
116
|
+
* The SQL read is FIRST because migration 018 made `engine/state.db` the sole
|
|
117
|
+
* PRD authority — `prd/*.json` files no longer exist on a migrated install, so
|
|
118
|
+
* a filesystem-only lookup answered `prd-not-found` for every real PRD and made
|
|
119
|
+
* the guide-preferring flow priority (which needs the PRD *filename*)
|
|
120
|
+
* unreachable. The filesystem read is kept as a fallback for legacy installs
|
|
121
|
+
* and fixture-seeded test dirs.
|
|
101
122
|
*
|
|
102
123
|
* @throws {QaFromPrdError} prd-invalid-input / prd-not-found / prd-invalid-json
|
|
103
124
|
*/
|
|
@@ -112,16 +133,24 @@ function _resolvePrd(prdInput) {
|
|
|
112
133
|
if (!_isSafePrdFileName(file)) {
|
|
113
134
|
throw new QaFromPrdError(`invalid PRD filename: ${file}`);
|
|
114
135
|
}
|
|
136
|
+
|
|
137
|
+
for (const archived of [false, true]) {
|
|
138
|
+
let stored = null;
|
|
139
|
+
try { stored = require('../planning/prd-store').readPrd(file, { archived }); }
|
|
140
|
+
catch { stored = null; /* no DB / unreadable row → fall through to disk */ }
|
|
141
|
+
if (stored && typeof stored === 'object' && !Array.isArray(stored)) {
|
|
142
|
+
return { prd: stored, prdFile: file };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
const candidates = [
|
|
116
147
|
path.join(_prdDir(), file),
|
|
117
148
|
path.join(_prdDir(), 'archive', file),
|
|
118
149
|
];
|
|
119
150
|
let raw = null;
|
|
120
|
-
let found = null;
|
|
121
151
|
for (const p of candidates) {
|
|
122
152
|
try {
|
|
123
153
|
raw = fs.readFileSync(p, 'utf8');
|
|
124
|
-
found = p;
|
|
125
154
|
break;
|
|
126
155
|
} catch { /* try next */ }
|
|
127
156
|
}
|
|
@@ -421,6 +450,7 @@ function buildQaSessionSpecFromPrd(prdInput, opts = {}) {
|
|
|
421
450
|
module.exports = {
|
|
422
451
|
buildQaSessionSpecFromPrd,
|
|
423
452
|
QaFromPrdError,
|
|
453
|
+
isQaFromPrdError,
|
|
424
454
|
// Constants + internals exposed for tests.
|
|
425
455
|
QA_READY_STATUSES,
|
|
426
456
|
QA_PRD_FILENAME_MAX,
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/qa/prd-session.js — the ONE PRD → QA-Session orchestration path.
|
|
3
|
+
*
|
|
4
|
+
* `POST /api/qa/from-prd` (dashboard.js#handleQaFromPrdCreate) and the
|
|
5
|
+
* post-verify auto-QA hook (engine/qa/auto-prd-qa.js) must create *identical*
|
|
6
|
+
* sessions: same spec builder, same project canonicalization, same
|
|
7
|
+
* createSession + queueSetup ordering, same provenance shape. Before this
|
|
8
|
+
* module the orchestration lived inline in the HTTP handler, so the only way
|
|
9
|
+
* for the engine to launch a PRD-driven session was to fork it — and a fork
|
|
10
|
+
* drifts. Everything that is genuinely HTTP-specific (body validation, header
|
|
11
|
+
* identity, status-code mapping, the non-blocking runner hint) stays in the
|
|
12
|
+
* handler; everything that defines *what a PRD-driven QA session is* lives
|
|
13
|
+
* here.
|
|
14
|
+
*
|
|
15
|
+
* The only injected seam is `resolveTargets`. Project resolution differs by
|
|
16
|
+
* caller on purpose: the dashboard runs each name through its HTTP-input
|
|
17
|
+
* validator so a bad project is a 400 with an `ApiInputError` path, while the
|
|
18
|
+
* engine resolves names that came out of the PRD itself against the live
|
|
19
|
+
* config. Injecting it keeps those two contracts intact without duplicating
|
|
20
|
+
* the orchestration around them.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const shared = require('../core/shared');
|
|
24
|
+
const qaSessions = require('./sessions');
|
|
25
|
+
const qaFromPrd = require('./from-prd');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when the resolver rejects a project. Carries `.reason` so callers can
|
|
29
|
+
* surface the same 400 body the inline implementation produced.
|
|
30
|
+
*/
|
|
31
|
+
class QaPrdSessionError extends Error {
|
|
32
|
+
constructor(reason, extra = {}) {
|
|
33
|
+
super(reason);
|
|
34
|
+
this.name = 'QaPrdSessionError';
|
|
35
|
+
this.reason = reason;
|
|
36
|
+
if (extra && typeof extra === 'object') {
|
|
37
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
38
|
+
if (k !== 'reason' && k !== 'name') this[k] = v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Name-based classifier, for the same reason `from-prd.js#isQaFromPrdError`
|
|
46
|
+
* exists: this module is re-required per isolated test root, so `instanceof`
|
|
47
|
+
* across a module boundary is not a reliable way to recognise the error.
|
|
48
|
+
*/
|
|
49
|
+
function isQaPrdSessionError(err) {
|
|
50
|
+
return !!err && err.name === 'QaPrdSessionError' && typeof err.reason === 'string';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Rewrite the spec's project field(s) to the CANONICAL names the resolver
|
|
55
|
+
* produced, preserving the single-string vs array shape validateSpec expects.
|
|
56
|
+
* Extracted verbatim from dashboard.js#_qaCanonicalizeResolvedProjects.
|
|
57
|
+
*
|
|
58
|
+
* W-msb9kgs402813a97-a — `primaryProject` and the `projectTargets` keys name
|
|
59
|
+
* projects too, so they are canonicalized through the SAME resolver output.
|
|
60
|
+
* Without this, a spec that named `"Web"` would end up with
|
|
61
|
+
* `projects: ["web"]` but `primaryProject: "Web"`, and validateSpec would
|
|
62
|
+
* reject a spec the caller wrote correctly.
|
|
63
|
+
*/
|
|
64
|
+
function canonicalizeResolvedProjects(spec, resolution) {
|
|
65
|
+
const normalized = { ...spec };
|
|
66
|
+
const targets = Array.isArray(resolution && resolution.targets) ? resolution.targets : [];
|
|
67
|
+
// Both resolvers stamp `requested` (the name the caller wrote) next to the
|
|
68
|
+
// canonical `project`, so the mapping survives de-duplication and the
|
|
69
|
+
// primary-first reordering below. The case-insensitive fallback covers
|
|
70
|
+
// callers that hand back bare `{ project, scope }` targets.
|
|
71
|
+
const byRequested = new Map();
|
|
72
|
+
const byLowerCanonical = new Map();
|
|
73
|
+
for (const target of targets) {
|
|
74
|
+
if (!target || !target.project) continue;
|
|
75
|
+
if (typeof target.requested === 'string' && target.requested) {
|
|
76
|
+
byRequested.set(target.requested, target.project);
|
|
77
|
+
}
|
|
78
|
+
byLowerCanonical.set(String(target.project).toLowerCase(), target.project);
|
|
79
|
+
}
|
|
80
|
+
const canonical = (name) => byRequested.get(name)
|
|
81
|
+
|| byLowerCanonical.get(String(name).trim().toLowerCase())
|
|
82
|
+
|| name;
|
|
83
|
+
|
|
84
|
+
if (Array.isArray(normalized.projects) && normalized.projects.length > 0) {
|
|
85
|
+
normalized.projects = targets.map(target => target.project);
|
|
86
|
+
} else if (targets.length === 1 && targets[0].project) {
|
|
87
|
+
normalized.project = targets[0].project;
|
|
88
|
+
}
|
|
89
|
+
if (typeof normalized.primaryProject === 'string' && normalized.primaryProject) {
|
|
90
|
+
normalized.primaryProject = canonical(normalized.primaryProject);
|
|
91
|
+
}
|
|
92
|
+
if (normalized.projectTargets && typeof normalized.projectTargets === 'object'
|
|
93
|
+
&& !Array.isArray(normalized.projectTargets)) {
|
|
94
|
+
const rekeyed = {};
|
|
95
|
+
for (const [name, target] of Object.entries(normalized.projectTargets)) {
|
|
96
|
+
rekeyed[canonical(name)] = target;
|
|
97
|
+
}
|
|
98
|
+
normalized.projectTargets = rekeyed;
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Move the target that hosts `spec.primaryProject` to the front and stamp a
|
|
105
|
+
* `primary` boolean on every entry. Shared by both resolvers so the HTTP and
|
|
106
|
+
* engine paths hand `queueSetup` an identically-ordered list.
|
|
107
|
+
*
|
|
108
|
+
* A `primaryProject` naming a project that was not resolved is left alone —
|
|
109
|
+
* `qaSessions.validateSpec` owns that rejection and produces the actionable
|
|
110
|
+
* error; silently reordering around an unknown name would hide it.
|
|
111
|
+
*/
|
|
112
|
+
function orderTargetsByPrimary(spec, targets) {
|
|
113
|
+
const requestedPrimary = spec && typeof spec.primaryProject === 'string' ? spec.primaryProject : '';
|
|
114
|
+
const ordered = targets.slice();
|
|
115
|
+
if (requestedPrimary) {
|
|
116
|
+
const lower = requestedPrimary.trim().toLowerCase();
|
|
117
|
+
const index = ordered.findIndex(t => t
|
|
118
|
+
&& (t.requested === requestedPrimary || String(t.project || '').toLowerCase() === lower));
|
|
119
|
+
if (index > 0) ordered.unshift(ordered.splice(index, 1)[0]);
|
|
120
|
+
}
|
|
121
|
+
return ordered.map((target, index) => ({ ...target, primary: index === 0 }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Engine-side project resolver — the non-HTTP counterpart to the dashboard's
|
|
126
|
+
* `_qaSessionsResolveTargets`. Walks the same `shared.resolveProjectSource`
|
|
127
|
+
* seam `resolveWorkItemsCreateTarget` uses (including `defaultWhenSingle`, so a
|
|
128
|
+
* single-project install still binds a project when the PRD names none) and
|
|
129
|
+
* returns `{ targets }` or `{ error }`.
|
|
130
|
+
*
|
|
131
|
+
* Duplicates are DROPPED rather than rejected: unlike an operator-authored HTTP
|
|
132
|
+
* body, a PRD's project list is derived from its items, so a repeat is expected
|
|
133
|
+
* shape rather than a caller bug.
|
|
134
|
+
*/
|
|
135
|
+
function resolveTargetsFromConfig(spec, config) {
|
|
136
|
+
const projects = shared.getProjects(config);
|
|
137
|
+
const list = Array.isArray(spec && spec.projects) && spec.projects.length > 0
|
|
138
|
+
? spec.projects.slice()
|
|
139
|
+
: [spec && spec.project ? spec.project : ''];
|
|
140
|
+
const targets = [];
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
for (const name of list) {
|
|
143
|
+
const resolved = shared.resolveProjectSource(name, projects, {
|
|
144
|
+
defaultWhenSingle: true,
|
|
145
|
+
minionsDir: shared.MINIONS_DIR,
|
|
146
|
+
});
|
|
147
|
+
if (resolved.error) return { error: `project "${name || '(central)'}": ${resolved.error}` };
|
|
148
|
+
const projectName = resolved.project ? resolved.project.name : null;
|
|
149
|
+
const key = projectName || '__central__';
|
|
150
|
+
if (seen.has(key)) continue;
|
|
151
|
+
seen.add(key);
|
|
152
|
+
targets.push({ scope: resolved.scope, project: projectName, requested: name });
|
|
153
|
+
}
|
|
154
|
+
return { targets: orderTargetsByPrimary(spec, targets) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build a QA Session from a PRD and queue its SETUP phase.
|
|
159
|
+
*
|
|
160
|
+
* @param {string|object} prdInput PRD filename (under prd/) or parsed PRD object.
|
|
161
|
+
* @param {object} opts
|
|
162
|
+
* @param {string} [opts.mode] 'confirm' (default) | 'auto'
|
|
163
|
+
* @param {string} [opts.runner] explicit kebab-case runner
|
|
164
|
+
* @param {string} [opts.createdBy] audit identity stamped on the session
|
|
165
|
+
* @param {object} [opts.provenanceExtra] merged into the builder's provenance
|
|
166
|
+
* @param {Function} opts.resolveTargets (spec) => { targets } | { error }
|
|
167
|
+
* @returns {{ session, sessionId, setupWorkItemId, resolution, spec, warnings, provenance, flowSource }}
|
|
168
|
+
* @throws {qaFromPrd.QaFromPrdError} unreadable/malformed/non-QA-ready PRD, bad runner
|
|
169
|
+
* @throws {QaPrdSessionError} project resolution rejected the spec
|
|
170
|
+
* @throws {Error} createSession/queueSetup rejection (`.sessionId`
|
|
171
|
+
* is stamped when the session already exists)
|
|
172
|
+
*/
|
|
173
|
+
function createQaSessionFromPrd(prdInput, opts = {}) {
|
|
174
|
+
const options = opts && typeof opts === 'object' ? opts : {};
|
|
175
|
+
if (typeof options.resolveTargets !== 'function') {
|
|
176
|
+
throw new TypeError('qa/prd-session: createQaSessionFromPrd requires opts.resolveTargets');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const built = qaFromPrd.buildQaSessionSpecFromPrd(prdInput, {
|
|
180
|
+
mode: options.mode,
|
|
181
|
+
runner: options.runner,
|
|
182
|
+
});
|
|
183
|
+
const { spec, warnings, provenance } = built;
|
|
184
|
+
|
|
185
|
+
let createSpec = { ...spec };
|
|
186
|
+
if (typeof options.createdBy === 'string' && options.createdBy) {
|
|
187
|
+
createSpec.createdBy = options.createdBy;
|
|
188
|
+
}
|
|
189
|
+
// Provenance rides alongside the spec → persisted on the session record by
|
|
190
|
+
// createSession (P-c5a2f8b7). Not part of the validateSpec contract.
|
|
191
|
+
createSpec.provenance = options.provenanceExtra && typeof options.provenanceExtra === 'object'
|
|
192
|
+
? { ...provenance, ...options.provenanceExtra }
|
|
193
|
+
: provenance;
|
|
194
|
+
|
|
195
|
+
// Resolve EACH project BEFORE persisting — one bad project rejects the whole
|
|
196
|
+
// submit rather than stranding a half-created session.
|
|
197
|
+
const resolution = options.resolveTargets(createSpec);
|
|
198
|
+
if (resolution && resolution.error) {
|
|
199
|
+
throw new QaPrdSessionError(resolution.error, { kind: 'project' });
|
|
200
|
+
}
|
|
201
|
+
if (!resolution || !Array.isArray(resolution.targets) || resolution.targets.length === 0) {
|
|
202
|
+
throw new QaPrdSessionError('QA project resolution produced no targets', { kind: 'project' });
|
|
203
|
+
}
|
|
204
|
+
createSpec = canonicalizeResolvedProjects(createSpec, resolution);
|
|
205
|
+
|
|
206
|
+
const session = qaSessions.createSession(createSpec);
|
|
207
|
+
|
|
208
|
+
let setupWorkItemId = null;
|
|
209
|
+
try {
|
|
210
|
+
setupWorkItemId = qaSessions.queueSetup(session.id, {
|
|
211
|
+
resolvedTargets: resolution.targets.map(t => ({ project: t.project, scope: t.scope })),
|
|
212
|
+
});
|
|
213
|
+
} catch (e) {
|
|
214
|
+
// The session exists but has no SETUP WI. Surface the id so the caller can
|
|
215
|
+
// report/inspect it instead of leaking an orphan.
|
|
216
|
+
e.sessionId = session.id;
|
|
217
|
+
e.kind = 'queue';
|
|
218
|
+
throw e;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
session,
|
|
223
|
+
sessionId: session.id,
|
|
224
|
+
setupWorkItemId,
|
|
225
|
+
resolution,
|
|
226
|
+
spec: createSpec,
|
|
227
|
+
warnings: Array.isArray(warnings) ? warnings : [],
|
|
228
|
+
provenance: createSpec.provenance,
|
|
229
|
+
flowSource: (createSpec.provenance && createSpec.provenance.flowSource) || null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
createQaSessionFromPrd,
|
|
235
|
+
canonicalizeResolvedProjects,
|
|
236
|
+
orderTargetsByPrimary,
|
|
237
|
+
resolveTargetsFromConfig,
|
|
238
|
+
QaPrdSessionError,
|
|
239
|
+
isQaPrdSessionError,
|
|
240
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const apiValidation = require('
|
|
4
|
-
const keepProcesses = require('
|
|
5
|
-
const qaFromPrd = require('./
|
|
6
|
-
const qaRunbooks = require('./
|
|
7
|
-
const qaRuns = require('./
|
|
8
|
-
const qaSessions = require('./
|
|
9
|
-
const shared = require('
|
|
3
|
+
const apiValidation = require('../api/api-validation');
|
|
4
|
+
const keepProcesses = require('../processes/keep-process-sweep');
|
|
5
|
+
const qaFromPrd = require('./from-prd');
|
|
6
|
+
const qaRunbooks = require('./runbooks');
|
|
7
|
+
const qaRuns = require('./runs');
|
|
8
|
+
const qaSessions = require('./sessions');
|
|
9
|
+
const shared = require('../core/shared');
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
12
|
ApiInputError,
|
|
@@ -112,7 +112,7 @@ function validateObjectFields(value, allowedFields, pathName = 'body') {
|
|
|
112
112
|
|
|
113
113
|
function validateKnownRunner(runner, pathName) {
|
|
114
114
|
if (runner === undefined || runner === null || runner === '') return runner;
|
|
115
|
-
const qaRunners = require('
|
|
115
|
+
const qaRunners = require('../qa-runners');
|
|
116
116
|
if (!qaRunners.getRunner(runner)) {
|
|
117
117
|
throw new ApiInputError(`Unknown QA runner: ${runner}`, {
|
|
118
118
|
code: 'unknown-qa-runner',
|
|
@@ -163,6 +163,11 @@ function validateQaSessionRequest(body, options = {}) {
|
|
|
163
163
|
'runner',
|
|
164
164
|
'project',
|
|
165
165
|
'projects',
|
|
166
|
+
// W-msb9kgs402813a97-a — explicit multi-project contract. The allowlist
|
|
167
|
+
// rejects unknown body fields, so omitting these would 400 a spec that
|
|
168
|
+
// qaSessions.validateSpec accepts.
|
|
169
|
+
'primaryProject',
|
|
170
|
+
'projectTargets',
|
|
166
171
|
'createdBy',
|
|
167
172
|
]);
|
|
168
173
|
const result = qaSessions.validateSpec(body);
|
|
@@ -414,7 +419,7 @@ function validateRunbookId(value, pathName = 'path.id') {
|
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
function validateManagedSpecName(value, pathName) {
|
|
417
|
-
const result = require('
|
|
422
|
+
const result = require('../processes/managed-spawn').validateManagedSpecName(value);
|
|
418
423
|
if (!result.ok) {
|
|
419
424
|
throw new ApiInputError('Invalid managed process name', {
|
|
420
425
|
code: 'invalid-managed-process-name',
|