@yemi33/minions 0.1.2447 → 0.1.2449
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +52 -6
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/prd-store.js
|
|
1
|
+
// engine/planning/prd-store.js
|
|
2
2
|
//
|
|
3
3
|
// SQL-authoritative PRD state.
|
|
4
4
|
|
|
@@ -192,11 +192,11 @@ function writePrd(filename, prd, { archived = false } = {}) {
|
|
|
192
192
|
throw new TypeError('prd-store.writePrd requires a filename and object');
|
|
193
193
|
}
|
|
194
194
|
const normalizedPrd = _normalizePrdForStorage(prd);
|
|
195
|
-
const { getDb, withTransaction } = require('
|
|
195
|
+
const { getDb, withTransaction } = require('../db');
|
|
196
196
|
const db = getDb();
|
|
197
197
|
const archivedValue = archived ? 1 : 0;
|
|
198
198
|
const prdId = withTransaction(db, () => _upsertPrd(db, filename, archivedValue, normalizedPrd, Date.now()));
|
|
199
|
-
require('
|
|
199
|
+
require('../persistence/db-events').emitStateEvent('prds', { filename, archived: archivedValue });
|
|
200
200
|
return { ok: true, id: prdId, plan: normalizedPrd };
|
|
201
201
|
}
|
|
202
202
|
|
|
@@ -207,7 +207,7 @@ function createPrdUnique(baseFilename, prd) {
|
|
|
207
207
|
}
|
|
208
208
|
const normalizedPrd = _normalizePrdForStorage(prd);
|
|
209
209
|
const stem = baseFilename.slice(0, -'.json'.length);
|
|
210
|
-
const { getDb, withTransaction } = require('
|
|
210
|
+
const { getDb, withTransaction } = require('../db');
|
|
211
211
|
const db = getDb();
|
|
212
212
|
const filename = withTransaction(db, () => {
|
|
213
213
|
const exists = db.prepare('SELECT 1 FROM prds WHERE filename=? LIMIT 1');
|
|
@@ -219,7 +219,7 @@ function createPrdUnique(baseFilename, prd) {
|
|
|
219
219
|
}
|
|
220
220
|
throw new Error('Could not reserve unique PRD filename after 100 attempts');
|
|
221
221
|
});
|
|
222
|
-
require('
|
|
222
|
+
require('../persistence/db-events').emitStateEvent('prds', { filename, archived: 0 });
|
|
223
223
|
return filename;
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -228,7 +228,7 @@ function mutatePrd(filename, mutator, { archived = false, defaultValue = {} } =
|
|
|
228
228
|
if (typeof mutator !== 'function') {
|
|
229
229
|
throw new TypeError('prd-store.mutatePrd requires a filename and mutator');
|
|
230
230
|
}
|
|
231
|
-
const { getDb, withTransaction } = require('
|
|
231
|
+
const { getDb, withTransaction } = require('../db');
|
|
232
232
|
const db = getDb();
|
|
233
233
|
const result = withTransaction(db, () => {
|
|
234
234
|
const archivedValue = archived ? 1 : 0;
|
|
@@ -244,13 +244,13 @@ function mutatePrd(filename, mutator, { archived = false, defaultValue = {} } =
|
|
|
244
244
|
_upsertPrd(db, filename, archivedValue, normalizedPrd, Date.now());
|
|
245
245
|
return normalizedPrd;
|
|
246
246
|
});
|
|
247
|
-
require('
|
|
247
|
+
require('../persistence/db-events').emitStateEvent('prds', { filename, archived: archived ? 1 : 0 });
|
|
248
248
|
return result;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function deletePrd(filename, { archived = false } = {}) {
|
|
252
252
|
validatePrdFilename(filename);
|
|
253
|
-
const { getDb, withTransaction } = require('
|
|
253
|
+
const { getDb, withTransaction } = require('../db');
|
|
254
254
|
const db = getDb();
|
|
255
255
|
const removed = withTransaction(db, () => {
|
|
256
256
|
const archivedValue = archived ? 1 : 0;
|
|
@@ -259,13 +259,13 @@ function deletePrd(filename, { archived = false } = {}) {
|
|
|
259
259
|
_deletePrdRow(db, row.id);
|
|
260
260
|
return true;
|
|
261
261
|
});
|
|
262
|
-
if (removed) require('
|
|
262
|
+
if (removed) require('../persistence/db-events').emitStateEvent('prds', { filename, archived: archived ? 1 : 0, removed: true });
|
|
263
263
|
return removed;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
function readPrd(filename, { archived = false } = {}) {
|
|
267
267
|
validatePrdFilename(filename);
|
|
268
|
-
const row = require('
|
|
268
|
+
const row = require('../db').getDb()
|
|
269
269
|
.prepare('SELECT data FROM prds WHERE filename=? AND archived=?')
|
|
270
270
|
.get(filename, archived ? 1 : 0);
|
|
271
271
|
return row ? JSON.parse(row.data) : null;
|
|
@@ -284,7 +284,7 @@ function restorePrdFromArchive(filename) {
|
|
|
284
284
|
if (!filename || typeof filename !== 'string') {
|
|
285
285
|
throw new TypeError('prd-store.restorePrdFromArchive requires a filename');
|
|
286
286
|
}
|
|
287
|
-
const { getDb, withTransaction } = require('
|
|
287
|
+
const { getDb, withTransaction } = require('../db');
|
|
288
288
|
const db = getDb();
|
|
289
289
|
const restored = withTransaction(db, () => {
|
|
290
290
|
const row = db.prepare('SELECT id, data FROM prds WHERE filename=? AND archived=1').get(filename);
|
|
@@ -295,7 +295,7 @@ function restorePrdFromArchive(filename) {
|
|
|
295
295
|
return prd;
|
|
296
296
|
});
|
|
297
297
|
if (restored) {
|
|
298
|
-
require('
|
|
298
|
+
require('../persistence/db-events').emitStateEvent('prds', { filename, archived: 0, restored: true });
|
|
299
299
|
}
|
|
300
300
|
return restored;
|
|
301
301
|
}
|
|
@@ -309,7 +309,7 @@ function movePrdToArchive(filename, replacementPrd) {
|
|
|
309
309
|
&& (!replacementPrd || typeof replacementPrd !== 'object' || Array.isArray(replacementPrd))) {
|
|
310
310
|
throw new TypeError('prd-store.movePrdToArchive replacement must be a PRD object');
|
|
311
311
|
}
|
|
312
|
-
const { getDb, withTransaction } = require('
|
|
312
|
+
const { getDb, withTransaction } = require('../db');
|
|
313
313
|
const db = getDb();
|
|
314
314
|
const archived = withTransaction(db, () => {
|
|
315
315
|
const row = db.prepare('SELECT id, data FROM prds WHERE filename=? AND archived=0').get(filename);
|
|
@@ -322,7 +322,7 @@ function movePrdToArchive(filename, replacementPrd) {
|
|
|
322
322
|
return prd;
|
|
323
323
|
});
|
|
324
324
|
if (archived) {
|
|
325
|
-
require('
|
|
325
|
+
require('../persistence/db-events').emitStateEvent('prds', {
|
|
326
326
|
filename,
|
|
327
327
|
archived: 1,
|
|
328
328
|
compensated: true,
|
|
@@ -338,9 +338,9 @@ function movePrdToArchive(filename, replacementPrd) {
|
|
|
338
338
|
// Returns { ok, byWorkItemId, bySourcePlan } counts.
|
|
339
339
|
function backfillWorkItemPrdItemIds() {
|
|
340
340
|
let db;
|
|
341
|
-
try { db = require('
|
|
341
|
+
try { db = require('../db').getDb(); } catch { return { ok: false }; }
|
|
342
342
|
try {
|
|
343
|
-
return require('
|
|
343
|
+
return require('../db').withTransaction(db, () => {
|
|
344
344
|
// Path 1 — explicit prd_items.work_item_id backref (most precise).
|
|
345
345
|
const r1 = db.prepare(`
|
|
346
346
|
UPDATE work_items SET prd_item_id = (
|
|
@@ -378,7 +378,7 @@ function backfillWorkItemPrdItemIds() {
|
|
|
378
378
|
// data blob. Sorted by (archived, filename) for deterministic consumer order
|
|
379
379
|
// (matches the disk-scan order once the caller also sorts). Read-flip source.
|
|
380
380
|
function listPrdRows() {
|
|
381
|
-
const db = require('
|
|
381
|
+
const db = require('../db').getDb();
|
|
382
382
|
const rows = db.prepare('SELECT filename, archived, data, updated_at FROM prds ORDER BY archived, filename').all();
|
|
383
383
|
const out = [];
|
|
384
384
|
for (const r of rows) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { execFileSync: defaultExecFileSync } = require('child_process');
|
|
8
|
-
const shared = require('
|
|
8
|
+
const shared = require('../core/shared');
|
|
9
9
|
|
|
10
10
|
function decodeUrlSegment(segment) {
|
|
11
11
|
try { return decodeURIComponent(String(segment || '')); } catch { return String(segment || ''); }
|
|
@@ -196,7 +196,7 @@ function runAzJson(execFileSync, args, timeoutMs) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// W-mq8li79a000889fa — official ADO REST endpoint for repo lookup.
|
|
199
|
-
// Matches engine/ado.js#resolveAdoBuildRepositoryGuid (the canonical
|
|
199
|
+
// Matches engine/ado/index.js#resolveAdoBuildRepositoryGuid (the canonical
|
|
200
200
|
// PR-poll path). Path-style encoding mirrors that helper so a project
|
|
201
201
|
// named with reserved chars (e.g. "Project With Spaces") routes the
|
|
202
202
|
// same way at link time as at poll time. The repo segment is URL-
|
|
@@ -212,7 +212,7 @@ function _buildAdoRepoLookupUrl({ orgUrl, project, repoName }) {
|
|
|
212
212
|
// this AFTER `az repos show/list` to fill in the GUID when the CLI is
|
|
213
213
|
// unavailable (typical agent box without Azure CLI installed).
|
|
214
214
|
//
|
|
215
|
-
// Token acquisition uses engine/ado
|
|
215
|
+
// Token acquisition uses engine/ado/token.js#acquireAdoToken (az CLI
|
|
216
216
|
// then azureauth fallback). Tests inject `options.fetch` and
|
|
217
217
|
// `options.acquireAdoToken` directly.
|
|
218
218
|
async function resolveAdoRemoteMetadataViaRest(remote, options = {}) {
|
|
@@ -221,7 +221,7 @@ async function resolveAdoRemoteMetadataViaRest(remote, options = {}) {
|
|
|
221
221
|
if (typeof fetchImpl !== 'function') return null;
|
|
222
222
|
let acquireToken = options.acquireAdoToken;
|
|
223
223
|
if (typeof acquireToken !== 'function') {
|
|
224
|
-
try { acquireToken = require('
|
|
224
|
+
try { acquireToken = require('../ado/token').acquireAdoToken; }
|
|
225
225
|
catch { return null; }
|
|
226
226
|
}
|
|
227
227
|
let token;
|
|
@@ -387,7 +387,7 @@ function discoverProjectMetadata(targetDir, options = {}) {
|
|
|
387
387
|
|
|
388
388
|
// W-mq8li79a000889fa — async wrapper that runs the sync `az`-based
|
|
389
389
|
// discovery first, then falls back to the official ADO REST endpoint
|
|
390
|
-
// (via engine/ado
|
|
390
|
+
// (via engine/ado/token.js + globalThis.fetch) when the remote is ADO
|
|
391
391
|
// but no repositoryId was resolved. Non-ADO remotes are returned
|
|
392
392
|
// unchanged. Failure to resolve the GUID via REST leaves
|
|
393
393
|
// `repositoryId` empty — callers (engine/projects.js#addProject) MUST
|
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const shared = require('
|
|
11
|
-
const dispatch = require('
|
|
12
|
-
const queries = require('
|
|
10
|
+
const shared = require('../core/shared');
|
|
11
|
+
const dispatch = require('../orchestration/dispatch');
|
|
12
|
+
const queries = require('../core/queries');
|
|
13
13
|
const projectDiscovery = require('./project-discovery');
|
|
14
|
-
const scheduleBootstrap = require('
|
|
15
|
-
const apiValidation = require('
|
|
14
|
+
const scheduleBootstrap = require('../orchestration/schedule-bootstrap');
|
|
15
|
+
const apiValidation = require('../api/api-validation');
|
|
16
16
|
const { MINIONS_DIR } = shared;
|
|
17
17
|
|
|
18
18
|
const PROJECT_PATH_MAX_LENGTH = 4096;
|
|
@@ -195,7 +195,7 @@ function resolveProjectRemovalDataMode(options = {}) {
|
|
|
195
195
|
// Windows EPERM/EBUSY can hit fs.renameSync transiently when antivirus,
|
|
196
196
|
// indexer, or another scanner has the directory open. Retry with backoff
|
|
197
197
|
// before giving up — same pattern used by shared.safeWrite for atomic
|
|
198
|
-
// JSON writes (engine/shared.js:424).
|
|
198
|
+
// JSON writes (engine/core/shared.js:424).
|
|
199
199
|
function _renameWithRetry(src, dest) {
|
|
200
200
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
201
201
|
try {
|
|
@@ -377,7 +377,7 @@ function removeProject(target, options = {}) {
|
|
|
377
377
|
// Kills + unlinks every spec owned by this project, including the .log
|
|
378
378
|
// and .log.1 rotated sibling. Best-effort: failures only warn.
|
|
379
379
|
try {
|
|
380
|
-
const managedSpawn = require('
|
|
380
|
+
const managedSpawn = require('../processes/managed-spawn');
|
|
381
381
|
const result = managedSpawn.removeManagedSpecsForProject(project.name);
|
|
382
382
|
summary.killedManagedProcesses = result.killed || 0;
|
|
383
383
|
} catch (e) {
|
|
@@ -423,7 +423,7 @@ function removeProject(target, options = {}) {
|
|
|
423
423
|
const archivedSourcePlans = new Set();
|
|
424
424
|
try {
|
|
425
425
|
{
|
|
426
|
-
const { archivePlan } = require('
|
|
426
|
+
const { archivePlan } = require('../orchestration/lifecycle');
|
|
427
427
|
for (const { filename: f, plan: prd, archived } of require('./prd-store').listPrdRows()) {
|
|
428
428
|
if (archived) continue;
|
|
429
429
|
try {
|
|
@@ -458,7 +458,7 @@ function removeProject(target, options = {}) {
|
|
|
458
458
|
// 6. Surface pipelines that reference this project so the user can review
|
|
459
459
|
// them. Don't auto-modify — user intent there is unclear.
|
|
460
460
|
try {
|
|
461
|
-
const { getPipelines } = require('
|
|
461
|
+
const { getPipelines } = require('../orchestration/pipeline');
|
|
462
462
|
for (const p of getPipelines() || []) {
|
|
463
463
|
const refs = [
|
|
464
464
|
...(p.monitoredResources || []),
|
|
@@ -602,7 +602,7 @@ function removeProject(target, options = {}) {
|
|
|
602
602
|
|
|
603
603
|
// 9. Drop SQL rows for the removed project scope.
|
|
604
604
|
try { require('./work-items-store').dropScope(project.name); } catch { /* best-effort */ }
|
|
605
|
-
try { require('
|
|
605
|
+
try { require('../persistence/pull-requests-store').dropScope(project.name); } catch { /* best-effort */ }
|
|
606
606
|
|
|
607
607
|
summary.ok = true;
|
|
608
608
|
return summary;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/resolve-area.js — Reverse
|
|
2
|
+
* engine/planning/resolve-area.js — Reverse component-to-path resolver.
|
|
3
3
|
*
|
|
4
4
|
* WI W-mrpbsbs7000ce8ac. Command Center (and other callers) frequently receive
|
|
5
5
|
* a monorepo component name in a natural-language request ("audit OCM changes",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const apiValidation = require('
|
|
1
|
+
const apiValidation = require('../api/api-validation');
|
|
2
2
|
|
|
3
3
|
const WORK_ITEM_SCOPES = Object.freeze(['fan-out']);
|
|
4
4
|
const WORK_ITEM_FEEDBACK_RATINGS = Object.freeze(['up', 'down']);
|
|
@@ -323,6 +323,21 @@ function _normalizeWorkdir(value, field, validateWorkdir) {
|
|
|
323
323
|
return result.value;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
function _normalizeExecutionSurface(value, resolvedType, validateExecutionSurface) {
|
|
327
|
+
if (typeof validateExecutionSurface !== 'function') {
|
|
328
|
+
throw new TypeError('normalize work-item input requires validateExecutionSurface');
|
|
329
|
+
}
|
|
330
|
+
const result = validateExecutionSurface(value, resolvedType);
|
|
331
|
+
if (!result || result.valid !== true) {
|
|
332
|
+
_inputError(`meta.executionSurface: ${result?.error || 'invalid execution surface'}`, {
|
|
333
|
+
code: result?.code || 'invalid-execution-surface',
|
|
334
|
+
field: 'meta.executionSurface',
|
|
335
|
+
rejectedValue: value,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return result.value;
|
|
339
|
+
}
|
|
340
|
+
|
|
326
341
|
function _normalizeMeta(value, options = {}) {
|
|
327
342
|
if (value === undefined) return undefined;
|
|
328
343
|
apiValidation.validatePlainObject(value, {
|
|
@@ -341,6 +356,20 @@ function _normalizeMeta(value, options = {}) {
|
|
|
341
356
|
if (workdir === null) delete normalized.workdir;
|
|
342
357
|
else normalized.workdir = workdir;
|
|
343
358
|
}
|
|
359
|
+
// W-ms5tb6ha016fda10 — explicit control-plane opt-in, validated fail-closed
|
|
360
|
+
// against the resolved work type. An ineligible (repo-reading/mutating) type
|
|
361
|
+
// is REJECTED at the API boundary rather than silently downgraded, so an
|
|
362
|
+
// operator cannot accidentally strip a checkout from work that needs one.
|
|
363
|
+
// The default 'repo' value is dropped so unchanged items keep today's shape.
|
|
364
|
+
if (_hasOwn(value, 'executionSurface')) {
|
|
365
|
+
const surface = _normalizeExecutionSurface(
|
|
366
|
+
value.executionSurface,
|
|
367
|
+
options.resolvedType,
|
|
368
|
+
options.validateExecutionSurface,
|
|
369
|
+
);
|
|
370
|
+
if (!surface || surface === 'repo') delete normalized.executionSurface;
|
|
371
|
+
else normalized.executionSurface = surface;
|
|
372
|
+
}
|
|
344
373
|
for (const field of META_BOOLEAN_FIELDS) {
|
|
345
374
|
if (_hasOwn(value, field)) {
|
|
346
375
|
normalized[field] = _normalizeBoolean(value[field], `meta.${field}`);
|
|
@@ -445,7 +474,7 @@ function normalizeCreateInput(body, options = {}) {
|
|
|
445
474
|
for (const field of ['skipPr', 'oneShot', 'agentLock', 'hardAgent']) {
|
|
446
475
|
if (_hasOwn(body, field)) normalized[field] = _normalizeBoolean(body[field], field);
|
|
447
476
|
}
|
|
448
|
-
normalized.meta = _normalizeMeta(body.meta, options);
|
|
477
|
+
normalized.meta = _normalizeMeta(body.meta, { ...options, resolvedType: normalized.type });
|
|
449
478
|
for (const field of ['planFile', 'branch', 'branchStrategy', 'featureBranch']) {
|
|
450
479
|
if (_hasOwn(body, field)) {
|
|
451
480
|
normalized[field] = _normalizeText(body[field], field, {
|
|
@@ -510,7 +539,14 @@ function normalizeUpdateInput(body, options = {}) {
|
|
|
510
539
|
if (_hasOwn(body, 'workdir')) {
|
|
511
540
|
normalized.workdir = _normalizeWorkdir(body.workdir, 'workdir', options.validateWorkdir);
|
|
512
541
|
}
|
|
513
|
-
|
|
542
|
+
// W-ms5tb6ha016fda10 — an update may change type, meta.executionSurface, or
|
|
543
|
+
// both. Prefer the incoming type; fall back to the stored item's type
|
|
544
|
+
// (`options.currentType`, supplied by the dashboard handler) so a
|
|
545
|
+
// meta-only edit is still checked against the type the item will run as.
|
|
546
|
+
normalized.meta = _normalizeMeta(body.meta, {
|
|
547
|
+
...options,
|
|
548
|
+
resolvedType: _hasOwn(body, 'type') ? normalized.type : options.currentType,
|
|
549
|
+
});
|
|
514
550
|
return normalized;
|
|
515
551
|
}
|
|
516
552
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/work-items-store.js — SQL-backed scoped work-item storage.
|
|
1
|
+
// engine/planning/work-items-store.js — SQL-backed scoped work-item storage.
|
|
2
2
|
//
|
|
3
3
|
// readWorkItemsForScope(scope) -> [wi, wi, ...]
|
|
4
4
|
// applyWorkItemsMutation(scope, fn) -> runs fn against the array,
|
|
@@ -22,7 +22,7 @@ function _parseRow(row) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function readWorkItemsForScope(scope) {
|
|
25
|
-
const { getDb } = require('
|
|
25
|
+
const { getDb } = require('../db');
|
|
26
26
|
const db = getDb();
|
|
27
27
|
|
|
28
28
|
const rows = db.prepare(`
|
|
@@ -43,16 +43,24 @@ function readWorkItemsForScope(scope) {
|
|
|
43
43
|
// needs to surface central + every project's items in a single shot,
|
|
44
44
|
// tagged with their source scope.
|
|
45
45
|
function readAllWorkItems() {
|
|
46
|
-
const { getDb } = require('
|
|
46
|
+
const { getDb } = require('../db');
|
|
47
47
|
const db = getDb();
|
|
48
48
|
|
|
49
|
-
const rows = db.prepare('SELECT data, scope FROM work_items WHERE archived = 0 ORDER BY rowid').all();
|
|
49
|
+
const rows = db.prepare('SELECT data, scope, updated_at FROM work_items WHERE archived = 0 ORDER BY rowid').all();
|
|
50
50
|
const out = [];
|
|
51
51
|
for (const row of rows) {
|
|
52
52
|
const wi = _parseRow(row);
|
|
53
53
|
if (!wi) continue;
|
|
54
54
|
// Preserve the legacy `_source` convention from queries.getWorkItems.
|
|
55
55
|
wi._source = row.scope;
|
|
56
|
+
// Surface the authoritative last-mutation time (updated_at column, ms epoch)
|
|
57
|
+
// as an ISO `updatedAt` so /api/work-items consumers can sort on real
|
|
58
|
+
// last-updated rather than faking it from created/dispatched_at. The column
|
|
59
|
+
// wins over any stale in-blob value. (W-ms7kutzv003q17ad)
|
|
60
|
+
if (row.updated_at != null) {
|
|
61
|
+
const ms = Number(row.updated_at);
|
|
62
|
+
if (Number.isFinite(ms)) wi.updatedAt = new Date(ms).toISOString();
|
|
63
|
+
}
|
|
56
64
|
out.push(wi);
|
|
57
65
|
}
|
|
58
66
|
return out;
|
|
@@ -136,7 +144,7 @@ function _applyWorkItemsDiff(db, diff) {
|
|
|
136
144
|
// result — the post-mutation array (legacy return shape of
|
|
137
145
|
// mutateJsonFileLocked)
|
|
138
146
|
function applyWorkItemsMutation(scope, mutator, opts = {}) {
|
|
139
|
-
const { getDb, withTransaction } = require('
|
|
147
|
+
const { getDb, withTransaction } = require('../db');
|
|
140
148
|
const db = getDb();
|
|
141
149
|
|
|
142
150
|
return withTransaction(db, () => {
|
|
@@ -170,17 +178,17 @@ function applyWorkItemsMutation(scope, mutator, opts = {}) {
|
|
|
170
178
|
// applyWorkItemsMutation's callers (see shared.js mutateWorkItems).
|
|
171
179
|
function dropScope(scope) {
|
|
172
180
|
try {
|
|
173
|
-
const { getDb } = require('
|
|
181
|
+
const { getDb } = require('../db');
|
|
174
182
|
const result = getDb().prepare('DELETE FROM work_items WHERE scope = ?').run(scope);
|
|
175
183
|
if (result && result.changes > 0) {
|
|
176
|
-
try { require('
|
|
177
|
-
try { require('
|
|
184
|
+
try { require('../persistence/db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
185
|
+
try { require('../core/queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
178
186
|
}
|
|
179
187
|
} catch { /* db unavailable */ }
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
function readArchivedWorkItemsForScope(scope) {
|
|
183
|
-
const rows = require('
|
|
191
|
+
const rows = require('../db').getDb().prepare(`
|
|
184
192
|
SELECT data FROM work_items
|
|
185
193
|
WHERE scope = ? AND archived = 1
|
|
186
194
|
ORDER BY updated_at DESC, rowid DESC
|
|
@@ -194,7 +202,7 @@ function readArchivedWorkItemsForScope(scope) {
|
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
function archiveWorkItem(scope, id, opts = {}) {
|
|
197
|
-
const { getDb, withTransaction } = require('
|
|
205
|
+
const { getDb, withTransaction } = require('../db');
|
|
198
206
|
const db = getDb();
|
|
199
207
|
const archived = withTransaction(db, () => {
|
|
200
208
|
const row = db.prepare('SELECT data FROM work_items WHERE scope=? AND id=? AND archived=0').get(scope, String(id));
|
|
@@ -219,14 +227,14 @@ function archiveWorkItem(scope, id, opts = {}) {
|
|
|
219
227
|
return item;
|
|
220
228
|
});
|
|
221
229
|
if (archived) {
|
|
222
|
-
try { require('
|
|
223
|
-
try { require('
|
|
230
|
+
try { require('../persistence/db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
231
|
+
try { require('../core/queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
224
232
|
}
|
|
225
233
|
return archived;
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
function replaceArchivedWorkItems(scope, items) {
|
|
229
|
-
const { getDb, withTransaction } = require('
|
|
237
|
+
const { getDb, withTransaction } = require('../db');
|
|
230
238
|
const db = getDb();
|
|
231
239
|
const count = withTransaction(db, () => {
|
|
232
240
|
db.prepare('DELETE FROM work_items WHERE scope=? AND archived=1').run(scope);
|
|
@@ -257,13 +265,13 @@ function replaceArchivedWorkItems(scope, items) {
|
|
|
257
265
|
}
|
|
258
266
|
return inserted;
|
|
259
267
|
});
|
|
260
|
-
try { require('
|
|
261
|
-
try { require('
|
|
268
|
+
try { require('../persistence/db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
269
|
+
try { require('../core/queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
262
270
|
return count;
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
function moveWorkItem(sourceScope, targetScope, id, item) {
|
|
266
|
-
const { getDb, withTransaction } = require('
|
|
274
|
+
const { getDb, withTransaction } = require('../db');
|
|
267
275
|
const db = getDb();
|
|
268
276
|
const moved = withTransaction(db, () => {
|
|
269
277
|
const source = db.prepare('SELECT 1 FROM work_items WHERE scope=? AND id=? AND archived=0')
|
|
@@ -292,15 +300,15 @@ function moveWorkItem(sourceScope, targetScope, id, item) {
|
|
|
292
300
|
return Number(result.changes) > 0;
|
|
293
301
|
});
|
|
294
302
|
if (moved) {
|
|
295
|
-
try { require('
|
|
296
|
-
try { require('
|
|
303
|
+
try { require('../persistence/db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
304
|
+
try { require('../core/queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
297
305
|
}
|
|
298
306
|
return moved;
|
|
299
307
|
}
|
|
300
308
|
|
|
301
309
|
function moveWorkItemWithUpdate(sourceScope, targetScope, id, updater) {
|
|
302
310
|
if (typeof updater !== 'function') throw new TypeError('moveWorkItemWithUpdate requires an updater');
|
|
303
|
-
const { getDb, withTransaction } = require('
|
|
311
|
+
const { getDb, withTransaction } = require('../db');
|
|
304
312
|
const db = getDb();
|
|
305
313
|
const outcome = withTransaction(db, () => {
|
|
306
314
|
const source = db.prepare(
|
|
@@ -340,8 +348,8 @@ function moveWorkItemWithUpdate(sourceScope, targetScope, id, updater) {
|
|
|
340
348
|
return { moved: true, reason: null, item: updated };
|
|
341
349
|
});
|
|
342
350
|
if (outcome.moved) {
|
|
343
|
-
try { require('
|
|
344
|
-
try { require('
|
|
351
|
+
try { require('../persistence/db-events').emitStateEvent('work_items'); } catch { /* optional */ }
|
|
352
|
+
try { require('../core/queries').invalidateWorkItemsCache(); } catch { /* queries not loaded */ }
|
|
345
353
|
}
|
|
346
354
|
return outcome;
|
|
347
355
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/keep-process-sweep.js -- opt-in `meta.keep_processes` reaper.
|
|
2
|
+
* engine/processes/keep-process-sweep.js -- opt-in `meta.keep_processes` reaper.
|
|
3
3
|
*
|
|
4
4
|
* Walks agents/<id>/keep-pids.json files and, for each:
|
|
5
5
|
* - Validates the file (bounded JSON shape, see validateKeepPidsRecord).
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
const fs = require('fs');
|
|
21
21
|
const path = require('path');
|
|
22
|
-
const shared = require('
|
|
23
|
-
const queries = require('
|
|
22
|
+
const shared = require('../core/shared');
|
|
23
|
+
const queries = require('../core/queries');
|
|
24
24
|
const processUtils = require('./process-utils');
|
|
25
25
|
|
|
26
26
|
const { log, ENGINE_DEFAULTS } = shared;
|
|
@@ -262,14 +262,16 @@ function readKeepPidsFile(agentId, opts) {
|
|
|
262
262
|
let raw;
|
|
263
263
|
try { raw = fs.readFileSync(filePath, 'utf8'); }
|
|
264
264
|
catch (_e) { return null; }
|
|
265
|
+
let mtimeMs = 0;
|
|
266
|
+
try { mtimeMs = fs.statSync(filePath).mtimeMs; } catch {}
|
|
265
267
|
let parsed = null;
|
|
266
268
|
try { parsed = JSON.parse(raw); }
|
|
267
269
|
catch (e) {
|
|
268
|
-
return { agentId: agentId, filePath: filePath, valid: false, reason: 'json-parse: ' + e.message, parsed: null };
|
|
270
|
+
return { agentId: agentId, filePath: filePath, mtimeMs, valid: false, reason: 'json-parse: ' + e.message, parsed: null };
|
|
269
271
|
}
|
|
270
272
|
const v = validateKeepPidsRecord(parsed, opts);
|
|
271
|
-
if (!v.ok) return { agentId: agentId, filePath: filePath, valid: false, reason: v.reason, parsed: parsed };
|
|
272
|
-
return { agentId: agentId, filePath: filePath, valid: true, value: v.value, parsed: parsed };
|
|
273
|
+
if (!v.ok) return { agentId: agentId, filePath: filePath, mtimeMs, valid: false, reason: v.reason, parsed: parsed };
|
|
274
|
+
return { agentId: agentId, filePath: filePath, mtimeMs, valid: true, value: v.value, parsed: parsed };
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
function listAllKeepPidsFiles(opts) {
|
|
@@ -305,12 +307,28 @@ function _audit(eventType, payload) {
|
|
|
305
307
|
} catch (_e) { /* logging best-effort */ }
|
|
306
308
|
}
|
|
307
309
|
|
|
310
|
+
function _identityMatchesDeclaration(rec, pid, opts) {
|
|
311
|
+
const getIdentity = typeof opts.getIdentity === 'function'
|
|
312
|
+
? opts.getIdentity
|
|
313
|
+
: getKeepProcessIdentity;
|
|
314
|
+
let identity = null;
|
|
315
|
+
try { identity = getIdentity(pid, opts); } catch {}
|
|
316
|
+
return isKeepProcessIdentityCompatible(identity, rec.mtimeMs);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function _withIdentitySnapshot(opts) {
|
|
320
|
+
if (typeof opts.getIdentity === 'function' || Array.isArray(opts.processes)) return opts;
|
|
321
|
+
return { ...opts, processes: processUtils.listAllProcesses() };
|
|
322
|
+
}
|
|
323
|
+
|
|
308
324
|
function sweepKeepProcesses(opts) {
|
|
309
325
|
opts = opts || {};
|
|
310
326
|
const now = Number.isFinite(opts.now) ? opts.now : Date.now();
|
|
311
327
|
const killOne = typeof opts.killOne === 'function'
|
|
312
328
|
? opts.killOne
|
|
313
|
-
: function (pid) {
|
|
329
|
+
: function (pid, proof) {
|
|
330
|
+
return shared.terminateProcess('engine.keep-process.ttl', pid, proof);
|
|
331
|
+
};
|
|
314
332
|
const stats = { scanned: 0, expiredFiles: 0, killedPids: 0, deadFiles: 0, malformed: 0 };
|
|
315
333
|
// W-mp6k7ywi000fa33c — sweep ignores workdir validation so TTL/dead-PID
|
|
316
334
|
// cleanup still drains files whose cwd no longer exists (e.g., the agent
|
|
@@ -318,6 +336,7 @@ function sweepKeepProcesses(opts) {
|
|
|
318
336
|
// getActiveAnchorPids*) keep the default-on validation so invalid records
|
|
319
337
|
// are not honored as anchors.
|
|
320
338
|
const records = listAllKeepPidsFiles({ now: now, requireGitWorkdir: false });
|
|
339
|
+
if (records.length > 0) opts = _withIdentitySnapshot(opts);
|
|
321
340
|
for (const rec of records) {
|
|
322
341
|
stats.scanned++;
|
|
323
342
|
if (!rec.valid) {
|
|
@@ -328,20 +347,30 @@ function sweepKeepProcesses(opts) {
|
|
|
328
347
|
const value = rec.value;
|
|
329
348
|
const filePath = rec.filePath;
|
|
330
349
|
const agentId = rec.agentId;
|
|
350
|
+
const killDeclaredPid = (pid, reason) => {
|
|
351
|
+
if (!_identityMatchesDeclaration(rec, pid, opts)) {
|
|
352
|
+
_audit('identity-mismatch-skip', { agentId, pid, reason, wi_id: value.wi_id });
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return killOne(pid, {
|
|
356
|
+
recordedAtMs: rec.mtimeMs,
|
|
357
|
+
skewMs: KEEP_PROCESS_IDENTITY_CLOCK_SKEW_MS,
|
|
358
|
+
});
|
|
359
|
+
};
|
|
331
360
|
const executionContext = validateKeepProcessExecutionContext(value, opts);
|
|
332
361
|
if (!executionContext.ok) {
|
|
333
362
|
for (const pid of value.pids) {
|
|
334
|
-
|
|
335
|
-
_audit('unsafe-context-kill', { agentId, pid, reason: executionContext.reason, wi_id: value.wi_id });
|
|
363
|
+
_audit('unsafe-context-skip', { agentId, pid, reason: executionContext.reason, wi_id: value.wi_id });
|
|
336
364
|
}
|
|
337
365
|
try { fs.unlinkSync(filePath); } catch {}
|
|
338
|
-
log('warn', 'keep-processes: removed unsafe execution context
|
|
366
|
+
log('warn', 'keep-processes: removed unsafe execution context without trusting its PIDs for ' +
|
|
367
|
+
agentId + ' — ' + executionContext.reason);
|
|
339
368
|
continue;
|
|
340
369
|
}
|
|
341
370
|
if (value.expiresAtMs < now) {
|
|
342
371
|
let killed = 0;
|
|
343
372
|
for (const pid of value.pids) {
|
|
344
|
-
const ok =
|
|
373
|
+
const ok = killDeclaredPid(pid, 'ttl_expired');
|
|
345
374
|
if (ok) killed++;
|
|
346
375
|
_audit('ttl-expired-kill', { agentId: agentId, pid: pid, reason: 'ttl_expired', wi_id: value.wi_id });
|
|
347
376
|
}
|
|
@@ -350,7 +379,8 @@ function sweepKeepProcesses(opts) {
|
|
|
350
379
|
try { fs.unlinkSync(filePath); } catch (_e) { /* gone already */ }
|
|
351
380
|
continue;
|
|
352
381
|
}
|
|
353
|
-
const stillAlive = alivePids(value.pids, opts)
|
|
382
|
+
const stillAlive = alivePids(value.pids, opts)
|
|
383
|
+
.filter(pid => _identityMatchesDeclaration(rec, pid, opts));
|
|
354
384
|
if (stillAlive.length === 0) {
|
|
355
385
|
stats.deadFiles++;
|
|
356
386
|
try { fs.unlinkSync(filePath); } catch (_e) { /* gone already */ }
|
|
@@ -364,11 +394,15 @@ function getActiveAnchorPids(opts) {
|
|
|
364
394
|
opts = opts || {};
|
|
365
395
|
const now = Number.isFinite(opts.now) ? opts.now : Date.now();
|
|
366
396
|
const out = new Set();
|
|
367
|
-
|
|
397
|
+
const records = listAllKeepPidsFiles({ now: now });
|
|
398
|
+
if (records.length > 0) opts = _withIdentitySnapshot(opts);
|
|
399
|
+
for (const rec of records) {
|
|
368
400
|
if (!rec.valid) continue;
|
|
369
401
|
if (rec.value.expiresAtMs < now) continue;
|
|
370
402
|
if (!validateKeepProcessExecutionContext(rec.value, opts).ok) continue;
|
|
371
|
-
for (const pid of rec.value.pids)
|
|
403
|
+
for (const pid of rec.value.pids) {
|
|
404
|
+
if (_identityMatchesDeclaration(rec, pid, opts)) out.add(pid);
|
|
405
|
+
}
|
|
372
406
|
}
|
|
373
407
|
return out;
|
|
374
408
|
}
|
|
@@ -388,7 +422,10 @@ function getActiveAnchorPidsForAgent(agentId, opts) {
|
|
|
388
422
|
if (rec.value.expiresAtMs < now) return { pids: out, record: rec.value, reason: 'expired' };
|
|
389
423
|
const executionContext = validateKeepProcessExecutionContext(rec.value, opts);
|
|
390
424
|
if (!executionContext.ok) return { pids: out, record: rec.value, reason: executionContext.reason };
|
|
391
|
-
|
|
425
|
+
opts = _withIdentitySnapshot(opts);
|
|
426
|
+
for (const pid of rec.value.pids) {
|
|
427
|
+
if (_identityMatchesDeclaration(rec, pid, opts)) out.add(pid);
|
|
428
|
+
}
|
|
392
429
|
return { pids: out, record: rec.value };
|
|
393
430
|
}
|
|
394
431
|
|
|
@@ -396,10 +433,13 @@ function getActiveKeepProcessCwds(opts) {
|
|
|
396
433
|
opts = opts || {};
|
|
397
434
|
const now = Number.isFinite(opts.now) ? opts.now : Date.now();
|
|
398
435
|
const out = [];
|
|
399
|
-
|
|
436
|
+
const records = listAllKeepPidsFiles({ now });
|
|
437
|
+
if (records.length > 0) opts = _withIdentitySnapshot(opts);
|
|
438
|
+
for (const rec of records) {
|
|
400
439
|
if (!rec.valid || rec.value.expiresAtMs < now || !rec.value.cwd) continue;
|
|
401
440
|
if (!validateKeepProcessExecutionContext(rec.value, opts).ok) continue;
|
|
402
|
-
if (alivePids(rec.value.pids, opts)
|
|
441
|
+
if (!alivePids(rec.value.pids, opts)
|
|
442
|
+
.some(pid => _identityMatchesDeclaration(rec, pid, opts))) continue;
|
|
403
443
|
try { out.push(shared.realPathForComparison(rec.value.cwd)); }
|
|
404
444
|
catch { /* malformed/unreachable cwd contributes no anchor */ }
|
|
405
445
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
|
+
const { terminateProcess } = require('./process-utils');
|
|
5
6
|
|
|
6
7
|
const [cmd, ...args] = process.argv.slice(2);
|
|
7
8
|
if (!cmd) {
|
|
@@ -37,10 +38,9 @@ if (!cmd) {
|
|
|
37
38
|
console.error('[managed-spawn-launcher] target exited (code='
|
|
38
39
|
+ (code == null ? 'null' : code) + ', signal=' + (signal || 'null') + ')');
|
|
39
40
|
if (signal) {
|
|
40
|
-
|
|
41
|
-
process.kill(process.pid, signal);
|
|
41
|
+
if (terminateProcess('engine.managed-launcher.self-signal', { pid: process.pid }, { signal })) {
|
|
42
42
|
return;
|
|
43
|
-
}
|
|
43
|
+
} else {
|
|
44
44
|
process.exitCode = 1;
|
|
45
45
|
return;
|
|
46
46
|
}
|