@tamsensedev/runner 0.20.1 → 0.21.1

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.
Files changed (3) hide show
  1. package/dist/cli.js +1407 -1072
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -545,9 +545,14 @@ import { z as z7 } from "zod";
545
545
  // src/askPerson.ts
546
546
  var MAX_ASK_TRIES = 5;
547
547
  var MAX_ASK_OPTIONS = 4;
548
- function personLine(person) {
549
- const roles = person.roles.length > 0 ? `. Team roles: ${person.roles.join("; ")}` : "";
550
- return `${person.id}: ${person.name} (${person.relation}, ${person.access} access)${roles}`;
548
+ var PERSON_TEXTS = {
549
+ en: { access: (access) => `${access} access`, roles: "Team roles" },
550
+ ru: { access: (access) => `\u0434\u043E\u0441\u0442\u0443\u043F ${access}`, roles: "\u0420\u043E\u043B\u0438 \u0432 \u043A\u043E\u043C\u0430\u043D\u0434\u0435" }
551
+ };
552
+ function personLine(person, language = "en") {
553
+ const texts2 = PERSON_TEXTS[language];
554
+ const roles = person.roles.length > 0 ? `. ${texts2.roles}: ${person.roles.join("; ")}` : "";
555
+ return `${person.id}: ${person.name} (${person.relation}, ${texts2.access(person.access)})${roles}`;
551
556
  }
552
557
  function findPerson(people, to) {
553
558
  const key = String(to).trim().toLowerCase();
@@ -566,7 +571,7 @@ function checkAsk(people, request) {
566
571
  const person = findPerson(people, request.to);
567
572
  if (!person) {
568
573
  return { error: `"${request.to}" is not a person on this task. Ask one of them by id:
569
- ${people.map(personLine).join("\n")}` };
574
+ ${people.map((candidate) => personLine(candidate)).join("\n")}` };
570
575
  }
571
576
  if (!why) {
572
577
  return { error: `Say in one line why ${person.name} is the right person to ask.` };
@@ -1161,40 +1166,61 @@ var UPDATE_KNOWLEDGE_TOOL_NAME = "update_knowledge";
1161
1166
  var READ_KNOWLEDGE_TOOL_NAME = "read_knowledge";
1162
1167
  var MAX_KNOWLEDGE_TITLE = 80;
1163
1168
  var MAX_KNOWLEDGE_TEXT = 600;
1164
- var UPDATE_RULE = `Rely on this knowledge instead of asking people again. When you relied on an entry and found it true, call ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue true. When the code shows an entry is out of date (a file moved, a command or a port changed), fix it yourself at once: ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue false, the full new text and the evidence (the commit, the file or the command output that shows it). The change applies right away and people can revert it. Never ask a person whether an entry is still right, and never move facts of this computer into project knowledge.`;
1165
- var PROPOSE_RULE = `When a person's answer to your question teaches something later runs need (how the team runs the local stand, where something lives, a convention), propose one short note with ${PROPOSE_KNOWLEDGE_TOOL_NAME} and the question number from the answer. Scope computer is for what is true only on this computer (folders, ports, installed tools, local quirks); scope project is for what comes from the repository and works on every computer. Never propose secrets, personal data or decisions about this one task. A person approves every note; keep working while they decide.`;
1166
- function dateOf(iso) {
1167
- return iso ? `checked ${iso.slice(0, 10)}` : "not checked yet";
1169
+ var KNOWLEDGE_TEXTS = {
1170
+ en: {
1171
+ update: `Rely on this knowledge instead of asking people again. When you relied on an entry and found it true, call ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue true. When the code shows an entry is out of date (a file moved, a command or a port changed), fix it yourself at once: ${UPDATE_KNOWLEDGE_TOOL_NAME} with stillTrue false, the full new text and the evidence (the commit, the file or the command output that shows it). The change applies right away and people can revert it. Never ask a person whether an entry is still right, and never move facts of this computer into project knowledge.`,
1172
+ propose: `When a person's answer to your question teaches something later runs need (how the team runs the local stand, where something lives, a convention), propose one short note with ${PROPOSE_KNOWLEDGE_TOOL_NAME} and the question number from the answer. Scope computer is for what is true only on this computer (folders, ports, installed tools, local quirks); scope project is for what comes from the repository and works on every computer. Never propose secrets, personal data or decisions about this one task. A person approves every note; keep working while they decide.`,
1173
+ checked: (date) => `checked ${date}`,
1174
+ notChecked: "not checked yet",
1175
+ workplace: (computer, folder) => `You run on ${computer.name}, the ${computer.platform} computer of ${computer.ownerName}. Your task folder is ${folder}; everything outside it belongs to ${computer.ownerName}.`,
1176
+ empty: (rules) => `Knowledge of this project is empty so far. ${rules}`,
1177
+ project: (lines) => `Project knowledge (approved by the team, true on every computer):
1178
+ ${lines}`,
1179
+ computer: (name, lines) => `Knowledge for ${name ?? "this computer"} only (true here, not on other computers):
1180
+ ${lines}`,
1181
+ more: (titles) => `More entries, titles only to keep this short: ${titles}. When a title touches your work, read the entry with ${READ_KNOWLEDGE_TOOL_NAME} and its number.`
1182
+ },
1183
+ ru: {
1184
+ update: `\u041E\u043F\u0438\u0440\u0430\u0439\u0441\u044F \u043D\u0430 \u044D\u0442\u0438 \u0437\u043D\u0430\u043D\u0438\u044F, \u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u043B\u044E\u0434\u0435\u0439 \u0437\u0430\u043D\u043E\u0432\u043E. \u0415\u0441\u043B\u0438 \u0442\u044B \u043E\u043F\u0438\u0440\u0430\u043B\u0441\u044F \u043D\u0430 \u0437\u0430\u043C\u0435\u0442\u043A\u0443 \u0438 \u043E\u043D\u0430 \u043E\u043A\u0430\u0437\u0430\u043B\u0430\u0441\u044C \u0432\u0435\u0440\u043D\u043E\u0439, \u0432\u044B\u0437\u043E\u0432\u0438 ${UPDATE_KNOWLEDGE_TOOL_NAME} \u0441 stillTrue true. \u0415\u0441\u043B\u0438 \u043A\u043E\u0434 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442, \u0447\u0442\u043E \u0437\u0430\u043C\u0435\u0442\u043A\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043B\u0430 (\u0444\u0430\u0439\u043B \u043F\u0435\u0440\u0435\u0435\u0445\u0430\u043B, \u0441\u043C\u0435\u043D\u0438\u043B\u0430\u0441\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0438\u043B\u0438 \u043F\u043E\u0440\u0442), \u0441\u0440\u0430\u0437\u0443 \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0435\u0451 \u0441\u0430\u043C: ${UPDATE_KNOWLEDGE_TOOL_NAME} \u0441 stillTrue false, \u043F\u043E\u043B\u043D\u044B\u043C \u043D\u043E\u0432\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0438 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u043E\u043C (\u043A\u043E\u043C\u043C\u0438\u0442, \u0444\u0430\u0439\u043B \u0438\u043B\u0438 \u0432\u044B\u0432\u043E\u0434 \u043A\u043E\u043C\u0430\u043D\u0434\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u044D\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442). \u041F\u0440\u0430\u0432\u043A\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u0435\u0442 \u0441\u0440\u0430\u0437\u0443, \u0438 \u043B\u044E\u0434\u0438 \u043C\u043E\u0433\u0443\u0442 \u0435\u0451 \u043E\u0442\u043A\u0430\u0442\u0438\u0442\u044C. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u0432\u0435\u0440\u043D\u0430 \u043B\u0438 \u0435\u0449\u0451 \u0437\u0430\u043C\u0435\u0442\u043A\u0430, \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0438 \u0444\u0430\u043A\u0442\u044B \u044D\u0442\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430 \u0432 \u0437\u043D\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`,
1185
+ propose: `\u0415\u0441\u043B\u0438 \u043E\u0442\u0432\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043D\u0430 \u0442\u0432\u043E\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0443\u0447\u0438\u0442 \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043F\u0440\u0438\u0433\u043E\u0434\u0438\u0442\u0441\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0438\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u043C (\u043A\u0430\u043A \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0441\u0442\u0435\u043D\u0434, \u0433\u0434\u0435 \u0447\u0442\u043E \u043B\u0435\u0436\u0438\u0442, \u0434\u043E\u0433\u043E\u0432\u043E\u0440\u0451\u043D\u043D\u043E\u0441\u0442\u044C), \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u043E\u0434\u043D\u0443 \u043A\u043E\u0440\u043E\u0442\u043A\u0443\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443 \u0447\u0435\u0440\u0435\u0437 ${PROPOSE_KNOWLEDGE_TOOL_NAME} \u0441 \u043D\u043E\u043C\u0435\u0440\u043E\u043C \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u0438\u0437 \u043E\u0442\u0432\u0435\u0442\u0430. \u041E\u0431\u043B\u0430\u0441\u0442\u044C computer \u0434\u043B\u044F \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0432\u0435\u0440\u043D\u043E \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 (\u043F\u0430\u043F\u043A\u0438, \u043F\u043E\u0440\u0442\u044B, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u044B, \u043C\u0435\u0441\u0442\u043D\u044B\u0435 \u043E\u0441\u043E\u0431\u0435\u043D\u043D\u043E\u0441\u0442\u0438); \u043E\u0431\u043B\u0430\u0441\u0442\u044C project \u0434\u043B\u044F \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u0441\u043B\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 \u043B\u044E\u0431\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0439 \u0441\u0435\u043A\u0440\u0435\u0442\u044B, \u043B\u0438\u0447\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u0438\u043B\u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043F\u043E \u043E\u0434\u043D\u043E\u0439 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0435. \u041A\u0430\u0436\u0434\u0443\u044E \u0437\u0430\u043C\u0435\u0442\u043A\u0443 \u043E\u0434\u043E\u0431\u0440\u044F\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A; \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443, \u043F\u043E\u043A\u0430 \u043E\u043D \u0440\u0435\u0448\u0430\u0435\u0442.`,
1186
+ checked: (date) => `\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E ${date}`,
1187
+ notChecked: "\u0435\u0449\u0451 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E",
1188
+ workplace: (computer, folder) => `\u0422\u044B \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u043D\u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 ${computer.name} (${computer.platform}), \u0435\u0433\u043E \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446 ${computer.ownerName}. \u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0434\u0430\u0447\u0438: ${folder}; \u0432\u0441\u0451 \u0437\u0430 \u0435\u0451 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u043C\u0438 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0443.`,
1189
+ empty: (rules) => `\u0417\u043D\u0430\u043D\u0438\u0439 \u043F\u043E \u044D\u0442\u043E\u043C\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0443 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442. ${rules}`,
1190
+ project: (lines) => `\u0417\u043D\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (\u043E\u0434\u043E\u0431\u0440\u0435\u043D\u044B \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439, \u0432\u0435\u0440\u043D\u044B \u043D\u0430 \u043B\u044E\u0431\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435):
1191
+ ${lines}`,
1192
+ computer: (name, lines) => `\u0417\u043D\u0430\u043D\u0438\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F ${name ? `\u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430 ${name}` : "\u044D\u0442\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430"} (\u0432\u0435\u0440\u043D\u044B \u0437\u0434\u0435\u0441\u044C, \u0430 \u043D\u0435 \u043D\u0430 \u0434\u0440\u0443\u0433\u0438\u0445 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430\u0445):
1193
+ ${lines}`,
1194
+ more: (titles) => `\u0415\u0449\u0451 \u0437\u0430\u043C\u0435\u0442\u043A\u0438, \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u0442\u044C \u0442\u0435\u043A\u0441\u0442: ${titles}. \u0415\u0441\u043B\u0438 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u043A\u0430\u0441\u0430\u0435\u0442\u0441\u044F \u0442\u0432\u043E\u0435\u0439 \u0440\u0430\u0431\u043E\u0442\u044B, \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0437\u0430\u043C\u0435\u0442\u043A\u0443 \u0447\u0435\u0440\u0435\u0437 ${READ_KNOWLEDGE_TOOL_NAME} \u043F\u043E \u0435\u0451 \u043D\u043E\u043C\u0435\u0440\u0443.`
1195
+ }
1196
+ };
1197
+ function dateOf(iso, texts2) {
1198
+ return iso ? texts2.checked(iso.slice(0, 10)) : texts2.notChecked;
1168
1199
  }
1169
- function entryText(entry) {
1170
- return `[${entry.id}] ${entry.title}: ${entry.text.replace(/\s+/g, " ").trim()} (${dateOf(entry.checkedAt)})`;
1200
+ function entryText(entry, texts2 = KNOWLEDGE_TEXTS.en) {
1201
+ return `[${entry.id}] ${entry.title}: ${entry.text.replace(/\s+/g, " ").trim()} (${dateOf(entry.checkedAt, texts2)})`;
1171
1202
  }
1172
- function entryLine(entry) {
1173
- return `- ${entryText(entry)}`;
1203
+ function entryLines(entries, texts2) {
1204
+ return entries.map((entry) => `- ${entryText(entry, texts2)}`).join("\n");
1174
1205
  }
1175
- function workplaceText(computer, folder) {
1176
- if (!computer) {
1177
- return null;
1178
- }
1179
- return `You run on ${computer.name}, the ${computer.platform} computer of ${computer.ownerName}. Your task folder is ${folder}; everything outside it belongs to ${computer.ownerName}.`;
1206
+ function workplaceText(computer, folder, language = "en") {
1207
+ return computer ? KNOWLEDGE_TEXTS[language].workplace(computer, folder) : null;
1180
1208
  }
1181
1209
  function hasKnowledge(knowledge) {
1182
1210
  return Boolean(knowledge && (knowledge.project.length > 0 || knowledge.computer.length > 0 || knowledge.more.length > 0));
1183
1211
  }
1184
- function knowledgeText(knowledge, computer, canPropose) {
1185
- const rules = canPropose ? [PROPOSE_RULE] : [];
1212
+ function knowledgeText(knowledge, computer, canPropose, language = "en") {
1213
+ const texts2 = KNOWLEDGE_TEXTS[language];
1214
+ const rules = canPropose ? [texts2.propose] : [];
1186
1215
  if (!hasKnowledge(knowledge)) {
1187
- return rules.length > 0 ? `Knowledge of this project is empty so far. ${rules.join(" ")}` : null;
1216
+ return rules.length > 0 ? texts2.empty(rules.join(" ")) : null;
1188
1217
  }
1189
- const here = computer ? computer.name : "this computer";
1190
1218
  const sections = [
1191
- knowledge.project.length > 0 ? `Project knowledge (approved by the team, true on every computer):
1192
- ${knowledge.project.map(entryLine).join("\n")}` : null,
1193
- knowledge.computer.length > 0 ? `Knowledge for ${here} only (true here, not on other computers):
1194
- ${knowledge.computer.map(entryLine).join("\n")}` : null,
1195
- knowledge.more.length > 0 ? `More entries, titles only to keep this short: ${knowledge.more.map((entry) => `[${entry.id}] ${entry.title} (${entry.scope})`).join("; ")}. When a title touches your work, read the entry with ${READ_KNOWLEDGE_TOOL_NAME} and its number.` : null
1219
+ knowledge.project.length > 0 ? texts2.project(entryLines(knowledge.project, texts2)) : null,
1220
+ knowledge.computer.length > 0 ? texts2.computer(computer ? computer.name : null, entryLines(knowledge.computer, texts2)) : null,
1221
+ knowledge.more.length > 0 ? texts2.more(knowledge.more.map((entry) => `[${entry.id}] ${entry.title} (${entry.scope})`).join("; ")) : null
1196
1222
  ];
1197
- return [...sections, UPDATE_RULE, ...rules].filter(Boolean).join("\n\n");
1223
+ return [...sections, texts2.update, ...rules].filter(Boolean).join("\n\n");
1198
1224
  }
1199
1225
  function knowledgeIds(knowledge) {
1200
1226
  return knowledge ? [...knowledge.project, ...knowledge.computer, ...knowledge.more].map((entry) => entry.id) : [];
@@ -1351,15 +1377,16 @@ function askPermission(tools, lastWords, folder) {
1351
1377
  }
1352
1378
  var denyWhenAsked = async () => deny(NOBODY_TO_ASK);
1353
1379
 
1354
- // src/localWorkspace.ts
1355
- import { existsSync as existsSync4 } from "fs";
1356
- import { join as join4, resolve as resolve3 } from "path";
1380
+ // src/results.ts
1381
+ import { mkdir as mkdir2, readFile as readFile2, rm as rm3, stat, writeFile as writeFile2 } from "fs/promises";
1382
+ import { basename, extname, isAbsolute, join as join3, resolve as resolve2 } from "path";
1383
+ import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
1384
+ import { z as z2 } from "zod";
1357
1385
 
1358
- // src/folders.ts
1359
- import { existsSync as existsSync2, realpathSync, statSync } from "fs";
1360
- import { readdir } from "fs/promises";
1361
- import { basename, isAbsolute, join as join2, relative, resolve } from "path";
1362
- import process3 from "process";
1386
+ // src/workspace.ts
1387
+ import { existsSync as existsSync2 } from "fs";
1388
+ import { rm as rm2 } from "fs/promises";
1389
+ import { join as join2, resolve } from "path";
1363
1390
 
1364
1391
  // src/git.ts
1365
1392
  import { Buffer as Buffer2 } from "buffer";
@@ -1402,168 +1429,6 @@ async function git(args, options) {
1402
1429
  return result.stdout;
1403
1430
  }
1404
1431
 
1405
- // src/folders.ts
1406
- var FIRST_REMOTE = "origin";
1407
- var USUAL_BASES = ["main", "master"];
1408
- var CHECKS_INTERVAL_MS = 5e3;
1409
- var WEB_REMOTE = /^[a-z][\w+.-]*:\/\//i;
1410
- var NETWORK_REMOTE = /^(?!file:)[a-z][\w+.-]*:\/\/|^[^/@]+@[^/:]+:/i;
1411
- function problem(text2) {
1412
- return { ok: false, problem: text2 };
1413
- }
1414
- async function gitText(args, cwd) {
1415
- const result = await runGit(args, { cwd });
1416
- return result.code === 0 && result.stdout ? result.stdout : null;
1417
- }
1418
- function expandHome(path, home) {
1419
- const trimmed = path.trim();
1420
- return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join2(home, trimmed.slice(1)) : trimmed;
1421
- }
1422
- function remoteWithoutSecrets(url) {
1423
- if (!WEB_REMOTE.test(url)) {
1424
- return url;
1425
- }
1426
- try {
1427
- const parsed = new URL(url);
1428
- if (parsed.username || parsed.password) {
1429
- parsed.username = "";
1430
- parsed.password = "";
1431
- }
1432
- return parsed.toString().replace(/\/$/, "");
1433
- } catch {
1434
- return url;
1435
- }
1436
- }
1437
- function remoteRepository(remoteUrl) {
1438
- const parts = remoteUrl && NETWORK_REMOTE.test(remoteUrl) ? remoteUrl.replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).filter(Boolean) : [];
1439
- return parts.length >= 3 ? `${parts.at(-2)}/${parts.at(-1)}` : null;
1440
- }
1441
- function repositoryName(remoteUrl, folder) {
1442
- return remoteRepository(remoteUrl) ?? basename(folder);
1443
- }
1444
- async function remoteOf(folder) {
1445
- const remotes = (await gitText(["remote"], folder))?.split("\n").map((line) => line.trim()).filter(Boolean) ?? [];
1446
- return remotes.includes(FIRST_REMOTE) ? FIRST_REMOTE : remotes[0] ?? null;
1447
- }
1448
- async function hasRef(folder, ref) {
1449
- return (await runGit(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { cwd: folder })).code === 0;
1450
- }
1451
- async function baseBranchOf(folder, remote, current) {
1452
- const remoteHead = remote ? await gitText(["symbolic-ref", "--quiet", "--short", `refs/remotes/${remote}/HEAD`], folder) : null;
1453
- if (remoteHead?.startsWith(`${remote}/`)) {
1454
- return remoteHead.slice(remote.length + 1);
1455
- }
1456
- for (const name of USUAL_BASES) {
1457
- if (await hasRef(folder, `refs/heads/${name}`) || remote && await hasRef(folder, `refs/remotes/${remote}/${name}`)) {
1458
- return name;
1459
- }
1460
- }
1461
- return current;
1462
- }
1463
- function samePath(first, second) {
1464
- const [a, b] = [realpathSync.native(first), realpathSync.native(second)];
1465
- return process3.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
1466
- }
1467
- function isInside(path, folder) {
1468
- const between = relative(resolve(folder), resolve(path));
1469
- return between === "" || !between.startsWith("..") && !isAbsolute(between);
1470
- }
1471
- async function inspectFolder(path, home) {
1472
- const folder = expandHome(path, home);
1473
- if (!isAbsolute(folder)) {
1474
- return problem("Enter the full path of the folder, for example /Users/anna/code/shop or C:\\code\\shop.");
1475
- }
1476
- if (!existsSync2(folder) || !statSync(folder).isDirectory()) {
1477
- return problem(`There is no folder ${folder} on this computer.`);
1478
- }
1479
- if (isInside(folder, join2(home, ".tamsense"))) {
1480
- return problem("This is a TAMsense work folder. Choose your own copy of the repository.");
1481
- }
1482
- const top = await gitText(["rev-parse", "--show-toplevel"], folder);
1483
- if (!top) {
1484
- return problem(`${folder} is not a git repository. Choose the folder of a cloned repository, or run git init there.`);
1485
- }
1486
- if (!samePath(top, folder)) {
1487
- return problem(`${folder} is inside the repository ${resolve(top)}. Add the top folder of the repository instead.`);
1488
- }
1489
- if (!await hasRef(folder, "HEAD")) {
1490
- return problem("The repository has no commits yet. Make the first commit, then check the folder again.");
1491
- }
1492
- const remote = await remoteOf(folder);
1493
- const remoteUrl = remote ? await gitText(["remote", "get-url", remote], folder) : null;
1494
- const currentBranch = await gitText(["symbolic-ref", "--quiet", "--short", "HEAD"], folder);
1495
- const defaultBranch = await baseBranchOf(folder, remote, currentBranch);
1496
- if (!defaultBranch) {
1497
- return problem("Could not tell the main branch of the repository. Check out the main branch in this folder, then check it again.");
1498
- }
1499
- const safeUrl = remoteUrl && remoteWithoutSecrets(remoteUrl);
1500
- return { ok: true, repository: repositoryName(safeUrl, folder), currentBranch, defaultBranch, remoteUrl: safeUrl };
1501
- }
1502
- function reportLine(path, report) {
1503
- return report.ok ? `Checked the folder ${path}: ${report.repository}, main branch ${report.defaultBranch}.` : `The folder ${path} cannot be used: ${report.problem}`;
1504
- }
1505
- async function checkFolders(tools, stop) {
1506
- let complained = false;
1507
- while (!stop.aborted) {
1508
- try {
1509
- const { checks } = await tools.api.request("POST", FOLDER_CHECKS_PATH, void 0, { safeToRepeat: true });
1510
- for (const check of checks) {
1511
- const report = await tools.inspect(check.path).catch((error) => problem(error instanceof Error ? error.message : String(error)));
1512
- await tools.api.request("POST", folderCheckPath(check.id), report);
1513
- tools.print(reportLine(check.path, report));
1514
- }
1515
- complained = false;
1516
- } catch (error) {
1517
- if (error instanceof ApiError && error.status === 404) {
1518
- return;
1519
- }
1520
- if (!complained && !stop.aborted && !alreadyReported(error)) {
1521
- tools.print(`Could not check the folders for TAMsense: ${error instanceof Error ? error.message : String(error)}`);
1522
- complained = true;
1523
- }
1524
- }
1525
- await tools.sleep(CHECKS_INTERVAL_MS, stop);
1526
- }
1527
- }
1528
- var SKIPPED_FOLDER = "node_modules";
1529
- var MORE_CODE_TEXT = "To add more code, start the program from your code folder or the folder above it, or add a path in Settings, under your computer on the Account tab.";
1530
- async function childRepositories(start) {
1531
- const entries = await readdir(start, { withFileTypes: true }).catch(() => []);
1532
- return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== SKIPPED_FOLDER).map((entry) => join2(start, entry.name)).filter((folder) => existsSync2(join2(folder, ".git"))).sort();
1533
- }
1534
- async function findCodeFolders(start, home) {
1535
- const folder = resolve(start);
1536
- if (!existsSync2(folder)) {
1537
- return [];
1538
- }
1539
- const top = await gitText(["rev-parse", "--show-toplevel"], folder);
1540
- const candidates = top && samePath(top, folder) ? [folder] : await childRepositories(folder);
1541
- const found = [];
1542
- for (const candidate of candidates) {
1543
- const report = await inspectFolder(candidate, home);
1544
- if (report.ok) {
1545
- const { currentBranch, defaultBranch, remoteUrl } = report;
1546
- found.push({ path: candidate, name: basename(candidate), repository: remoteRepository(remoteUrl), currentBranch, defaultBranch, remoteUrl });
1547
- }
1548
- }
1549
- return found;
1550
- }
1551
- function foundCodeLines(start, found) {
1552
- const lines = found.length > 0 ? found.map((folder) => `Found code: ${folder.name} (${folder.path})`) : [`No code found in ${resolve(start)}.`];
1553
- return [...lines, MORE_CODE_TEXT];
1554
- }
1555
- async function reportFoundFolders(tools, start) {
1556
- try {
1557
- const found = await findCodeFolders(start, tools.home);
1558
- foundCodeLines(start, found).forEach((line) => tools.print(line));
1559
- await tools.api.request("PUT", FOUND_FOLDERS_PATH, { folders: found });
1560
- } catch (error) {
1561
- if (!(error instanceof ApiError && error.status === 404)) {
1562
- tools.print(`Could not tell TAMsense about the code on this computer: ${error instanceof Error ? error.message : String(error)}`);
1563
- }
1564
- }
1565
- }
1566
-
1567
1432
  // src/lock.ts
1568
1433
  function keyedLock() {
1569
1434
  const tails = /* @__PURE__ */ new Map();
@@ -1591,9 +1456,6 @@ function keyedLock() {
1591
1456
  }
1592
1457
 
1593
1458
  // src/workspace.ts
1594
- import { existsSync as existsSync3 } from "fs";
1595
- import { rm as rm2 } from "fs/promises";
1596
- import { join as join3, resolve as resolve2 } from "path";
1597
1459
  var RESULTS_FOLDER = "results";
1598
1460
  var FALLBACK_IDENTITY = { name: "TAMsense", email: "noreply@tamsense.com" };
1599
1461
  var cacheLock = keyedLock();
@@ -1601,7 +1463,7 @@ function githubRemoteUrl(fullName) {
1601
1463
  return `https://github.com/${fullName}.git`;
1602
1464
  }
1603
1465
  function tamsenseFolder(dataFolder, ...parts) {
1604
- return join3(dataFolder, ...parts);
1466
+ return join2(dataFolder, ...parts);
1605
1467
  }
1606
1468
  function folderNames(repositories) {
1607
1469
  const shortName = (fullName) => fullName.split("/").pop() ?? fullName;
@@ -1615,7 +1477,7 @@ function folderNames(repositories) {
1615
1477
  });
1616
1478
  }
1617
1479
  async function refreshCache(cache, repository, token, tools) {
1618
- if (existsSync3(join3(cache, ".git"))) {
1480
+ if (existsSync2(join2(cache, ".git"))) {
1619
1481
  await git(["fetch", "--prune", "origin"], { cwd: cache, token });
1620
1482
  return;
1621
1483
  }
@@ -1647,7 +1509,7 @@ function branchHolders(porcelain, branch) {
1647
1509
  }
1648
1510
  async function releaseBranch(cache, branch, keep) {
1649
1511
  const listed = await runGit(["worktree", "list", "--porcelain"], { cwd: cache });
1650
- for (const path of branchHolders(listed.stdout, branch).filter((holder) => resolve2(holder) !== resolve2(keep))) {
1512
+ for (const path of branchHolders(listed.stdout, branch).filter((holder) => resolve(holder) !== resolve(keep))) {
1651
1513
  await removeWorktree(cache, path);
1652
1514
  }
1653
1515
  }
@@ -1656,7 +1518,7 @@ async function baseRef(cache, job, repository) {
1656
1518
  return pushed.code === 0 ? `origin/${job.branch}` : `origin/${repository.defaultBranch}`;
1657
1519
  }
1658
1520
  async function keptBase(cache, path, job, repository) {
1659
- if (!job.keepWorkspace || !existsSync3(join3(path, ".git"))) {
1521
+ if (!job.keepWorkspace || !existsSync2(join2(path, ".git"))) {
1660
1522
  return null;
1661
1523
  }
1662
1524
  const branch = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path });
@@ -1675,7 +1537,7 @@ async function prepareWorkspace(job, token, tools) {
1675
1537
  for (const [index, repository] of job.repositories.entries()) {
1676
1538
  const cache = tamsenseFolder(tools.dataFolder, "repos", repository.fullName.replace("/", "__"));
1677
1539
  const folder = folders[index] ?? repository.fullName;
1678
- const path = join3(root, folder);
1540
+ const path = join2(root, folder);
1679
1541
  const baseSha = await cacheLock.run(repository.fullName, async () => {
1680
1542
  await refreshCache(cache, repository, token, tools);
1681
1543
  const kept = await keptBase(cache, path, job, repository);
@@ -1732,237 +1594,62 @@ async function publishWorkspace(workspace, job, token, identity) {
1732
1594
  return pushed;
1733
1595
  }
1734
1596
 
1735
- // src/localWorkspace.ts
1736
- var NETWORK_TIMEOUT_MS = 12e4;
1737
- var sourceLock = keyedLock();
1738
- function isLocalJob(job) {
1739
- return job.workspace === "local";
1597
+ // src/results.ts
1598
+ var SAVE_DOCUMENT_TOOL_NAME = "save_result_document";
1599
+ var SAVE_TABLE_TOOL_NAME = "save_result_table";
1600
+ var ATTACH_FILE_TOOL_NAME = "attach_result_file";
1601
+ var ADD_LINK_TOOL_NAME = "add_result_link";
1602
+ var HAND_OVER_TOOL_NAME = "hand_over";
1603
+ var MAX_NAME_LENGTH = 60;
1604
+ var TYPES_BY_EXTENSION = {
1605
+ ".pdf": "application/pdf",
1606
+ ".png": "image/png",
1607
+ ".jpg": "image/jpeg",
1608
+ ".jpeg": "image/jpeg",
1609
+ ".webp": "image/webp",
1610
+ ".gif": "image/gif",
1611
+ ".csv": "text/csv",
1612
+ ".txt": "text/plain",
1613
+ ".md": "text/markdown",
1614
+ ".json": "application/json",
1615
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1616
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1617
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1618
+ ".zip": "application/zip"
1619
+ };
1620
+ var EXTENSIONS = { document: ".md", table: ".csv", file: "", link: "" };
1621
+ function isFolderJob(job) {
1622
+ return job.workspace === "folder";
1740
1623
  }
1741
- function localBranchLine(branch) {
1742
- const local = branch.local;
1743
- if (!local) {
1744
- return `Pushed ${branch.branch} in ${branch.repository}.`;
1745
- }
1746
- const where = `Branch ${branch.branch} has ${local.commits} commit(s) in ${local.folder}`;
1747
- if (local.pushProblem) {
1748
- return `${where}. It was not pushed: ${local.pushProblem}`;
1624
+ function resultTools(job, api, root) {
1625
+ if (!isFolderJob(job) || job.stage.kind !== "code") {
1626
+ return null;
1749
1627
  }
1750
- return local.remoteUrl ? `${where}, pushed to ${local.remoteUrl}.` : `${where}.`;
1751
- }
1752
- function readsOnly(job) {
1753
- return isReadOnlyCheck(job) || job.stage.kind === "interview" || job.stage.kind === "spec";
1754
- }
1755
- async function isAncestor(folder, older, newer) {
1756
- return (await runGit(["merge-base", "--is-ancestor", older, newer], { cwd: folder })).code === 0;
1628
+ return {
1629
+ root,
1630
+ save: (body) => api.request("POST", runPath(job.runId, "results"), body),
1631
+ attach: (body) => api.request("POST", runPath(job.runId, "result-files"), body)
1632
+ };
1757
1633
  }
1758
- async function fetchRemote(source, remote, print2) {
1759
- const fetched = await runGit(["fetch", "--prune", remote], { cwd: source, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS });
1760
- if (fetched.code !== 0) {
1761
- print2(`Could not fetch from ${remote} in ${source}, going on with what the folder has: ${fetched.stderr || `exit code ${fetched.code}`}`);
1634
+ function handOverTools(job, api) {
1635
+ const boards = job.handOverBoards ?? [];
1636
+ if (boards.length === 0 || job.stage.kind !== "code") {
1637
+ return null;
1762
1638
  }
1639
+ return { boards, send: (body) => api.request("POST", runPath(job.runId, "hand-over"), body) };
1763
1640
  }
1764
- async function baseBranchRef(source, branch, remote) {
1765
- const own = await hasRef(source, `refs/heads/${branch}`) ? branch : null;
1766
- const fetched = remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null;
1767
- if (own && fetched) {
1768
- return await isAncestor(source, own, fetched) ? fetched : own;
1769
- }
1770
- const found = own ?? fetched;
1771
- if (!found) {
1772
- throw new Error(`The main branch ${branch} is not in ${source}. Check the folder again in Settings, under your computer on the Account tab.`);
1641
+ function resultFileName(result, taken) {
1642
+ const stem = (result.kind === "file" && result.fileName ? result.fileName.replace(/\.[^.]*$/, "") : result.title).normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, MAX_NAME_LENGTH) || "result";
1643
+ const extension = result.kind === "file" ? extname(result.fileName ?? "") : EXTENSIONS[result.kind];
1644
+ let name = `${stem}${extension}`;
1645
+ for (let index = 2; taken.has(name.toLowerCase()); index += 1) {
1646
+ name = `${stem}-${index}${extension}`;
1773
1647
  }
1774
- return found;
1648
+ taken.add(name.toLowerCase());
1649
+ return name;
1775
1650
  }
1776
- async function taskBranchRef(source, branch, remote, base) {
1777
- if (await hasRef(source, `refs/heads/${branch}`) && Number(await git(["rev-list", "--count", `${base}..${branch}`], { cwd: source })) > 0) {
1778
- return branch;
1779
- }
1780
- return remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null;
1781
- }
1782
- async function startRef(source, job, local) {
1783
- const base = await baseBranchRef(source, local.baseBranch, local.remote);
1784
- const task = await taskBranchRef(source, job.branch, local.remote, base);
1785
- if (task || !isReadOnlyCheck(job) || job.severalBranches) {
1786
- return task ?? base;
1787
- }
1788
- const where = local.remote ? `${source} or on its remote ${local.remote}` : source;
1789
- throw new Error(`Branch ${job.branch} is not in ${where}. It was made on another computer: push it to a remote this folder can fetch, or run this check on that computer.`);
1790
- }
1791
- async function releaseBranch2(source, branch, keep, dataFolder) {
1792
- const listed = await runGit(["worktree", "list", "--porcelain"], { cwd: source });
1793
- const work2 = tamsenseFolder(dataFolder, "work");
1794
- for (const holder of branchHolders(listed.stdout, branch).filter((path) => resolve3(path) !== resolve3(keep))) {
1795
- if (!isInside(holder, work2)) {
1796
- throw new Error(`Branch ${branch} is checked out in ${resolve3(holder)}. Switch that folder to another branch, then start the step again.`);
1797
- }
1798
- await removeWorktree(source, holder);
1799
- }
1800
- }
1801
- async function keptBase2(path, job) {
1802
- if (!job.keepWorkspace || !existsSync4(join4(path, ".git"))) {
1803
- return null;
1804
- }
1805
- const branch = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path });
1806
- if (branch.code !== 0 || branch.stdout !== (readsOnly(job) ? "HEAD" : job.branch)) {
1807
- return null;
1808
- }
1809
- return git(["rev-parse", "HEAD"], { cwd: path });
1810
- }
1811
- function sourceOf(repository) {
1812
- if (!repository.folder) {
1813
- throw new Error(`TAMsense did not say which folder holds ${repository.fullName} on this computer.`);
1814
- }
1815
- return repository.folder;
1816
- }
1817
- async function prepareRepository(job, repository, path, tools) {
1818
- const source = sourceOf(repository);
1819
- if ((await runGit(["rev-parse", "--show-toplevel"], { cwd: source }).catch(() => null))?.code !== 0) {
1820
- throw new Error(`${source} is no longer a git repository on this computer. Check the folder again in Settings, under your computer on the Account tab.`);
1821
- }
1822
- const local = { source, remote: await remoteOf(source), baseBranch: repository.defaultBranch };
1823
- if (local.remote) {
1824
- await fetchRemote(source, local.remote, tools.print);
1825
- }
1826
- const kept = await keptBase2(path, job);
1827
- if (kept) {
1828
- return { baseSha: kept, local };
1829
- }
1830
- await runGit(["worktree", "prune"], { cwd: source });
1831
- if (existsSync4(path)) {
1832
- await removeWorktree(source, path);
1833
- }
1834
- const start = await startRef(source, job, local);
1835
- if (readsOnly(job)) {
1836
- await git(["worktree", "add", "--detach", path, start], { cwd: source });
1837
- } else {
1838
- await releaseBranch2(source, job.branch, path, tools.dataFolder);
1839
- await git(["worktree", "add", "-B", job.branch, path, start], { cwd: source });
1840
- }
1841
- return { baseSha: await git(["rev-parse", "HEAD"], { cwd: path }), local };
1842
- }
1843
- async function prepareLocalWorkspace(job, tools) {
1844
- const root = taskFolder(tools.dataFolder, job);
1845
- const folders = folderNames(job.repositories);
1846
- const repositories = [];
1847
- for (const [index, repository] of job.repositories.entries()) {
1848
- const folder = folders[index] ?? repository.fullName;
1849
- const path = join4(root, folder);
1850
- const prepared = await sourceLock.run(resolve3(sourceOf(repository)), () => prepareRepository(job, repository, path, tools));
1851
- repositories.push({ fullName: repository.fullName, folder, path, baseSha: prepared.baseSha, local: prepared.local });
1852
- }
1853
- return { root, repositories };
1854
- }
1855
- async function pushBranch(path, local, branch) {
1856
- if (!local.remote) {
1857
- return { remoteUrl: null, pushProblem: null };
1858
- }
1859
- if (branch === local.baseBranch) {
1860
- return { remoteUrl: null, pushProblem: `TAMsense never pushes the main branch ${branch}.` };
1861
- }
1862
- const pushed = await runGit(["push", local.remote, `HEAD:refs/heads/${branch}`], { cwd: path, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS });
1863
- if (pushed.code !== 0) {
1864
- return { remoteUrl: null, pushProblem: pushed.stderr || `exit code ${pushed.code}` };
1865
- }
1866
- return { remoteUrl: await git(["remote", "get-url", local.remote], { cwd: path }), pushProblem: null };
1867
- }
1868
- async function commitAndPush(repository, local, job, identity) {
1869
- const options = { cwd: repository.path, env: identity };
1870
- await git(["add", "--all"], options);
1871
- if ((await runGit(["diff", "--cached", "--quiet"], options)).code === 1) {
1872
- await git(["commit", "--message", `Task #${job.taskId}: ${job.title}`], options);
1873
- }
1874
- if (Number(await git(["rev-list", "--count", `${repository.baseSha}..HEAD`], options)) === 0) {
1875
- return null;
1876
- }
1877
- const base = await baseBranchRef(repository.path, local.baseBranch, local.remote);
1878
- const commits = Number(await git(["rev-list", "--count", `${base}..HEAD`], options));
1879
- const sent3 = await pushBranch(repository.path, local, job.branch);
1880
- return { sha: await git(["rev-parse", "HEAD"], options), commits, ...sent3 };
1881
- }
1882
- async function publishLocalWorkspace(workspace, job, identity) {
1883
- const pushed = [];
1884
- for (const repository of workspace.repositories) {
1885
- const local = repository.local;
1886
- if (!local) {
1887
- continue;
1888
- }
1889
- const branch = await sourceLock.run(resolve3(local.source), async () => {
1890
- const published = await commitAndPush(repository, local, job, identity);
1891
- await runGit(["checkout", "--quiet", "--detach"], { cwd: repository.path });
1892
- return published;
1893
- });
1894
- if (branch) {
1895
- pushed.push({
1896
- repository: repository.fullName,
1897
- branch: job.branch,
1898
- sha: branch.sha,
1899
- local: { folder: local.source, baseBranch: local.baseBranch, commits: branch.commits, remoteUrl: branch.remoteUrl && remoteWithoutSecrets(branch.remoteUrl), pushProblem: branch.pushProblem }
1900
- });
1901
- }
1902
- }
1903
- return pushed;
1904
- }
1905
-
1906
- // src/results.ts
1907
- import { mkdir as mkdir2, readFile as readFile2, rm as rm3, stat, writeFile as writeFile2 } from "fs/promises";
1908
- import { basename as basename2, extname, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "path";
1909
- import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
1910
- import { z as z2 } from "zod";
1911
- var SAVE_DOCUMENT_TOOL_NAME = "save_result_document";
1912
- var SAVE_TABLE_TOOL_NAME = "save_result_table";
1913
- var ATTACH_FILE_TOOL_NAME = "attach_result_file";
1914
- var ADD_LINK_TOOL_NAME = "add_result_link";
1915
- var HAND_OVER_TOOL_NAME = "hand_over";
1916
- var MAX_NAME_LENGTH = 60;
1917
- var TYPES_BY_EXTENSION = {
1918
- ".pdf": "application/pdf",
1919
- ".png": "image/png",
1920
- ".jpg": "image/jpeg",
1921
- ".jpeg": "image/jpeg",
1922
- ".webp": "image/webp",
1923
- ".gif": "image/gif",
1924
- ".csv": "text/csv",
1925
- ".txt": "text/plain",
1926
- ".md": "text/markdown",
1927
- ".json": "application/json",
1928
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1929
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1930
- ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1931
- ".zip": "application/zip"
1932
- };
1933
- var EXTENSIONS = { document: ".md", table: ".csv", file: "", link: "" };
1934
- function isFolderJob(job) {
1935
- return job.workspace === "folder";
1936
- }
1937
- function resultTools(job, api, root) {
1938
- if (!isFolderJob(job) || job.stage.kind !== "code") {
1939
- return null;
1940
- }
1941
- return {
1942
- root,
1943
- save: (body) => api.request("POST", runPath(job.runId, "results"), body),
1944
- attach: (body) => api.request("POST", runPath(job.runId, "result-files"), body)
1945
- };
1946
- }
1947
- function handOverTools(job, api) {
1948
- const boards = job.handOverBoards ?? [];
1949
- if (boards.length === 0 || job.stage.kind !== "code") {
1950
- return null;
1951
- }
1952
- return { boards, send: (body) => api.request("POST", runPath(job.runId, "hand-over"), body) };
1953
- }
1954
- function resultFileName(result, taken) {
1955
- const stem = (result.kind === "file" && result.fileName ? result.fileName.replace(/\.[^.]*$/, "") : result.title).normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, MAX_NAME_LENGTH) || "result";
1956
- const extension = result.kind === "file" ? extname(result.fileName ?? "") : EXTENSIONS[result.kind];
1957
- let name = `${stem}${extension}`;
1958
- for (let index = 2; taken.has(name.toLowerCase()); index += 1) {
1959
- name = `${stem}-${index}${extension}`;
1960
- }
1961
- taken.add(name.toLowerCase());
1962
- return name;
1963
- }
1964
- function linksText(links) {
1965
- return links.map((link) => `- ${link.title}: ${link.url ?? ""}`).join("\n");
1651
+ function linksText(links) {
1652
+ return links.map((link) => `- ${link.title}: ${link.url ?? ""}`).join("\n");
1966
1653
  }
1967
1654
  async function writeResults(folder, job, api) {
1968
1655
  const results = job.results ?? [];
@@ -1979,24 +1666,24 @@ async function writeResults(folder, job, api) {
1979
1666
  if (result.kind === "file") {
1980
1667
  try {
1981
1668
  const file = await api.request("GET", resultFilePath(job.runId, result.id));
1982
- await writeFile2(join5(folder, name), Buffer.from(file.data, "base64"));
1669
+ await writeFile2(join3(folder, name), Buffer.from(file.data, "base64"));
1983
1670
  } catch (error) {
1984
1671
  missing.push(`${result.title}: ${error instanceof Error ? error.message : String(error)}`);
1985
1672
  continue;
1986
1673
  }
1987
1674
  } else {
1988
- await writeFile2(join5(folder, name), result.content ?? "");
1675
+ await writeFile2(join3(folder, name), result.content ?? "");
1989
1676
  }
1990
1677
  lines.push(`${RESULTS_FOLDER}/${name}: ${result.title}${result.version ? `, version ${result.version}` : ""}`);
1991
1678
  }
1992
1679
  const links = results.filter((item) => item.kind === "link");
1993
1680
  if (links.length > 0) {
1994
- await writeFile2(join5(folder, "links.md"), `${linksText(links)}
1681
+ await writeFile2(join3(folder, "links.md"), `${linksText(links)}
1995
1682
  `);
1996
1683
  lines.push(`${RESULTS_FOLDER}/links.md: ${links.length} link(s)`);
1997
1684
  }
1998
1685
  if (missing.length > 0) {
1999
- await writeFile2(join5(folder, "notes.md"), `These files could not be downloaded:
1686
+ await writeFile2(join3(folder, "notes.md"), `These files could not be downloaded:
2000
1687
  ${missing.map((line) => `- ${line}`).join("\n")}
2001
1688
  `);
2002
1689
  }
@@ -2007,10 +1694,10 @@ async function prepareFolder(job, root, api) {
2007
1694
  await rm3(root, { recursive: true, force: true });
2008
1695
  }
2009
1696
  await mkdir2(root, { recursive: true });
2010
- return { root, repositories: [], results: await writeResults(join5(root, RESULTS_FOLDER), job, api) };
1697
+ return { root, repositories: [], results: await writeResults(join3(root, RESULTS_FOLDER), job, api) };
2011
1698
  }
2012
1699
  async function readResultFile(root, path, title) {
2013
- const full = isAbsolute2(path) ? path : resolve4(root, path);
1700
+ const full = isAbsolute(path) ? path : resolve2(root, path);
2014
1701
  const info = await stat(full).catch(() => null);
2015
1702
  if (!info?.isFile()) {
2016
1703
  return `There is no file at ${full}.`;
@@ -2018,7 +1705,7 @@ async function readResultFile(root, path, title) {
2018
1705
  if (info.size > MAX_RESULT_FILE_BYTES) {
2019
1706
  return `The file is larger than ${MAX_RESULT_FILE_BYTES / 1024 / 1024} MB. Attach a smaller one or split it.`;
2020
1707
  }
2021
- const name = basename2(full);
1708
+ const name = basename(full);
2022
1709
  return { name, title: title.trim() || name, contentType: TYPES_BY_EXTENSION[extname(full).toLowerCase()] ?? "application/octet-stream", data: (await readFile2(full)).toString("base64") };
2023
1710
  }
2024
1711
  function reply(text2, isError = false) {
@@ -2091,7 +1778,7 @@ function handOverTool(tools) {
2091
1778
  }
2092
1779
 
2093
1780
  // src/schedules.ts
2094
- import { existsSync as existsSync5 } from "fs";
1781
+ import { existsSync as existsSync3 } from "fs";
2095
1782
  import { mkdir as mkdir3 } from "fs/promises";
2096
1783
  import { createSdkMcpServer, query as query2, tool as tool5 } from "@anthropic-ai/claude-agent-sdk";
2097
1784
  import { z as z5 } from "zod";
@@ -2226,6 +1913,10 @@ var PROPOSE_SCHEDULE_TOOL_NAME = "propose_schedule";
2226
1913
  var REPORT_CHECK_TOOL_NAME = "report_check";
2227
1914
  var WAKE_TOOL_NAME = "wake_up_later";
2228
1915
  var SCHEDULE_RULE = `Some waits are long: a local stand or a database copy that takes half an hour to come up, a slow build or import, a service that answers only after a while, logs that show a problem only over hours. Do not sleep or poll in a loop for that. Propose a schedule with the ${PROPOSE_SCHEDULE_TOOL_NAME} tool: a short check repeated on a timer by a cheap model that reads the notes of earlier runs and reports one line in the task chat. Give it a clear condition that ends it, the longest interval that still helps, and when to call a person. With wait true this run ends its turn and continues here when the schedule ends. Never use a schedule for work you can finish now, for a check you can do once, or to watch something "just in case". Without autopilot a person starts it first; on autopilot it starts at once within the project limits.`;
1916
+ var SCHEDULE_RULE_RU = `\u041D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0434\u043E\u043B\u0433\u0438\u0435: \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0441\u0442\u0435\u043D\u0434 \u0438\u043B\u0438 \u043A\u043E\u043F\u0438\u044F \u0431\u0430\u0437\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043F\u043E\u043B\u0447\u0430\u0441\u0430, \u043C\u0435\u0434\u043B\u0435\u043D\u043D\u0430\u044F \u0441\u0431\u043E\u0440\u043A\u0430 \u0438\u043B\u0438 \u0438\u043C\u043F\u043E\u0440\u0442, \u0441\u0435\u0440\u0432\u0438\u0441, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0441\u043F\u0443\u0441\u0442\u044F \u0432\u0440\u0435\u043C\u044F, \u043B\u043E\u0433\u0438, \u0433\u0434\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0430 \u0432\u0438\u0434\u043D\u0430 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0430 \u0447\u0430\u0441\u044B. \u041D\u0435 \u0441\u043F\u0438 \u0438 \u043D\u0435 \u043E\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0432 \u0446\u0438\u043A\u043B\u0435 \u0440\u0430\u0434\u0438 \u044D\u0442\u043E\u0433\u043E. \u041F\u0440\u0435\u0434\u043B\u043E\u0436\u0438 \u0440\u0430\u0441\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C ${PROPOSE_SCHEDULE_TOOL_NAME}: \u043A\u043E\u0440\u043E\u0442\u043A\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u043E \u0442\u0430\u0439\u043C\u0435\u0440\u0443 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0435\u0442 \u0434\u0435\u0448\u0451\u0432\u0430\u044F \u043C\u043E\u0434\u0435\u043B\u044C; \u043E\u043D\u0430 \u0447\u0438\u0442\u0430\u0435\u0442 \u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u043F\u0440\u043E\u0448\u043B\u044B\u0445 \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u0432 \u0438 \u043F\u0438\u0448\u0435\u0442 \u043E\u0434\u043D\u0443 \u0441\u0442\u0440\u043E\u043A\u0443 \u0432 \u0447\u0430\u0442 \u0437\u0430\u0434\u0430\u0447\u0438. \u0414\u0430\u0439 \u0435\u043C\u0443 \u044F\u0441\u043D\u043E\u0435 \u0443\u0441\u043B\u043E\u0432\u0438\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u044F, \u0441\u0430\u043C\u044B\u0439 \u0434\u043B\u0438\u043D\u043D\u044B\u0439 \u0438\u043D\u0442\u0435\u0440\u0432\u0430\u043B, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0435\u0449\u0451 \u043F\u043E\u043B\u0435\u0437\u0435\u043D, \u0438 \u0441\u043A\u0430\u0436\u0438, \u043A\u043E\u0433\u0434\u0430 \u0437\u0432\u0430\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u0421 wait true \u044D\u0442\u043E\u0442 \u0437\u0430\u043F\u0443\u0441\u043A \u0437\u0430\u043A\u0430\u043D\u0447\u0438\u0432\u0430\u0435\u0442 \u0445\u043E\u0434 \u0438 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442\u0441\u044F \u0437\u0434\u0435\u0441\u044C, \u043A\u043E\u0433\u0434\u0430 \u0440\u0430\u0441\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u0441\u044F. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0440\u0430\u0441\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u0434\u043B\u044F \u0440\u0430\u0431\u043E\u0442\u044B, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043C\u043E\u0436\u0435\u0448\u044C \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0442\u044C \u0441\u0435\u0439\u0447\u0430\u0441, \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043E\u0434\u0438\u043D \u0440\u0430\u0437, \u0438\u043B\u0438 \u0447\u0442\u043E\u0431\u044B \u0441\u043B\u0435\u0434\u0438\u0442\u044C \u0437\u0430 \u0447\u0435\u043C-\u0442\u043E \xAB\u043D\u0430 \u0432\u0441\u044F\u043A\u0438\u0439 \u0441\u043B\u0443\u0447\u0430\u0439\xBB. \u0411\u0435\u0437 \u0430\u0432\u0442\u043E\u043F\u0438\u043B\u043E\u0442\u0430 \u0435\u0433\u043E \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A; \u043D\u0430 \u0430\u0432\u0442\u043E\u043F\u0438\u043B\u043E\u0442\u0435 \u043E\u043D\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443 \u0432 \u043F\u0440\u0435\u0434\u0435\u043B\u0430\u0445 \u043B\u0438\u043C\u0438\u0442\u043E\u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.`;
1917
+ function scheduleRule(language) {
1918
+ return language === "ru" ? SCHEDULE_RULE_RU : SCHEDULE_RULE;
1919
+ }
2229
1920
  var ALREADY_WAITING_TEXT2 = "This run already waits for something. End your turn now: the conversation continues when it comes.";
2230
1921
  function reply3(text2, isError = false) {
2231
1922
  return { content: [{ type: "text", text: text2 }], ...isError ? { isError: true } : {} };
@@ -2278,17 +1969,18 @@ function scheduleTools(api, path, wakePath) {
2278
1969
  function wakeUpLaterTool(tools) {
2279
1970
  return tool5(
2280
1971
  WAKE_TOOL_NAME,
2281
- "Put the task to sleep and wake it up later, once: for example 7 days after a mailing to collect its results, or 3 days after a follow-up. Nothing runs and nothing is charged while it sleeps. On waking the task moves to the step you name, along a rule of the board (a rule that only a person moves waits for that person), or this step starts again when you name none. A person moving the task meanwhile cancels the wake-up. Finish your run right after you set it.",
1972
+ "Plan one more run of this task later, once: for example after a mailing has finished sending, to collect its results, or 3 days after a follow-up. Nothing runs and nothing is charged until then. Then the task moves to the step you name, along a rule of the board (a rule that only a person moves waits for that person), or this step starts again when you name none. A person moving the task meanwhile cancels the planned run. Finish your run right after you plan it.",
2282
1973
  {
2283
1974
  inHours: z5.number().min(0.1).max(2160).describe("how many hours from now, for example 168 for 7 days"),
2284
- step: z5.string().describe("the name of the step to wake up on, empty to start this step again"),
2285
- note: z5.string().describe("what to do on waking, in plain words")
1975
+ step: z5.string().describe("the name of the step to go on with, empty to start this step again"),
1976
+ note: z5.string().describe("what to do then, in plain words: your own detailed note for later"),
1977
+ summary: z5.string().describe('one short line for the people on the task, in their language: what you will do then, for example "Sum up wave 1 and decide on wave 2". No ids, tool names or numbers they do not need')
2286
1978
  },
2287
1979
  async (args) => {
2288
1980
  try {
2289
- return reply3((await tools.wake({ inHours: args.inHours, step: args.step.trim(), note: args.note.trim() })).text);
1981
+ return reply3((await tools.wake({ inHours: args.inHours, step: args.step.trim(), note: args.note.trim(), summary: args.summary.trim() })).text);
2290
1982
  } catch (error) {
2291
- return reply3(`Could not set the wake-up: ${error instanceof Error ? error.message : String(error)}. Say in your report when the task should continue.`, true);
1983
+ return reply3(`Could not plan the run: ${error instanceof Error ? error.message : String(error)}. Say in your report when the task should continue.`, true);
2292
1984
  }
2293
1985
  }
2294
1986
  );
@@ -2296,7 +1988,7 @@ function wakeUpLaterTool(tools) {
2296
1988
  async function checkFolder(dataFolder, job) {
2297
1989
  if (job.taskId !== null) {
2298
1990
  const step = taskFolder(dataFolder, { taskId: job.taskId, stage: { columnId: job.stepColumnId } });
2299
- if (existsSync5(step)) {
1991
+ if (existsSync3(step)) {
2300
1992
  return step;
2301
1993
  }
2302
1994
  }
@@ -2427,108 +2119,11 @@ function checkLine(body) {
2427
2119
  }
2428
2120
  var runClaudeCheck = claudeCheckRunner(query2);
2429
2121
 
2430
- // src/specText.ts
2431
- function isRecord(value) {
2432
- return typeof value === "object" && value !== null && !Array.isArray(value);
2433
- }
2434
- function text(value) {
2435
- return typeof value === "string" ? value.trim() : "";
2436
- }
2437
- function texts(value) {
2438
- return Array.isArray(value) ? value.map(text).filter(Boolean) : [];
2439
- }
2440
- function records(value) {
2441
- return Array.isArray(value) ? value.filter(isRecord) : [];
2442
- }
2443
- function section(title, lines) {
2444
- return lines.length > 0 ? `${title}
2445
- ${lines.join("\n")}` : null;
2446
- }
2447
- function criterionLine(value) {
2448
- if (typeof value === "string") {
2449
- return value.trim() ? `- ${value.trim()}` : null;
2450
- }
2451
- if (!isRecord(value) || !text(value.text)) {
2452
- return null;
2453
- }
2454
- const kind = ["main", "edge", "keep"].includes(value.kind) ? `(${value.kind})` : "";
2455
- const head = [text(value.id), kind].filter(Boolean).join(" ");
2456
- return `- ${head ? `${head}: ` : ""}${text(value.text)}`;
2457
- }
2458
- function assumptionLine(value) {
2459
- if (typeof value === "string") {
2460
- return value.trim() ? `- ${value.trim()}` : null;
2461
- }
2462
- if (!isRecord(value) || !text(value.text)) {
2463
- return null;
2464
- }
2465
- return `- ${text(value.text)}${text(value.reason) ? ` (${text(value.reason)})` : ""}`;
2466
- }
2467
- function changeLines(change, index) {
2468
- const action = ["create", "modify", "delete"].includes(change.action) ? ` (${change.action})` : "";
2469
- const [produces, consumes, acceptance] = [texts(change.produces), texts(change.consumes), texts(change.acceptance)];
2470
- return [
2471
- `${index + 1}. ${text(change.repo) ? `${text(change.repo)} ` : ""}${text(change.file)}${action}: ${text(change.description)}`,
2472
- ...produces.length > 0 ? [` Produces: ${produces.join("; ")}`] : [],
2473
- ...consumes.length > 0 ? [` Consumes: ${consumes.join("; ")}`] : [],
2474
- ...acceptance.length > 0 ? [` Makes true: ${acceptance.join(", ")}`] : []
2475
- ];
2476
- }
2477
- var CODE_USE_TEXT = { reuse: "reuse", change: "change", keep: "do not touch" };
2478
- var RISK_LEVELS = ["low", "medium", "high"];
2479
- function present(lines) {
2480
- return lines.filter((line) => line !== null);
2481
- }
2482
- function decisionLines(spec) {
2483
- return records(spec.decisions).filter((item) => text(item.question) && text(item.answer)).map((item) => `- ${text(item.question)} \u2192 ${text(item.answer)} (${item.by === "agent" ? "agent's call" : "owner"})`);
2484
- }
2485
- function briefPromptText(brief) {
2486
- return [
2487
- text(brief.goal) && `Goal: ${text(brief.goal)}`,
2488
- text(brief.context) && `Summary: ${text(brief.context)}`,
2489
- section("The result must contain:", texts(brief.contains).map((item) => `- ${item}`)),
2490
- section("Acceptance criteria:", present(Array.isArray(brief.acceptance) ? brief.acceptance.map(criterionLine) : [])),
2491
- section("Constraints:", texts(brief.constraints).map((item) => `- ${item}`)),
2492
- section("Out of scope (do not make):", texts(brief.outOfScope).map((item) => `- ${item}`)),
2493
- section("Sources to use:", records(brief.sources).filter((item) => text(item.source)).map((item) => `- ${text(item.source)}${text(item.use) ? `: ${text(item.use)}` : ""}`)),
2494
- text(brief.approver) && `Approved by: ${text(brief.approver)}`,
2495
- section("Decisions:", decisionLines(brief)),
2496
- section("Assumptions:", present(Array.isArray(brief.assumptions) ? brief.assumptions.map(assumptionLine) : [])),
2497
- section("Open questions (not answered yet):", records(brief.openQuestions).filter((item) => text(item.question)).map((item) => `- ${text(item.question)}`))
2498
- ].filter(Boolean).join("\n\n");
2499
- }
2500
- function specPromptText(spec) {
2501
- if (!isRecord(spec)) {
2502
- return null;
2503
- }
2504
- if (spec.format === "brief") {
2505
- return briefPromptText(spec);
2506
- }
2507
- const structured = Array.isArray(spec.outOfScope) || isRecord(spec.testPlan);
2508
- const plan = isRecord(spec.testPlan) ? spec.testPlan : {};
2509
- const risks = records(spec.risks).filter((item) => text(item.risk));
2510
- const riskLevel = RISK_LEVELS.includes(spec.riskLevel) ? spec.riskLevel : null;
2511
- return [
2512
- text(spec.goal) && `Goal: ${text(spec.goal)}`,
2513
- text(spec.context) && `${structured ? "Summary" : "Context"}: ${text(spec.context)}`,
2514
- section("In scope:", texts(spec.inScope).map((item) => `- ${item}`)),
2515
- section("Out of scope (do not build):", texts(spec.outOfScope).map((item) => `- ${item}`)),
2516
- section("Decisions:", decisionLines(spec)),
2517
- section("Code map:", records(spec.codeMap).filter((item) => text(item.file)).map((item) => `- ${text(item.repo) ? `${text(item.repo)} ` : ""}${text(item.file)}: ${text(item.role)}${CODE_USE_TEXT[item.use] ? ` [${CODE_USE_TEXT[item.use]}]` : ""}`)),
2518
- section("Changes, in order:", records(spec.changes).filter((item) => text(item.file)).flatMap(changeLines)),
2519
- section("Acceptance criteria:", present(Array.isArray(spec.acceptance) ? spec.acceptance.map(criterionLine) : [])),
2520
- section("Test plan:", [
2521
- ...records(plan.automated).filter((item) => text(item.command)).map((item) => `- Run in ${text(item.repo) || "the repository"}: ${text(item.command)}${text(item.expect) ? `. Passing: ${text(item.expect)}` : ""}`),
2522
- ...records(plan.newTests).filter((item) => text(item.seam)).map((item) => `- New test at ${text(item.seam)}${texts(item.covers).length > 0 ? `, covers ${texts(item.covers).join(", ")}` : ""}`),
2523
- ...records(plan.manual).filter((item) => text(item.steps)).map((item) => `- By hand${text(item.page) ? ` on ${text(item.page)}` : ""}: ${text(item.steps)}`)
2524
- ]),
2525
- risks.length > 0 ? section(`Risks${riskLevel ? ` (risk level ${riskLevel})` : ""}:`, risks.map((item) => `- ${text(item.risk)}${text(item.mitigation) ? `. Mitigation: ${text(item.mitigation)}` : ""}`)) : riskLevel && `Risk level: ${riskLevel}`,
2526
- section("Assumptions:", present(Array.isArray(spec.assumptions) ? spec.assumptions.map(assumptionLine) : [])),
2527
- section("Open questions (not answered yet):", records(spec.openQuestions).filter((item) => text(item.question)).map((item) => `- ${text(item.question)}`))
2528
- ].filter(Boolean).join("\n\n");
2529
- }
2122
+ // src/specTools.ts
2123
+ import { tool as tool6 } from "@anthropic-ai/claude-agent-sdk";
2124
+ import { z as z6 } from "zod";
2530
2125
 
2531
- // src/prompt.ts
2126
+ // src/toolNames.ts
2532
2127
  var VERDICT_TOOL_NAME = "submit_verdict";
2533
2128
  var SCREENSHOT_TOOL_NAME = "attach_screenshot";
2534
2129
  var REPLAY_TOOL_NAME = "attach_replay";
@@ -2537,414 +2132,8 @@ var HANDOFF_TOOL_NAME = "hand_off";
2537
2132
  var SUBMIT_QUESTIONS_TOOL_NAME = "submit_questions";
2538
2133
  var SUBMIT_SPEC_TOOL_NAME = "submit_spec";
2539
2134
  var ASK_PERSON_TOOL_NAME = "ask_person";
2540
- var MESSAGES_RULE = `You are working on a TAMsense task on the user's own computer. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them: they take priority over the original task where the two disagree.`;
2541
- var UNATTENDED_RULE = `${MESSAGES_RULE} Nobody answers questions during the run, so never stop to wait for a reply: decide yourself, keep working, and explain your decisions in your report.`;
2542
- var ASKING_RULE = `${MESSAGES_RULE} Decide yourself when you reasonably can, keep working, and explain your decisions in your report. When you are stuck, or the decision belongs to a person (intent, design, access, how the team runs something locally), ask one of the people on the task with the ${ASK_PERSON_TOOL_NAME} tool instead of guessing, and say what you tried. Never ask what the code or the project knowledge already answers. Nobody watches your messages, so ask only with the tool.`;
2543
- var APPROVAL_RULE = "Some actions need a person's approval. When Claude Code would ask for one, TAMsense checks the project safety rules: a Never action is refused at once, an Allowed one goes ahead, and anything else goes to a person on the task while your tool call waits up to 15 minutes. Before such an action, write one short line: why you need it and what you will do instead if it is denied. Never get around a refused action with another command that has the same effect.";
2544
- var MACHINE_RULE = "This is the person's own computer, and their other programs may be running on it. Stop only the processes you started yourself, by their process id, and stop them before you finish. Never kill processes by name or pattern (taskkill /IM, pkill, killall, Stop-Process -Name) and never touch programs you did not start.";
2545
- var CODE_RULES = [
2546
- "Make the changes the task asks for, and keep them focused on it.",
2547
- "When there is an approved spec, follow it: make the changes in its order with the names and data shapes it gives, build nothing from its out of scope list, and add the tests and run the commands of its test plan.",
2548
- "Check your work with the project's own tools: run the tests, type checks and linters that exist, and fix what you broke.",
2549
- "Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch and opens the pull request."
2550
- ];
2551
- var LOCAL_COMMIT_RULE = "Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch when the folder has a remote, and the person merges it.";
2552
- var LOCAL_FOLDERS_RULE = "Each repository here is a git worktree of the person's own folder on this computer. Work only here: never open, change or check out branches in the person's original folder.";
2553
- var REWORK_RULE = "This is a rework. The branch already has the earlier work and maybe the reviewer's own commits: treat the current branch as the source of truth, keep the reviewer's changes unless a comment asks otherwise, and address every comment.";
2554
- var CODE_REPORT_RULE = "End with a short report for the reviewer: what you changed, how you checked it, and anything left open.";
2555
- var READ_ONLY_CODE_RULES = [
2556
- "Your role on this step only reads. Do the work the task asks for by researching and running what helps, then report. Do not change, commit or push product code: nothing from this run is pushed. You may create throwaway files outside the repositories.",
2557
- "Check what you report with the project's own tools where they exist, and say how you checked it."
2558
- ];
2559
- var READ_ONLY_REPORT_RULE = "End with a short report: what you found, how you checked it, and what is left open.";
2560
- var CHECK_RULES2 = [
2561
- "The work to check is on the current branch. See it with git diff against the base branch named for each repository below, and read the code around it.",
2562
- "Do not change, commit or push product code. You may run anything and create throwaway files outside the repositories.",
2563
- "Judge against the task, the spec and its acceptance criteria, checking each criterion by its id (AC-1 and so on) when the spec has ids. Be specific: every problem gets a file, a line and what to do about it.",
2564
- `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`
2565
- ];
2566
- var SCREENSHOT_RULE = `If you render screens (screenshots of the interface), attach the ones that show the result with the ${SCREENSHOT_TOOL_NAME} tool. People see them on the task without opening the code.`;
2567
- var NAMING_RULE = 'Name everything you attach (screenshots, replays, videos) by what it shows and why it was made: a short title such as "Design option 1", "Feature testing" or "Feature testing, attempt 2", and a one-line purpose such as "Comparing two layouts for the empty orders state". Number design variants, name test runs by their goal and attempt, and never use generic names like "screenshot1".';
2568
- var RECORDING_RULE = `Every browser you open with Playwright from Node is recorded for the task as a replay people can watch, requests from the tested site to the TAMsense tracker are blocked, so your runs never reach analytics, and requests other than GET, HEAD and OPTIONS to addresses outside this computer are aborted. In every browser script, load the recorder from the TAMSENSE_RECORDER environment variable (require(process.env.TAMSENSE_RECORDER), or createRequire(import.meta.url)(process.env.TAMSENSE_RECORDER) in an .mjs script), open each context with await recorder.newRecordedContext(browser, { title, purpose, context: { viewport } }), call await recorder.mark(page, 'what just happened') at every meaningful step, and end with await recorder.closeRecorded(context, { notes }), where notes say in your own words what you tested in that browser and what happened: what you opened, what you clicked or typed, what you expected, and whether it passed or failed (at most 600 characters). People watching the replay read them first. Add video: true when the page has a canvas, a map, a video player or iframes from another site (a replay cannot show them), or when someone asks for a video. Recordings are attached to the task when the run ends; ${REPLAY_TOOL_NAME} and ${VIDEO_TOOL_NAME} attach one earlier with a better title.`;
2569
- var BROWSER_RULE = "A headless browser is installed for interface checks. If the project has no Playwright of its own, load playwright-core from the folder in the TAMSENSE_PLAYWRIGHT environment variable, with PLAYWRIGHT_BROWSERS_PATH set to the folder in TAMSENSE_PLAYWRIGHT_BROWSERS.";
2570
- var FOLDER_RULE = "This run has no repositories. The current folder belongs to this task: work in it and keep your throwaway files here.";
2571
- var RESULT_HOME_RULE = "The result lives only on the TAMsense task: never save it in outside services such as Google Drive or a shared folder. Use connected services only to read. Sending anything to real people, such as an email or a message, is an action that needs approval like any other.";
2572
- var FOLDER_DO_RULES = [
2573
- `Save what you make on the task with the TAMsense tools: ${SAVE_DOCUMENT_TOOL_NAME} for text in Markdown, ${SAVE_TABLE_TOOL_NAME} for rows of data as CSV, ${ATTACH_FILE_TOOL_NAME} for files you made in this folder, ${ADD_LINK_TOOL_NAME} for pages the result relies on. Keep one title per document and save every change as a new version of it.`,
2574
- "Use only facts from the task, the notes, the earlier results and what you read yourself; never invent numbers, names or dates.",
2575
- RESULT_HOME_RULE
2576
- ];
2577
- var FOLDER_REWORK_RULE = "The task came back with notes. Read the earlier results in the results folder, fix what the notes ask and save new versions of the same documents.";
2578
- var FOLDER_REPORT_RULE = "End with a short report for the people on the task: what you made, what you decided yourself and what is still open.";
2579
- var FOLDER_CHECK_RULES = [
2580
- `The result to check is in the ${RESULTS_FOLDER} folder: the latest version of each document (.md) and table (.csv), the files and links.md. Read all of it.`,
2581
- `Do not change the result. You may create throwaway files outside the ${RESULTS_FOLDER} folder.`,
2582
- "Judge it against the task, the notes of earlier steps and the instructions of this step. Be specific: every problem names the document, the place in it and what to change.",
2583
- `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`
2584
- ];
2585
- var DEPENDENCIES_RULE = "The checkout may have no dependencies installed. To run the app or its tests, install the project's own dependencies from its lockfile with its package manager (pnpm install --frozen-lockfile, npm ci, yarn install --immutable and similar). Never add packages, change a lockfile or download browsers.";
2586
- function composeProjectName(job) {
2587
- return ["tamsense", ...stepFolder(job)].join("-");
2588
- }
2589
- function localAppRule(job, isCheck, canAsk) {
2590
- const project = composeProjectName(job);
2591
- const cannotRun = isCheck ? `call ${VERDICT_TOOL_NAME} with passed false and couldNotRun true, write the exact reason and what a person should do in the report, and do not hand off: the task waits on this step for a person.` : "say so in your report with the exact reason and what a person should do.";
2592
- return [
2593
- "When you need the running app, start a local copy from this task folder only.",
2594
- "Find how the project runs locally yourself: the README, compose files named local or dev, .env.example and local env files, package scripts.",
2595
- canAsk ? `If you still cannot tell how the team prepares it, for example how the local database gets its tables, ask with ${ASK_PERSON_TOOL_NAME} instead of guessing.` : null,
2596
- "Use only settings meant for a local run that are in this checkout. Never read or copy .env files from elsewhere on this computer, including the person's own checkout, and never use staging, shared dev or production addresses.",
2597
- "Before starting, check every address in the settings you use: databases, caches, queues and APIs must point at localhost or at containers you started, and keys of outside services stay empty.",
2598
- `Start containers as a separate compose project named ${project} on free host ports (an override file in a temp folder), never stop or change containers you did not start, and remove yours with their volumes at the end (docker compose -p ${project} down -v).`,
2599
- `If Docker is not running, there is no local setup, or the settings point outside this computer, do not start anything: ${cannotRun}`
2600
- ].filter(Boolean).join(" ");
2601
- }
2602
- var STAGE_GOALS = {
2603
- code: "Implement",
2604
- review: "Review the code for",
2605
- test: "Test"
2606
- };
2607
- var READ_ONLY_GOAL = "Work on";
2608
- function isReadOnlyCode(stage) {
2609
- return stage.kind === "code" && stage.role?.access === "read";
2610
- }
2611
- var SPEC_UNATTENDED_RULE = `You are preparing a TAMsense task on the user's own computer. Nobody answers you during this run: the owner answers your questions and reviews the spec on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them.`;
2612
- var READ_ONLY_RULE = "Read the code to research the task. Do not change, commit or push anything: nothing from this run is pushed, and the code step starts from a clean branch.";
2613
- var RESEARCH_RULE = "Start by loading the tamsense:task-interview skill with the Skill tool and follow it: it holds the question test, the corner sweep and the rules for rounds. Research before asking: read the code in the repositories here and the evidence of the linked action, if there is one. Finding facts is your job; the decisions about intent are the owner's.";
2614
- var BRIEF_RULE = "Write the brief as you go: findings (what the code does today that matters, each with owner/repo path:line), dataFacts (numbers and facts from the linked action, each with its source; empty when none), approach (2 to 4 sentences), codeMap (the files the change will touch or must reuse, each with its role), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).";
2615
- var QUESTION_SHAPE_RULE = 'Each question has a snake_case id, one plain sentence the owner can answer without reading code, a whyItMatters line, and 2 to 4 concrete options grounded in what you saw, each saying what happens if it is chosen. Put the option you recommend first and set recommended on it (in a multiSelect question, on every option you recommend). multiSelect is true only when the options are additive. Never add an "Other" option: the page adds a free-text field. An unanswered question takes your recommended option, so recommend the one you would defend.';
2616
- var QUESTIONS_REPLY_RULE = "Then reply in one or two plain sentences: what you understood from the code and why you ask these questions, or that no questions are needed. Do not repeat the questions: the page shows them.";
2617
- var SPEC_RULES = [
2618
- "Start by loading the tamsense:spec-writing skill with the Skill tool and follow it: it holds the shape of the spec, the rules for acceptance criteria, the size budget, the lint list and the builder check.",
2619
- "Start from the brief and the conversation below. Verify in the code what the spec depends on and research only what is missing; without a brief, research first as the tamsense task-interview skill describes.",
2620
- 'Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked "Recommended option applied, not answered" is an assumption with the reason "not answered, the recommended option applies". If an answer contradicts the code or another answer, check the code before following it and say so in your reply.',
2621
- "Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the scope or the behaviour becomes an open question; keep them to 3 at most.",
2622
- "Before you hand the spec in, run the builder check of the skill once. It is required, also for a small task: start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, and settle each point it reports as the skill says.",
2623
- `Hand in the FULL spec with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it and checks the files, and sends it back once if no fresh reader has checked it; if it returns feedback, the spec was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of lint feedback the spec is saved as it is.`,
2624
- "When the task already has a saved spec, the conversation holds the owner's feedback or answers to its open questions: apply them and hand in the full updated spec. An answered open question becomes a decision; a corrected or confirmed assumption becomes a decision and leaves the assumptions.",
2625
- "Then reply with a short plain summary: the approach, the risk level and the open questions, if any. Do not paste the spec: the page shows it."
2626
- ];
2627
- var SPEC_WORK_MISSING = "TAMsense sent a spec step without its round and brief.";
2628
- var BRIEF_UNATTENDED_RULE = `You are preparing a TAMsense task on the user's own computer. Nobody answers you during this run: the owner answers your questions and reviews the brief on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them.`;
2629
- var BRIEF_RESEARCH_RULE = "Research before asking: read what the task, the project knowledge, the files in this folder and the connected services give you. Finding facts is your job; the decisions about intent are the owner's.";
2630
- var RESEARCH_BRIEF_RULE = "Write the research brief as you go: findings (what the sources say that matters, each with where: the source), dataFacts (numbers and facts, each with its source; empty when none), approach (2 to 4 sentences), codeMap (always empty: this task has no repositories), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none).";
2631
- var RESULT_BRIEF_RULES = [
2632
- 'Write the brief of the result: goal, context (the summary), contains (what the result must contain), acceptance (criteria with ids AC-1, AC-2 and so on in order, each one observable property a checker confirms by reading the result), constraints, outOfScope, sources (where the facts come from: knowledge, linked items, files of the task, connected services that are read only), approver (who approves the result), decisions, assumptions and openQuestions. Set format to "brief". Never write a code map, files to touch or repositories.',
2633
- 'Start from the research brief and the conversation below. Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked "Recommended option applied, not answered" is an assumption with the reason "not answered, the recommended option applies".',
2634
- "Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the result becomes an open question; keep them to 3 at most. Never invent numbers, names or dates.",
2635
- `Hand in the FULL brief with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it; if it returns feedback, the brief was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of feedback the brief is saved as it is.`,
2636
- "When the task already has a saved brief, the conversation holds the owner's feedback or answers to its open questions: apply them and hand in the full updated brief. An answered open question becomes a decision.",
2637
- "Then reply with a short plain summary: what the result is and the open questions, if any. Do not paste the brief: the page shows it."
2638
- ];
2639
- var BRIEF_STYLE_RULE = "Write the research brief, the questions, the brief and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real names, numbers and sources; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.";
2640
- var SPEC_STYLE_RULE = "Write the brief, the questions, the spec and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real file paths, function and table names; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.";
2641
- function specText(spec) {
2642
- const text2 = specPromptText(spec);
2643
- return text2 ? `Approved spec:
2644
- ${text2}` : null;
2645
- }
2646
- function briefText(spec) {
2647
- const text2 = specPromptText(spec);
2648
- return text2 ? `Approved brief:
2649
- ${text2}` : null;
2650
- }
2651
- function contextText(context) {
2652
- return context.trim() ? `Project context from the team (follow it like the project's own rules):
2653
- ${context.trim()}` : null;
2654
- }
2655
- function safetyText(job) {
2656
- return job.safetyRules?.trim() || null;
2657
- }
2658
- function bulletList(lines) {
2659
- return lines.map((line) => `- ${line}`).join("\n");
2660
- }
2661
- function repositoryLines(job, workspace) {
2662
- const sourceOf2 = new Map(job.repositories.map((repository) => [repository.fullName, repository]));
2663
- return workspace.repositories.map((repository) => {
2664
- const source = sourceOf2.get(repository.fullName);
2665
- const pull = source?.pullRequestUrl ? `, pull request ${source.pullRequestUrl}` : "";
2666
- const base = repository.local ? repository.local.baseBranch : `origin/${source?.defaultBranch ?? "main"}`;
2667
- return `${repository.folder} (${repository.fullName}) at commit ${repository.baseSha.slice(0, 7)}, base branch ${base}${pull}`;
2668
- });
2669
- }
2670
- function codeRules(job) {
2671
- return isLocalJob(job) ? [...CODE_RULES.slice(0, -1), LOCAL_COMMIT_RULE] : CODE_RULES;
2672
- }
2673
- function stepLine(stage) {
2674
- return `This step of the board is "${stage.name}"${stage.helper ? `, and you work as the ${stage.helper}` : ""}.`;
2675
- }
2676
- function teamMessagesText(messages) {
2677
- return messages.length > 0 ? `Messages from the team before this run:
2678
- ${messages.map((message) => `${message.authorName}: ${message.text}`).join("\n")}` : null;
2679
- }
2680
- function peopleText(people) {
2681
- if (people.length === 0) {
2682
- return null;
2683
- }
2684
- return [
2685
- `People on this task you can ask with ${ASK_PERSON_TOOL_NAME} (id: name, relation, access, then their team roles with what each owns):`,
2686
- bulletList(people.map(personLine)),
2687
- 'Ask the assignee first. A question on a topic goes to the person whose team role fits it, and then why names that role and what it owns, for example "Designer, owns empty states". Set blocking only when you cannot go on without the answer: the run then pauses and the computer is freed for other tasks. Otherwise give the assumption you continue with.'
2688
- ].join("\n");
2689
- }
2690
- function canProposeKnowledge(job) {
2691
- return job.knowledge !== void 0 && (job.people ?? []).length > 0 && job.stage.kind !== "interview" && job.stage.kind !== "spec";
2692
- }
2693
- function folderText(root, results) {
2694
- const lines = results ?? [];
2695
- return lines.length > 0 ? `The task folder is ${root}. The results of the task so far, latest versions:
2696
- ${bulletList(lines)}` : `The task folder is ${root}. The task has no result yet.`;
2697
- }
2698
- function taskResultsText(results) {
2699
- const lines = results ?? [];
2700
- return lines.length > 0 ? `The task already has these documents and results, latest versions, in the ${RESULTS_FOLDER} folder next to the repositories. Read what concerns this step before you start:
2701
- ${bulletList(lines)}` : null;
2702
- }
2703
- function handOverText(boards) {
2704
- const names = (boards ?? []).map((board) => board.name);
2705
- if (names.length === 0) {
2706
- return null;
2707
- }
2708
- return `When part of the work belongs to another board, or the work goes on as a new task, such as the next wave, create a linked task with the ${HAND_OVER_TOOL_NAME} tool on: ${names.join(", ")}. Write a plain title and a note on what you found and what the new task should do; never put names, emails or other personal data of clients in the note. With wait true this task waits on its step until the linked task is done, then it goes on and you get its result note.`;
2709
- }
2710
- function handoffText(targets, isCheck) {
2711
- if (targets.length === 0) {
2712
- return null;
2713
- }
2714
- return [
2715
- `When you finish, hand the task on with the ${HANDOFF_TOOL_NAME} tool. Write a clear note for the next agent and the task owner: what was done, what to check and what is still open. The steps this task can go to from here:`,
2716
- bulletList(targets.map(handoffTargetLine)),
2717
- `If none of them fits, just finish without handing off.${isCheck ? ` Hand in your verdict with ${VERDICT_TOOL_NAME} as well.` : ""}`
2718
- ].join("\n");
2719
- }
2720
- function threadText(thread) {
2721
- if (thread.length === 0) {
2722
- return null;
2723
- }
2724
- const lines = thread.map((message) => `${message.role === "user" ? "Owner" : "You, earlier"}: ${message.text}`);
2725
- return `The conversation on the task so far (answers to the questions and the owner's feedback):
2726
- ${lines.join("\n\n")}`;
2727
- }
2728
- function questionRules(work2) {
2729
- const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`;
2730
- if (work2.round === 1) {
2731
- return [
2732
- RESEARCH_RULE,
2733
- BRIEF_RULE,
2734
- `Ask 0 to ${work2.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones that pass the three tests of the skill. Zero questions is a good result: the spec is then written right away. Ask only questions that do not depend on another question of this round; put a dependent decision into pendingDecisions.`,
2735
- QUESTION_SHAPE_RULE,
2736
- handIn,
2737
- QUESTIONS_REPLY_RULE
2738
- ];
2739
- }
2740
- const taken = work2.takenIds.length > 0 ? ` These ids are taken by round 1: ${work2.takenIds.join(", ")}.` : "";
2741
- return [
2742
- `This is the follow-up round ${work2.round} of 2, and there is no round after it. Follow the tamsense:task-interview skill (load it with the Skill tool if this conversation has not loaded it yet). Read the answers in the conversation below. Do not research again what your brief covers; check only what the answers changed.`,
2743
- `Ask at most ${work2.maxQuestions} more questions, and only when an answer is vague ("not sure", "depends", "maybe"), contradicts another answer or the code, or unlocked a decision from pendingDecisions that passes the tests of the tamsense task-interview skill. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,
2744
- QUESTION_SHAPE_RULE,
2745
- "Update the brief with what the answers changed and leave pendingDecisions empty.",
2746
- handIn,
2747
- QUESTIONS_REPLY_RULE
2748
- ];
2749
- }
2750
- function specGoal(job, work2) {
2751
- if (work2.round === null) {
2752
- return `Write the spec for TAMsense task #${job.taskId}: ${job.title}`;
2753
- }
2754
- return work2.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its spec: ${job.title}`;
2755
- }
2756
- function briefQuestionRules(work2) {
2757
- const handIn = `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the research brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`;
2758
- if (work2.round === 1) {
2759
- return [
2760
- BRIEF_RESEARCH_RULE,
2761
- RESEARCH_BRIEF_RULE,
2762
- `Ask 0 to ${work2.maxQuestions} questions, ideally 2 or 3, the most important first, and only the ones research cannot settle, whose answers change the result, and where a wrong default is costly. Zero questions is a good result: the brief is then written right away.`,
2763
- QUESTION_SHAPE_RULE,
2764
- handIn,
2765
- QUESTIONS_REPLY_RULE
2766
- ];
2767
- }
2768
- const taken = work2.takenIds.length > 0 ? ` These ids are taken by round 1: ${work2.takenIds.join(", ")}.` : "";
2769
- return [
2770
- `This is the follow-up round ${work2.round} of 2, and there is no round after it. Read the answers in the conversation below; do not research again what your research brief covers.`,
2771
- `Ask at most ${work2.maxQuestions} more questions, and only when an answer is vague, contradicts another answer or the facts, or unlocked a decision from pendingDecisions. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,
2772
- QUESTION_SHAPE_RULE,
2773
- "Update the research brief with what the answers changed and leave pendingDecisions empty.",
2774
- handIn,
2775
- QUESTIONS_REPLY_RULE
2776
- ];
2777
- }
2778
- function briefGoal(job, work2) {
2779
- if (work2.round === null) {
2780
- return `Write the brief for TAMsense task #${job.taskId}: ${job.title}`;
2781
- }
2782
- return work2.round > 1 ? `Follow-up questions for TAMsense task #${job.taskId}: ${job.title}` : `Prepare TAMsense task #${job.taskId} for its brief: ${job.title}`;
2783
- }
2784
- function buildBriefPrompt(job, work2, workspace, options) {
2785
- const prompt = [
2786
- briefGoal(job, work2),
2787
- job.description,
2788
- work2.linkedAction,
2789
- work2.brief ? `Research brief from the clarification phase (verify only what you rely on):
2790
- ${JSON.stringify(work2.brief)}` : null,
2791
- work2.round === null && job.spec ? `Current saved brief (hand in the full updated version):
2792
- ${JSON.stringify(job.spec)}` : null,
2793
- job.handoffNote && `Note from another step: ${job.handoffNote}`,
2794
- threadText(work2.thread),
2795
- teamMessagesText(job.messages)
2796
- ].filter(Boolean).join("\n\n");
2797
- const instructions = [
2798
- BRIEF_UNATTENDED_RULE,
2799
- MACHINE_RULE,
2800
- workplaceText(job.computer, workspace.root),
2801
- stepLine(job.stage),
2802
- job.stage.instructions,
2803
- job.sourcesText,
2804
- contextText(job.projectContext),
2805
- `${FOLDER_RULE} ${folderText(workspace.root, options.results)}`,
2806
- RESULT_HOME_RULE,
2807
- `How to work:
2808
- ${bulletList(work2.round === null ? RESULT_BRIEF_RULES : briefQuestionRules(work2))}`,
2809
- safetyText(job),
2810
- knowledgeText(job.knowledge, job.computer, false),
2811
- BRIEF_STYLE_RULE
2812
- ].filter(Boolean).join("\n\n");
2813
- return { prompt, instructions };
2814
- }
2815
- function buildSpecPrompt(job, work2, workspace, options) {
2816
- const prompt = [
2817
- specGoal(job, work2),
2818
- job.description,
2819
- work2.linkedAction,
2820
- work2.brief ? `Brief from the clarification phase (research already done, verify only what you rely on):
2821
- ${JSON.stringify(work2.brief)}` : null,
2822
- work2.round === null && job.spec ? `Current saved spec (hand in the full updated version):
2823
- ${JSON.stringify(job.spec)}` : null,
2824
- job.handoffNote && `Note from another step: ${job.handoffNote}`,
2825
- threadText(work2.thread),
2826
- teamMessagesText(job.messages)
2827
- ].filter(Boolean).join("\n\n");
2828
- const instructions = [
2829
- SPEC_UNATTENDED_RULE,
2830
- MACHINE_RULE,
2831
- workplaceText(job.computer, workspace.root),
2832
- stepLine(job.stage),
2833
- job.stage.instructions,
2834
- job.sourcesText,
2835
- contextText(job.projectContext),
2836
- `The repositories for this task are checked out in the current folder, one subfolder each:
2837
- ${bulletList(repositoryLines(job, workspace))}`,
2838
- isLocalJob(job) && LOCAL_FOLDERS_RULE,
2839
- taskResultsText(options.results),
2840
- READ_ONLY_RULE,
2841
- `How to work:
2842
- ${bulletList(work2.round === null ? SPEC_RULES : questionRules(work2))}`,
2843
- safetyText(job),
2844
- knowledgeText(job.knowledge, job.computer, false),
2845
- SPEC_STYLE_RULE
2846
- ].filter(Boolean).join("\n\n");
2847
- return { prompt, instructions };
2848
- }
2849
- var FOLDER_GOALS = {
2850
- code: "Work on",
2851
- review: "Check the result of",
2852
- test: "Check the result of"
2853
- };
2854
- function buildFolderPrompt(job, workspace, options) {
2855
- const kind = job.stage.kind === "review" || job.stage.kind === "test" ? job.stage.kind : "code";
2856
- const isCheck = kind !== "code";
2857
- const people = job.people ?? [];
2858
- const canAsk = people.length > 0;
2859
- const prompt = [
2860
- `${FOLDER_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,
2861
- job.description,
2862
- briefText(job.spec),
2863
- job.handoffNote && `Note from the previous step: ${job.handoffNote}`,
2864
- job.review && `Notes to address:
2865
- ${job.review}`,
2866
- job.scheduleNote,
2867
- teamMessagesText(job.messages)
2868
- ].filter(Boolean).join("\n\n");
2869
- const rules = isCheck ? FOLDER_CHECK_RULES : [...FOLDER_DO_RULES, ...job.review ? [FOLDER_REWORK_RULE] : [], FOLDER_REPORT_RULE];
2870
- const instructions = [
2871
- canAsk ? ASKING_RULE : UNATTENDED_RULE,
2872
- MACHINE_RULE,
2873
- workplaceText(job.computer, workspace.root),
2874
- stepLine(job.stage),
2875
- job.stage.instructions,
2876
- job.sourcesText,
2877
- contextText(job.projectContext),
2878
- `${FOLDER_RULE} ${folderText(workspace.root, options.results)}`,
2879
- `How to work:
2880
- ${bulletList(rules)}`,
2881
- safetyText(job),
2882
- knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),
2883
- APPROVAL_RULE,
2884
- peopleText(people),
2885
- isCheck ? null : handOverText(job.handOverBoards),
2886
- handoffText(job.allowedHandoffs, isCheck),
2887
- job.canSchedule && SCHEDULE_RULE,
2888
- options.browser && BROWSER_RULE
2889
- ].filter(Boolean).join("\n\n");
2890
- return { prompt, instructions };
2891
- }
2892
- function buildTaskPrompt(job, workspace, options = {}) {
2893
- const { kind } = job.stage;
2894
- if (kind === "interview" || kind === "spec") {
2895
- if (!job.specWork) {
2896
- throw new Error(SPEC_WORK_MISSING);
2897
- }
2898
- return isFolderJob(job) ? buildBriefPrompt(job, job.specWork, workspace, options) : buildSpecPrompt(job, job.specWork, workspace, options);
2899
- }
2900
- if (isFolderJob(job)) {
2901
- return buildFolderPrompt(job, workspace, options);
2902
- }
2903
- const isCheck = kind !== "code";
2904
- const readOnly = isReadOnlyCode(job.stage);
2905
- const people = job.people ?? [];
2906
- const canAsk = people.length > 0;
2907
- const prompt = [
2908
- `${readOnly ? READ_ONLY_GOAL : STAGE_GOALS[kind]} TAMsense task #${job.taskId}: ${job.title}`,
2909
- job.description,
2910
- specText(job.spec),
2911
- job.handoffNote && `Note from the previous step: ${job.handoffNote}`,
2912
- job.review && `Review comments to address:
2913
- ${job.review}`,
2914
- job.scheduleNote,
2915
- teamMessagesText(job.messages)
2916
- ].filter(Boolean).join("\n\n");
2917
- const evidence = [SCREENSHOT_RULE, ...options.recorder ? [RECORDING_RULE] : [], NAMING_RULE];
2918
- const localApp = localAppRule(job, isCheck, canAsk);
2919
- const rules = isCheck ? [...CHECK_RULES2, DEPENDENCIES_RULE, localApp, ...evidence] : readOnly ? [...READ_ONLY_CODE_RULES, DEPENDENCIES_RULE, localApp, ...evidence, READ_ONLY_REPORT_RULE] : [...codeRules(job), ...job.review ? [REWORK_RULE] : [], DEPENDENCIES_RULE, localApp, ...evidence, CODE_REPORT_RULE];
2920
- const instructions = [
2921
- canAsk ? ASKING_RULE : UNATTENDED_RULE,
2922
- MACHINE_RULE,
2923
- workplaceText(job.computer, workspace.root),
2924
- stepLine(job.stage),
2925
- job.stage.instructions,
2926
- job.sourcesText,
2927
- contextText(job.projectContext),
2928
- `The repositories for this task are checked out in the current folder, one subfolder each, ${isCheck ? "at the latest commit of branch" : "on branch"} ${job.branch}:
2929
- ${bulletList(repositoryLines(job, workspace))}`,
2930
- isLocalJob(job) && LOCAL_FOLDERS_RULE,
2931
- taskResultsText(options.results),
2932
- `How to work:
2933
- ${bulletList(rules)}`,
2934
- safetyText(job),
2935
- knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job)),
2936
- APPROVAL_RULE,
2937
- peopleText(people),
2938
- handoffText(job.allowedHandoffs, isCheck),
2939
- job.canSchedule && SCHEDULE_RULE,
2940
- options.browser && BROWSER_RULE
2941
- ].filter(Boolean).join("\n\n");
2942
- return { prompt, instructions };
2943
- }
2944
2135
 
2945
2136
  // src/specTools.ts
2946
- import { tool as tool6 } from "@anthropic-ai/claude-agent-sdk";
2947
- import { z as z6 } from "zod";
2948
2137
  var MAX_FIRST_ROUND = 4;
2949
2138
  var questionSchema = z6.object({
2950
2139
  id: z6.string().describe('Stable snake_case id, unique in the task, for example "empty_state"'),
@@ -3503,22 +2692,22 @@ var runClaude = claudeRunner(query3);
3503
2692
 
3504
2693
  // src/config.ts
3505
2694
  import { mkdir as mkdir4, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
3506
- import { dirname as dirname2, join as join6 } from "path";
2695
+ import { dirname as dirname2, join as join4 } from "path";
3507
2696
  var OWNER_ONLY_FILE = 384;
3508
2697
  var OWNER_ONLY_FOLDER = 448;
3509
2698
  var PROFILES_FOLDER = "profiles";
3510
2699
  function dataFolderFor(home, profile) {
3511
- const shared = join6(home, ".tamsense");
3512
- return profile === null ? shared : join6(shared, PROFILES_FOLDER, profile);
2700
+ const shared = join4(home, ".tamsense");
2701
+ return profile === null ? shared : join4(shared, PROFILES_FOLDER, profile);
3513
2702
  }
3514
2703
  function configPath(dataFolder) {
3515
- return join6(dataFolder, "runner.json");
2704
+ return join4(dataFolder, "runner.json");
3516
2705
  }
3517
- function isRecord2(value) {
2706
+ function isRecord(value) {
3518
2707
  return typeof value === "object" && value !== null && !Array.isArray(value);
3519
2708
  }
3520
2709
  function parseConfig(value) {
3521
- if (!isRecord2(value)) {
2710
+ if (!isRecord(value)) {
3522
2711
  return null;
3523
2712
  }
3524
2713
  const { server, token, deviceId, deviceName } = value;
@@ -3547,116 +2736,282 @@ async function removeConfig(path) {
3547
2736
  await rm4(path, { force: true });
3548
2737
  }
3549
2738
 
3550
- // src/login.ts
3551
- var MS_PER_SECOND = 1e3;
3552
- var EXPIRED_TEXT = "The code expired before it was confirmed. Run the same command again.";
3553
- var DENIED_TEXT = "The connection was cancelled in the browser. This computer was not connected.";
3554
- async function login(tools, request) {
3555
- const pairing = await tools.api.request("POST", PAIRINGS_PATH, request);
3556
- const pageUrl = new URL(pairing.verificationPath, tools.api.server).toString();
3557
- const expiresAt = Date.parse(pairing.expiresAt);
3558
- tools.print(`Your code: ${pairing.userCode}`);
3559
- tools.print(`Confirm it in the browser: ${pageUrl}`);
3560
- tools.print("Waiting for confirmation\u2026");
3561
- tools.openBrowser(pageUrl);
3562
- while (tools.now() < expiresAt) {
3563
- await tools.sleep(pairing.intervalSeconds * MS_PER_SECOND);
3564
- const reply5 = await tools.api.request("POST", PAIRING_TOKEN_PATH, { deviceCode: pairing.deviceCode });
3565
- if (reply5.status === "connected") {
3566
- return { token: reply5.token, device: reply5.device };
3567
- }
3568
- if (reply5.status === "denied") {
3569
- throw new Error(DENIED_TEXT);
3570
- }
3571
- if (reply5.status === "expired") {
3572
- break;
3573
- }
3574
- }
3575
- throw new Error(EXPIRED_TEXT);
3576
- }
3577
-
3578
- // src/recorderEnv.ts
3579
- import { existsSync as existsSync6 } from "fs";
3580
- import { join as join8 } from "path";
3581
- import { fileURLToPath } from "url";
3582
-
3583
- // src/recorder.ts
3584
- import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3585
- import Module, { createRequire } from "module";
3586
- import { basename as basename3, dirname as dirname3, join as join7 } from "path";
3587
- import process4 from "process";
3588
- var RECORDINGS_DIR_ENV = "TAMSENSE_RECORDINGS_DIR";
3589
- var SERVER_ENV = "TAMSENSE_SERVER";
3590
- var DEFAULT_TITLE_ENV = "TAMSENSE_RECORDING_TITLE";
3591
- var DEFAULT_PURPOSE_ENV = "TAMSENSE_RECORDING_PURPOSE";
3592
- var MARK_TAG = "tamsense.mark";
3593
- var REPLAY_SUFFIX = ".rrweb.ndjson";
3594
- var META_SUFFIX = ".json";
3595
- var MAX_TITLE_LENGTH = 60;
3596
- var MAX_PURPOSE_LENGTH = 160;
3597
- var MAX_NOTES_LENGTH = 600;
3598
- var DEFAULT_TRACKER_ORIGINS = ["https://my.tamsense.com"];
3599
- var READ_METHODS = ["GET", "HEAD", "OPTIONS"];
3600
- var BINDING = "__tamsenseRecord";
3601
- var STATE_KEY = /* @__PURE__ */ Symbol.for("tamsense.recorder");
3602
- var FLUSH_MS = 100;
3603
- var MAX_SESSION_BYTES = 300 * 1024 * 1024;
3604
- var GAP_CHECK_MS = 1500;
3605
- var RRWEB_INCREMENTAL = 3;
3606
- var RRWEB_META = 4;
3607
- var RRWEB_SOURCE_MOUSE_INTERACTION = 2;
3608
- var RRWEB_SOURCE_INPUT = 5;
3609
- function state() {
3610
- const holder = globalThis;
3611
- holder[STATE_KEY] ??= { contexts: /* @__PURE__ */ new WeakMap(), pages: /* @__PURE__ */ new WeakMap(), instrumented: /* @__PURE__ */ new WeakSet(), script: null, hooked: false };
3612
- return holder[STATE_KEY];
2739
+ // src/folders.ts
2740
+ import { existsSync as existsSync4, realpathSync, statSync } from "fs";
2741
+ import { readdir } from "fs/promises";
2742
+ import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve3 } from "path";
2743
+ import process3 from "process";
2744
+ var FIRST_REMOTE = "origin";
2745
+ var USUAL_BASES = ["main", "master"];
2746
+ var CHECKS_INTERVAL_MS = 5e3;
2747
+ var WEB_REMOTE = /^[a-z][\w+.-]*:\/\//i;
2748
+ var NETWORK_REMOTE = /^(?!file:)[a-z][\w+.-]*:\/\/|^[^/@]+@[^/:]+:/i;
2749
+ function problem(text2) {
2750
+ return { ok: false, problem: text2 };
3613
2751
  }
3614
- function recordingsDir() {
3615
- const dir = process4.env[RECORDINGS_DIR_ENV];
3616
- if (!dir) {
3617
- throw new Error(`${RECORDINGS_DIR_ENV} is not set: recordings work only inside a TAMsense run`);
3618
- }
3619
- return dir;
2752
+ async function gitText(args, cwd) {
2753
+ const result = await runGit(args, { cwd });
2754
+ return result.code === 0 && result.stdout ? result.stdout : null;
3620
2755
  }
3621
- function warn(text2) {
3622
- process4.stderr.write(`[tamsense recorder] ${text2}
3623
- `);
2756
+ function expandHome(path, home) {
2757
+ const trimmed = path.trim();
2758
+ return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("~\\") ? join5(home, trimmed.slice(1)) : trimmed;
3624
2759
  }
3625
- function isTrackerIngest(url, tamsenseOrigins) {
3626
- if (!/\/api\/events\/?$/.test(url.pathname)) {
3627
- return false;
2760
+ function remoteWithoutSecrets(url) {
2761
+ if (!WEB_REMOTE.test(url)) {
2762
+ return url;
3628
2763
  }
3629
- return url.searchParams.has("key") || tamsenseOrigins.includes(url.origin);
3630
- }
3631
- function isLocalHost(hostname2) {
3632
- const host = hostname2.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
3633
- return host === "localhost" || host.endsWith(".localhost") || host === "::1" || host === "0.0.0.0" || /^127(?:\.\d{1,3}){3}$/.test(host);
3634
- }
3635
- function isExternalWrite(method, url) {
3636
- return !READ_METHODS.includes(method.toUpperCase()) && !isLocalHost(url.hostname);
3637
- }
3638
- function trackerOrigins(env = process4.env) {
3639
- const origins = new Set(DEFAULT_TRACKER_ORIGINS);
3640
2764
  try {
3641
- if (env[SERVER_ENV]) {
3642
- origins.add(new URL(env[SERVER_ENV]).origin);
2765
+ const parsed = new URL(url);
2766
+ if (parsed.username || parsed.password) {
2767
+ parsed.username = "";
2768
+ parsed.password = "";
3643
2769
  }
2770
+ return parsed.toString().replace(/\/$/, "");
3644
2771
  } catch {
2772
+ return url;
3645
2773
  }
3646
- return [...origins];
3647
- }
3648
- function checkCaption(caption) {
3649
- const title = typeof caption?.title === "string" ? caption.title.trim() : "";
3650
- const purpose = typeof caption?.purpose === "string" ? caption.purpose.trim() : "";
3651
- if (!title || !purpose) {
3652
- throw new Error('Give it a title (what it shows, for example "Design option 1" or "Feature testing, attempt 2") and a purpose (one line on why it was made).');
3653
- }
3654
- if (title.length > MAX_TITLE_LENGTH || purpose.length > MAX_PURPOSE_LENGTH) {
3655
- throw new Error(`Keep the title within ${MAX_TITLE_LENGTH} characters and the purpose within ${MAX_PURPOSE_LENGTH}.`);
3656
- }
3657
- return { title, purpose };
3658
2774
  }
3659
- function checkNotes(notes) {
2775
+ function remoteRepository(remoteUrl) {
2776
+ const parts = remoteUrl && NETWORK_REMOTE.test(remoteUrl) ? remoteUrl.replace(/\/+$/, "").replace(/\.git$/, "").split(/[/:]/).filter(Boolean) : [];
2777
+ return parts.length >= 3 ? `${parts.at(-2)}/${parts.at(-1)}` : null;
2778
+ }
2779
+ function repositoryName(remoteUrl, folder) {
2780
+ return remoteRepository(remoteUrl) ?? basename2(folder);
2781
+ }
2782
+ async function remoteOf(folder) {
2783
+ const remotes = (await gitText(["remote"], folder))?.split("\n").map((line) => line.trim()).filter(Boolean) ?? [];
2784
+ return remotes.includes(FIRST_REMOTE) ? FIRST_REMOTE : remotes[0] ?? null;
2785
+ }
2786
+ async function hasRef(folder, ref) {
2787
+ return (await runGit(["rev-parse", "--verify", "--quiet", `${ref}^{commit}`], { cwd: folder })).code === 0;
2788
+ }
2789
+ async function baseBranchOf(folder, remote, current) {
2790
+ const remoteHead = remote ? await gitText(["symbolic-ref", "--quiet", "--short", `refs/remotes/${remote}/HEAD`], folder) : null;
2791
+ if (remoteHead?.startsWith(`${remote}/`)) {
2792
+ return remoteHead.slice(remote.length + 1);
2793
+ }
2794
+ for (const name of USUAL_BASES) {
2795
+ if (await hasRef(folder, `refs/heads/${name}`) || remote && await hasRef(folder, `refs/remotes/${remote}/${name}`)) {
2796
+ return name;
2797
+ }
2798
+ }
2799
+ return current;
2800
+ }
2801
+ function samePath(first, second) {
2802
+ const [a, b] = [realpathSync.native(first), realpathSync.native(second)];
2803
+ return process3.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
2804
+ }
2805
+ function isInside(path, folder) {
2806
+ const between = relative(resolve3(folder), resolve3(path));
2807
+ return between === "" || !between.startsWith("..") && !isAbsolute2(between);
2808
+ }
2809
+ async function inspectFolder(path, home) {
2810
+ const folder = expandHome(path, home);
2811
+ if (!isAbsolute2(folder)) {
2812
+ return problem("Enter the full path of the folder, for example /Users/anna/code/shop or C:\\code\\shop.");
2813
+ }
2814
+ if (!existsSync4(folder) || !statSync(folder).isDirectory()) {
2815
+ return problem(`There is no folder ${folder} on this computer.`);
2816
+ }
2817
+ if (isInside(folder, join5(home, ".tamsense"))) {
2818
+ return problem("This is a TAMsense work folder. Choose your own copy of the repository.");
2819
+ }
2820
+ const top = await gitText(["rev-parse", "--show-toplevel"], folder);
2821
+ if (!top) {
2822
+ return problem(`${folder} is not a git repository. Choose the folder of a cloned repository, or run git init there.`);
2823
+ }
2824
+ if (!samePath(top, folder)) {
2825
+ return problem(`${folder} is inside the repository ${resolve3(top)}. Add the top folder of the repository instead.`);
2826
+ }
2827
+ if (!await hasRef(folder, "HEAD")) {
2828
+ return problem("The repository has no commits yet. Make the first commit, then check the folder again.");
2829
+ }
2830
+ const remote = await remoteOf(folder);
2831
+ const remoteUrl = remote ? await gitText(["remote", "get-url", remote], folder) : null;
2832
+ const currentBranch = await gitText(["symbolic-ref", "--quiet", "--short", "HEAD"], folder);
2833
+ const defaultBranch = await baseBranchOf(folder, remote, currentBranch);
2834
+ if (!defaultBranch) {
2835
+ return problem("Could not tell the main branch of the repository. Check out the main branch in this folder, then check it again.");
2836
+ }
2837
+ const safeUrl = remoteUrl && remoteWithoutSecrets(remoteUrl);
2838
+ return { ok: true, repository: repositoryName(safeUrl, folder), currentBranch, defaultBranch, remoteUrl: safeUrl };
2839
+ }
2840
+ function reportLine(path, report) {
2841
+ return report.ok ? `Checked the folder ${path}: ${report.repository}, main branch ${report.defaultBranch}.` : `The folder ${path} cannot be used: ${report.problem}`;
2842
+ }
2843
+ async function checkFolders(tools, stop) {
2844
+ let complained = false;
2845
+ while (!stop.aborted) {
2846
+ try {
2847
+ const { checks } = await tools.api.request("POST", FOLDER_CHECKS_PATH, void 0, { safeToRepeat: true });
2848
+ for (const check of checks) {
2849
+ const report = await tools.inspect(check.path).catch((error) => problem(error instanceof Error ? error.message : String(error)));
2850
+ await tools.api.request("POST", folderCheckPath(check.id), report);
2851
+ tools.print(reportLine(check.path, report));
2852
+ }
2853
+ complained = false;
2854
+ } catch (error) {
2855
+ if (error instanceof ApiError && error.status === 404) {
2856
+ return;
2857
+ }
2858
+ if (!complained && !stop.aborted && !alreadyReported(error)) {
2859
+ tools.print(`Could not check the folders for TAMsense: ${error instanceof Error ? error.message : String(error)}`);
2860
+ complained = true;
2861
+ }
2862
+ }
2863
+ await tools.sleep(CHECKS_INTERVAL_MS, stop);
2864
+ }
2865
+ }
2866
+ var SKIPPED_FOLDER = "node_modules";
2867
+ var MORE_CODE_TEXT = "To add more code, start the program from your code folder or the folder above it, or add a path in Settings, under your computer on the Account tab.";
2868
+ async function childRepositories(start) {
2869
+ const entries = await readdir(start, { withFileTypes: true }).catch(() => []);
2870
+ return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== SKIPPED_FOLDER).map((entry) => join5(start, entry.name)).filter((folder) => existsSync4(join5(folder, ".git"))).sort();
2871
+ }
2872
+ async function findCodeFolders(start, home) {
2873
+ const folder = resolve3(start);
2874
+ if (!existsSync4(folder)) {
2875
+ return [];
2876
+ }
2877
+ const top = await gitText(["rev-parse", "--show-toplevel"], folder);
2878
+ const candidates = top && samePath(top, folder) ? [folder] : await childRepositories(folder);
2879
+ const found = [];
2880
+ for (const candidate of candidates) {
2881
+ const report = await inspectFolder(candidate, home);
2882
+ if (report.ok) {
2883
+ const { currentBranch, defaultBranch, remoteUrl } = report;
2884
+ found.push({ path: candidate, name: basename2(candidate), repository: remoteRepository(remoteUrl), currentBranch, defaultBranch, remoteUrl });
2885
+ }
2886
+ }
2887
+ return found;
2888
+ }
2889
+ function foundCodeLines(start, found) {
2890
+ const lines = found.length > 0 ? found.map((folder) => `Found code: ${folder.name} (${folder.path})`) : [`No code found in ${resolve3(start)}.`];
2891
+ return [...lines, MORE_CODE_TEXT];
2892
+ }
2893
+ async function reportFoundFolders(tools, start) {
2894
+ try {
2895
+ const found = await findCodeFolders(start, tools.home);
2896
+ foundCodeLines(start, found).forEach((line) => tools.print(line));
2897
+ await tools.api.request("PUT", FOUND_FOLDERS_PATH, { folders: found });
2898
+ } catch (error) {
2899
+ if (!(error instanceof ApiError && error.status === 404)) {
2900
+ tools.print(`Could not tell TAMsense about the code on this computer: ${error instanceof Error ? error.message : String(error)}`);
2901
+ }
2902
+ }
2903
+ }
2904
+
2905
+ // src/login.ts
2906
+ var MS_PER_SECOND = 1e3;
2907
+ var EXPIRED_TEXT = "The code expired before it was confirmed. Run the same command again.";
2908
+ var DENIED_TEXT = "The connection was cancelled in the browser. This computer was not connected.";
2909
+ async function login(tools, request) {
2910
+ const pairing = await tools.api.request("POST", PAIRINGS_PATH, request);
2911
+ const pageUrl = new URL(pairing.verificationPath, tools.api.server).toString();
2912
+ const expiresAt = Date.parse(pairing.expiresAt);
2913
+ tools.print(`Your code: ${pairing.userCode}`);
2914
+ tools.print(`Confirm it in the browser: ${pageUrl}`);
2915
+ tools.print("Waiting for confirmation\u2026");
2916
+ tools.openBrowser(pageUrl);
2917
+ while (tools.now() < expiresAt) {
2918
+ await tools.sleep(pairing.intervalSeconds * MS_PER_SECOND);
2919
+ const reply5 = await tools.api.request("POST", PAIRING_TOKEN_PATH, { deviceCode: pairing.deviceCode });
2920
+ if (reply5.status === "connected") {
2921
+ return { token: reply5.token, device: reply5.device };
2922
+ }
2923
+ if (reply5.status === "denied") {
2924
+ throw new Error(DENIED_TEXT);
2925
+ }
2926
+ if (reply5.status === "expired") {
2927
+ break;
2928
+ }
2929
+ }
2930
+ throw new Error(EXPIRED_TEXT);
2931
+ }
2932
+
2933
+ // src/recorderEnv.ts
2934
+ import { existsSync as existsSync5 } from "fs";
2935
+ import { join as join7 } from "path";
2936
+ import { fileURLToPath } from "url";
2937
+
2938
+ // src/recorder.ts
2939
+ import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2940
+ import Module, { createRequire } from "module";
2941
+ import { basename as basename3, dirname as dirname3, join as join6 } from "path";
2942
+ import process4 from "process";
2943
+ var RECORDINGS_DIR_ENV = "TAMSENSE_RECORDINGS_DIR";
2944
+ var SERVER_ENV = "TAMSENSE_SERVER";
2945
+ var DEFAULT_TITLE_ENV = "TAMSENSE_RECORDING_TITLE";
2946
+ var DEFAULT_PURPOSE_ENV = "TAMSENSE_RECORDING_PURPOSE";
2947
+ var MARK_TAG = "tamsense.mark";
2948
+ var REPLAY_SUFFIX = ".rrweb.ndjson";
2949
+ var META_SUFFIX = ".json";
2950
+ var MAX_TITLE_LENGTH = 60;
2951
+ var MAX_PURPOSE_LENGTH = 160;
2952
+ var MAX_NOTES_LENGTH = 600;
2953
+ var DEFAULT_TRACKER_ORIGINS = ["https://my.tamsense.com"];
2954
+ var READ_METHODS = ["GET", "HEAD", "OPTIONS"];
2955
+ var BINDING = "__tamsenseRecord";
2956
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("tamsense.recorder");
2957
+ var FLUSH_MS = 100;
2958
+ var MAX_SESSION_BYTES = 300 * 1024 * 1024;
2959
+ var GAP_CHECK_MS = 1500;
2960
+ var RRWEB_INCREMENTAL = 3;
2961
+ var RRWEB_META = 4;
2962
+ var RRWEB_SOURCE_MOUSE_INTERACTION = 2;
2963
+ var RRWEB_SOURCE_INPUT = 5;
2964
+ function state() {
2965
+ const holder = globalThis;
2966
+ holder[STATE_KEY] ??= { contexts: /* @__PURE__ */ new WeakMap(), pages: /* @__PURE__ */ new WeakMap(), instrumented: /* @__PURE__ */ new WeakSet(), script: null, hooked: false };
2967
+ return holder[STATE_KEY];
2968
+ }
2969
+ function recordingsDir() {
2970
+ const dir = process4.env[RECORDINGS_DIR_ENV];
2971
+ if (!dir) {
2972
+ throw new Error(`${RECORDINGS_DIR_ENV} is not set: recordings work only inside a TAMsense run`);
2973
+ }
2974
+ return dir;
2975
+ }
2976
+ function warn(text2) {
2977
+ process4.stderr.write(`[tamsense recorder] ${text2}
2978
+ `);
2979
+ }
2980
+ function isTrackerIngest(url, tamsenseOrigins) {
2981
+ if (!/\/api\/events\/?$/.test(url.pathname)) {
2982
+ return false;
2983
+ }
2984
+ return url.searchParams.has("key") || tamsenseOrigins.includes(url.origin);
2985
+ }
2986
+ function isLocalHost(hostname2) {
2987
+ const host = hostname2.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
2988
+ return host === "localhost" || host.endsWith(".localhost") || host === "::1" || host === "0.0.0.0" || /^127(?:\.\d{1,3}){3}$/.test(host);
2989
+ }
2990
+ function isExternalWrite(method, url) {
2991
+ return !READ_METHODS.includes(method.toUpperCase()) && !isLocalHost(url.hostname);
2992
+ }
2993
+ function trackerOrigins(env = process4.env) {
2994
+ const origins = new Set(DEFAULT_TRACKER_ORIGINS);
2995
+ try {
2996
+ if (env[SERVER_ENV]) {
2997
+ origins.add(new URL(env[SERVER_ENV]).origin);
2998
+ }
2999
+ } catch {
3000
+ }
3001
+ return [...origins];
3002
+ }
3003
+ function checkCaption(caption) {
3004
+ const title = typeof caption?.title === "string" ? caption.title.trim() : "";
3005
+ const purpose = typeof caption?.purpose === "string" ? caption.purpose.trim() : "";
3006
+ if (!title || !purpose) {
3007
+ throw new Error('Give it a title (what it shows, for example "Design option 1" or "Feature testing, attempt 2") and a purpose (one line on why it was made).');
3008
+ }
3009
+ if (title.length > MAX_TITLE_LENGTH || purpose.length > MAX_PURPOSE_LENGTH) {
3010
+ throw new Error(`Keep the title within ${MAX_TITLE_LENGTH} characters and the purpose within ${MAX_PURPOSE_LENGTH}.`);
3011
+ }
3012
+ return { title, purpose };
3013
+ }
3014
+ function checkNotes(notes) {
3660
3015
  const text2 = typeof notes === "string" ? notes.trim() : "";
3661
3016
  if (text2.length > MAX_NOTES_LENGTH) {
3662
3017
  throw new Error(`Keep the notes within ${MAX_NOTES_LENGTH} characters: what you opened, clicked and expected, and whether it passed.`);
@@ -3675,7 +3030,7 @@ function automaticCaption(env = process4.env, script2 = process4.argv[1]) {
3675
3030
  }
3676
3031
  function rrwebSource() {
3677
3032
  const entry = createRequire(import.meta.url).resolve("rrweb");
3678
- return readFileSync(join7(dirname3(entry), "rrweb.umd.min.cjs"), "utf8");
3033
+ return readFileSync(join6(dirname3(entry), "rrweb.umd.min.cjs"), "utf8");
3679
3034
  }
3680
3035
  function pageScript(rrweb) {
3681
3036
  return `(() => {
@@ -3738,8 +3093,8 @@ var Session = class {
3738
3093
  bytes = 0;
3739
3094
  constructor(dir, recording, url) {
3740
3095
  const id = `${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
3741
- this.file = join7(dir, `${id}${REPLAY_SUFFIX}`);
3742
- this.metaFile = join7(dir, `${id}${META_SUFFIX}`);
3096
+ this.file = join6(dir, `${id}${REPLAY_SUFFIX}`);
3097
+ this.metaFile = join6(dir, `${id}${META_SUFFIX}`);
3743
3098
  this.meta = { version: 1, id, ...recording.caption, startedAt: Date.now(), endedAt: null, url, marks: [], video: null, gaps: [], blockedTrackerRequests: 0, blockedExternalWrites: 0, truncated: false, automatic: recording.automatic, notes: "" };
3744
3099
  writeFileSync(this.file, "");
3745
3100
  this.save();
@@ -3912,10 +3267,10 @@ if (process4.env[RECORDINGS_DIR_ENV]) {
3912
3267
  var RECORDER_ENV = "TAMSENSE_RECORDER";
3913
3268
  function bundledRecorder() {
3914
3269
  const path = fileURLToPath(new URL("./recorder.cjs", import.meta.url));
3915
- return existsSync6(path) ? path : null;
3270
+ return existsSync5(path) ? path : null;
3916
3271
  }
3917
3272
  function recordingsFolder(dataFolder, runId) {
3918
- return join8(dataFolder, "recordings", `run-${runId}`);
3273
+ return join7(dataFolder, "recordings", `run-${runId}`);
3919
3274
  }
3920
3275
  function nodeOptionsWithRecorder(recorder, nodeOptions) {
3921
3276
  return [nodeOptions?.trim(), `--require "${recorder.replaceAll("\\", "/")}"`].filter(Boolean).join(" ");
@@ -3944,7 +3299,7 @@ function describeDevice(identity) {
3944
3299
  // src/sense.ts
3945
3300
  import { mkdtemp, rm as rm5, stat as stat2 } from "fs/promises";
3946
3301
  import { tmpdir } from "os";
3947
- import { dirname as dirname4, join as join9 } from "path";
3302
+ import { dirname as dirname4, join as join8 } from "path";
3948
3303
  import process5 from "process";
3949
3304
  import { createSdkMcpServer as createSdkMcpServer3, query as query4 } from "@anthropic-ai/claude-agent-sdk";
3950
3305
 
@@ -4062,7 +3417,7 @@ async function authorSession(job, dataFolder) {
4062
3417
  return found?.isDirectory() ? { folder, sessionId: job.author.sessionId } : null;
4063
3418
  }
4064
3419
  async function runSenseJob(job, runSense, tools, stop) {
4065
- const scratch = await mkdtemp(join9(tmpdir(), "tamsense-sense-"));
3420
+ const scratch = await mkdtemp(join8(tmpdir(), "tamsense-sense-"));
4066
3421
  const author = await authorSession(job, tools.dataFolder);
4067
3422
  const run2 = new AbortController();
4068
3423
  const stopRun = () => run2.abort(STOPPED_HERE);
@@ -4226,9 +3581,9 @@ function feedQueue(send) {
4226
3581
  }
4227
3582
 
4228
3583
  // src/library.ts
4229
- import { existsSync as existsSync7 } from "fs";
3584
+ import { existsSync as existsSync6 } from "fs";
4230
3585
  import { cp, mkdir as mkdir5, rm as rm6 } from "fs/promises";
4231
- import { join as join10 } from "path";
3586
+ import { join as join9 } from "path";
4232
3587
  import { fileURLToPath as fileURLToPath2 } from "url";
4233
3588
  var LIBRARY_PATH = fileURLToPath2(new URL("../library", import.meta.url));
4234
3589
  var SKILL_NAME = /^[a-z0-9-]+$/;
@@ -4241,22 +3596,1002 @@ function allowedSkills(stage) {
4241
3596
  return skills && [.../* @__PURE__ */ new Set([...skills, ...STAGE_SKILLS[stage.kind] ?? []])].filter((name) => SKILL_NAME.test(name));
4242
3597
  }
4243
3598
  function roleLibraryFolder(dataFolder, runId) {
4244
- return join10(dataFolder, "libraries", `run-${runId}`);
3599
+ return join9(dataFolder, "libraries", `run-${runId}`);
4245
3600
  }
4246
3601
  async function prepareLibrary(folder, skills, source = LIBRARY_PATH) {
4247
3602
  if (skills === null) {
4248
3603
  return source;
4249
3604
  }
4250
3605
  await rm6(folder, { recursive: true, force: true });
4251
- await mkdir5(join10(folder, "skills"), { recursive: true });
4252
- await cp(join10(source, ".claude-plugin"), join10(folder, ".claude-plugin"), { recursive: true });
4253
- await cp(join10(source, "agents"), join10(folder, "agents"), { recursive: true });
4254
- for (const skill of skills.filter((name) => SKILL_NAME.test(name) && existsSync7(join10(source, "skills", name)))) {
4255
- await cp(join10(source, "skills", skill), join10(folder, "skills", skill), { recursive: true });
3606
+ await mkdir5(join9(folder, "skills"), { recursive: true });
3607
+ await cp(join9(source, ".claude-plugin"), join9(folder, ".claude-plugin"), { recursive: true });
3608
+ await cp(join9(source, "agents"), join9(folder, "agents"), { recursive: true });
3609
+ for (const skill of skills.filter((name) => SKILL_NAME.test(name) && existsSync6(join9(source, "skills", name)))) {
3610
+ await cp(join9(source, "skills", skill), join9(folder, "skills", skill), { recursive: true });
4256
3611
  }
4257
3612
  return folder;
4258
3613
  }
4259
3614
 
3615
+ // src/localWorkspace.ts
3616
+ import { existsSync as existsSync7 } from "fs";
3617
+ import { join as join10, resolve as resolve4 } from "path";
3618
+ var NETWORK_TIMEOUT_MS = 12e4;
3619
+ var sourceLock = keyedLock();
3620
+ function isLocalJob(job) {
3621
+ return job.workspace === "local";
3622
+ }
3623
+ function localBranchLine(branch) {
3624
+ const local = branch.local;
3625
+ if (!local) {
3626
+ return `Pushed ${branch.branch} in ${branch.repository}.`;
3627
+ }
3628
+ const where = `Branch ${branch.branch} has ${local.commits} commit(s) in ${local.folder}`;
3629
+ if (local.pushProblem) {
3630
+ return `${where}. It was not pushed: ${local.pushProblem}`;
3631
+ }
3632
+ return local.remoteUrl ? `${where}, pushed to ${local.remoteUrl}.` : `${where}.`;
3633
+ }
3634
+ function readsOnly(job) {
3635
+ return isReadOnlyCheck(job) || job.stage.kind === "interview" || job.stage.kind === "spec";
3636
+ }
3637
+ async function isAncestor(folder, older, newer) {
3638
+ return (await runGit(["merge-base", "--is-ancestor", older, newer], { cwd: folder })).code === 0;
3639
+ }
3640
+ async function fetchRemote(source, remote, print2) {
3641
+ const fetched = await runGit(["fetch", "--prune", remote], { cwd: source, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS });
3642
+ if (fetched.code !== 0) {
3643
+ print2(`Could not fetch from ${remote} in ${source}, going on with what the folder has: ${fetched.stderr || `exit code ${fetched.code}`}`);
3644
+ }
3645
+ }
3646
+ async function baseBranchRef(source, branch, remote) {
3647
+ const own = await hasRef(source, `refs/heads/${branch}`) ? branch : null;
3648
+ const fetched = remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null;
3649
+ if (own && fetched) {
3650
+ return await isAncestor(source, own, fetched) ? fetched : own;
3651
+ }
3652
+ const found = own ?? fetched;
3653
+ if (!found) {
3654
+ throw new Error(`The main branch ${branch} is not in ${source}. Check the folder again in Settings, under your computer on the Account tab.`);
3655
+ }
3656
+ return found;
3657
+ }
3658
+ async function taskBranchRef(source, branch, remote, base) {
3659
+ if (await hasRef(source, `refs/heads/${branch}`) && Number(await git(["rev-list", "--count", `${base}..${branch}`], { cwd: source })) > 0) {
3660
+ return branch;
3661
+ }
3662
+ return remote && await hasRef(source, `refs/remotes/${remote}/${branch}`) ? `${remote}/${branch}` : null;
3663
+ }
3664
+ async function startRef(source, job, local) {
3665
+ const base = await baseBranchRef(source, local.baseBranch, local.remote);
3666
+ const task = await taskBranchRef(source, job.branch, local.remote, base);
3667
+ if (task || !isReadOnlyCheck(job) || job.severalBranches) {
3668
+ return task ?? base;
3669
+ }
3670
+ const where = local.remote ? `${source} or on its remote ${local.remote}` : source;
3671
+ throw new Error(`Branch ${job.branch} is not in ${where}. It was made on another computer: push it to a remote this folder can fetch, or run this check on that computer.`);
3672
+ }
3673
+ async function releaseBranch2(source, branch, keep, dataFolder) {
3674
+ const listed = await runGit(["worktree", "list", "--porcelain"], { cwd: source });
3675
+ const work2 = tamsenseFolder(dataFolder, "work");
3676
+ for (const holder of branchHolders(listed.stdout, branch).filter((path) => resolve4(path) !== resolve4(keep))) {
3677
+ if (!isInside(holder, work2)) {
3678
+ throw new Error(`Branch ${branch} is checked out in ${resolve4(holder)}. Switch that folder to another branch, then start the step again.`);
3679
+ }
3680
+ await removeWorktree(source, holder);
3681
+ }
3682
+ }
3683
+ async function keptBase2(path, job) {
3684
+ if (!job.keepWorkspace || !existsSync7(join10(path, ".git"))) {
3685
+ return null;
3686
+ }
3687
+ const branch = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: path });
3688
+ if (branch.code !== 0 || branch.stdout !== (readsOnly(job) ? "HEAD" : job.branch)) {
3689
+ return null;
3690
+ }
3691
+ return git(["rev-parse", "HEAD"], { cwd: path });
3692
+ }
3693
+ function sourceOf(repository) {
3694
+ if (!repository.folder) {
3695
+ throw new Error(`TAMsense did not say which folder holds ${repository.fullName} on this computer.`);
3696
+ }
3697
+ return repository.folder;
3698
+ }
3699
+ async function prepareRepository(job, repository, path, tools) {
3700
+ const source = sourceOf(repository);
3701
+ if ((await runGit(["rev-parse", "--show-toplevel"], { cwd: source }).catch(() => null))?.code !== 0) {
3702
+ throw new Error(`${source} is no longer a git repository on this computer. Check the folder again in Settings, under your computer on the Account tab.`);
3703
+ }
3704
+ const local = { source, remote: await remoteOf(source), baseBranch: repository.defaultBranch };
3705
+ if (local.remote) {
3706
+ await fetchRemote(source, local.remote, tools.print);
3707
+ }
3708
+ const kept = await keptBase2(path, job);
3709
+ if (kept) {
3710
+ return { baseSha: kept, local };
3711
+ }
3712
+ await runGit(["worktree", "prune"], { cwd: source });
3713
+ if (existsSync7(path)) {
3714
+ await removeWorktree(source, path);
3715
+ }
3716
+ const start = await startRef(source, job, local);
3717
+ if (readsOnly(job)) {
3718
+ await git(["worktree", "add", "--detach", path, start], { cwd: source });
3719
+ } else {
3720
+ await releaseBranch2(source, job.branch, path, tools.dataFolder);
3721
+ await git(["worktree", "add", "-B", job.branch, path, start], { cwd: source });
3722
+ }
3723
+ return { baseSha: await git(["rev-parse", "HEAD"], { cwd: path }), local };
3724
+ }
3725
+ async function prepareLocalWorkspace(job, tools) {
3726
+ const root = taskFolder(tools.dataFolder, job);
3727
+ const folders = folderNames(job.repositories);
3728
+ const repositories = [];
3729
+ for (const [index, repository] of job.repositories.entries()) {
3730
+ const folder = folders[index] ?? repository.fullName;
3731
+ const path = join10(root, folder);
3732
+ const prepared = await sourceLock.run(resolve4(sourceOf(repository)), () => prepareRepository(job, repository, path, tools));
3733
+ repositories.push({ fullName: repository.fullName, folder, path, baseSha: prepared.baseSha, local: prepared.local });
3734
+ }
3735
+ return { root, repositories };
3736
+ }
3737
+ async function pushBranch(path, local, branch) {
3738
+ if (!local.remote) {
3739
+ return { remoteUrl: null, pushProblem: null };
3740
+ }
3741
+ if (branch === local.baseBranch) {
3742
+ return { remoteUrl: null, pushProblem: `TAMsense never pushes the main branch ${branch}.` };
3743
+ }
3744
+ const pushed = await runGit(["push", local.remote, `HEAD:refs/heads/${branch}`], { cwd: path, env: personalGitEnv(), timeoutMs: NETWORK_TIMEOUT_MS });
3745
+ if (pushed.code !== 0) {
3746
+ return { remoteUrl: null, pushProblem: pushed.stderr || `exit code ${pushed.code}` };
3747
+ }
3748
+ return { remoteUrl: await git(["remote", "get-url", local.remote], { cwd: path }), pushProblem: null };
3749
+ }
3750
+ async function commitAndPush(repository, local, job, identity) {
3751
+ const options = { cwd: repository.path, env: identity };
3752
+ await git(["add", "--all"], options);
3753
+ if ((await runGit(["diff", "--cached", "--quiet"], options)).code === 1) {
3754
+ await git(["commit", "--message", `Task #${job.taskId}: ${job.title}`], options);
3755
+ }
3756
+ if (Number(await git(["rev-list", "--count", `${repository.baseSha}..HEAD`], options)) === 0) {
3757
+ return null;
3758
+ }
3759
+ const base = await baseBranchRef(repository.path, local.baseBranch, local.remote);
3760
+ const commits = Number(await git(["rev-list", "--count", `${base}..HEAD`], options));
3761
+ const sent3 = await pushBranch(repository.path, local, job.branch);
3762
+ return { sha: await git(["rev-parse", "HEAD"], options), commits, ...sent3 };
3763
+ }
3764
+ async function publishLocalWorkspace(workspace, job, identity) {
3765
+ const pushed = [];
3766
+ for (const repository of workspace.repositories) {
3767
+ const local = repository.local;
3768
+ if (!local) {
3769
+ continue;
3770
+ }
3771
+ const branch = await sourceLock.run(resolve4(local.source), async () => {
3772
+ const published = await commitAndPush(repository, local, job, identity);
3773
+ await runGit(["checkout", "--quiet", "--detach"], { cwd: repository.path });
3774
+ return published;
3775
+ });
3776
+ if (branch) {
3777
+ pushed.push({
3778
+ repository: repository.fullName,
3779
+ branch: job.branch,
3780
+ sha: branch.sha,
3781
+ local: { folder: local.source, baseBranch: local.baseBranch, commits: branch.commits, remoteUrl: branch.remoteUrl && remoteWithoutSecrets(branch.remoteUrl), pushProblem: branch.pushProblem }
3782
+ });
3783
+ }
3784
+ }
3785
+ return pushed;
3786
+ }
3787
+
3788
+ // src/promptEn.ts
3789
+ var MESSAGES_RULE = `You are working on a TAMsense task on the user's own computer. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them: they take priority over the original task where the two disagree.`;
3790
+ var RESULT_HOME_RULE = "The result lives only on the TAMsense task: never save it in outside services such as Google Drive or a shared folder. Use connected services only to read. Sending anything to real people, such as an email or a message, is an action that needs approval like any other.";
3791
+ var EN = {
3792
+ unattendedRule: `${MESSAGES_RULE} Nobody answers questions during the run, so never stop to wait for a reply: decide yourself, keep working, and explain your decisions in your report.`,
3793
+ askingRule: `${MESSAGES_RULE} Decide yourself when you reasonably can, keep working, and explain your decisions in your report. When you are stuck, or the decision belongs to a person (intent, design, access, how the team runs something locally), ask one of the people on the task with the ${ASK_PERSON_TOOL_NAME} tool instead of guessing, and say what you tried. Never ask what the code or the project knowledge already answers. Nobody watches your messages, so ask only with the tool.`,
3794
+ // Действия, о которых Claude Code спросил бы человека, решают правила проекта или люди задачи на сайте
3795
+ approvalRule: "Some actions need a person's approval. When Claude Code would ask for one, TAMsense checks the project safety rules: a Never action is refused at once, an Allowed one goes ahead, and anything else goes to a person on the task while your tool call waits up to 15 minutes. Before such an action, write one short line: why you need it and what you will do instead if it is denied. Never get around a refused action with another command that has the same effect.",
3796
+ machineRule: "This is the person's own computer, and their other programs may be running on it. Stop only the processes you started yourself, by their process id, and stop them before you finish. Never kill processes by name or pattern (taskkill /IM, pkill, killall, Stop-Process -Name) and never touch programs you did not start.",
3797
+ // Язык ответов людям и записей на задаче; английский язык ничего не добавляет к прежним инструкциям
3798
+ languageRule: null,
3799
+ codeRules: [
3800
+ "Make the changes the task asks for, and keep them focused on it.",
3801
+ "When there is an approved spec, follow it: make the changes in its order with the names and data shapes it gives, build nothing from its out of scope list, and add the tests and run the commands of its test plan.",
3802
+ "Check your work with the project's own tools: run the tests, type checks and linters that exist, and fix what you broke.",
3803
+ "Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch and opens the pull request."
3804
+ ],
3805
+ // Репозитории из папок человека: ветку задачи отправляет TAMsense, если у папки есть удалённый, а сливает человек
3806
+ localCommitRule: "Commit your work in every repository you changed, on the current branch. Do not push, do not switch or create branches, and do not rewrite history: TAMsense pushes the branch when the folder has a remote, and the person merges it.",
3807
+ localFoldersRule: "Each repository here is a git worktree of the person's own folder on this computer. Work only here: never open, change or check out branches in the person's original folder.",
3808
+ reworkRule: "This is a rework. The branch already has the earlier work and maybe the reviewer's own commits: treat the current branch as the source of truth, keep the reviewer's changes unless a comment asks otherwise, and address every comment.",
3809
+ codeReportRule: "End with a short report for the reviewer: what you changed, how you checked it, and anything left open.",
3810
+ // Роль только для чтения на шаге кода: работа это исследование и отчёт, ничего не коммитится и не отправляется
3811
+ readOnlyCodeRules: [
3812
+ "Your role on this step only reads. Do the work the task asks for by researching and running what helps, then report. Do not change, commit or push product code: nothing from this run is pushed. You may create throwaway files outside the repositories.",
3813
+ "Check what you report with the project's own tools where they exist, and say how you checked it."
3814
+ ],
3815
+ readOnlyReportRule: "End with a short report: what you found, how you checked it, and what is left open.",
3816
+ checkRules: [
3817
+ "The work to check is on the current branch. See it with git diff against the base branch named for each repository below, and read the code around it.",
3818
+ "Do not change, commit or push product code. You may run anything and create throwaway files outside the repositories.",
3819
+ "Judge against the task, the spec and its acceptance criteria, checking each criterion by its id (AC-1 and so on) when the spec has ids. Be specific: every problem gets a file, a line and what to do about it.",
3820
+ `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`
3821
+ ],
3822
+ screenshotRule: `If you render screens (screenshots of the interface), attach the ones that show the result with the ${SCREENSHOT_TOOL_NAME} tool. People see them on the task without opening the code.`,
3823
+ namingRule: 'Name everything you attach (screenshots, replays, videos) by what it shows and why it was made: a short title such as "Design option 1", "Feature testing" or "Feature testing, attempt 2", and a one-line purpose such as "Comparing two layouts for the empty orders state". Number design variants, name test runs by their goal and attempt, and never use generic names like "screenshot1".',
3824
+ recordingRule: `Every browser you open with Playwright from Node is recorded for the task as a replay people can watch, requests from the tested site to the TAMsense tracker are blocked, so your runs never reach analytics, and requests other than GET, HEAD and OPTIONS to addresses outside this computer are aborted. In every browser script, load the recorder from the TAMSENSE_RECORDER environment variable (require(process.env.TAMSENSE_RECORDER), or createRequire(import.meta.url)(process.env.TAMSENSE_RECORDER) in an .mjs script), open each context with await recorder.newRecordedContext(browser, { title, purpose, context: { viewport } }), call await recorder.mark(page, 'what just happened') at every meaningful step, and end with await recorder.closeRecorded(context, { notes }), where notes say in your own words what you tested in that browser and what happened: what you opened, what you clicked or typed, what you expected, and whether it passed or failed (at most 600 characters). People watching the replay read them first. Add video: true when the page has a canvas, a map, a video player or iframes from another site (a replay cannot show them), or when someone asks for a video. Recordings are attached to the task when the run ends; ${REPLAY_TOOL_NAME} and ${VIDEO_TOOL_NAME} attach one earlier with a better title.`,
3825
+ browserRule: "A headless browser is installed for interface checks. If the project has no Playwright of its own, load playwright-core from the folder in the TAMSENSE_PLAYWRIGHT environment variable, with PLAYWRIGHT_BROWSERS_PATH set to the folder in TAMSENSE_PLAYWRIGHT_BROWSERS.",
3826
+ // Агент без репозиториев: репозиториев нет, работа идёт в папке задачи, а результат кладётся на задачу инструментами TAMsense
3827
+ folderRule: "This run has no repositories. The current folder belongs to this task: work in it and keep your throwaway files here.",
3828
+ resultHomeRule: RESULT_HOME_RULE,
3829
+ folderDoRules: [
3830
+ `Save what you make on the task with the TAMsense tools: ${SAVE_DOCUMENT_TOOL_NAME} for text in Markdown, ${SAVE_TABLE_TOOL_NAME} for rows of data as CSV, ${ATTACH_FILE_TOOL_NAME} for files you made in this folder, ${ADD_LINK_TOOL_NAME} for pages the result relies on. Keep one title per document and save every change as a new version of it.`,
3831
+ "Use only facts from the task, the notes, the earlier results and what you read yourself; never invent numbers, names or dates.",
3832
+ RESULT_HOME_RULE
3833
+ ],
3834
+ folderReworkRule: "The task came back with notes. Read the earlier results in the results folder, fix what the notes ask and save new versions of the same documents.",
3835
+ folderReportRule: "End with a short report for the people on the task: what you made, what you decided yourself and what is still open.",
3836
+ folderCheckRules: [
3837
+ `The result to check is in the ${RESULTS_FOLDER} folder: the latest version of each document (.md) and table (.csv), the files and links.md. Read all of it.`,
3838
+ `Do not change the result. You may create throwaway files outside the ${RESULTS_FOLDER} folder.`,
3839
+ "Judge it against the task, the notes of earlier steps and the instructions of this step. Be specific: every problem names the document, the place in it and what to change.",
3840
+ `Finish by calling the ${VERDICT_TOOL_NAME} tool exactly once: passed is true only if nothing must be fixed; report says what you checked and, if it failed, exactly what to fix.`
3841
+ ],
3842
+ dependenciesRule: "The checkout may have no dependencies installed. To run the app or its tests, install the project's own dependencies from its lockfile with its package manager (pnpm install --frozen-lockfile, npm ci, yarn install --immutable and similar). Never add packages, change a lockfile or download browsers.",
3843
+ // Приложение поднимается только локально из папки задачи; не поднялось из-за компьютера: проверка ждёт человека, а не проходит и не уходит назад
3844
+ localApp: {
3845
+ start: "When you need the running app, start a local copy from this task folder only.",
3846
+ find: "Find how the project runs locally yourself: the README, compose files named local or dev, .env.example and local env files, package scripts.",
3847
+ ask: `If you still cannot tell how the team prepares it, for example how the local database gets its tables, ask with ${ASK_PERSON_TOOL_NAME} instead of guessing.`,
3848
+ localSettings: "Use only settings meant for a local run that are in this checkout. Never read or copy .env files from elsewhere on this computer, including the person's own checkout, and never use staging, shared dev or production addresses.",
3849
+ addresses: "Before starting, check every address in the settings you use: databases, caches, queues and APIs must point at localhost or at containers you started, and keys of outside services stay empty.",
3850
+ containers: (project) => `Start containers as a separate compose project named ${project} on free host ports (an override file in a temp folder), never stop or change containers you did not start, and remove yours with their volumes at the end (docker compose -p ${project} down -v).`,
3851
+ cannotStart: (cannotRun) => `If Docker is not running, there is no local setup, or the settings point outside this computer, do not start anything: ${cannotRun}`,
3852
+ cannotRunCheck: `call ${VERDICT_TOOL_NAME} with passed false and couldNotRun true, write the exact reason and what a person should do in the report, and do not hand off: the task waits on this step for a person.`,
3853
+ cannotRunReport: "say so in your report with the exact reason and what a person should do."
3854
+ },
3855
+ stageGoals: {
3856
+ code: "Implement",
3857
+ review: "Review the code for",
3858
+ test: "Test"
3859
+ },
3860
+ readOnlyGoal: "Work on",
3861
+ folderGoals: {
3862
+ code: "Work on",
3863
+ review: "Check the result of",
3864
+ test: "Check the result of"
3865
+ },
3866
+ taskHeading: (goal, taskId, title) => `${goal} TAMsense task #${taskId}: ${title}`,
3867
+ specUnattendedRule: `You are preparing a TAMsense task on the user's own computer. Nobody answers you during this run: the owner answers your questions and reviews the spec on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them.`,
3868
+ readOnlyRule: "Read the code to research the task. Do not change, commit or push anything: nothing from this run is pushed, and the code step starts from a clean branch.",
3869
+ questionShapeRule: 'Each question has a snake_case id, one plain sentence the owner can answer without reading code, a whyItMatters line, and 2 to 4 concrete options grounded in what you saw, each saying what happens if it is chosen. Put the option you recommend first and set recommended on it (in a multiSelect question, on every option you recommend). multiSelect is true only when the options are additive. Never add an "Other" option: the page adds a free-text field. An unanswered question takes your recommended option, so recommend the one you would defend.',
3870
+ questionsReplyRule: "Then reply in one or two plain sentences: what you understood from the code and why you ask these questions, or that no questions are needed. Do not repeat the questions: the page shows them.",
3871
+ specRules: [
3872
+ "Start by loading the tamsense:spec-writing skill with the Skill tool and follow it: it holds the shape of the spec, the rules for acceptance criteria, the size budget, the lint list and the builder check.",
3873
+ "Start from the brief and the conversation below. Verify in the code what the spec depends on and research only what is missing; without a brief, research first as the tamsense task-interview skill describes.",
3874
+ 'Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked "Recommended option applied, not answered" is an assumption with the reason "not answered, the recommended option applies". If an answer contradicts the code or another answer, check the code before following it and say so in your reply.',
3875
+ "Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the scope or the behaviour becomes an open question; keep them to 3 at most.",
3876
+ "Before you hand the spec in, run the builder check of the skill once. It is required, also for a small task: start one fresh general-purpose subagent with the Agent tool, give it only the spec as text inside the builder check prompt of the skill, and settle each point it reports as the skill says.",
3877
+ `Hand in the FULL spec with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it and checks the files, and sends it back once if no fresh reader has checked it; if it returns feedback, the spec was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of lint feedback the spec is saved as it is.`,
3878
+ "When the task already has a saved spec, the conversation holds the owner's feedback or answers to its open questions: apply them and hand in the full updated spec. An answered open question becomes a decision; a corrected or confirmed assumption becomes a decision and leaves the assumptions.",
3879
+ "Then reply with a short plain summary: the approach, the risk level and the open questions, if any. Do not paste the spec: the page shows it."
3880
+ ],
3881
+ // Шаг Clarify у агента без репозиториев: вопросы и бриф результата вместо ТЗ разработки; кода и навыков разработки здесь нет
3882
+ briefUnattendedRule: `You are preparing a TAMsense task on the user's own computer. Nobody answers you during this run: the owner answers your questions and reviews the brief on the task page after it. So never stop to wait for a reply and never ask in your messages: hand in what this stage asks for with the TAMsense tool, then finish. People on the task may send you messages during this run, each starting with "Message from <name>:". Follow them.`,
3883
+ resultBriefRules: [
3884
+ 'Write the brief of the result: goal, context (the summary), contains (what the result must contain), acceptance (criteria with ids AC-1, AC-2 and so on in order, each one observable property a checker confirms by reading the result), constraints, outOfScope, sources (where the facts come from: knowledge, linked items, files of the task, connected services that are read only), approver (who approves the result), decisions, assumptions and openQuestions. Set format to "brief". Never write a code map, files to touch or repositories.',
3885
+ 'Start from the research brief and the conversation below. Apply the answers: an answer the owner gave is a decision with by: owner; an answer marked "Recommended option applied, not answered" is an assumption with the reason "not answered, the recommended option applies".',
3886
+ "Where the task, the answers and the project context are silent and a safe default exists, choose it and record it as an assumption with its reason. A point with no safe default that changes the result becomes an open question; keep them to 3 at most. Never invent numbers, names or dates.",
3887
+ `Hand in the FULL brief with ${SUBMIT_SPEC_TOOL_NAME}. TAMsense lints it; if it returns feedback, the brief was not saved: fix it and call ${SUBMIT_SPEC_TOOL_NAME} again. After two rounds of feedback the brief is saved as it is.`,
3888
+ "When the task already has a saved brief, the conversation holds the owner's feedback or answers to its open questions: apply them and hand in the full updated brief. An answered open question becomes a decision.",
3889
+ "Then reply with a short plain summary: what the result is and the open questions, if any. Do not paste the brief: the page shows it."
3890
+ ],
3891
+ briefStyleRule: "Write the research brief, the questions, the brief and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real names, numbers and sources; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.",
3892
+ specStyleRule: "Write the brief, the questions, the spec and your reply in the language the task is written in. Replies are plain text, no Markdown. Use real file paths, function and table names; if you could not verify something, say so. No em dashes and no en dashes; use commas, colons or a plain hyphen.",
3893
+ approvedSpec: (text2) => `Approved spec:
3894
+ ${text2}`,
3895
+ // Утверждённый бриф агента без репозиториев: по его критериям AC-1 и так далее судят проверки
3896
+ approvedBrief: (text2) => `Approved brief:
3897
+ ${text2}`,
3898
+ // Контекст проекта пишет команда в настройках доски: Claude следует ему, как правилам проекта
3899
+ projectContext: (context) => `Project context from the team (follow it like the project's own rules):
3900
+ ${context}`,
3901
+ repositoryLine: (line) => `${line.folder} (${line.fullName}) at commit ${line.commit}, base branch ${line.base}${line.pullRequestUrl ? `, pull request ${line.pullRequestUrl}` : ""}`,
3902
+ repositories: (list) => `The repositories for this task are checked out in the current folder, one subfolder each:
3903
+ ${list}`,
3904
+ repositoriesOnBranch: (isCheck, branch, list) => `The repositories for this task are checked out in the current folder, one subfolder each, ${isCheck ? "at the latest commit of branch" : "on branch"} ${branch}:
3905
+ ${list}`,
3906
+ stepLine: (name, helper) => `This step of the board is "${name}"${helper ? `, and you work as the ${helper}` : ""}.`,
3907
+ howToWork: (list) => `How to work:
3908
+ ${list}`,
3909
+ // Сообщения людей, которые ждали этого запуска, идут вместе с задачей
3910
+ teamMessages: (lines) => `Messages from the team before this run:
3911
+ ${lines}`,
3912
+ noteFromPreviousStep: (note) => `Note from the previous step: ${note}`,
3913
+ noteFromAnotherStep: (note) => `Note from another step: ${note}`,
3914
+ reviewComments: (review) => `Review comments to address:
3915
+ ${review}`,
3916
+ notesToAddress: (review) => `Notes to address:
3917
+ ${review}`,
3918
+ specBrief: (brief) => `Brief from the clarification phase (research already done, verify only what you rely on):
3919
+ ${brief}`,
3920
+ currentSpec: (spec) => `Current saved spec (hand in the full updated version):
3921
+ ${spec}`,
3922
+ researchBrief: (brief) => `Research brief from the clarification phase (verify only what you rely on):
3923
+ ${brief}`,
3924
+ currentBrief: (brief) => `Current saved brief (hand in the full updated version):
3925
+ ${brief}`,
3926
+ // Кого из людей задачи агент может спросить и как выбрать: исполнителя первым, вопрос по теме тому, чья роль подходит
3927
+ peopleIntro: `People on this task you can ask with ${ASK_PERSON_TOOL_NAME} (id: name, relation, access, then their team roles with what each owns):`,
3928
+ peopleRule: 'Ask the assignee first. A question on a topic goes to the person whose team role fits it, and then why names that role and what it owns, for example "Designer, owns empty states". Set blocking only when you cannot go on without the answer: the run then pauses and the computer is freed for other tasks. Otherwise give the assumption you continue with.',
3929
+ // Что задача уже сделала: файлы папки results; у агента без репозиториев вместо списка репозиториев
3930
+ folderResults: (root, list) => `The task folder is ${root}. The results of the task so far, latest versions:
3931
+ ${list}`,
3932
+ folderEmpty: (root) => `The task folder is ${root}. The task has no result yet.`,
3933
+ // Документы задачи рядом с репозиториями: их оставили другие агенты и люди, они читаются вместе с задачей
3934
+ taskResults: (list) => `The task already has these documents and results, latest versions, in the ${RESULTS_FOLDER} folder next to the repositories. Read what concerns this step before you start:
3935
+ ${list}`,
3936
+ // Доски проекта, и своя тоже, где шаг может завести связанную задачу; агент сам решает, нужна ли она
3937
+ handOver: (boards) => `When part of the work belongs to another board, or the work goes on as a new task, such as the next wave, create a linked task with the ${HAND_OVER_TOOL_NAME} tool on: ${boards}. Write a plain title and a note on what you found and what the new task should do; never put names, emails or other personal data of clients in the note. With wait true this task waits on its step until the linked task is done, then it goes on and you get its result note.`,
3938
+ // Куда агент может передать задачу по правилам доски; на шаге проверки вердикт всё равно нужен
3939
+ handoffIntro: `When you finish, hand the task on with the ${HANDOFF_TOOL_NAME} tool. Write a clear note for the next agent and the task owner: what was done, what to check and what is still open. The steps this task can go to from here:`,
3940
+ handoffNone: "If none of them fits, just finish without handing off.",
3941
+ handoffVerdict: `Hand in your verdict with ${VERDICT_TOOL_NAME} as well.`,
3942
+ threadOwner: "Owner",
3943
+ threadYou: "You, earlier",
3944
+ thread: (lines) => `The conversation on the task so far (answers to the questions and the owner's feedback):
3945
+ ${lines}`,
3946
+ specGoals: {
3947
+ write: (taskId, title) => `Write the spec for TAMsense task #${taskId}: ${title}`,
3948
+ prepare: (taskId, title) => `Prepare TAMsense task #${taskId} for its spec: ${title}`
3949
+ },
3950
+ briefGoals: {
3951
+ write: (taskId, title) => `Write the brief for TAMsense task #${taskId}: ${title}`,
3952
+ prepare: (taskId, title) => `Prepare TAMsense task #${taskId} for its brief: ${title}`
3953
+ },
3954
+ followUpGoal: (taskId, title) => `Follow-up questions for TAMsense task #${taskId}: ${title}`,
3955
+ questionsTaken: (ids) => ` These ids are taken by round 1: ${ids.join(", ")}.`,
3956
+ specQuestions: {
3957
+ research: [
3958
+ "Start by loading the tamsense:task-interview skill with the Skill tool and follow it: it holds the question test, the corner sweep and the rules for rounds. Research before asking: read the code in the repositories here and the evidence of the linked action, if there is one. Finding facts is your job; the decisions about intent are the owner's.",
3959
+ "Write the brief as you go: findings (what the code does today that matters, each with owner/repo path:line), dataFacts (numbers and facts from the linked action, each with its source; empty when none), approach (2 to 4 sentences), codeMap (the files the change will touch or must reuse, each with its role), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none)."
3960
+ ],
3961
+ handIn: `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`,
3962
+ firstRound: (max) => `Ask 0 to ${max} questions, ideally 2 or 3, the most important first, and only the ones that pass the three tests of the skill. Zero questions is a good result: the spec is then written right away. Ask only questions that do not depend on another question of this round; put a dependent decision into pendingDecisions.`,
3963
+ followUp: (round) => `This is the follow-up round ${round} of 2, and there is no round after it. Follow the tamsense:task-interview skill (load it with the Skill tool if this conversation has not loaded it yet). Read the answers in the conversation below. Do not research again what your brief covers; check only what the answers changed.`,
3964
+ followUpAsk: (max, taken) => `Ask at most ${max} more questions, and only when an answer is vague ("not sure", "depends", "maybe"), contradicts another answer or the code, or unlocked a decision from pendingDecisions that passes the tests of the tamsense task-interview skill. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,
3965
+ update: "Update the brief with what the answers changed and leave pendingDecisions empty."
3966
+ },
3967
+ briefQuestions: {
3968
+ research: [
3969
+ "Research before asking: read what the task, the project knowledge, the files in this folder and the connected services give you. Finding facts is your job; the decisions about intent are the owner's.",
3970
+ "Write the research brief as you go: findings (what the sources say that matters, each with where: the source), dataFacts (numbers and facts, each with its source; empty when none), approach (2 to 4 sentences), codeMap (always empty: this task has no repositories), assumptions (each corner you settled with a default, with the reason) and pendingDecisions (decisions that depend on an answer to this round; empty when none)."
3971
+ ],
3972
+ handIn: `Call ${SUBMIT_QUESTIONS_TOOL_NAME} once with the research brief and the questions, or an empty list when none are needed. If it returns problems, fix them and call it again.`,
3973
+ firstRound: (max) => `Ask 0 to ${max} questions, ideally 2 or 3, the most important first, and only the ones research cannot settle, whose answers change the result, and where a wrong default is costly. Zero questions is a good result: the brief is then written right away.`,
3974
+ followUp: (round) => `This is the follow-up round ${round} of 2, and there is no round after it. Read the answers in the conversation below; do not research again what your research brief covers.`,
3975
+ followUpAsk: (max, taken) => `Ask at most ${max} more questions, and only when an answer is vague, contradicts another answer or the facts, or unlocked a decision from pendingDecisions. Otherwise hand in no questions: whatever is still open becomes an assumption.${taken}`,
3976
+ update: "Update the research brief with what the answers changed and leave pendingDecisions empty."
3977
+ }
3978
+ };
3979
+
3980
+ // src/promptRu.ts
3981
+ var MESSAGES_RULE2 = "\u0422\u044B \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u043D\u0430\u0434 \u0437\u0430\u0434\u0430\u0447\u0435\u0439 TAMsense \u043D\u0430 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. \u041B\u044E\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u043C\u043E\u0433\u0443\u0442 \u043F\u0438\u0441\u0430\u0442\u044C \u0442\u0435\u0431\u0435 \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430, \u043A\u0430\u0436\u0434\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0441 \xABMessage from <name>:\xBB. \u0421\u043B\u0435\u0434\u0443\u0439 \u0438\u043C: \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u0440\u0430\u0441\u0445\u043E\u0434\u044F\u0442\u0441\u044F \u0441 \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439, \u043E\u043D\u0438 \u0432\u0430\u0436\u043D\u0435\u0435.";
3982
+ var RESULT_HOME_RULE2 = "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0445\u0440\u0430\u043D\u0438\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0435 TAMsense: \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0439 \u0435\u0433\u043E \u0432\u043E \u0432\u043D\u0435\u0448\u043D\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445 \u0432\u0440\u043E\u0434\u0435 Google Drive \u0438\u043B\u0438 \u043E\u0431\u0449\u0435\u0439 \u043F\u0430\u043F\u043A\u0438. \u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F. \u041E\u0442\u043F\u0440\u0430\u0432\u043A\u0430 \u0447\u0435\u0433\u043E-\u043B\u0438\u0431\u043E \u0440\u0435\u0430\u043B\u044C\u043D\u044B\u043C \u043B\u044E\u0434\u044F\u043C, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u043F\u0438\u0441\u044C\u043C\u0430 \u0438\u043B\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F, \u044D\u0442\u043E \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043A\u043E\u0442\u043E\u0440\u043E\u043C\u0443 \u043D\u0443\u0436\u043D\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435, \u043A\u0430\u043A \u0438 \u043B\u044E\u0431\u043E\u043C\u0443 \u0434\u0440\u0443\u0433\u043E\u043C\u0443.";
3983
+ var VERDICT_RULE = `\u0417\u0430\u043A\u043E\u043D\u0447\u0438, \u0432\u044B\u0437\u0432\u0430\u0432 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442 ${VERDICT_TOOL_NAME} \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u0438\u043D \u0440\u0430\u0437: passed \u0440\u0430\u0432\u043D\u043E true, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0443\u0436\u043D\u043E \u0438\u0441\u043F\u0440\u0430\u0432\u043B\u044F\u0442\u044C; \u0432 report \u043D\u0430\u043F\u0438\u0448\u0438, \u0447\u0442\u043E \u0442\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B, \u0438, \u0435\u0441\u043B\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430, \u0447\u0442\u043E \u0438\u043C\u0435\u043D\u043D\u043E \u0438\u0441\u043F\u0440\u0430\u0432\u0438\u0442\u044C.`;
3984
+ var RU = {
3985
+ unattendedRule: `${MESSAGES_RULE2} \u0412\u043E \u0432\u0440\u0435\u043C\u044F \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0439\u0441\u044F \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0438 \u043E\u0442\u0432\u0435\u0442\u0430: \u0440\u0435\u0448\u0430\u0439 \u0441\u0430\u043C, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u0438 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u0441\u0432\u043E\u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u0432 \u043E\u0442\u0447\u0451\u0442\u0435.`,
3986
+ askingRule: `${MESSAGES_RULE2} \u0420\u0435\u0448\u0430\u0439 \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u044D\u0442\u043E \u0440\u0430\u0437\u0443\u043C\u043D\u043E, \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443 \u0438 \u043E\u0431\u044A\u044F\u0441\u043D\u0438 \u0441\u0432\u043E\u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u0432 \u043E\u0442\u0447\u0451\u0442\u0435. \u0415\u0441\u043B\u0438 \u0442\u044B \u0437\u0430\u0441\u0442\u0440\u044F\u043B \u0438\u043B\u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0437\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u043E\u043C (\u0437\u0430\u043C\u044B\u0441\u0435\u043B, \u0434\u0438\u0437\u0430\u0439\u043D, \u0434\u043E\u0441\u0442\u0443\u043F\u044B, \u043A\u0430\u043A \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0447\u0442\u043E-\u0442\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E), \u043D\u0435 \u0433\u0430\u0434\u0430\u0439, \u0430 \u0441\u043F\u0440\u043E\u0441\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0438\u0437 \u043B\u044E\u0434\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C ${ASK_PERSON_TOOL_NAME} \u0438 \u0441\u043A\u0430\u0436\u0438, \u0447\u0442\u043E \u0443\u0436\u0435 \u043F\u0440\u043E\u0431\u043E\u0432\u0430\u043B. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0442\u043E, \u043D\u0430 \u0447\u0442\u043E \u0443\u0436\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u044E\u0442 \u043A\u043E\u0434 \u0438\u043B\u0438 \u0437\u043D\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430. \u0422\u0432\u043E\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u0447\u0438\u0442\u0430\u0435\u0442, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C.`,
3987
+ approvalRule: "\u041D\u0435\u043A\u043E\u0442\u043E\u0440\u044B\u043C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F\u043C \u043D\u0443\u0436\u043D\u043E \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430. \u041A\u043E\u0433\u0434\u0430 Claude Code \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u043B \u0431\u044B \u0435\u0433\u043E, TAMsense \u0441\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F \u0441 \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C\u0438 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0438\u0437 Never \u0441\u0440\u0430\u0437\u0443 \u043E\u0442\u043A\u043B\u043E\u043D\u044F\u0435\u0442\u0441\u044F, \u0438\u0437 Allowed \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442\u0441\u044F, \u0430 \u0432\u0441\u0451 \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u043E\u0435 \u0443\u0445\u043E\u0434\u0438\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0437\u0430\u0434\u0430\u0447\u0438, \u0438 \u0432\u044B\u0437\u043E\u0432 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 \u0436\u0434\u0451\u0442 \u0434\u043E 15 \u043C\u0438\u043D\u0443\u0442. \u041F\u0435\u0440\u0435\u0434 \u0442\u0430\u043A\u0438\u043C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435\u043C \u043D\u0430\u043F\u0438\u0448\u0438 \u043E\u0434\u043D\u0443 \u043A\u043E\u0440\u043E\u0442\u043A\u0443\u044E \u0441\u0442\u0440\u043E\u043A\u0443: \u0437\u0430\u0447\u0435\u043C \u043E\u043D\u043E \u043D\u0443\u0436\u043D\u043E \u0438 \u0447\u0442\u043E \u0442\u044B \u0441\u0434\u0435\u043B\u0430\u0435\u0448\u044C \u0432\u043C\u0435\u0441\u0442\u043E \u043D\u0435\u0433\u043E, \u0435\u0441\u043B\u0438 \u043E\u0442\u043A\u0430\u0436\u0443\u0442. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0431\u0445\u043E\u0434\u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0451\u043D\u043D\u043E\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0434\u0440\u0443\u0433\u043E\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \u0441 \u0442\u0435\u043C \u0436\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C.",
3988
+ machineRule: "\u042D\u0442\u043E \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u0438 \u043D\u0430 \u043D\u0451\u043C \u043C\u043E\u0433\u0443\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0434\u0440\u0443\u0433\u0438\u0435 \u0435\u0433\u043E \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B. \u041E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B \u0441\u0430\u043C, \u043F\u043E \u0438\u0445 id \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430, \u0438 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u0438\u0445 \u0434\u043E \u0442\u043E\u0433\u043E, \u043A\u0430\u043A \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0448\u044C. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0439 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u044B \u043F\u043E \u0438\u043C\u0435\u043D\u0438 \u0438\u043B\u0438 \u0448\u0430\u0431\u043B\u043E\u043D\u0443 (taskkill /IM, pkill, killall, Stop-Process -Name) \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0439 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B \u043D\u0435 \u0442\u044B.",
3989
+ languageRule: "\u041E\u0442\u0432\u0435\u0447\u0430\u0439 \u043B\u044E\u0434\u044F\u043C, \u043F\u0438\u0448\u0438 \u043E\u0442\u0447\u0451\u0442\u044B, \u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043F\u043E-\u0440\u0443\u0441\u0441\u043A\u0438, \u0435\u0441\u043B\u0438 \u0437\u0430\u0434\u0430\u0447\u0430 \u0438\u043B\u0438 \u0440\u043E\u043B\u044C \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u044E\u0442 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u044F\u0437\u044B\u043A\u0430 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, \u043F\u0438\u0441\u044C\u043C\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0430\u043C \u043D\u0430 \u0430\u043D\u0433\u043B\u0438\u0439\u0441\u043A\u043E\u043C \u043F\u043E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u0438 \u0440\u043E\u043B\u0438).",
3990
+ codeRules: [
3991
+ "\u0421\u0434\u0435\u043B\u0430\u0439 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0440\u043E\u0441\u0438\u0442 \u0437\u0430\u0434\u0430\u0447\u0430, \u0438 \u043D\u0435 \u043E\u0442\u0445\u043E\u0434\u0438 \u043E\u0442 \u043D\u0435\u0451.",
3992
+ "\u0415\u0441\u043B\u0438 \u0435\u0441\u0442\u044C \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D\u043D\u043E\u0435 \u0422\u0417, \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u043C\u0443: \u0432\u043D\u043E\u0441\u0438 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432 \u0435\u0433\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0435, \u0441 \u0438\u043C\u0435\u043D\u0430\u043C\u0438 \u0438 \u0444\u043E\u0440\u043C\u0430\u043C\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u0438\u0437 \u043D\u0435\u0433\u043E, \u043D\u0435 \u0434\u0435\u043B\u0430\u0439 \u043D\u0438\u0447\u0435\u0433\u043E \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \xAB\u0432\u043D\u0435 \u0440\u0430\u043C\u043E\u043A\xBB, \u0434\u043E\u0431\u0430\u0432\u044C \u0442\u0435\u0441\u0442\u044B \u0438 \u0432\u044B\u043F\u043E\u043B\u043D\u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0438\u0437 \u0435\u0433\u043E \u043F\u043B\u0430\u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438.",
3993
+ "\u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430\u043C\u0438 \u0441\u0430\u043C\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430: \u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0442\u0435\u0441\u0442\u044B, \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u0442\u0438\u043F\u043E\u0432 \u0438 \u043B\u0438\u043D\u0442\u0435\u0440\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0432 \u043D\u0451\u043C \u0435\u0441\u0442\u044C, \u0438 \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0442\u043E, \u0447\u0442\u043E \u0441\u043B\u043E\u043C\u0430\u043B.",
3994
+ "\u0417\u0430\u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0432 \u043A\u0430\u0436\u0434\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u0438\u0437\u043C\u0435\u043D\u0438\u043B, \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u0435\u0442\u043A\u0435. \u041D\u0435 \u0434\u0435\u043B\u0430\u0439 push, \u043D\u0435 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u0439 \u0438 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439 \u0432\u0435\u0442\u043A\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u0438\u0441\u0442\u043E\u0440\u0438\u044E: \u0432\u0435\u0442\u043A\u0443 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442 \u0438 PR \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0442 TAMsense."
3995
+ ],
3996
+ localCommitRule: "\u0417\u0430\u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0432 \u043A\u0430\u0436\u0434\u043E\u043C \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u0438\u0437\u043C\u0435\u043D\u0438\u043B, \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u0435\u0442\u043A\u0435. \u041D\u0435 \u0434\u0435\u043B\u0430\u0439 push, \u043D\u0435 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u0439 \u0438 \u043D\u0435 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439 \u0432\u0435\u0442\u043A\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u0438\u0441\u0442\u043E\u0440\u0438\u044E: \u0432\u0435\u0442\u043A\u0443 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442 TAMsense, \u0435\u0441\u043B\u0438 \u0443 \u043F\u0430\u043F\u043A\u0438 \u0435\u0441\u0442\u044C \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u044B\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439, \u0430 \u0441\u043B\u0438\u0432\u0430\u0435\u0442 \u0435\u0451 \u0447\u0435\u043B\u043E\u0432\u0435\u043A.",
3997
+ localFoldersRule: "\u041A\u0430\u0436\u0434\u044B\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0437\u0434\u0435\u0441\u044C \u044D\u0442\u043E git worktree \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043D\u0430 \u044D\u0442\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435. \u0420\u0430\u0431\u043E\u0442\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u0434\u0435\u0441\u044C: \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0439, \u043D\u0435 \u043C\u0435\u043D\u044F\u0439 \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0430\u0439 \u0432\u0435\u0442\u043A\u0438 \u0432 \u0438\u0441\u0445\u043E\u0434\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u0435 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430.",
3998
+ reworkRule: "\u042D\u0442\u043E \u0434\u043E\u0440\u0430\u0431\u043E\u0442\u043A\u0430. \u0412 \u0432\u0435\u0442\u043A\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u043F\u0440\u0435\u0436\u043D\u044F\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u0438, \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E, \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E: \u0441\u0447\u0438\u0442\u0430\u0439 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0432\u0435\u0442\u043A\u0443 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u043E\u043C \u0438\u0441\u0442\u0438\u043D\u044B, \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E, \u0435\u0441\u043B\u0438 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 \u043D\u0435 \u043F\u0440\u043E\u0441\u0438\u0442 \u0438\u043D\u043E\u0433\u043E, \u0438 \u043E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439.",
3999
+ codeReportRule: "\u0412 \u043A\u043E\u043D\u0446\u0435 \u043D\u0430\u043F\u0438\u0448\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0447\u0451\u0442 \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0435\u0433\u043E: \u0447\u0442\u043E \u0442\u044B \u0438\u0437\u043C\u0435\u043D\u0438\u043B, \u043A\u0430\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C.",
4000
+ readOnlyCodeRules: [
4001
+ "\u0422\u0432\u043E\u044F \u0440\u043E\u043B\u044C \u043D\u0430 \u044D\u0442\u043E\u043C \u0448\u0430\u0433\u0435 \u0442\u043E\u043B\u044C\u043A\u043E \u0447\u0438\u0442\u0430\u0435\u0442. \u0421\u0434\u0435\u043B\u0430\u0439 \u0440\u0430\u0431\u043E\u0442\u0443, \u043A\u043E\u0442\u043E\u0440\u0443\u044E \u043F\u0440\u043E\u0441\u0438\u0442 \u0437\u0430\u0434\u0430\u0447\u0430, \u0438\u0441\u0441\u043B\u0435\u0434\u0443\u044F \u0438 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u044F \u0442\u043E, \u0447\u0442\u043E \u043F\u043E\u043C\u043E\u0433\u0430\u0435\u0442, \u0437\u0430\u0442\u0435\u043C \u043E\u0442\u0447\u0438\u0442\u0430\u0439\u0441\u044F. \u041D\u0435 \u043C\u0435\u043D\u044F\u0439, \u043D\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0439 \u043A\u043E\u0434 \u043F\u0440\u043E\u0434\u0443\u043A\u0442\u0430: \u043D\u0438\u0447\u0435\u0433\u043E \u0438\u0437 \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F. \u0412\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u043C\u043E\u0436\u043D\u043E \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0442\u044C \u0432\u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432.",
4002
+ "\u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u043E, \u043E \u0447\u0451\u043C \u043E\u0442\u0447\u0438\u0442\u044B\u0432\u0430\u0435\u0448\u044C\u0441\u044F, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430\u043C\u0438 \u0441\u0430\u043C\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0433\u0434\u0435 \u043E\u043D\u0438 \u0435\u0441\u0442\u044C, \u0438 \u0441\u043A\u0430\u0436\u0438, \u043A\u0430\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B."
4003
+ ],
4004
+ readOnlyReportRule: "\u0412 \u043A\u043E\u043D\u0446\u0435 \u043D\u0430\u043F\u0438\u0448\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0447\u0451\u0442: \u0447\u0442\u043E \u0442\u044B \u043D\u0430\u0448\u0451\u043B, \u043A\u0430\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u043B \u0438 \u0447\u0442\u043E \u043E\u0441\u0442\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C.",
4005
+ checkRules: [
4006
+ "\u0420\u0430\u0431\u043E\u0442\u0430 \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043B\u0435\u0436\u0438\u0442 \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u0432\u0435\u0442\u043A\u0435. \u0421\u043C\u043E\u0442\u0440\u0438 \u0435\u0451 \u0447\u0435\u0440\u0435\u0437 git diff \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u0431\u0430\u0437\u043E\u0432\u043E\u0439 \u0432\u0435\u0442\u043A\u0438, \u0443\u043A\u0430\u0437\u0430\u043D\u043D\u043E\u0439 \u0434\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u043D\u0438\u0436\u0435, \u0438 \u0447\u0438\u0442\u0430\u0439 \u043A\u043E\u0434 \u0432\u043E\u043A\u0440\u0443\u0433.",
4007
+ "\u041D\u0435 \u043C\u0435\u043D\u044F\u0439, \u043D\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0439 \u043A\u043E\u0434 \u043F\u0440\u043E\u0434\u0443\u043A\u0442\u0430. \u0417\u0430\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u043C\u043E\u0436\u043D\u043E \u0447\u0442\u043E \u0443\u0433\u043E\u0434\u043D\u043E, \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439 \u0432\u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432.",
4008
+ "\u0421\u0443\u0434\u0438 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435, \u0422\u0417 \u0438 \u0435\u0433\u043E \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u044F\u043C \u043F\u0440\u0438\u0451\u043C\u043A\u0438, \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044F \u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0439 \u043F\u043E \u0435\u0433\u043E id (AC-1 \u0438 \u0442\u0430\u043A \u0434\u0430\u043B\u0435\u0435), \u0435\u0441\u043B\u0438 \u0443 \u0422\u0417 \u0435\u0441\u0442\u044C id. \u0411\u0443\u0434\u044C \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0435\u043D: \u0443 \u043A\u0430\u0436\u0434\u043E\u0439 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B \u0435\u0441\u0442\u044C \u0444\u0430\u0439\u043B, \u0441\u0442\u0440\u043E\u043A\u0430 \u0438 \u0447\u0442\u043E \u0441 \u043D\u0435\u0439 \u0441\u0434\u0435\u043B\u0430\u0442\u044C.",
4009
+ VERDICT_RULE
4010
+ ],
4011
+ screenshotRule: `\u0415\u0441\u043B\u0438 \u0442\u044B \u0441\u043D\u0438\u043C\u0430\u0435\u0448\u044C \u044D\u043A\u0440\u0430\u043D\u044B (\u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430), \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u0438 \u0442\u0435, \u0447\u0442\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u044E\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C ${SCREENSHOT_TOOL_NAME}. \u041B\u044E\u0434\u0438 \u0432\u0438\u0434\u044F\u0442 \u0438\u0445 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0435, \u043D\u0435 \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u044F \u043A\u043E\u0434.`,
4012
+ namingRule: "\u041D\u0430\u0437\u044B\u0432\u0430\u0439 \u0432\u0441\u0451, \u0447\u0442\u043E \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u044F\u0435\u0448\u044C (\u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B, \u0437\u0430\u043F\u0438\u0441\u0438, \u0432\u0438\u0434\u0435\u043E), \u043F\u043E \u0442\u043E\u043C\u0443, \u0447\u0442\u043E \u043D\u0430 \u043D\u0451\u043C \u0432\u0438\u0434\u043D\u043E \u0438 \u0437\u0430\u0447\u0435\u043C \u043E\u043D\u043E \u0441\u0434\u0435\u043B\u0430\u043D\u043E: \u043A\u043E\u0440\u043E\u0442\u043A\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \xAB\u0412\u0430\u0440\u0438\u0430\u043D\u0442 \u0434\u0438\u0437\u0430\u0439\u043D\u0430 1\xBB, \xAB\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u0438\xBB \u0438\u043B\u0438 \xAB\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u0438, \u043F\u043E\u043F\u044B\u0442\u043A\u0430 2\xBB, \u0438 \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0432 \u043E\u0434\u043D\u0443 \u0441\u0442\u0440\u043E\u043A\u0443, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \xAB\u0421\u0440\u0430\u0432\u043D\u0435\u043D\u0438\u0435 \u0434\u0432\u0443\u0445 \u043C\u0430\u043A\u0435\u0442\u043E\u0432 \u043F\u0443\u0441\u0442\u043E\u0433\u043E \u0441\u043F\u0438\u0441\u043A\u0430 \u0437\u0430\u043A\u0430\u0437\u043E\u0432\xBB. \u041D\u0443\u043C\u0435\u0440\u0443\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u0434\u0438\u0437\u0430\u0439\u043D\u0430, \u043D\u0430\u0437\u044B\u0432\u0430\u0439 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043F\u043E \u0446\u0435\u043B\u0438 \u0438 \u043F\u043E\u043F\u044B\u0442\u043A\u0435 \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0431\u0435\u0437\u043B\u0438\u043A\u0438\u0435 \u0438\u043C\u0435\u043D\u0430 \u0432\u0440\u043E\u0434\u0435 \xABscreenshot1\xBB.",
4013
+ recordingRule: `\u041A\u0430\u0436\u0434\u044B\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0435\u0448\u044C \u0447\u0435\u0440\u0435\u0437 Playwright \u0438\u0437 Node, \u0437\u0430\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447\u0438, \u0438 \u043B\u044E\u0434\u0438 \u043C\u043E\u0433\u0443\u0442 \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0437\u0430\u043F\u0438\u0441\u044C; \u0437\u0430\u043F\u0440\u043E\u0441\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u043C\u043E\u0433\u043E \u0441\u0430\u0439\u0442\u0430 \u043A \u0442\u0440\u0435\u043A\u0435\u0440\u0443 TAMsense \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0442\u0441\u044F, \u0442\u0430\u043A \u0447\u0442\u043E \u0442\u0432\u043E\u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u044B \u043D\u0435 \u043F\u043E\u043F\u0430\u0434\u0430\u044E\u0442 \u0432 \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u043A\u0443, \u0430 \u0437\u0430\u043F\u0440\u043E\u0441\u044B, \u043A\u0440\u043E\u043C\u0435 GET, HEAD \u0438 OPTIONS, \u043A \u0430\u0434\u0440\u0435\u0441\u0430\u043C \u0432\u043D\u0435 \u044D\u0442\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430 \u043E\u0431\u0440\u044B\u0432\u0430\u044E\u0442\u0441\u044F. \u0412 \u043A\u0430\u0436\u0434\u043E\u043C \u0441\u043A\u0440\u0438\u043F\u0442\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0439 \u0440\u0435\u043A\u043E\u0440\u0434\u0435\u0440 \u0438\u0437 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F TAMSENSE_RECORDER (require(process.env.TAMSENSE_RECORDER) \u0438\u043B\u0438 createRequire(import.meta.url)(process.env.TAMSENSE_RECORDER) \u0432 \u0441\u043A\u0440\u0438\u043F\u0442\u0435 .mjs), \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u0439 \u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0447\u0435\u0440\u0435\u0437 await recorder.newRecordedContext(browser, { title, purpose, context: { viewport } }), \u043D\u0430 \u043A\u0430\u0436\u0434\u043E\u043C \u0437\u043D\u0430\u0447\u0438\u043C\u043E\u043C \u0448\u0430\u0433\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0439 await recorder.mark(page, 'what just happened') \u0438 \u0437\u0430\u043A\u0430\u043D\u0447\u0438\u0432\u0430\u0439 \u0432\u044B\u0437\u043E\u0432\u043E\u043C await recorder.closeRecorded(context, { notes }), \u0433\u0434\u0435 notes \u0441\u0432\u043E\u0438\u043C\u0438 \u0441\u043B\u043E\u0432\u0430\u043C\u0438 \u0433\u043E\u0432\u043E\u0440\u044F\u0442, \u0447\u0442\u043E \u0442\u044B \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B \u0432 \u044D\u0442\u043E\u043C \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438 \u0447\u0442\u043E \u043F\u0440\u043E\u0438\u0437\u043E\u0448\u043B\u043E: \u0447\u0442\u043E \u043E\u0442\u043A\u0440\u044B\u043B, \u0447\u0442\u043E \u043D\u0430\u0436\u0430\u043B \u0438\u043B\u0438 \u0432\u0432\u0451\u043B, \u0447\u0435\u0433\u043E \u043E\u0436\u0438\u0434\u0430\u043B \u0438 \u043F\u0440\u043E\u0448\u043B\u043E \u043B\u0438 \u044D\u0442\u043E (\u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 600 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432). \u041B\u044E\u0434\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0441\u043C\u043E\u0442\u0440\u044F\u0442 \u0437\u0430\u043F\u0438\u0441\u044C, \u0447\u0438\u0442\u0430\u044E\u0442 \u0438\u0445 \u043F\u0435\u0440\u0432\u044B\u043C\u0438. \u0414\u043E\u0431\u0430\u0432\u044C video: true, \u0435\u0441\u043B\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0435\u0441\u0442\u044C canvas, \u043A\u0430\u0440\u0442\u0430, \u0432\u0438\u0434\u0435\u043E\u043F\u043B\u0435\u0435\u0440 \u0438\u043B\u0438 iframe \u0441 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u0441\u0430\u0439\u0442\u0430 (\u0437\u0430\u043F\u0438\u0441\u044C \u0438\u0445 \u043D\u0435 \u043F\u043E\u043A\u0430\u0436\u0435\u0442), \u0438\u043B\u0438 \u0435\u0441\u043B\u0438 \u043A\u0442\u043E-\u0442\u043E \u043F\u0440\u043E\u0441\u0438\u0442 \u0432\u0438\u0434\u0435\u043E. \u0417\u0430\u043F\u0438\u0441\u0438 \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u044F\u044E\u0442\u0441\u044F \u043A \u0437\u0430\u0434\u0430\u0447\u0435, \u043A\u043E\u0433\u0434\u0430 \u0437\u0430\u043F\u0443\u0441\u043A \u0437\u0430\u043A\u0430\u043D\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F; ${REPLAY_TOOL_NAME} \u0438 ${VIDEO_TOOL_NAME} \u043F\u0440\u0438\u043A\u0440\u0435\u043F\u043B\u044F\u044E\u0442 \u0437\u0430\u043F\u0438\u0441\u044C \u0440\u0430\u043D\u044C\u0448\u0435 \u0438 \u0441 \u043B\u0443\u0447\u0448\u0438\u043C \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435\u043C.`,
4014
+ browserRule: "\u0414\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0431\u0440\u0430\u0443\u0437\u0435\u0440 \u0431\u0435\u0437 \u043E\u043A\u043D\u0430. \u0415\u0441\u043B\u0438 \u0432 \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u043D\u0435\u0442 \u0441\u0432\u043E\u0435\u0433\u043E Playwright, \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0439 playwright-core \u0438\u0437 \u043F\u0430\u043F\u043A\u0438 \u0432 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u043E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u044F TAMSENSE_PLAYWRIGHT, \u0430 PLAYWRIGHT_BROWSERS_PATH \u0441\u0442\u0430\u0432\u044C \u0440\u0430\u0432\u043D\u044B\u043C \u043F\u0430\u043F\u043A\u0435 \u0438\u0437 TAMSENSE_PLAYWRIGHT_BROWSERS.",
4015
+ folderRule: "\u0412 \u044D\u0442\u043E\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 \u043D\u0435\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432. \u0422\u0435\u043A\u0443\u0449\u0430\u044F \u043F\u0430\u043F\u043A\u0430 \u043F\u0440\u0438\u043D\u0430\u0434\u043B\u0435\u0436\u0438\u0442 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0435: \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0432 \u043D\u0435\u0439 \u0438 \u0445\u0440\u0430\u043D\u0438 \u0437\u0434\u0435\u0441\u044C \u0441\u0432\u043E\u0438 \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B.",
4016
+ resultHomeRule: RESULT_HOME_RULE2,
4017
+ folderDoRules: [
4018
+ `\u0421\u043E\u0445\u0440\u0430\u043D\u044F\u0439 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0435 \u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430\u043C\u0438 TAMsense: ${SAVE_DOCUMENT_TOOL_NAME} \u0434\u043B\u044F \u0442\u0435\u043A\u0441\u0442\u0430 \u0432 Markdown, ${SAVE_TABLE_TOOL_NAME} \u0434\u043B\u044F \u0441\u0442\u0440\u043E\u043A \u0434\u0430\u043D\u043D\u044B\u0445 \u0432 CSV, ${ATTACH_FILE_TOOL_NAME} \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0442\u044B \u0441\u0434\u0435\u043B\u0430\u043B \u0432 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0435, ${ADD_LINK_TOOL_NAME} \u0434\u043B\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043E\u043F\u0438\u0440\u0430\u0435\u0442\u0441\u044F \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0414\u0435\u0440\u0436\u0438 \u043E\u0434\u043D\u043E \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0439 \u043A\u0430\u0436\u0434\u043E\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u043A\u0430\u043A \u043D\u043E\u0432\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E \u044D\u0442\u043E\u0433\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430.`,
4019
+ "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0444\u0430\u043A\u0442\u044B \u0438\u0437 \u0437\u0430\u0434\u0430\u0447\u0438, \u0437\u0430\u043C\u0435\u0442\u043E\u043A, \u043F\u0440\u0435\u0436\u043D\u0438\u0445 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u0432 \u0438 \u0442\u043E\u0433\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043B \u0441\u0430\u043C; \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0447\u0438\u0441\u043B\u0430, \u0438\u043C\u0435\u043D\u0430 \u0438\u043B\u0438 \u0434\u0430\u0442\u044B.",
4020
+ RESULT_HOME_RULE2
4021
+ ],
4022
+ folderReworkRule: "\u0417\u0430\u0434\u0430\u0447\u0430 \u0432\u0435\u0440\u043D\u0443\u043B\u0430\u0441\u044C \u0441 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F\u043C\u0438. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u043F\u0440\u0435\u0436\u043D\u0438\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0432 \u043F\u0430\u043F\u043A\u0435 results, \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0442\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0441\u044F\u0442 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F, \u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0438 \u043D\u043E\u0432\u044B\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0442\u0435\u0445 \u0436\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432.",
4023
+ folderReportRule: "\u0412 \u043A\u043E\u043D\u0446\u0435 \u043D\u0430\u043F\u0438\u0448\u0438 \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u0439 \u043E\u0442\u0447\u0451\u0442 \u0434\u043B\u044F \u043B\u044E\u0434\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0438: \u0447\u0442\u043E \u0442\u044B \u0441\u0434\u0435\u043B\u0430\u043B, \u0447\u0442\u043E \u0440\u0435\u0448\u0438\u043B \u0441\u0430\u043C \u0438 \u0447\u0442\u043E \u0435\u0449\u0451 \u043E\u0442\u043A\u0440\u044B\u0442\u043E.",
4024
+ folderCheckRules: [
4025
+ `\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0434\u043B\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043B\u0435\u0436\u0438\u0442 \u0432 \u043F\u0430\u043F\u043A\u0435 ${RESULTS_FOLDER}: \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u044F\u044F \u0432\u0435\u0440\u0441\u0438\u044F \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 (.md) \u0438 \u0442\u0430\u0431\u043B\u0438\u0446\u044B (.csv), \u0444\u0430\u0439\u043B\u044B \u0438 links.md. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0432\u0441\u0451.`,
4026
+ `\u041D\u0435 \u043C\u0435\u043D\u044F\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442. \u0412\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u043C\u043E\u0436\u043D\u043E \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0442\u044C \u0432\u043D\u0435 \u043F\u0430\u043F\u043A\u0438 ${RESULTS_FOLDER}.`,
4027
+ "\u0421\u0443\u0434\u0438 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435, \u0437\u0430\u043C\u0435\u0442\u043A\u0430\u043C \u043F\u0440\u0435\u0436\u043D\u0438\u0445 \u0448\u0430\u0433\u043E\u0432 \u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044F\u043C \u044D\u0442\u043E\u0433\u043E \u0448\u0430\u0433\u0430. \u0411\u0443\u0434\u044C \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u0435\u043D: \u0443 \u043A\u0430\u0436\u0434\u043E\u0439 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B \u043D\u0430\u0437\u0432\u0430\u043D \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442, \u043C\u0435\u0441\u0442\u043E \u0432 \u043D\u0451\u043C \u0438 \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C.",
4028
+ VERDICT_RULE
4029
+ ],
4030
+ dependenciesRule: "\u0412 \u043A\u043E\u043F\u0438\u0438 \u043C\u043E\u0433\u0443\u0442 \u0431\u044B\u0442\u044C \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438. \u0427\u0442\u043E\u0431\u044B \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u0438\u043B\u0438 \u0435\u0433\u043E \u0442\u0435\u0441\u0442\u044B, \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u0438\u0437 \u0435\u0433\u043E lock-\u0444\u0430\u0439\u043B\u0430 \u0435\u0433\u043E \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C \u043F\u0430\u043A\u0435\u0442\u043E\u0432 (pnpm install --frozen-lockfile, npm ci, yarn install --immutable \u0438 \u043F\u043E\u0434\u043E\u0431\u043D\u044B\u0435). \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u043F\u0430\u043A\u0435\u0442\u044B, \u043D\u0435 \u043C\u0435\u043D\u044F\u0439 lock-\u0444\u0430\u0439\u043B \u0438 \u043D\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u0439 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u044B.",
4031
+ localApp: {
4032
+ start: "\u041A\u043E\u0433\u0434\u0430 \u043D\u0443\u0436\u043D\u043E \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0449\u0435\u0435 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u0442\u043E\u043B\u044C\u043A\u043E \u0438\u0437 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438.",
4033
+ find: "\u041A\u0430\u043A \u043F\u0440\u043E\u0435\u043A\u0442 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E, \u043D\u0430\u0439\u0434\u0438 \u0441\u0430\u043C: README, compose-\u0444\u0430\u0439\u043B\u044B \u0441 local \u0438\u043B\u0438 dev \u0432 \u0438\u043C\u0435\u043D\u0438, .env.example \u0438 \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0435 env-\u0444\u0430\u0439\u043B\u044B, \u0441\u043A\u0440\u0438\u043F\u0442\u044B \u043F\u0430\u043A\u0435\u0442\u0430.",
4034
+ ask: `\u0415\u0441\u043B\u0438 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043D\u0435\u043F\u043E\u043D\u044F\u0442\u043D\u043E, \u043A\u0430\u043A \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0435\u0433\u043E \u0433\u043E\u0442\u043E\u0432\u0438\u0442, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u043A\u0430\u043A \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0431\u0430\u0437\u0430 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u0441\u0432\u043E\u0438 \u0442\u0430\u0431\u043B\u0438\u0446\u044B, \u0441\u043F\u0440\u043E\u0441\u0438 \u0447\u0435\u0440\u0435\u0437 ${ASK_PERSON_TOOL_NAME}, \u0430 \u043D\u0435 \u0433\u0430\u0434\u0430\u0439.`,
4035
+ localSettings: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0435\u0441\u0442\u044C \u0432 \u044D\u0442\u043E\u0439 \u043A\u043E\u043F\u0438\u0438. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0447\u0438\u0442\u0430\u0439 \u0438 \u043D\u0435 \u043A\u043E\u043F\u0438\u0440\u0443\u0439 .env-\u0444\u0430\u0439\u043B\u044B \u0438\u0437 \u0434\u0440\u0443\u0433\u0438\u0445 \u043C\u0435\u0441\u0442 \u044D\u0442\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430, \u0432\u043A\u043B\u044E\u0447\u0430\u044F \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u0443\u044E \u043A\u043E\u043F\u0438\u044E \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430, \u0438 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0430\u0434\u0440\u0435\u0441\u0430 staging, \u043E\u0431\u0449\u0435\u0433\u043E dev \u0438\u043B\u0438 \u043F\u0440\u043E\u0434\u0430.",
4036
+ addresses: "\u041F\u0435\u0440\u0435\u0434 \u0437\u0430\u043F\u0443\u0441\u043A\u043E\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u0430\u0436\u0434\u044B\u0439 \u0430\u0434\u0440\u0435\u0441 \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0448\u044C: \u0431\u0430\u0437\u044B, \u043A\u044D\u0448\u0438, \u043E\u0447\u0435\u0440\u0435\u0434\u0438 \u0438 API \u0434\u043E\u043B\u0436\u043D\u044B \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u043D\u0430 localhost \u0438\u043B\u0438 \u043D\u0430 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0442\u044B \u043F\u043E\u0434\u043D\u044F\u043B, \u0430 \u043A\u043B\u044E\u0447\u0438 \u0432\u043D\u0435\u0448\u043D\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432 \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F \u043F\u0443\u0441\u0442\u044B\u043C\u0438.",
4037
+ containers: (project) => `\u041F\u043E\u0434\u043D\u0438\u043C\u0430\u0439 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u044B \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u044B\u043C compose-\u043F\u0440\u043E\u0435\u043A\u0442\u043E\u043C \u0441 \u0438\u043C\u0435\u043D\u0435\u043C ${project} \u043D\u0430 \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u044B\u0445 \u043F\u043E\u0440\u0442\u0430\u0445 \u0445\u043E\u0441\u0442\u0430 (\u0444\u0430\u0439\u043B override \u0432\u043E \u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E\u0439 \u043F\u0430\u043F\u043A\u0435), \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0439 \u0438 \u043D\u0435 \u043C\u0435\u043D\u044F\u0439 \u043A\u043E\u043D\u0442\u0435\u0439\u043D\u0435\u0440\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u043E\u0434\u043D\u044F\u043B \u043D\u0435 \u0442\u044B, \u0430 \u0441\u0432\u043E\u0438 \u0432 \u043A\u043E\u043D\u0446\u0435 \u0443\u0434\u0430\u043B\u0438 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u0442\u043E\u043C\u0430\u043C\u0438 (docker compose -p ${project} down -v).`,
4038
+ cannotStart: (cannotRun) => `\u0415\u0441\u043B\u0438 Docker \u043D\u0435 \u0437\u0430\u043F\u0443\u0449\u0435\u043D, \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0439 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u043D\u0435\u0442 \u0438\u043B\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 \u0441\u043C\u043E\u0442\u0440\u044F\u0442 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B \u044D\u0442\u043E\u0433\u043E \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0430, \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439: ${cannotRun}`,
4039
+ cannotRunCheck: `\u0432\u044B\u0437\u043E\u0432\u0438 ${VERDICT_TOOL_NAME} \u0441 passed false \u0438 couldNotRun true, \u043D\u0430\u043F\u0438\u0448\u0438 \u0432 report \u0442\u043E\u0447\u043D\u0443\u044E \u043F\u0440\u0438\u0447\u0438\u043D\u0443 \u0438 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u0430\u043B\u044C\u0448\u0435: \u0437\u0430\u0434\u0430\u0447\u0430 \u0436\u0434\u0451\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u043D\u0430 \u044D\u0442\u043E\u043C \u0448\u0430\u0433\u0435.`,
4040
+ cannotRunReport: "\u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u043E\u0442\u0447\u0451\u0442\u0435 \u0441 \u0442\u043E\u0447\u043D\u043E\u0439 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \u0438 \u0442\u0435\u043C, \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u0447\u0435\u043B\u043E\u0432\u0435\u043A."
4041
+ },
4042
+ stageGoals: {
4043
+ code: "\u0420\u0435\u0430\u043B\u0438\u0437\u0443\u0439 \u0437\u0430\u0434\u0430\u0447\u0443",
4044
+ review: "\u041F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043E\u0434 \u0437\u0430\u0434\u0430\u0447\u0438",
4045
+ test: "\u041F\u0440\u043E\u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0439 \u0437\u0430\u0434\u0430\u0447\u0443"
4046
+ },
4047
+ readOnlyGoal: "\u041F\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043D\u0430\u0434 \u0437\u0430\u0434\u0430\u0447\u0435\u0439",
4048
+ folderGoals: {
4049
+ code: "\u041F\u043E\u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u043D\u0430\u0434 \u0437\u0430\u0434\u0430\u0447\u0435\u0439",
4050
+ review: "\u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0437\u0430\u0434\u0430\u0447\u0438",
4051
+ test: "\u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0437\u0430\u0434\u0430\u0447\u0438"
4052
+ },
4053
+ taskHeading: (goal, taskId, title) => `${goal} TAMsense #${taskId}: ${title}`,
4054
+ specUnattendedRule: "\u0422\u044B \u0433\u043E\u0442\u043E\u0432\u0438\u0448\u044C \u0437\u0430\u0434\u0430\u0447\u0443 TAMsense \u043D\u0430 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. \u0412\u043E \u0432\u0440\u0435\u043C\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0442\u0435\u0431\u0435 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442: \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043D\u0430 \u0442\u0432\u043E\u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0438 \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0422\u0417 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u043E\u0441\u043B\u0435 \u043D\u0435\u0433\u043E. \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0439\u0441\u044F \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0438 \u043E\u0442\u0432\u0435\u0442\u0430 \u0438 \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F\u0445: \u0441\u0434\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0441\u0438\u0442 \u044D\u0442\u043E\u0442 \u044D\u0442\u0430\u043F, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C TAMsense \u0438 \u0437\u0430\u043A\u043E\u043D\u0447\u0438. \u041B\u044E\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u043C\u043E\u0433\u0443\u0442 \u043F\u0438\u0441\u0430\u0442\u044C \u0442\u0435\u0431\u0435 \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430, \u043A\u0430\u0436\u0434\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0441 \xABMessage from <name>:\xBB. \u0421\u043B\u0435\u0434\u0443\u0439 \u0438\u043C.",
4055
+ readOnlyRule: "\u0427\u0438\u0442\u0430\u0439 \u043A\u043E\u0434, \u0447\u0442\u043E\u0431\u044B \u0438\u0437\u0443\u0447\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443. \u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043C\u0435\u043D\u044F\u0439, \u043D\u0435 \u043A\u043E\u043C\u043C\u0438\u0442\u044C \u0438 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0439: \u043D\u0438\u0447\u0435\u0433\u043E \u0438\u0437 \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F, \u0430 \u0448\u0430\u0433 \u043A\u043E\u0434\u0430 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0441 \u0447\u0438\u0441\u0442\u043E\u0439 \u0432\u0435\u0442\u043A\u0438.",
4056
+ questionShapeRule: "\u0423 \u043A\u0430\u0436\u0434\u043E\u0433\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u0435\u0441\u0442\u044C id \u0432 snake_case, \u043E\u0434\u043D\u043E \u043F\u0440\u043E\u0441\u0442\u043E\u0435 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0435, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446 \u043E\u0442\u0432\u0435\u0442\u0438\u0442, \u043D\u0435 \u0447\u0438\u0442\u0430\u044F \u043A\u043E\u0434, \u0441\u0442\u0440\u043E\u043A\u0430 whyItMatters \u0438 \u043E\u0442 2 \u0434\u043E 4 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u043E\u0432, \u043E\u0441\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0445 \u043D\u0430 \u0442\u043E\u043C, \u0447\u0442\u043E \u0442\u044B \u0432\u0438\u0434\u0435\u043B; \u043A\u0430\u0436\u0434\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \u0433\u043E\u0432\u043E\u0440\u0438\u0442, \u0447\u0442\u043E \u0431\u0443\u0434\u0435\u0442, \u0435\u0441\u043B\u0438 \u0435\u0433\u043E \u0432\u044B\u0431\u0440\u0430\u0442\u044C. \u0412\u0430\u0440\u0438\u0430\u043D\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u0441\u043E\u0432\u0435\u0442\u0443\u0435\u0448\u044C, \u0441\u0442\u0430\u0432\u044C \u043F\u0435\u0440\u0432\u044B\u043C \u0438 \u043E\u0442\u043C\u0435\u0447\u0430\u0439 recommended (\u0432 \u0432\u043E\u043F\u0440\u043E\u0441\u0435 multiSelect \u043D\u0430 \u043A\u0430\u0436\u0434\u043E\u043C \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u0435, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0441\u043E\u0432\u0435\u0442\u0443\u0435\u0448\u044C). multiSelect \u0440\u0430\u0432\u043D\u043E true, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0432\u0430\u0440\u0438\u0430\u043D\u0442\u044B \u0441\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u044E\u0442\u0441\u044F. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442 \xAB\u0414\u0440\u0443\u0433\u043E\u0435\xBB: \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0441\u0430\u043C\u0430 \u0434\u043E\u0431\u0430\u0432\u043B\u044F\u0435\u0442 \u043F\u043E\u043B\u0435 \u0434\u043B\u044F \u0441\u0432\u043E\u0431\u043E\u0434\u043D\u043E\u0433\u043E \u043E\u0442\u0432\u0435\u0442\u0430. \u041D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0442\u0432\u043E\u0439 \u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u043E\u0432\u0430\u043D\u043D\u044B\u0439 \u0432\u0430\u0440\u0438\u0430\u043D\u0442, \u0442\u0430\u043A \u0447\u0442\u043E \u0441\u043E\u0432\u0435\u0442\u0443\u0439 \u0442\u043E\u0442, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0433\u043E\u0442\u043E\u0432 \u043E\u0442\u0441\u0442\u043E\u044F\u0442\u044C.",
4057
+ questionsReplyRule: "\u0417\u0430\u0442\u0435\u043C \u043E\u0442\u0432\u0435\u0442\u044C \u043E\u0434\u043D\u0438\u043C \u0438\u043B\u0438 \u0434\u0432\u0443\u043C\u044F \u043F\u0440\u043E\u0441\u0442\u044B\u043C\u0438 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u044F\u043C\u0438: \u0447\u0442\u043E \u0442\u044B \u043F\u043E\u043D\u044F\u043B \u0438\u0437 \u043A\u043E\u0434\u0430 \u0438 \u0437\u0430\u0447\u0435\u043C \u0437\u0430\u0434\u0430\u0451\u0448\u044C \u044D\u0442\u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0438\u043B\u0438 \u0447\u0442\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043D\u0435 \u043D\u0443\u0436\u043D\u044B. \u041D\u0435 \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u044B: \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0438\u0445 \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442.",
4058
+ specRules: [
4059
+ "\u041D\u0430\u0447\u043D\u0438 \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u043D\u0430\u0432\u044B\u043A\u0430 tamsense:spec-writing \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C Skill \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u043C\u0443: \u0432 \u043D\u0451\u043C \u0444\u043E\u0440\u043C\u0430 \u0422\u0417, \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0434\u043B\u044F \u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0435\u0432 \u043F\u0440\u0438\u0451\u043C\u043A\u0438, \u0431\u044E\u0434\u0436\u0435\u0442 \u0440\u0430\u0437\u043C\u0435\u0440\u0430, \u0441\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u043E\u0432\u0435\u0440\u043E\u043A \u043B\u0438\u043D\u0442\u0435\u0440\u0430 \u0438 builder check.",
4060
+ "\u041D\u0430\u0447\u043D\u0438 \u0441 \u0431\u0440\u0438\u0444\u0430 \u0438 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0430 \u043D\u0438\u0436\u0435. \u041F\u0440\u043E\u0432\u0435\u0440\u044C \u0432 \u043A\u043E\u0434\u0435 \u0442\u043E, \u043D\u0430 \u0447\u0451\u043C \u0434\u0435\u0440\u0436\u0438\u0442\u0441\u044F \u0422\u0417, \u0438 \u0438\u0437\u0443\u0447\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u043D\u0435\u0434\u043E\u0441\u0442\u0430\u044E\u0449\u0435\u0435; \u0431\u0435\u0437 \u0431\u0440\u0438\u0444\u0430 \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0438\u0437\u0443\u0447\u0438 \u0437\u0430\u0434\u0430\u0447\u0443 \u0442\u0430\u043A, \u043A\u0430\u043A \u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u043D\u0430\u0432\u044B\u043A tamsense task-interview.",
4061
+ "\u041F\u0440\u0438\u043C\u0435\u043D\u0438 \u043E\u0442\u0432\u0435\u0442\u044B: \u043E\u0442\u0432\u0435\u0442 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 by: owner; \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 \xABRecommended option applied, not answered\xBB \u044D\u0442\u043E \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \xABnot answered, the recommended option applies\xBB. \u0415\u0441\u043B\u0438 \u043E\u0442\u0432\u0435\u0442 \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u043A\u043E\u0434\u0443 \u0438\u043B\u0438 \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043E\u0442\u0432\u0435\u0442\u0443, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u043A\u043E\u0434, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u044C \u0435\u043C\u0443, \u0438 \u0441\u043A\u0430\u0436\u0438 \u043E\u0431 \u044D\u0442\u043E\u043C \u0432 \u043E\u0442\u0432\u0435\u0442\u0435.",
4062
+ "\u0413\u0434\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u043E\u0442\u0432\u0435\u0442\u044B \u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043C\u043E\u043B\u0447\u0430\u0442, \u0430 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0435\u0441\u0442\u044C, \u0432\u044B\u0431\u0435\u0440\u0438 \u0435\u0433\u043E \u0438 \u0437\u0430\u043F\u0438\u0448\u0438 \u043A\u0430\u043A \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u041F\u0443\u043D\u043A\u0442 \u0431\u0435\u0437 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043C\u0435\u043D\u044F\u0435\u0442 \u0440\u0430\u043C\u043A\u0438 \u0438\u043B\u0438 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u0435, \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u043C; \u0442\u0430\u043A\u0438\u0445 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 3.",
4063
+ "\u041F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u0434\u0430\u0442\u044C \u0422\u0417, \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0432\u044B\u043F\u043E\u043B\u043D\u0438 builder check \u0438\u0437 \u043D\u0430\u0432\u044B\u043A\u0430. \u041E\u043D \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D, \u0434\u0430\u0436\u0435 \u0434\u043B\u044F \u043C\u0430\u043B\u0435\u043D\u044C\u043A\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438: \u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u043D\u043E\u0432\u043E\u0433\u043E \u0441\u0443\u0431\u0430\u0433\u0435\u043D\u0442\u0430 general-purpose \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C Agent, \u0434\u0430\u0439 \u0435\u043C\u0443 \u0442\u043E\u043B\u044C\u043A\u043E \u0422\u0417 \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0432\u043D\u0443\u0442\u0440\u0438 \u043F\u0440\u043E\u043C\u043F\u0442\u0430 builder check \u0438\u0437 \u043D\u0430\u0432\u044B\u043A\u0430 \u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0438 \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u0443\u043D\u043A\u0442, \u043E \u043A\u043E\u0442\u043E\u0440\u043E\u043C \u043E\u043D \u0441\u043E\u043E\u0431\u0449\u0438\u0442, \u043A\u0430\u043A \u0433\u043E\u0432\u043E\u0440\u0438\u0442 \u043D\u0430\u0432\u044B\u043A.",
4064
+ `\u0421\u0434\u0430\u0439 \u041F\u041E\u041B\u041D\u041E\u0415 \u0422\u0417 \u0447\u0435\u0440\u0435\u0437 ${SUBMIT_SPEC_TOOL_NAME}. TAMsense \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0435\u0433\u043E \u043B\u0438\u043D\u0442\u0435\u0440\u043E\u043C \u0438 \u0441\u0432\u0435\u0440\u044F\u0435\u0442 \u0444\u0430\u0439\u043B\u044B, \u0430 \u0435\u0441\u043B\u0438 \u0435\u0433\u043E \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u043B \u0441\u0432\u0435\u0436\u0438\u0439 \u0447\u0438\u0442\u0430\u0442\u0435\u043B\u044C, \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0432\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0435\u0433\u043E; \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u0448\u043B\u0438 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F, \u0422\u0417 \u043D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E: \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0435\u0433\u043E \u0438 \u0441\u043D\u043E\u0432\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 ${SUBMIT_SPEC_TOOL_NAME}. \u041F\u043E\u0441\u043B\u0435 \u0434\u0432\u0443\u0445 \u0440\u0430\u0443\u043D\u0434\u043E\u0432 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439 \u043B\u0438\u043D\u0442\u0435\u0440\u0430 \u0422\u0417 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0435\u0441\u0442\u044C.`,
4065
+ "\u0415\u0441\u043B\u0438 \u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u043E\u0435 \u0422\u0417, \u0432 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435 \u043B\u0435\u0436\u0430\u0442 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u0438\u043B\u0438 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u0435\u0433\u043E \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B: \u043F\u0440\u0438\u043C\u0435\u043D\u0438 \u0438\u0445 \u0438 \u0441\u0434\u0430\u0439 \u043F\u043E\u043B\u043D\u043E\u0435 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u043E\u0435 \u0422\u0417. \u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0441 \u043E\u0442\u0432\u0435\u0442\u043E\u043C \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C; \u0438\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u043E\u0435 \u0438\u043B\u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D\u043D\u043E\u0435 \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435 \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C \u0438 \u0443\u0445\u043E\u0434\u0438\u0442 \u0438\u0437 \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0439.",
4066
+ "\u0417\u0430\u0442\u0435\u043C \u043E\u0442\u0432\u0435\u0442\u044C \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u043C \u043F\u0440\u043E\u0441\u0442\u044B\u043C \u0438\u0442\u043E\u0433\u043E\u043C: \u043F\u043E\u0434\u0445\u043E\u0434, \u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0440\u0438\u0441\u043A\u0430 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u0435\u0441\u0442\u044C. \u041D\u0435 \u0432\u0441\u0442\u0430\u0432\u043B\u044F\u0439 \u0422\u0417: \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0435\u0433\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442."
4067
+ ],
4068
+ briefUnattendedRule: "\u0422\u044B \u0433\u043E\u0442\u043E\u0432\u0438\u0448\u044C \u0437\u0430\u0434\u0430\u0447\u0443 TAMsense \u043D\u0430 \u0441\u043E\u0431\u0441\u0442\u0432\u0435\u043D\u043D\u043E\u043C \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440\u0435 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F. \u0412\u043E \u0432\u0440\u0435\u043C\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0442\u0435\u0431\u0435 \u043D\u0438\u043A\u0442\u043E \u043D\u0435 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442: \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043D\u0430 \u0442\u0432\u043E\u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0438 \u0441\u043C\u043E\u0442\u0440\u0438\u0442 \u0431\u0440\u0438\u0444 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u043E\u0441\u043B\u0435 \u043D\u0435\u0433\u043E. \u041F\u043E\u044D\u0442\u043E\u043C\u0443 \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043E\u0441\u0442\u0430\u043D\u0430\u0432\u043B\u0438\u0432\u0430\u0439\u0441\u044F \u0432 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u0438 \u043E\u0442\u0432\u0435\u0442\u0430 \u0438 \u043D\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F\u0445: \u0441\u0434\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0441\u0438\u0442 \u044D\u0442\u043E\u0442 \u044D\u0442\u0430\u043F, \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C TAMsense \u0438 \u0437\u0430\u043A\u043E\u043D\u0447\u0438. \u041B\u044E\u0434\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u043C\u043E\u0433\u0443\u0442 \u043F\u0438\u0441\u0430\u0442\u044C \u0442\u0435\u0431\u0435 \u0432\u043E \u0432\u0440\u0435\u043C\u044F \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430, \u043A\u0430\u0436\u0434\u043E\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0441 \xABMessage from <name>:\xBB. \u0421\u043B\u0435\u0434\u0443\u0439 \u0438\u043C.",
4069
+ resultBriefRules: [
4070
+ '\u041D\u0430\u043F\u0438\u0448\u0438 \u0431\u0440\u0438\u0444 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430: goal, context (\u043A\u0440\u0430\u0442\u043A\u043E\u0435 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435), contains (\u0447\u0442\u043E \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442), acceptance (\u043A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u0441 id AC-1, AC-2 \u0438 \u0442\u0430\u043A \u0434\u0430\u043B\u0435\u0435 \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443, \u043A\u0430\u0436\u0434\u044B\u0439 \u044D\u0442\u043E \u043E\u0434\u043D\u043E \u043D\u0430\u0431\u043B\u044E\u0434\u0430\u0435\u043C\u043E\u0435 \u0441\u0432\u043E\u0439\u0441\u0442\u0432\u043E, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u044E\u0449\u0438\u0439 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442, \u0447\u0438\u0442\u0430\u044F \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442), constraints, outOfScope, sources (\u043E\u0442\u043A\u0443\u0434\u0430 \u0431\u0435\u0440\u0443\u0442\u0441\u044F \u0444\u0430\u043A\u0442\u044B: \u0437\u043D\u0430\u043D\u0438\u044F, \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B, \u0444\u0430\u0439\u043B\u044B \u0437\u0430\u0434\u0430\u0447\u0438, \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F), approver (\u043A\u0442\u043E \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442), decisions, assumptions \u0438 openQuestions. \u041F\u043E\u0441\u0442\u0430\u0432\u044C format \u0440\u0430\u0432\u043D\u044B\u043C "brief". \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0438\u0448\u0438 \u043A\u0430\u0440\u0442\u0443 \u043A\u043E\u0434\u0430, \u0444\u0430\u0439\u043B\u044B \u0434\u043B\u044F \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0438\u043B\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438.',
4071
+ "\u041D\u0430\u0447\u043D\u0438 \u0441 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u043E\u0433\u043E \u0431\u0440\u0438\u0444\u0430 \u0438 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0430 \u043D\u0438\u0436\u0435. \u041F\u0440\u0438\u043C\u0435\u043D\u0438 \u043E\u0442\u0432\u0435\u0442\u044B: \u043E\u0442\u0432\u0435\u0442 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u044D\u0442\u043E \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0441 by: owner; \u043E\u0442\u0432\u0435\u0442 \u0441 \u043F\u043E\u043C\u0435\u0442\u043A\u043E\u0439 \xABRecommended option applied, not answered\xBB \u044D\u0442\u043E \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439 \xABnot answered, the recommended option applies\xBB.",
4072
+ "\u0413\u0434\u0435 \u0437\u0430\u0434\u0430\u0447\u0430, \u043E\u0442\u0432\u0435\u0442\u044B \u0438 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043C\u043E\u043B\u0447\u0430\u0442, \u0430 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0435\u0441\u0442\u044C, \u0432\u044B\u0431\u0435\u0440\u0438 \u0435\u0433\u043E \u0438 \u0437\u0430\u043F\u0438\u0448\u0438 \u043A\u0430\u043A \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435 \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439. \u041F\u0443\u043D\u043A\u0442 \u0431\u0435\u0437 \u0431\u0435\u0437\u043E\u043F\u0430\u0441\u043D\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u043C\u0435\u043D\u044F\u0435\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442, \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u043C \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u043C; \u0442\u0430\u043A\u0438\u0445 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 3. \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0432\u044B\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0447\u0438\u0441\u043B\u0430, \u0438\u043C\u0435\u043D\u0430 \u0438\u043B\u0438 \u0434\u0430\u0442\u044B.",
4073
+ `\u0421\u0434\u0430\u0439 \u041F\u041E\u041B\u041D\u042B\u0419 \u0431\u0440\u0438\u0444 \u0447\u0435\u0440\u0435\u0437 ${SUBMIT_SPEC_TOOL_NAME}. TAMsense \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442 \u0435\u0433\u043E \u043B\u0438\u043D\u0442\u0435\u0440\u043E\u043C; \u0435\u0441\u043B\u0438 \u043F\u0440\u0438\u0448\u043B\u0438 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F, \u0431\u0440\u0438\u0444 \u043D\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D: \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0435\u0433\u043E \u0438 \u0441\u043D\u043E\u0432\u0430 \u0432\u044B\u0437\u043E\u0432\u0438 ${SUBMIT_SPEC_TOOL_NAME}. \u041F\u043E\u0441\u043B\u0435 \u0434\u0432\u0443\u0445 \u0440\u0430\u0443\u043D\u0434\u043E\u0432 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u0439 \u0431\u0440\u0438\u0444 \u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u0442\u0441\u044F \u043A\u0430\u043A \u0435\u0441\u0442\u044C.`,
4074
+ "\u0415\u0441\u043B\u0438 \u0443 \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u0431\u0440\u0438\u0444, \u0432 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435 \u043B\u0435\u0436\u0430\u0442 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u0438\u043B\u0438 \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u0435\u0433\u043E \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B: \u043F\u0440\u0438\u043C\u0435\u043D\u0438 \u0438\u0445 \u0438 \u0441\u0434\u0430\u0439 \u043F\u043E\u043B\u043D\u044B\u0439 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u044B\u0439 \u0431\u0440\u0438\u0444. \u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0439 \u0432\u043E\u043F\u0440\u043E\u0441 \u0441 \u043E\u0442\u0432\u0435\u0442\u043E\u043C \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u0440\u0435\u0448\u0435\u043D\u0438\u0435\u043C.",
4075
+ "\u0417\u0430\u0442\u0435\u043C \u043E\u0442\u0432\u0435\u0442\u044C \u043A\u043E\u0440\u043E\u0442\u043A\u0438\u043C \u043F\u0440\u043E\u0441\u0442\u044B\u043C \u0438\u0442\u043E\u0433\u043E\u043C: \u0447\u0442\u043E \u043F\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043B\u044F\u0435\u0442 \u0441\u043E\u0431\u043E\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0435\u0441\u043B\u0438 \u043E\u043D\u0438 \u0435\u0441\u0442\u044C. \u041D\u0435 \u0432\u0441\u0442\u0430\u0432\u043B\u044F\u0439 \u0431\u0440\u0438\u0444: \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0435\u0433\u043E \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442."
4076
+ ],
4077
+ briefStyleRule: "\u041F\u0438\u0448\u0438 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u0431\u0440\u0438\u0444, \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0431\u0440\u0438\u0444 \u0438 \u0441\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 \u043D\u0430 \u044F\u0437\u044B\u043A\u0435, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u043E\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0430. \u041E\u0442\u0432\u0435\u0442\u044B \u043F\u0440\u043E\u0441\u0442\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C, \u0431\u0435\u0437 Markdown. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0435 \u0438\u043C\u0435\u043D\u0430, \u0447\u0438\u0441\u043B\u0430 \u0438 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0438; \u0435\u0441\u043B\u0438 \u0447\u0442\u043E-\u0442\u043E \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0442\u0430\u043A \u0438 \u0441\u043A\u0430\u0436\u0438. \u0411\u0435\u0437 \u0434\u043B\u0438\u043D\u043D\u043E\u0433\u043E \u0438 \u0441\u0440\u0435\u0434\u043D\u0435\u0433\u043E \u0442\u0438\u0440\u0435; \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0437\u0430\u043F\u044F\u0442\u044B\u0435, \u0434\u0432\u043E\u0435\u0442\u043E\u0447\u0438\u044F \u0438\u043B\u0438 \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u0444\u0438\u0441.",
4078
+ specStyleRule: "\u041F\u0438\u0448\u0438 \u0431\u0440\u0438\u0444, \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u0422\u0417 \u0438 \u0441\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442 \u043D\u0430 \u044F\u0437\u044B\u043A\u0435, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u043E\u043C \u043D\u0430\u043F\u0438\u0441\u0430\u043D\u0430 \u0437\u0430\u0434\u0430\u0447\u0430. \u041E\u0442\u0432\u0435\u0442\u044B \u043F\u0440\u043E\u0441\u0442\u044B\u043C \u0442\u0435\u043A\u0441\u0442\u043E\u043C, \u0431\u0435\u0437 Markdown. \u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u043D\u0430\u0441\u0442\u043E\u044F\u0449\u0438\u0435 \u043F\u0443\u0442\u0438 \u0444\u0430\u0439\u043B\u043E\u0432, \u0438\u043C\u0435\u043D\u0430 \u0444\u0443\u043D\u043A\u0446\u0438\u0439 \u0438 \u0442\u0430\u0431\u043B\u0438\u0446; \u0435\u0441\u043B\u0438 \u0447\u0442\u043E-\u0442\u043E \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C, \u0442\u0430\u043A \u0438 \u0441\u043A\u0430\u0436\u0438. \u0411\u0435\u0437 \u0434\u043B\u0438\u043D\u043D\u043E\u0433\u043E \u0438 \u0441\u0440\u0435\u0434\u043D\u0435\u0433\u043E \u0442\u0438\u0440\u0435; \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \u0437\u0430\u043F\u044F\u0442\u044B\u0435, \u0434\u0432\u043E\u0435\u0442\u043E\u0447\u0438\u044F \u0438\u043B\u0438 \u043E\u0431\u044B\u0447\u043D\u044B\u0439 \u0434\u0435\u0444\u0438\u0441.",
4079
+ approvedSpec: (text2) => `\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D\u043D\u043E\u0435 \u0422\u0417:
4080
+ ${text2}`,
4081
+ approvedBrief: (text2) => `\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043D\u043D\u044B\u0439 \u0431\u0440\u0438\u0444:
4082
+ ${text2}`,
4083
+ projectContext: (context) => `\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043E\u0442 \u043A\u043E\u043C\u0430\u043D\u0434\u044B (\u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u043C\u0443 \u043A\u0430\u043A \u043F\u0440\u0430\u0432\u0438\u043B\u0430\u043C \u0441\u0430\u043C\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430):
4084
+ ${context}`,
4085
+ repositoryLine: (line) => `${line.folder} (${line.fullName}) \u043D\u0430 \u043A\u043E\u043C\u043C\u0438\u0442\u0435 ${line.commit}, \u0431\u0430\u0437\u043E\u0432\u0430\u044F \u0432\u0435\u0442\u043A\u0430 ${line.base}${line.pullRequestUrl ? `, PR ${line.pullRequestUrl}` : ""}`,
4086
+ repositories: (list) => `\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u043F\u0430\u043F\u043A\u0435, \u043A\u0430\u0436\u0434\u044B\u0439 \u0432 \u0441\u0432\u043E\u0435\u0439 \u043F\u043E\u0434\u043F\u0430\u043F\u043A\u0435:
4087
+ ${list}`,
4088
+ repositoriesOnBranch: (isCheck, branch, list) => `\u0420\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043B\u0435\u0436\u0430\u0442 \u0432 \u0442\u0435\u043A\u0443\u0449\u0435\u0439 \u043F\u0430\u043F\u043A\u0435, \u043A\u0430\u0436\u0434\u044B\u0439 \u0432 \u0441\u0432\u043E\u0435\u0439 \u043F\u043E\u0434\u043F\u0430\u043F\u043A\u0435, ${isCheck ? "\u043D\u0430 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u043C \u043A\u043E\u043C\u043C\u0438\u0442\u0435 \u0432\u0435\u0442\u043A\u0438" : "\u0432 \u0432\u0435\u0442\u043A\u0435"} ${branch}:
4089
+ ${list}`,
4090
+ stepLine: (name, helper) => `\u042D\u0442\u043E\u0442 \u0448\u0430\u0433 \u0434\u043E\u0441\u043A\u0438 \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \xAB${name}\xBB${helper ? `, \u0438 \u0442\u044B \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0448\u044C \u0432 \u0440\u043E\u043B\u0438 \xAB${helper}\xBB` : ""}.`,
4091
+ howToWork: (list) => `\u041A\u0430\u043A \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C:
4092
+ ${list}`,
4093
+ teamMessages: (lines) => `\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u0434\u043E \u044D\u0442\u043E\u0433\u043E \u0437\u0430\u043F\u0443\u0441\u043A\u0430:
4094
+ ${lines}`,
4095
+ noteFromPreviousStep: (note) => `\u0417\u0430\u043F\u0438\u0441\u043A\u0430 \u043E\u0442 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E \u0448\u0430\u0433\u0430: ${note}`,
4096
+ noteFromAnotherStep: (note) => `\u0417\u0430\u043F\u0438\u0441\u043A\u0430 \u043E\u0442 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u0448\u0430\u0433\u0430: ${note}`,
4097
+ reviewComments: (review) => `\u041A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0443\u0436\u043D\u043E \u043E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C:
4098
+ ${review}`,
4099
+ notesToAddress: (review) => `\u0417\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0443\u0436\u043D\u043E \u043E\u0442\u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C:
4100
+ ${review}`,
4101
+ specBrief: (brief) => `\u0411\u0440\u0438\u0444 \u0438\u0437 \u044D\u0442\u0430\u043F\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u044F (\u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u043D\u0438\u0435 \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u043D\u0430 \u0447\u0442\u043E \u043E\u043F\u0438\u0440\u0430\u0435\u0448\u044C\u0441\u044F):
4102
+ ${brief}`,
4103
+ currentSpec: (spec) => `\u0422\u0435\u043A\u0443\u0449\u0435\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u043E\u0435 \u0422\u0417 (\u0441\u0434\u0430\u0439 \u043F\u043E\u043B\u043D\u0443\u044E \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E):
4104
+ ${spec}`,
4105
+ researchBrief: (brief) => `\u0418\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u0431\u0440\u0438\u0444 \u0438\u0437 \u044D\u0442\u0430\u043F\u0430 \u0443\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u044F (\u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u043D\u0430 \u0447\u0442\u043E \u043E\u043F\u0438\u0440\u0430\u0435\u0448\u044C\u0441\u044F):
4106
+ ${brief}`,
4107
+ currentBrief: (brief) => `\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0439 \u0431\u0440\u0438\u0444 (\u0441\u0434\u0430\u0439 \u043F\u043E\u043B\u043D\u0443\u044E \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D\u043D\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E):
4108
+ ${brief}`,
4109
+ peopleIntro: `\u041B\u044E\u0434\u0438 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u043A\u043E\u0442\u043E\u0440\u044B\u0445 \u043C\u043E\u0436\u043D\u043E \u0441\u043F\u0440\u043E\u0441\u0438\u0442\u044C \u0447\u0435\u0440\u0435\u0437 ${ASK_PERSON_TOOL_NAME} (id: \u0438\u043C\u044F, \u043E\u0442\u043D\u043E\u0448\u0435\u043D\u0438\u0435 \u043A \u0437\u0430\u0434\u0430\u0447\u0435, \u0434\u043E\u0441\u0442\u0443\u043F, \u0437\u0430\u0442\u0435\u043C \u0438\u0445 \u0440\u043E\u043B\u0438 \u0432 \u043A\u043E\u043C\u0430\u043D\u0434\u0435 \u0438 \u0437\u0430 \u0447\u0442\u043E \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u043A\u0430\u0436\u0434\u0430\u044F):`,
4110
+ peopleRule: "\u0421\u043D\u0430\u0447\u0430\u043B\u0430 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0439 \u0438\u0441\u043F\u043E\u043B\u043D\u0438\u0442\u0435\u043B\u044F. \u0412\u043E\u043F\u0440\u043E\u0441 \u043F\u043E \u0442\u0435\u043C\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439 \u0442\u043E\u043C\u0443, \u0447\u044C\u044F \u0440\u043E\u043B\u044C \u0432 \u043A\u043E\u043C\u0430\u043D\u0434\u0435 \u043A \u043D\u0435\u0439 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442, \u0438 \u0442\u043E\u0433\u0434\u0430 \u0432 why \u043D\u0430\u0437\u043E\u0432\u0438 \u044D\u0442\u0443 \u0440\u043E\u043B\u044C \u0438 \u0437\u0430 \u0447\u0442\u043E \u043E\u043D\u0430 \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \xABDesigner, \u043E\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u043F\u0443\u0441\u0442\u044B\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F\xBB. \u0421\u0442\u0430\u0432\u044C blocking, \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430 \u0434\u0430\u043B\u044C\u0448\u0435 \u0438\u0434\u0442\u0438 \u043D\u0435\u043B\u044C\u0437\u044F: \u0442\u043E\u0433\u0434\u0430 \u0437\u0430\u043F\u0443\u0441\u043A \u0432\u0441\u0442\u0430\u0451\u0442 \u043D\u0430 \u043F\u0430\u0443\u0437\u0443, \u0430 \u043A\u043E\u043C\u043F\u044C\u044E\u0442\u0435\u0440 \u043E\u0441\u0432\u043E\u0431\u043E\u0436\u0434\u0430\u0435\u0442\u0441\u044F \u0434\u043B\u044F \u0434\u0440\u0443\u0433\u0438\u0445 \u0437\u0430\u0434\u0430\u0447. \u0418\u043D\u0430\u0447\u0435 \u0443\u043A\u0430\u0436\u0438 \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435, \u0441 \u043A\u043E\u0442\u043E\u0440\u044B\u043C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0448\u044C.",
4111
+ folderResults: (root, list) => `\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0434\u0430\u0447\u0438: ${root}. \u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0430 \u0441\u0435\u0439\u0447\u0430\u0441, \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0438:
4112
+ ${list}`,
4113
+ folderEmpty: (root) => `\u041F\u0430\u043F\u043A\u0430 \u0437\u0430\u0434\u0430\u0447\u0438: ${root}. \u0423 \u0437\u0430\u0434\u0430\u0447\u0438 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430.`,
4114
+ taskResults: (list) => `\u0423 \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u044D\u0442\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B, \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0438, \u0432 \u043F\u0430\u043F\u043A\u0435 ${RESULTS_FOLDER} \u0440\u044F\u0434\u043E\u043C \u0441 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u043C\u0438. \u041F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u043D\u0430\u0447\u0430\u0442\u044C, \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0442\u043E, \u0447\u0442\u043E \u043A\u0430\u0441\u0430\u0435\u0442\u0441\u044F \u044D\u0442\u043E\u0433\u043E \u0448\u0430\u0433\u0430:
4115
+ ${list}`,
4116
+ handOver: (boards) => `\u0415\u0441\u043B\u0438 \u0447\u0430\u0441\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u044B \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0441\u044F \u043A \u0434\u0440\u0443\u0433\u043E\u0439 \u0434\u043E\u0441\u043A\u0435 \u0438\u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430 \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0435\u0442\u0441\u044F \u043D\u043E\u0432\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0435\u0439, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0439 \u0432\u043E\u043B\u043D\u043E\u0439, \u0441\u043E\u0437\u0434\u0430\u0439 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C ${HAND_OVER_TOOL_NAME} \u043D\u0430 \u043E\u0434\u043D\u043E\u0439 \u0438\u0437 \u0434\u043E\u0441\u043E\u043A: ${boards}. \u041D\u0430\u043F\u0438\u0448\u0438 \u043F\u0440\u043E\u0441\u0442\u043E\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u0435 \u0438 \u0437\u0430\u043F\u0438\u0441\u043A\u0443 \u043E \u0442\u043E\u043C, \u0447\u0442\u043E \u0442\u044B \u043D\u0430\u0448\u0451\u043B \u0438 \u0447\u0442\u043E \u0434\u043E\u043B\u0436\u043D\u0430 \u0441\u0434\u0435\u043B\u0430\u0442\u044C \u043D\u043E\u0432\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430; \u043D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u043F\u0438\u0448\u0438 \u0432 \u0437\u0430\u043F\u0438\u0441\u043A\u0443 \u0438\u043C\u0435\u043D\u0430, \u043F\u043E\u0447\u0442\u0443 \u0438\u043B\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043B\u0438\u0447\u043D\u044B\u0435 \u0434\u0430\u043D\u043D\u044B\u0435 \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u0432. \u0421 wait true \u044D\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u0436\u0434\u0451\u0442 \u043D\u0430 \u0441\u0432\u043E\u0451\u043C \u0448\u0430\u0433\u0435, \u043F\u043E\u043A\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u043D\u0435 \u0431\u0443\u0434\u0435\u0442 \u0441\u0434\u0435\u043B\u0430\u043D\u0430, \u0437\u0430\u0442\u0435\u043C \u0438\u0434\u0451\u0442 \u0434\u0430\u043B\u044C\u0448\u0435, \u0430 \u0442\u044B \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0448\u044C \u0437\u0430\u043F\u0438\u0441\u043A\u0443 \u0441 \u0435\u0451 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C.`,
4117
+ handoffIntro: `\u041A\u043E\u0433\u0434\u0430 \u0437\u0430\u043A\u043E\u043D\u0447\u0438\u0448\u044C, \u043F\u0435\u0440\u0435\u0434\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u0430\u043B\u044C\u0448\u0435 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C ${HANDOFF_TOOL_NAME}. \u041D\u0430\u043F\u0438\u0448\u0438 \u044F\u0441\u043D\u0443\u044E \u0437\u0430\u043F\u0438\u0441\u043A\u0443 \u0434\u043B\u044F \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0433\u043E \u0430\u0433\u0435\u043D\u0442\u0430 \u0438 \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430 \u0437\u0430\u0434\u0430\u0447\u0438: \u0447\u0442\u043E \u0441\u0434\u0435\u043B\u0430\u043D\u043E, \u0447\u0442\u043E \u043F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0438 \u0447\u0442\u043E \u0435\u0449\u0451 \u043E\u0442\u043A\u0440\u044B\u0442\u043E. \u0428\u0430\u0433\u0438, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u043C\u043E\u0436\u0435\u0442 \u043F\u0435\u0440\u0435\u0439\u0442\u0438 \u043E\u0442\u0441\u044E\u0434\u0430:`,
4118
+ handoffNone: "\u0415\u0441\u043B\u0438 \u043D\u0438 \u043E\u0434\u0438\u043D \u043D\u0435 \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442, \u043F\u0440\u043E\u0441\u0442\u043E \u0437\u0430\u043A\u043E\u043D\u0447\u0438, \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0443.",
4119
+ handoffVerdict: `\u0412\u0435\u0440\u0434\u0438\u043A\u0442 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u0441\u0434\u0430\u0439 \u0447\u0435\u0440\u0435\u0437 ${VERDICT_TOOL_NAME}.`,
4120
+ threadOwner: "\u0412\u043B\u0430\u0434\u0435\u043B\u0435\u0446",
4121
+ threadYou: "\u0422\u044B, \u0440\u0430\u043D\u044C\u0448\u0435",
4122
+ thread: (lines) => `\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435 \u043D\u0430 \u0441\u0435\u0439\u0447\u0430\u0441 (\u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u0438 \u0437\u0430\u043C\u0435\u0447\u0430\u043D\u0438\u044F \u0432\u043B\u0430\u0434\u0435\u043B\u044C\u0446\u0430):
4123
+ ${lines}`,
4124
+ specGoals: {
4125
+ write: (taskId, title) => `\u041D\u0430\u043F\u0438\u0448\u0438 \u0422\u0417 \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447\u0438 TAMsense #${taskId}: ${title}`,
4126
+ prepare: (taskId, title) => `\u041F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u044C \u0437\u0430\u0434\u0430\u0447\u0443 TAMsense #${taskId} \u043A \u0422\u0417: ${title}`
4127
+ },
4128
+ briefGoals: {
4129
+ write: (taskId, title) => `\u041D\u0430\u043F\u0438\u0448\u0438 \u0431\u0440\u0438\u0444 \u0434\u043B\u044F \u0437\u0430\u0434\u0430\u0447\u0438 TAMsense #${taskId}: ${title}`,
4130
+ prepare: (taskId, title) => `\u041F\u043E\u0434\u0433\u043E\u0442\u043E\u0432\u044C \u0437\u0430\u0434\u0430\u0447\u0443 TAMsense #${taskId} \u043A \u0431\u0440\u0438\u0444\u0443: ${title}`
4131
+ },
4132
+ followUpGoal: (taskId, title) => `\u0423\u0442\u043E\u0447\u043D\u044F\u044E\u0449\u0438\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043F\u043E \u0437\u0430\u0434\u0430\u0447\u0435 TAMsense #${taskId}: ${title}`,
4133
+ questionsTaken: (ids) => ` \u042D\u0442\u0438 id \u0443\u0436\u0435 \u0437\u0430\u043D\u044F\u0442\u044B \u0440\u0430\u0443\u043D\u0434\u043E\u043C 1: ${ids.join(", ")}.`,
4134
+ specQuestions: {
4135
+ research: [
4136
+ "\u041D\u0430\u0447\u043D\u0438 \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u043D\u0430\u0432\u044B\u043A\u0430 tamsense:task-interview \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C Skill \u0438 \u0441\u043B\u0435\u0434\u0443\u0439 \u0435\u043C\u0443: \u0432 \u043D\u0451\u043C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432, \u043E\u0431\u0445\u043E\u0434 \u043A\u0440\u0430\u0439\u043D\u0438\u0445 \u0441\u043B\u0443\u0447\u0430\u0435\u0432 \u0438 \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u0440\u0430\u0443\u043D\u0434\u043E\u0432. \u0418\u0437\u0443\u0447\u0438, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C: \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u043A\u043E\u0434 \u0432 \u0437\u0434\u0435\u0448\u043D\u0438\u0445 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F\u0445 \u0438 \u0434\u043E\u043A\u0430\u0437\u0430\u0442\u0435\u043B\u044C\u0441\u0442\u0432\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u043E\u0439 \u043D\u0430\u0445\u043E\u0434\u043A\u0438, \u0435\u0441\u043B\u0438 \u043E\u043D\u0430 \u0435\u0441\u0442\u044C. \u0418\u0441\u043A\u0430\u0442\u044C \u0444\u0430\u043A\u0442\u044B \u0442\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430; \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043E \u0437\u0430\u043C\u044B\u0441\u043B\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446.",
4137
+ "\u041F\u0438\u0448\u0438 \u0431\u0440\u0438\u0444 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0431\u043E\u0442\u044B: findings (\u0447\u0442\u043E \u0432\u0430\u0436\u043D\u043E\u0433\u043E \u043A\u043E\u0434 \u0434\u0435\u043B\u0430\u0435\u0442 \u0441\u0435\u0433\u043E\u0434\u043D\u044F, \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u0443\u043D\u043A\u0442 \u0441 owner/repo path:line), dataFacts (\u0447\u0438\u0441\u043B\u0430 \u0438 \u0444\u0430\u043A\u0442\u044B \u0438\u0437 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u043E\u0439 \u043D\u0430\u0445\u043E\u0434\u043A\u0438, \u043A\u0430\u0436\u0434\u044B\u0439 \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u043E\u043C; \u043F\u0443\u0441\u0442\u043E, \u0435\u0441\u043B\u0438 \u0438\u0445 \u043D\u0435\u0442), approach (\u043E\u0442 2 \u0434\u043E 4 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0439), codeMap (\u0444\u0430\u0439\u043B\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u0437\u0430\u0442\u0440\u043E\u043D\u0435\u0442 \u0438\u043B\u0438 \u0434\u043E\u043B\u0436\u043D\u043E \u043F\u0435\u0440\u0435\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C, \u043A\u0430\u0436\u0434\u044B\u0439 \u0441\u043E \u0441\u0432\u043E\u0435\u0439 \u0440\u043E\u043B\u044C\u044E), assumptions (\u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u0440\u0430\u0439\u043D\u0438\u0439 \u0441\u043B\u0443\u0447\u0430\u0439, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u0440\u0435\u0448\u0438\u043B \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439) \u0438 pendingDecisions (\u0440\u0435\u0448\u0435\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E\u0442 \u0440\u0430\u0443\u043D\u0434; \u043F\u0443\u0441\u0442\u043E, \u0435\u0441\u043B\u0438 \u0438\u0445 \u043D\u0435\u0442)."
4138
+ ],
4139
+ handIn: `\u0412\u044B\u0437\u043E\u0432\u0438 ${SUBMIT_QUESTIONS_TOOL_NAME} \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0441 \u0431\u0440\u0438\u0444\u043E\u043C \u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u0430\u043C\u0438 \u0438\u043B\u0438 \u0441 \u043F\u0443\u0441\u0442\u044B\u043C \u0441\u043F\u0438\u0441\u043A\u043E\u043C, \u0435\u0441\u043B\u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043D\u0435 \u043D\u0443\u0436\u043D\u044B. \u0415\u0441\u043B\u0438 \u043E\u043D \u0432\u0435\u0440\u043D\u0451\u0442 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B, \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0438\u0445 \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 \u0441\u043D\u043E\u0432\u0430.`,
4140
+ firstRound: (max) => `\u0417\u0430\u0434\u0430\u0439 \u043E\u0442 0 \u0434\u043E ${max} \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432, \u043B\u0443\u0447\u0448\u0435 2 \u0438\u043B\u0438 3, \u0441\u0430\u043C\u044B\u0435 \u0432\u0430\u0436\u043D\u044B\u0435 \u043F\u0435\u0440\u0432\u044B\u043C\u0438, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0435, \u0447\u0442\u043E \u043F\u0440\u043E\u0445\u043E\u0434\u044F\u0442 \u0442\u0440\u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430\u0432\u044B\u043A\u0430. \u041D\u043E\u043B\u044C \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u044D\u0442\u043E \u0445\u043E\u0440\u043E\u0448\u0438\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442: \u0442\u043E\u0433\u0434\u0430 \u0422\u0417 \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443. \u0417\u0430\u0434\u0430\u0432\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u044B, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0435 \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u0434\u0440\u0443\u0433\u043E\u0433\u043E \u0432\u043E\u043F\u0440\u043E\u0441\u0430 \u044D\u0442\u043E\u0433\u043E \u0440\u0430\u0443\u043D\u0434\u0430; \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0435 \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u043F\u043E\u043B\u043E\u0436\u0438 \u0432 pendingDecisions.`,
4141
+ followUp: (round) => `\u042D\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u044F\u044E\u0449\u0438\u0439 \u0440\u0430\u0443\u043D\u0434 ${round} \u0438\u0437 2, \u0438 \u043F\u043E\u0441\u043B\u0435 \u043D\u0435\u0433\u043E \u0440\u0430\u0443\u043D\u0434\u043E\u0432 \u043D\u0435\u0442. \u0421\u043B\u0435\u0434\u0443\u0439 \u043D\u0430\u0432\u044B\u043A\u0443 tamsense:task-interview (\u0437\u0430\u0433\u0440\u0443\u0437\u0438 \u0435\u0433\u043E \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u043C Skill, \u0435\u0441\u043B\u0438 \u044D\u0442\u043E\u0442 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0435\u0433\u043E \u0435\u0449\u0451 \u043D\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043B). \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u043E\u0442\u0432\u0435\u0442\u044B \u0432 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435 \u043D\u0438\u0436\u0435. \u041D\u0435 \u0438\u0437\u0443\u0447\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E \u0442\u043E, \u0447\u0442\u043E \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u0442\u0432\u043E\u0439 \u0431\u0440\u0438\u0444; \u043F\u0440\u043E\u0432\u0435\u0440\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u043E, \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438 \u043E\u0442\u0432\u0435\u0442\u044B.`,
4142
+ followUpAsk: (max, taken) => `\u0417\u0430\u0434\u0430\u0439 \u0435\u0449\u0451 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${max} \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u0442\u0432\u0435\u0442 \u0440\u0430\u0441\u043F\u043B\u044B\u0432\u0447\u0430\u0442\u044B\u0439 (\xAB\u043D\u0435 \u0443\u0432\u0435\u0440\u0435\u043D\xBB, \xAB\u0437\u0430\u0432\u0438\u0441\u0438\u0442\xBB, \xAB\u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C\xBB), \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043E\u0442\u0432\u0435\u0442\u0443 \u0438\u043B\u0438 \u043A\u043E\u0434\u0443 \u0438\u043B\u0438 \u043E\u0442\u043A\u0440\u044B\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0438\u0437 pendingDecisions, \u043A\u043E\u0442\u043E\u0440\u043E\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 \u043D\u0430\u0432\u044B\u043A\u0430 tamsense task-interview. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u0441\u0434\u0430\u0432\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432: \u0432\u0441\u0451, \u0447\u0442\u043E \u0435\u0449\u0451 \u043E\u0442\u043A\u0440\u044B\u0442\u043E, \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435\u043C.${taken}`,
4143
+ update: "\u041E\u0431\u043D\u043E\u0432\u0438 \u0431\u0440\u0438\u0444 \u0442\u0435\u043C, \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438 \u043E\u0442\u0432\u0435\u0442\u044B, \u0438 \u043E\u0441\u0442\u0430\u0432\u044C pendingDecisions \u043F\u0443\u0441\u0442\u044B\u043C."
4144
+ },
4145
+ briefQuestions: {
4146
+ research: [
4147
+ "\u0418\u0437\u0443\u0447\u0438, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C: \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439, \u0447\u0442\u043E \u0434\u0430\u044E\u0442 \u0437\u0430\u0434\u0430\u0447\u0430, \u0437\u043D\u0430\u043D\u0438\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0444\u0430\u0439\u043B\u044B \u0432 \u044D\u0442\u043E\u0439 \u043F\u0430\u043F\u043A\u0435 \u0438 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D\u043D\u044B\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044B. \u0418\u0441\u043A\u0430\u0442\u044C \u0444\u0430\u043A\u0442\u044B \u0442\u0432\u043E\u044F \u0440\u0430\u0431\u043E\u0442\u0430; \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043E \u0437\u0430\u043C\u044B\u0441\u043B\u0435 \u043F\u0440\u0438\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446.",
4148
+ "\u041F\u0438\u0448\u0438 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u0431\u0440\u0438\u0444 \u043F\u043E \u0445\u043E\u0434\u0443 \u0440\u0430\u0431\u043E\u0442\u044B: findings (\u0447\u0442\u043E \u0432\u0430\u0436\u043D\u043E\u0433\u043E \u0433\u043E\u0432\u043E\u0440\u044F\u0442 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0438, \u043A\u0430\u0436\u0434\u044B\u0439 \u043F\u0443\u043D\u043A\u0442 \u0441 where: \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A), dataFacts (\u0447\u0438\u0441\u043B\u0430 \u0438 \u0444\u0430\u043A\u0442\u044B, \u043A\u0430\u0436\u0434\u044B\u0439 \u0441\u043E \u0441\u0432\u043E\u0438\u043C \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u043E\u043C; \u043F\u0443\u0441\u0442\u043E, \u0435\u0441\u043B\u0438 \u0438\u0445 \u043D\u0435\u0442), approach (\u043E\u0442 2 \u0434\u043E 4 \u043F\u0440\u0435\u0434\u043B\u043E\u0436\u0435\u043D\u0438\u0439), codeMap (\u0432\u0441\u0435\u0433\u0434\u0430 \u043F\u0443\u0441\u0442\u043E: \u0443 \u044D\u0442\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u043D\u0435\u0442 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0435\u0432), assumptions (\u043A\u0430\u0436\u0434\u044B\u0439 \u043A\u0440\u0430\u0439\u043D\u0438\u0439 \u0441\u043B\u0443\u0447\u0430\u0439, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0442\u044B \u0440\u0435\u0448\u0438\u043B \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435\u043C \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E, \u0441 \u043F\u0440\u0438\u0447\u0438\u043D\u043E\u0439) \u0438 pendingDecisions (\u0440\u0435\u0448\u0435\u043D\u0438\u044F, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0437\u0430\u0432\u0438\u0441\u044F\u0442 \u043E\u0442 \u043E\u0442\u0432\u0435\u0442\u0430 \u043D\u0430 \u044D\u0442\u043E\u0442 \u0440\u0430\u0443\u043D\u0434; \u043F\u0443\u0441\u0442\u043E, \u0435\u0441\u043B\u0438 \u0438\u0445 \u043D\u0435\u0442)."
4149
+ ],
4150
+ handIn: `\u0412\u044B\u0437\u043E\u0432\u0438 ${SUBMIT_QUESTIONS_TOOL_NAME} \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u0441 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u043C \u0431\u0440\u0438\u0444\u043E\u043C \u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u0430\u043C\u0438 \u0438\u043B\u0438 \u0441 \u043F\u0443\u0441\u0442\u044B\u043C \u0441\u043F\u0438\u0441\u043A\u043E\u043C, \u0435\u0441\u043B\u0438 \u0432\u043E\u043F\u0440\u043E\u0441\u044B \u043D\u0435 \u043D\u0443\u0436\u043D\u044B. \u0415\u0441\u043B\u0438 \u043E\u043D \u0432\u0435\u0440\u043D\u0451\u0442 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B, \u0438\u0441\u043F\u0440\u0430\u0432\u044C \u0438\u0445 \u0438 \u0432\u044B\u0437\u043E\u0432\u0438 \u0441\u043D\u043E\u0432\u0430.`,
4151
+ firstRound: (max) => `\u0417\u0430\u0434\u0430\u0439 \u043E\u0442 0 \u0434\u043E ${max} \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432, \u043B\u0443\u0447\u0448\u0435 2 \u0438\u043B\u0438 3, \u0441\u0430\u043C\u044B\u0435 \u0432\u0430\u0436\u043D\u044B\u0435 \u043F\u0435\u0440\u0432\u044B\u043C\u0438, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0435, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043D\u0435 \u0440\u0435\u0448\u0438\u0442\u044C \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u043D\u0438\u0435\u043C, \u043E\u0442\u0432\u0435\u0442\u044B \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043C\u0435\u043D\u044F\u044E\u0442 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0438 \u0433\u0434\u0435 \u043E\u0448\u0438\u0431\u043A\u0430 \u0432 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0438 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0434\u043E\u0440\u043E\u0433\u043E \u0441\u0442\u043E\u0438\u0442. \u041D\u043E\u043B\u044C \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432 \u044D\u0442\u043E \u0445\u043E\u0440\u043E\u0448\u0438\u0439 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442: \u0442\u043E\u0433\u0434\u0430 \u0431\u0440\u0438\u0444 \u043F\u0438\u0448\u0435\u0442\u0441\u044F \u0441\u0440\u0430\u0437\u0443.`,
4152
+ followUp: (round) => `\u042D\u0442\u043E \u0443\u0442\u043E\u0447\u043D\u044F\u044E\u0449\u0438\u0439 \u0440\u0430\u0443\u043D\u0434 ${round} \u0438\u0437 2, \u0438 \u043F\u043E\u0441\u043B\u0435 \u043D\u0435\u0433\u043E \u0440\u0430\u0443\u043D\u0434\u043E\u0432 \u043D\u0435\u0442. \u041F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u043E\u0442\u0432\u0435\u0442\u044B \u0432 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u0435 \u043D\u0438\u0436\u0435; \u043D\u0435 \u0438\u0437\u0443\u0447\u0430\u0439 \u0437\u0430\u043D\u043E\u0432\u043E \u0442\u043E, \u0447\u0442\u043E \u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442 \u0442\u0432\u043E\u0439 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u0431\u0440\u0438\u0444.`,
4153
+ followUpAsk: (max, taken) => `\u0417\u0430\u0434\u0430\u0439 \u0435\u0449\u0451 \u043D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 ${max} \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432, \u0438 \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u043E\u0442\u0432\u0435\u0442 \u0440\u0430\u0441\u043F\u043B\u044B\u0432\u0447\u0430\u0442\u044B\u0439, \u043F\u0440\u043E\u0442\u0438\u0432\u043E\u0440\u0435\u0447\u0438\u0442 \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043E\u0442\u0432\u0435\u0442\u0443 \u0438\u043B\u0438 \u0444\u0430\u043A\u0442\u0430\u043C \u0438\u043B\u0438 \u043E\u0442\u043A\u0440\u044B\u043B \u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0438\u0437 pendingDecisions. \u0418\u043D\u0430\u0447\u0435 \u043D\u0435 \u0441\u0434\u0430\u0432\u0430\u0439 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432: \u0432\u0441\u0451, \u0447\u0442\u043E \u0435\u0449\u0451 \u043E\u0442\u043A\u0440\u044B\u0442\u043E, \u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0441\u044F \u0434\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u0435\u043C.${taken}`,
4154
+ update: "\u041E\u0431\u043D\u043E\u0432\u0438 \u0438\u0441\u0441\u043B\u0435\u0434\u043E\u0432\u0430\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u0431\u0440\u0438\u0444 \u0442\u0435\u043C, \u0447\u0442\u043E \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438 \u043E\u0442\u0432\u0435\u0442\u044B, \u0438 \u043E\u0441\u0442\u0430\u0432\u044C pendingDecisions \u043F\u0443\u0441\u0442\u044B\u043C."
4155
+ }
4156
+ };
4157
+
4158
+ // src/specText.ts
4159
+ var LABELS = {
4160
+ en: {
4161
+ goal: "Goal",
4162
+ summary: "Summary",
4163
+ context: "Context",
4164
+ resultContains: "The result must contain:",
4165
+ acceptance: "Acceptance criteria:",
4166
+ constraints: "Constraints:",
4167
+ outOfScopeResult: "Out of scope (do not make):",
4168
+ sources: "Sources to use:",
4169
+ approvedBy: "Approved by",
4170
+ decisions: "Decisions:",
4171
+ assumptions: "Assumptions:",
4172
+ openQuestions: "Open questions (not answered yet):",
4173
+ inScope: "In scope:",
4174
+ outOfScopeCode: "Out of scope (do not build):",
4175
+ codeMap: "Code map:",
4176
+ changes: "Changes, in order:",
4177
+ testPlan: "Test plan:",
4178
+ produces: "Produces",
4179
+ consumes: "Consumes",
4180
+ makesTrue: "Makes true",
4181
+ codeUse: { reuse: "reuse", change: "change", keep: "do not touch" },
4182
+ agentDecision: "agent's call",
4183
+ ownerDecision: "owner",
4184
+ runIn: (repo) => `Run in ${repo ?? "the repository"}`,
4185
+ passing: "Passing",
4186
+ newTest: (seam) => `New test at ${seam}`,
4187
+ covers: "covers",
4188
+ byHand: (page) => `By hand${page ? ` on ${page}` : ""}`,
4189
+ risks: (level) => `Risks${level ? ` (risk level ${level})` : ""}:`,
4190
+ mitigation: "Mitigation",
4191
+ riskLevel: "Risk level"
4192
+ },
4193
+ ru: {
4194
+ goal: "\u0426\u0435\u043B\u044C",
4195
+ summary: "\u041A\u0440\u0430\u0442\u043A\u043E",
4196
+ context: "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442",
4197
+ resultContains: "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0442\u044C:",
4198
+ acceptance: "\u041A\u0440\u0438\u0442\u0435\u0440\u0438\u0438 \u043F\u0440\u0438\u0451\u043C\u043A\u0438:",
4199
+ constraints: "\u041E\u0433\u0440\u0430\u043D\u0438\u0447\u0435\u043D\u0438\u044F:",
4200
+ outOfScopeResult: "\u0412\u043D\u0435 \u0440\u0430\u043C\u043E\u043A (\u043D\u0435 \u0434\u0435\u043B\u0430\u0442\u044C):",
4201
+ sources: "\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0438:",
4202
+ approvedBy: "\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u0435\u0442",
4203
+ decisions: "\u0420\u0435\u0448\u0435\u043D\u0438\u044F:",
4204
+ assumptions: "\u0414\u043E\u043F\u0443\u0449\u0435\u043D\u0438\u044F:",
4205
+ openQuestions: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0435 \u0432\u043E\u043F\u0440\u043E\u0441\u044B (\u043F\u043E\u043A\u0430 \u0431\u0435\u0437 \u043E\u0442\u0432\u0435\u0442\u0430):",
4206
+ inScope: "\u0412 \u0440\u0430\u043C\u043A\u0430\u0445 \u0437\u0430\u0434\u0430\u0447\u0438:",
4207
+ outOfScopeCode: "\u0412\u043D\u0435 \u0440\u0430\u043C\u043E\u043A (\u043D\u0435 \u0434\u0435\u043B\u0430\u0442\u044C):",
4208
+ codeMap: "\u041A\u0430\u0440\u0442\u0430 \u043A\u043E\u0434\u0430:",
4209
+ changes: "\u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u043F\u043E \u043F\u043E\u0440\u044F\u0434\u043A\u0443:",
4210
+ testPlan: "\u041F\u043B\u0430\u043D \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438:",
4211
+ produces: "\u0414\u0430\u0451\u0442",
4212
+ consumes: "\u0418\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442",
4213
+ makesTrue: "\u0412\u044B\u043F\u043E\u043B\u043D\u044F\u0435\u0442",
4214
+ codeUse: { reuse: "\u0438\u0441\u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u044C", change: "\u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C", keep: "\u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0442\u044C" },
4215
+ agentDecision: "\u0440\u0435\u0448\u0435\u043D\u0438\u0435 \u0430\u0433\u0435\u043D\u0442\u0430",
4216
+ ownerDecision: "\u0432\u043B\u0430\u0434\u0435\u043B\u0435\u0446",
4217
+ runIn: (repo) => `\u0417\u0430\u043F\u0443\u0441\u0442\u0438 \u0432 ${repo ?? "\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438"}`,
4218
+ passing: "\u0423\u0441\u043F\u0435\u0445",
4219
+ newTest: (seam) => `\u041D\u043E\u0432\u044B\u0439 \u0442\u0435\u0441\u0442 \u0432 ${seam}`,
4220
+ covers: "\u043F\u043E\u043A\u0440\u044B\u0432\u0430\u0435\u0442",
4221
+ byHand: (page) => `\u0412\u0440\u0443\u0447\u043D\u0443\u044E${page ? ` \u043D\u0430 ${page}` : ""}`,
4222
+ risks: (level) => `\u0420\u0438\u0441\u043A\u0438${level ? ` (\u0443\u0440\u043E\u0432\u0435\u043D\u044C \u0440\u0438\u0441\u043A\u0430 ${level})` : ""}:`,
4223
+ mitigation: "\u041A\u0430\u043A \u0441\u043D\u0438\u0437\u0438\u0442\u044C",
4224
+ riskLevel: "\u0423\u0440\u043E\u0432\u0435\u043D\u044C \u0440\u0438\u0441\u043A\u0430"
4225
+ }
4226
+ };
4227
+ function isRecord2(value) {
4228
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4229
+ }
4230
+ function text(value) {
4231
+ return typeof value === "string" ? value.trim() : "";
4232
+ }
4233
+ function texts(value) {
4234
+ return Array.isArray(value) ? value.map(text).filter(Boolean) : [];
4235
+ }
4236
+ function records(value) {
4237
+ return Array.isArray(value) ? value.filter(isRecord2) : [];
4238
+ }
4239
+ function section(title, lines) {
4240
+ return lines.length > 0 ? `${title}
4241
+ ${lines.join("\n")}` : null;
4242
+ }
4243
+ function criterionLine(value) {
4244
+ if (typeof value === "string") {
4245
+ return value.trim() ? `- ${value.trim()}` : null;
4246
+ }
4247
+ if (!isRecord2(value) || !text(value.text)) {
4248
+ return null;
4249
+ }
4250
+ const kind = ["main", "edge", "keep"].includes(value.kind) ? `(${value.kind})` : "";
4251
+ const head = [text(value.id), kind].filter(Boolean).join(" ");
4252
+ return `- ${head ? `${head}: ` : ""}${text(value.text)}`;
4253
+ }
4254
+ function assumptionLine(value) {
4255
+ if (typeof value === "string") {
4256
+ return value.trim() ? `- ${value.trim()}` : null;
4257
+ }
4258
+ if (!isRecord2(value) || !text(value.text)) {
4259
+ return null;
4260
+ }
4261
+ return `- ${text(value.text)}${text(value.reason) ? ` (${text(value.reason)})` : ""}`;
4262
+ }
4263
+ function changeLines(change, index, labels) {
4264
+ const action = ["create", "modify", "delete"].includes(change.action) ? ` (${change.action})` : "";
4265
+ const [produces, consumes, acceptance] = [texts(change.produces), texts(change.consumes), texts(change.acceptance)];
4266
+ return [
4267
+ `${index + 1}. ${text(change.repo) ? `${text(change.repo)} ` : ""}${text(change.file)}${action}: ${text(change.description)}`,
4268
+ ...produces.length > 0 ? [` ${labels.produces}: ${produces.join("; ")}`] : [],
4269
+ ...consumes.length > 0 ? [` ${labels.consumes}: ${consumes.join("; ")}`] : [],
4270
+ ...acceptance.length > 0 ? [` ${labels.makesTrue}: ${acceptance.join(", ")}`] : []
4271
+ ];
4272
+ }
4273
+ var RISK_LEVELS = ["low", "medium", "high"];
4274
+ function present(lines) {
4275
+ return lines.filter((line) => line !== null);
4276
+ }
4277
+ function decisionLines(spec, labels) {
4278
+ return records(spec.decisions).filter((item) => text(item.question) && text(item.answer)).map((item) => `- ${text(item.question)} \u2192 ${text(item.answer)} (${item.by === "agent" ? labels.agentDecision : labels.ownerDecision})`);
4279
+ }
4280
+ function briefPromptText(brief, labels) {
4281
+ return [
4282
+ text(brief.goal) && `${labels.goal}: ${text(brief.goal)}`,
4283
+ text(brief.context) && `${labels.summary}: ${text(brief.context)}`,
4284
+ section(labels.resultContains, texts(brief.contains).map((item) => `- ${item}`)),
4285
+ section(labels.acceptance, present(Array.isArray(brief.acceptance) ? brief.acceptance.map(criterionLine) : [])),
4286
+ section(labels.constraints, texts(brief.constraints).map((item) => `- ${item}`)),
4287
+ section(labels.outOfScopeResult, texts(brief.outOfScope).map((item) => `- ${item}`)),
4288
+ section(labels.sources, records(brief.sources).filter((item) => text(item.source)).map((item) => `- ${text(item.source)}${text(item.use) ? `: ${text(item.use)}` : ""}`)),
4289
+ text(brief.approver) && `${labels.approvedBy}: ${text(brief.approver)}`,
4290
+ section(labels.decisions, decisionLines(brief, labels)),
4291
+ section(labels.assumptions, present(Array.isArray(brief.assumptions) ? brief.assumptions.map(assumptionLine) : [])),
4292
+ section(labels.openQuestions, records(brief.openQuestions).filter((item) => text(item.question)).map((item) => `- ${text(item.question)}`))
4293
+ ].filter(Boolean).join("\n\n");
4294
+ }
4295
+ function specPromptText(spec, language = "en") {
4296
+ if (!isRecord2(spec)) {
4297
+ return null;
4298
+ }
4299
+ const labels = LABELS[language];
4300
+ if (spec.format === "brief") {
4301
+ return briefPromptText(spec, labels);
4302
+ }
4303
+ const structured = Array.isArray(spec.outOfScope) || isRecord2(spec.testPlan);
4304
+ const plan = isRecord2(spec.testPlan) ? spec.testPlan : {};
4305
+ const risks = records(spec.risks).filter((item) => text(item.risk));
4306
+ const riskLevel = RISK_LEVELS.includes(spec.riskLevel) ? spec.riskLevel : null;
4307
+ return [
4308
+ text(spec.goal) && `${labels.goal}: ${text(spec.goal)}`,
4309
+ text(spec.context) && `${structured ? labels.summary : labels.context}: ${text(spec.context)}`,
4310
+ section(labels.inScope, texts(spec.inScope).map((item) => `- ${item}`)),
4311
+ section(labels.outOfScopeCode, texts(spec.outOfScope).map((item) => `- ${item}`)),
4312
+ section(labels.decisions, decisionLines(spec, labels)),
4313
+ section(labels.codeMap, records(spec.codeMap).filter((item) => text(item.file)).map((item) => `- ${text(item.repo) ? `${text(item.repo)} ` : ""}${text(item.file)}: ${text(item.role)}${labels.codeUse[item.use] ? ` [${labels.codeUse[item.use]}]` : ""}`)),
4314
+ section(labels.changes, records(spec.changes).filter((item) => text(item.file)).flatMap((change, index) => changeLines(change, index, labels))),
4315
+ section(labels.acceptance, present(Array.isArray(spec.acceptance) ? spec.acceptance.map(criterionLine) : [])),
4316
+ section(labels.testPlan, [
4317
+ ...records(plan.automated).filter((item) => text(item.command)).map((item) => `- ${labels.runIn(text(item.repo) || null)}: ${text(item.command)}${text(item.expect) ? `. ${labels.passing}: ${text(item.expect)}` : ""}`),
4318
+ ...records(plan.newTests).filter((item) => text(item.seam)).map((item) => `- ${labels.newTest(text(item.seam))}${texts(item.covers).length > 0 ? `, ${labels.covers} ${texts(item.covers).join(", ")}` : ""}`),
4319
+ ...records(plan.manual).filter((item) => text(item.steps)).map((item) => `- ${labels.byHand(text(item.page) || null)}: ${text(item.steps)}`)
4320
+ ]),
4321
+ risks.length > 0 ? section(labels.risks(riskLevel), risks.map((item) => `- ${text(item.risk)}${text(item.mitigation) ? `. ${labels.mitigation}: ${text(item.mitigation)}` : ""}`)) : riskLevel && `${labels.riskLevel}: ${riskLevel}`,
4322
+ section(labels.assumptions, present(Array.isArray(spec.assumptions) ? spec.assumptions.map(assumptionLine) : [])),
4323
+ section(labels.openQuestions, records(spec.openQuestions).filter((item) => text(item.question)).map((item) => `- ${text(item.question)}`))
4324
+ ].filter(Boolean).join("\n\n");
4325
+ }
4326
+
4327
+ // src/prompt.ts
4328
+ var TEXTS = { en: EN, ru: RU };
4329
+ function jobLanguage(job) {
4330
+ return job.language === "ru" ? "ru" : "en";
4331
+ }
4332
+ function composeProjectName(job) {
4333
+ return ["tamsense", ...stepFolder(job)].join("-");
4334
+ }
4335
+ function localAppRule(t, job, isCheck, canAsk) {
4336
+ const project = composeProjectName(job);
4337
+ return [
4338
+ t.localApp.start,
4339
+ t.localApp.find,
4340
+ canAsk ? t.localApp.ask : null,
4341
+ t.localApp.localSettings,
4342
+ t.localApp.addresses,
4343
+ t.localApp.containers(project),
4344
+ t.localApp.cannotStart(isCheck ? t.localApp.cannotRunCheck : t.localApp.cannotRunReport)
4345
+ ].filter(Boolean).join(" ");
4346
+ }
4347
+ function isReadOnlyCode(stage) {
4348
+ return stage.kind === "code" && stage.role?.access === "read";
4349
+ }
4350
+ var SPEC_WORK_MISSING = "TAMsense sent a spec step without its round and brief.";
4351
+ function specText(t, spec, language) {
4352
+ const text2 = specPromptText(spec, language);
4353
+ return text2 ? t.approvedSpec(text2) : null;
4354
+ }
4355
+ function briefText(t, spec, language) {
4356
+ const text2 = specPromptText(spec, language);
4357
+ return text2 ? t.approvedBrief(text2) : null;
4358
+ }
4359
+ function contextText(t, context) {
4360
+ return context.trim() ? t.projectContext(context.trim()) : null;
4361
+ }
4362
+ function safetyText(job) {
4363
+ return job.safetyRules?.trim() || null;
4364
+ }
4365
+ function bulletList(lines) {
4366
+ return lines.map((line) => `- ${line}`).join("\n");
4367
+ }
4368
+ function repositoryLines(t, job, workspace) {
4369
+ const sourceOf2 = new Map(job.repositories.map((repository) => [repository.fullName, repository]));
4370
+ return workspace.repositories.map((repository) => {
4371
+ const source = sourceOf2.get(repository.fullName);
4372
+ return t.repositoryLine({
4373
+ folder: repository.folder,
4374
+ fullName: repository.fullName,
4375
+ commit: repository.baseSha.slice(0, 7),
4376
+ base: repository.local ? repository.local.baseBranch : `origin/${source?.defaultBranch ?? "main"}`,
4377
+ pullRequestUrl: source?.pullRequestUrl ?? null
4378
+ });
4379
+ });
4380
+ }
4381
+ function codeRules(t, job) {
4382
+ return isLocalJob(job) ? [...t.codeRules.slice(0, -1), t.localCommitRule] : t.codeRules;
4383
+ }
4384
+ function teamMessagesText(t, messages) {
4385
+ return messages.length > 0 ? t.teamMessages(messages.map((message) => `${message.authorName}: ${message.text}`).join("\n")) : null;
4386
+ }
4387
+ function peopleText(t, people, language) {
4388
+ if (people.length === 0) {
4389
+ return null;
4390
+ }
4391
+ return [t.peopleIntro, bulletList(people.map((person) => personLine(person, language))), t.peopleRule].join("\n");
4392
+ }
4393
+ function canProposeKnowledge(job) {
4394
+ return job.knowledge !== void 0 && (job.people ?? []).length > 0 && job.stage.kind !== "interview" && job.stage.kind !== "spec";
4395
+ }
4396
+ function folderText(t, root, results) {
4397
+ const lines = results ?? [];
4398
+ return lines.length > 0 ? t.folderResults(root, bulletList(lines)) : t.folderEmpty(root);
4399
+ }
4400
+ function taskResultsText(t, results) {
4401
+ const lines = results ?? [];
4402
+ return lines.length > 0 ? t.taskResults(bulletList(lines)) : null;
4403
+ }
4404
+ function handOverText(t, boards) {
4405
+ const names = (boards ?? []).map((board) => board.name);
4406
+ return names.length > 0 ? t.handOver(names.join(", ")) : null;
4407
+ }
4408
+ function handoffText(t, targets, isCheck) {
4409
+ if (targets.length === 0) {
4410
+ return null;
4411
+ }
4412
+ return [
4413
+ t.handoffIntro,
4414
+ bulletList(targets.map(handoffTargetLine)),
4415
+ isCheck ? `${t.handoffNone} ${t.handoffVerdict}` : t.handoffNone
4416
+ ].join("\n");
4417
+ }
4418
+ function threadText(t, thread) {
4419
+ if (thread.length === 0) {
4420
+ return null;
4421
+ }
4422
+ return t.thread(thread.map((message) => `${message.role === "user" ? t.threadOwner : t.threadYou}: ${message.text}`).join("\n\n"));
4423
+ }
4424
+ function takenText(t, work2) {
4425
+ return work2.takenIds.length > 0 ? t.questionsTaken(work2.takenIds) : "";
4426
+ }
4427
+ function questionRules(t, texts2, work2, round) {
4428
+ if (round === 1) {
4429
+ return [...texts2.research, texts2.firstRound(work2.maxQuestions), t.questionShapeRule, texts2.handIn, t.questionsReplyRule];
4430
+ }
4431
+ return [texts2.followUp(round), texts2.followUpAsk(work2.maxQuestions, takenText(t, work2)), t.questionShapeRule, texts2.update, texts2.handIn, t.questionsReplyRule];
4432
+ }
4433
+ function goalOf(t, goals, job, work2) {
4434
+ if (work2.round === null) {
4435
+ return goals.write(job.taskId, job.title);
4436
+ }
4437
+ return work2.round > 1 ? t.followUpGoal(job.taskId, job.title) : goals.prepare(job.taskId, job.title);
4438
+ }
4439
+ function buildBriefPrompt(job, work2, workspace, options) {
4440
+ const language = jobLanguage(job);
4441
+ const t = TEXTS[language];
4442
+ const prompt = [
4443
+ goalOf(t, t.briefGoals, job, work2),
4444
+ job.description,
4445
+ work2.linkedAction,
4446
+ work2.brief ? t.researchBrief(JSON.stringify(work2.brief)) : null,
4447
+ work2.round === null && job.spec ? t.currentBrief(JSON.stringify(job.spec)) : null,
4448
+ job.handoffNote && t.noteFromAnotherStep(job.handoffNote),
4449
+ threadText(t, work2.thread),
4450
+ teamMessagesText(t, job.messages)
4451
+ ].filter(Boolean).join("\n\n");
4452
+ const instructions = [
4453
+ t.briefUnattendedRule,
4454
+ t.machineRule,
4455
+ t.languageRule,
4456
+ workplaceText(job.computer, workspace.root, language),
4457
+ t.stepLine(job.stage.name, job.stage.helper),
4458
+ job.stage.instructions,
4459
+ job.sourcesText,
4460
+ contextText(t, job.projectContext),
4461
+ `${t.folderRule} ${folderText(t, workspace.root, options.results)}`,
4462
+ t.resultHomeRule,
4463
+ t.howToWork(bulletList(work2.round === null ? t.resultBriefRules : questionRules(t, t.briefQuestions, work2, work2.round))),
4464
+ safetyText(job),
4465
+ knowledgeText(job.knowledge, job.computer, false, language),
4466
+ t.briefStyleRule
4467
+ ].filter(Boolean).join("\n\n");
4468
+ return { prompt, instructions };
4469
+ }
4470
+ function buildSpecPrompt(job, work2, workspace, options) {
4471
+ const language = jobLanguage(job);
4472
+ const t = TEXTS[language];
4473
+ const prompt = [
4474
+ goalOf(t, t.specGoals, job, work2),
4475
+ job.description,
4476
+ work2.linkedAction,
4477
+ work2.brief ? t.specBrief(JSON.stringify(work2.brief)) : null,
4478
+ work2.round === null && job.spec ? t.currentSpec(JSON.stringify(job.spec)) : null,
4479
+ job.handoffNote && t.noteFromAnotherStep(job.handoffNote),
4480
+ threadText(t, work2.thread),
4481
+ teamMessagesText(t, job.messages)
4482
+ ].filter(Boolean).join("\n\n");
4483
+ const instructions = [
4484
+ t.specUnattendedRule,
4485
+ t.machineRule,
4486
+ t.languageRule,
4487
+ workplaceText(job.computer, workspace.root, language),
4488
+ t.stepLine(job.stage.name, job.stage.helper),
4489
+ job.stage.instructions,
4490
+ job.sourcesText,
4491
+ contextText(t, job.projectContext),
4492
+ t.repositories(bulletList(repositoryLines(t, job, workspace))),
4493
+ isLocalJob(job) && t.localFoldersRule,
4494
+ taskResultsText(t, options.results),
4495
+ t.readOnlyRule,
4496
+ t.howToWork(bulletList(work2.round === null ? t.specRules : questionRules(t, t.specQuestions, work2, work2.round))),
4497
+ safetyText(job),
4498
+ knowledgeText(job.knowledge, job.computer, false, language),
4499
+ t.specStyleRule
4500
+ ].filter(Boolean).join("\n\n");
4501
+ return { prompt, instructions };
4502
+ }
4503
+ function buildFolderPrompt(job, workspace, options) {
4504
+ const language = jobLanguage(job);
4505
+ const t = TEXTS[language];
4506
+ const kind = job.stage.kind === "review" || job.stage.kind === "test" ? job.stage.kind : "code";
4507
+ const isCheck = kind !== "code";
4508
+ const people = job.people ?? [];
4509
+ const canAsk = people.length > 0;
4510
+ const prompt = [
4511
+ t.taskHeading(t.folderGoals[kind], job.taskId, job.title),
4512
+ job.description,
4513
+ briefText(t, job.spec, language),
4514
+ job.handoffNote && t.noteFromPreviousStep(job.handoffNote),
4515
+ job.review && t.notesToAddress(job.review),
4516
+ job.scheduleNote,
4517
+ teamMessagesText(t, job.messages)
4518
+ ].filter(Boolean).join("\n\n");
4519
+ const rules = isCheck ? t.folderCheckRules : [...t.folderDoRules, ...job.review ? [t.folderReworkRule] : [], t.folderReportRule];
4520
+ const instructions = [
4521
+ canAsk ? t.askingRule : t.unattendedRule,
4522
+ t.machineRule,
4523
+ t.languageRule,
4524
+ workplaceText(job.computer, workspace.root, language),
4525
+ t.stepLine(job.stage.name, job.stage.helper),
4526
+ job.stage.instructions,
4527
+ job.sourcesText,
4528
+ contextText(t, job.projectContext),
4529
+ `${t.folderRule} ${folderText(t, workspace.root, options.results)}`,
4530
+ t.howToWork(bulletList(rules)),
4531
+ safetyText(job),
4532
+ knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job), language),
4533
+ t.approvalRule,
4534
+ peopleText(t, people, language),
4535
+ isCheck ? null : handOverText(t, job.handOverBoards),
4536
+ handoffText(t, job.allowedHandoffs, isCheck),
4537
+ job.canSchedule && scheduleRule(language),
4538
+ options.browser && t.browserRule
4539
+ ].filter(Boolean).join("\n\n");
4540
+ return { prompt, instructions };
4541
+ }
4542
+ function buildTaskPrompt(job, workspace, options = {}) {
4543
+ const { kind } = job.stage;
4544
+ if (kind === "interview" || kind === "spec") {
4545
+ if (!job.specWork) {
4546
+ throw new Error(SPEC_WORK_MISSING);
4547
+ }
4548
+ return isFolderJob(job) ? buildBriefPrompt(job, job.specWork, workspace, options) : buildSpecPrompt(job, job.specWork, workspace, options);
4549
+ }
4550
+ if (isFolderJob(job)) {
4551
+ return buildFolderPrompt(job, workspace, options);
4552
+ }
4553
+ const language = jobLanguage(job);
4554
+ const t = TEXTS[language];
4555
+ const isCheck = kind !== "code";
4556
+ const readOnly = isReadOnlyCode(job.stage);
4557
+ const people = job.people ?? [];
4558
+ const canAsk = people.length > 0;
4559
+ const prompt = [
4560
+ t.taskHeading(readOnly ? t.readOnlyGoal : t.stageGoals[kind], job.taskId, job.title),
4561
+ job.description,
4562
+ specText(t, job.spec, language),
4563
+ job.handoffNote && t.noteFromPreviousStep(job.handoffNote),
4564
+ job.review && t.reviewComments(job.review),
4565
+ job.scheduleNote,
4566
+ teamMessagesText(t, job.messages)
4567
+ ].filter(Boolean).join("\n\n");
4568
+ const evidence = [t.screenshotRule, ...options.recorder ? [t.recordingRule] : [], t.namingRule];
4569
+ const localApp = localAppRule(t, job, isCheck, canAsk);
4570
+ const rules = isCheck ? [...t.checkRules, t.dependenciesRule, localApp, ...evidence] : readOnly ? [...t.readOnlyCodeRules, t.dependenciesRule, localApp, ...evidence, t.readOnlyReportRule] : [...codeRules(t, job), ...job.review ? [t.reworkRule] : [], t.dependenciesRule, localApp, ...evidence, t.codeReportRule];
4571
+ const instructions = [
4572
+ canAsk ? t.askingRule : t.unattendedRule,
4573
+ t.machineRule,
4574
+ t.languageRule,
4575
+ workplaceText(job.computer, workspace.root, language),
4576
+ t.stepLine(job.stage.name, job.stage.helper),
4577
+ job.stage.instructions,
4578
+ job.sourcesText,
4579
+ contextText(t, job.projectContext),
4580
+ t.repositoriesOnBranch(isCheck, job.branch, bulletList(repositoryLines(t, job, workspace))),
4581
+ isLocalJob(job) && t.localFoldersRule,
4582
+ taskResultsText(t, options.results),
4583
+ t.howToWork(bulletList(rules)),
4584
+ safetyText(job),
4585
+ knowledgeText(job.knowledge, job.computer, canProposeKnowledge(job), language),
4586
+ t.approvalRule,
4587
+ peopleText(t, people, language),
4588
+ handoffText(t, job.allowedHandoffs, isCheck),
4589
+ job.canSchedule && scheduleRule(language),
4590
+ options.browser && t.browserRule
4591
+ ].filter(Boolean).join("\n\n");
4592
+ return { prompt, instructions };
4593
+ }
4594
+
4260
4595
  // src/recordings.ts
4261
4596
  import { randomUUID } from "crypto";
4262
4597
  import { createReadStream } from "fs";