@yemi33/minions 0.1.2448 → 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 +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 +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 +13 -2
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/
|
|
2
|
+
* engine/watches/actions.js — Follow-up action registry for watches.
|
|
3
3
|
*
|
|
4
4
|
* When a watch fires, the engine first writes the inbox alert (the existing
|
|
5
5
|
* `notify` field) and then — if the watch carries a structured `action` —
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Handlers MUST swallow their own errors and return `{ ok: false, summary }`
|
|
12
12
|
* rather than throwing — this keeps a single bad action from poisoning the
|
|
13
13
|
* watches tick (other watches should still fire). The caller in
|
|
14
|
-
* engine/watches.js wraps the invocation in try/catch as a backstop.
|
|
14
|
+
* engine/watches/index.js wraps the invocation in try/catch as a backstop.
|
|
15
15
|
*
|
|
16
16
|
* Templating: action params support {{var}} substitution from `ctx`. Strings
|
|
17
17
|
* and one level of nested string fields are templated recursively. Built-in
|
|
@@ -29,7 +29,7 @@ const https = require('https');
|
|
|
29
29
|
const crypto = require('crypto');
|
|
30
30
|
const { URL } = require('url');
|
|
31
31
|
|
|
32
|
-
const shared = require('
|
|
32
|
+
const shared = require('../core/shared');
|
|
33
33
|
const {
|
|
34
34
|
WATCH_ACTION_TYPE, WI_STATUS, WORK_TYPE, DONE_STATUSES, PLAN_STATUS,
|
|
35
35
|
log, ts, uid, mutateWorkItems,
|
|
@@ -53,7 +53,7 @@ const WATCH_DEDUP_ACTIVE_STATUSES = new Set([
|
|
|
53
53
|
// must NOT include Date.now()/uid()/triggerCount so the same logical work
|
|
54
54
|
// produces the same key across ticks and engine restarts.
|
|
55
55
|
//
|
|
56
|
-
// Stored on the dispatched WI under `meta._watchDedupKey`;
|
|
56
|
+
// Stored on the dispatched WI under `meta._watchDedupKey`; this module
|
|
57
57
|
// scans existing pending+active WIs and skips the push if a match is found.
|
|
58
58
|
function computeWatchDedupKey(watch, dispatchedItemShape) {
|
|
59
59
|
const stable = {
|
|
@@ -77,7 +77,7 @@ function computeWatchDedupKey(watch, dispatchedItemShape) {
|
|
|
77
77
|
// `false` on parse/eval errors (with a `[safe-expr]` warn). That contract
|
|
78
78
|
// is what keeps the "invalid expression skips + warns" acceptance criterion
|
|
79
79
|
// a one-liner here — we trust safe-expr to do the logging.
|
|
80
|
-
const safeExpr = require('
|
|
80
|
+
const safeExpr = require('../core/safe-expr');
|
|
81
81
|
|
|
82
82
|
// ── Action registry ──────────────────────────────────────────────────────────
|
|
83
83
|
|
|
@@ -362,7 +362,7 @@ async function runWatchActionChain(watch, ctx) {
|
|
|
362
362
|
//
|
|
363
363
|
// P-w11a4c9e — Phase 6.1: NOTIFY is now the single registered code path for
|
|
364
364
|
// inbox notifications. The legacy `watch.notify === 'inbox'` branch in
|
|
365
|
-
// engine/watches.js no longer writes to the inbox itself — it instead
|
|
365
|
+
// engine/watches/index.js no longer writes to the inbox itself — it instead
|
|
366
366
|
// synthesizes an implicit `{type:'notify'}` step prepended to the action
|
|
367
367
|
// chain. To keep the legacy slug shape `watch-<id>-<triggerCount>` (so
|
|
368
368
|
// existing dedup / readers see no change), this handler defaults to that
|
|
@@ -720,7 +720,7 @@ registerActionType(WATCH_ACTION_TYPE.TRIGGER_PIPELINE, {
|
|
|
720
720
|
const pipelineId = String(p.pipelineId || ctx.pipelineId || '').trim();
|
|
721
721
|
if (!pipelineId) return { ok: false, summary: 'trigger-pipeline: pipelineId is required' };
|
|
722
722
|
let pipelineMod;
|
|
723
|
-
try { pipelineMod = require('
|
|
723
|
+
try { pipelineMod = require('../orchestration/pipeline'); }
|
|
724
724
|
catch (err) { return { ok: false, summary: `trigger-pipeline: pipeline module unavailable (${err.message})` }; }
|
|
725
725
|
let pipeline;
|
|
726
726
|
try { pipeline = pipelineMod.getPipeline(pipelineId); }
|
|
@@ -749,7 +749,7 @@ registerActionType(WATCH_ACTION_TYPE.ARCHIVE_PLAN, {
|
|
|
749
749
|
const p = ctx.params || {};
|
|
750
750
|
const planFile = String(p.planFile || ctx.planFile || '').trim();
|
|
751
751
|
if (!planFile) return { ok: false, summary: 'archive-plan: planFile is required' };
|
|
752
|
-
const store = require('
|
|
752
|
+
const store = require('../planning/prd-store');
|
|
753
753
|
if (!store.readPrd(planFile)) return { ok: false, summary: `archive-plan: ${planFile} not found` };
|
|
754
754
|
let updated = false;
|
|
755
755
|
store.mutatePrd(planFile, (data) => {
|
|
@@ -776,7 +776,7 @@ registerActionType(WATCH_ACTION_TYPE.RESUME_PLAN, {
|
|
|
776
776
|
const p = ctx.params || {};
|
|
777
777
|
const planFile = String(p.planFile || ctx.planFile || '').trim();
|
|
778
778
|
if (!planFile) return { ok: false, summary: 'resume-plan: planFile is required' };
|
|
779
|
-
const store = require('
|
|
779
|
+
const store = require('../planning/prd-store');
|
|
780
780
|
if (!store.readPrd(planFile)) return { ok: false, summary: `resume-plan: ${planFile} not found` };
|
|
781
781
|
let updated = false;
|
|
782
782
|
store.mutatePrd(planFile, (data) => {
|
|
@@ -844,7 +844,7 @@ function _ccTriageReadFileTail(filePath, maxBytes) {
|
|
|
844
844
|
function _ccTriageLookupLatestDispatch(workItemId) {
|
|
845
845
|
if (!workItemId) return null;
|
|
846
846
|
try {
|
|
847
|
-
const queries = require('
|
|
847
|
+
const queries = require('../core/queries');
|
|
848
848
|
const dispatch = queries.getDispatch() || {};
|
|
849
849
|
let latest = null;
|
|
850
850
|
for (const listName of ['completed', 'active', 'pending']) {
|
|
@@ -862,7 +862,7 @@ function _ccTriageLookupLatestDispatch(workItemId) {
|
|
|
862
862
|
}
|
|
863
863
|
|
|
864
864
|
function _ccTriageBuildArtifactSection(watch, ctx, kinds) {
|
|
865
|
-
const fence = require('
|
|
865
|
+
const fence = require('../core/untrusted-fence');
|
|
866
866
|
const attachments = [];
|
|
867
867
|
const requested = Array.isArray(kinds) && kinds.length > 0
|
|
868
868
|
? kinds.map(k => String(k || '').trim().toLowerCase()).filter(k => CC_TRIAGE_ALLOWED_ARTIFACTS.has(k))
|
|
@@ -879,7 +879,7 @@ function _ccTriageBuildArtifactSection(watch, ctx, kinds) {
|
|
|
879
879
|
dispatchEntry = _ccTriageLookupLatestDispatch(workItemId);
|
|
880
880
|
if (dispatchEntry?.id && requested.includes('completion-report')) {
|
|
881
881
|
try {
|
|
882
|
-
const queries = require('
|
|
882
|
+
const queries = require('../core/queries');
|
|
883
883
|
completionReport = queries.getDispatchCompletionReport(dispatchEntry.id);
|
|
884
884
|
} catch { completionReport = null; }
|
|
885
885
|
}
|
|
@@ -936,7 +936,7 @@ registerActionType(WATCH_ACTION_TYPE.CC_TRIAGE, {
|
|
|
936
936
|
{ key: 'timeoutMs', required: false, description: `Engine-side HTTP timeout in ms. Default ${CC_TRIAGE_DEFAULT_TIMEOUT_MS} (10 min), capped at ${CC_TRIAGE_MAX_TIMEOUT_MS} (1 h).` },
|
|
937
937
|
],
|
|
938
938
|
handler: async (watch, ctx) => {
|
|
939
|
-
const fence = require('
|
|
939
|
+
const fence = require('../core/untrusted-fence');
|
|
940
940
|
const p = ctx.params || {};
|
|
941
941
|
const promptRaw = String(p.prompt || '').trim();
|
|
942
942
|
if (!promptRaw) return { ok: false, summary: 'cc-triage: prompt is required' };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/watches.js -- Persistent watch jobs that survive engine restarts.
|
|
3
|
-
* Zero dependencies -- uses only Node.js built-ins + engine/shared.js.
|
|
2
|
+
* engine/watches/index.js -- Persistent watch jobs that survive engine restarts.
|
|
3
|
+
* Zero dependencies -- uses only Node.js built-ins + engine/core/shared.js.
|
|
4
4
|
*
|
|
5
5
|
* Watches monitor targets (PRs, work items, meetings, plans, schedules,
|
|
6
6
|
* pipelines, dispatches, agents, ...) for conditions (merged, build-fail,
|
|
@@ -29,18 +29,19 @@
|
|
|
29
29
|
* Built-in types: pr, work-item, meeting, plan, schedule, pipeline, dispatch,
|
|
30
30
|
* agent. Additional types can be added at runtime via registerTargetType().
|
|
31
31
|
*
|
|
32
|
-
* Canonical plugin example: `
|
|
32
|
+
* Canonical plugin example: `watch-plugins/http.js` (W-mp7i22mu00191b07) — a
|
|
33
33
|
* generic HTTP poller that demonstrates the full plugin contract including
|
|
34
34
|
* `contextVars` (exposing `{{httpStatus}}`, `{{extracted}}`, `{{prevExtracted}}`,
|
|
35
35
|
* etc. to action templates) and a background-fetch cache (because the engine
|
|
36
36
|
* tick path is synchronous; plugins that need async I/O maintain their own
|
|
37
37
|
* cache and return cached entities from the synchronous fetchEntity).
|
|
38
38
|
*
|
|
39
|
-
* ── Plugin folder (`
|
|
39
|
+
* ── Plugin folder (`watch-plugins/`) ─────────────────────────────────────────
|
|
40
40
|
*
|
|
41
|
-
* At engine boot, every `*.js` file in `<MINIONS_DIR>/
|
|
41
|
+
* At engine boot, every `*.js` file in `<MINIONS_DIR>/watch-plugins/` is loaded
|
|
42
42
|
* and its exports are passed to registerTargetType(). This lets users add
|
|
43
|
-
* new watch target types without editing engine source.
|
|
43
|
+
* new watch target types without editing engine source. Plugins cannot replace
|
|
44
|
+
* built-in or previously registered target types.
|
|
44
45
|
*
|
|
45
46
|
* Each plugin file exports either:
|
|
46
47
|
*
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
*
|
|
52
53
|
* or an array of those objects for multi-target plugins.
|
|
53
54
|
*
|
|
54
|
-
* Resolution: `path.join(MINIONS_DIR, '
|
|
55
|
+
* Resolution: `path.join(MINIONS_DIR, 'watch-plugins')` so it works in both dev
|
|
55
56
|
* (D:/squad) and installed (~/.minions) layouts. A missing folder is a silent
|
|
56
57
|
* no-op (not an error). Each file is required inside its own try/catch — one
|
|
57
58
|
* bad plugin must not break boot or block other plugins. Successful loads
|
|
@@ -66,11 +67,11 @@
|
|
|
66
67
|
|
|
67
68
|
const fs = require('fs');
|
|
68
69
|
const path = require('path');
|
|
69
|
-
const shared = require('
|
|
70
|
+
const shared = require('../core/shared');
|
|
70
71
|
const { mutateWatches, ts, uid, log, writeToInbox,
|
|
71
72
|
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS } = shared;
|
|
72
|
-
const watchActions = require('./
|
|
73
|
-
const apiValidation = require('
|
|
73
|
+
const watchActions = require('./actions');
|
|
74
|
+
const apiValidation = require('../api/api-validation');
|
|
74
75
|
|
|
75
76
|
// Default check interval: 5 minutes (300000ms). Engine tick runs every 10s
|
|
76
77
|
// (W-mpxpckey000laa4f), so watches are checked every N ticks where
|
|
@@ -483,7 +484,7 @@ function validateWatchId(value) {
|
|
|
483
484
|
* @returns {Array<object>}
|
|
484
485
|
*/
|
|
485
486
|
function getWatches() {
|
|
486
|
-
return require('./
|
|
487
|
+
return require('./store').readWatches();
|
|
487
488
|
}
|
|
488
489
|
|
|
489
490
|
/**
|
|
@@ -507,7 +508,7 @@ function createWatch(input, config) {
|
|
|
507
508
|
notify: validated.notify,
|
|
508
509
|
stopAfter: validated.stopAfter, // 0 = run forever; N = expire after N triggers
|
|
509
510
|
onNotMet: validated.onNotMet, // null | 'notify' — action per poll when condition not met
|
|
510
|
-
action: validated.action, // optional follow-up action — see engine/
|
|
511
|
+
action: validated.action, // optional follow-up action — see engine/watches/actions.js
|
|
511
512
|
// P-w10b6e2d — Phase 5: optional requires[] — when present every entry
|
|
512
513
|
// must also evaluate truthy for the watch to fire. See evaluateWatch.
|
|
513
514
|
requires: validated.requires,
|
|
@@ -1118,7 +1119,7 @@ registerTargetType(WATCH_TARGET_TYPE.PR, {
|
|
|
1118
1119
|
return { triggered: changed, message: changed ? `PR ${target} head commit changed: ${prev} → ${cur}` : '' };
|
|
1119
1120
|
}
|
|
1120
1121
|
case WATCH_CONDITION.MERGEABLE_FLIPPED: {
|
|
1121
|
-
// engine/github.js:902-923 documents that mergeable=null means
|
|
1122
|
+
// engine/providers/github.js:902-923 documents that mergeable=null means
|
|
1122
1123
|
// "GitHub still computing" — NOT a positive signal. Only fire on
|
|
1123
1124
|
// transitions between defined boolean values (true↔false). Any
|
|
1124
1125
|
// transition involving null or undefined is ignored.
|
|
@@ -1130,7 +1131,7 @@ registerTargetType(WATCH_TARGET_TYPE.PR, {
|
|
|
1130
1131
|
}
|
|
1131
1132
|
case WATCH_CONDITION.READY_FOR_MERGE: {
|
|
1132
1133
|
// Compound predicate — fires when ALL gating signals line up.
|
|
1133
|
-
// Registered as an absolute condition (engine/shared.js
|
|
1134
|
+
// Registered as an absolute condition (engine/core/shared.js
|
|
1134
1135
|
// WATCH_ABSOLUTE_CONDITIONS) so it auto-expires on first trigger
|
|
1135
1136
|
// when stopAfter=0. Treat isDraft !== true (catches null/undefined
|
|
1136
1137
|
// legacy PRs that don't expose the field).
|
|
@@ -1431,7 +1432,7 @@ registerTargetType(WATCH_TARGET_TYPE.SCHEDULE, {
|
|
|
1431
1432
|
validateTarget: _validateStringTarget,
|
|
1432
1433
|
fetchEntity: _findSchedule,
|
|
1433
1434
|
// P-w2c8d1e7 — Phase 1.2: surface next_run as null. Cron parsing in
|
|
1434
|
-
// engine/scheduler.js exposes parseCronExpr/.matches() but not a
|
|
1435
|
+
// engine/orchestration/scheduler.js exposes parseCronExpr/.matches() but not a
|
|
1435
1436
|
// next-fire calculator, so the snapshot field is reserved (always null
|
|
1436
1437
|
// today) for forward-compat with future predicates that gain a helper.
|
|
1437
1438
|
captureState: (s) => ({
|
|
@@ -1490,7 +1491,7 @@ function _completedStageCount(run) {
|
|
|
1490
1491
|
}
|
|
1491
1492
|
// P-w2c8d1e7 — Phase 1.2: report the id of the first non-terminal stage
|
|
1492
1493
|
// in run.stages iteration order. run.stages is built from pipeline.stages
|
|
1493
|
-
// in declaration order (engine/pipeline.js startRun ~line 84) so the first
|
|
1494
|
+
// in declaration order (engine/orchestration/pipeline.js startRun ~line 84) so the first
|
|
1494
1495
|
// non-terminal entry is the currently-executing or next-pending stage.
|
|
1495
1496
|
const _PIPELINE_TERMINAL = new Set([shared.PIPELINE_STATUS.COMPLETED, shared.PIPELINE_STATUS.FAILED, shared.PIPELINE_STATUS.STOPPED]);
|
|
1496
1497
|
function _currentPipelineStageId(run) {
|
|
@@ -1688,22 +1689,24 @@ registerTargetType(WATCH_TARGET_TYPE.AGENT, {
|
|
|
1688
1689
|
},
|
|
1689
1690
|
});
|
|
1690
1691
|
|
|
1691
|
-
// ── Plugin folder discovery
|
|
1692
|
-
// Auto-load `<MINIONS_DIR>/
|
|
1693
|
-
// built-in registerTargetType() calls
|
|
1694
|
-
//
|
|
1695
|
-
//
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const dir = path.join(shared.MINIONS_DIR, 'watches.d');
|
|
1692
|
+
// ── Plugin folder discovery ───────────────────────────────────────────────────
|
|
1693
|
+
// Auto-load `<MINIONS_DIR>/watch-plugins/*.js` at module load time, AFTER the 8
|
|
1694
|
+
// built-in registerTargetType() calls. Duplicate target names are rejected so
|
|
1695
|
+
// a plugin cannot silently replace core behavior. Missing folder is silent;
|
|
1696
|
+
// per-file try/catch isolates failures and INFO/WARN logs trace what loaded.
|
|
1697
|
+
function _loadPluginDirectory(directoryName, { legacy = false } = {}) {
|
|
1698
|
+
const dir = path.join(shared.MINIONS_DIR, directoryName);
|
|
1699
1699
|
let entries;
|
|
1700
1700
|
try {
|
|
1701
1701
|
entries = fs.readdirSync(dir);
|
|
1702
1702
|
} catch (err) {
|
|
1703
1703
|
if (err && err.code === 'ENOENT') return; // silent no-op
|
|
1704
|
-
log('warn',
|
|
1704
|
+
log('warn', `${directoryName}/ readdir failed: ${err.message}`);
|
|
1705
1705
|
return;
|
|
1706
1706
|
}
|
|
1707
|
+
if (legacy && entries.some(name => name.endsWith('.js'))) {
|
|
1708
|
+
log('warn', 'Legacy watches.d/ plugins are still loaded for compatibility; move them to watch-plugins/');
|
|
1709
|
+
}
|
|
1707
1710
|
for (const fname of entries.sort()) {
|
|
1708
1711
|
if (!fname.endsWith('.js')) continue;
|
|
1709
1712
|
const fp = path.join(dir, fname);
|
|
@@ -1714,21 +1717,29 @@ function _loadPluginTargetTypes() {
|
|
|
1714
1717
|
const list = Array.isArray(mod) ? mod : [mod];
|
|
1715
1718
|
for (const entry of list) {
|
|
1716
1719
|
if (!entry || !entry.name || !entry.spec) {
|
|
1717
|
-
log('warn',
|
|
1720
|
+
log('warn', `${directoryName}/${fname}: export missing { name, spec } — skipping entry`);
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
if (Object.prototype.hasOwnProperty.call(TARGET_TYPES, entry.name)) {
|
|
1724
|
+
log('warn', `${directoryName}/${fname}: target type '${entry.name}' already registered — skipping duplicate`);
|
|
1718
1725
|
continue;
|
|
1719
1726
|
}
|
|
1720
1727
|
try {
|
|
1721
1728
|
registerTargetType(entry.name, entry.spec);
|
|
1722
|
-
log('info', `Loaded watch target type '${entry.name}' from
|
|
1729
|
+
log('info', `Loaded watch target type '${entry.name}' from ${directoryName}/${fname}`);
|
|
1723
1730
|
} catch (regErr) {
|
|
1724
|
-
log('warn',
|
|
1731
|
+
log('warn', `${directoryName}/${fname}: registerTargetType('${entry.name}') failed: ${regErr.message}`);
|
|
1725
1732
|
}
|
|
1726
1733
|
}
|
|
1727
1734
|
} catch (err) {
|
|
1728
|
-
log('warn',
|
|
1735
|
+
log('warn', `${directoryName}/${fname}: load failed: ${err.message}`);
|
|
1729
1736
|
}
|
|
1730
1737
|
}
|
|
1731
1738
|
}
|
|
1739
|
+
function _loadPluginTargetTypes() {
|
|
1740
|
+
_loadPluginDirectory('watch-plugins');
|
|
1741
|
+
_loadPluginDirectory('watches.d', { legacy: true });
|
|
1742
|
+
}
|
|
1732
1743
|
_loadPluginTargetTypes();
|
|
1733
1744
|
|
|
1734
1745
|
module.exports = {
|
|
@@ -1748,8 +1759,8 @@ module.exports = {
|
|
|
1748
1759
|
registerTargetType,
|
|
1749
1760
|
getTargetType,
|
|
1750
1761
|
listTargetTypes,
|
|
1751
|
-
// Re-exported from engine/
|
|
1752
|
-
// import surface (dashboard.js handlers, tests).
|
|
1762
|
+
// Re-exported from engine/watches/actions for callers that prefer a single
|
|
1763
|
+
// import surface (dashboard.js handlers, tests).
|
|
1753
1764
|
// for the canonical implementations and the action registry.
|
|
1754
1765
|
registerActionType: watchActions.registerActionType,
|
|
1755
1766
|
getActionType: watchActions.getActionType,
|
|
@@ -1761,5 +1772,5 @@ module.exports = {
|
|
|
1761
1772
|
_captureState, // exported for testing
|
|
1762
1773
|
_runActionTask, // exported for testing — invoked by checkWatches per fired action
|
|
1763
1774
|
_TARGET_TYPES: TARGET_TYPES, // exported for testing — direct registry access
|
|
1764
|
-
_loadPluginTargetTypes, // exported for testing — re-scans
|
|
1775
|
+
_loadPluginTargetTypes, // exported for testing — re-scans watch-plugins/
|
|
1765
1776
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/watches
|
|
1
|
+
// engine/watches/store.js — SQL-backed watches.
|
|
2
2
|
//
|
|
3
3
|
// A single SQL table (no per-scope split), with a diff-then-apply mutator.
|
|
4
4
|
//
|
|
@@ -29,7 +29,7 @@ function _readWatchesFromSqlOnly(db) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function readWatches() {
|
|
32
|
-
const { getDb } = require('
|
|
32
|
+
const { getDb } = require('../db');
|
|
33
33
|
return _readWatchesFromSqlOnly(getDb());
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -100,11 +100,11 @@ function _applyWatchesDiff(db, diff) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function applyWatchesMutation(mutator) {
|
|
103
|
-
const { getDb, withTransaction } = require('
|
|
103
|
+
const { getDb, withTransaction } = require('../db');
|
|
104
104
|
let db;
|
|
105
105
|
try { db = getDb(); }
|
|
106
106
|
catch (e) {
|
|
107
|
-
throw new Error(`engine/watches
|
|
107
|
+
throw new Error(`engine/watches/store: SQLite unavailable (${e.message}); cannot mutate watches`);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
return withTransaction(db, () => {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// worktree was borrowed from the pool, returned to the pool, or is
|
|
10
10
|
// anchored by live keep_processes PIDs / managed_spawn cwds.
|
|
11
11
|
//
|
|
12
|
-
// 2. On engine boot (engine/cli.js): scan each project's worktree root
|
|
12
|
+
// 2. On engine boot (engine/operations/cli.js): scan each project's worktree root
|
|
13
13
|
// for orphan W-* dirs whose dispatch is not active
|
|
14
14
|
// or pending) AND that are not pool entries. Such dirs are leftovers
|
|
15
15
|
// from a prior crash that block retries with
|
|
@@ -24,21 +24,21 @@
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
26
|
|
|
27
|
-
const shared = require('
|
|
28
|
-
const worktreePool = require('./
|
|
27
|
+
const shared = require('../core/shared');
|
|
28
|
+
const worktreePool = require('./pool');
|
|
29
29
|
|
|
30
30
|
const DEFAULT_ORPHAN_RETENTION_MS = 2 * 60 * 60 * 1000;
|
|
31
31
|
const DEFAULT_QUARANTINE_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
32
32
|
|
|
33
33
|
let keepProcessSweep = null;
|
|
34
34
|
function _keepProcessSweep() {
|
|
35
|
-
if (!keepProcessSweep) keepProcessSweep = require('
|
|
35
|
+
if (!keepProcessSweep) keepProcessSweep = require('../processes/keep-process-sweep');
|
|
36
36
|
return keepProcessSweep;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
let managedSpawn = null;
|
|
40
40
|
function _managedSpawn() {
|
|
41
|
-
if (!managedSpawn) managedSpawn = require('
|
|
41
|
+
if (!managedSpawn) managedSpawn = require('../processes/managed-spawn');
|
|
42
42
|
return managedSpawn;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -263,12 +263,13 @@ function _maybeEscalateStuck(resolvedPath, errorMsg, opts = {}) {
|
|
|
263
263
|
const minAgeMs = agentTimeoutMs * 2;
|
|
264
264
|
const killFn = typeof opts.killImmediate === 'function'
|
|
265
265
|
? opts.killImmediate
|
|
266
|
-
: shared.
|
|
266
|
+
: (proc, holder) => shared.terminateProcess('engine.worktree-gc.orphan-holder', proc, {
|
|
267
|
+
expectedIdentity: { pid: holder.pid, startedAt: holder.startedAt },
|
|
268
|
+
});
|
|
267
269
|
for (const h of holders) {
|
|
268
270
|
if (!_isSafeReapHolder(h, basename, minAgeMs)) continue;
|
|
269
271
|
try {
|
|
270
|
-
killFn({ pid: h.pid });
|
|
271
|
-
reapedPids.push(h.pid);
|
|
272
|
+
if (killFn({ pid: h.pid }, h) !== false) reapedPids.push(h.pid);
|
|
272
273
|
} catch { /* fail-open — keep trying other holders */ }
|
|
273
274
|
}
|
|
274
275
|
if (reapedPids.length > 0) {
|
|
@@ -869,6 +870,15 @@ function reconcileStaleWorktreeDirectories(opts) {
|
|
|
869
870
|
Number.isFinite(Number(opts.quarantineRetentionMs))
|
|
870
871
|
? Number(opts.quarantineRetentionMs)
|
|
871
872
|
: DEFAULT_QUARANTINE_RETENTION_MS);
|
|
873
|
+
// W-ms5tb6ha016fda10 — bounded (but longest) window for PROTECTED quarantine
|
|
874
|
+
// dirs, whose dirty WIP has no durable recovery ref. 0 keeps them forever.
|
|
875
|
+
// W-ms87rbe601a66d13 — the config→default resolution goes through
|
|
876
|
+
// `shared.resolveQuarantineRetentionMs` so this sweep and the recovery-REF
|
|
877
|
+
// sweep read the operator override through ONE seam and cannot diverge.
|
|
878
|
+
const protectedQuarantineRetentionMs = Math.max(0,
|
|
879
|
+
Number.isFinite(Number(opts.protectedQuarantineRetentionMs))
|
|
880
|
+
? Number(opts.protectedQuarantineRetentionMs)
|
|
881
|
+
: shared.resolveQuarantineRetentionMs(opts.config));
|
|
872
882
|
|
|
873
883
|
const result = {
|
|
874
884
|
scanned: 0,
|
|
@@ -1120,9 +1130,29 @@ function reconcileStaleWorktreeDirectories(opts) {
|
|
|
1120
1130
|
}
|
|
1121
1131
|
let protectedQuarantine = true;
|
|
1122
1132
|
try { protectedQuarantine = !!_hasProtectedQuarantineMarker(candidate); } catch {}
|
|
1133
|
+
// W-ms5tb6ha016fda10 — a protected quarantine keeps the LONGEST window,
|
|
1134
|
+
// but no longer an infinite one. `protectedQuarantineRetentionMs === 0`
|
|
1135
|
+
// is the explicit operator opt-out that restores forever-retention.
|
|
1136
|
+
// Non-quarantine dirs are never affected: the marker probe fails open to
|
|
1137
|
+
// `true`, so an unreadable dir is still preserved.
|
|
1123
1138
|
if (protectedQuarantine) {
|
|
1124
|
-
|
|
1125
|
-
|
|
1139
|
+
if (protectedQuarantineRetentionMs === 0) {
|
|
1140
|
+
keep('protected', candidate);
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
let protectedStat;
|
|
1144
|
+
try { protectedStat = _fs.statSync(candidate); }
|
|
1145
|
+
catch {
|
|
1146
|
+
keep('protected', candidate);
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
const protectedCreatedAt = _isQuarantineDirName(name)
|
|
1150
|
+
? _quarantineCreatedAt(name, protectedStat.mtimeMs)
|
|
1151
|
+
: protectedStat.mtimeMs;
|
|
1152
|
+
if ((now - protectedCreatedAt) < protectedQuarantineRetentionMs) {
|
|
1153
|
+
keep('protected', candidate);
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1126
1156
|
}
|
|
1127
1157
|
|
|
1128
1158
|
let owned = false;
|
|
@@ -1222,7 +1252,7 @@ function reconcileStaleWorktreeDirectories(opts) {
|
|
|
1222
1252
|
* 2. Normalized abs path is currently in any `worktreePool` entry (idle
|
|
1223
1253
|
* or borrowed) — see `extraPoolPaths` for the test override.
|
|
1224
1254
|
* 3. Worktree contains (or equals) the cwd of any live managed_spawn
|
|
1225
|
-
* spec — matches `engine/cleanup.js`'s tick-time protection so a
|
|
1255
|
+
* spec — matches `engine/orchestration/cleanup.js`'s tick-time protection so a
|
|
1226
1256
|
* completed dispatch with long-running services survives engine
|
|
1227
1257
|
* restart. (PR #2627 review — Issue 1)
|
|
1228
1258
|
*
|
|
@@ -1394,16 +1424,21 @@ function pruneOrphanWorktrees(opts) {
|
|
|
1394
1424
|
continue;
|
|
1395
1425
|
}
|
|
1396
1426
|
// Quarantine directories are recovery artifacts, not ordinary crash
|
|
1397
|
-
// residue. Keep them for a deterministic recovery window
|
|
1398
|
-
//
|
|
1427
|
+
// residue. Keep them for a deterministic recovery window.
|
|
1428
|
+
//
|
|
1429
|
+
// W-ms5tb6ha016fda10 — PROTECTED quarantines (dirty WIP that could NOT be
|
|
1430
|
+
// snapshotted to a durable `refs/minions/quarantine-wip/*` ref) used to be
|
|
1431
|
+
// kept FOREVER. That is why a busy install accumulated 20 quarantine dirs
|
|
1432
|
+
// with no expiry. They still get the LONGEST window — `engine
|
|
1433
|
+
// .worktreeProtectedQuarantineRetentionMs`, whose default is DERIVED from
|
|
1434
|
+
// `shared.QUARANTINE_REF_RETENTION_MS` (30 days) so directories and
|
|
1435
|
+
// recovery refs expire together — but the window is now finite. Setting the
|
|
1436
|
+
// config value to 0 restores indefinite retention (for the recovery refs
|
|
1437
|
+
// too, since W-ms87rbe601a66d13 routes both sweeps through
|
|
1438
|
+
// `shared.resolveQuarantineRetentionMs`).
|
|
1399
1439
|
if (_isQuarantineDirName(name)) {
|
|
1400
1440
|
let protectedQuarantine = true;
|
|
1401
1441
|
try { protectedQuarantine = shared.hasProtectedQuarantineMarker(wtPath); } catch {}
|
|
1402
|
-
if (protectedQuarantine) {
|
|
1403
|
-
projStats.kept++; result.kept++;
|
|
1404
|
-
projStats.preserved++; result.preserved++;
|
|
1405
|
-
continue;
|
|
1406
|
-
}
|
|
1407
1442
|
let quarantineStat;
|
|
1408
1443
|
try { quarantineStat = _fs.statSync(wtPath); }
|
|
1409
1444
|
catch {
|
|
@@ -1412,10 +1447,23 @@ function pruneOrphanWorktrees(opts) {
|
|
|
1412
1447
|
continue;
|
|
1413
1448
|
}
|
|
1414
1449
|
const now = Number.isFinite(Number(opts.now)) ? Number(opts.now) : Date.now();
|
|
1415
|
-
const
|
|
1416
|
-
Number.isFinite(Number(opts.
|
|
1417
|
-
? Number(opts.
|
|
1418
|
-
:
|
|
1450
|
+
const protectedRetentionMs = Math.max(0,
|
|
1451
|
+
Number.isFinite(Number(opts.protectedQuarantineRetentionMs))
|
|
1452
|
+
? Number(opts.protectedQuarantineRetentionMs)
|
|
1453
|
+
: shared.resolveQuarantineRetentionMs(opts.config));
|
|
1454
|
+
const retentionMs = protectedQuarantine
|
|
1455
|
+
? protectedRetentionMs
|
|
1456
|
+
: Math.max(0,
|
|
1457
|
+
Number.isFinite(Number(opts.quarantineRetentionMs))
|
|
1458
|
+
? Number(opts.quarantineRetentionMs)
|
|
1459
|
+
: DEFAULT_QUARANTINE_RETENTION_MS);
|
|
1460
|
+
// 0 == keep forever (explicit operator opt-out), and only for the
|
|
1461
|
+
// protected class — an unprotected quarantine always has a durable ref.
|
|
1462
|
+
if (protectedQuarantine && retentionMs === 0) {
|
|
1463
|
+
projStats.kept++; result.kept++;
|
|
1464
|
+
projStats.preserved++; result.preserved++;
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1419
1467
|
const createdAt = _quarantineCreatedAt(name, quarantineStat.mtimeMs);
|
|
1420
1468
|
if ((now - createdAt) < retentionMs) {
|
|
1421
1469
|
projStats.kept++; result.kept++;
|