@yemi33/minions 0.1.2448 → 0.1.2450
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 +58 -4
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +179 -20
- 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 +246 -34
- 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 +13 -2
- package/dashboard.js +974 -430
- 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} +283 -69
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/queries.js — Shared read-only state queries for Minions engine + dashboard.
|
|
2
|
+
* engine/core/queries.js — Shared read-only state queries for Minions engine + dashboard.
|
|
3
3
|
* Single source of truth for all data reading/aggregation.
|
|
4
4
|
* Both engine.js and dashboard.js require() this module.
|
|
5
5
|
*/
|
|
@@ -9,7 +9,7 @@ const fsp = require('fs').promises;
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
const shared = require('./shared');
|
|
12
|
-
const steering = require('
|
|
12
|
+
const steering = require('../agents/steering');
|
|
13
13
|
const { normalizeExecutionModel, resolveWorkItemModel } = require('./execution-model');
|
|
14
14
|
|
|
15
15
|
const { safeRead, safeReadDir, safeJson, safeJsonObj, safeWrite, getProjects, mutateJsonFileLocked,
|
|
@@ -32,7 +32,7 @@ const _prEnrichmentInFlight = new Set();
|
|
|
32
32
|
let _lifecycleModule = null;
|
|
33
33
|
function _lifecycle() {
|
|
34
34
|
if (!_lifecycleModule) {
|
|
35
|
-
try { _lifecycleModule = require('
|
|
35
|
+
try { _lifecycleModule = require('../orchestration/lifecycle'); }
|
|
36
36
|
catch { _lifecycleModule = null; }
|
|
37
37
|
}
|
|
38
38
|
return _lifecycleModule;
|
|
@@ -248,7 +248,7 @@ function getDispatch() {
|
|
|
248
248
|
const now = Date.now();
|
|
249
249
|
if (_dispatchCache && (now - _dispatchCacheAt) < 2000) return _dispatchCache;
|
|
250
250
|
// SQL is the canonical (and only) dispatch store after Phase 9.
|
|
251
|
-
const store = require('
|
|
251
|
+
const store = require('../persistence/dispatch-store');
|
|
252
252
|
_dispatchCache = store.readDispatchSectioned();
|
|
253
253
|
_dispatchCacheAt = now;
|
|
254
254
|
return _dispatchCache;
|
|
@@ -388,7 +388,7 @@ function getDispatchQueue() {
|
|
|
388
388
|
const d = getDispatch();
|
|
389
389
|
const allCompleted = d.completed || [];
|
|
390
390
|
// Lifetime total from metrics (dispatch.completed is capped at 100)
|
|
391
|
-
const metrics = require('
|
|
391
|
+
const metrics = require('../observability/metrics-store').readMetrics() || {};
|
|
392
392
|
// Periodically prune cache entries for dispatches that have rotated out of the queue.
|
|
393
393
|
if (_completionReportCache.size >= 200) {
|
|
394
394
|
const activeIds = new Set();
|
|
@@ -420,18 +420,18 @@ function getNotesWithMeta() {
|
|
|
420
420
|
|
|
421
421
|
function getEngineLog() {
|
|
422
422
|
// SQL is the canonical (and only) log store after Phase 9.
|
|
423
|
-
const store = require('
|
|
423
|
+
const store = require('../observability/logs-store');
|
|
424
424
|
return store.readRecentLogsChronological(50) || [];
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
function getMetrics() {
|
|
428
428
|
// SQL is the canonical (and only) metrics store after Phase 9.
|
|
429
|
-
const store = require('
|
|
429
|
+
const store = require('../observability/metrics-store');
|
|
430
430
|
const metrics = store.readMetrics() || {};
|
|
431
431
|
const config = getConfig();
|
|
432
432
|
const engineConfig = config.engine || {};
|
|
433
433
|
const configuredAgents = config.agents || {};
|
|
434
|
-
const { resolveRuntime } = require('
|
|
434
|
+
const { resolveRuntime } = require('../runtimes');
|
|
435
435
|
|
|
436
436
|
for (const [agentId, m] of Object.entries(metrics)) {
|
|
437
437
|
if (agentId.startsWith('_')) continue;
|
|
@@ -448,7 +448,7 @@ function getMetrics() {
|
|
|
448
448
|
const prRejectedByAgent = {};
|
|
449
449
|
for (const pr of allPrs) {
|
|
450
450
|
const agent = (pr.agent || '').toLowerCase();
|
|
451
|
-
if (!agent || agent.startsWith('temp-')) continue;
|
|
451
|
+
if (!agent || agent === shared.PR_AGENT_HUMAN || agent.startsWith('temp-')) continue;
|
|
452
452
|
prCountByAgent[agent] = (prCountByAgent[agent] || 0) + 1;
|
|
453
453
|
if (pr.reviewStatus === shared.REVIEW_STATUS.APPROVED || pr.status === shared.PR_STATUS.MERGED) prApprovedByAgent[agent] = (prApprovedByAgent[agent] || 0) + 1;
|
|
454
454
|
// BUG-H13 / P-h13-rejected: this used to compare to the literal 'rejected',
|
|
@@ -519,7 +519,7 @@ function getInboxFiles() {
|
|
|
519
519
|
// P-34fa5d79 — Note ↔ Work Item ↔ KB Entry linkage.
|
|
520
520
|
//
|
|
521
521
|
// Parse the YAML frontmatter from a note body and return the parsed fields.
|
|
522
|
-
// Same minimal contract as engine/kb-sweep.js#_parseFrontmatter — a single
|
|
522
|
+
// Same minimal contract as engine/memory/kb-sweep.js#_parseFrontmatter — a single
|
|
523
523
|
// shared regex would have been nicer but importing kb-sweep from queries
|
|
524
524
|
// would form a circular dep with lifecycle (queries → kb-sweep → … →
|
|
525
525
|
// queries), so we keep a local copy of the four-line parser.
|
|
@@ -737,7 +737,7 @@ function resolveRuntimeModelDisplay({ runtime, configuredModel, agentId, dispatc
|
|
|
737
737
|
* model). An active dispatch is the live run and always beats a completed one;
|
|
738
738
|
* among entries of the same kind the most recently started wins.
|
|
739
739
|
*
|
|
740
|
-
* Note `dispatch.completed` is capped (engine/dispatch.js), so an old work item
|
|
740
|
+
* Note `dispatch.completed` is capped (engine/orchestration/dispatch.js), so an old work item
|
|
741
741
|
* can legitimately have no entry here — callers must handle that.
|
|
742
742
|
*/
|
|
743
743
|
function buildWorkItemDispatchIndex(dispatch) {
|
|
@@ -773,7 +773,7 @@ function resolveConfiguredAgentModel(config, agentId, cache) {
|
|
|
773
773
|
const configured = shared.resolveAgentModel(agentConfig, engine) || null;
|
|
774
774
|
let model = configured;
|
|
775
775
|
try {
|
|
776
|
-
const runtime = require('
|
|
776
|
+
const runtime = require('../runtimes').resolveRuntime(runtimeName);
|
|
777
777
|
if (typeof runtime?.resolveModel === 'function') model = runtime.resolveModel(configured) || configured;
|
|
778
778
|
} catch { /* registry unavailable — fall back to the raw configured value */ }
|
|
779
779
|
const resolved = { runtimeName, model };
|
|
@@ -1017,7 +1017,7 @@ function getAgentDetail(id) {
|
|
|
1017
1017
|
|
|
1018
1018
|
function getPrs(project) {
|
|
1019
1019
|
if (project) {
|
|
1020
|
-
const store = require('
|
|
1020
|
+
const store = require('../persistence/pull-requests-store');
|
|
1021
1021
|
const prs = store.readPullRequestsForScope(project.name) || [];
|
|
1022
1022
|
shared.normalizePrRecords(prs, project);
|
|
1023
1023
|
return prs;
|
|
@@ -1137,7 +1137,7 @@ function _pickPrDedupeWinner(group, projects) {
|
|
|
1137
1137
|
// W-mr3mvgfe — terminal PR statuses (done being polled/reviewed) get pushed
|
|
1138
1138
|
// to the bottom of the PR tab's default sort. Includes `CLOSED` (a PR closed
|
|
1139
1139
|
// without merging, distinct from `ABANDONED` but equally inactive — see
|
|
1140
|
-
// engine/github.js `newStatus = PR_STATUS.CLOSED` on `prData.state === 'closed'`
|
|
1140
|
+
// engine/providers/github.js `newStatus = PR_STATUS.CLOSED` on `prData.state === 'closed'`
|
|
1141
1141
|
// without `merged`) alongside `MERGED`/`ABANDONED`.
|
|
1142
1142
|
function _isTerminalPrStatus(status) {
|
|
1143
1143
|
return status === PR_STATUS.MERGED || status === PR_STATUS.ABANDONED || status === PR_STATUS.CLOSED;
|
|
@@ -1152,7 +1152,7 @@ function getPullRequests(config) {
|
|
|
1152
1152
|
const allPrs = [];
|
|
1153
1153
|
|
|
1154
1154
|
// SQL is the canonical (and only) PR store after Phase 9.
|
|
1155
|
-
const store = require('
|
|
1155
|
+
const store = require('../persistence/pull-requests-store');
|
|
1156
1156
|
const sqlPrs = store.readAllPullRequests() || [];
|
|
1157
1157
|
|
|
1158
1158
|
// W-mqba5ulq000nd255 — Read-side de-dupe across project scopes. When the
|
|
@@ -1361,6 +1361,92 @@ let _getSkillsResultCacheTs = 0;
|
|
|
1361
1361
|
let _getSkillsResultCacheKey = null;
|
|
1362
1362
|
const SKILLS_CACHE_TTL = 30000; // 30s — skill files change rarely (agent extraction, manual authoring)
|
|
1363
1363
|
|
|
1364
|
+
// ── Nested project-embedded skill discovery (W-ms3a68j2) ────────────────────
|
|
1365
|
+
// A configured project can be a massive monorepo (e.g. `src` = C:/office/src),
|
|
1366
|
+
// and project-embedded skills can live in deeply nested `.claude/skills` /
|
|
1367
|
+
// `.github/skills` / `.agents/skills` dirs far below the repo root (observed:
|
|
1368
|
+
// `loop/app/android/android/.claude/skills/*`). The repo-root-only project skill
|
|
1369
|
+
// roots exposed by the runtime adapters miss these. `collectSkillFiles` does a
|
|
1370
|
+
// single bounded walk per project to find every nested skill root. The defensive
|
|
1371
|
+
// caps mirror engine/agents/harness-context.js's "Defensive caps" pattern so the walk
|
|
1372
|
+
// can never balloon or wander into node_modules/.git/build output.
|
|
1373
|
+
const SKILL_WALK_MAX_DEPTH = 12; // directory levels below the project root before we stop descending
|
|
1374
|
+
const SKILL_WALK_MAX_ENTRIES = 40000; // directory dirents inspected across one project walk (files are free)
|
|
1375
|
+
|
|
1376
|
+
// Test seam: allow lowering the directory-dirent budget so the entry cap can be
|
|
1377
|
+
// exercised without materializing 40k directories. Falls back to the real cap.
|
|
1378
|
+
function _skillWalkMaxEntries() {
|
|
1379
|
+
const override = Number(process.env.MINIONS_SKILL_WALK_MAX_ENTRIES);
|
|
1380
|
+
return Number.isFinite(override) && override > 0 ? override : SKILL_WALK_MAX_ENTRIES;
|
|
1381
|
+
}
|
|
1382
|
+
const SKILL_WALK_PRUNE_DIRS = new Set([
|
|
1383
|
+
'.git', 'node_modules', 'bin', 'obj', 'build', 'dist', 'out', 'output',
|
|
1384
|
+
'target', '.gradle', '.venv', 'venv', 'vendor', '.next', '.nuxt', 'coverage',
|
|
1385
|
+
'.idea', '.vs', '.cache', '__pycache__', '.terraform', 'Pods', 'DerivedData',
|
|
1386
|
+
]);
|
|
1387
|
+
|
|
1388
|
+
// Sort skill dirs shallowest-first so repo-root skills keep priority over deeper
|
|
1389
|
+
// ones during the per-project name dedup.
|
|
1390
|
+
function _sortSkillDirsShallowFirst(dirs) {
|
|
1391
|
+
return dirs.sort((a, b) => {
|
|
1392
|
+
const da = a.split(/[\\/]/).length;
|
|
1393
|
+
const db = b.split(/[\\/]/).length;
|
|
1394
|
+
return da - db || a.localeCompare(b);
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Bounded recursive walk of a project checkout that returns every nested
|
|
1399
|
+
// skill-root directory (absolute path) of the form `**/<marker>/skills`, where
|
|
1400
|
+
// `<marker>` is one of `markerNames` (e.g. `.claude`, `.github`, `.agents`).
|
|
1401
|
+
// Marker names are derived from the runtime adapters' own project skill roots so
|
|
1402
|
+
// discovery stays runtime-agnostic (no hard-coded runtime name checks). The walk
|
|
1403
|
+
// is bounded by depth, total dirents, and a heavy-dir prune set. Marker dirs are
|
|
1404
|
+
// never descended into, so we never surface skills nested inside build output.
|
|
1405
|
+
function _findNestedProjectSkillDirs(projectRoot, markerNames) {
|
|
1406
|
+
const found = [];
|
|
1407
|
+
if (!projectRoot || !markerNames || markerNames.size === 0) return found;
|
|
1408
|
+
const seenDirs = new Set();
|
|
1409
|
+
const maxEntries = _skillWalkMaxEntries();
|
|
1410
|
+
let dirsScanned = 0;
|
|
1411
|
+
// Breadth-first drain (FIFO via an index cursor) so the directory budget is
|
|
1412
|
+
// spent shallowest-first — matching _sortSkillDirsShallowFirst's declared
|
|
1413
|
+
// priority. A depth-first walk would let a single fat subtree exhaust the
|
|
1414
|
+
// budget before sibling subtrees are ever visited, so nested discovery would
|
|
1415
|
+
// silently no-op depending on directory-name ordering (e.g. a monorepo whose
|
|
1416
|
+
// skills sort after a 40k-file tree). Only *directory* dirents count toward
|
|
1417
|
+
// SKILL_WALK_MAX_ENTRIES — a file can never be a skill root, so files are free
|
|
1418
|
+
// and don't starve the walk.
|
|
1419
|
+
const queue = [{ dir: projectRoot, depth: 0 }];
|
|
1420
|
+
for (let head = 0; head < queue.length; head++) {
|
|
1421
|
+
const { dir, depth } = queue[head];
|
|
1422
|
+
let entries;
|
|
1423
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
1424
|
+
for (const dirent of entries) {
|
|
1425
|
+
let isDir = false;
|
|
1426
|
+
try { isDir = dirent.isDirectory(); } catch { isDir = false; }
|
|
1427
|
+
if (!isDir) continue;
|
|
1428
|
+
if (dirsScanned >= maxEntries) return _sortSkillDirsShallowFirst(found);
|
|
1429
|
+
dirsScanned++;
|
|
1430
|
+
const name = dirent.name;
|
|
1431
|
+
const childPath = path.join(dir, name);
|
|
1432
|
+
if (markerNames.has(name)) {
|
|
1433
|
+
// `<marker>/skills` — record if present; never descend into the marker dir.
|
|
1434
|
+
const skillsDir = path.join(childPath, 'skills');
|
|
1435
|
+
let st = null;
|
|
1436
|
+
try { st = fs.statSync(skillsDir); } catch { st = null; }
|
|
1437
|
+
if (st && st.isDirectory()) {
|
|
1438
|
+
const abs = path.resolve(skillsDir);
|
|
1439
|
+
if (!seenDirs.has(abs)) { seenDirs.add(abs); found.push(abs); }
|
|
1440
|
+
}
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (SKILL_WALK_PRUNE_DIRS.has(name)) continue;
|
|
1444
|
+
if (depth + 1 <= SKILL_WALK_MAX_DEPTH) queue.push({ dir: childPath, depth: depth + 1 });
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return _sortSkillDirsShallowFirst(found);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1364
1450
|
function invalidateSkillsCache() {
|
|
1365
1451
|
_skillsCache = null;
|
|
1366
1452
|
_skillsCacheTs = 0;
|
|
@@ -1422,7 +1508,7 @@ function _collectHarnessesScope(scopeFilter, runtimeOpts) {
|
|
|
1422
1508
|
const mcpsByPath = new Map();
|
|
1423
1509
|
let runtimeNames = [];
|
|
1424
1510
|
try {
|
|
1425
|
-
const { listRuntimes, resolveRuntime } = require('
|
|
1511
|
+
const { listRuntimes, resolveRuntime } = require('../runtimes');
|
|
1426
1512
|
runtimeNames = listRuntimes();
|
|
1427
1513
|
for (const runtimeName of runtimeNames) {
|
|
1428
1514
|
let runtime;
|
|
@@ -1511,7 +1597,7 @@ function collectSkillFiles(config) {
|
|
|
1511
1597
|
return seenByScope.get(key);
|
|
1512
1598
|
}
|
|
1513
1599
|
try {
|
|
1514
|
-
const { listRuntimes, resolveRuntime } = require('
|
|
1600
|
+
const { listRuntimes, resolveRuntime } = require('../runtimes');
|
|
1515
1601
|
for (const runtimeName of listRuntimes()) {
|
|
1516
1602
|
const runtime = resolveRuntime(runtimeName);
|
|
1517
1603
|
if (typeof runtime.getSkillRoots !== 'function') continue;
|
|
@@ -1525,15 +1611,42 @@ function collectSkillFiles(config) {
|
|
|
1525
1611
|
|
|
1526
1612
|
// Project-scope skill roots: aggregate across runtimes via the shared
|
|
1527
1613
|
// getProjectHarnesses helper so dedup-by-absolute-path happens once
|
|
1528
|
-
// (e.g. `<repo>/.agents/skills` is exposed by both Codex and Copilot).
|
|
1614
|
+
// (e.g. `<repo>/.agents/skills` is exposed by both Codex and Copilot). In
|
|
1615
|
+
// addition to the repo-root roots the adapters expose, walk each checkout for
|
|
1616
|
+
// nested project-embedded skill dirs (`**/.claude/skills`, `**/.github/skills`,
|
|
1617
|
+
// `**/.agents/skills`) so deeply-nested skills (e.g.
|
|
1618
|
+
// `loop/app/android/android/.claude/skills/*` in a monorepo) are discovered too.
|
|
1529
1619
|
for (const project of projects) {
|
|
1530
1620
|
if (!project.localPath) continue;
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1621
|
+
const projectSkillRoots = getProjectHarnesses(project).skills.filter(r => r.scope === 'project');
|
|
1622
|
+
if (projectSkillRoots.length === 0) continue;
|
|
1623
|
+
const projectName = projectSkillRoots[0].projectName;
|
|
1624
|
+
const dedup = seenFor('project', projectName);
|
|
1625
|
+
|
|
1626
|
+
// Marker dir names (`.claude` / `.github` / `.agents`) come from the adapter
|
|
1627
|
+
// roots themselves, keeping nested discovery runtime-agnostic. Track the
|
|
1628
|
+
// repo-root skill dirs so the nested walk doesn't re-enumerate them.
|
|
1629
|
+
const markerNames = new Set();
|
|
1630
|
+
const rootLevelDirs = new Set();
|
|
1631
|
+
for (const root of projectSkillRoots) {
|
|
1632
|
+
rootLevelDirs.add(path.resolve(root.dir));
|
|
1633
|
+
const rel = path.relative(project.localPath, root.dir);
|
|
1634
|
+
const firstSeg = rel.split(/[\\/]/)[0];
|
|
1635
|
+
if (firstSeg && !firstSeg.startsWith('..')) markerNames.add(firstSeg);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// 1. Repo-root skill roots exposed by the adapters (`<repo>/.claude/skills`).
|
|
1639
|
+
for (const root of projectSkillRoots) {
|
|
1640
|
+
_collectNativeSkillsDir(root.dir, root.scope, dedup, skillFiles, {
|
|
1534
1641
|
projectName: root.projectName,
|
|
1535
1642
|
});
|
|
1536
1643
|
}
|
|
1644
|
+
|
|
1645
|
+
// 2. Nested project-embedded skill roots anywhere under the checkout.
|
|
1646
|
+
for (const nestedDir of _findNestedProjectSkillDirs(project.localPath, markerNames)) {
|
|
1647
|
+
if (rootLevelDirs.has(path.resolve(nestedDir))) continue; // already enumerated above
|
|
1648
|
+
_collectNativeSkillsDir(nestedDir, 'project', dedup, skillFiles, { projectName });
|
|
1649
|
+
}
|
|
1537
1650
|
}
|
|
1538
1651
|
|
|
1539
1652
|
// 1b. Installed plugin skills: ~/.claude/plugins/installed_plugins.json
|
|
@@ -1717,7 +1830,7 @@ function collectCommandFiles(config) {
|
|
|
1717
1830
|
// `~/.claude/commands`; Copilot exposes `~/.copilot/commands` and Codex
|
|
1718
1831
|
// currently returns nothing — adapters fall in/out as their CLIs evolve).
|
|
1719
1832
|
try {
|
|
1720
|
-
const { listRuntimes, resolveRuntime } = require('
|
|
1833
|
+
const { listRuntimes, resolveRuntime } = require('../runtimes');
|
|
1721
1834
|
for (const runtimeName of listRuntimes()) {
|
|
1722
1835
|
const runtime = resolveRuntime(runtimeName);
|
|
1723
1836
|
if (typeof runtime.getCommandRoots !== 'function') continue;
|
|
@@ -1993,7 +2106,83 @@ function invalidateWorkItemsCache() {
|
|
|
1993
2106
|
_workItemsLeanCacheAt = 0;
|
|
1994
2107
|
}
|
|
1995
2108
|
|
|
1996
|
-
//
|
|
2109
|
+
// attachWorkItemPrLinks(items, config) — backfill the _pr / _prUrl fields the
|
|
2110
|
+
// /api/work-items LIST rows render (dashboard/js/render-work-items.js `wiRow`).
|
|
2111
|
+
// This is the ONLY cross-reference the list rows need; the far more expensive
|
|
2112
|
+
// _artifacts/_notes/_model enrichment stays detail-only (GET /api/work-items/<id>).
|
|
2113
|
+
//
|
|
2114
|
+
// Root-cause perf note (W-mscir7fv000u6321): the list endpoint used to run the
|
|
2115
|
+
// FULL enrich over the entire work-item history on every 4 s poll, whose
|
|
2116
|
+
// _artifacts/_notes step is an O(items × files) synchronous KB/inbox/archive/
|
|
2117
|
+
// agent-dir scan that hard-froze the single dashboard event loop (~26-30 s
|
|
2118
|
+
// requests at ~2,281 completed items), starving /api/health and Command Center
|
|
2119
|
+
// SSE. Extracting the cheap PR link-up lets the endpoint paginate FIRST and then
|
|
2120
|
+
// attach links to just the page slice. This helper is deliberately map-based —
|
|
2121
|
+
// O(items + prs), never O(items × prs) — and performs NO filesystem access, so
|
|
2122
|
+
// it is safe to call on the full list or a single page. Mutates and returns
|
|
2123
|
+
// `items` (same semantics the enrich block relied on).
|
|
2124
|
+
function attachWorkItemPrLinks(items, config) {
|
|
2125
|
+
if (!Array.isArray(items) || items.length === 0) return items || [];
|
|
2126
|
+
config = config || getConfig();
|
|
2127
|
+
const projects = getProjects(config);
|
|
2128
|
+
const allPrs = getPullRequests(config);
|
|
2129
|
+
// Prebuilt lookups so each item is O(1) instead of scanning allPrs.
|
|
2130
|
+
const prById = new Map();
|
|
2131
|
+
const prsByDisplayId = new Map();
|
|
2132
|
+
const prByProjectPrdItem = new Map();
|
|
2133
|
+
const prByProjectBranch = new Map();
|
|
2134
|
+
for (const p of allPrs) {
|
|
2135
|
+
if (p.id != null && !prById.has(p.id)) prById.set(p.id, p);
|
|
2136
|
+
const disp = shared.getPrDisplayId(p);
|
|
2137
|
+
if (disp != null) {
|
|
2138
|
+
const bucket = prsByDisplayId.get(disp);
|
|
2139
|
+
if (bucket) bucket.push(p); else prsByDisplayId.set(disp, [p]);
|
|
2140
|
+
}
|
|
2141
|
+
if (p._project) {
|
|
2142
|
+
for (const wid of (p.prdItems || [])) {
|
|
2143
|
+
const key = p._project + '\0' + wid;
|
|
2144
|
+
if (!prByProjectPrdItem.has(key)) prByProjectPrdItem.set(key, p);
|
|
2145
|
+
}
|
|
2146
|
+
if (p.branch) {
|
|
2147
|
+
const bkey = p._project + '\0' + p.branch;
|
|
2148
|
+
if (!prByProjectBranch.has(bkey)) prByProjectBranch.set(bkey, p);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
for (const item of items) {
|
|
2153
|
+
if (item._pr && !item._prUrl) {
|
|
2154
|
+
const project = shared.resolveProjectSource(item.project || item._source, projects, { allowCentral: false }).project || null;
|
|
2155
|
+
const canonicalPrId = shared.getCanonicalPrId(project, item._pr);
|
|
2156
|
+
const displayPrId = shared.getPrDisplayId(item._pr);
|
|
2157
|
+
const exactPr = prById.get(canonicalPrId);
|
|
2158
|
+
const displayMatches = exactPr ? [] : (prsByDisplayId.get(displayPrId) || []);
|
|
2159
|
+
const pr = exactPr || (displayMatches.length === 1 ? displayMatches[0] : null);
|
|
2160
|
+
if (pr) {
|
|
2161
|
+
item._pr = pr.id;
|
|
2162
|
+
item._prUrl = pr.url;
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
if (!item._pr) {
|
|
2166
|
+
// Derive from PR.prdItems (single source of truth), then fall back to
|
|
2167
|
+
// branch identity. The branch fallback heals decomposed-child WIs whose
|
|
2168
|
+
// PR-opening agent only stamped the parent id into prdItems — branch
|
|
2169
|
+
// names (`work/<wi-id>`) are unique per WI within a project, so a
|
|
2170
|
+
// branch hit is as authoritative as a prdItems hit. Scoped to the same
|
|
2171
|
+
// project so cross-repo branch collisions don't false-link.
|
|
2172
|
+
// (W-mpn0b76200044eed)
|
|
2173
|
+
const itemProject = item.project || item._source;
|
|
2174
|
+
const linkedPr = prByProjectPrdItem.get(itemProject + '\0' + item.id)
|
|
2175
|
+
|| (item.branch ? prByProjectBranch.get(itemProject + '\0' + item.branch) : null);
|
|
2176
|
+
if (linkedPr) {
|
|
2177
|
+
item._pr = linkedPr.id;
|
|
2178
|
+
item._prUrl = linkedPr.url;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
return items;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// getWorkItems(config, { enrich, onlyId })
|
|
1997
2186
|
// enrich (default true): also cross-reference PRs and populate the
|
|
1998
2187
|
// detail-modal _artifacts/_notes fields. The /api/work-items list payload
|
|
1999
2188
|
// needs these (PR badges + note chips).
|
|
@@ -2003,11 +2192,26 @@ function invalidateWorkItemsCache() {
|
|
|
2003
2192
|
// never read _artifacts/_notes (getAgentStatus fallback, count-only callers)
|
|
2004
2193
|
// so the agent roster / CC preamble don't pay the enrichment cost on every
|
|
2005
2194
|
// cold rebuild.
|
|
2195
|
+
// onlyId (W-mscmqk6b03ha5673, enrich-only) — narrow the per-item detail
|
|
2196
|
+
// enrichment to the row(s) carrying this work-item id. The single-record
|
|
2197
|
+
// GET /api/work-items/<id> handler needs ONE fully-enriched row, but paid
|
|
2198
|
+
// the whole-history enrichment on every detail-modal open (at ~2.3k items
|
|
2199
|
+
// that's the same multi-second synchronous block the polled list endpoint
|
|
2200
|
+
// was fixed for, just triggered by a click). The full list is still
|
|
2201
|
+
// returned and the cheap pre-enrich dispatch cross-reference still runs
|
|
2202
|
+
// over everything, so _pendingReason/_skipReason/_blockedBy resolve
|
|
2203
|
+
// identically and the caller can still resolve the complete id-matched set
|
|
2204
|
+
// for its ambiguity check before reading the enriched row.
|
|
2006
2205
|
function getWorkItems(config, opts) {
|
|
2007
2206
|
const enrich = !(opts && opts.enrich === false);
|
|
2207
|
+
const onlyId = enrich && opts && typeof opts.onlyId === 'string' && opts.onlyId !== ''
|
|
2208
|
+
? opts.onlyId
|
|
2209
|
+
: null;
|
|
2008
2210
|
const now = Date.now();
|
|
2009
2211
|
config = config || getConfig();
|
|
2010
2212
|
if (enrich) {
|
|
2213
|
+
// An onlyId caller can be served the fully-enriched cache verbatim — it is
|
|
2214
|
+
// a strict superset of what the caller asked for.
|
|
2011
2215
|
if (_workItemsCache && (now - _workItemsCacheAt) < 1000) return _workItemsCache;
|
|
2012
2216
|
} else if (_workItemsLeanCache && (now - _workItemsLeanCacheAt) < 1000) {
|
|
2013
2217
|
return _workItemsLeanCache;
|
|
@@ -2015,7 +2219,7 @@ function getWorkItems(config, opts) {
|
|
|
2015
2219
|
let allItems = [];
|
|
2016
2220
|
|
|
2017
2221
|
// SQL is the canonical (and only) work-items store after Phase 9.
|
|
2018
|
-
const store = require('
|
|
2222
|
+
const store = require('../planning/work-items-store');
|
|
2019
2223
|
allItems = store.readAllWorkItems() || [];
|
|
2020
2224
|
|
|
2021
2225
|
// Cross-reference with dispatch (fill in agent from active dispatch if missing on work item)
|
|
@@ -2063,42 +2267,24 @@ function getWorkItems(config, opts) {
|
|
|
2063
2267
|
}
|
|
2064
2268
|
}
|
|
2065
2269
|
|
|
2270
|
+
// Rows that pay the per-item detail enrichment below. Without `onlyId` this
|
|
2271
|
+
// is the whole list, exactly as before.
|
|
2272
|
+
const enrichTargets = onlyId ? allItems.filter(it => it && it.id === onlyId) : allItems;
|
|
2273
|
+
// An `onlyId` lookup that matches nothing (the caller's 404 path) skips the
|
|
2274
|
+
// block wholesale, so a miss pays none of the once-per-call prep either.
|
|
2275
|
+
// Callers without `onlyId` keep today's behaviour exactly, empty store
|
|
2276
|
+
// included.
|
|
2277
|
+
const skipEnrichment = onlyId !== null && enrichTargets.length === 0;
|
|
2278
|
+
|
|
2066
2279
|
// Detail-modal enrichment (PR cross-ref + _artifacts/_notes). Skipped for
|
|
2067
2280
|
// enrich:false callers — see the getWorkItems header.
|
|
2068
|
-
if (enrich) {
|
|
2069
|
-
|
|
2070
|
-
//
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
const canonicalPrId = shared.getCanonicalPrId(project, item._pr);
|
|
2076
|
-
const displayPrId = shared.getPrDisplayId(item._pr);
|
|
2077
|
-
const exactPr = allPrs.find(p => p.id === canonicalPrId);
|
|
2078
|
-
const displayMatches = exactPr ? [] : allPrs.filter(p => shared.getPrDisplayId(p) === displayPrId);
|
|
2079
|
-
const pr = exactPr || (displayMatches.length === 1 ? displayMatches[0] : null);
|
|
2080
|
-
if (pr) {
|
|
2081
|
-
item._pr = pr.id;
|
|
2082
|
-
item._prUrl = pr.url;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
if (!item._pr) {
|
|
2086
|
-
// Derive from PR.prdItems (single source of truth), then fall back to
|
|
2087
|
-
// branch identity. The branch fallback heals decomposed-child WIs whose
|
|
2088
|
-
// PR-opening agent only stamped the parent id into prdItems — branch
|
|
2089
|
-
// names (`work/<wi-id>`) are unique per WI within a project, so a
|
|
2090
|
-
// branch hit is as authoritative as a prdItems hit. Scoped to the same
|
|
2091
|
-
// project so cross-repo branch collisions don't false-link.
|
|
2092
|
-
// (W-mpn0b76200044eed)
|
|
2093
|
-
const itemProject = item.project || item._source;
|
|
2094
|
-
const linkedPr = allPrs.find(p => p._project === itemProject && (p.prdItems || []).includes(item.id))
|
|
2095
|
-
|| (item.branch && allPrs.find(p => p.branch === item.branch && p._project === itemProject));
|
|
2096
|
-
if (linkedPr) {
|
|
2097
|
-
item._pr = linkedPr.id;
|
|
2098
|
-
item._prUrl = linkedPr.url;
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2281
|
+
if (enrich && !skipEnrichment) {
|
|
2282
|
+
// Cross-reference with PRs (the _pr/_prUrl LIST-row fields). Extracted to a
|
|
2283
|
+
// shared, map-based helper so the /api/work-items list endpoint can attach
|
|
2284
|
+
// the same links to a PAGINATED slice without the expensive _artifacts/_notes
|
|
2285
|
+
// filesystem scan below (W-mscir7fv000u6321). Narrowed to enrichTargets so an
|
|
2286
|
+
// onlyId detail lookup only links its own row (W-mscmqk6b03ha5673).
|
|
2287
|
+
attachWorkItemPrLinks(enrichTargets, config);
|
|
2102
2288
|
|
|
2103
2289
|
// Populate _artifacts for the work item detail modal
|
|
2104
2290
|
// Build dispatch ID → work item ID lookup from completed dispatches
|
|
@@ -2135,11 +2321,14 @@ function getWorkItems(config, opts) {
|
|
|
2135
2321
|
// P-34fa5d79 — pre-build the wiId → notes map so each item's _notes lookup
|
|
2136
2322
|
// is O(1) instead of re-scanning inbox+archive per item.
|
|
2137
2323
|
const _notesByWi = _buildNotesByWiMap(dispatch);
|
|
2324
|
+
// `_workItemIdCounts` stays a full-list tally even under `onlyId` — the
|
|
2325
|
+
// `hasUniqueId` note-linkage guard below asks whether an id collides
|
|
2326
|
+
// ANYWHERE, so narrowing it would change the enriched row's output.
|
|
2138
2327
|
const _workItemIdCounts = new Map();
|
|
2139
2328
|
for (const item of allItems) {
|
|
2140
2329
|
_workItemIdCounts.set(item.id, (_workItemIdCounts.get(item.id) || 0) + 1);
|
|
2141
2330
|
}
|
|
2142
|
-
for (const item of
|
|
2331
|
+
for (const item of enrichTargets) {
|
|
2143
2332
|
const arts = shared.isPlainObject(item._artifacts) ? { ...item._artifacts } : {};
|
|
2144
2333
|
const agentId = item.dispatched_to || item.agent;
|
|
2145
2334
|
// W-ms0ytfdd000d5a26 — the model this item actually ran on (or, for items
|
|
@@ -2235,10 +2424,14 @@ function getWorkItems(config, opts) {
|
|
|
2235
2424
|
// >= 1 000 ms and miss the cache, defeating the cache entirely.
|
|
2236
2425
|
// (W-mr00aooc000i1d6b — queries-work-items-cache CI failure on slow runners)
|
|
2237
2426
|
const cacheSetAt = Date.now();
|
|
2238
|
-
|
|
2427
|
+
// An `onlyId` result is only PARTIALLY enriched, so it must never be
|
|
2428
|
+
// published as either module cache: as the enriched cache it would serve
|
|
2429
|
+
// unenriched rows to the next list caller, and as the lean cache it would
|
|
2430
|
+
// carry enrichment the lean shape does not promise.
|
|
2431
|
+
if (enrich && !onlyId) {
|
|
2239
2432
|
_workItemsCache = allItems;
|
|
2240
2433
|
_workItemsCacheAt = cacheSetAt;
|
|
2241
|
-
} else {
|
|
2434
|
+
} else if (!enrich) {
|
|
2242
2435
|
_workItemsLeanCache = allItems;
|
|
2243
2436
|
_workItemsLeanCacheAt = cacheSetAt;
|
|
2244
2437
|
}
|
|
@@ -2258,7 +2451,7 @@ let _prdResultInputHash = ''; // SQL event version + authored-plan mtim
|
|
|
2258
2451
|
function _getPrdInputHash() {
|
|
2259
2452
|
const mtimes = [];
|
|
2260
2453
|
try {
|
|
2261
|
-
const row = require('
|
|
2454
|
+
const row = require('../db').getDb().prepare('SELECT COALESCE(MAX(id), 0) AS version FROM events').get();
|
|
2262
2455
|
mtimes.push(Number(row?.version) || 0);
|
|
2263
2456
|
} catch { mtimes.push(0); }
|
|
2264
2457
|
// W-mqacrzis0003df4a — Source-plan markdown mtimes. Without this, the
|
|
@@ -2294,6 +2487,10 @@ function getPrdInfo(config) {
|
|
|
2294
2487
|
// filename so render-prd.js#renderE2eSection can merge them with the live
|
|
2295
2488
|
// tracker and keep rendering a merged aggregate after its live record is swept.
|
|
2296
2489
|
const verifyPrsByPlan = {};
|
|
2490
|
+
// W-msa0mrus00mh466f — live (non-archived) PRD objects keyed by filename so
|
|
2491
|
+
// the revision-state derivation below can run once, server-side, after the
|
|
2492
|
+
// work-item index is built.
|
|
2493
|
+
const livePlansByFile = {};
|
|
2297
2494
|
let latestUpdatedAt = 0;
|
|
2298
2495
|
|
|
2299
2496
|
// Phase 10 step 4.3 — FK-based WI↔PRD-item join scaffolding. `prdItemIdByKey`
|
|
@@ -2332,6 +2529,7 @@ function getPrdInfo(config) {
|
|
|
2332
2529
|
completedAt: plan.completedAt || '',
|
|
2333
2530
|
_archived: isArch,
|
|
2334
2531
|
});
|
|
2532
|
+
if (!isArch) livePlansByFile[pf] = plan;
|
|
2335
2533
|
if (Array.isArray(plan.verifyPrs) && plan.verifyPrs.length > 0) {
|
|
2336
2534
|
verifyPrsByPlan[pf] = plan.verifyPrs.map(r => ({
|
|
2337
2535
|
id: r.id, url: r.url || '', title: r.title || '',
|
|
@@ -2365,7 +2563,7 @@ function getPrdInfo(config) {
|
|
|
2365
2563
|
// feature-id string match.
|
|
2366
2564
|
if (useFkJoin) {
|
|
2367
2565
|
try {
|
|
2368
|
-
const db = require('
|
|
2566
|
+
const db = require('../db').getDb();
|
|
2369
2567
|
const map = new Map();
|
|
2370
2568
|
for (const r of db.prepare('SELECT p.filename AS filename, p.archived AS archived, pi.feature_id AS feature_id, pi.id AS id FROM prd_items pi JOIN prds p ON p.id = pi.prd_id WHERE pi.feature_id IS NOT NULL').all()) {
|
|
2371
2569
|
map.set(`${r.archived ? 1 : 0} ${r.filename} ${r.feature_id}`, r.id);
|
|
@@ -2375,7 +2573,7 @@ function getPrdInfo(config) {
|
|
|
2375
2573
|
}
|
|
2376
2574
|
|
|
2377
2575
|
// SQLite is the unconditional PRD source.
|
|
2378
|
-
const prdStore = require('
|
|
2576
|
+
const prdStore = require('../planning/prd-store');
|
|
2379
2577
|
for (const { filename, archived, plan, updatedAt } of prdStore.listPrdRows()) {
|
|
2380
2578
|
try {
|
|
2381
2579
|
latestUpdatedAt = Math.max(latestUpdatedAt, updatedAt);
|
|
@@ -2394,7 +2592,7 @@ function getPrdInfo(config) {
|
|
|
2394
2592
|
// so they don't masquerade as a 2nd PRD item in the aggregate guard (W-mpem52qn).
|
|
2395
2593
|
const wiById = {};
|
|
2396
2594
|
const allWiById = {};
|
|
2397
|
-
for (const wi of require('
|
|
2595
|
+
for (const wi of require('../planning/work-items-store').readAllWorkItems()) {
|
|
2398
2596
|
if (!wi?.id) {
|
|
2399
2597
|
console.warn(`[queries] Skipping work item without id in ${wi?._source || 'unknown'}:`, JSON.stringify(wi).slice(0, 120));
|
|
2400
2598
|
continue;
|
|
@@ -2403,13 +2601,27 @@ function getPrdInfo(config) {
|
|
|
2403
2601
|
if (wi.sourcePlan && !wiById[wi.id]) wiById[wi.id] = wi;
|
|
2404
2602
|
}
|
|
2405
2603
|
|
|
2604
|
+
// W-msa0mrus00mh466f — canonical PRD revision state, derived ONCE here.
|
|
2605
|
+
// `revision-requested` and `planStale` can both be true on the same PRD; the
|
|
2606
|
+
// dashboard must not offer competing recovery paths, so the server decides
|
|
2607
|
+
// which state applies and the renderer just paints it.
|
|
2608
|
+
const planRevisions = {};
|
|
2609
|
+
const allWorkItemList = Object.values(allWiById);
|
|
2610
|
+
for (const [pf, plan] of Object.entries(livePlansByFile)) {
|
|
2611
|
+
try {
|
|
2612
|
+
const revisionWi = shared.findPrdRevisionWorkItem(plan, pf, allWorkItemList);
|
|
2613
|
+
const revision = shared.derivePrdRevisionState(plan, revisionWi);
|
|
2614
|
+
if (revision.state !== shared.PRD_REVISION_STATE.NONE) planRevisions[pf] = revision;
|
|
2615
|
+
} catch { /* a malformed PRD must not break the whole PRD read */ }
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2406
2618
|
// Index work items by their stable SQL FK so the per-item join can resolve
|
|
2407
2619
|
// PRD item → WI by prd_items.id rather than the feature-id string.
|
|
2408
2620
|
// Best-effort: null map → the join falls back to the legacy string match.
|
|
2409
2621
|
let wiByPrdItemId = null;
|
|
2410
2622
|
if (useFkJoin && prdItemIdByKey) {
|
|
2411
2623
|
try {
|
|
2412
|
-
const db = require('
|
|
2624
|
+
const db = require('../db').getDb();
|
|
2413
2625
|
const fkByWiId = new Map();
|
|
2414
2626
|
for (const r of db.prepare('SELECT id, prd_item_id FROM work_items WHERE prd_item_id IS NOT NULL').all()) {
|
|
2415
2627
|
if (!fkByWiId.has(r.id)) fkByWiId.set(r.id, r.prd_item_id);
|
|
@@ -2578,7 +2790,7 @@ function getPrdInfo(config) {
|
|
|
2578
2790
|
|
|
2579
2791
|
const progress = {
|
|
2580
2792
|
total, complete, inProgress, paused, missing, donePercent, planTimings,
|
|
2581
|
-
verifyPrsByPlan,
|
|
2793
|
+
verifyPrsByPlan, planRevisions,
|
|
2582
2794
|
items: items.map(i => ({
|
|
2583
2795
|
id: i.id, name: i.name || i.title, priority: i.priority,
|
|
2584
2796
|
complexity: i.estimated_complexity || i.size, status: i.status || 'missing',
|
|
@@ -2733,7 +2945,9 @@ function _gitExec(localPath, args) {
|
|
|
2733
2945
|
// Belt-and-suspenders: explicitly kill the stuck child so it can't
|
|
2734
2946
|
// linger as an orphaned git.exe. SIGKILL on Unix, forced termination
|
|
2735
2947
|
// on Windows (Node maps both to TerminateProcess via libuv).
|
|
2736
|
-
try {
|
|
2948
|
+
try {
|
|
2949
|
+
if (child) shared.terminateProcess('engine.query.git-probe-timeout', child);
|
|
2950
|
+
}
|
|
2737
2951
|
catch { /* child may already be dead */ }
|
|
2738
2952
|
reject(new Error(`git probe watchdog: \`git ${args.join(' ')}\` did not settle within ${watchdogMs}ms`));
|
|
2739
2953
|
}, watchdogMs);
|
|
@@ -3444,7 +3658,7 @@ module.exports = {
|
|
|
3444
3658
|
getKnowledgeBaseEntries, getKnowledgeBaseEntriesSnapshot, getKnowledgeBaseIndex,
|
|
3445
3659
|
|
|
3446
3660
|
// Work items & PRD
|
|
3447
|
-
getWorkItems, invalidateWorkItemsCache, getPrdInfo,
|
|
3661
|
+
getWorkItems, attachWorkItemPrLinks, invalidateWorkItemsCache, getPrdInfo,
|
|
3448
3662
|
|
|
3449
3663
|
// W-mq5uzmc6001d708f — test hooks for the defensive PR enrichment cache.
|
|
3450
3664
|
_resetPrEnrichmentCacheForTest,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/safe-expr.js — Hand-rolled recursive-descent expression evaluator.
|
|
2
|
+
* engine/core/safe-expr.js — Hand-rolled recursive-descent expression evaluator.
|
|
3
3
|
*
|
|
4
4
|
* Phase 3.1 of feat/plan-resilient-watches (P-w6f4a3e8). Used by watch action
|
|
5
5
|
* guards (P-w7c5d8b3) and predicate-based watches (Phase 2.x) to evaluate
|