@yemi33/minions 0.1.2453 → 0.1.2455
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 +136 -6
- package/bin/minions.js +27 -13
- package/dashboard/js/agent-identity.js +121 -0
- package/dashboard/js/modal.js +4 -0
- package/dashboard/js/refresh.js +38 -3
- package/dashboard/js/render-agents.js +14 -2
- package/dashboard/js/render-dispatch.js +2 -2
- package/dashboard/js/render-other.js +147 -13
- package/dashboard/js/render-prd.js +6 -5
- package/dashboard/js/render-prs.js +44 -25
- package/dashboard/js/render-work-items.js +442 -22
- package/dashboard/js/settings.js +8 -0
- package/dashboard/js/utils.js +40 -0
- package/dashboard/pages/tools.html +1 -0
- package/dashboard/pages/work.html +13 -0
- package/dashboard/shared/pr-author.js +75 -0
- package/dashboard/shared/pr-filters.js +28 -5
- package/dashboard/styles.css +85 -0
- package/dashboard-build.js +3 -3
- package/dashboard.js +93 -7
- package/docs/README.md +1 -0
- package/docs/copilot-cli-schema.md +1 -0
- package/docs/engine-restart.md +4 -2
- package/docs/internal-install.md +44 -5
- package/docs/named-agents.md +48 -0
- package/docs/pr-author-identity.md +63 -10
- package/docs/runtime-adapters.md +39 -0
- package/docs/temporary-agents.md +172 -0
- package/engine/ado/comment.js +261 -4
- package/engine/agents/llm.js +26 -0
- package/engine/agents/playbook.js +2 -1
- package/engine/api/settings-validation.js +25 -0
- package/engine/core/operator-identity.js +23 -1
- package/engine/core/queries.js +53 -2
- package/engine/core/shared.js +286 -9
- package/engine/db/migrations/032-review-enrolled-pr-context-only.js +95 -0
- package/engine/operations/cli.js +102 -1
- package/engine/orchestration/lifecycle.js +7 -0
- package/engine/orchestration/routing.js +4 -1
- package/engine/providers/gh-comment.js +159 -0
- package/engine/recovery/stop-stack.js +16 -2
- package/engine/runtimes/claude.js +3 -0
- package/engine/runtimes/codex.js +4 -0
- package/engine/runtimes/copilot.js +23 -2
- package/engine.js +2 -2
- package/package.json +1 -1
- package/playbooks/fix.md +23 -2
- package/playbooks/shared-rules.md +24 -2
|
@@ -108,6 +108,35 @@ function _runGh(execFileSync, args, timeoutMs, env) {
|
|
|
108
108
|
return execFileSync('gh', args, opts);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// GitHub GraphQL node id for a review thread (opaque, e.g. `PRRT_kwDO…`). We
|
|
112
|
+
// don't decode it — only guard against empty / whitespace values before it is
|
|
113
|
+
// interpolated into a `gh api graphql` argv (argv form, no shell, so this is a
|
|
114
|
+
// sanity check, not an injection gate).
|
|
115
|
+
const GH_NODE_ID_RE = /^[A-Za-z0-9_=+/.:-]+$/;
|
|
116
|
+
|
|
117
|
+
function _validateThreadNodeId(threadId) {
|
|
118
|
+
if (typeof threadId !== 'string' || !GH_NODE_ID_RE.test(threadId)) {
|
|
119
|
+
throw new Error(`invalid reviewThreadId: ${JSON.stringify(threadId)} (expected a GitHub review-thread node id)`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run `gh api graphql` and return the parsed JSON `data` object. Throws on a
|
|
125
|
+
* transport failure (execFileSync non-zero — e.g. permission denied) and on a
|
|
126
|
+
* GraphQL `errors` payload (some gh versions exit 0 but embed errors).
|
|
127
|
+
*/
|
|
128
|
+
function _runGhGraphql(execFileSync, gqlArgs, timeoutMs, env) {
|
|
129
|
+
const raw = _runGh(execFileSync, ['api', 'graphql', ...gqlArgs], timeoutMs, env);
|
|
130
|
+
let parsed;
|
|
131
|
+
try { parsed = JSON.parse(String(raw || '').trim() || '{}'); }
|
|
132
|
+
catch (e) { throw new Error(`gh api graphql: unparseable response (${e.message})`); }
|
|
133
|
+
if (parsed && Array.isArray(parsed.errors) && parsed.errors.length > 0) {
|
|
134
|
+
const msg = parsed.errors.map((x) => x?.message || String(x)).join('; ');
|
|
135
|
+
throw new Error(`gh api graphql error: ${msg}`);
|
|
136
|
+
}
|
|
137
|
+
return parsed && parsed.data ? parsed.data : {};
|
|
138
|
+
}
|
|
139
|
+
|
|
111
140
|
/**
|
|
112
141
|
* Resolve the per-slug PAT for `repo` and return an env override suitable for
|
|
113
142
|
* passing to `_runGh`. Returns `undefined` when no mapping exists (caller
|
|
@@ -207,6 +236,132 @@ function postPrReviewComment({
|
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Reply to an existing GitHub PR *review thread* with a fix summary and resolve
|
|
241
|
+
* that thread — instead of opening a fresh top-level comment or a new review
|
|
242
|
+
* thread that the author must triage.
|
|
243
|
+
*
|
|
244
|
+
* This is the fix-summary path for GitHub (W-msd9os8e00qnd628). GitHub has no
|
|
245
|
+
* "resolved" state for conversation (issue) comments — only inline review
|
|
246
|
+
* threads can be resolved — so this is the ONLY way to post an addressed-finding
|
|
247
|
+
* summary that ends in a resolved state. When no review-thread node id is
|
|
248
|
+
* available the caller must fall back to `postPrComment` (an informational
|
|
249
|
+
* top-level summary that MUST NOT claim to be "resolved").
|
|
250
|
+
*
|
|
251
|
+
* Mechanics (via `gh api graphql`, per-slug PAT threaded through GH_TOKEN):
|
|
252
|
+
* 1. Query the thread by node id → `isResolved` + existing comment bodies.
|
|
253
|
+
* 2. `addPullRequestReviewThreadReply` unless a comment already carries THIS
|
|
254
|
+
* dispatch's hidden marker (agentId + kind + workItemId) — marker-keyed so
|
|
255
|
+
* retries / restarts / duplicate callbacks don't double-post.
|
|
256
|
+
* 3. `resolveReviewThread` unless already resolved.
|
|
257
|
+
*
|
|
258
|
+
* Idempotency + partial-failure: the reply and resolve are distinct round-trips.
|
|
259
|
+
* A reply success followed by a resolve failure throws (so the caller sees it
|
|
260
|
+
* and can retry); the retry re-queries, finds the reply present (skips it), and
|
|
261
|
+
* re-attempts only the resolve. Local state is never marked resolved when the
|
|
262
|
+
* `resolveReviewThread` mutation failed.
|
|
263
|
+
*
|
|
264
|
+
* Self-authored / shared-credential semantics: resolution is gated only by the
|
|
265
|
+
* thread node identity and marker, never by `viewerDidAuthor` or reviewer/fixer
|
|
266
|
+
* equality — a shared platform credential must not block a legitimate fix reply.
|
|
267
|
+
*
|
|
268
|
+
* @param {object} args
|
|
269
|
+
* @param {string} args.repo owner/name (for per-slug token routing)
|
|
270
|
+
* @param {string} args.threadId review-thread GraphQL node id
|
|
271
|
+
* @param {string} args.body reply body (pre-marker; builder prepends marker)
|
|
272
|
+
* @param {string} args.agentId
|
|
273
|
+
* @param {string} args.kind
|
|
274
|
+
* @param {string} [args.workItemId]
|
|
275
|
+
* @param {string} [args.model]
|
|
276
|
+
* @param {boolean} [args.resolve=true] resolve the thread after replying
|
|
277
|
+
* @param {number} [args.timeoutMs=30000]
|
|
278
|
+
* @param {Function} [args.execFileSync]
|
|
279
|
+
* @param {Function} [args.resolveTokenForSlug]
|
|
280
|
+
* @returns {{threadId:string, replied:boolean, alreadyReplied:boolean, resolved:boolean, alreadyResolved:boolean}}
|
|
281
|
+
*/
|
|
282
|
+
function replyAndResolveReviewThread({
|
|
283
|
+
repo,
|
|
284
|
+
threadId,
|
|
285
|
+
body,
|
|
286
|
+
agentId,
|
|
287
|
+
kind,
|
|
288
|
+
workItemId,
|
|
289
|
+
model,
|
|
290
|
+
resolve = true,
|
|
291
|
+
timeoutMs = 30000,
|
|
292
|
+
execFileSync = _execFileSync,
|
|
293
|
+
resolveTokenForSlug,
|
|
294
|
+
} = {}) {
|
|
295
|
+
_validateRepo(repo);
|
|
296
|
+
_validateThreadNodeId(threadId);
|
|
297
|
+
const finalBody = buildMinionsCommentBody({ agentId, kind, workItemId, model, body });
|
|
298
|
+
const env = _resolveTokenEnvForRepo(repo, resolveTokenForSlug);
|
|
299
|
+
|
|
300
|
+
// ── 1. Read the current thread (isResolved + comment bodies) for idempotency ─
|
|
301
|
+
const queryData = _runGhGraphql(
|
|
302
|
+
execFileSync,
|
|
303
|
+
[
|
|
304
|
+
'-f',
|
|
305
|
+
'query=query($id:ID!){ node(id:$id){ __typename ... on PullRequestReviewThread { id isResolved comments(first:100){ nodes { body } } } } }',
|
|
306
|
+
'-f', `id=${threadId}`,
|
|
307
|
+
],
|
|
308
|
+
timeoutMs,
|
|
309
|
+
env,
|
|
310
|
+
);
|
|
311
|
+
const node = queryData && queryData.node;
|
|
312
|
+
if (!node || (node.__typename && node.__typename !== 'PullRequestReviewThread')) {
|
|
313
|
+
throw new Error(`gh review thread ${threadId}: not a PullRequestReviewThread (only inline review threads can be resolved)`);
|
|
314
|
+
}
|
|
315
|
+
const alreadyResolved = node.isResolved === true;
|
|
316
|
+
const existingBodies = Array.isArray(node.comments?.nodes)
|
|
317
|
+
? node.comments.nodes.map((c) => String(c?.body || ''))
|
|
318
|
+
: [];
|
|
319
|
+
const alreadyReplied = existingBodies.some((b) => {
|
|
320
|
+
const marker = parseMinionsMarker(b);
|
|
321
|
+
if (!marker) return false;
|
|
322
|
+
if (marker.agentId !== agentId || marker.kind !== kind) return false;
|
|
323
|
+
if (workItemId) return marker.workItemId === workItemId;
|
|
324
|
+
return marker.workItemId === undefined;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ── 2. Reply on the originating thread (unless we already did) ──
|
|
328
|
+
let replied = false;
|
|
329
|
+
if (!alreadyReplied) {
|
|
330
|
+
_runGhGraphql(
|
|
331
|
+
execFileSync,
|
|
332
|
+
[
|
|
333
|
+
'-f',
|
|
334
|
+
'query=mutation($id:ID!,$body:String!){ addPullRequestReviewThreadReply(input:{pullRequestReviewThreadId:$id, body:$body}){ comment { id } } }',
|
|
335
|
+
'-f', `id=${threadId}`,
|
|
336
|
+
'-f', `body=${finalBody}`,
|
|
337
|
+
],
|
|
338
|
+
timeoutMs,
|
|
339
|
+
env,
|
|
340
|
+
);
|
|
341
|
+
replied = true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── 3. Resolve the thread (unless already resolved / not requested) ──
|
|
345
|
+
// A failure here after a successful reply throws; the retry skips the reply
|
|
346
|
+
// (marker present) and re-attempts only the resolve.
|
|
347
|
+
let resolved = alreadyResolved;
|
|
348
|
+
if (resolve && !alreadyResolved) {
|
|
349
|
+
_runGhGraphql(
|
|
350
|
+
execFileSync,
|
|
351
|
+
[
|
|
352
|
+
'-f',
|
|
353
|
+
'query=mutation($id:ID!){ resolveReviewThread(input:{threadId:$id}){ thread { isResolved } } }',
|
|
354
|
+
'-f', `id=${threadId}`,
|
|
355
|
+
],
|
|
356
|
+
timeoutMs,
|
|
357
|
+
env,
|
|
358
|
+
);
|
|
359
|
+
resolved = true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { threadId, replied, alreadyReplied, resolved, alreadyResolved };
|
|
363
|
+
}
|
|
364
|
+
|
|
210
365
|
module.exports = {
|
|
211
366
|
// Builders / parsers (pure functions — usable from anywhere)
|
|
212
367
|
buildMinionsCommentBody,
|
|
@@ -222,9 +377,13 @@ module.exports = {
|
|
|
222
377
|
// gh wrappers (argv-form, --body-file)
|
|
223
378
|
postPrComment,
|
|
224
379
|
postPrReviewComment,
|
|
380
|
+
// GitHub review-thread reply + resolve (fix-summary path, GraphQL)
|
|
381
|
+
replyAndResolveReviewThread,
|
|
225
382
|
// Internal helpers exported for tests / advanced callers
|
|
226
383
|
_buildMarker,
|
|
227
384
|
_writeTempBodyFile,
|
|
228
385
|
_resolveTokenEnvForRepo,
|
|
386
|
+
_validateThreadNodeId,
|
|
387
|
+
_runGhGraphql,
|
|
229
388
|
_clearTokenWarnings: () => _warnedUnresolvedSlugs.clear(),
|
|
230
389
|
};
|
|
@@ -104,9 +104,22 @@ const STOP_STACK_EXIT = Object.freeze({
|
|
|
104
104
|
REFUSED_UNVERIFIED: 4,
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
107
|
+
// The FLOOR of the internal installer's quiescence window (see
|
|
108
|
+
// bin/install-internal-minions.js#QUIESCE_TIMEOUT_FLOOR_MS), so a stop driven by
|
|
109
|
+
// the migration gate can never be shorter than the wait that follows it. That
|
|
110
|
+
// window is derived from the target runtime and only ever widens from here.
|
|
109
111
|
const DEFAULT_STOP_TIMEOUT_MS = 60000;
|
|
112
|
+
/**
|
|
113
|
+
* Grace added on top of the ENGINE'S OWN drain budget (`engine.shutdownTimeout`)
|
|
114
|
+
* when a caller derives a teardown deadline from it, covering the fixed work
|
|
115
|
+
* either side of the drain: writing stop intent, stopping the supervisor and
|
|
116
|
+
* dashboard, the verified reap, and the WAL-index release.
|
|
117
|
+
*
|
|
118
|
+
* Exported because it is a cross-surface number: `minions restart` applies it,
|
|
119
|
+
* and the standalone internal installer — which requires nothing from `engine/`
|
|
120
|
+
* and therefore mirrors the value — is held to it by a drift test.
|
|
121
|
+
*/
|
|
122
|
+
const DRAIN_GRACE_MS = 5000;
|
|
110
123
|
const PID_POLL_MS = 100;
|
|
111
124
|
const SHM_POLL_MS = 500;
|
|
112
125
|
/**
|
|
@@ -600,6 +613,7 @@ module.exports = {
|
|
|
600
613
|
STOP_STACK_HOLDER_REASONS,
|
|
601
614
|
STOP_STACK_EXIT,
|
|
602
615
|
DEFAULT_STOP_TIMEOUT_MS,
|
|
616
|
+
DRAIN_GRACE_MS,
|
|
603
617
|
POST_KILL_GRACE_MS,
|
|
604
618
|
PID_POLL_MS,
|
|
605
619
|
SHM_POLL_MS,
|
|
@@ -874,6 +874,9 @@ const capabilities = {
|
|
|
874
874
|
effortLevels: true,
|
|
875
875
|
// Emits `total_cost_usd` and detailed token usage in the result event
|
|
876
876
|
costTracking: true,
|
|
877
|
+
// Native billable unit is USD dollar cost (rendered as `$`). Typed metadata
|
|
878
|
+
// so the display layer never branches on the runtime name (W-msdcgitc).
|
|
879
|
+
billableUnit: 'usd',
|
|
877
880
|
// Family shorthands (`sonnet` / `opus` / `haiku`) are accepted by the CLI
|
|
878
881
|
modelShorthands: true,
|
|
879
882
|
// No public model enumeration mechanism — settings UI uses free-text
|
package/engine/runtimes/codex.js
CHANGED
|
@@ -993,6 +993,10 @@ const capabilities = {
|
|
|
993
993
|
systemPromptFile: false,
|
|
994
994
|
effortLevels: true,
|
|
995
995
|
costTracking: false,
|
|
996
|
+
// Native billable unit is USD (Codex reports no cost telemetry today, so it
|
|
997
|
+
// renders as unavailable). Typed metadata so the display layer never branches
|
|
998
|
+
// on the runtime name (W-msdcgitc).
|
|
999
|
+
billableUnit: 'usd',
|
|
996
1000
|
modelShorthands: false,
|
|
997
1001
|
modelDiscovery: true,
|
|
998
1002
|
// Bundled catalogs can lag staged/custom model availability. Let Codex
|
|
@@ -906,8 +906,24 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
|
|
|
906
906
|
numTurns: turnEndCount,
|
|
907
907
|
// Copilot-specific extension — preserved alongside the standard shape
|
|
908
908
|
// so the engine can distinguish "this turn cost N premium requests"
|
|
909
|
-
// from token accounting on the Claude path.
|
|
910
|
-
|
|
909
|
+
// from token accounting on the Claude path. NULL (not 0) when the
|
|
910
|
+
// installed CLI did not report the field for this run, so the engine
|
|
911
|
+
// records it as UNAVAILABLE rather than a real "0 premium requests"
|
|
912
|
+
// (W-msdcgitc). Treated as authoritative — never derived from tokens,
|
|
913
|
+
// model, or elapsed time.
|
|
914
|
+
premiumRequests: typeof u.premiumRequests === 'number' ? u.premiumRequests : null,
|
|
915
|
+
// Generic typed billable-unit descriptor (runtime/usage contract,
|
|
916
|
+
// W-msdcgitc). `value` is the authoritative provider-reported count for
|
|
917
|
+
// THIS result event (per-invocation, already scoped — not a running
|
|
918
|
+
// total the engine must diff), and `reported:false` marks an
|
|
919
|
+
// unavailable observation the engine keeps distinct from a real 0. The
|
|
920
|
+
// engine accumulates this generically (shared.accumulateBillableUnits)
|
|
921
|
+
// without ever branching on the runtime name.
|
|
922
|
+
billable: {
|
|
923
|
+
unit: 'premiumRequests',
|
|
924
|
+
value: typeof u.premiumRequests === 'number' ? u.premiumRequests : null,
|
|
925
|
+
reported: typeof u.premiumRequests === 'number',
|
|
926
|
+
},
|
|
911
927
|
sessionDurationMs: typeof u.sessionDurationMs === 'number' ? u.sessionDurationMs : 0,
|
|
912
928
|
};
|
|
913
929
|
}
|
|
@@ -1439,6 +1455,11 @@ const capabilities = {
|
|
|
1439
1455
|
effortLevels: true,
|
|
1440
1456
|
// result.usage carries premiumRequests count, no USD or tokens
|
|
1441
1457
|
costTracking: false,
|
|
1458
|
+
// Native billable unit is GitHub Copilot premium requests, not USD. The
|
|
1459
|
+
// display layer renders this in the same position Claude shows dollar cost
|
|
1460
|
+
// (W-msdcgitc). Typed metadata so orchestration/dashboard never branch on the
|
|
1461
|
+
// runtime name; adapters without a native credit unit default to 'usd'.
|
|
1462
|
+
billableUnit: 'premiumRequests',
|
|
1442
1463
|
// No 'sonnet'/'opus'/'haiku' shorthand — Copilot expects full model IDs
|
|
1443
1464
|
modelShorthands: false,
|
|
1444
1465
|
// GET https://api.githubcopilot.com/models works (verified during spike)
|
package/engine.js
CHANGED
|
@@ -11706,7 +11706,7 @@ function discoverFromWorkItems(config, project) {
|
|
|
11706
11706
|
newWork.push({
|
|
11707
11707
|
type: workType,
|
|
11708
11708
|
agent: agentId,
|
|
11709
|
-
agentName: config.agents[agentId]?.name || tempAgents.get(agentId)?.name || agentId,
|
|
11709
|
+
agentName: config.agents[agentId]?.name || tempAgents.get(agentId)?.name || shared.tempAgentCallSign(agentId) || agentId,
|
|
11710
11710
|
agentRole: config.agents[agentId]?.role || tempAgents.get(agentId)?.role || 'Agent',
|
|
11711
11711
|
task: `[${project?.name || 'project'}] ${item.title || item.description?.slice(0, 80) || item.id}`,
|
|
11712
11712
|
prompt,
|
|
@@ -12952,7 +12952,7 @@ function resolvePendingDispatchAgent(item, config) {
|
|
|
12952
12952
|
function assignPendingDispatchAgent(item, agentId, config) {
|
|
12953
12953
|
const agents = config.agents || {};
|
|
12954
12954
|
item.agent = agentId;
|
|
12955
|
-
item.agentName = agents[agentId]?.name || tempAgents.get(agentId)?.name || agentId;
|
|
12955
|
+
item.agentName = agents[agentId]?.name || tempAgents.get(agentId)?.name || shared.tempAgentCallSign(agentId) || agentId;
|
|
12956
12956
|
item.agentRole = agents[agentId]?.role || tempAgents.get(agentId)?.role || 'Agent';
|
|
12957
12957
|
delete item._agentBusySince;
|
|
12958
12958
|
delete item.skipReason;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2455",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|
package/playbooks/fix.md
CHANGED
|
@@ -232,8 +232,29 @@ Pick the disposition that matches the finding and post the matching reply, then
|
|
|
232
232
|
- **Won't fix / disagree** (comment is invalid, stale, already addressed, out of scope, or harmful) → Reply with the evidence-backed rationale explaining why no code change was made. Leave the thread **active** for the reviewer to decide; do NOT resolve unilaterally.
|
|
233
233
|
|
|
234
234
|
Host-specific mechanics:
|
|
235
|
-
- **
|
|
236
|
-
|
|
235
|
+
- **Preferred (both hosts): reply on the ORIGINATING thread and resolve it in one call** with
|
|
236
|
+
`minions pr comment <repo> --thread <threadId> --resolve --agent <id> --kind fix [--wi <finding-id>] --body-file <path>`
|
|
237
|
+
(ADO: add `--host ado --ado-org … --ado-project … --repo-id …` and, for Deferred, `--resolve-status closed`;
|
|
238
|
+
GitHub: `--thread` is the review-thread GraphQL node id). The `--thread` id is the **structured thread/finding
|
|
239
|
+
identity carried on the work item / your `reviewFindingResolution` evidence** — never resolve by matching title or
|
|
240
|
+
body text, and never resolve sibling threads or all threads in bulk. This path is idempotent: re-running after a
|
|
241
|
+
retry, restart, or a provider timeout re-checks the thread, skips a reply it already posted (matched by the hidden
|
|
242
|
+
minions marker), and re-attempts only the unfinished resolve.
|
|
243
|
+
- **Do NOT open a fresh active thread to summarize an addressed finding.** A new top-level/active thread for a fix
|
|
244
|
+
you already made just adds triage noise the author must re-close. Post the summary as a reply on the finding's own
|
|
245
|
+
thread via `--thread … --resolve` above.
|
|
246
|
+
- **GitHub**: `--thread … --resolve` replies to the review thread and resolves it via `resolveReviewThread` for the
|
|
247
|
+
four resolve-eligible dispositions. Leave Won't-fix threads unresolved. If no review-thread id exists (the finding
|
|
248
|
+
was a top-level conversation comment), a top-level `minions pr comment` reply is informational only — GitHub has no
|
|
249
|
+
resolved state for conversation comments, so do **not** claim it is "resolved."
|
|
250
|
+
- **ADO**: `--thread … --resolve` posts the reply on the originating thread and transitions it (`fixed` for
|
|
251
|
+
Fixed/Satisfied-elsewhere/Tracked-as-follow-up, `--resolve-status closed` for Deferred). Prefer it over raw `az`;
|
|
252
|
+
if you must fall back, use `az` first (ADO MCP only when `az` is insufficient), reply on the existing thread, and
|
|
253
|
+
set its status — do not create a new thread just for the fix summary. Leave `active` for Won't-fix rationale replies.
|
|
254
|
+
|
|
255
|
+
Never auto-resolve a finding the agent did NOT actually address. When the fix report says the finding was not
|
|
256
|
+
addressed, is ambiguous, needs human input, or lacks the required commit/`file:line` resolution evidence, reply with
|
|
257
|
+
the rationale and leave the thread **active**. Human feedback is not auto-resolved merely because a dispatch completed.
|
|
237
258
|
|
|
238
259
|
Hard rule: never resolve, close, or mark-fixed a thread without first posting a reply that explains the disposition. The chunk-5 / Caleb-Tseng / MaiLibraryViewModel thread (ADO 5215549 thread 65692221) is the canonical bad example — closed silently with no audit trail.
|
|
239
260
|
|
|
@@ -262,6 +262,18 @@ non-actionable thread is posted pre-resolved in a single call. Prefer
|
|
|
262
262
|
thread `status` field yourself: `closed` (4) for non-actionable notes (or `fixed`
|
|
263
263
|
(2) when the comment records something already handled), `active` (1) for
|
|
264
264
|
actionable findings. The verdict/summary comment itself stays `active`.
|
|
265
|
+
|
|
266
|
+
**Fix summaries reply on the originating thread — never a new active thread.**
|
|
267
|
+
When you are recording that an existing review finding was *addressed*, reply on
|
|
268
|
+
that finding's own thread and transition it, rather than opening a fresh thread:
|
|
269
|
+
`minions pr comment <pr> --host ado --ado-org … --ado-project … --repo-id … --thread <threadId> --resolve [--resolve-status closed] --agent <id> --kind fix [--wi <finding-id>] --body-file <path>`.
|
|
270
|
+
The `--thread` id is **structured thread/finding identity** from the work item or
|
|
271
|
+
your `reviewFindingResolution` evidence — resolve only that thread, keyed by id,
|
|
272
|
+
never by matching comment title/body text and never in bulk across sibling
|
|
273
|
+
threads. The reply+status transition is idempotent across retries/restarts: it
|
|
274
|
+
re-reads the thread, skips a reply already carrying your minions marker, and only
|
|
275
|
+
re-applies the status if the earlier PATCH failed — so a partial failure stays
|
|
276
|
+
visible and retryable instead of being silently marked resolved locally.
|
|
265
277
|
{{/ado_shared_rules}}
|
|
266
278
|
{{#github_shared_rules}}
|
|
267
279
|
**GitHub mechanism.** GitHub has no "resolved" state for PR *conversation*
|
|
@@ -273,9 +285,19 @@ single API call that creates a thread pre-resolved. So on GitHub:
|
|
|
273
285
|
`<details><summary>Non-blocking observations</summary>…</details>` block inside
|
|
274
286
|
the verdict comment so they are visible but de-emphasized and create no separate
|
|
275
287
|
unresolved threads. `--resolved` is rejected on the GitHub path for this reason.
|
|
288
|
+
- **To record that an existing review finding was addressed**, reply on that
|
|
289
|
+
finding's own review thread and resolve it in one idempotent call:
|
|
290
|
+
`minions pr comment <repo> --thread <reviewThreadNodeId> --resolve --agent <id> --kind fix [--wi <finding-id>] --body-file <path>`.
|
|
291
|
+
The `--thread` node id is **structured thread identity** from the work item or
|
|
292
|
+
your `reviewFindingResolution` evidence — resolve only that thread, never by
|
|
293
|
+
matching comment text and never in bulk. Do **not** open a fresh active thread
|
|
294
|
+
to summarize a fix you already made. If the finding was a *top-level*
|
|
295
|
+
conversation comment (no review-thread node id), a top-level `minions pr comment`
|
|
296
|
+
reply is informational only — never call it "resolved."
|
|
276
297
|
- If you DO post a non-actionable note as its own **inline review thread**, resolve
|
|
277
|
-
it immediately after posting
|
|
278
|
-
|
|
298
|
+
it immediately after posting (equivalent to `--thread … --resolve`, or the raw
|
|
299
|
+
GraphQL `resolveReviewThread` mutation below; issue comments cannot be resolved
|
|
300
|
+
this way, only review threads can):
|
|
279
301
|
```bash
|
|
280
302
|
# 1. find the thread node id for the comment you just posted
|
|
281
303
|
gh api graphql -f query='query($owner:String!,$repo:String!,$pr:Int!){
|