@tablation/crew 0.78.0 → 0.79.0
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/cli.js
CHANGED
|
@@ -230,6 +230,13 @@ function extractTurnTokens(line) {
|
|
|
230
230
|
if (!usage2) return void 0;
|
|
231
231
|
return (usage2.input_tokens ?? 0) + (usage2.output_tokens ?? 0) + (usage2.cache_read_input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
|
|
232
232
|
}
|
|
233
|
+
function extractPrefixTokens(line) {
|
|
234
|
+
const obj = line;
|
|
235
|
+
if (!obj || obj.type !== "assistant") return void 0;
|
|
236
|
+
const usage2 = obj.message?.usage;
|
|
237
|
+
if (!usage2) return void 0;
|
|
238
|
+
return (usage2.input_tokens ?? 0) + (usage2.cache_creation_input_tokens ?? 0);
|
|
239
|
+
}
|
|
233
240
|
function extractResult(line) {
|
|
234
241
|
const obj = line;
|
|
235
242
|
if (!obj || obj.type !== "result") return void 0;
|
|
@@ -580,6 +587,7 @@ var claudeAdapter = {
|
|
|
580
587
|
mapStreamLine,
|
|
581
588
|
extractResult,
|
|
582
589
|
extractTurnTokens,
|
|
590
|
+
extractPrefixTokens,
|
|
583
591
|
inspectMcp(o) {
|
|
584
592
|
const s = inspectClaudeMcp(o);
|
|
585
593
|
return s.state === "no-claude" ? { state: "no-binary" } : s;
|
|
@@ -5114,6 +5122,8 @@ async function reportAgentRun(target, run2) {
|
|
|
5114
5122
|
cacheReadTokens: run2.cacheReadTokens,
|
|
5115
5123
|
cacheWriteTokens: run2.cacheWriteTokens,
|
|
5116
5124
|
costUsd: run2.costUsd,
|
|
5125
|
+
numTurns: run2.numTurns,
|
|
5126
|
+
prefixTokens: run2.prefixTokens,
|
|
5117
5127
|
promptVersion: run2.promptVersion,
|
|
5118
5128
|
promptSha: run2.promptSha
|
|
5119
5129
|
};
|
|
@@ -5271,24 +5281,31 @@ function ticketMentions(text) {
|
|
|
5271
5281
|
}
|
|
5272
5282
|
var TicketAttributionTracker = class {
|
|
5273
5283
|
#tokensByTicket = /* @__PURE__ */ new Map();
|
|
5284
|
+
#turns = 0;
|
|
5274
5285
|
#current;
|
|
5275
5286
|
/** Tracker record uuid → ticket key, learned when a write names both (a claim resolves the key from the last mention). */
|
|
5276
5287
|
#keyByRecord = /* @__PURE__ */ new Map();
|
|
5277
5288
|
#lastBy = {};
|
|
5278
|
-
/**
|
|
5289
|
+
/**
|
|
5290
|
+
* The ticket key most recently named in the transcript, or undefined before any.
|
|
5291
|
+
* Only the FIRST key in a turn counts: a closing summary names its own
|
|
5292
|
+
* ticket first and then lists others ("Other tickets", "Blocked by", "See
|
|
5293
|
+
* also"), and those trailing keys are not what the run worked (CREW-2598).
|
|
5294
|
+
*/
|
|
5279
5295
|
get current() {
|
|
5280
5296
|
return this.#current;
|
|
5281
5297
|
}
|
|
5282
5298
|
/** Record the mentions in `text` without crediting tokens (a claim needs "which ticket" before the turn's usage line arrives). */
|
|
5283
5299
|
note(text) {
|
|
5284
5300
|
const mentions = ticketMentions(text);
|
|
5285
|
-
if (mentions.length > 0) this.#current = mentions[
|
|
5301
|
+
if (mentions.length > 0) this.#current = mentions[0];
|
|
5286
5302
|
}
|
|
5287
5303
|
/** One assistant turn: its combined visible text (thinking + text blocks) and its own token cost. */
|
|
5288
5304
|
add(text, tokens) {
|
|
5289
5305
|
const mentions = ticketMentions(text);
|
|
5290
|
-
if (mentions.length > 0) this.#current = mentions[
|
|
5306
|
+
if (mentions.length > 0) this.#current = mentions[0];
|
|
5291
5307
|
if (this.#current === void 0 || tokens <= 0) return;
|
|
5308
|
+
this.#turns++;
|
|
5292
5309
|
this.#tokensByTicket.set(this.#current, (this.#tokensByTicket.get(this.#current) ?? 0) + tokens);
|
|
5293
5310
|
}
|
|
5294
5311
|
/**
|
|
@@ -5310,16 +5327,31 @@ var TicketAttributionTracker = class {
|
|
|
5310
5327
|
/**
|
|
5311
5328
|
* How the attribution is decided: a hand-off write beats a claim, a claim
|
|
5312
5329
|
* beats a comment (so a follow-up ticket filed or commented on while
|
|
5313
|
-
* working never displaces the claimed one), and any write beats
|
|
5314
|
-
*
|
|
5315
|
-
*
|
|
5330
|
+
* working never displaces the claimed one), and any write beats everything
|
|
5331
|
+
* below. Next, `hintMoved` — the post-run read-back showed the hinted
|
|
5332
|
+
* ticket changed status or assignee during the run — is a hand-off signal
|
|
5333
|
+
* for a run that wrote through MCP tools crew cannot see. Then the
|
|
5334
|
+
* mention-token heuristic, which with a hint may override it only when a
|
|
5335
|
+
* different ticket holds a strict majority of the credited tokens (a single
|
|
5336
|
+
* credited turn, like a lone end-of-run summary naming another ticket,
|
|
5337
|
+
* must not). A run with no hint
|
|
5338
|
+
* keeps the plain winner. The pre-run hint is last.
|
|
5316
5339
|
*/
|
|
5317
|
-
decide(hint) {
|
|
5340
|
+
decide(hint, opts = {}) {
|
|
5318
5341
|
for (const by of ["handoff", "claim", "comment"]) {
|
|
5319
5342
|
const ticket = this.#lastBy[by];
|
|
5320
5343
|
if (ticket) return { ticket, by };
|
|
5321
5344
|
}
|
|
5322
|
-
if (
|
|
5345
|
+
if (hint && opts.hintMoved) return { ticket: hint, by: "readback" };
|
|
5346
|
+
if (this.#tokensByTicket.size > 0) {
|
|
5347
|
+
if (!hint) return { ticket: this.winner(), by: "mentions" };
|
|
5348
|
+
const winner = this.winner(hint);
|
|
5349
|
+
if (winner === hint) return { ticket: hint, by: "mentions" };
|
|
5350
|
+
let total = 0;
|
|
5351
|
+
for (const n of this.#tokensByTicket.values()) total += n;
|
|
5352
|
+
const share = (this.#tokensByTicket.get(winner) ?? 0) / total;
|
|
5353
|
+
return share > 0.5 && this.#turns > 1 ? { ticket: winner, by: "mentions" } : { ticket: hint, by: "hint" };
|
|
5354
|
+
}
|
|
5323
5355
|
return { ticket: hint, by: "hint" };
|
|
5324
5356
|
}
|
|
5325
5357
|
/**
|
|
@@ -7099,6 +7131,28 @@ async function finishRunWorktree(dir, cfg, branch) {
|
|
|
7099
7131
|
// src/handoff-guard.ts
|
|
7100
7132
|
var HANDOFF_BOUNCED_MARKER = calloutHeader("WARNING", "Hand-off Bounced");
|
|
7101
7133
|
var pushedEventBody = (sha, remote, branch) => `pushed ${sha} to ${remote}/${branch}`;
|
|
7134
|
+
function acceptanceLines(description) {
|
|
7135
|
+
if (typeof description !== "string") return [];
|
|
7136
|
+
const out = [];
|
|
7137
|
+
let inside = false;
|
|
7138
|
+
for (const raw of description.split("\n")) {
|
|
7139
|
+
const heading = /^#{1,6}\s+(.*?)\s*$/.exec(raw);
|
|
7140
|
+
if (heading) {
|
|
7141
|
+
inside = /^acceptance\b/i.test(heading[1]);
|
|
7142
|
+
continue;
|
|
7143
|
+
}
|
|
7144
|
+
if (!inside) continue;
|
|
7145
|
+
const bullet = /^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.+?)\s*$/.exec(raw);
|
|
7146
|
+
if (bullet) out.push(bullet[1]);
|
|
7147
|
+
}
|
|
7148
|
+
return out;
|
|
7149
|
+
}
|
|
7150
|
+
var flat = (s) => s.replace(/[`*_>]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
7151
|
+
function unquotedAcceptance(lines, comment) {
|
|
7152
|
+
const body = flat(comment);
|
|
7153
|
+
return lines.filter((l) => !body.includes(flat(l)));
|
|
7154
|
+
}
|
|
7155
|
+
var ACCEPTANCE_BOUNCED_MARKER = calloutHeader("WARNING", "Acceptance Checklist Missing");
|
|
7102
7156
|
function remoteTip(dir, remote, branch) {
|
|
7103
7157
|
const out = gitOk(dir, ["ls-remote", "--heads", remote, `refs/heads/${branch}`]);
|
|
7104
7158
|
if (out === null) return null;
|
|
@@ -7174,6 +7228,27 @@ async function takenOverSince(i, ticketRow) {
|
|
|
7174
7228
|
return !!by && by !== i.shipName;
|
|
7175
7229
|
});
|
|
7176
7230
|
}
|
|
7231
|
+
async function missingAcceptance(i, t) {
|
|
7232
|
+
const lines = acceptanceLines(t.description);
|
|
7233
|
+
if (lines.length === 0 || !i.writer.comments) return null;
|
|
7234
|
+
const cc = i.contract.comments;
|
|
7235
|
+
const mine = (await i.writer.comments()).map((c) => c).filter((r) => r[cc.parent] === t.id && r[cc.author] === i.seatId).sort((a, b) => String(a[cc.createdAt] ?? "").localeCompare(String(b[cc.createdAt] ?? "")));
|
|
7236
|
+
const last = mine.at(-1);
|
|
7237
|
+
const missing = unquotedAcceptance(lines, last ? String(last[cc.body] ?? "") : "");
|
|
7238
|
+
return missing.length > 0 ? missing : null;
|
|
7239
|
+
}
|
|
7240
|
+
function acceptanceBounceComment(missing, shipName2) {
|
|
7241
|
+
return `${ACCEPTANCE_BOUNCED_MARKER}
|
|
7242
|
+
|
|
7243
|
+
**Hand-off bounced: the hand-off comment does not quote every line of the ticket's Acceptance section.**
|
|
7244
|
+
|
|
7245
|
+
${shipName2 ? `Ship: ${shipName2}
|
|
7246
|
+
|
|
7247
|
+
` : ""}Not quoted verbatim:
|
|
7248
|
+
${missing.map((l) => `- ${l}`).join("\n")}
|
|
7249
|
+
|
|
7250
|
+
Set back to \`in_progress\`. Post a comment that lists every Acceptance line verbatim, each followed by the test, command or manual check that exercised it in this run and its result (a line you could not exercise is listed with the reason, never omitted), then set \`fixed\` again. The branch is already pushed.`;
|
|
7251
|
+
}
|
|
7177
7252
|
async function enforceHandoff(i) {
|
|
7178
7253
|
try {
|
|
7179
7254
|
const t = await i.writer.ticket(i.ticketId);
|
|
@@ -7189,6 +7264,26 @@ async function enforceHandoff(i) {
|
|
|
7189
7264
|
i.log.warn(`${t.issue_id} is at ${t.status} but the push was not checked: ${v.why}`, log);
|
|
7190
7265
|
return { kind: "unverified", why: v.why };
|
|
7191
7266
|
}
|
|
7267
|
+
if (v.kind === "pushed" && !claimed) {
|
|
7268
|
+
const missing = await missingAcceptance(i, t);
|
|
7269
|
+
if (missing) {
|
|
7270
|
+
const heldNow = i.hold ? t[i.hold.column] : void 0;
|
|
7271
|
+
if (i.hold && heldNow && heldNow !== i.hold.shipId || await takenOverSince(i, t.id)) {
|
|
7272
|
+
i.log.warn(`${t.issue_id} is held by another ship now \u2014 the acceptance check is stale, left alone`, log);
|
|
7273
|
+
return { kind: "unverified", why: "ticket is held by another ship" };
|
|
7274
|
+
}
|
|
7275
|
+
await i.writer.updateTicket(t.id, {
|
|
7276
|
+
status: i.contract.statuses.building,
|
|
7277
|
+
...t.assignee_id ? {} : { assignee_id: i.seatId },
|
|
7278
|
+
...i.hold && !heldNow ? { [i.hold.column]: i.hold.shipId } : {}
|
|
7279
|
+
});
|
|
7280
|
+
await i.writer.postEvent(t.id, acceptanceBounceComment(missing, i.shipName), i.seatId, "bounced").catch(() => {
|
|
7281
|
+
i.log.warn("bounced, but the note failed to post", log);
|
|
7282
|
+
});
|
|
7283
|
+
i.log.warn(`${t.issue_id} bounced back to ${i.contract.statuses.building}: hand-off comment lacks the acceptance checklist`, log);
|
|
7284
|
+
return { kind: "bounced" };
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7192
7287
|
if (v.kind === "pushed") {
|
|
7193
7288
|
await i.writer.postEvent(t.id, pushedEventBody(v.sha, v.remote, v.branch), i.seatId, "pushed");
|
|
7194
7289
|
i.log.emit(`${t.issue_id} pushed at ${v.sha.slice(0, 7)} \u2014 handed on to QA`, log);
|
|
@@ -8467,6 +8562,8 @@ var RunObserver = class {
|
|
|
8467
8562
|
/** Hand-off writes and their outcomes, to tell a run whose hand-off never landed (CREW-2363). */
|
|
8468
8563
|
handoff = new HandoffTranscriptTracker();
|
|
8469
8564
|
result;
|
|
8565
|
+
/** Prefix tokens of the first assistant call that carried usage; kept once. */
|
|
8566
|
+
prefixTokens;
|
|
8470
8567
|
/** Set once the stream carries the harness's final result (turn count or cost). */
|
|
8471
8568
|
sawResult = false;
|
|
8472
8569
|
muted = false;
|
|
@@ -8593,6 +8690,7 @@ var RunObserver = class {
|
|
|
8593
8690
|
}
|
|
8594
8691
|
}
|
|
8595
8692
|
}
|
|
8693
|
+
if (this.prefixTokens === void 0 && parsed !== void 0) this.prefixTokens = this.adapter.extractPrefixTokens?.(parsed);
|
|
8596
8694
|
const turnTokens = parsed === void 0 ? void 0 : this.adapter.extractTurnTokens(parsed);
|
|
8597
8695
|
if (turnTokens !== void 0) this.attribution.add(turnText, turnTokens);
|
|
8598
8696
|
}
|
|
@@ -8616,8 +8714,15 @@ function writeRunMeta(id, startedAt, liveTicket, extra) {
|
|
|
8616
8714
|
} catch {
|
|
8617
8715
|
}
|
|
8618
8716
|
}
|
|
8619
|
-
async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
8620
|
-
|
|
8717
|
+
async function finishRun(id, obs, emit, startedAt, end, agentLogKey, hintMoved) {
|
|
8718
|
+
let moved = false;
|
|
8719
|
+
if (hintMoved) {
|
|
8720
|
+
try {
|
|
8721
|
+
moved = await hintMoved();
|
|
8722
|
+
} catch {
|
|
8723
|
+
}
|
|
8724
|
+
}
|
|
8725
|
+
const { ticket: attributedTicket, by: attributedBy } = obs.attribution.decide(obs.liveTicket, { hintMoved: moved });
|
|
8621
8726
|
const identity = { toolchain: id.toolchain, harness: id.harness, provider: id.provider };
|
|
8622
8727
|
const { result } = obs;
|
|
8623
8728
|
const pending = end.interrupted ? null : obs.handoff.pending();
|
|
@@ -8649,6 +8754,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8649
8754
|
...pending ? { handoff: "pending" } : {},
|
|
8650
8755
|
sessionId: result?.sessionId ?? null,
|
|
8651
8756
|
numTurns: result?.numTurns ?? null,
|
|
8757
|
+
prefixTokens: obs.prefixTokens ?? null,
|
|
8652
8758
|
totalCostUsd: result?.costUsd ?? null
|
|
8653
8759
|
});
|
|
8654
8760
|
} catch {
|
|
@@ -8661,6 +8767,8 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8661
8767
|
...pending ? { handoff: "pending" } : {},
|
|
8662
8768
|
...end.interrupted ? { interrupted: end.interrupted } : {},
|
|
8663
8769
|
...identity,
|
|
8770
|
+
attributedTicket: attributedTicket ?? null,
|
|
8771
|
+
attributedBy,
|
|
8664
8772
|
...result?.sessionId ? { sessionId: result.sessionId } : {},
|
|
8665
8773
|
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
8666
8774
|
...result?.costUsd !== void 0 ? { totalCostUsd: result.costUsd } : {}
|
|
@@ -8679,6 +8787,9 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8679
8787
|
cacheReadTokens: result?.cacheReadTokens,
|
|
8680
8788
|
cacheWriteTokens: result?.cacheWriteTokens,
|
|
8681
8789
|
costUsd: result?.costUsd,
|
|
8790
|
+
// Both come from a finished run: without the result line (interrupted) neither is trustworthy.
|
|
8791
|
+
...result?.numTurns !== void 0 ? { numTurns: result.numTurns } : {},
|
|
8792
|
+
...result?.numTurns !== void 0 && obs.prefixTokens !== void 0 ? { prefixTokens: obs.prefixTokens } : {},
|
|
8682
8793
|
harness: identity.harness,
|
|
8683
8794
|
provider: identity.provider,
|
|
8684
8795
|
promptVersion: id.promptVersion,
|
|
@@ -8723,7 +8834,7 @@ async function finishRun(id, obs, emit, startedAt, end, agentLogKey) {
|
|
|
8723
8834
|
}
|
|
8724
8835
|
return { ms: ms2, ticket: attributedTicket, handoffPending: !!pending };
|
|
8725
8836
|
}
|
|
8726
|
-
function spawnAgent(plan, emit) {
|
|
8837
|
+
function spawnAgent(plan, emit, hintMoved) {
|
|
8727
8838
|
const adapter = adapterFor(plan.harness ?? LEGACY_TOOLCHAIN_HARNESS);
|
|
8728
8839
|
const started = Date.now();
|
|
8729
8840
|
const startedAt = new Date(started).toISOString();
|
|
@@ -8788,7 +8899,7 @@ function spawnAgent(plan, emit) {
|
|
|
8788
8899
|
obs.end();
|
|
8789
8900
|
await endSink(eventsSink);
|
|
8790
8901
|
if (spawnFailed) return;
|
|
8791
|
-
const { ms: ms2, handoffPending } = await finishRun(id, obs, emit, startedAt, { code: code ?? 1 }, plan.agentLog?.apiKey);
|
|
8902
|
+
const { ms: ms2, handoffPending } = await finishRun(id, obs, emit, startedAt, { code: code ?? 1 }, plan.agentLog?.apiKey, hintMoved);
|
|
8792
8903
|
resolve6({ code: code ?? 1, ms: ms2, ...handoffPending ? { handoffPending } : {} });
|
|
8793
8904
|
});
|
|
8794
8905
|
child.stdin?.on("error", (err) => {
|
|
@@ -16914,7 +17025,19 @@ async function runRoleAgent(role, decision, ctx) {
|
|
|
16914
17025
|
}
|
|
16915
17026
|
try {
|
|
16916
17027
|
const runStart = (/* @__PURE__ */ new Date()).toISOString();
|
|
16917
|
-
|
|
17028
|
+
let hintMoved;
|
|
17029
|
+
if (workingId) {
|
|
17030
|
+
try {
|
|
17031
|
+
const before = await tracker.ticket(workingId);
|
|
17032
|
+
const col = tracker.contract.columns;
|
|
17033
|
+
hintMoved = async () => {
|
|
17034
|
+
const after = await tracker.ticket(workingId);
|
|
17035
|
+
return after[col.status] !== before[col.status] || after[col.assignee] !== before[col.assignee];
|
|
17036
|
+
};
|
|
17037
|
+
} catch {
|
|
17038
|
+
}
|
|
17039
|
+
}
|
|
17040
|
+
await spawnAgent(plan, emit, hintMoved);
|
|
16918
17041
|
await guardRunHandoff(route, ship, tracker, role, workingId, emit, runStart);
|
|
16919
17042
|
} finally {
|
|
16920
17043
|
if (memberId) {
|
|
@@ -19383,7 +19506,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
19383
19506
|
}
|
|
19384
19507
|
for (const e of entries) {
|
|
19385
19508
|
process.stdout.write(
|
|
19386
|
-
`${e.id} ${e.started_at ?? "?"} ${e.outcome ?? "?"}${e.ticket_reference ? ` ${e.ticket_reference}` : ""}${e.model ? ` ${e.model}` : ""}${e.harness || e.provider ? ` (${[e.harness, e.provider].filter(Boolean).join(" \xB7 ")})` : ""}
|
|
19509
|
+
`${e.id} ${e.started_at ?? "?"} ${e.outcome ?? "?"}${e.ticket_reference ? ` ${e.ticket_reference}` : ""}${e.model ? ` ${e.model}` : ""}${e.harness || e.provider ? ` (${[e.harness, e.provider].filter(Boolean).join(" \xB7 ")})` : ""}${e.num_turns != null ? ` turns ${e.num_turns}` : ""}${e.prefix_tokens != null ? ` prefix ${e.prefix_tokens}` : ""}
|
|
19387
19510
|
`
|
|
19388
19511
|
);
|
|
19389
19512
|
}
|
|
@@ -19401,7 +19524,9 @@ ticket: ${entry.ticket_reference ?? "(none)"}
|
|
|
19401
19524
|
model: ${entry.model ?? "?"}
|
|
19402
19525
|
harness: ${entry.harness ?? "?"}
|
|
19403
19526
|
provider: ${entry.provider ?? "?"}
|
|
19404
|
-
`
|
|
19527
|
+
` + (entry.num_turns != null ? `turns: ${entry.num_turns}
|
|
19528
|
+
` : "") + (entry.prefix_tokens != null ? `prefix: ${entry.prefix_tokens} tokens
|
|
19529
|
+
` : "")
|
|
19405
19530
|
);
|
|
19406
19531
|
if (flag("prompt")) {
|
|
19407
19532
|
process.stdout.write(`
|
package/package.json
CHANGED
|
@@ -94,6 +94,11 @@ pass.
|
|
|
94
94
|
Judge by exit code and summary line, and quote the summary in your
|
|
95
95
|
comment.
|
|
96
96
|
7. **Check the hand-off itself.**
|
|
97
|
+
- **Acceptance checklist.** Start from the builder's checklist: their
|
|
98
|
+
comment quotes each Acceptance line with the check that exercised it.
|
|
99
|
+
Re-run the checks that matter, and when a line fails, a bounce cites that
|
|
100
|
+
line by its quoted text. A line the builder marked unexercised is yours
|
|
101
|
+
to exercise.
|
|
97
102
|
- **Documentation.** If the repository's agent instructions (CLAUDE.md,
|
|
98
103
|
AGENTS.md, GEMINI.md or equivalent) name a user-facing documentation
|
|
99
104
|
location, a change to a user-facing surface must come with a matching
|
|
@@ -416,6 +416,14 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
416
416
|
used, and anything you could not test yourself. Anything not committed and
|
|
417
417
|
pushed is gone.
|
|
418
418
|
|
|
419
|
+
**When the ticket has an Acceptance section, your last progress comment
|
|
420
|
+
quotes it.** List every line of that section verbatim, each followed by the
|
|
421
|
+
test, command or manual check that exercised it in this run and its result.
|
|
422
|
+
A line you could not exercise is listed with the reason, never omitted. The
|
|
423
|
+
runner checks your newest comment for every line and, if one is missing,
|
|
424
|
+
sets the ticket back to `in_progress` instead of passing it to QA. A ticket
|
|
425
|
+
with no Acceptance section is unaffected.
|
|
426
|
+
|
|
419
427
|
## Step 4 — you never merge, and you never deploy
|
|
420
428
|
|
|
421
429
|
**Nothing you do touches the base branch.** The release phase takes every
|
|
@@ -93,6 +93,11 @@ pass.
|
|
|
93
93
|
Judge by exit code and summary line, and quote the summary in your
|
|
94
94
|
comment.
|
|
95
95
|
7. **Check the hand-off itself.**
|
|
96
|
+
- **Acceptance checklist.** Start from the builder's checklist: their
|
|
97
|
+
comment quotes each Acceptance line with the check that exercised it.
|
|
98
|
+
Re-run the checks that matter, and when a line fails, a bounce cites that
|
|
99
|
+
line by its quoted text. A line the builder marked unexercised is yours
|
|
100
|
+
to exercise.
|
|
96
101
|
- **Documentation.** If the repository's agent instructions (CLAUDE.md,
|
|
97
102
|
AGENTS.md, GEMINI.md or equivalent) name a user-facing documentation
|
|
98
103
|
location, a change to a user-facing surface must come with a matching
|
|
@@ -411,6 +411,14 @@ before going further, and never change a ticket's `report_type` yourself.
|
|
|
411
411
|
used, and anything you could not test yourself. Anything not committed and
|
|
412
412
|
pushed is gone.
|
|
413
413
|
|
|
414
|
+
**When the ticket has an Acceptance section, your last progress comment
|
|
415
|
+
quotes it.** List every line of that section verbatim, each followed by the
|
|
416
|
+
test, command or manual check that exercised it in this run and its result.
|
|
417
|
+
A line you could not exercise is listed with the reason, never omitted. The
|
|
418
|
+
runner checks your newest comment for every line and, if one is missing,
|
|
419
|
+
sets the ticket back to `in_progress` instead of passing it to QA. A ticket
|
|
420
|
+
with no Acceptance section is unaffected.
|
|
421
|
+
|
|
414
422
|
## Step 4 — you never merge, and you never deploy
|
|
415
423
|
|
|
416
424
|
**Nothing you do touches the base branch.** The release phase takes every
|