@rallycry/conveyor-agent 11.0.27 → 11.0.28

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.
@@ -47,13 +47,14 @@ import {
47
47
  Lifecycle,
48
48
  MAX_CI_WAIT_TIMEOUT_MINUTES,
49
49
  MAX_FILE_SIZE_BYTES,
50
+ MAX_MANUAL_TESTS_PER_TASK,
51
+ MAX_MANUAL_TEST_TITLE_LENGTH,
50
52
  PM_CHAT_HISTORY_LIMIT,
51
53
  PRE_BUILD_TASK_STATUSES,
52
54
  PTY_STREAM_PORT_ATTEMPTS,
53
55
  PTY_STREAM_PORT_BASE,
54
56
  PortDiscovery,
55
57
  PtyStreamFrameReader,
56
- ReviewGuideContentSchema,
57
58
  SEVERITY_ENUM,
58
59
  TAG_DESCRIPTION_MAX,
59
60
  TAG_OVERVIEW_MAX,
@@ -63,12 +64,14 @@ import {
63
64
  awaitGitReady,
64
65
  clearForceFreshCooldown,
65
66
  createServiceLogger,
67
+ describeManualTestRejection,
66
68
  encodePtyStreamFrame,
67
69
  ensureOnTaskBranch,
68
70
  flushAllPendingWork,
69
71
  flushPendingChanges,
70
72
  forceFreshCooldownNotice,
71
73
  forceFreshMintBlocked,
74
+ formatEvaluateQuestionsResult,
72
75
  getCurrentBranch,
73
76
  git,
74
77
  hasTaskPlan,
@@ -92,7 +95,7 @@ import {
92
95
  statWorkspacePath,
93
96
  updateRemoteToken,
94
97
  verifyGitCredential
95
- } from "./chunk-PBSFLQA7.js";
98
+ } from "./chunk-GHNYP67H.js";
96
99
  import {
97
100
  registerBootMilestoneSocketFallback,
98
101
  reportBootMilestone
@@ -6748,6 +6751,7 @@ function compileNumber(z11, spec) {
6748
6751
  function compileArray(z11, spec) {
6749
6752
  let schema = z11.array(compileField(z11, spec.item));
6750
6753
  if (spec.min !== void 0) schema = schema.min(spec.min);
6754
+ if (spec.max !== void 0) schema = schema.max(spec.max);
6751
6755
  return schema;
6752
6756
  }
6753
6757
  function compileBase(z11, spec) {
@@ -6877,16 +6881,24 @@ var readTaskChatContract = defineToolContract({
6877
6881
  }
6878
6882
  }
6879
6883
  });
6884
+ var listTagsDetail = f.optional(
6885
+ f.enum(["summary", "full"], {
6886
+ desc: `"summary" (default) returns one compact row per tag with a contextPathCount. "full" also ships each tag's contextPaths inline (each link with its verification status). Full mode can exceed a client's tool-output budget on a large glossary; use get_tag for one term instead.`
6887
+ })
6888
+ );
6880
6889
  var listTagsContract = defineToolContract({
6881
6890
  name: "list_tags",
6882
6891
  agent: {
6883
- description: "List the project glossary: every tag's id, name, color, description, parent/child tag ids, its contextPaths (the rule/doc/file/folder links it wires into agent context \u2014 `[]` means none), whether it carries a full overview (fetch that with get_tag), and its attachmentCount (files labelled as examples of the term). The context links ship inline, so you only need get_tag for a term's full overview. Use the ids with get_tag / update_tag.",
6884
- fields: {}
6892
+ description: "List the whole project glossary in one call. The default summary gives every tag's id, name, color, description, parent/child tag ids, whether it carries a full overview (hasOverview), its attachmentCount (files labelled as examples of the term), and its contextPathCount (the rule/doc/file/folder links it wires into agent context \u2014 `0` means none). Pass detail: \"full\" to ship the contextPaths themselves inline. Use get_tag for one term's full overview and full link verification. Use the ids with get_tag / update_tag.",
6893
+ fields: {
6894
+ detail: listTagsDetail
6895
+ }
6885
6896
  },
6886
6897
  mcp: {
6887
- description: "List all project tags with names, IDs, colors, descriptions, hierarchy (parent/child ids), contextPaths (the rule/doc/file/folder links each tag wires into agent context \u2014 `[]` means none), a hasOverview flag, and an attachmentCount (files labelled as examples of the term \u2014 read the tiles with list_tag_attachments). Context links ship inline; call get_tag only for a term's full overview. Pass projectId to target a specific project; otherwise the configured default project is used.",
6898
+ description: 'List all project tags in one call. The default summary gives names, IDs, colors, descriptions, hierarchy (parent/child ids), a hasOverview flag, an attachmentCount (files labelled as examples of the term \u2014 read the tiles with list_tag_attachments), and a contextPathCount (the rule/doc/file/folder links each tag wires into agent context \u2014 `0` means none). Pass detail: "full" to ship the contextPaths themselves inline. Call get_tag for one term\'s full overview and full link verification. Pass projectId to target a specific project; otherwise the configured default project is used.',
6888
6899
  fields: {
6889
- projectId: mcpProjectId
6900
+ projectId: mcpProjectId,
6901
+ detail: listTagsDetail
6890
6902
  }
6891
6903
  }
6892
6904
  });
@@ -7022,7 +7034,7 @@ var updateTaskContract = defineToolContract({
7022
7034
  }
7023
7035
  },
7024
7036
  mcp: {
7025
- description: "Update task fields: title, description, plan, status, risk, story points, assignment, githubBranch, or tags. Set status to claim a card (InProgress), triage it (Open), or cancel it (Cancelled); for review approvals prefer approve_task / request_changes, which guard against stale-state races. Tags are additive/subtractive \u2014 pass addTags/removeTags with tag names (not a replace-set). Pass projectId to target a specific project; otherwise the configured default project is used. Moving a task beyond Planning auto-fills any missing icon, story points, and agent assignment \u2014 don't spend turns on them; pass storyPointValue only to correct the sizing yourself. For subtasks use update_subtask.",
7037
+ description: "Update task fields: title, description, plan, status, risk, story points, assignment, githubBranch, onHold, or tags. Set status to claim a card (InProgress), triage it (Open), or cancel it (Cancelled); for review approvals prefer approve_task / request_changes, which guard against stale-state races. Set onHold to park a card (true) or bring it back to the active board (false) without cancelling it. Tags are additive/subtractive \u2014 pass addTags/removeTags with tag names (not a replace-set). Pass projectId to target a specific project; otherwise the configured default project is used. Moving a task beyond Planning auto-fills any missing icon, story points, and agent assignment \u2014 don't spend turns on them; pass storyPointValue only to correct the sizing yourself. For subtasks use update_subtask.",
7026
7038
  fields: {
7027
7039
  projectId: mcpProjectId,
7028
7040
  taskId: f.string({ desc: "The task ID" }),
@@ -7053,6 +7065,11 @@ var updateTaskContract = defineToolContract({
7053
7065
  )
7054
7066
  ),
7055
7067
  githubBranch: f.optional(f.nullable(f.string({ desc: GITHUB_BRANCH_DESC }))),
7068
+ onHold: f.optional(
7069
+ f.boolean({
7070
+ desc: "Park the card on hold (true) or return it to the active board (false). On hold hides the card from the active columns without cancelling it; a card already at an active work status is demoted to Open when parked."
7071
+ })
7072
+ ),
7056
7073
  addTags: f.optional(
7057
7074
  f.array(f.string(), {
7058
7075
  desc: 'Tag names to add to the card (e.g. ["refactor"]). Additive \u2014 existing tags are kept. Unknown names are rejected; use list_tags to see available tags or manage_tags to create one.'
@@ -7096,9 +7113,20 @@ var testStatuses = f.optional(
7096
7113
  })
7097
7114
  );
7098
7115
  var setManualTestItems = f.array(
7099
- f.object({ title: f.string({ min: 1, desc: "A concise, actionable test step" }) }),
7100
- { min: 1, desc: "List of manual test steps to add" }
7116
+ f.object({
7117
+ title: f.string({
7118
+ min: 1,
7119
+ max: MAX_MANUAL_TEST_TITLE_LENGTH,
7120
+ desc: "One sentence a non-engineer can follow: where to go, what to do, what they should see. No commands, no code."
7121
+ })
7122
+ }),
7123
+ {
7124
+ min: 1,
7125
+ max: MAX_MANUAL_TESTS_PER_TASK,
7126
+ desc: `Manual tests to add. A card holds at most ${MAX_MANUAL_TESTS_PER_TASK}; most changes need 1, and a change with no user-visible behavior needs none.`
7127
+ }
7101
7128
  );
7129
+ var setManualTestsBar = 'A manual test is a real user path in plain language ("Open a card with a PR: the Pull Request slat sits directly under Description"), not an engineering check. Never record lint, typecheck, unit tests, CI, curl, database, log, or deploy steps; those run automatically. Record at most 5 per card, usually 1, and none when the change has no user-visible behavior. Existing items with the same title are skipped; items that name an automated step or exceed the cap are rejected and reported back.';
7102
7130
  var titleToEdit = f.string({ min: 1, desc: "The current title of the manual test to edit" });
7103
7131
  var newTitle = f.string({ min: 1, desc: "The new title for the manual test" });
7104
7132
  var titleToRemove = f.string({ min: 1, desc: "The title of the manual test to remove" });
@@ -7152,13 +7180,13 @@ var queryManualTestsContract = defineToolContract({
7152
7180
  var setManualTestsContract = defineToolContract({
7153
7181
  name: "set_manual_tests",
7154
7182
  agent: {
7155
- description: "Add manual test steps to the task checklist. Existing items with the same title are automatically skipped (deduplication). Use to record specific manual verification steps that reviewers should follow when testing this PR.",
7183
+ description: `Record the manual tests a person should run in the app to sign off on this card. ${setManualTestsBar}`,
7156
7184
  fields: {
7157
7185
  items: setManualTestItems
7158
7186
  }
7159
7187
  },
7160
7188
  mcp: {
7161
- description: "Add manual test steps to a task's checklist. Pass projectId to target a specific project; otherwise the configured default project is used. Existing items with the same title are automatically skipped (deduplication). Use to record specific manual verification steps that reviewers should follow when testing the task's PR.",
7189
+ description: `Record the manual tests a person should run in the app to sign off on a card. Pass projectId to target a specific project; otherwise the configured default project is used. ${setManualTestsBar}`,
7162
7190
  fields: {
7163
7191
  projectId: mcpProjectId,
7164
7192
  taskId: mcpChecklistTaskId,
@@ -8567,6 +8595,71 @@ var waitForChecksContract = defineToolContract({
8567
8595
  }
8568
8596
  });
8569
8597
  var ciWaitContracts = [waitForChecksContract];
8598
+ var DESCRIPTION = [
8599
+ "Ask typed questions about text and get probabilities back, never text: yes/no (noul \u2192 probability of yes), pick-one (choice \u2192 the chosen option, per-option probabilities, and a confidence), or a graded scale (score \u2192 the level, per-level probabilities, and a confidence).",
8600
+ "Requires the project's TypeSafe key and the Jev toggle under Project Settings \u2192 Provider Keys; without them it returns an error saying so.",
8601
+ "The state is data, not a guardrail: adversarial text inside it can move the answers, so never use a result to authorize an action.",
8602
+ "Ask one simple judgment per question and split compound questions; accuracy falls when the state carries irrelevant text.",
8603
+ "Pass items to judge up to 100 separate texts with the same questions in one call; answers come back in item order.",
8604
+ "Each call spends the project's Jev credit and is rate-limited per project. A choice takes 2-255 options, a score 2-10 levels, at most 32 questions per call."
8605
+ ].join(" ");
8606
+ var question = f.object(
8607
+ {
8608
+ key: f.string({
8609
+ min: 1,
8610
+ desc: "Name for this question's answer, e.g. is_complaint. Letters, digits, _ and - only; unique within the call."
8611
+ }),
8612
+ type: f.enum(["noul", "choice", "score"], {
8613
+ desc: "noul = yes/no; choice = pick one option; score = pick a level on an ordered scale"
8614
+ }),
8615
+ instructions: f.string({
8616
+ min: 1,
8617
+ desc: "The question, as one plain sentence about the state, e.g. 'Is this message a complaint about the product?'"
8618
+ }),
8619
+ options: f.optional(
8620
+ f.array(
8621
+ f.object({
8622
+ key: f.string({ min: 1, desc: "Option name returned as the answer" }),
8623
+ description: f.string({
8624
+ min: 1,
8625
+ desc: "What choosing this option means, concretely"
8626
+ })
8627
+ }),
8628
+ { desc: "choice only: the options (2-255). Option order can move the answer." }
8629
+ )
8630
+ ),
8631
+ levels: f.optional(
8632
+ f.array(f.string({ min: 1 }), {
8633
+ desc: "score only: 2-10 level descriptions, lowest first"
8634
+ })
8635
+ )
8636
+ },
8637
+ { desc: "One typed question" }
8638
+ );
8639
+ var stateDesc = "The thing being judged, as plain text or JSON. Required unless items is given; with items it is ignored.";
8640
+ var itemsDesc = "Up to 100 separate texts (each up to 8,000 characters), each judged on its own with the same questions. Use this for bulk classification instead of one call per item.";
8641
+ var questionsDesc = "1-32 questions, all answered independently against the same state.";
8642
+ var evaluateQuestionsContract = defineToolContract({
8643
+ name: "evaluate_questions",
8644
+ agent: {
8645
+ description: DESCRIPTION,
8646
+ fields: {
8647
+ state: f.optional(f.string({ desc: stateDesc })),
8648
+ questions: f.array(question, { min: 1, desc: questionsDesc }),
8649
+ items: f.optional(f.array(f.string(), { desc: itemsDesc }))
8650
+ }
8651
+ },
8652
+ mcp: {
8653
+ description: `${DESCRIPTION} Pass projectId to target a specific project; otherwise the configured default project is used.`,
8654
+ fields: {
8655
+ projectId: mcpProjectId,
8656
+ state: f.optional(f.string({ desc: stateDesc })),
8657
+ questions: f.array(question, { min: 1, desc: questionsDesc }),
8658
+ items: f.optional(f.array(f.string(), { desc: itemsDesc }))
8659
+ }
8660
+ }
8661
+ });
8662
+ var evaluateContracts = [evaluateQuestionsContract];
8570
8663
  var TOOL_CONTRACTS = Object.fromEntries(
8571
8664
  [
8572
8665
  ...tasksContracts,
@@ -8583,7 +8676,8 @@ var TOOL_CONTRACTS = Object.fromEntries(
8583
8676
  ...driveContracts,
8584
8677
  ...meetingsContracts,
8585
8678
  ...logsContracts,
8586
- ...ciWaitContracts
8679
+ ...ciWaitContracts,
8680
+ ...evaluateContracts
8587
8681
  ].map((contract) => [contract.name, contract])
8588
8682
  );
8589
8683
 
@@ -9588,6 +9682,7 @@ function buildSetManualTestsTool(connection) {
9588
9682
  });
9589
9683
  const parts = [`Created ${result.created} manual test item(s).`];
9590
9684
  if (result.skipped > 0) parts.push(`Skipped ${result.skipped} duplicate(s).`);
9685
+ for (const item of result.rejected ?? []) parts.push(describeManualTestRejection(item));
9591
9686
  return textResult(parts.join(" "));
9592
9687
  } catch (error) {
9593
9688
  const msg = error instanceof Error ? error.message : "Unknown error";
@@ -10149,9 +10244,9 @@ async function rejectBadContextPaths(contextPaths, workspaceDir) {
10149
10244
  function buildListTagsTool(connection, projectId) {
10150
10245
  return defineContractTool(
10151
10246
  listTagsContract,
10152
- async () => {
10247
+ async ({ detail }) => {
10153
10248
  try {
10154
- const tags = await connection.call("listProjectTags", { projectId });
10249
+ const tags = await connection.call("listProjectTags", { projectId, detail });
10155
10250
  return textResult(JSON.stringify(tags, null, 2));
10156
10251
  } catch (error) {
10157
10252
  return errText("Failed to list tags", error);
@@ -11016,6 +11111,31 @@ function settleTools(connection, projectId) {
11016
11111
  ];
11017
11112
  }
11018
11113
 
11114
+ // src/tools/evaluate-tools.ts
11115
+ function buildEvaluateTools(connection, projectId) {
11116
+ return [
11117
+ defineContractTool(
11118
+ evaluateQuestionsContract,
11119
+ async (input) => {
11120
+ try {
11121
+ const res = await connection.call("evaluateQuestions", {
11122
+ projectId,
11123
+ state: input.state,
11124
+ items: input.items,
11125
+ questions: input.questions
11126
+ });
11127
+ return textResult(formatEvaluateQuestionsResult(res));
11128
+ } catch (error) {
11129
+ return textResult(
11130
+ `evaluate_questions failed: ${error instanceof Error ? error.message : "Unknown error"}`
11131
+ );
11132
+ }
11133
+ },
11134
+ { annotations: { readOnlyHint: true } }
11135
+ )
11136
+ ];
11137
+ }
11138
+
11019
11139
  // src/tools/connected-tools.ts
11020
11140
  function connectedToolsFor(connection, context) {
11021
11141
  const projectId = context?.projectId;
@@ -11028,6 +11148,9 @@ function connectedToolsFor(connection, context) {
11028
11148
  // Always available: the decision tables exist on every project, and a fork
11029
11149
  // an agent cannot file is a fork it assumes its way past instead.
11030
11150
  ...buildDecisionTools(connection, projectId),
11151
+ // Ungated: whether the project has Jev on is answered by calling it, and
11152
+ // the error names what an admin has to switch on.
11153
+ ...buildEvaluateTools(connection, projectId),
11031
11154
  ...context.googleAnalyticsConfigured ? [buildAnalyticsSummaryTool(connection, projectId)] : [],
11032
11155
  ...context.gcpLogsConfigured ? [buildQueryGcpLogsTool(connection, projectId)] : [],
11033
11156
  ...context.grafanaLogsConfigured ? [buildQueryGrafanaLogsTool(connection, projectId)] : []
@@ -11035,8 +11158,6 @@ function connectedToolsFor(connection, context) {
11035
11158
  }
11036
11159
 
11037
11160
  // src/tools/code-review-tools.ts
11038
- import { execFile } from "child_process";
11039
- import { promisify } from "util";
11040
11161
  import { z as z10 } from "zod";
11041
11162
  async function endReviewSession(connection, reason) {
11042
11163
  await connection.call("endReviewSession", {
@@ -11047,101 +11168,6 @@ async function endReviewSession(connection, reason) {
11047
11168
  var RISK_LEVELS = ["critical", "high", "medium", "low"];
11048
11169
  var reviewedShaSchema = z10.string().regex(/^[0-9a-f]{40}$/i).describe("REQUIRED. The full 40-character commit SHA this verdict reviews.");
11049
11170
  var riskDescription = "REQUIRED. The risk level this change carries, judged by the surface area it touches: critical = touches critical/foundational surface, high = important surface, medium = moderate, low = small/isolated. Set this on every verdict. You have authority to override a risk level already set on the task if you disagree with it.";
11050
- var ReviewGuideToolSchema = z10.strictObject({
11051
- reviewedSha: z10.string().regex(/^[0-9a-f]{40}$/i).describe(
11052
- "REQUIRED. The PR's current head as a full 40-char SHA. Run `git rev-parse HEAD` immediately before this call \u2014 never extend an abbreviated hash into 40 characters."
11053
- ),
11054
- overview: z10.string().min(1).max(6e4).describe("REQUIRED. Plain-text walkthrough intro, max 3000 characters. Keep it short."),
11055
- sections: z10.array(
11056
- z10.strictObject({
11057
- title: z10.string().min(1).max(160),
11058
- explanation: z10.string().min(1).max(2e3),
11059
- classification: z10.enum(["core", "supporting"]).optional(),
11060
- files: z10.array(
11061
- z10.strictObject({
11062
- path: z10.string().min(1).max(500).describe(
11063
- "A file the PR's diff actually changed. Context files you merely read are rejected."
11064
- ),
11065
- startLine: z10.number().int().positive().max(1e6).optional(),
11066
- endLine: z10.number().int().positive().max(1e6).optional(),
11067
- hunkHeader: z10.string().min(1).max(300).optional().describe(
11068
- "Optional anchor, matched byte-exactly against the full hunk header line from `git diff` INCLUDING the context text after the second @@. Copy it verbatim from `git diff <base>..HEAD -- <file> | grep '^@@'`, or omit anchors entirely (path-only entries always validate)."
11069
- )
11070
- })
11071
- ).min(1).max(20)
11072
- })
11073
- ).min(1).max(12).optional().describe(
11074
- "REQUIRED top-level array (never text appended to overview) of ordered conceptual sections."
11075
- )
11076
- });
11077
- var FLATTENED_SECTIONS_PATTERN = /<\/overview>\s*(?:<parameter name="sections">|<sections>)/;
11078
- var TRAILING_TAGS_PATTERN = /(?:\s*<\/(?:parameter|sections|invoke)>)+\s*$/;
11079
- function sliceOutermostArray(tail) {
11080
- const start = tail.indexOf("[");
11081
- const end = tail.lastIndexOf("]");
11082
- return start >= 0 && end > start ? tail.slice(start, end + 1) : null;
11083
- }
11084
- function recoverFlattenedGuide(overview) {
11085
- const match = FLATTENED_SECTIONS_PATTERN.exec(overview);
11086
- if (!match) return null;
11087
- const head = overview.slice(0, match.index).trim();
11088
- const tail = overview.slice(match.index + match[0].length).replace(TRAILING_TAGS_PATTERN, "").trim();
11089
- for (const candidate of [tail, sliceOutermostArray(tail)]) {
11090
- if (!candidate) continue;
11091
- try {
11092
- return { overview: head, sections: JSON.parse(candidate) };
11093
- } catch {
11094
- }
11095
- }
11096
- return null;
11097
- }
11098
- async function resolveGitHeadSha(cwd) {
11099
- try {
11100
- const stdout = workbenchEnabled() ? (await getWorkbenchClient().execFile("git", ["rev-parse", "HEAD"], {
11101
- cwd,
11102
- timeout: 1e4
11103
- })).stdout : (await promisify(execFile)("git", ["rev-parse", "HEAD"], { cwd, timeout: 1e4 })).stdout;
11104
- const sha = stdout.trim();
11105
- return /^[0-9a-f]{40}$/i.test(sha) ? sha : null;
11106
- } catch {
11107
- return null;
11108
- }
11109
- }
11110
- function buildPublishReviewGuideTool(connection, options = {}) {
11111
- const { resolveHeadSha } = options;
11112
- return defineTool(
11113
- "publish_review_guide",
11114
- "Publish or update the PR guide for the CURRENT PR head commit. Call right after create_pull_request succeeds, and again after every push to the branch. The head SHA is resolved from the local repo, so never hand-expand a short hash. Best-effort \u2014 it does not gate opening or updating the PR.",
11115
- ReviewGuideToolSchema.shape,
11116
- async ({ reviewedSha, overview, sections }) => {
11117
- let resolvedOverview = overview;
11118
- let resolvedSections = sections;
11119
- if (resolvedSections === void 0) {
11120
- const recovered = recoverFlattenedGuide(overview);
11121
- if (!recovered) {
11122
- throw new Error(
11123
- "publish_review_guide requires a top-level `sections` array \u2014 an ordered list of conceptual sections, each with title, explanation, and files[]. Your call arrived with only `reviewedSha` and `overview`; the `sections` parameter never reached the server, which usually means the call encoding flattened it into `overview`. Do not append the sections to `overview` as text. Retry with sections as a real array argument, and shorten `overview` if the failure repeats."
11124
- );
11125
- }
11126
- resolvedOverview = recovered.overview;
11127
- resolvedSections = recovered.sections;
11128
- }
11129
- const content = ReviewGuideContentSchema.parse({
11130
- overview: resolvedOverview,
11131
- sections: resolvedSections
11132
- });
11133
- const result = await connection.call("publishReviewGuide", {
11134
- sessionId: connection.sessionId,
11135
- reviewedSha: (resolveHeadSha ? await resolveHeadSha() : null) ?? reviewedSha,
11136
- ...content
11137
- });
11138
- return textResult(
11139
- `Review guide published for ${result.reviewedSha}${result.replaced ? " (replaced)" : ""}.`
11140
- );
11141
- },
11142
- { strict: true }
11143
- );
11144
- }
11145
11171
  function buildApproveCodeReviewTool(connection) {
11146
11172
  return defineTool(
11147
11173
  "approve_code_review",
@@ -11251,15 +11277,6 @@ function getModeTools(agentMode, connection, config, context) {
11251
11277
  return config.mode === "pm" ? buildPmTools(connection) : [];
11252
11278
  }
11253
11279
  }
11254
- function buildPrGuideToolsFor(effectiveMode, connection, config, context) {
11255
- const isLeafBuild = effectiveMode === "building" || effectiveMode === "auto" || effectiveMode === "chat";
11256
- const isPackRunner = config.mode === "pack";
11257
- return (isLeafBuild || isPackRunner) && (isPackRunner || !context?.isParentTask) ? [
11258
- buildPublishReviewGuideTool(connection, {
11259
- resolveHeadSha: () => resolveGitHeadSha(config.workspaceDir)
11260
- })
11261
- ] : [];
11262
- }
11263
11280
  function ciWaitToolsFor(effectiveMode, connection, config) {
11264
11281
  const isPackRunner = config.mode === "pack";
11265
11282
  return effectiveMode === "review" && !isPackRunner ? [] : [buildWaitForChecksTool(connection)];
@@ -11294,8 +11311,6 @@ var ALWAYS_LOADED_TOOLS = /* @__PURE__ */ new Set([
11294
11311
  // The shared-skill card write: one name whose fields match the mcp surface,
11295
11312
  // so a single skill text drives a local session and a pod verbatim.
11296
11313
  "update_task",
11297
- // Building/auto — the PR guide is published as part of opening the PR
11298
- "publish_review_guide",
11299
11314
  // Review mode
11300
11315
  "approve_code_review",
11301
11316
  "request_code_changes"
@@ -11349,7 +11364,6 @@ function buildConveyorTools(connection, config, context, agentMode) {
11349
11364
  const modeTools = getModeTools(effectiveMode, connection, config, context);
11350
11365
  const discoveryTools = effectiveMode === "discovery" || effectiveMode === "auto" || effectiveMode === "building" || effectiveMode === "chat" ? buildDiscoveryTools(connection) : [];
11351
11366
  const codeReviewTools = effectiveMode === "review" ? buildCodeReviewTools(connection) : [];
11352
- const prGuideTools = buildPrGuideToolsFor(effectiveMode, connection, config, context);
11353
11367
  const ciWaitTools = ciWaitToolsFor(effectiveMode, connection, config);
11354
11368
  const handoffTools = config.mode === "pm" && (effectiveMode === "discovery" || effectiveMode === "auto") ? [buildHandoffTool(connection)] : [];
11355
11369
  const emergencyTools = [buildForceUpdateTaskStatusTool(connection)];
@@ -11362,7 +11376,6 @@ function buildConveyorTools(connection, config, context, agentMode) {
11362
11376
  ...modeTools,
11363
11377
  ...discoveryTools,
11364
11378
  ...codeReviewTools,
11365
- ...prGuideTools,
11366
11379
  ...ciWaitTools,
11367
11380
  ...handoffTools,
11368
11381
  ...glossaryTools,
@@ -13927,9 +13940,9 @@ function buildUnmeasurableEvent(reason, codingAgentKeyId) {
13927
13940
  }
13928
13941
 
13929
13942
  // src/runner/parent-pull-handler.ts
13930
- import { execFile as execFile2 } from "child_process";
13931
- import { promisify as promisify2 } from "util";
13932
- var execFileAsync = promisify2(execFile2);
13943
+ import { execFile } from "child_process";
13944
+ import { promisify } from "util";
13945
+ var execFileAsync = promisify(execFile);
13933
13946
  async function handlePullBranch(workDir, branch) {
13934
13947
  if (!branch) return;
13935
13948
  const current = await getCurrentBranch(workDir);
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  WorkspaceCommandSupervisor,
8
8
  startWorkspaceCommandsAfterConnect,
9
9
  stopWorkspaceCommands
10
- } from "./chunk-SI7WNWZZ.js";
10
+ } from "./chunk-DMTBXIA4.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-7HK6OIV4.js";
28
+ } from "./chunk-ND5INHM4.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-PBSFLQA7.js";
47
+ } from "./chunk-GHNYP67H.js";
48
48
  import "./chunk-Q4FQOJ7D.js";
49
49
  import "./chunk-W4LZ7R6Z.js";
50
50
  import "./chunk-372R6E4C.js";
@@ -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-WGUFEWYJ.js");
1461
+ const { runServingSession } = await import("./serve-boot-CYVW7Z27.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-7HK6OIV4.js";
4
+ } from "./chunk-ND5INHM4.js";
5
5
  import "./chunk-37J5MMQT.js";
6
6
  import "./chunk-WCJ6HRVU.js";
7
7
  import {
@@ -17,7 +17,7 @@ import {
17
17
  stageAndCommit,
18
18
  updateRemoteToken,
19
19
  workspacePathExists
20
- } from "./chunk-PBSFLQA7.js";
20
+ } from "./chunk-GHNYP67H.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-SI7WNWZZ.js";
3
+ } from "./chunk-DMTBXIA4.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-PBSFLQA7.js";
14
+ } from "./chunk-GHNYP67H.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.27",
3
+ "version": "11.0.28",
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",
@@ -206,6 +206,15 @@ capture a screenshot (static) or a short recording (interaction) with the host
206
206
  repo's own tooling and attach it with `mcp__conveyor__upload_attachment`, then
207
207
  embed the returned URL in the PR body. A UI PR without it is incomplete.
208
208
 
209
+ **Manual tests are for human eyes, and few.** Before the PR, record with
210
+ `mcp__conveyor__set_manual_tests` only what a person must see in the running
211
+ app to sign the card off: at most 5, usually 1, none for a change with no
212
+ user-visible behavior. One sentence each, in plain language: where to go, what
213
+ to do, what they should see ("Open any card with a PR: the Pull Request slat
214
+ sits directly under Description"). Never record gates, commands, CI, logs, or
215
+ database steps; the server rejects them. A rejected item is a signal to drop
216
+ it, not to rephrase it.
217
+
209
218
  **On a deep pack the gate surface grows, and that is expected.**
210
219
  `test:affected` diffs against `origin/dev`, so by the fourth child it also
211
220
  covers the three already merged into the pack branch. That is correct
@@ -276,17 +285,7 @@ nothing invalidates the verification you just did:
276
285
  recorded on the card. That fallback is usually right and is exactly why the
277
286
  mistake is invisible — until the card's recorded branch has drifted, and the
278
287
  PR opens against `dev`.
279
- 4. **Publish the PR guide** with `mcp__conveyor__publish_review_guide`, right
280
- after `create_pull_request` succeeds — it is part of opening a PR, not a
281
- follow-up. `sections` is a top-level array argument, not prose stuffed into
282
- `overview`; keep `overview` a short intro and order the sections with core
283
- behavior first, tests and generated files later. Reference only files this
284
- diff actually changed, never context you merely read. Anchors are optional
285
- and strict — `{"path": "..."}` alone is the safe form. Republish for the new
286
- head SHA after any later push, or the card's Guide tab shows stale. It is
287
- best-effort: if it still fails after one corrected retry, carry on — it
288
- never blocks opening or updating the PR.
289
- 5. Post a chat summary: what shipped, how it was verified, what a reviewer
288
+ 4. Post a chat summary: what shipped, how it was verified, what a reviewer
290
289
  should look at, and anything you did NOT do.
291
290
 
292
291
  Then confirm CI actually started (read-only `gh pr checks`). Do not wait on it.
@@ -21,7 +21,7 @@ The genre: **an uninterested observer pulls every relevant source, counts what a
21
21
  1. `get_connection_context` — who and where. Same call in a pod and a local MCP session; it reports the card in a pod and the account/board in an MCP client.
22
22
  2. `list_project_integrations` — which of repository, Slack/Discord, email, GCP, Grafana, Google Analytics, Drive, Cloudflare, incidents are configured, plus the registered work channels. Credential-free booleans; this is the map.
23
23
  3. `list_project_channels` — the channels an admin registered as agent-reachable, each with a description of what happens there. **The description is the routing signal**: it is how you tell the channel where support complaints land from the one where releases are announced. Only registered channels are reachable at all; an unregistered channel does not exist for this skill regardless of what the bot can see.
24
- 4. **ToolSearch for the optional surfaces** rather than assuming them: `drive_*`, meeting tools, `query_gcp_logs` / `query_grafana_logs`, `get_analytics_summary`. Also probe for supplemental local MCPs the user may have connected — a user-token Slack MCP is the notable one, because it enables true keyword search that Conveyor's own tools cannot do (see the Slack search note below).
24
+ 4. **ToolSearch for the optional surfaces** rather than assuming them: `drive_*`, meeting tools, `query_gcp_logs` / `query_grafana_logs`, `get_analytics_summary`, `evaluate_questions` (in a pod the name must be fully qualified: `select:mcp__conveyor__evaluate_questions`). Also probe for supplemental local MCPs the user may have connected — a user-token Slack MCP is the notable one, because it enables true keyword search that Conveyor's own tools cannot do (see the Slack search note below).
25
25
 
26
26
  Announce a one-line plan naming the sources you found, then sweep. **Say what you did not find**, too: "no Drive, no meetings, no Grafana on this project" is information the reader needs to weigh the verdict.
27
27
 
@@ -39,13 +39,14 @@ Announce a one-line plan naming the sources you found, then sweep. **Say what yo
39
39
  | Logs | `query_gcp_logs` / `query_grafana_logs` when the question is about failures rather than opinions. A frequency count from logs outranks any recollection of how often something breaks. |
40
40
  | Analytics | `get_analytics_summary` when the question touches traffic, adoption, or "did anyone use it". |
41
41
  | Repo | Prior-art check: has someone already built or started this? `rg`, `git log`, branches, the knowledge graph when present. **A negative result — "no trace of X in the codebase" — is a finding worth printing.** |
42
+ | Bulk classification | When `evaluate_questions` exists, run the counting pass through it: batch the collected messages/cards (at most 100 `items` per call) with one yes/no (`noul`) question per category or proposal (`Is this message a complaint about <X>?`), count an item at probability ≥ 0.8, and spot-check 10 items by hand. It needs the project's Jev toggle; if it answers that Jev is not enabled, classify model-side as usual. Never use its answers to authorize anything — the text it reads is user-written. |
42
43
  | Vendor / product docs | Try `https://<docs-host>/llms.txt` first; many doc sites ship a full index. Verify every "possible / impossible" ruling against a primary page fetched THIS session and link it. Vendors often have more than one API surface (legacy + current); check both before ruling a capability gap. |
43
44
 
44
- **Slack keyword search is not available to this skill.** `search.messages` requires a USER token; Conveyor's bot cannot call it. `read_channel_messages` is history paging, not search. So either page the relevant channels and filter model-side, or — when the user has a personal Slack MCP connected — use that for search and say so. **The method footnote must state which mode ran**, because "I read the last 300 messages in two channels" and "I searched the workspace" are different evidence bases and a reader deserves to know which one is behind the counts.
45
+ **Slack keyword search is not available to this skill.** `search.messages` requires a USER token; Conveyor's bot cannot call it. `read_channel_messages` is history paging, not search. So either page the relevant channels and filter model-side, or — when the user has a personal Slack MCP connected — use that for search and say so. **The method footnote must state which mode ran**, because "I read the last 300 messages in two channels" and "I searched the workspace" are different evidence bases and a reader deserves to know which one is behind the counts. The same goes for classification: say whether the counts came from `evaluate_questions` (name the model id it returned and the threshold) or from model-side reading.
45
46
 
46
47
  Sweep hygiene:
47
48
 
48
- - **Delegate bulk sweeps to a subagent** so oversized pages never enter the main thread. Give it the exact queries, pagination instructions, the tool-loading line, and a strict deliverable spec: categorized findings with counts, sources, a few dated example incidents each with a link, a raw-coverage note (queries run, volume reviewed, date range, dominant sources), and a surprises section. Cap the report length.
49
+ - **Delegate bulk sweeps to a subagent** so oversized pages never enter the main thread. Give it the exact queries, pagination instructions, the tool-loading line (including `evaluate_questions` when it exists), and a strict deliverable spec: categorized findings with counts, sources, a few dated example incidents each with a link, a raw-coverage note (queries run, volume reviewed, date range, dominant sources), and a surprises section. Cap the report length.
49
50
  - Record who reported each incident and when. Attribution splits ("their side vs ours", "process vs code defect") and time windows ("only since <month>") are the follow-up questions every single time; keep the underlying dated list so re-slicing is cheap.
50
51
 
51
52
  ## Phase 2: analysis
@@ -14,7 +14,8 @@ High-level strategy with relevant files/patterns/APIs.
14
14
  2. Concrete step.
15
15
 
16
16
  ## Testing
17
- - Commands and manual checks.
17
+ - Gates: the scoped lint/typecheck/test commands.
18
+ - Manual tests (0-3): one plain sentence each, user path only; omit when nothing is user-visible.
18
19
  - Edge cases.
19
20
 
20
21
  ## Notes
@@ -49,8 +50,11 @@ The executor treats it as the acceptance criteria, so enumerate:
49
50
  affected-test commands — naming the specific package suites the diff will
50
51
  touch. A docs-only plan should say plainly that no local gates are needed and
51
52
  that CI validates on the PR.
52
- - Any task-specific end-to-end check: a manual UI walk-through, an API smoke
53
- test, a migration dry-run.
53
+ - Manual tests, as a separate list of 0-3 items: one plain sentence each
54
+ naming where to go, what to do, and what a person should see. User paths
55
+ only; never a command, a gate, CI, or a log check. Omit the list when the
56
+ change has nothing user-visible. The builder copies the Manual tests list
57
+ into `set_manual_tests` and nothing else from this section.
54
58
 
55
59
  A planner working read-only is not expected to RUN these. Describe them.
56
60