@rallycry/conveyor-agent 11.0.21 → 11.0.23
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/dist/{boot-Z3EBJ7GX.js → boot-S73ZNWBJ.js} +28 -0
- package/dist/{chunk-4ZDIBDQC.js → chunk-I7QYTA2V.js} +256 -94
- package/dist/{chunk-5WQHDGIW.js → chunk-LBB7DXOC.js} +1 -1
- package/dist/{chunk-WPXSZKMS.js → chunk-XJSW42TM.js} +381 -5
- package/dist/cli.js +5 -5
- package/dist/index.js +2 -2
- package/dist/{serve-boot-IRRHYA7W.js → serve-boot-ODH57W6I.js} +2 -2
- package/package.json +1 -1
- package/skills/conveyor-build/SKILL.md +21 -0
- package/skills/conveyor-consensus/SKILL.md +44 -1
- package/skills/conveyor-plan/SKILL.md +11 -0
- package/skills/conveyor-plan/references/product-decisions.md +103 -0
- package/skills/conveyor-review/SKILL.md +11 -0
- package/skills/conveyor-triage/SKILL.md +8 -0
- package/skills/conveyor-workflows/SKILL.md +16 -3
|
@@ -217,6 +217,30 @@ async function runPipeline(steps, ctx) {
|
|
|
217
217
|
);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// src/boot/bootstrap-refusal.ts
|
|
221
|
+
var BootstrapRejectedError = class extends Error {
|
|
222
|
+
constructor(message, code) {
|
|
223
|
+
super(message);
|
|
224
|
+
this.code = code;
|
|
225
|
+
this.name = "BootstrapRejectedError";
|
|
226
|
+
}
|
|
227
|
+
code;
|
|
228
|
+
};
|
|
229
|
+
async function readRefusal(res) {
|
|
230
|
+
if (res.status < 400 || res.status >= 500) return void 0;
|
|
231
|
+
let body;
|
|
232
|
+
try {
|
|
233
|
+
body = JSON.parse(await res.text());
|
|
234
|
+
} catch {
|
|
235
|
+
return void 0;
|
|
236
|
+
}
|
|
237
|
+
const payload = body;
|
|
238
|
+
if (payload?.retryable !== false) return void 0;
|
|
239
|
+
const message = typeof payload.error === "string" ? payload.error : "bootstrap refused";
|
|
240
|
+
const code = typeof payload.code === "string" ? payload.code : void 0;
|
|
241
|
+
return new BootstrapRejectedError(`${message} (HTTP ${res.status})`, code);
|
|
242
|
+
}
|
|
243
|
+
|
|
220
244
|
// src/boot/poll.ts
|
|
221
245
|
var BootstrapUnauthorizedError = class extends Error {
|
|
222
246
|
constructor(message) {
|
|
@@ -261,6 +285,10 @@ async function pollBootstrapBundle(apiUrl, token, deps) {
|
|
|
261
285
|
if (res?.status === 401) {
|
|
262
286
|
throw new BootstrapUnauthorizedError("bootstrap token rejected (401) \u2014 pod identity invalid");
|
|
263
287
|
}
|
|
288
|
+
if (res) {
|
|
289
|
+
const refusal = await readRefusal(res);
|
|
290
|
+
if (refusal) throw refusal;
|
|
291
|
+
}
|
|
264
292
|
pollCount++;
|
|
265
293
|
if (pollCount % PROGRESS_LOG_EVERY === 0) {
|
|
266
294
|
deps.log.info(`[boot] Still waiting for bind (poll #${pollCount})...`);
|
|
@@ -1446,7 +1446,7 @@ ${q.question}${q.options.length ? "\n" + q.options.map((o) => `- ${o.label}: ${o
|
|
|
1446
1446
|
}
|
|
1447
1447
|
};
|
|
1448
1448
|
|
|
1449
|
-
// ../shared/dist/chunk-
|
|
1449
|
+
// ../shared/dist/chunk-6NPOGQPP.js
|
|
1450
1450
|
import { z } from "zod";
|
|
1451
1451
|
var DEFAULT_SONNET_MODEL = "claude-sonnet-5";
|
|
1452
1452
|
var DEFAULT_OPUS_MODEL = "claude-opus-5";
|
|
@@ -1709,6 +1709,8 @@ import { z as z7 } from "zod";
|
|
|
1709
1709
|
import { z as z8 } from "zod";
|
|
1710
1710
|
import { z as z9 } from "zod";
|
|
1711
1711
|
import { z as z10 } from "zod";
|
|
1712
|
+
import { z as z11 } from "zod";
|
|
1713
|
+
import { z as z12 } from "zod";
|
|
1712
1714
|
var EXTERNAL_AGENT_MESSAGE_SOURCE = "external_agent";
|
|
1713
1715
|
var TUI_KINDS = ["claude-code", "opencode", "codex"];
|
|
1714
1716
|
var ACHIEVEMENT_RARITIES = [
|
|
@@ -3235,135 +3237,291 @@ var CRITICAL_AUTOMATED_SOURCES = /* @__PURE__ */ new Set([
|
|
|
3235
3237
|
// clear the completion guard.
|
|
3236
3238
|
"ci_result"
|
|
3237
3239
|
]);
|
|
3240
|
+
var DECISION_STATUS_LABELS = ["Open", "Decided", "Cancelled"];
|
|
3241
|
+
var DECISION_OPTION_KINDS = ["current", "planned", "proposed"];
|
|
3242
|
+
var DECISION_VOTE_SOURCES = ["web", "slack", "discord", "agent"];
|
|
3243
|
+
var DECISION_OPTIONS_MIN = 2;
|
|
3244
|
+
var DECISION_OPTIONS_MAX = 3;
|
|
3245
|
+
var DECISION_OPTION_LABEL_MAX = 80;
|
|
3246
|
+
var DECISION_QUESTION_MAX = 200;
|
|
3247
|
+
var DECISION_RESOLUTION_MAX = CARD_DESCRIPTION_MAX;
|
|
3248
|
+
var DECISION_VOTE_REASON_MAX = CARD_DESCRIPTION_MAX;
|
|
3249
|
+
var DECISION_DEADLINE_MIN_HOURS = 1;
|
|
3250
|
+
var DECISION_DEADLINE_MAX_DAYS = 30;
|
|
3251
|
+
var DECISION_DEADLINE_MAX_HOURS = DECISION_DEADLINE_MAX_DAYS * 24;
|
|
3252
|
+
var DECISION_LIST_MAX_LIMIT = 200;
|
|
3253
|
+
var decisionIdSchema = z10.string().cuid("Invalid decision ID");
|
|
3254
|
+
var optionInputSchema = z10.object({
|
|
3255
|
+
label: z10.string().min(1, "Option label is required").max(DECISION_OPTION_LABEL_MAX),
|
|
3256
|
+
description: z10.string().min(1, "Describe the option as GIVEN / WHEN / THEN"),
|
|
3257
|
+
kind: z10.enum(DECISION_OPTION_KINDS).optional()
|
|
3258
|
+
});
|
|
3259
|
+
var optionsArraySchema = z10.array(optionInputSchema).min(DECISION_OPTIONS_MIN, `A decision needs at least ${DECISION_OPTIONS_MIN} options`).max(
|
|
3260
|
+
DECISION_OPTIONS_MAX,
|
|
3261
|
+
`A decision takes at most ${DECISION_OPTIONS_MAX} options \u2014 more than that means the question is not framed yet`
|
|
3262
|
+
);
|
|
3263
|
+
var CreateDecisionRequestSchema = z10.object({
|
|
3264
|
+
projectId: z10.string().cuid("Invalid project ID"),
|
|
3265
|
+
/** The question, as one plain sentence. */
|
|
3266
|
+
title: z10.string().min(1, "A decision needs a question").max(DECISION_QUESTION_MAX),
|
|
3267
|
+
description: z10.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
3268
|
+
context: z10.string().min(1, "Context is required \u2014 say why this is being asked now"),
|
|
3269
|
+
options: optionsArraySchema,
|
|
3270
|
+
/** 1-based position of the option that wins on silence. */
|
|
3271
|
+
defaultOptionOrdinal: z10.number().int().min(1).max(DECISION_OPTIONS_MAX),
|
|
3272
|
+
deadlineHours: z10.number().min(DECISION_DEADLINE_MIN_HOURS).max(DECISION_DEADLINE_MAX_HOURS).optional(),
|
|
3273
|
+
/** Cards this decision blocks, by cuid or slug. */
|
|
3274
|
+
impactedCardRefs: z10.array(z10.string().min(1)).max(50).optional(),
|
|
3275
|
+
tagNames: z10.array(z10.string().min(1)).max(10).optional(),
|
|
3276
|
+
raisedByTaskId: z10.string().min(1).optional(),
|
|
3277
|
+
raisedByPrNumber: z10.number().int().positive().optional()
|
|
3278
|
+
});
|
|
3279
|
+
var VoteDecisionRequestSchema = z10.object({
|
|
3280
|
+
decisionId: decisionIdSchema,
|
|
3281
|
+
optionId: z10.string().cuid("Invalid option ID"),
|
|
3282
|
+
reason: z10.string().max(DECISION_VOTE_REASON_MAX).optional(),
|
|
3283
|
+
source: z10.enum(DECISION_VOTE_SOURCES).optional()
|
|
3284
|
+
});
|
|
3285
|
+
var RemoveDecisionVoteRequestSchema = z10.object({
|
|
3286
|
+
decisionId: decisionIdSchema
|
|
3287
|
+
});
|
|
3288
|
+
var ResolveDecisionRequestSchema = z10.object({
|
|
3289
|
+
decisionId: decisionIdSchema,
|
|
3290
|
+
chosenOptionId: z10.string().cuid("Invalid option ID"),
|
|
3291
|
+
resolution: z10.string().min(1, "A resolution is required \u2014 say what was decided").max(DECISION_RESOLUTION_MAX),
|
|
3292
|
+
resolutionNotes: z10.string().optional()
|
|
3293
|
+
});
|
|
3294
|
+
var CancelDecisionRequestSchema = z10.object({
|
|
3295
|
+
decisionId: decisionIdSchema,
|
|
3296
|
+
/** Why it is being dropped. Posted to the decision thread. */
|
|
3297
|
+
reason: z10.string().max(DECISION_RESOLUTION_MAX).optional()
|
|
3298
|
+
});
|
|
3299
|
+
var ReopenDecisionRequestSchema = z10.object({
|
|
3300
|
+
decisionId: decisionIdSchema,
|
|
3301
|
+
deadlineHours: z10.number().min(DECISION_DEADLINE_MIN_HOURS).max(DECISION_DEADLINE_MAX_HOURS).optional()
|
|
3302
|
+
});
|
|
3303
|
+
var UpdateDecisionRequestSchema = z10.object({
|
|
3304
|
+
decisionId: decisionIdSchema,
|
|
3305
|
+
title: z10.string().min(1).max(200).optional(),
|
|
3306
|
+
description: z10.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
3307
|
+
context: z10.string().min(1).optional(),
|
|
3308
|
+
deadlineAt: z10.string().datetime().optional(),
|
|
3309
|
+
/** Only accepted while the decision has zero votes. */
|
|
3310
|
+
options: optionsArraySchema.optional(),
|
|
3311
|
+
defaultOptionOrdinal: z10.number().int().min(1).max(DECISION_OPTIONS_MAX).optional(),
|
|
3312
|
+
addImpactedCardRefs: z10.array(z10.string().min(1)).max(50).optional(),
|
|
3313
|
+
removeImpactedCardIds: z10.array(z10.string().min(1)).max(50).optional()
|
|
3314
|
+
});
|
|
3315
|
+
var GetDecisionRequestSchema = z10.object({
|
|
3316
|
+
/** cuid or slug. A slug needs `projectId` to resolve unambiguously. */
|
|
3317
|
+
decisionId: z10.string().min(1),
|
|
3318
|
+
projectId: z10.string().cuid().optional()
|
|
3319
|
+
});
|
|
3320
|
+
var ListDecisionsRequestSchema = z10.object({
|
|
3321
|
+
projectId: z10.string().cuid("Invalid project ID"),
|
|
3322
|
+
status: z10.enum(DECISION_STATUS_LABELS).optional(),
|
|
3323
|
+
/** Tag name, not id — an agent knows the tag it is working under. */
|
|
3324
|
+
tag: z10.string().min(1).optional(),
|
|
3325
|
+
/** Decisions that this card is blocked by (cuid or slug). */
|
|
3326
|
+
cardId: z10.string().min(1).optional(),
|
|
3327
|
+
query: z10.string().min(1).optional(),
|
|
3328
|
+
limit: z10.number().int().min(1).max(DECISION_LIST_MAX_LIMIT).optional()
|
|
3329
|
+
});
|
|
3330
|
+
var optionInput = z11.object({
|
|
3331
|
+
label: z11.string().min(1).max(DECISION_OPTION_LABEL_MAX),
|
|
3332
|
+
description: z11.string().min(1),
|
|
3333
|
+
kind: z11.enum(DECISION_OPTION_KINDS).optional()
|
|
3334
|
+
});
|
|
3335
|
+
var optionsInput = z11.array(optionInput).min(DECISION_OPTIONS_MIN).max(DECISION_OPTIONS_MAX);
|
|
3336
|
+
var deadlineHours = z11.number().min(DECISION_DEADLINE_MIN_HOURS).max(DECISION_DEADLINE_MAX_HOURS).optional();
|
|
3337
|
+
var CreateProjectDecisionRequestSchema = z11.object({
|
|
3338
|
+
projectId: z11.string(),
|
|
3339
|
+
question: z11.string().min(1).max(200),
|
|
3340
|
+
summary: z11.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
3341
|
+
context: z11.string().min(1),
|
|
3342
|
+
options: optionsInput,
|
|
3343
|
+
defaultOption: z11.number().int().min(1).max(DECISION_OPTIONS_MAX),
|
|
3344
|
+
deadlineHours,
|
|
3345
|
+
impactedCards: z11.array(z11.string().min(1)).max(50).optional(),
|
|
3346
|
+
/**
|
|
3347
|
+
* Pod surface only: also block the card this session is working on. The
|
|
3348
|
+
* session's task id is resolved SERVER-side from `sessionId` — a
|
|
3349
|
+
* client-supplied card id would let a pod block any card in the project.
|
|
3350
|
+
*/
|
|
3351
|
+
includeCurrentCard: z11.boolean().optional(),
|
|
3352
|
+
sessionId: z11.string().optional(),
|
|
3353
|
+
tagNames: z11.array(z11.string().min(1)).max(10).optional(),
|
|
3354
|
+
raisedByPr: z11.number().int().positive().optional(),
|
|
3355
|
+
requestingUserId: z11.string().optional()
|
|
3356
|
+
});
|
|
3357
|
+
var ListProjectDecisionsRequestSchema = z11.object({
|
|
3358
|
+
projectId: z11.string(),
|
|
3359
|
+
status: z11.enum(DECISION_STATUS_LABELS).optional(),
|
|
3360
|
+
tag: z11.string().min(1).optional(),
|
|
3361
|
+
card: z11.string().min(1).optional(),
|
|
3362
|
+
query: z11.string().min(1).optional(),
|
|
3363
|
+
limit: z11.number().int().min(1).max(DECISION_LIST_MAX_LIMIT).optional(),
|
|
3364
|
+
requestingUserId: z11.string().optional()
|
|
3365
|
+
});
|
|
3366
|
+
var GetProjectDecisionRequestSchema = z11.object({
|
|
3367
|
+
projectId: z11.string(),
|
|
3368
|
+
decision: z11.string().min(1),
|
|
3369
|
+
requestingUserId: z11.string().optional()
|
|
3370
|
+
});
|
|
3371
|
+
var VoteProjectDecisionRequestSchema = z11.object({
|
|
3372
|
+
projectId: z11.string(),
|
|
3373
|
+
decision: z11.string().min(1),
|
|
3374
|
+
/** An option id, or its 1-based position. */
|
|
3375
|
+
option: z11.string().min(1),
|
|
3376
|
+
reason: z11.string().max(CARD_DESCRIPTION_MAX).optional(),
|
|
3377
|
+
requestingUserId: z11.string().optional()
|
|
3378
|
+
});
|
|
3379
|
+
var ResolveProjectDecisionRequestSchema = z11.object({
|
|
3380
|
+
projectId: z11.string(),
|
|
3381
|
+
decision: z11.string().min(1),
|
|
3382
|
+
option: z11.string().min(1),
|
|
3383
|
+
resolution: z11.string().min(1).max(DECISION_RESOLUTION_MAX),
|
|
3384
|
+
notes: z11.string().optional(),
|
|
3385
|
+
requestingUserId: z11.string().optional()
|
|
3386
|
+
});
|
|
3387
|
+
var UpdateProjectDecisionRequestSchema = z11.object({
|
|
3388
|
+
projectId: z11.string(),
|
|
3389
|
+
decision: z11.string().min(1),
|
|
3390
|
+
deadlineHours,
|
|
3391
|
+
addImpactedCards: z11.array(z11.string().min(1)).max(50).optional(),
|
|
3392
|
+
removeImpactedCards: z11.array(z11.string().min(1)).max(50).optional(),
|
|
3393
|
+
status: z11.enum(["Cancelled", "Open"]).optional(),
|
|
3394
|
+
requestingUserId: z11.string().optional()
|
|
3395
|
+
});
|
|
3238
3396
|
var MEETING_CHECKLIST_TITLE_MAX = 300;
|
|
3239
3397
|
var MEETING_TRANSCRIPT_MAX_CHARS = 2e6;
|
|
3240
3398
|
var MEETING_TITLE_MAX = 200;
|
|
3241
3399
|
var MEETING_OCCURRED_AT_MIN_YEAR = 2e3;
|
|
3242
3400
|
var MEETING_OCCURRED_AT_MAX_FUTURE_MS = 48 * 60 * 60 * 1e3;
|
|
3243
3401
|
var OCCURRED_AT_RANGE_MESSAGE = `occurredAt must be a real date: no earlier than ${MEETING_OCCURRED_AT_MIN_YEAR}, and no more than 48 hours in the future.`;
|
|
3244
|
-
var MeetingOccurredAtSchema =
|
|
3402
|
+
var MeetingOccurredAtSchema = z12.string().datetime().refine((value) => {
|
|
3245
3403
|
const ms = Date.parse(value);
|
|
3246
3404
|
if (Number.isNaN(ms)) return false;
|
|
3247
3405
|
if (ms > Date.now() + MEETING_OCCURRED_AT_MAX_FUTURE_MS) return false;
|
|
3248
3406
|
return new Date(ms).getUTCFullYear() >= MEETING_OCCURRED_AT_MIN_YEAR;
|
|
3249
3407
|
}, OCCURRED_AT_RANGE_MESSAGE);
|
|
3250
|
-
var CreateMeetingFromTranscriptRequestSchema =
|
|
3251
|
-
projectId:
|
|
3252
|
-
rawText:
|
|
3253
|
-
title:
|
|
3408
|
+
var CreateMeetingFromTranscriptRequestSchema = z12.object({
|
|
3409
|
+
projectId: z12.string().cuid(),
|
|
3410
|
+
rawText: z12.string().min(1).max(MEETING_TRANSCRIPT_MAX_CHARS),
|
|
3411
|
+
title: z12.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
3254
3412
|
/** ISO 8601. Defaults to now when the source carries no date. */
|
|
3255
3413
|
occurredAt: MeetingOccurredAtSchema.optional(),
|
|
3256
3414
|
/** Override auto-detection. Rarely needed; detection handles the three formats. */
|
|
3257
|
-
format:
|
|
3258
|
-
source:
|
|
3415
|
+
format: z12.enum(["text", "vtt", "srt"]).optional(),
|
|
3416
|
+
source: z12.enum(["manual", "slack"]).optional()
|
|
3259
3417
|
});
|
|
3260
|
-
var GetMeetingRequestSchema =
|
|
3261
|
-
projectId:
|
|
3262
|
-
meetingId:
|
|
3418
|
+
var GetMeetingRequestSchema = z12.object({
|
|
3419
|
+
projectId: z12.string().cuid(),
|
|
3420
|
+
meetingId: z12.string().cuid()
|
|
3263
3421
|
});
|
|
3264
|
-
var UpdateMeetingRequestSchema =
|
|
3265
|
-
projectId:
|
|
3266
|
-
meetingId:
|
|
3267
|
-
title:
|
|
3422
|
+
var UpdateMeetingRequestSchema = z12.object({
|
|
3423
|
+
projectId: z12.string().cuid(),
|
|
3424
|
+
meetingId: z12.string().cuid(),
|
|
3425
|
+
title: z12.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
3268
3426
|
occurredAt: MeetingOccurredAtSchema.optional()
|
|
3269
3427
|
});
|
|
3270
|
-
var RegenerateMeetingSummaryRequestSchema =
|
|
3271
|
-
projectId:
|
|
3272
|
-
meetingId:
|
|
3428
|
+
var RegenerateMeetingSummaryRequestSchema = z12.object({
|
|
3429
|
+
projectId: z12.string().cuid(),
|
|
3430
|
+
meetingId: z12.string().cuid()
|
|
3273
3431
|
});
|
|
3274
|
-
var DeleteMeetingRequestSchema =
|
|
3275
|
-
projectId:
|
|
3276
|
-
meetingId:
|
|
3432
|
+
var DeleteMeetingRequestSchema = z12.object({
|
|
3433
|
+
projectId: z12.string().cuid(),
|
|
3434
|
+
meetingId: z12.string().cuid()
|
|
3277
3435
|
});
|
|
3278
|
-
var checklistTitle =
|
|
3279
|
-
var ListMeetingChecklistRequestSchema =
|
|
3280
|
-
projectId:
|
|
3281
|
-
meetingId:
|
|
3436
|
+
var checklistTitle = z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX);
|
|
3437
|
+
var ListMeetingChecklistRequestSchema = z12.object({
|
|
3438
|
+
projectId: z12.string().cuid(),
|
|
3439
|
+
meetingId: z12.string().cuid()
|
|
3282
3440
|
});
|
|
3283
|
-
var AddMeetingChecklistItemsRequestSchema =
|
|
3284
|
-
projectId:
|
|
3285
|
-
meetingId:
|
|
3286
|
-
items:
|
|
3441
|
+
var AddMeetingChecklistItemsRequestSchema = z12.object({
|
|
3442
|
+
projectId: z12.string().cuid(),
|
|
3443
|
+
meetingId: z12.string().cuid(),
|
|
3444
|
+
items: z12.array(z12.object({ title: checklistTitle })).min(1).max(50)
|
|
3287
3445
|
});
|
|
3288
|
-
var UpdateMeetingChecklistItemRequestSchema =
|
|
3289
|
-
projectId:
|
|
3290
|
-
meetingId:
|
|
3291
|
-
itemId:
|
|
3446
|
+
var UpdateMeetingChecklistItemRequestSchema = z12.object({
|
|
3447
|
+
projectId: z12.string().cuid(),
|
|
3448
|
+
meetingId: z12.string().cuid(),
|
|
3449
|
+
itemId: z12.string().cuid(),
|
|
3292
3450
|
title: checklistTitle.optional(),
|
|
3293
|
-
ordinal:
|
|
3451
|
+
ordinal: z12.number().int().min(0).optional(),
|
|
3294
3452
|
/** Explicit null clears the link; undefined leaves it alone. */
|
|
3295
|
-
linkedTaskId:
|
|
3453
|
+
linkedTaskId: z12.string().cuid().nullable().optional()
|
|
3296
3454
|
}).refine(
|
|
3297
3455
|
(v) => v.title !== void 0 || v.ordinal !== void 0 || v.linkedTaskId !== void 0,
|
|
3298
3456
|
"Pass at least one of title, ordinal, or linkedTaskId."
|
|
3299
3457
|
);
|
|
3300
|
-
var DeleteMeetingChecklistItemRequestSchema =
|
|
3301
|
-
projectId:
|
|
3302
|
-
meetingId:
|
|
3303
|
-
itemId:
|
|
3304
|
-
});
|
|
3305
|
-
var SetMeetingChecklistItemCheckedRequestSchema =
|
|
3306
|
-
projectId:
|
|
3307
|
-
meetingId:
|
|
3308
|
-
itemId:
|
|
3309
|
-
checked:
|
|
3458
|
+
var DeleteMeetingChecklistItemRequestSchema = z12.object({
|
|
3459
|
+
projectId: z12.string().cuid(),
|
|
3460
|
+
meetingId: z12.string().cuid(),
|
|
3461
|
+
itemId: z12.string().cuid()
|
|
3462
|
+
});
|
|
3463
|
+
var SetMeetingChecklistItemCheckedRequestSchema = z12.object({
|
|
3464
|
+
projectId: z12.string().cuid(),
|
|
3465
|
+
meetingId: z12.string().cuid(),
|
|
3466
|
+
itemId: z12.string().cuid(),
|
|
3467
|
+
checked: z12.boolean(),
|
|
3310
3468
|
/** Attach the card in the same call that ticks the item. */
|
|
3311
|
-
linkedTaskId:
|
|
3469
|
+
linkedTaskId: z12.string().cuid().nullable().optional()
|
|
3312
3470
|
});
|
|
3313
|
-
var ListMeetingsRequestSchema =
|
|
3314
|
-
projectId:
|
|
3315
|
-
limit:
|
|
3316
|
-
search:
|
|
3471
|
+
var ListMeetingsRequestSchema = z12.object({
|
|
3472
|
+
projectId: z12.string().cuid(),
|
|
3473
|
+
limit: z12.number().int().min(1).max(50).optional(),
|
|
3474
|
+
search: z12.string().max(200).optional()
|
|
3317
3475
|
});
|
|
3318
|
-
var ReadMeetingTranscriptRequestSchema =
|
|
3319
|
-
projectId:
|
|
3320
|
-
meetingId:
|
|
3321
|
-
offset:
|
|
3322
|
-
limit:
|
|
3476
|
+
var ReadMeetingTranscriptRequestSchema = z12.object({
|
|
3477
|
+
projectId: z12.string().cuid(),
|
|
3478
|
+
meetingId: z12.string().cuid(),
|
|
3479
|
+
offset: z12.number().int().min(0).optional(),
|
|
3480
|
+
limit: z12.number().int().min(1).max(500).optional()
|
|
3323
3481
|
});
|
|
3324
3482
|
var MEETING_SUMMARY_MAX_CHARS = 5e4;
|
|
3325
|
-
var AddProjectMeetingChecklistItemsRequestSchema =
|
|
3326
|
-
projectId:
|
|
3327
|
-
meetingId:
|
|
3328
|
-
items:
|
|
3329
|
-
requestingUserId:
|
|
3330
|
-
});
|
|
3331
|
-
var CheckProjectMeetingChecklistItemRequestSchema =
|
|
3332
|
-
projectId:
|
|
3333
|
-
meetingId:
|
|
3334
|
-
title:
|
|
3335
|
-
checked:
|
|
3483
|
+
var AddProjectMeetingChecklistItemsRequestSchema = z12.object({
|
|
3484
|
+
projectId: z12.string().cuid(),
|
|
3485
|
+
meetingId: z12.string().cuid(),
|
|
3486
|
+
items: z12.array(z12.object({ title: z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX) })).min(1).max(50),
|
|
3487
|
+
requestingUserId: z12.string().optional()
|
|
3488
|
+
});
|
|
3489
|
+
var CheckProjectMeetingChecklistItemRequestSchema = z12.object({
|
|
3490
|
+
projectId: z12.string().cuid(),
|
|
3491
|
+
meetingId: z12.string().cuid(),
|
|
3492
|
+
title: z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX),
|
|
3493
|
+
checked: z12.boolean(),
|
|
3336
3494
|
/** Card id or slug. Resolved server-side and required to be in the project. */
|
|
3337
|
-
linkedTask:
|
|
3338
|
-
requestingUserId:
|
|
3339
|
-
});
|
|
3340
|
-
var EditProjectMeetingChecklistItemRequestSchema =
|
|
3341
|
-
projectId:
|
|
3342
|
-
meetingId:
|
|
3343
|
-
title:
|
|
3344
|
-
newTitle:
|
|
3345
|
-
requestingUserId:
|
|
3346
|
-
});
|
|
3347
|
-
var RemoveProjectMeetingChecklistItemRequestSchema =
|
|
3348
|
-
projectId:
|
|
3349
|
-
meetingId:
|
|
3350
|
-
title:
|
|
3351
|
-
requestingUserId:
|
|
3352
|
-
});
|
|
3353
|
-
var CreateProjectMeetingRequestSchema =
|
|
3354
|
-
projectId:
|
|
3355
|
-
rawText:
|
|
3356
|
-
title:
|
|
3495
|
+
linkedTask: z12.string().min(1).optional(),
|
|
3496
|
+
requestingUserId: z12.string().optional()
|
|
3497
|
+
});
|
|
3498
|
+
var EditProjectMeetingChecklistItemRequestSchema = z12.object({
|
|
3499
|
+
projectId: z12.string().cuid(),
|
|
3500
|
+
meetingId: z12.string().cuid(),
|
|
3501
|
+
title: z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX),
|
|
3502
|
+
newTitle: z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX),
|
|
3503
|
+
requestingUserId: z12.string().optional()
|
|
3504
|
+
});
|
|
3505
|
+
var RemoveProjectMeetingChecklistItemRequestSchema = z12.object({
|
|
3506
|
+
projectId: z12.string().cuid(),
|
|
3507
|
+
meetingId: z12.string().cuid(),
|
|
3508
|
+
title: z12.string().min(1).max(MEETING_CHECKLIST_TITLE_MAX),
|
|
3509
|
+
requestingUserId: z12.string().optional()
|
|
3510
|
+
});
|
|
3511
|
+
var CreateProjectMeetingRequestSchema = z12.object({
|
|
3512
|
+
projectId: z12.string().cuid(),
|
|
3513
|
+
rawText: z12.string().min(1).max(MEETING_TRANSCRIPT_MAX_CHARS),
|
|
3514
|
+
title: z12.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
3357
3515
|
occurredAt: MeetingOccurredAtSchema.optional(),
|
|
3358
|
-
requestingUserId:
|
|
3516
|
+
requestingUserId: z12.string().optional()
|
|
3359
3517
|
});
|
|
3360
|
-
var UpdateProjectMeetingRequestSchema =
|
|
3361
|
-
projectId:
|
|
3362
|
-
meetingId:
|
|
3363
|
-
title:
|
|
3518
|
+
var UpdateProjectMeetingRequestSchema = z12.object({
|
|
3519
|
+
projectId: z12.string().cuid(),
|
|
3520
|
+
meetingId: z12.string().cuid(),
|
|
3521
|
+
title: z12.string().min(1).max(MEETING_TITLE_MAX).optional(),
|
|
3364
3522
|
occurredAt: MeetingOccurredAtSchema.optional(),
|
|
3365
|
-
summary:
|
|
3366
|
-
requestingUserId:
|
|
3523
|
+
summary: z12.string().min(1).max(MEETING_SUMMARY_MAX_CHARS).optional(),
|
|
3524
|
+
requestingUserId: z12.string().optional()
|
|
3367
3525
|
}).refine(
|
|
3368
3526
|
(v) => v.title !== void 0 || v.occurredAt !== void 0 || v.summary !== void 0,
|
|
3369
3527
|
"Pass at least one of title, occurredAt, or summary."
|
|
@@ -3738,13 +3896,17 @@ var CARD_TYPE_SURFACE = {
|
|
|
3738
3896
|
task: "board",
|
|
3739
3897
|
chat: "board",
|
|
3740
3898
|
incident: "report",
|
|
3741
|
-
suggestion: "report"
|
|
3899
|
+
suggestion: "report",
|
|
3900
|
+
// Decisions get their own surface: they are neither built on the board nor
|
|
3901
|
+
// triaged in the report feeds. Cards → Decisions is their only feed.
|
|
3902
|
+
decision: "decision"
|
|
3742
3903
|
};
|
|
3743
3904
|
var surfaceTypes = (surface) => Object.keys(CARD_TYPE_SURFACE).filter(
|
|
3744
3905
|
(type) => CARD_TYPE_SURFACE[type] === surface
|
|
3745
3906
|
);
|
|
3746
3907
|
var BOARD_CARD_TYPES = surfaceTypes("board");
|
|
3747
3908
|
var REPORT_CARD_TYPES = surfaceTypes("report");
|
|
3909
|
+
var DECISION_CARD_TYPES = surfaceTypes("decision");
|
|
3748
3910
|
|
|
3749
3911
|
// src/runner/git-run.ts
|
|
3750
3912
|
import { execFile } from "child_process";
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
statWorkspacePath,
|
|
92
92
|
updateRemoteToken,
|
|
93
93
|
verifyGitCredential
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-I7QYTA2V.js";
|
|
95
95
|
import {
|
|
96
96
|
registerBootMilestoneSocketFallback,
|
|
97
97
|
reportBootMilestone
|
|
@@ -6338,8 +6338,9 @@ Working rules:`,
|
|
|
6338
6338
|
`- Read a file before your first Write/Edit to it, and batch multiple changes to the same file into a single call instead of many sequential edits.`,
|
|
6339
6339
|
`- To learn what calls a symbol or where it lives, query the prebuilt code graph before grepping: \`graphify query "<SymbolName>"\` from the repo root. Query a SYMBOL, never a sentence \u2014 \`graphify query "resolveTaskBaseBranch"\` returns the definition plus every call site, while "how does a task get its base branch" seeds unrelated start nodes and returns test files and loggers. Don't know the symbol yet? Grep for the name first, then query it: grep finds names, the graph finds relationships. \`No matching nodes found\` means "not in this graph" (it is prebuilt, so very recent code is absent), NOT "not in the codebase" \u2014 fall back to \`git grep\`. Skip all of this if \`graphify-out/graph.json\` is not present.`,
|
|
6340
6340
|
`- When a build/lint/test run fails, capture its output to a file once and grep the file \u2014 never re-run the suite just to re-filter the same output.`,
|
|
6341
|
-
|
|
6342
|
-
runtimeTui === "codex" ? `-
|
|
6341
|
+
`- App servers (including development and production servers) keep running after startup. Start them with the harness background/session facility, then run a separate bounded HTTP readiness check and continue verification. Never wait for server exit or a completion notification to mean the app is ready. If readiness times out, inspect build output and process/resource state before restarting.`,
|
|
6342
|
+
runtimeTui === "codex" ? `- Waiting on long-running commands: retain and resume the command session until it returns an exit result. Finish each required gate before ending the task. Never pretend a completion notification will resume you, and never start another gate while the current one is running.` : `- Waiting on long-running commands: if a gate finishes in under ~2 minutes, run it in the foreground with a timeout. For a longer one, launch it with run_in_background and STOP; a completion notification arrives when it finishes, and tracked background work holds the workspace awake for up to 45 minutes. Inspect any gate approaching that cap. For the final pre-PR gate a bounded foreground run (\`timeout 590 <gate>\` with Bash \`timeout: 600000\`) is still preferred as defense in depth. A pod restart loses both foreground and background processes. Never busy-wait with sleep/pgrep/tail loops, and never re-run the suite to escape a wait that looks stalled.`,
|
|
6343
|
+
runtimeTui === "codex" ? `- Do not end your turn to wait on a required gate. Resume its command session, inspect its exit result, and then continue the checklist.` : `- For a finite background gate, end your turn and its completion notification re-invokes you within the bounded liveness window. Never emit filler commands (\`echo waiting\`, \`true\`, \`sleep N; echo done\`) to "stay alive" \u2014 they are detected and blocked. The proven long-wait shape: start the job with run_in_background, then end the turn.${agentMode === "review" ? "" : " For CI on a pushed commit the shape is `wait_for_checks`, then end the turn (the pod may sleep; the GitHub result wakes it)."} Arm a ScheduleWakeup (delaySeconds 900-1500, prompt restating your next steps) only when nothing will notify you \u2014 a deploy, a remote queue \u2014 never for CI, and never as insurance against a background job's own notification, which does fire.`,
|
|
6343
6344
|
`
|
|
6344
6345
|
Git:`,
|
|
6345
6346
|
`- Stay on \`${context.githubBranch}\` for the whole task: do not check out another branch and do not create one. It was cut from \`${context.baseBranch}\`, and PRs target that automatically.`,
|
|
@@ -6859,7 +6860,7 @@ var SEARCH_STATUSES = [
|
|
|
6859
6860
|
"Complete",
|
|
6860
6861
|
"Cancelled"
|
|
6861
6862
|
];
|
|
6862
|
-
var SEARCH_CARD_TYPES = ["task", "incident", "suggestion"];
|
|
6863
|
+
var SEARCH_CARD_TYPES = ["task", "incident", "suggestion", "decision"];
|
|
6863
6864
|
var searchFilterFields = {
|
|
6864
6865
|
tagNames: f.optional(
|
|
6865
6866
|
f.array(f.string(), {
|
|
@@ -7552,6 +7553,235 @@ var createSuggestionContract = defineToolContract({
|
|
|
7552
7553
|
}
|
|
7553
7554
|
});
|
|
7554
7555
|
var suggestionsContracts = [createSuggestionContract];
|
|
7556
|
+
var OPTION_KINDS = ["current", "planned", "proposed"];
|
|
7557
|
+
var STATUS_LABELS = ["Open", "Decided", "Cancelled"];
|
|
7558
|
+
var optionDescription = "What this option means in the product, written as GIVEN / WHEN / THEN. Concrete enough that a reader six months from now can tell which behaviour was chosen.";
|
|
7559
|
+
var optionsDesc = "Two or three options. More than three means the question is not framed yet \u2014 narrow it or split it. Mark the option describing what the code does today as kind: current.";
|
|
7560
|
+
var defaultOptionDesc = "1-based position of the option that wins if nobody decides by the deadline. Required: silence has to become a decision, never a stall.";
|
|
7561
|
+
var agentOption = f.object(
|
|
7562
|
+
{
|
|
7563
|
+
label: f.string({ min: 1, desc: "Short name for the option, under 80 characters" }),
|
|
7564
|
+
description: f.string({ min: 1, desc: optionDescription }),
|
|
7565
|
+
kind: f.optional(
|
|
7566
|
+
f.enum(OPTION_KINDS, {
|
|
7567
|
+
desc: 'current = what the code does today, planned = already agreed but unbuilt, proposed = new. Defaults to "proposed".'
|
|
7568
|
+
})
|
|
7569
|
+
)
|
|
7570
|
+
},
|
|
7571
|
+
{ desc: "One option people can choose" }
|
|
7572
|
+
);
|
|
7573
|
+
var createDecisionContract = defineToolContract({
|
|
7574
|
+
name: "create_decision",
|
|
7575
|
+
agent: {
|
|
7576
|
+
description: `Raise a product decision: a fork the plan cannot be right without. Two or three options, one default, a deadline. Run list_decisions FIRST \u2014 an answered decision is the answer, and re-asking it wastes everybody's time. Do NOT file a decision for an assumption you can simply state: an assumed behaviour belongs in the plan as a GIVEN / WHEN / THEN line under "Assumed behaviours". Returns the decision's id, slug, and url.`,
|
|
7577
|
+
fields: {
|
|
7578
|
+
question: f.string({
|
|
7579
|
+
min: 1,
|
|
7580
|
+
desc: "The question, as one plain sentence ending in a question mark"
|
|
7581
|
+
}),
|
|
7582
|
+
summary: f.optional(
|
|
7583
|
+
f.string({
|
|
7584
|
+
desc: cardDescriptionDesc("One or two sentences of context for the card face")
|
|
7585
|
+
})
|
|
7586
|
+
),
|
|
7587
|
+
context: f.string({
|
|
7588
|
+
min: 1,
|
|
7589
|
+
desc: "Markdown background: why this is being asked now, and what breaks if it is answered wrongly"
|
|
7590
|
+
}),
|
|
7591
|
+
options: f.array(agentOption, { min: 2, desc: optionsDesc }),
|
|
7592
|
+
default_option: f.number({ int: true, min: 1, max: 3, desc: defaultOptionDesc }),
|
|
7593
|
+
deadline_hours: f.optional(
|
|
7594
|
+
f.number({ min: 1, max: 720, desc: "Hours until the default applies. Default 72." })
|
|
7595
|
+
),
|
|
7596
|
+
impacted_cards: f.optional(
|
|
7597
|
+
f.array(f.string(), {
|
|
7598
|
+
desc: "Slugs or ids of cards this decision blocks. They cannot start until it is settled, and they are un-blocked the moment it is. Blocking a card needs Moderate access: without it the decision is still raised and each ref comes back under refused."
|
|
7599
|
+
})
|
|
7600
|
+
),
|
|
7601
|
+
include_current_card: f.optional(
|
|
7602
|
+
f.boolean({
|
|
7603
|
+
desc: "Also block the card this session is working on. Defaults to true \u2014 you are raising this because your own plan is stuck on it. Pass false to raise the question without stopping your own work."
|
|
7604
|
+
})
|
|
7605
|
+
),
|
|
7606
|
+
tag_names: f.optional(
|
|
7607
|
+
f.array(f.string(), {
|
|
7608
|
+
desc: "Tag names. The settled decision is written into each tag's overview, which is how the next agent finds it."
|
|
7609
|
+
})
|
|
7610
|
+
),
|
|
7611
|
+
raised_by_pr: f.optional(
|
|
7612
|
+
f.number({ int: true, min: 1, desc: "PR number that surfaced the question" })
|
|
7613
|
+
)
|
|
7614
|
+
}
|
|
7615
|
+
},
|
|
7616
|
+
mcp: {
|
|
7617
|
+
description: "Raise a product decision on a Conveyor project: a fork that has to be settled before the work can be planned correctly. Two or three options, one default that applies if nobody decides, and a deadline. Search list_decisions first \u2014 a Decided decision is the answer. Pass projectId to target a specific project; otherwise the configured default project is used.",
|
|
7618
|
+
fields: {
|
|
7619
|
+
projectId: mcpProjectId,
|
|
7620
|
+
question: f.string({ desc: "The question, as one plain sentence" }),
|
|
7621
|
+
summary: f.optional(f.string({ desc: cardDescriptionDesc("Context for the card face") })),
|
|
7622
|
+
context: f.string({ desc: "Markdown background: why this is being asked now" }),
|
|
7623
|
+
options: f.array(agentOption, { min: 2, desc: optionsDesc }),
|
|
7624
|
+
defaultOption: f.number({ int: true, min: 1, max: 3, desc: defaultOptionDesc }),
|
|
7625
|
+
deadlineHours: f.optional(
|
|
7626
|
+
f.number({ min: 1, max: 720, desc: "Hours until the default applies. Default 72." })
|
|
7627
|
+
),
|
|
7628
|
+
impactedCards: f.optional(
|
|
7629
|
+
f.array(f.string(), {
|
|
7630
|
+
desc: "Slugs or ids of cards this decision blocks. Needs Moderate access; without it each ref comes back under refused and the decision is still raised."
|
|
7631
|
+
})
|
|
7632
|
+
),
|
|
7633
|
+
tagNames: f.optional(f.array(f.string(), { desc: "Tag names to categorize the decision" })),
|
|
7634
|
+
raisedByPr: f.optional(
|
|
7635
|
+
f.number({ int: true, min: 1, desc: "PR number that surfaced the question" })
|
|
7636
|
+
)
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
});
|
|
7640
|
+
var listDecisionsContract = defineToolContract({
|
|
7641
|
+
name: "list_decisions",
|
|
7642
|
+
agent: {
|
|
7643
|
+
description: `Search this project's decisions. Run this BEFORE planning anything in an area: "have we already decided how X behaves?" Decided rows carry their resolution, so the list doubles as the decision log. Filter by card to see what is blocking a specific card.`,
|
|
7644
|
+
fields: {
|
|
7645
|
+
status: f.optional(
|
|
7646
|
+
f.enum(STATUS_LABELS, {
|
|
7647
|
+
desc: "Open = still being voted on, Decided = settled, Cancelled = dropped"
|
|
7648
|
+
})
|
|
7649
|
+
),
|
|
7650
|
+
tag: f.optional(f.string({ desc: 'Tag name, e.g. "slack"' })),
|
|
7651
|
+
card: f.optional(
|
|
7652
|
+
f.string({ desc: "Slug or id of a card \u2014 returns the decisions it is blocked by" })
|
|
7653
|
+
),
|
|
7654
|
+
query: f.optional(f.string({ desc: "Text search on the question" })),
|
|
7655
|
+
limit: f.optional(f.number({ desc: "Max rows to return (default 50, max 200)" }))
|
|
7656
|
+
}
|
|
7657
|
+
},
|
|
7658
|
+
mcp: {
|
|
7659
|
+
description: `List and search a Conveyor project's decisions. Decided rows include their resolution, so this is the fastest way to answer "did we already settle this?". Pass projectId to target a specific project; otherwise the configured default project is used.`,
|
|
7660
|
+
fields: {
|
|
7661
|
+
projectId: mcpProjectId,
|
|
7662
|
+
status: f.optional(f.enum(STATUS_LABELS, { desc: "Filter by state" })),
|
|
7663
|
+
tag: f.optional(f.string({ desc: "Tag name to filter by" })),
|
|
7664
|
+
card: f.optional(f.string({ desc: "Card slug or id \u2014 decisions blocking that card" })),
|
|
7665
|
+
query: f.optional(f.string({ desc: "Text search on the question" })),
|
|
7666
|
+
limit: f.optional(
|
|
7667
|
+
f.number({ int: true, min: 1, max: 200, desc: "Max rows to return (default 50)" })
|
|
7668
|
+
)
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
7671
|
+
});
|
|
7672
|
+
var getDecisionContract = defineToolContract({
|
|
7673
|
+
name: "get_decision",
|
|
7674
|
+
agent: {
|
|
7675
|
+
description: "Read one decision in full: its context, every option with its GIVEN / WHEN / THEN, every vote with who cast it and why, the running tally, the cards it blocks, and the resolution once there is one. Use read_task_chat with the same id for the discussion thread.",
|
|
7676
|
+
fields: {
|
|
7677
|
+
decision: f.string({ min: 1, desc: "Decision slug or id" })
|
|
7678
|
+
}
|
|
7679
|
+
},
|
|
7680
|
+
mcp: {
|
|
7681
|
+
description: "Read one Conveyor decision in full \u2014 context, options, votes with reasons, tally, impacted cards, and the resolution. Pass projectId to disambiguate a slug; otherwise the configured default project is used.",
|
|
7682
|
+
fields: {
|
|
7683
|
+
projectId: mcpProjectId,
|
|
7684
|
+
decision: f.string({ desc: "Decision slug or id" })
|
|
7685
|
+
}
|
|
7686
|
+
}
|
|
7687
|
+
});
|
|
7688
|
+
var voteDecisionContract = defineToolContract({
|
|
7689
|
+
name: "vote_decision",
|
|
7690
|
+
agent: {
|
|
7691
|
+
description: "Vote on an open decision. One vote per voter: voting again replaces your previous one. Always give a reason \u2014 the resolution weighs reasons over counts, and a vote with no reason carries almost nothing.",
|
|
7692
|
+
fields: {
|
|
7693
|
+
decision: f.string({ min: 1, desc: "Decision slug or id" }),
|
|
7694
|
+
option: f.string({ min: 1, desc: "Option id, or its 1-based position" }),
|
|
7695
|
+
reason: f.optional(
|
|
7696
|
+
f.string({ desc: "Why this option, in one or two sentences (max 255 characters)" })
|
|
7697
|
+
)
|
|
7698
|
+
}
|
|
7699
|
+
},
|
|
7700
|
+
mcp: {
|
|
7701
|
+
description: "Vote on an open Conveyor decision, with a reason. One vote per voter \u2014 voting again replaces the previous one.",
|
|
7702
|
+
fields: {
|
|
7703
|
+
projectId: mcpProjectId,
|
|
7704
|
+
decision: f.string({ desc: "Decision slug or id" }),
|
|
7705
|
+
option: f.string({ desc: "Option id, or its 1-based position" }),
|
|
7706
|
+
reason: f.optional(f.string({ desc: "Why this option (max 255 characters)" }))
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
});
|
|
7710
|
+
var resolveDecisionContract = defineToolContract({
|
|
7711
|
+
name: "resolve_decision",
|
|
7712
|
+
agent: {
|
|
7713
|
+
description: "Settle a decision. This is not a formality: the resolution is written into the plan of every card the decision blocked, into each of their chats, and into the overview of every tag it carries, and those cards become startable. Say what was decided in one or two sentences \u2014 a reader months later should not need to open the thread.",
|
|
7714
|
+
fields: {
|
|
7715
|
+
decision: f.string({ min: 1, desc: "Decision slug or id" }),
|
|
7716
|
+
option: f.string({ min: 1, desc: "Winning option id, or its 1-based position" }),
|
|
7717
|
+
resolution: f.string({
|
|
7718
|
+
min: 1,
|
|
7719
|
+
desc: "What was decided, in one or two plain sentences (max 255 characters)"
|
|
7720
|
+
}),
|
|
7721
|
+
notes: f.optional(
|
|
7722
|
+
f.string({ desc: "Markdown reasoning. Link any verdict attachment here." })
|
|
7723
|
+
)
|
|
7724
|
+
}
|
|
7725
|
+
},
|
|
7726
|
+
mcp: {
|
|
7727
|
+
description: "Settle a Conveyor decision. The resolution is written into every impacted card's plan and chat and into its tags' overviews, and the impacted cards become startable.",
|
|
7728
|
+
fields: {
|
|
7729
|
+
projectId: mcpProjectId,
|
|
7730
|
+
decision: f.string({ desc: "Decision slug or id" }),
|
|
7731
|
+
option: f.string({ desc: "Winning option id, or its 1-based position" }),
|
|
7732
|
+
resolution: f.string({ desc: "What was decided (max 255 characters)" }),
|
|
7733
|
+
notes: f.optional(f.string({ desc: "Markdown reasoning" }))
|
|
7734
|
+
}
|
|
7735
|
+
}
|
|
7736
|
+
});
|
|
7737
|
+
var updateDecisionContract = defineToolContract({
|
|
7738
|
+
name: "update_decision",
|
|
7739
|
+
agent: {
|
|
7740
|
+
description: "Change an open decision: extend its deadline, link or unlink the cards it blocks, or cancel it. Cancelling un-blocks the impacted cards without answering the question, so only do it when the question stopped mattering.",
|
|
7741
|
+
fields: {
|
|
7742
|
+
decision: f.string({ min: 1, desc: "Decision slug or id" }),
|
|
7743
|
+
deadline_hours: f.optional(f.number({ desc: "New deadline, in hours from now. 1 to 720." })),
|
|
7744
|
+
add_impacted_cards: f.optional(
|
|
7745
|
+
f.array(f.string(), { desc: "Slugs or ids of cards to block on this decision" })
|
|
7746
|
+
),
|
|
7747
|
+
remove_impacted_cards: f.optional(
|
|
7748
|
+
f.array(f.string(), { desc: "Ids of cards to stop blocking" })
|
|
7749
|
+
),
|
|
7750
|
+
status: f.optional(
|
|
7751
|
+
f.enum(["Cancelled", "Open"], {
|
|
7752
|
+
desc: "Cancelled drops the question; Open reopens a settled one and clears its answer"
|
|
7753
|
+
})
|
|
7754
|
+
)
|
|
7755
|
+
}
|
|
7756
|
+
},
|
|
7757
|
+
mcp: {
|
|
7758
|
+
description: "Change an open Conveyor decision: extend the deadline, link or unlink impacted cards, cancel it, or reopen a settled one.",
|
|
7759
|
+
fields: {
|
|
7760
|
+
projectId: mcpProjectId,
|
|
7761
|
+
decision: f.string({ desc: "Decision slug or id" }),
|
|
7762
|
+
deadlineHours: f.optional(
|
|
7763
|
+
f.number({ min: 1, max: 720, desc: "New deadline, in hours from now" })
|
|
7764
|
+
),
|
|
7765
|
+
addImpactedCards: f.optional(
|
|
7766
|
+
f.array(f.string(), { desc: "Slugs or ids of cards to block on this decision" })
|
|
7767
|
+
),
|
|
7768
|
+
removeImpactedCards: f.optional(
|
|
7769
|
+
f.array(f.string(), { desc: "Ids of cards to stop blocking" })
|
|
7770
|
+
),
|
|
7771
|
+
status: f.optional(
|
|
7772
|
+
f.enum(["Cancelled", "Open"], { desc: "Cancel it, or reopen a settled one" })
|
|
7773
|
+
)
|
|
7774
|
+
}
|
|
7775
|
+
}
|
|
7776
|
+
});
|
|
7777
|
+
var decisionsContracts = [
|
|
7778
|
+
createDecisionContract,
|
|
7779
|
+
listDecisionsContract,
|
|
7780
|
+
getDecisionContract,
|
|
7781
|
+
voteDecisionContract,
|
|
7782
|
+
resolveDecisionContract,
|
|
7783
|
+
updateDecisionContract
|
|
7784
|
+
];
|
|
7555
7785
|
var createPullRequestContract = defineToolContract({
|
|
7556
7786
|
name: "create_pull_request",
|
|
7557
7787
|
agent: {
|
|
@@ -8212,6 +8442,7 @@ var TOOL_CONTRACTS = Object.fromEntries(
|
|
|
8212
8442
|
...subtasksContracts,
|
|
8213
8443
|
...attachmentsContracts,
|
|
8214
8444
|
...suggestionsContracts,
|
|
8445
|
+
...decisionsContracts,
|
|
8215
8446
|
...pullRequestContracts,
|
|
8216
8447
|
...integrationsContracts,
|
|
8217
8448
|
...driveContracts,
|
|
@@ -10464,6 +10695,143 @@ ${res.map(describeItem).join("\n")}`);
|
|
|
10464
10695
|
];
|
|
10465
10696
|
}
|
|
10466
10697
|
|
|
10698
|
+
// src/tools/decision-tools.ts
|
|
10699
|
+
function errText5(prefix, error) {
|
|
10700
|
+
return textResult(`${prefix}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
10701
|
+
}
|
|
10702
|
+
function buildDecisionTools(connection, projectId) {
|
|
10703
|
+
return [
|
|
10704
|
+
...readTools2(connection, projectId),
|
|
10705
|
+
...raiseAndVoteTools(connection, projectId),
|
|
10706
|
+
...settleTools(connection, projectId)
|
|
10707
|
+
];
|
|
10708
|
+
}
|
|
10709
|
+
function readTools2(connection, projectId) {
|
|
10710
|
+
return [
|
|
10711
|
+
defineContractTool(
|
|
10712
|
+
listDecisionsContract,
|
|
10713
|
+
async (input) => {
|
|
10714
|
+
try {
|
|
10715
|
+
const res = await connection.call("listProjectDecisions", {
|
|
10716
|
+
projectId,
|
|
10717
|
+
status: input.status,
|
|
10718
|
+
tag: input.tag,
|
|
10719
|
+
card: input.card,
|
|
10720
|
+
query: input.query,
|
|
10721
|
+
limit: input.limit
|
|
10722
|
+
});
|
|
10723
|
+
if (res.decisions.length === 0) {
|
|
10724
|
+
return textResult(
|
|
10725
|
+
"No decisions match. Nothing has been settled here, so you are free to choose \u2014 state the choice in the plan as a GIVEN / WHEN / THEN assumption, or raise a decision with create_decision if the plan cannot be right without an answer."
|
|
10726
|
+
);
|
|
10727
|
+
}
|
|
10728
|
+
return textResult(JSON.stringify(res.decisions, null, 2));
|
|
10729
|
+
} catch (error) {
|
|
10730
|
+
return errText5("Failed to list decisions", error);
|
|
10731
|
+
}
|
|
10732
|
+
},
|
|
10733
|
+
{ annotations: { readOnlyHint: true } }
|
|
10734
|
+
),
|
|
10735
|
+
defineContractTool(
|
|
10736
|
+
getDecisionContract,
|
|
10737
|
+
async (input) => {
|
|
10738
|
+
try {
|
|
10739
|
+
const res = await connection.call("getProjectDecision", {
|
|
10740
|
+
projectId,
|
|
10741
|
+
decision: input.decision
|
|
10742
|
+
});
|
|
10743
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
10744
|
+
} catch (error) {
|
|
10745
|
+
return errText5("Failed to read the decision", error);
|
|
10746
|
+
}
|
|
10747
|
+
},
|
|
10748
|
+
{ annotations: { readOnlyHint: true } }
|
|
10749
|
+
)
|
|
10750
|
+
];
|
|
10751
|
+
}
|
|
10752
|
+
function raiseAndVoteTools(connection, projectId) {
|
|
10753
|
+
return [
|
|
10754
|
+
defineContractTool(createDecisionContract, async (input) => {
|
|
10755
|
+
try {
|
|
10756
|
+
const res = await connection.call("createProjectDecision", {
|
|
10757
|
+
projectId,
|
|
10758
|
+
question: input.question,
|
|
10759
|
+
summary: input.summary,
|
|
10760
|
+
context: input.context,
|
|
10761
|
+
options: input.options,
|
|
10762
|
+
defaultOption: input.default_option,
|
|
10763
|
+
deadlineHours: input.deadline_hours,
|
|
10764
|
+
impactedCards: input.impacted_cards,
|
|
10765
|
+
includeCurrentCard: input.include_current_card,
|
|
10766
|
+
sessionId: connection.sessionId,
|
|
10767
|
+
tagNames: input.tag_names,
|
|
10768
|
+
raisedByPr: input.raised_by_pr
|
|
10769
|
+
});
|
|
10770
|
+
const refused = res.refused.length > 0 ? `
|
|
10771
|
+
|
|
10772
|
+
Not linked: ${res.refused.map((r) => `${r.ref} (${r.reason})`).join("; ")}` : "";
|
|
10773
|
+
return textResult(
|
|
10774
|
+
`Raised decision ${res.slug} \u2014 ${res.url}
|
|
10775
|
+
Blocking ${res.impactedLinked.length} card(s).${refused}`
|
|
10776
|
+
);
|
|
10777
|
+
} catch (error) {
|
|
10778
|
+
return errText5("Failed to raise the decision", error);
|
|
10779
|
+
}
|
|
10780
|
+
}),
|
|
10781
|
+
defineContractTool(voteDecisionContract, async (input) => {
|
|
10782
|
+
try {
|
|
10783
|
+
const res = await connection.call("voteProjectDecision", {
|
|
10784
|
+
projectId,
|
|
10785
|
+
decision: input.decision,
|
|
10786
|
+
option: input.option,
|
|
10787
|
+
reason: input.reason
|
|
10788
|
+
});
|
|
10789
|
+
const tally = res.options.map((o) => `${o.label}: ${o.votes}`).join(", ");
|
|
10790
|
+
return textResult(`Voted on ${res.slug}. Tally \u2014 ${tally}.`);
|
|
10791
|
+
} catch (error) {
|
|
10792
|
+
return errText5("Failed to vote", error);
|
|
10793
|
+
}
|
|
10794
|
+
})
|
|
10795
|
+
];
|
|
10796
|
+
}
|
|
10797
|
+
function settleTools(connection, projectId) {
|
|
10798
|
+
return [
|
|
10799
|
+
defineContractTool(resolveDecisionContract, async (input) => {
|
|
10800
|
+
try {
|
|
10801
|
+
const res = await connection.call("resolveProjectDecision", {
|
|
10802
|
+
projectId,
|
|
10803
|
+
decision: input.decision,
|
|
10804
|
+
option: input.option,
|
|
10805
|
+
resolution: input.resolution,
|
|
10806
|
+
notes: input.notes
|
|
10807
|
+
});
|
|
10808
|
+
return textResult(
|
|
10809
|
+
`Settled ${res.slug} as "${res.chosenOption}". ${res.impactedCount} card(s) were written back to and are no longer blocked.`
|
|
10810
|
+
);
|
|
10811
|
+
} catch (error) {
|
|
10812
|
+
return errText5("Failed to settle the decision", error);
|
|
10813
|
+
}
|
|
10814
|
+
}),
|
|
10815
|
+
defineContractTool(updateDecisionContract, async (input) => {
|
|
10816
|
+
try {
|
|
10817
|
+
const res = await connection.call("updateProjectDecision", {
|
|
10818
|
+
projectId,
|
|
10819
|
+
decision: input.decision,
|
|
10820
|
+
deadlineHours: input.deadline_hours,
|
|
10821
|
+
addImpactedCards: input.add_impacted_cards,
|
|
10822
|
+
removeImpactedCards: input.remove_impacted_cards,
|
|
10823
|
+
status: input.status
|
|
10824
|
+
});
|
|
10825
|
+
return textResult(
|
|
10826
|
+
`Updated ${res.slug} \u2014 ${res.status}, ${res.impactedCount} impacted card(s), deadline ${res.deadlineAt}.`
|
|
10827
|
+
);
|
|
10828
|
+
} catch (error) {
|
|
10829
|
+
return errText5("Failed to update the decision", error);
|
|
10830
|
+
}
|
|
10831
|
+
})
|
|
10832
|
+
];
|
|
10833
|
+
}
|
|
10834
|
+
|
|
10467
10835
|
// src/tools/connected-tools.ts
|
|
10468
10836
|
function connectedToolsFor(connection, context) {
|
|
10469
10837
|
const projectId = context?.projectId;
|
|
@@ -10473,6 +10841,9 @@ function connectedToolsFor(connection, context) {
|
|
|
10473
10841
|
buildListProjectIntegrationsTool(connection, projectId),
|
|
10474
10842
|
...context.chatChannelsConfigured ? buildChannelTools(connection, projectId) : [],
|
|
10475
10843
|
...context.meetingsAvailable ? buildMeetingTools(connection, projectId) : [],
|
|
10844
|
+
// Always available: the decision tables exist on every project, and a fork
|
|
10845
|
+
// an agent cannot file is a fork it assumes its way past instead.
|
|
10846
|
+
...buildDecisionTools(connection, projectId),
|
|
10476
10847
|
...context.googleAnalyticsConfigured ? [buildAnalyticsSummaryTool(connection, projectId)] : [],
|
|
10477
10848
|
...context.gcpLogsConfigured ? [buildQueryGcpLogsTool(connection, projectId)] : [],
|
|
10478
10849
|
...context.grafanaLogsConfigured ? [buildQueryGrafanaLogsTool(connection, projectId)] : []
|
|
@@ -11482,6 +11853,8 @@ function matchesReadOnlyBlocked(cmd) {
|
|
|
11482
11853
|
}
|
|
11483
11854
|
var POST_CHANNEL_TOOL = /(^|__)post_channel_message$/;
|
|
11484
11855
|
var CHANNEL_POST_DENIED = "Posting into a chat channel is not available in this mode. Reading channels still is \u2014 use read_channel_messages if you need the discussion.";
|
|
11856
|
+
var DECISION_SETTLE_TOOLS = /(^|__)(resolve_decision|update_decision)$/;
|
|
11857
|
+
var DECISION_SETTLE_DENIED = "Settling a decision is not available in review mode. Say on the PR that the change turns on an unsettled decision, and cite it with list_decisions \u2014 a reviewer does not make the product call.";
|
|
11485
11858
|
function handleReadOnlyToolAccess(toolName, input) {
|
|
11486
11859
|
if (POST_CHANNEL_TOOL.test(toolName)) {
|
|
11487
11860
|
return { behavior: "deny", message: CHANNEL_POST_DENIED };
|
|
@@ -11531,6 +11904,9 @@ function handleReviewToolAccess(toolName, input) {
|
|
|
11531
11904
|
if (POST_CHANNEL_TOOL.test(toolName)) {
|
|
11532
11905
|
return { behavior: "deny", message: CHANNEL_POST_DENIED };
|
|
11533
11906
|
}
|
|
11907
|
+
if (DECISION_SETTLE_TOOLS.test(toolName)) {
|
|
11908
|
+
return { behavior: "deny", message: DECISION_SETTLE_DENIED };
|
|
11909
|
+
}
|
|
11534
11910
|
return handleBuildingToolAccess(toolName, input);
|
|
11535
11911
|
}
|
|
11536
11912
|
function handleAutoToolAccess(toolName, input, hasExitedPlanMode, isParentTask) {
|
|
@@ -14075,8 +14451,8 @@ var SessionRunner = class _SessionRunner {
|
|
|
14075
14451
|
"[conveyor-agent] Completed \u2014 entering dormant idle (staying connected)\n"
|
|
14076
14452
|
);
|
|
14077
14453
|
}
|
|
14454
|
+
this.pendingMessages = this.pendingMessages.filter((msg) => msg.source === "pty_passive");
|
|
14078
14455
|
await this.flushWipNow("WIP: turn complete");
|
|
14079
|
-
this.pendingMessages.length = 0;
|
|
14080
14456
|
if (this._state !== "idle") await this.setState("idle");
|
|
14081
14457
|
const remainingMs = Math.max(0, this.dormantDeadline - Date.now());
|
|
14082
14458
|
this.lifecycle.startDormantTimer(remainingMs);
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
WorkspaceCommandSupervisor,
|
|
8
8
|
startWorkspaceCommandsAfterConnect,
|
|
9
9
|
stopWorkspaceCommands
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LBB7DXOC.js";
|
|
11
11
|
import {
|
|
12
12
|
PtyHarness,
|
|
13
13
|
SessionRunner,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
runUsageProbe,
|
|
26
26
|
sampleKeyUsage,
|
|
27
27
|
usesNativeUsageReporting
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-XJSW42TM.js";
|
|
29
29
|
import "./chunk-37J5MMQT.js";
|
|
30
30
|
import {
|
|
31
31
|
inheritedEnv,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
createServiceLogger,
|
|
45
45
|
fetchBootstrap,
|
|
46
46
|
loadConveyorConfig
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-I7QYTA2V.js";
|
|
48
48
|
import "./chunk-Q4FQOJ7D.js";
|
|
49
49
|
import "./chunk-W4LZ7R6Z.js";
|
|
50
50
|
import "./chunk-372R6E4C.js";
|
|
@@ -1205,7 +1205,7 @@ function wireSpawnChildren(mode, connection, supervisors, logger7) {
|
|
|
1205
1205
|
|
|
1206
1206
|
// src/cli.ts
|
|
1207
1207
|
if (process.argv[2] === "boot") {
|
|
1208
|
-
const { runBoot } = await import("./boot-
|
|
1208
|
+
const { runBoot } = await import("./boot-S73ZNWBJ.js");
|
|
1209
1209
|
process.exit(await runBoot(process.argv.slice(3)));
|
|
1210
1210
|
}
|
|
1211
1211
|
if (isLegacyEntrypointLaunch(process.env)) {
|
|
@@ -1458,7 +1458,7 @@ if (!RUNNER_MODES.includes(CONVEYOR_MODE)) {
|
|
|
1458
1458
|
process.exit(1);
|
|
1459
1459
|
}
|
|
1460
1460
|
if (CONVEYOR_MODE === "serving") {
|
|
1461
|
-
const { runServingSession } = await import("./serve-boot-
|
|
1461
|
+
const { runServingSession } = await import("./serve-boot-ODH57W6I.js");
|
|
1462
1462
|
exitContext.runnerMode = "serving";
|
|
1463
1463
|
exitContext.sessionId = process.env.CONVEYOR_SESSION_ID ?? exitContext.sessionId;
|
|
1464
1464
|
const outcome = await runServingSession({
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SessionRunner,
|
|
3
3
|
unshallowRepo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XJSW42TM.js";
|
|
5
5
|
import "./chunk-37J5MMQT.js";
|
|
6
6
|
import "./chunk-W5INK3NE.js";
|
|
7
7
|
import {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stageAndCommit,
|
|
18
18
|
updateRemoteToken,
|
|
19
19
|
workspacePathExists
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-I7QYTA2V.js";
|
|
21
21
|
import "./chunk-Q4FQOJ7D.js";
|
|
22
22
|
import {
|
|
23
23
|
runAuthTokenCommand,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
WorkspaceCommandSupervisor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LBB7DXOC.js";
|
|
4
4
|
import {
|
|
5
5
|
AgentConnection,
|
|
6
6
|
CodespacePortVisibility,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
createServiceLogger,
|
|
12
12
|
ensureOnTaskBranch,
|
|
13
13
|
loadConveyorConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-I7QYTA2V.js";
|
|
15
15
|
import "./chunk-Q4FQOJ7D.js";
|
|
16
16
|
import "./chunk-W4LZ7R6Z.js";
|
|
17
17
|
import "./chunk-IA45XHOA.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rallycry/conveyor-agent",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.23",
|
|
4
4
|
"description": "Conveyor Agent Runner v10 - PTY harness for the task chat (SDK harness for audit/project-chat). Agent-as-User architecture with BaseService patterns. Works locally too.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -215,6 +215,27 @@ behavior — do not go looking for why "unrelated" tests are running.
|
|
|
215
215
|
> more broadly; if it does, let CI finish before merging. Confirm which case
|
|
216
216
|
> you are in rather than assuming, since the two lead to opposite behavior.
|
|
217
217
|
|
|
218
|
+
## A plan that turns on an unsettled question
|
|
219
|
+
|
|
220
|
+
A plan you cannot execute correctly because a product choice was never made is
|
|
221
|
+
not a blocker by itself. Check `list_decisions(card: "<slug>")` and the owning
|
|
222
|
+
tag's overview first — the answer is often already there, and citing it is the
|
|
223
|
+
whole point of the type. When it genuinely is not:
|
|
224
|
+
|
|
225
|
+
- **Most of the time, decide and say so.** Write the choice into the plan as a
|
|
226
|
+
GIVEN / WHEN / THEN line under "Assumed behaviours" and keep building. A
|
|
227
|
+
reviewer who disagrees says so, and it costs one comment.
|
|
228
|
+
- **Raise a decision when the answer changes what you build** and getting it
|
|
229
|
+
wrong means rework rather than a follow-up commit. `create_decision` blocks
|
|
230
|
+
this card by default, so proceed on the decision's default option and mark
|
|
231
|
+
the plan `Planned: <default> pending <decision url>` — the answer is written
|
|
232
|
+
back onto this card's plan when it settles.
|
|
233
|
+
- **Park only when building on the default would be thrown away.** That is the
|
|
234
|
+
Blocked path below.
|
|
235
|
+
|
|
236
|
+
The bar, and how to write options somebody can actually choose between, is in
|
|
237
|
+
[../conveyor-plan/references/product-decisions.md](../conveyor-plan/references/product-decisions.md).
|
|
238
|
+
|
|
218
239
|
## Not every task ends in a PR
|
|
219
240
|
|
|
220
241
|
`create_pull_request` is for work that changes code. Plenty of cards don't:
|
|
@@ -30,7 +30,8 @@ Announce a one-line plan naming the sources you found, then sweep. **Say what yo
|
|
|
30
30
|
| Source | How |
|
|
31
31
|
| ------ | --- |
|
|
32
32
|
| Work channels | `read_channel_messages` per registered readable channel, paging back with `olderCursor` until the window covers the question. `authorIsBot` separates the team's discussion from Conveyor's own card feed — check it before treating a message as a teammate's. Read thread replies (`threadTs`) where a thread carries the argument. |
|
|
33
|
-
| Conveyor cards | `search_tasks` with ALL `typeFilters` (task, incident, suggestion) and several keyword variants — the term, the term plus symptom words, the adjacent nouns people actually use. Incidents carry fingerprint dedup, so an incident's upvote count is itself a frequency signal.
|
|
33
|
+
| Conveyor cards | `search_tasks` with ALL `typeFilters` (task, incident, suggestion) and several keyword variants — the term, the term plus symptom words, the adjacent nouns people actually use. Incidents carry fingerprint dedup, so an incident's upvote count is itself a frequency signal. |
|
|
34
|
+
| Decisions | `list_decisions` — open ones are the questions on the table, Decided ones carry their resolution and are the project's own record of what it already settled. A verdict that contradicts a Decided decision has to say so and say why. `list_project_integrations` reports `decisions.openCount` / `decidedCount`, so you know whether there is a log here before you search it. |
|
|
34
35
|
| Tags | `list_tags` then `get_tag` on the relevant ones — the overview is the project's own domain vocabulary, and it names the subsystems your categories should line up with. |
|
|
35
36
|
| Card chat | `read_task_chat` on the cards the search surfaced. The argument usually lives in the chat, not the description. |
|
|
36
37
|
| Meetings | If the meeting tools exist, list and read the ones in the window. A transcript is the densest source of "what people actually said" you will find. |
|
|
@@ -83,6 +84,48 @@ Chart rules that repeatedly matter: plain div/CSS charts over JS; one strong cha
|
|
|
83
84
|
4. **Local sessions may also publish an Artifact** for a shareable URL, and `SendUserFile` the HTML — both are additive. In a pod neither exists; the attachment is the deliverable.
|
|
84
85
|
5. **Revisions re-upload to the SAME card.** The doc is living; never fork it into `report-v2.html`.
|
|
85
86
|
|
|
87
|
+
## Decision mode
|
|
88
|
+
|
|
89
|
+
When the argument IS a decision card — the user named one, or the sweep found
|
|
90
|
+
an open decision that this question is about — the shape changes. The doc is
|
|
91
|
+
not a survey of opinion; it is the input to settling a specific question that
|
|
92
|
+
has options, a deadline, and a default that wins if nobody acts.
|
|
93
|
+
|
|
94
|
+
**Sources, in place of the Phase 1 table:**
|
|
95
|
+
|
|
96
|
+
| Source | How |
|
|
97
|
+
| ------ | --- |
|
|
98
|
+
| The decision | `get_decision` — the context, every option's GIVEN / WHEN / THEN, every vote with who cast it and why, the running tally, and the cards it blocks. |
|
|
99
|
+
| The thread | `read_task_chat` on the decision. The Slack and Discord replies already mirror here, so this is the whole discussion in one read. |
|
|
100
|
+
| The impacted cards | Their plans say what each option would cost to build. A clean option that nobody can implement is not the cheap one. |
|
|
101
|
+
| Everything else | The ordinary sweep above, scoped to the question. |
|
|
102
|
+
|
|
103
|
+
**Weigh reasons, not counts.** Two people giving the same reason are one
|
|
104
|
+
argument. A single vote with a concrete failure case outranks three with none.
|
|
105
|
+
Say so explicitly in the verdict — a decision settled on a head count that
|
|
106
|
+
contradicted the reasoning is the thing this mode exists to prevent.
|
|
107
|
+
|
|
108
|
+
**Then settle it, in this order:**
|
|
109
|
+
|
|
110
|
+
1. Attach the verdict HTML. **Where it lands depends on the surface**, and the
|
|
111
|
+
difference matters: a local MCP session's `upload_attachment` takes a
|
|
112
|
+
`taskId`, so send it to the DECISION card, titled with the question. The
|
|
113
|
+
in-pod tool takes no card argument and always posts to the session's own
|
|
114
|
+
card — so in a pod, attach it there and put the link in the resolution
|
|
115
|
+
notes rather than pretending it reached the decision.
|
|
116
|
+
2. `resolve_decision` with the winning option, a resolution of one or two
|
|
117
|
+
plain sentences (255 characters), and `notes` linking wherever the
|
|
118
|
+
attachment actually landed.
|
|
119
|
+
|
|
120
|
+
Step 2 is what makes it real: the resolution is written into the plan and the
|
|
121
|
+
chat of every card the decision blocked and into the owning tags' overviews,
|
|
122
|
+
and those cards become startable. Do not stop at the doc.
|
|
123
|
+
|
|
124
|
+
**If the evidence does not settle it**, say that and do not resolve. Extend the
|
|
125
|
+
deadline with `update_decision` and post what would settle it. A verdict of
|
|
126
|
+
"this needs one more data point, and here is which" is a real answer; picking
|
|
127
|
+
an option to look decisive is not.
|
|
128
|
+
|
|
86
129
|
## Phase 5: the consensus loop
|
|
87
130
|
|
|
88
131
|
The doc is the midpoint, not the end. Expect and serve:
|
|
@@ -121,6 +121,17 @@ Ask the user only decisions that change the plan's shape — scope cuts, UX
|
|
|
121
121
|
choices, irreversible tradeoffs. Batch them in one round; never drip. Facts
|
|
122
122
|
the repo can answer are yours to find, not theirs.
|
|
123
123
|
|
|
124
|
+
**Search the decision log first, and raise one when the user is not there.**
|
|
125
|
+
`list_decisions(status: "Decided", tag: "<tag>")` often answers the question
|
|
126
|
+
outright — an answered decision is the answer, and re-asking it is the waste
|
|
127
|
+
this exists to stop. When it does not, and you are running headless, prefer
|
|
128
|
+
`create_decision` over blocking on `AskUserQuestion`: a decision has a
|
|
129
|
+
deadline, so it settles on its default rather than stalling, and it links the
|
|
130
|
+
cards it blocks. Most forks are not decisions at all — see
|
|
131
|
+
[references/product-decisions.md](references/product-decisions.md) for the bar,
|
|
132
|
+
how to write options a person can actually choose between, and how to keep
|
|
133
|
+
building while one is open.
|
|
134
|
+
|
|
124
135
|
## Phase 3 — Draft the plan
|
|
125
136
|
|
|
126
137
|
Use the plan format in [references/plan-format.md](references/plan-format.md):
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Product decisions
|
|
2
|
+
|
|
3
|
+
A product decision is a fork the plan cannot be right without — where two
|
|
4
|
+
answers lead to two different, both-defensible builds, and picking one silently
|
|
5
|
+
means somebody finds out at review. Conveyor has a card type for it:
|
|
6
|
+
`create_decision` raises one with two or three options, a default, and a
|
|
7
|
+
deadline; `list_decisions` finds the answer next time.
|
|
8
|
+
|
|
9
|
+
Shared by the plan, triage, build, and review skills. The rules are the same
|
|
10
|
+
wherever you are.
|
|
11
|
+
|
|
12
|
+
## 1. Search before you ask
|
|
13
|
+
|
|
14
|
+
A decision that was settled once must never be re-litigated. Before planning
|
|
15
|
+
anything in an area:
|
|
16
|
+
|
|
17
|
+
1. Read the owning tag's overview (`get_tag`) — a settled decision is written
|
|
18
|
+
into a `## Decisions` section there.
|
|
19
|
+
2. `list_decisions(status: "Decided", tag: "<tag>")` — every row carries its
|
|
20
|
+
resolution, so the list IS the decision log.
|
|
21
|
+
3. `list_decisions(card: "<slug>")` — what is blocking this card right now.
|
|
22
|
+
|
|
23
|
+
Cite what you find. "We decided this on 2026-09-19: <resolution>" ends the
|
|
24
|
+
question; re-opening it without new information wastes everybody's time.
|
|
25
|
+
|
|
26
|
+
## 2. Most forks are not decisions
|
|
27
|
+
|
|
28
|
+
The common case is an assumption you can simply state. Write it in the plan
|
|
29
|
+
under **Assumed behaviours**, one GIVEN / WHEN / THEN line each:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
## Assumed behaviours
|
|
33
|
+
- GIVEN a decision with no votes, WHEN its deadline passes, THEN the default option wins.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A reviewer who disagrees says so, and it costs one comment. That is cheaper
|
|
37
|
+
than a card, a deadline, and three people's attention.
|
|
38
|
+
|
|
39
|
+
Raise a decision only when **all** of these hold:
|
|
40
|
+
|
|
41
|
+
- Two or three options are genuinely defensible — you cannot pick on merit.
|
|
42
|
+
- Getting it wrong means rework, not a follow-up commit.
|
|
43
|
+
- Somebody other than you has to live with the answer.
|
|
44
|
+
|
|
45
|
+
## 3. Writing one that is answerable
|
|
46
|
+
|
|
47
|
+
- **The question is one plain sentence**, ending in a question mark. If it
|
|
48
|
+
needs two, it is two decisions.
|
|
49
|
+
- **Two or three options.** More than three means the question is not framed
|
|
50
|
+
yet: narrow it, or split it.
|
|
51
|
+
- **Each option is GIVEN / WHEN / THEN** — what the product does, concretely,
|
|
52
|
+
if this option wins. "Option B: use a queue" is not an option; "GIVEN a
|
|
53
|
+
second build request, WHEN one is already running, THEN it queues rather
|
|
54
|
+
than being refused" is.
|
|
55
|
+
- **Mark the status quo.** The option describing what the code does today gets
|
|
56
|
+
`kind: "current"`; an already-agreed-but-unbuilt one gets `kind: "planned"`.
|
|
57
|
+
A reader six months later needs to know whether the winner was a change.
|
|
58
|
+
- **Name the default and what it costs if wrong.** The default wins on
|
|
59
|
+
silence. If silence would be dangerous, say so in the context — and set a
|
|
60
|
+
shorter deadline.
|
|
61
|
+
- **Link the cards it blocks.** They cannot start until it settles, and they
|
|
62
|
+
un-block the moment it does. In a pod this includes your own card by
|
|
63
|
+
default.
|
|
64
|
+
- **Tag it.** The settled answer is written into each tag's overview, which is
|
|
65
|
+
how the next agent finds it without knowing the decision exists.
|
|
66
|
+
|
|
67
|
+
## 4. Keep moving
|
|
68
|
+
|
|
69
|
+
Raising a decision is not a reason to stop. Either:
|
|
70
|
+
|
|
71
|
+
- **Proceed on the default.** Mark the plan: `Planned: <default option>
|
|
72
|
+
pending <decision url>`. If the decision settles differently, the write-back
|
|
73
|
+
lands on this card's plan and the card is un-blocked with the correct answer.
|
|
74
|
+
- **Park**, per the parked protocol in
|
|
75
|
+
`conveyor-build/references/pack-path.md`, when building on the default would
|
|
76
|
+
be actively unsafe or would be thrown away.
|
|
77
|
+
|
|
78
|
+
Never sit idle waiting for a vote.
|
|
79
|
+
|
|
80
|
+
## 5. Settling one
|
|
81
|
+
|
|
82
|
+
`resolve_decision` is not a formality. The resolution is written into the plan
|
|
83
|
+
and the chat of every card the decision blocked, and into the overview of every
|
|
84
|
+
tag it carries — and those cards become startable. So:
|
|
85
|
+
|
|
86
|
+
- Write the resolution as one or two plain sentences a non-engineer can read.
|
|
87
|
+
A reader months later should not have to open the thread.
|
|
88
|
+
- Weigh the reasons, not the count. Two people with the same reason are one
|
|
89
|
+
argument.
|
|
90
|
+
- Put the reasoning in `notes`, and link any verdict attachment there.
|
|
91
|
+
|
|
92
|
+
**A reviewer does not settle decisions.** A review runs against a diff that
|
|
93
|
+
already exists. If the diff turns on an unsettled decision, say so on the PR
|
|
94
|
+
and cite it — `resolve_decision` is refused in review mode for that reason.
|
|
95
|
+
|
|
96
|
+
## 6. When nobody answers
|
|
97
|
+
|
|
98
|
+
At the deadline, the sweeper settles it: the plurality winner, or the default
|
|
99
|
+
on a tie or an empty ballot, stamped `resolvedHow: "deadline"`. Silence becomes
|
|
100
|
+
a decision rather than a stall. A reminder lands in the thread a day before.
|
|
101
|
+
|
|
102
|
+
That is why the default is required, and why it has to be the answer you can
|
|
103
|
+
live with.
|
|
@@ -115,6 +115,17 @@ You have write access. Use it in proportion:
|
|
|
115
115
|
|
|
116
116
|
Fixing something you do not fully understand is worse than flagging it.
|
|
117
117
|
|
|
118
|
+
**A finding that turns on a product choice is neither.** When the diff is
|
|
119
|
+
defensible and the disagreement is really "should the product do X or Y", check
|
|
120
|
+
`list_decisions` first: if it was settled, cite the resolution and the diff is
|
|
121
|
+
either right or wrong on the record. If it was never settled, say so in the
|
|
122
|
+
verdict and name the fork — do NOT settle it yourself. `resolve_decision` is
|
|
123
|
+
refused in review mode for that reason: a review runs on a fixed budget against
|
|
124
|
+
a diff that already exists, and settling a decision writes that answer into
|
|
125
|
+
every card it was blocking. Raising one is fine if the fork is real; the author
|
|
126
|
+
or a moderator settles it. See
|
|
127
|
+
[../conveyor-plan/references/product-decisions.md](../conveyor-plan/references/product-decisions.md).
|
|
128
|
+
|
|
118
129
|
## The verdict
|
|
119
130
|
|
|
120
131
|
> **Environment — the tools differ, and only one pair exists per surface.**
|
|
@@ -144,6 +144,14 @@ is still unknown — including what evidence would resolve it. Then move it to
|
|
|
144
144
|
`Open`. "Needs a repro with the console open" is an actionable handoff;
|
|
145
145
|
"couldn't reproduce" is not.
|
|
146
146
|
|
|
147
|
+
**When the unknown is a product choice, not missing evidence**, the handoff is
|
|
148
|
+
a decision rather than a triage note. "The code does X, the reporter expected
|
|
149
|
+
Y, and both are defensible" is not something more logs will settle — raise it
|
|
150
|
+
with `create_decision`, two or three options written GIVEN / WHEN / THEN, and
|
|
151
|
+
link this card as impacted so it un-blocks the moment the question is answered.
|
|
152
|
+
See
|
|
153
|
+
[../conveyor-plan/references/product-decisions.md](../conveyor-plan/references/product-decisions.md).
|
|
154
|
+
|
|
147
155
|
**Cancelling:** explain the actual behavior, then cancel.
|
|
148
156
|
|
|
149
157
|
## 7. File at least one suggestion — always
|
|
@@ -38,9 +38,22 @@ and let Conveyor's own automation do the linking.
|
|
|
38
38
|
it (post your context to its chat) rather than forking a duplicate.
|
|
39
39
|
- **Classify**: buildable work → `mcp__conveyor__create_task`; an
|
|
40
40
|
idea/improvement you are NOT committing to build →
|
|
41
|
-
`mcp__conveyor__create_suggestion`;
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
`mcp__conveyor__create_suggestion`; a product fork that has to be settled
|
|
42
|
+
before the work can be planned correctly → `mcp__conveyor__create_decision`;
|
|
43
|
+
incidents
|
|
44
|
+
(production breakage) are filed by monitoring and users through Conveyor's
|
|
45
|
+
incident tooling — you will usually *work* incident cards, not create them.
|
|
46
|
+
- **Decisions**: a decision card holds one question, two or three options
|
|
47
|
+
written GIVEN / WHEN / THEN, a default that wins on silence, and a deadline.
|
|
48
|
+
It BLOCKS the cards it names until it settles, and un-blocks them the moment
|
|
49
|
+
it does — the answer is written into each of their plans and into the owning
|
|
50
|
+
tags' overviews, so the next agent finds it without knowing the decision
|
|
51
|
+
existed. Search with `mcp__conveyor__list_decisions` BEFORE planning in an
|
|
52
|
+
area: a settled
|
|
53
|
+
decision is the answer, and re-asking it is the waste the type exists to
|
|
54
|
+
stop. Most forks are not decisions — an assumption you can simply state
|
|
55
|
+
belongs in the plan as a GIVEN / WHEN / THEN line. The full bar is in
|
|
56
|
+
[../conveyor-plan/references/product-decisions.md](../conveyor-plan/references/product-decisions.md).
|
|
44
57
|
- **Mechanics**: `create_task` takes the title, description, `plan`
|
|
45
58
|
(markdown), and optional status/tags; cards start in `Planning`. Every
|
|
46
59
|
status change you make goes through `mcp__conveyor__update_task`
|