@calltelemetry/openclaw-linear 0.9.14 → 0.9.15
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/index.ts +50 -0
- package/package.json +1 -1
- package/src/pipeline/webhook.test.ts +7 -7
- package/src/pipeline/webhook.ts +144 -6
package/index.ts
CHANGED
|
@@ -229,6 +229,56 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
229
229
|
}
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
// Hard gate: prepend planning-only constraints to code_run when issue is not "started".
|
|
233
|
+
// Even if the orchestrator LLM ignores scope rules, the coding agent receives hard constraints.
|
|
234
|
+
api.on("before_tool_call", async (event: any, _ctx: any) => {
|
|
235
|
+
if (event.toolName !== "code_run") return;
|
|
236
|
+
|
|
237
|
+
const { getCurrentSession } = await import("./src/pipeline/active-session.js");
|
|
238
|
+
const session = getCurrentSession();
|
|
239
|
+
if (!session?.issueId) return; // Non-Linear context, allow
|
|
240
|
+
|
|
241
|
+
// Check issue state
|
|
242
|
+
const hookTokenInfo = resolveLinearToken(pluginConfig);
|
|
243
|
+
if (!hookTokenInfo.accessToken) return;
|
|
244
|
+
const hookLinearApi = new LinearAgentApi(hookTokenInfo.accessToken, {
|
|
245
|
+
refreshToken: hookTokenInfo.refreshToken,
|
|
246
|
+
expiresAt: hookTokenInfo.expiresAt,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const issue = await hookLinearApi.getIssueDetails(session.issueId);
|
|
251
|
+
const stateType = issue?.state?.type ?? "";
|
|
252
|
+
const isStarted = stateType === "started";
|
|
253
|
+
|
|
254
|
+
if (!isStarted) {
|
|
255
|
+
const constraint = [
|
|
256
|
+
"CRITICAL CONSTRAINT — PLANNING MODE ONLY:",
|
|
257
|
+
`This issue (${session.issueIdentifier}) is in "${issue?.state?.name ?? stateType}" state — NOT In Progress.`,
|
|
258
|
+
"You may ONLY:",
|
|
259
|
+
"- Read and explore files to understand the codebase",
|
|
260
|
+
"- Write plan files (PLAN.md, notes, design outlines)",
|
|
261
|
+
"- Search code to inform planning",
|
|
262
|
+
"You MUST NOT:",
|
|
263
|
+
"- Create, modify, or delete source code, config, or infrastructure files",
|
|
264
|
+
"- Run system commands that change state (deploys, installs, migrations)",
|
|
265
|
+
"- Make external API requests that modify data",
|
|
266
|
+
"- Build, implement, or scaffold any application code",
|
|
267
|
+
"Plan and explore ONLY. Do not implement anything.",
|
|
268
|
+
"---",
|
|
269
|
+
].join("\n");
|
|
270
|
+
|
|
271
|
+
const originalPrompt = event.params?.prompt ?? "";
|
|
272
|
+
return {
|
|
273
|
+
params: { ...event.params, prompt: `${constraint}\n${originalPrompt}` },
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
} catch (err) {
|
|
277
|
+
api.logger.warn(`before_tool_call: issue state check failed: ${err}`);
|
|
278
|
+
// Don't block on failure — fall through to allow
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
232
282
|
// Narration Guard: catch short "Let me explore..." responses that narrate intent
|
|
233
283
|
// without actually calling tools, and append a warning for the user.
|
|
234
284
|
const NARRATION_PATTERNS = [
|
package/package.json
CHANGED
|
@@ -1261,7 +1261,7 @@ describe("Comment.create intent routing", () => {
|
|
|
1261
1261
|
identifier: "ENG-RW",
|
|
1262
1262
|
title: "Request Work",
|
|
1263
1263
|
description: "desc",
|
|
1264
|
-
state: { name: "
|
|
1264
|
+
state: { name: "In Progress", type: "started" },
|
|
1265
1265
|
team: { id: "team-rw" },
|
|
1266
1266
|
comments: { nodes: [] },
|
|
1267
1267
|
});
|
|
@@ -2219,7 +2219,7 @@ describe("dispatchCommentToAgent via Comment.create intents", () => {
|
|
|
2219
2219
|
identifier: "ENG-DE",
|
|
2220
2220
|
title: "Dispatch Error",
|
|
2221
2221
|
description: "desc",
|
|
2222
|
-
state: { name: "
|
|
2222
|
+
state: { name: "In Progress", type: "started" },
|
|
2223
2223
|
team: { id: "team-de" },
|
|
2224
2224
|
comments: { nodes: [] },
|
|
2225
2225
|
});
|
|
@@ -2709,7 +2709,7 @@ describe("postAgentComment edge cases", () => {
|
|
|
2709
2709
|
identifier: "ENG-IF",
|
|
2710
2710
|
title: "Identity Fail",
|
|
2711
2711
|
description: "desc",
|
|
2712
|
-
state: { name: "
|
|
2712
|
+
state: { name: "In Progress", type: "started" },
|
|
2713
2713
|
team: { id: "team-if" },
|
|
2714
2714
|
comments: { nodes: [] },
|
|
2715
2715
|
});
|
|
@@ -3405,7 +3405,7 @@ describe("Comment.create .catch callbacks on fire-and-forget dispatches", () =>
|
|
|
3405
3405
|
identifier: "ENG-RWC",
|
|
3406
3406
|
title: "Request Work Catch",
|
|
3407
3407
|
description: "desc",
|
|
3408
|
-
state: { name: "
|
|
3408
|
+
state: { name: "In Progress", type: "started" },
|
|
3409
3409
|
team: { id: "team-rwc" },
|
|
3410
3410
|
comments: { nodes: [] },
|
|
3411
3411
|
});
|
|
@@ -3599,7 +3599,7 @@ describe("dispatchCommentToAgent internal .catch callbacks", () => {
|
|
|
3599
3599
|
identifier: "ENG-DCE",
|
|
3600
3600
|
title: "DCA Error",
|
|
3601
3601
|
description: "desc",
|
|
3602
|
-
state: { name: "
|
|
3602
|
+
state: { name: "In Progress", type: "started" },
|
|
3603
3603
|
team: { id: "team-dce" },
|
|
3604
3604
|
comments: { nodes: [] },
|
|
3605
3605
|
});
|
|
@@ -4865,7 +4865,7 @@ describe("session affinity routing", () => {
|
|
|
4865
4865
|
identifier: "ENG-AFF-RW",
|
|
4866
4866
|
title: "Affinity Request Work",
|
|
4867
4867
|
description: "desc",
|
|
4868
|
-
state: { name: "
|
|
4868
|
+
state: { name: "In Progress", type: "started" },
|
|
4869
4869
|
team: { id: "team-aff" },
|
|
4870
4870
|
comments: { nodes: [] },
|
|
4871
4871
|
});
|
|
@@ -4899,7 +4899,7 @@ describe("session affinity routing", () => {
|
|
|
4899
4899
|
identifier: "ENG-NO-AFF",
|
|
4900
4900
|
title: "No Affinity",
|
|
4901
4901
|
description: "desc",
|
|
4902
|
-
state: { name: "
|
|
4902
|
+
state: { name: "In Progress", type: "started" },
|
|
4903
4903
|
team: { id: "team-noaff" },
|
|
4904
4904
|
comments: { nodes: [] },
|
|
4905
4905
|
});
|
package/src/pipeline/webhook.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { readPlanningState, isInPlanningMode, getPlanningSession, endPlanningSes
|
|
|
15
15
|
import { initiatePlanningSession, handlePlannerTurn, runPlanAudit } from "./planner.js";
|
|
16
16
|
import { startProjectDispatch } from "./dag-dispatch.js";
|
|
17
17
|
import { emitDiagnostic } from "../infra/observability.js";
|
|
18
|
-
import { classifyIntent } from "./intent-classify.js";
|
|
18
|
+
import { classifyIntent, type Intent } from "./intent-classify.js";
|
|
19
19
|
import { extractGuidance, formatGuidanceAppendix, cacheGuidanceForTeam, getCachedGuidanceForTeam, isGuidanceEnabled, _resetGuidanceCacheForTesting } from "./guidance.js";
|
|
20
20
|
import { loadAgentProfiles, buildMentionPattern, resolveAgentFromAlias, validateProfiles, _resetProfilesCacheForTesting, type AgentProfile } from "../infra/shared-profiles.js";
|
|
21
21
|
|
|
@@ -35,6 +35,28 @@ export function sanitizePromptInput(text: string, maxLength = 4000): string {
|
|
|
35
35
|
return sanitized;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Check if a work request should be blocked based on issue state.
|
|
40
|
+
* Returns a rejection message if blocked, null if allowed.
|
|
41
|
+
*/
|
|
42
|
+
function shouldBlockWorkRequest(
|
|
43
|
+
intent: Intent,
|
|
44
|
+
stateType: string,
|
|
45
|
+
stateName: string,
|
|
46
|
+
issueRef: string,
|
|
47
|
+
): string | null {
|
|
48
|
+
if (intent !== "request_work") return null;
|
|
49
|
+
if (stateType === "started") return null; // In Progress — allow
|
|
50
|
+
return (
|
|
51
|
+
`This issue (${issueRef}) is in **${stateName}** — it needs planning and scoping before implementation.\n\n` +
|
|
52
|
+
`**To move forward:**\n` +
|
|
53
|
+
`1. Update the issue description with requirements and acceptance criteria\n` +
|
|
54
|
+
`2. Move the issue to **In Progress**\n` +
|
|
55
|
+
`3. Then ask me to implement it\n\n` +
|
|
56
|
+
`I can help you scope and plan — just ask questions or discuss the approach.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
// Track issues with active agent runs to prevent concurrent duplicate runs.
|
|
39
61
|
const activeRuns = new Set<string>();
|
|
40
62
|
|
|
@@ -448,7 +470,7 @@ export async function handleLinearWebhook(
|
|
|
448
470
|
: [
|
|
449
471
|
`**Tool access:**`,
|
|
450
472
|
`- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${issueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
|
|
451
|
-
`- \`code_run\`:
|
|
473
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
452
474
|
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
453
475
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
454
476
|
];
|
|
@@ -461,6 +483,35 @@ export async function handleLinearWebhook(
|
|
|
461
483
|
api.logger.info(`Guidance injected (${guidanceCtx.source}): ${guidanceCtx.guidance?.slice(0, 120)}...`);
|
|
462
484
|
}
|
|
463
485
|
|
|
486
|
+
// ── Intent gate: classify user request and block work requests on untriaged issues ──
|
|
487
|
+
const classifyText = userMessage || promptContext || enrichedIssue?.title || "";
|
|
488
|
+
const projectId = enrichedIssue?.project?.id;
|
|
489
|
+
let isPlanning = false;
|
|
490
|
+
if (projectId) {
|
|
491
|
+
try {
|
|
492
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
493
|
+
isPlanning = isInPlanningMode(planState, projectId);
|
|
494
|
+
} catch { /* proceed without planning context */ }
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const intentResult = await classifyIntent(api, {
|
|
498
|
+
commentBody: classifyText,
|
|
499
|
+
issueTitle: enrichedIssue?.title ?? "(untitled)",
|
|
500
|
+
issueStatus: enrichedIssue?.state?.name,
|
|
501
|
+
isPlanning,
|
|
502
|
+
agentNames: Object.keys(profiles),
|
|
503
|
+
hasProject: !!projectId,
|
|
504
|
+
}, pluginConfig as Record<string, unknown> | undefined);
|
|
505
|
+
|
|
506
|
+
api.logger.info(`AgentSession.created intent: ${intentResult.intent}${intentResult.agentId ? ` (agent: ${intentResult.agentId})` : ""} — ${intentResult.reasoning}`);
|
|
507
|
+
|
|
508
|
+
const blockMsg = shouldBlockWorkRequest(intentResult.intent, stateType, enrichedIssue?.state?.name ?? "Unknown", issueRef);
|
|
509
|
+
if (blockMsg) {
|
|
510
|
+
api.logger.info(`AgentSession.created: blocking work request on untriaged issue ${issueRef}`);
|
|
511
|
+
await linearApi.emitActivity(session.id, { type: "response", body: blockMsg }).catch(() => {});
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
|
|
464
515
|
const message = [
|
|
465
516
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
466
517
|
``,
|
|
@@ -698,7 +749,7 @@ export async function handleLinearWebhook(
|
|
|
698
749
|
: [
|
|
699
750
|
`**Tool access:**`,
|
|
700
751
|
`- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${followUpIssueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
|
|
701
|
-
`- \`code_run\`:
|
|
752
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
702
753
|
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
703
754
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
704
755
|
];
|
|
@@ -711,6 +762,37 @@ export async function handleLinearWebhook(
|
|
|
711
762
|
api.logger.info(`Follow-up guidance injected: ${(guidanceCtxPrompted.guidance ?? "cached").slice(0, 120)}...`);
|
|
712
763
|
}
|
|
713
764
|
|
|
765
|
+
// ── Intent gate: classify follow-up and block work requests on untriaged issues ──
|
|
766
|
+
const followUpProjectId = enrichedIssue?.project?.id;
|
|
767
|
+
let followUpIsPlanning = false;
|
|
768
|
+
if (followUpProjectId) {
|
|
769
|
+
try {
|
|
770
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
771
|
+
followUpIsPlanning = isInPlanningMode(planState, followUpProjectId);
|
|
772
|
+
} catch { /* proceed without planning context */ }
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const followUpIntentResult = await classifyIntent(api, {
|
|
776
|
+
commentBody: userMessage,
|
|
777
|
+
issueTitle: enrichedIssue?.title ?? "(untitled)",
|
|
778
|
+
issueStatus: enrichedIssue?.state?.name,
|
|
779
|
+
isPlanning: followUpIsPlanning,
|
|
780
|
+
agentNames: Object.keys(profiles),
|
|
781
|
+
hasProject: !!followUpProjectId,
|
|
782
|
+
}, pluginConfig as Record<string, unknown> | undefined);
|
|
783
|
+
|
|
784
|
+
api.logger.info(`AgentSession.prompted intent: ${followUpIntentResult.intent}${followUpIntentResult.agentId ? ` (agent: ${followUpIntentResult.agentId})` : ""} — ${followUpIntentResult.reasoning}`);
|
|
785
|
+
|
|
786
|
+
const followUpBlockMsg = shouldBlockWorkRequest(
|
|
787
|
+
followUpIntentResult.intent, followUpStateType, enrichedIssue?.state?.name ?? "Unknown", followUpIssueRef,
|
|
788
|
+
);
|
|
789
|
+
if (followUpBlockMsg) {
|
|
790
|
+
api.logger.info(`AgentSession.prompted: blocking work request on untriaged issue ${followUpIssueRef}`);
|
|
791
|
+
await linearApi.emitActivity(session.id, { type: "response", body: followUpBlockMsg }).catch(() => {});
|
|
792
|
+
activeRuns.delete(issue.id);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
714
796
|
const message = [
|
|
715
797
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
716
798
|
``,
|
|
@@ -856,7 +938,7 @@ export async function handleLinearWebhook(
|
|
|
856
938
|
const profiles = loadAgentProfiles();
|
|
857
939
|
const agentNames = Object.keys(profiles);
|
|
858
940
|
|
|
859
|
-
// ── @mention fast path —
|
|
941
|
+
// ── @mention fast path — with intent gate ────────────────────
|
|
860
942
|
const mentionPattern = buildMentionPattern(profiles);
|
|
861
943
|
const mentionMatches = mentionPattern ? commentBody.match(mentionPattern) : null;
|
|
862
944
|
if (mentionMatches && mentionMatches.length > 0) {
|
|
@@ -864,6 +946,42 @@ export async function handleLinearWebhook(
|
|
|
864
946
|
const resolved = resolveAgentFromAlias(alias, profiles);
|
|
865
947
|
if (resolved) {
|
|
866
948
|
api.logger.info(`Comment @mention fast path: @${resolved.agentId} on ${issue.identifier ?? issue.id}`);
|
|
949
|
+
|
|
950
|
+
// Classify intent even on @mention path to gate work requests
|
|
951
|
+
let enrichedForGate: any = issue;
|
|
952
|
+
try { enrichedForGate = await linearApi.getIssueDetails(issue.id); } catch {}
|
|
953
|
+
const mentionStateType = enrichedForGate?.state?.type ?? "";
|
|
954
|
+
const mentionProjectId = enrichedForGate?.project?.id;
|
|
955
|
+
let mentionIsPlanning = false;
|
|
956
|
+
if (mentionProjectId) {
|
|
957
|
+
try {
|
|
958
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
959
|
+
mentionIsPlanning = isInPlanningMode(planState, mentionProjectId);
|
|
960
|
+
} catch {}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const mentionIntentResult = await classifyIntent(api, {
|
|
964
|
+
commentBody,
|
|
965
|
+
issueTitle: enrichedForGate?.title ?? "(untitled)",
|
|
966
|
+
issueStatus: enrichedForGate?.state?.name,
|
|
967
|
+
isPlanning: mentionIsPlanning,
|
|
968
|
+
agentNames,
|
|
969
|
+
hasProject: !!mentionProjectId,
|
|
970
|
+
}, pluginConfig);
|
|
971
|
+
|
|
972
|
+
api.logger.info(`Comment @mention intent: ${mentionIntentResult.intent} — ${mentionIntentResult.reasoning}`);
|
|
973
|
+
|
|
974
|
+
const mentionBlockMsg = shouldBlockWorkRequest(
|
|
975
|
+
mentionIntentResult.intent, mentionStateType,
|
|
976
|
+
enrichedForGate?.state?.name ?? "Unknown",
|
|
977
|
+
enrichedForGate?.identifier ?? issue.identifier ?? issue.id,
|
|
978
|
+
);
|
|
979
|
+
if (mentionBlockMsg) {
|
|
980
|
+
api.logger.info(`Comment @mention: blocking work request on untriaged issue ${enrichedForGate?.identifier ?? issue.identifier ?? issue.id}`);
|
|
981
|
+
try { await createCommentWithDedup(linearApi, issue.id, mentionBlockMsg); } catch {}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
|
|
867
985
|
void dispatchCommentToAgent(api, linearApi, profiles, resolved.agentId, issue, comment, commentBody, commentor, pluginConfig)
|
|
868
986
|
.catch((err) => api.logger.error(`Comment dispatch error: ${err}`));
|
|
869
987
|
return true;
|
|
@@ -906,6 +1024,19 @@ export async function handleLinearWebhook(
|
|
|
906
1024
|
|
|
907
1025
|
api.logger.info(`Comment intent: ${intentResult.intent}${intentResult.agentId ? ` (agent: ${intentResult.agentId})` : ""} — ${intentResult.reasoning} (fallback: ${intentResult.fromFallback})`);
|
|
908
1026
|
|
|
1027
|
+
// ── Gate work requests on untriaged issues ────────────────────
|
|
1028
|
+
const commentStateType = enrichedIssue?.state?.type ?? "";
|
|
1029
|
+
const commentBlockMsg = shouldBlockWorkRequest(
|
|
1030
|
+
intentResult.intent, commentStateType,
|
|
1031
|
+
enrichedIssue?.state?.name ?? "Unknown",
|
|
1032
|
+
enrichedIssue?.identifier ?? issue.identifier ?? issue.id,
|
|
1033
|
+
);
|
|
1034
|
+
if (commentBlockMsg) {
|
|
1035
|
+
api.logger.info(`Comment: blocking work request on untriaged issue ${enrichedIssue?.identifier ?? issue.identifier ?? issue.id}`);
|
|
1036
|
+
try { await createCommentWithDedup(linearApi, issue.id, commentBlockMsg); } catch {}
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
909
1040
|
// ── Route by intent ────────────────────────────────────────────
|
|
910
1041
|
|
|
911
1042
|
switch (intentResult.intent) {
|
|
@@ -1461,7 +1592,7 @@ async function dispatchCommentToAgent(
|
|
|
1461
1592
|
: [
|
|
1462
1593
|
`**Tool access:**`,
|
|
1463
1594
|
`- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${issueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
|
|
1464
|
-
`- \`code_run\`:
|
|
1595
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
1465
1596
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
1466
1597
|
];
|
|
1467
1598
|
|
|
@@ -1487,7 +1618,14 @@ async function dispatchCommentToAgent(
|
|
|
1487
1618
|
``,
|
|
1488
1619
|
`IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities.`,
|
|
1489
1620
|
``,
|
|
1490
|
-
|
|
1621
|
+
`## Scope Rules`,
|
|
1622
|
+
`1. **Read the issue first.** The issue title + description define your scope. Everything you do must serve the issue as written.`,
|
|
1623
|
+
`2. **\`code_run\` is ONLY for issue-body work.** Only dispatch \`code_run\` when the issue description contains implementation requirements. A greeting, question, or conversational issue gets a conversational response — NOT code_run.`,
|
|
1624
|
+
`3. **Comments explore, issue body builds.** The comment above may explore scope or ask questions but NEVER trigger \`code_run\` from a comment alone. If the comment requests new implementation, suggest updating the issue description or creating a new issue.`,
|
|
1625
|
+
`4. **Plan before building.** For non-trivial work, respond with a plan first. Only dispatch \`code_run\` after the plan is clear and grounded in the issue body.`,
|
|
1626
|
+
`5. **Match response to request.** Greeting → greet. Question → answer. No implementation requirements in the issue body → no code_run.`,
|
|
1627
|
+
``,
|
|
1628
|
+
`Respond within the scope defined above. Be concise and action-oriented.`,
|
|
1491
1629
|
commentGuidanceAppendix,
|
|
1492
1630
|
].filter(Boolean).join("\n");
|
|
1493
1631
|
|