@yemi33/minions 0.1.2424 → 0.1.2426
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/install-internal-minions.js +1209 -0
- package/bin/minions.js +75 -9
- package/dashboard/docs/typography.md +27 -12
- package/dashboard/js/command-center.js +13 -11
- package/dashboard/js/live-stream.js +1 -1
- package/dashboard/js/memory-search.js +388 -43
- package/dashboard/js/modal-qa.js +5 -5
- package/dashboard/js/qa.js +39 -26
- package/dashboard/js/refresh.js +37 -18
- package/dashboard/js/render-agents.js +14 -18
- package/dashboard/js/render-dispatch.js +14 -74
- package/dashboard/js/render-inbox.js +3 -3
- package/dashboard/js/render-meetings.js +8 -8
- package/dashboard/js/render-other.js +45 -20
- package/dashboard/js/render-pipelines.js +6 -6
- package/dashboard/js/render-plans.js +14 -14
- package/dashboard/js/render-prd.js +46 -46
- package/dashboard/js/render-prs.js +9 -65
- package/dashboard/js/render-schedules.js +5 -5
- package/dashboard/js/render-skills.js +6 -6
- package/dashboard/js/render-utils.js +4 -4
- package/dashboard/js/render-watches.js +4 -16
- package/dashboard/js/render-work-items.js +95 -107
- package/dashboard/js/settings.js +175 -74
- package/dashboard/layout.html +3 -2
- package/dashboard/pages/inbox.html +1 -1
- package/dashboard/pages/work.html +1 -1
- package/dashboard/shared/model-display.js +9 -0
- package/dashboard/shared/watches-source.js +43 -0
- package/dashboard/shared/welcome-popup.js +259 -0
- package/dashboard/slim/body.html +6 -6
- package/dashboard/slim/js/chat.js +4 -2
- package/dashboard/slim/js/history.js +10 -3
- package/dashboard/slim/js/modals-tiles.js +85 -16
- package/dashboard/slim/js/status.js +12 -16
- package/dashboard/slim/styles.css +24 -4
- package/dashboard/styles.css +62 -27
- package/dashboard-build.js +8 -2
- package/dashboard.js +3854 -2514
- package/docs/README.md +7 -5
- package/docs/api-errors.md +144 -0
- package/docs/auto-discovery.md +83 -61
- package/docs/capture-demos.js +173 -30
- package/docs/command-center.md +5 -1
- package/docs/completion-reports.md +127 -11
- package/docs/constants.md +15 -3
- package/docs/constellation-style-telemetry.md +2 -2
- package/docs/copilot-cli-schema.md +32 -22
- package/docs/cross-repo-plans.md +30 -30
- package/docs/demo/memory-system.html +1 -1
- package/docs/deprecated.json +132 -18
- package/docs/diagnostics-memory.md +8 -4
- package/docs/human-vs-automated.md +2 -2
- package/docs/index.html +9 -2
- package/docs/internal-install.md +212 -0
- package/docs/kb-pr3223-cascade-archiving.md +16 -0
- package/docs/kb-pr696-merge-conflict-docs.md +23 -0
- package/docs/kb-sweep.md +2 -2
- package/docs/keep-processes.md +8 -1
- package/docs/live-checkout-mode.md +1 -1
- package/docs/managed-spawn.md +21 -3
- package/docs/named-agents.md +3 -2
- package/docs/onboarding.md +26 -0
- package/docs/plan-lifecycle.md +32 -32
- 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-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/qa-runbook-lifecycle.md +67 -51
- package/docs/qa-runbooks.md +2 -2
- package/docs/runtime-adapters.md +101 -50
- package/docs/security.md +28 -0
- package/docs/self-improvement.md +47 -13
- package/docs/skills.md +29 -0
- package/docs/slim-ux/concepts.md +2 -2
- package/docs/specs/agent-configurability.md +43 -41
- package/docs/specs/agent-rename.md +13 -13
- package/docs/team-memory.md +283 -14
- package/docs/tutorials/01-install-and-connect.md +23 -0
- package/docs/tutorials/03-plan-driven-feature.md +3 -3
- package/docs/tutorials/04-runtimes-and-harness.md +18 -21
- package/docs/tutorials/08-operations-and-recovery.md +3 -2
- package/docs/tutorials/README.md +4 -3
- package/docs/visual-evidence-ci.md +103 -0
- package/docs/watches.md +35 -30
- package/docs/worktree-lifecycle.md +151 -13
- package/engine/acp-transport.js +1 -0
- package/engine/ado.js +80 -33
- package/engine/agent-api-validation.js +571 -0
- package/engine/agent-worker-pool.js +7 -6
- package/engine/api-contracts/agent-content.js +761 -0
- package/engine/api-contracts/cc-ops.js +219 -0
- package/engine/api-contracts/config-runtime.js +419 -0
- package/engine/api-contracts/core.js +138 -0
- package/engine/api-contracts/index.js +506 -0
- package/engine/api-contracts/orchestration.js +602 -0
- package/engine/api-contracts/pull-requests.js +323 -0
- package/engine/api-contracts/qa-process.js +764 -0
- package/engine/api-contracts/work-plan-prd.js +494 -0
- package/engine/api-validation.js +543 -0
- package/engine/cc-api-validation.js +586 -0
- package/engine/cc-worker-pool.js +9 -1
- package/engine/cleanup.js +393 -271
- package/engine/cli.js +65 -7
- package/engine/comment-classifier.js +70 -5
- package/engine/consolidation.js +123 -5
- package/engine/content-api-validation.js +606 -0
- package/engine/create-pr-worktree.js +22 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
- package/engine/dispatch.js +33 -0
- package/engine/distribution.js +189 -0
- package/engine/execution-model.js +59 -0
- package/engine/features.js +12 -0
- package/engine/github.js +67 -25
- package/engine/harness-context.js +313 -0
- package/engine/inbox-store.js +1 -1
- package/engine/kb-sweep.js +161 -38
- package/engine/keep-process-sweep.js +43 -3
- package/engine/lifecycle.js +1066 -165
- package/engine/llm.js +4 -0
- package/engine/managed-spawn.js +463 -48
- package/engine/meeting.js +197 -34
- package/engine/memory-retrieval.js +280 -0
- package/engine/memory-store.js +821 -38
- package/engine/model-discovery.js +17 -3
- package/engine/pipeline.js +802 -14
- package/engine/plan-prd-validation.js +745 -0
- package/engine/playbook.js +206 -17
- package/engine/pooled-agent-process.js +6 -1
- package/engine/pr-action.js +1 -1
- package/engine/pr-issue-validation.js +653 -0
- package/engine/pr-resolve.js +75 -13
- package/engine/prd-store.js +39 -6
- package/engine/preflight.js +7 -2
- package/engine/process-utils.js +79 -31
- package/engine/projects.js +198 -12
- package/engine/promotion.js +371 -0
- package/engine/qa-from-prd.js +3 -0
- package/engine/qa-process-validation.js +572 -0
- package/engine/qa-runbooks.js +41 -37
- package/engine/qa-runners/maestro.js +11 -7
- package/engine/qa-runners/playwright.js +37 -14
- package/engine/qa-runners.js +9 -10
- package/engine/qa-runs.js +124 -11
- package/engine/qa-sessions.js +139 -40
- package/engine/quarantine-refs.js +81 -11
- package/engine/queries.js +163 -9
- package/engine/review-learning-backfill.js +482 -0
- package/engine/review-learning.js +1236 -0
- package/engine/runtimes/claude.js +60 -2
- package/engine/runtimes/codex.js +57 -16
- package/engine/runtimes/contract.js +21 -0
- package/engine/runtimes/copilot.js +51 -3
- package/engine/runtimes/index.js +1 -0
- package/engine/scheduler.js +201 -32
- package/engine/settings-validation.js +909 -0
- package/engine/shared.js +705 -208
- package/engine/spawn-agent.js +48 -21
- package/engine/steering-constraints.js +31 -0
- package/engine/steering-store.js +10 -2
- package/engine/steering.js +14 -2
- package/engine/timeout.js +404 -111
- package/engine/untrusted-fence.js +24 -10
- package/engine/watch-actions.js +7 -2
- package/engine/watches.js +353 -51
- package/engine/work-item-validation.js +561 -0
- package/engine/work-items-store.js +61 -1
- package/engine/worktree-gc.js +656 -37
- package/engine/worktree-preflight.js +154 -0
- package/engine.js +2068 -342
- package/package.json +1 -1
- package/playbooks/_pr-description-audit.md +88 -41
- package/playbooks/build-fix-complex.md +3 -3
- package/playbooks/docs.md +2 -2
- package/playbooks/fix.md +18 -18
- package/playbooks/implement.md +18 -18
- package/playbooks/qa-session-draft.md +10 -12
- package/playbooks/qa-session-execute.md +16 -12
- package/playbooks/qa-validate.md +8 -7
- package/playbooks/shared-rules.md +45 -0
- package/playbooks/test.md +4 -0
- package/prompts/cc-system.md +1 -1
- package/skills/check-self-authored-review-comment/SKILL.md +2 -2
package/engine/pr-resolve.js
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* Two responsibilities:
|
|
9
9
|
* 1. `normalizePrRef(input)` — accept any GitHub or ADO PR URL OR the canonical
|
|
10
10
|
* `github:owner/repo#N` / `ado:org/proj/repo#N` id and return a normalized
|
|
11
|
-
* `{ host, slug, repo, number, ... }` struct.
|
|
12
|
-
*
|
|
11
|
+
* `{ host, slug, repo, number, ... }` struct. URL forms are parsed from the
|
|
12
|
+
* validated hostname/path; canonical ids reuse `shared.parseCanonicalPrId`.
|
|
13
13
|
* 2. `fetchPrPayload(refOrInput)` — fetch `{ diff, title, body, comments, author }`
|
|
14
14
|
* for that PR WITHOUT a configured project or clone, via `gh` (GitHub) and the
|
|
15
15
|
* ADO REST API (`_apis/git/...`).
|
|
@@ -38,6 +38,38 @@ const ADO_API_VERSION = '7.1';
|
|
|
38
38
|
|
|
39
39
|
// ── Normalization ───────────────────────────────────────────────────────────
|
|
40
40
|
|
|
41
|
+
function _parsePrUrl(parsedUrl) {
|
|
42
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
43
|
+
let match;
|
|
44
|
+
if (hostname === 'github.com') {
|
|
45
|
+
match = parsedUrl.pathname.match(/^\/([^/]+)\/([^/]+)\/pull\/([0-9]+)(?:\/|$)/i);
|
|
46
|
+
if (!match) return null;
|
|
47
|
+
return {
|
|
48
|
+
scope: `github:${decodeURIComponent(match[1])}/${decodeURIComponent(match[2])}`,
|
|
49
|
+
prNumber: match[3],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (hostname === 'dev.azure.com') {
|
|
53
|
+
match = parsedUrl.pathname.match(/^\/([^/]+)\/([^/]+)\/_git\/([^/]+)\/pullrequest\/([0-9]+)(?:\/|$)/i);
|
|
54
|
+
if (!match) return null;
|
|
55
|
+
return {
|
|
56
|
+
scope: `ado:${decodeURIComponent(match[1])}/${decodeURIComponent(match[2])}/${decodeURIComponent(match[3])}`,
|
|
57
|
+
prNumber: match[4],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (hostname.endsWith('.visualstudio.com')) {
|
|
61
|
+
const org = hostname.slice(0, -'.visualstudio.com'.length);
|
|
62
|
+
if (!org || org.includes('.')) return null;
|
|
63
|
+
match = parsedUrl.pathname.match(/^\/(?:DefaultCollection\/)?([^/]+)\/_git\/([^/]+)\/pullrequest\/([0-9]+)(?:\/|$)/i);
|
|
64
|
+
if (!match) return null;
|
|
65
|
+
return {
|
|
66
|
+
scope: `ado:${org}/${decodeURIComponent(match[1])}/${decodeURIComponent(match[2])}`,
|
|
67
|
+
prNumber: match[3],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/**
|
|
42
74
|
* Turn a `{ scope, prNumber }` pair (as returned by shared.parsePrUrl /
|
|
43
75
|
* parseCanonicalPrId) into a normalized projectless PR ref. Returns null when the
|
|
@@ -45,21 +77,37 @@ const ADO_API_VERSION = '7.1';
|
|
|
45
77
|
*/
|
|
46
78
|
function _normalizeFromScope(scope, number) {
|
|
47
79
|
if (!scope || typeof scope !== 'string') return null;
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
let num;
|
|
81
|
+
try { num = shared.validatePrNum(number); }
|
|
82
|
+
catch { return null; }
|
|
50
83
|
|
|
51
84
|
const sepIdx = scope.indexOf(':');
|
|
52
85
|
if (sepIdx <= 0) return null;
|
|
53
86
|
const host = scope.slice(0, sepIdx).toLowerCase();
|
|
54
87
|
const slug = scope.slice(sepIdx + 1);
|
|
55
|
-
const
|
|
88
|
+
const expectedSegments = { github: 2, ado: 3 }[host] || 0;
|
|
89
|
+
const rawSegments = slug.split('/');
|
|
90
|
+
if (!expectedSegments || rawSegments.length !== expectedSegments || rawSegments.some(segment => !segment.trim())) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
let normalizedScope;
|
|
94
|
+
try {
|
|
95
|
+
[normalizedScope] = shared.validateAdditionalRepoScopes([`${host}:${slug}`]) || [];
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (!normalizedScope) return null;
|
|
100
|
+
const normalizedSlug = normalizedScope.slice(host.length + 1);
|
|
101
|
+
const segs = normalizedSlug.split('/');
|
|
102
|
+
if (segs.some(segment => segment === '.' || segment === '..')) return null;
|
|
56
103
|
|
|
57
104
|
if (host === 'github') {
|
|
58
|
-
if (segs.length !== 2) return null;
|
|
59
105
|
const [owner, repo] = segs;
|
|
106
|
+
try { shared.validateGhSlug(`${owner}/${repo}`); }
|
|
107
|
+
catch { return null; }
|
|
60
108
|
return {
|
|
61
109
|
host: 'github',
|
|
62
|
-
scope,
|
|
110
|
+
scope: normalizedScope,
|
|
63
111
|
slug: `${owner}/${repo}`,
|
|
64
112
|
owner,
|
|
65
113
|
repo,
|
|
@@ -68,11 +116,13 @@ function _normalizeFromScope(scope, number) {
|
|
|
68
116
|
};
|
|
69
117
|
}
|
|
70
118
|
if (host === 'ado') {
|
|
71
|
-
if (segs
|
|
119
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/i.test(segs[0])
|
|
120
|
+
|| segs.some(segment => segment.length > 128
|
|
121
|
+
|| /[\u0000-\u001f\u007f#?\\]/.test(segment))) return null;
|
|
72
122
|
const [org, project, repo] = segs;
|
|
73
123
|
return {
|
|
74
124
|
host: 'ado',
|
|
75
|
-
scope,
|
|
125
|
+
scope: normalizedScope,
|
|
76
126
|
slug: `${org}/${project}/${repo}`,
|
|
77
127
|
org,
|
|
78
128
|
project,
|
|
@@ -95,9 +145,21 @@ function normalizePrRef(input) {
|
|
|
95
145
|
return _normalizeFromScope(input.scope || `${input.host}:${input.slug}`, input.number);
|
|
96
146
|
}
|
|
97
147
|
const text = String(input == null ? '' : input).trim();
|
|
98
|
-
if (!text) return null;
|
|
99
|
-
|
|
100
|
-
|
|
148
|
+
if (!text || text.length > 2048) return null;
|
|
149
|
+
let parsed = null;
|
|
150
|
+
try {
|
|
151
|
+
if (/^https?:\/\//i.test(text)) {
|
|
152
|
+
const parsedUrl = new URL(text);
|
|
153
|
+
if (!['http:', 'https:'].includes(parsedUrl.protocol)
|
|
154
|
+
|| parsedUrl.username
|
|
155
|
+
|| parsedUrl.password) return null;
|
|
156
|
+
parsed = _parsePrUrl(parsedUrl);
|
|
157
|
+
} else {
|
|
158
|
+
parsed = shared.parseCanonicalPrId(text);
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
101
163
|
if (!parsed) return null;
|
|
102
164
|
return _normalizeFromScope(parsed.scope, parsed.prNumber);
|
|
103
165
|
}
|
|
@@ -125,7 +187,7 @@ function resolvePrActionRef(input, PrActionError) {
|
|
|
125
187
|
: (typeof input === 'string' ? input.trim() : '');
|
|
126
188
|
if (!rawUrl) throw new PrActionError('url required');
|
|
127
189
|
const ref = normalizePrRef(rawUrl);
|
|
128
|
-
if (!ref) throw new PrActionError(
|
|
190
|
+
if (!ref) throw new PrActionError('unrecognized PR reference');
|
|
129
191
|
return ref;
|
|
130
192
|
}
|
|
131
193
|
|
package/engine/prd-store.js
CHANGED
|
@@ -10,7 +10,7 @@ function _toMs(v) {
|
|
|
10
10
|
}
|
|
11
11
|
function _bool01(v) { return v ? 1 : 0; }
|
|
12
12
|
|
|
13
|
-
function
|
|
13
|
+
function validatePrdFilename(filename) {
|
|
14
14
|
if (typeof filename !== 'string' || !filename.endsWith('.json')
|
|
15
15
|
|| filename.includes('/') || filename.includes('\\') || filename === '.json') {
|
|
16
16
|
throw new TypeError('PRD filename must be a basename ending in .json');
|
|
@@ -187,7 +187,7 @@ function _upsertPrd(db, filename, archived, prd, now) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
function writePrd(filename, prd, { archived = false } = {}) {
|
|
190
|
-
|
|
190
|
+
validatePrdFilename(filename);
|
|
191
191
|
if (!prd || typeof prd !== 'object' || Array.isArray(prd)) {
|
|
192
192
|
throw new TypeError('prd-store.writePrd requires a filename and object');
|
|
193
193
|
}
|
|
@@ -201,7 +201,7 @@ function writePrd(filename, prd, { archived = false } = {}) {
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
function createPrdUnique(baseFilename, prd) {
|
|
204
|
-
|
|
204
|
+
validatePrdFilename(baseFilename);
|
|
205
205
|
if (!prd || typeof prd !== 'object' || Array.isArray(prd)) {
|
|
206
206
|
throw new TypeError('prd-store.createPrdUnique requires a PRD object');
|
|
207
207
|
}
|
|
@@ -224,7 +224,7 @@ function createPrdUnique(baseFilename, prd) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
function mutatePrd(filename, mutator, { archived = false, defaultValue = {} } = {}) {
|
|
227
|
-
|
|
227
|
+
validatePrdFilename(filename);
|
|
228
228
|
if (typeof mutator !== 'function') {
|
|
229
229
|
throw new TypeError('prd-store.mutatePrd requires a filename and mutator');
|
|
230
230
|
}
|
|
@@ -249,7 +249,7 @@ function mutatePrd(filename, mutator, { archived = false, defaultValue = {} } =
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function deletePrd(filename, { archived = false } = {}) {
|
|
252
|
-
|
|
252
|
+
validatePrdFilename(filename);
|
|
253
253
|
const { getDb, withTransaction } = require('./db');
|
|
254
254
|
const db = getDb();
|
|
255
255
|
const removed = withTransaction(db, () => {
|
|
@@ -264,7 +264,7 @@ function deletePrd(filename, { archived = false } = {}) {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
function readPrd(filename, { archived = false } = {}) {
|
|
267
|
-
|
|
267
|
+
validatePrdFilename(filename);
|
|
268
268
|
const row = require('./db').getDb()
|
|
269
269
|
.prepare('SELECT data FROM prds WHERE filename=? AND archived=?')
|
|
270
270
|
.get(filename, archived ? 1 : 0);
|
|
@@ -300,6 +300,37 @@ function restorePrdFromArchive(filename) {
|
|
|
300
300
|
return restored;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
+
// Atomic inverse of restorePrdFromArchive, used only to compensate a failed
|
|
304
|
+
// legacy unarchive after its archived=1 row has already moved into the live
|
|
305
|
+
// bucket. Modern archives remain in place and do not call this helper.
|
|
306
|
+
function movePrdToArchive(filename, replacementPrd) {
|
|
307
|
+
validatePrdFilename(filename);
|
|
308
|
+
if (replacementPrd !== undefined
|
|
309
|
+
&& (!replacementPrd || typeof replacementPrd !== 'object' || Array.isArray(replacementPrd))) {
|
|
310
|
+
throw new TypeError('prd-store.movePrdToArchive replacement must be a PRD object');
|
|
311
|
+
}
|
|
312
|
+
const { getDb, withTransaction } = require('./db');
|
|
313
|
+
const db = getDb();
|
|
314
|
+
const archived = withTransaction(db, () => {
|
|
315
|
+
const row = db.prepare('SELECT id, data FROM prds WHERE filename=? AND archived=0').get(filename);
|
|
316
|
+
if (!row) return null;
|
|
317
|
+
const prd = replacementPrd === undefined
|
|
318
|
+
? _normalizePrdForStorage(JSON.parse(row.data))
|
|
319
|
+
: _normalizePrdForStorage(structuredClone(replacementPrd));
|
|
320
|
+
_upsertPrd(db, filename, 1, prd, Date.now());
|
|
321
|
+
_deletePrdRow(db, row.id);
|
|
322
|
+
return prd;
|
|
323
|
+
});
|
|
324
|
+
if (archived) {
|
|
325
|
+
require('./db-events').emitStateEvent('prds', {
|
|
326
|
+
filename,
|
|
327
|
+
archived: 1,
|
|
328
|
+
compensated: true,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return archived;
|
|
332
|
+
}
|
|
333
|
+
|
|
303
334
|
// Idempotent boot sweep for work items that predate PRD FK stamping.
|
|
304
335
|
// This is set-based, only touches NULL FKs, and prefers live PRDs when a
|
|
305
336
|
// basename exists in both live and archived buckets.
|
|
@@ -359,12 +390,14 @@ function listPrdRows() {
|
|
|
359
390
|
}
|
|
360
391
|
|
|
361
392
|
module.exports = {
|
|
393
|
+
validatePrdFilename,
|
|
362
394
|
writePrd,
|
|
363
395
|
createPrdUnique,
|
|
364
396
|
mutatePrd,
|
|
365
397
|
deletePrd,
|
|
366
398
|
readPrd,
|
|
367
399
|
restorePrdFromArchive,
|
|
400
|
+
movePrdToArchive,
|
|
368
401
|
backfillWorkItemPrdItemIds,
|
|
369
402
|
listPrdRows,
|
|
370
403
|
_upsertPrd, // exported for tests
|
package/engine/preflight.js
CHANGED
|
@@ -353,12 +353,17 @@ const _FEATURE_FLAG_DEFAULTS = {
|
|
|
353
353
|
* 2. `CC overrides` — only when `ccCli` or `ccModel` is set
|
|
354
354
|
* 3. `Active fleet flags` — only when any feature flag deviates from default
|
|
355
355
|
*/
|
|
356
|
-
function _fleetSummaryResults(config) {
|
|
356
|
+
function _fleetSummaryResults(config, { dispatch } = {}) {
|
|
357
357
|
const results = [];
|
|
358
358
|
if (!config || typeof config !== 'object') return results;
|
|
359
359
|
const engine = config.engine || {};
|
|
360
360
|
const defaultCli = engine.defaultCli ? String(engine.defaultCli) : 'copilot';
|
|
361
|
-
const
|
|
361
|
+
const display = require('./queries').resolveRuntimeModelDisplay({
|
|
362
|
+
runtime: defaultCli,
|
|
363
|
+
configuredModel: engine.defaultModel,
|
|
364
|
+
dispatch,
|
|
365
|
+
});
|
|
366
|
+
const defaultModel = display.model || '(runtime model not observed yet)';
|
|
362
367
|
results.push({ name: 'Fleet', ok: true, message: `defaultCli=${defaultCli} defaultModel=${defaultModel}` });
|
|
363
368
|
|
|
364
369
|
const ccBits = [];
|
package/engine/process-utils.js
CHANGED
|
@@ -24,12 +24,7 @@ const { execSync: _execSync, execFile: _execFile } = require('child_process');
|
|
|
24
24
|
function isPidAlive(pid) {
|
|
25
25
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
26
26
|
try { process.kill(pid, 0); return true; }
|
|
27
|
-
catch
|
|
28
|
-
// Signal 0 returning EPERM means the process exists but this caller cannot
|
|
29
|
-
// inspect it. Treating that as dead lets lock recovery delete a live
|
|
30
|
-
// holder's lock and breaks cross-process mutual exclusion.
|
|
31
|
-
return err && err.code === 'EPERM';
|
|
32
|
-
}
|
|
27
|
+
catch { return false; }
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
// ─── Cross-Platform Process Kill Helpers ─────────────────────────────────────
|
|
@@ -52,12 +47,13 @@ function unixChildPids(pid) {
|
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
function killUnixProcessTree(pid, signal, seen = new Set()) {
|
|
55
|
-
if (!Number.isInteger(pid) || pid <= 0 || seen.has(pid)) return;
|
|
50
|
+
if (!Number.isInteger(pid) || pid <= 0 || seen.has(pid)) return false;
|
|
56
51
|
seen.add(pid);
|
|
57
52
|
for (const childPid of unixChildPids(pid)) {
|
|
58
53
|
killUnixProcessTree(childPid, signal, seen);
|
|
59
54
|
}
|
|
60
|
-
try { process.kill(pid, signal);
|
|
55
|
+
try { process.kill(pid, signal); return true; }
|
|
56
|
+
catch { return false; }
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
function unrefTimer(timer) {
|
|
@@ -83,12 +79,16 @@ function killGracefully(proc, graceMs = 5000) {
|
|
|
83
79
|
|
|
84
80
|
function killImmediate(proc) {
|
|
85
81
|
const pid = normalizeKillPid(proc);
|
|
86
|
-
if (!pid) return;
|
|
82
|
+
if (!pid) return false;
|
|
87
83
|
if (process.platform === 'win32') {
|
|
88
|
-
try {
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
try {
|
|
85
|
+
_execSync(`taskkill /PID ${pid} /F /T`, { stdio: 'pipe', timeout: 3000, windowsHide: true });
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
91
90
|
}
|
|
91
|
+
return killUnixProcessTree(pid, 'SIGKILL');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Decide whether a Windows `tasklist /FI "PID eq <pid>" /NH` output proves the
|
|
@@ -289,7 +289,7 @@ function _winListProcesses() {
|
|
|
289
289
|
if (ps && ps.length) return ps;
|
|
290
290
|
try {
|
|
291
291
|
const out = _execSync(
|
|
292
|
-
'wmic process get Name,ParentProcessId,ProcessId
|
|
292
|
+
'wmic process get CreationDate,Name,ParentProcessId,ProcessId /format:csv',
|
|
293
293
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 4000, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }
|
|
294
294
|
);
|
|
295
295
|
return _parseWmicCsv(out);
|
|
@@ -299,10 +299,15 @@ function _winListProcesses() {
|
|
|
299
299
|
function _unixListProcesses() {
|
|
300
300
|
try {
|
|
301
301
|
const out = _execSync(
|
|
302
|
-
'ps -A -o pid=,ppid=,comm=',
|
|
303
|
-
{
|
|
302
|
+
'ps -A -o pid=,ppid=,lstart=,comm=',
|
|
303
|
+
{
|
|
304
|
+
encoding: 'utf8',
|
|
305
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
306
|
+
timeout: 5000,
|
|
307
|
+
env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
|
|
308
|
+
}
|
|
304
309
|
);
|
|
305
|
-
return
|
|
310
|
+
return _parseUnixProcessesWithStart(out);
|
|
306
311
|
} catch { return []; }
|
|
307
312
|
}
|
|
308
313
|
|
|
@@ -345,6 +350,28 @@ function _parseUnixProcessesWithStart(out) {
|
|
|
345
350
|
}).filter(Boolean);
|
|
346
351
|
}
|
|
347
352
|
|
|
353
|
+
function getProcessIdentity(pid, opts = {}) {
|
|
354
|
+
const value = Number(pid);
|
|
355
|
+
if (!Number.isInteger(value) || value <= 0) return null;
|
|
356
|
+
const processes = Array.isArray(opts.processes) ? opts.processes : listAllProcesses();
|
|
357
|
+
const match = processes.find(entry => Number(entry && entry.pid) === value);
|
|
358
|
+
const startedAt = Number(match && match.startedAt);
|
|
359
|
+
return startedAt > 0 ? { pid: value, startedAt } : null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function isProcessIdentityCompatible(identity, recordedAtMs, skewMs = 1000) {
|
|
363
|
+
return !!identity
|
|
364
|
+
&& Number.isFinite(recordedAtMs)
|
|
365
|
+
&& identity.startedAt <= recordedAtMs + skewMs;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function isSameProcessIdentity(left, right) {
|
|
369
|
+
return !!left
|
|
370
|
+
&& !!right
|
|
371
|
+
&& left.pid === right.pid
|
|
372
|
+
&& left.startedAt === right.startedAt;
|
|
373
|
+
}
|
|
374
|
+
|
|
348
375
|
async function _applyLinuxStartTicks(processes) {
|
|
349
376
|
if (process.platform !== 'linux') return processes;
|
|
350
377
|
await Promise.all(processes.map(async (entry) => {
|
|
@@ -408,12 +435,13 @@ async function listAllProcessesAsync() {
|
|
|
408
435
|
}
|
|
409
436
|
|
|
410
437
|
// W-mq6f2fe0000557fa — orphan-worktree GC: identify OS processes whose cwd
|
|
411
|
-
// is at or inside `dir`. Cross-platform
|
|
412
|
-
//
|
|
413
|
-
// escalation path and an unenriched note).
|
|
438
|
+
// is at or inside `dir`. Cross-platform and fail-open by default; callers that
|
|
439
|
+
// gate deletion may pass `throwOnFailure:true` to fail closed instead.
|
|
414
440
|
//
|
|
415
|
-
// Return shape: [{ pid, cmdline, startedAt, ageMs }, ...]
|
|
441
|
+
// Return shape: [{ pid, cwd, cmdline, startedAt, ageMs }, ...]
|
|
416
442
|
// - pid: numeric OS pid
|
|
443
|
+
// - cwd: matched current working directory when available (the legacy
|
|
444
|
+
// Windows command-line heuristic omits it)
|
|
417
445
|
// - cmdline: full command line as a single string (best-effort; possibly truncated)
|
|
418
446
|
// - startedAt: ms-since-epoch process start time (0 when unknown)
|
|
419
447
|
// - ageMs: Date.now() - startedAt (0 when startedAt is 0)
|
|
@@ -451,10 +479,13 @@ function findProcessesWithCwdInside(dir, opts = {}) {
|
|
|
451
479
|
}
|
|
452
480
|
|
|
453
481
|
if (process.platform === 'linux') {
|
|
454
|
-
return _findLinuxProcessesWithCwdInside(resolved, timeoutMs, now);
|
|
482
|
+
return _findLinuxProcessesWithCwdInside(resolved, timeoutMs, now, !!opts.throwOnFailure);
|
|
455
483
|
}
|
|
456
|
-
return _findMacProcessesWithCwdInside(resolved, timeoutMs, now);
|
|
457
|
-
} catch {
|
|
484
|
+
return _findMacProcessesWithCwdInside(resolved, timeoutMs, now, !!opts.throwOnFailure);
|
|
485
|
+
} catch (err) {
|
|
486
|
+
if (opts.throwOnFailure) throw err;
|
|
487
|
+
return [];
|
|
488
|
+
}
|
|
458
489
|
}
|
|
459
490
|
|
|
460
491
|
async function findProcessesWithCwdInsideAsync(dir, opts = {}) {
|
|
@@ -529,11 +560,14 @@ function _findWindowsProcessesWithCwdInside(resolved, timeoutMs, now) {
|
|
|
529
560
|
return out;
|
|
530
561
|
}
|
|
531
562
|
|
|
532
|
-
function _findLinuxProcessesWithCwdInside(resolved, _timeoutMs, now) {
|
|
563
|
+
function _findLinuxProcessesWithCwdInside(resolved, _timeoutMs, now, throwOnFailure = false) {
|
|
533
564
|
// /proc walk: scan numeric entries, readlink cwd, keep matches.
|
|
534
565
|
let entries;
|
|
535
566
|
try { entries = fs.readdirSync('/proc'); }
|
|
536
|
-
catch {
|
|
567
|
+
catch (err) {
|
|
568
|
+
if (throwOnFailure) throw err;
|
|
569
|
+
return [];
|
|
570
|
+
}
|
|
537
571
|
const prefix = resolved + path.sep;
|
|
538
572
|
const out = [];
|
|
539
573
|
for (const e of entries) {
|
|
@@ -556,6 +590,7 @@ function _findLinuxProcessesWithCwdInside(resolved, _timeoutMs, now) {
|
|
|
556
590
|
} catch { /* stat can fail on race */ }
|
|
557
591
|
out.push({
|
|
558
592
|
pid,
|
|
593
|
+
cwd,
|
|
559
594
|
cmdline,
|
|
560
595
|
startedAt,
|
|
561
596
|
ageMs: startedAt > 0 ? Math.max(0, now - startedAt) : 0,
|
|
@@ -564,7 +599,7 @@ function _findLinuxProcessesWithCwdInside(resolved, _timeoutMs, now) {
|
|
|
564
599
|
return out;
|
|
565
600
|
}
|
|
566
601
|
|
|
567
|
-
function _findMacProcessesWithCwdInside(resolved, timeoutMs, now) {
|
|
602
|
+
function _findMacProcessesWithCwdInside(resolved, timeoutMs, now, throwOnFailure = false) {
|
|
568
603
|
// `lsof -a -d cwd -F pn` emits PID then cwd path. Filter for dir matches,
|
|
569
604
|
// then run a single `ps` pass to enrich cmdline + start time.
|
|
570
605
|
let raw;
|
|
@@ -572,10 +607,15 @@ function _findMacProcessesWithCwdInside(resolved, timeoutMs, now) {
|
|
|
572
607
|
raw = _execSync('lsof -a -d cwd -F pn 2>/dev/null', {
|
|
573
608
|
encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024,
|
|
574
609
|
});
|
|
575
|
-
} catch {
|
|
610
|
+
} catch (err) {
|
|
611
|
+
// lsof exits 1 when no process matches; that is a successful empty probe.
|
|
612
|
+
if (err?.status === 1 && !String(err.stdout || '').trim()) return [];
|
|
613
|
+
if (throwOnFailure) throw err;
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
576
616
|
if (!raw) return [];
|
|
577
617
|
const prefix = resolved + path.sep;
|
|
578
|
-
const pids = new
|
|
618
|
+
const pids = new Map();
|
|
579
619
|
let currentPid = null;
|
|
580
620
|
for (const line of String(raw).split(/\r?\n/)) {
|
|
581
621
|
if (!line) continue;
|
|
@@ -585,12 +625,12 @@ function _findMacProcessesWithCwdInside(resolved, timeoutMs, now) {
|
|
|
585
625
|
const n = Number(val);
|
|
586
626
|
currentPid = Number.isInteger(n) && n > 0 ? n : null;
|
|
587
627
|
} else if (tag === 'n' && currentPid != null) {
|
|
588
|
-
if (val === resolved || val.startsWith(prefix)) pids.
|
|
628
|
+
if (val === resolved || val.startsWith(prefix)) pids.set(currentPid, val);
|
|
589
629
|
}
|
|
590
630
|
}
|
|
591
631
|
if (pids.size === 0) return [];
|
|
592
632
|
const out = [];
|
|
593
|
-
for (const pid of pids) {
|
|
633
|
+
for (const [pid, cwd] of pids) {
|
|
594
634
|
let line;
|
|
595
635
|
try {
|
|
596
636
|
line = String(_execSync(`ps -p ${pid} -o lstart=,command=`, {
|
|
@@ -608,6 +648,7 @@ function _findMacProcessesWithCwdInside(resolved, timeoutMs, now) {
|
|
|
608
648
|
}
|
|
609
649
|
out.push({
|
|
610
650
|
pid,
|
|
651
|
+
cwd,
|
|
611
652
|
cmdline,
|
|
612
653
|
startedAt,
|
|
613
654
|
ageMs: startedAt > 0 ? Math.max(0, now - startedAt) : 0,
|
|
@@ -725,7 +766,10 @@ function findProcessCwdHolders(worktreePath, opts = {}) {
|
|
|
725
766
|
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${scriptPath}"`,
|
|
726
767
|
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: timeoutMs, windowsHide: true, maxBuffer: 4 * 1024 * 1024 }
|
|
727
768
|
);
|
|
728
|
-
} catch {
|
|
769
|
+
} catch (err) {
|
|
770
|
+
if (opts.throwOnFailure) throw err;
|
|
771
|
+
raw = ''; // ETIMEDOUT / spawn fail / write fail -> reap nothing
|
|
772
|
+
}
|
|
729
773
|
finally {
|
|
730
774
|
if (scriptPath) { try { fs.unlinkSync(scriptPath); } catch { /* best-effort */ } }
|
|
731
775
|
}
|
|
@@ -861,8 +905,12 @@ module.exports = {
|
|
|
861
905
|
_psListProcesses,
|
|
862
906
|
_winListProcesses,
|
|
863
907
|
_unixListProcesses,
|
|
908
|
+
_parseUnixProcessesWithStart, // exported for testing
|
|
864
909
|
listAllProcesses,
|
|
865
910
|
listAllProcessesAsync,
|
|
911
|
+
getProcessIdentity,
|
|
912
|
+
isProcessIdentityCompatible,
|
|
913
|
+
isSameProcessIdentity,
|
|
866
914
|
findProcessesWithCwdInside,
|
|
867
915
|
findProcessesWithCwdInsideAsync,
|
|
868
916
|
_findWindowsProcessesWithCwdInside,
|