@k0t0vich/meta-agents-template 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this package are documented in this file.
4
4
 
5
+ ## 0.1.10 - 2026-03-20
6
+
7
+ ### Fixed
8
+ - Implemented GitHub TrackerGateway adapter operations instead of `not implemented` stubs (`CREATE_TASK`, `SET_STATUS`, `ASSIGN_SPRINT`, `COMMIT_BY_NAME`).
9
+ - Added safe `dryRun` support for GitHub tracker operations to validate flows without side effects.
10
+ - Added smoke regression check for `meta:ops CREATE_TASK` (dry-run) to prevent future regressions where command exists but adapter is stubbed.
11
+
12
+ ## 0.1.9 - 2026-03-20
13
+
14
+ ### Fixed
15
+ - `meta-agents init` now creates `AGENTS.md` by default for new projects, so agent runtimes that expect uppercase bootstrap filename can load rules reliably.
16
+ - Existing projects with pre-created `agents.md` remain non-destructive: init still updates only managed links block instead of replacing full file.
17
+ - Bootstrap block now includes explicit required instruction to read canonical rules from npm-linked template files.
18
+ - Branch context protection for `meta:task-start` now checks `AGENTS.md` with fallback to `agents.md`.
19
+ - Local smoke now validates `AGENTS.md` presence and required bootstrap instruction in empty-folder install scenario.
20
+
5
21
  ## 0.1.8 - 2026-03-20
6
22
 
7
23
  ### Changed
package/README.md CHANGED
@@ -31,7 +31,7 @@ npx -y @k0t0vich/meta-agents-template init . --yes --tracker github
31
31
  Важно:
32
32
  - если пользователь дал URL `https://www.npmjs.com/package/@k0t0vich/meta-agents-template`, извлекай имя пакета `@k0t0vich/meta-agents-template` и ставь именно его;
33
33
  - не запускай `npm i -D ...` в папке без `package.json`, иначе npm может установить пакет в родительский проект.
34
- - `meta-agents init` по умолчанию работает non-destructive: в корне создаёт/обновляет только project-specific конфиги `.meta-agents/config/project-context.yaml` (имя проекта + tracker) и `.meta-agents/config/trackers.yaml`, аккуратно merge-ит `meta:*` scripts в `package.json` и добавляет ссылочный блок в `agents.md` вместо полной перезаписи.
34
+ - `meta-agents init` по умолчанию работает non-destructive: в корне создаёт/обновляет только project-specific конфиги `.meta-agents/config/project-context.yaml` (имя проекта + tracker) и `.meta-agents/config/trackers.yaml`, аккуратно merge-ит `meta:*` scripts в `package.json` и добавляет/обновляет bootstrap-блок в `AGENTS.md` (или в уже существующий `agents.md`) вместо полной перезаписи.
35
35
  - `--force` нужен только если надо перезаписать уже существующие project-specific конфиги (`project-context.yaml`, `trackers.yaml`) и мигрировать legacy `meta:*` scripts.
36
36
 
37
37
  ## One-line для агентного запуска
@@ -48,7 +48,7 @@ mkdir -p my-project && cd my-project && npm init -y && npm i -D @k0t0vich/meta-a
48
48
  Проверка, что шаблон и правила действительно развернуты:
49
49
 
50
50
  ```bash
51
- test -f agents.md && test -f .meta-agents/config/project-context.yaml && test -f node_modules/@k0t0vich/meta-agents-template/template/agents.md && echo "meta-agents template ready"
51
+ (test -f AGENTS.md || test -f agents.md) && test -f .meta-agents/config/project-context.yaml && test -f node_modules/@k0t0vich/meta-agents-template/template/agents.md && echo "meta-agents template ready"
52
52
  ```
53
53
 
54
54
  ## Диалог при `init`
@@ -64,7 +64,7 @@ test -f agents.md && test -f .meta-agents/config/project-context.yaml && test -f
64
64
  my-project/
65
65
  package.json
66
66
  package-lock.json
67
- agents.md
67
+ AGENTS.md # или agents.md, если файл уже существовал
68
68
  .meta-agents/
69
69
  config/
70
70
  project-context.yaml
@@ -76,7 +76,7 @@ my-project/
76
76
  template/.meta-agents/scripts/*.mjs
77
77
  ```
78
78
 
79
- `agents.md` в проекте не перезаписывается целиком: `init` добавляет/обновляет в нём только ссылочный блок на правила из npm-пакета.
79
+ Файл `AGENTS.md` (или уже существующий `agents.md`) не перезаписывается целиком: `init` добавляет/обновляет в нём только managed bootstrap-блок со ссылками на canonical rules из npm-пакета.
80
80
 
81
81
  ## Канонические команды
82
82
  0. `VERIFY_GOVERNANCE_GATE`
@@ -153,7 +153,7 @@ npm run meta:status
153
153
  `meta:ops` принудительно проверяет tracker provider lock и блокирует выполнение, если переданный `--tracker` не совпадает с зафиксированным provider проекта.
154
154
  `meta:branch` валидирует ветку по Git Flow Lite (`main`, `develop`, `feature/*`, `release/*`, `hotfix/*`).
155
155
  `meta:task-start` делает branch-routing preflight: сравнивает задачу с текущей веткой, показывает dirty/ahead блокеры и готовит маршрут (`stay_on_current_branch` или `create_new_branch`).
156
- `meta:task-start` также делает context-protection check для `agents.md` между текущей и базовой веткой и требует явного подтверждения при diff.
156
+ `meta:task-start` также делает context-protection check для `AGENTS.md` (fallback: `agents.md`) между текущей и базовой веткой и требует явного подтверждения при diff.
157
157
  Именование branch task ref: использовать GitHub issue ref в формате `issue-<number>`.
158
158
  `meta:mr-review` формирует сводный pre-merge отчёт по MR/PR, `meta:mr-review-approve` фиксирует финальный PASS после подтверждения пользователя.
159
159
  `meta:status` отдаёт единый статус-срез: trackers used, current sprint, current task, current branch context, git uncommitted и summary.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k0t0vich/meta-agents-template",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Template system for verification-first agentic development",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -279,12 +279,26 @@ export async function main(argv) {
279
279
  if (result.config.skipped.length > 0) {
280
280
  console.log(`Config files kept: ${result.config.skipped.join(", ")}`);
281
281
  }
282
- if (result.agents.created) {
283
- console.log("agents.md: created with npm links to template rules");
284
- } else if (result.agents.updated) {
285
- console.log("agents.md: appended/updated npm links block");
286
- } else {
287
- console.log("agents.md: npm links block already up to date");
282
+ if (result.agents.created.length > 0) {
283
+ console.log(
284
+ `Agent bootstrap created: ${result.agents.created
285
+ .map((item) => path.basename(item))
286
+ .join(", ")}`,
287
+ );
288
+ }
289
+ if (result.agents.updated.length > 0) {
290
+ console.log(
291
+ `Agent bootstrap updated: ${result.agents.updated
292
+ .map((item) => path.basename(item))
293
+ .join(", ")}`,
294
+ );
295
+ }
296
+ if (result.agents.created.length === 0 && result.agents.updated.length === 0) {
297
+ console.log(
298
+ `Agent bootstrap already up to date: ${result.agents.unchanged
299
+ .map((item) => path.basename(item))
300
+ .join(", ")}`,
301
+ );
288
302
  }
289
303
  if (result.git.actions.length > 0) {
290
304
  console.log(`Git actions: ${result.git.actions.join("; ")}`);
package/src/init.mjs CHANGED
@@ -13,6 +13,7 @@ const TEXT_EXTENSIONS = new Set([
13
13
  ".js",
14
14
  ]);
15
15
  const ROOT_CONFIG_FILES = ["project-context.yaml", "trackers.yaml"];
16
+ const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
16
17
 
17
18
  const AGENTS_LINK_BLOCK_START = "<!-- meta-agents-template:links:start -->";
18
19
  const AGENTS_LINK_BLOCK_END = "<!-- meta-agents-template:links:end -->";
@@ -219,6 +220,8 @@ function buildAgentsLinkBlock(packageName) {
219
220
  const lines = [
220
221
  AGENTS_LINK_BLOCK_START,
221
222
  "## Meta Agents Template (npm-linked)",
223
+ "ОБЯЗАТЕЛЬНО: перед выполнением любой команды сначала прочитай canonical rules по ссылкам ниже.",
224
+ "При конфликте локального текста и шаблона приоритет у canonical files из node_modules.",
222
225
  "Базовые правила шаблона:",
223
226
  `- [Template agents.md](${packageRoot}/template/agents.md)`,
224
227
  `- [Template tracker-command-template.md](${packageRoot}/template/tracker-command-template.md)`,
@@ -230,12 +233,22 @@ function buildAgentsLinkBlock(packageName) {
230
233
  return `${lines.join("\n")}\n`;
231
234
  }
232
235
 
233
- async function upsertAgentsLinks(targetDir, packageName) {
234
- const agentsPath = path.join(targetDir, "agents.md");
236
+ function buildAgentsBootstrapContent(fileName, block) {
237
+ return [
238
+ `# ${fileName}`,
239
+ "",
240
+ "Bootstrap file for agent runtime.",
241
+ "Before any task, read canonical rules from the npm-linked files in the block below.",
242
+ "",
243
+ block,
244
+ ].join("\n");
245
+ }
246
+
247
+ async function upsertAgentsLinksInFile(agentsPath, packageName) {
235
248
  const block = buildAgentsLinkBlock(packageName);
236
249
 
237
250
  if (!(await pathExists(agentsPath))) {
238
- const content = ["# agents.md", "", block].join("\n");
251
+ const content = buildAgentsBootstrapContent(path.basename(agentsPath), block);
239
252
  await fs.writeFile(agentsPath, content, "utf8");
240
253
  return { created: true, updated: false, path: agentsPath };
241
254
  }
@@ -265,6 +278,31 @@ async function upsertAgentsLinks(targetDir, packageName) {
265
278
  return { created: false, updated, path: agentsPath };
266
279
  }
267
280
 
281
+ async function detectAgentsFilePath(targetDir) {
282
+ try {
283
+ const entries = await fs.readdir(targetDir, { withFileTypes: true });
284
+ const match = entries.find(
285
+ (entry) => entry.isFile() && entry.name.toLowerCase() === "agents.md",
286
+ );
287
+ if (match) {
288
+ return path.join(targetDir, match.name);
289
+ }
290
+ } catch {
291
+ // target directory may not exist yet
292
+ }
293
+ return path.join(targetDir, DEFAULT_AGENTS_FILENAME);
294
+ }
295
+
296
+ async function upsertAgentsLinks(targetDir, packageName) {
297
+ const agentsPath = await detectAgentsFilePath(targetDir);
298
+ const result = await upsertAgentsLinksInFile(agentsPath, packageName);
299
+ return {
300
+ created: result.created ? [result.path] : [],
301
+ updated: result.updated ? [result.path] : [],
302
+ unchanged: !result.created && !result.updated ? [result.path] : [],
303
+ };
304
+ }
305
+
268
306
  async function syncConfigFiles({ templateRoot, targetDir, values, force }) {
269
307
  const sourceConfigRoot = path.join(templateRoot, ".meta-agents", "config");
270
308
  const targetConfigRoot = path.join(targetDir, ".meta-agents", "config");
@@ -15,6 +15,7 @@ const TASK_KIND_MAP = {
15
15
  release: "release",
16
16
  hotfix: "hotfix",
17
17
  };
18
+ const GOVERNANCE_CONTEXT_FILES = ["AGENTS.md", "agents.md"];
18
19
 
19
20
  function git(args, allowFailure = false) {
20
21
  try {
@@ -266,6 +267,16 @@ function readFileAtRef(ref, filePath) {
266
267
  return git(["show", `${ref}:${filePath}`], true);
267
268
  }
268
269
 
270
+ function readGovernanceContextAtRef(ref) {
271
+ for (const filePath of GOVERNANCE_CONTEXT_FILES) {
272
+ const content = readFileAtRef(ref, filePath);
273
+ if (content) {
274
+ return { filePath, content };
275
+ }
276
+ }
277
+ return null;
278
+ }
279
+
269
280
  function getContextWarnings(baseBranch) {
270
281
  const warnings = [];
271
282
  if (!baseBranch) {
@@ -280,19 +291,19 @@ function getContextWarnings(baseBranch) {
280
291
  return warnings;
281
292
  }
282
293
 
283
- const currentAgents = readFileAtRef("HEAD", "agents.md");
284
- const baseAgents = readFileAtRef(baseBranch, "agents.md");
294
+ const currentAgents = readGovernanceContextAtRef("HEAD");
295
+ const baseAgents = readGovernanceContextAtRef(baseBranch);
285
296
 
286
297
  if (!currentAgents || !baseAgents) {
287
298
  warnings.push(
288
- "Cannot compare governance context file 'agents.md' between current and base branch (missing in one of refs).",
299
+ "Cannot compare governance context file 'AGENTS.md/agents.md' between current and base branch (missing in one of refs).",
289
300
  );
290
301
  return warnings;
291
302
  }
292
303
 
293
- if (currentAgents !== baseAgents) {
304
+ if (currentAgents.content !== baseAgents.content) {
294
305
  warnings.push(
295
- `Governance context differs: 'agents.md' in current branch is not equal to '${baseBranch}:agents.md'. Confirm switch before apply.`,
306
+ `Governance context differs: '${currentAgents.filePath}' in current branch is not equal to '${baseBranch}:${baseAgents.filePath}'. Confirm switch before apply.`,
296
307
  );
297
308
  }
298
309
 
@@ -1,4 +1,549 @@
1
- export async function createTask() { throw new Error("github adapter: not implemented"); }
2
- export async function setStatus() { throw new Error("github adapter: not implemented"); }
3
- export async function commitByName() { throw new Error("github adapter: not implemented"); }
4
- export async function assignSprint() { throw new Error("github adapter: not implemented"); }
1
+ import { execFileSync } from "node:child_process";
2
+ import process from "node:process";
3
+
4
+ const STATUS_LABELS = ["TODO", "IN_PROGRESS", "REVIEW", "READY", "BLOCKED", "DONE", "PUBLISH"];
5
+ const STATUS_COLORS = {
6
+ IN_PROGRESS: "FBCA04",
7
+ REVIEW: "5319E7",
8
+ READY: "0E8A16",
9
+ BLOCKED: "B60205",
10
+ DONE: "1D76DB",
11
+ PUBLISH: "0052CC",
12
+ };
13
+ STATUS_COLORS.TODO = "D4C5F9";
14
+ const FALLBACK_COLORS = {
15
+ type: "C2E0C6",
16
+ owner: "0E8A16",
17
+ tracker: "0052CC",
18
+ sprint: "F9D0C4",
19
+ default: "BFDADC",
20
+ };
21
+
22
+ function run(command, args, allowFailure = false) {
23
+ try {
24
+ return execFileSync(command, args, {
25
+ cwd: process.cwd(),
26
+ encoding: "utf8",
27
+ stdio: ["ignore", "pipe", "pipe"],
28
+ }).trim();
29
+ } catch (error) {
30
+ if (allowFailure) {
31
+ return "";
32
+ }
33
+ const stderr = String(error?.stderr || "").trim();
34
+ const stdout = String(error?.stdout || "").trim();
35
+ throw new Error(`${command} ${args.join(" ")} failed: ${stderr || stdout || error.message}`);
36
+ }
37
+ }
38
+
39
+ function readJson(command, args, allowFailure = false) {
40
+ const raw = run(command, args, allowFailure);
41
+ if (!raw) {
42
+ return null;
43
+ }
44
+ try {
45
+ return JSON.parse(raw);
46
+ } catch (error) {
47
+ throw new Error(`failed to parse JSON from '${command} ${args.join(" ")}': ${error.message}`);
48
+ }
49
+ }
50
+
51
+ function ensureGhReady() {
52
+ run("gh", ["--version"], false);
53
+ const auth = run("gh", ["auth", "status"], true);
54
+ if (!auth) {
55
+ throw new Error("gh is not authenticated. Run 'gh auth login' first.");
56
+ }
57
+ }
58
+
59
+ function toRepoArgs(repo) {
60
+ if (!repo) {
61
+ return [];
62
+ }
63
+ return ["--repo", String(repo)];
64
+ }
65
+
66
+ function toNonEmptyString(value) {
67
+ if (value === null || value === undefined) {
68
+ return "";
69
+ }
70
+ return String(value).trim();
71
+ }
72
+
73
+ function pickFirstNonEmpty(...values) {
74
+ for (const value of values) {
75
+ const normalized = toNonEmptyString(value);
76
+ if (normalized) {
77
+ return normalized;
78
+ }
79
+ }
80
+ return "";
81
+ }
82
+
83
+ function parseIssueNumber(value) {
84
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
85
+ return String(value);
86
+ }
87
+
88
+ const source = toNonEmptyString(value);
89
+ if (!source) {
90
+ return "";
91
+ }
92
+
93
+ const direct = source.match(/^#?(\d+)$/);
94
+ if (direct) {
95
+ return direct[1];
96
+ }
97
+
98
+ const issueToken = source.match(/(?:^|[\/\s])issue-(\d+)(?:-|$|\s)/i);
99
+ if (issueToken) {
100
+ return issueToken[1];
101
+ }
102
+
103
+ const githubUrl = source.match(/\/issues\/(\d+)(?:$|[/?#])/i);
104
+ if (githubUrl) {
105
+ return githubUrl[1];
106
+ }
107
+
108
+ const ref = source.match(/#(\d+)/);
109
+ if (ref) {
110
+ return ref[1];
111
+ }
112
+
113
+ return "";
114
+ }
115
+
116
+ function normalizeStatus(value, fallback = "TODO") {
117
+ const normalized = toNonEmptyString(value).toUpperCase() || fallback;
118
+ if (!STATUS_LABELS.includes(normalized)) {
119
+ throw new Error(
120
+ `unsupported status '${normalized}'. Allowed: ${STATUS_LABELS.join(", ")}`,
121
+ );
122
+ }
123
+ return normalized;
124
+ }
125
+
126
+ function normalizeSprintLabel(value) {
127
+ const sprint = toNonEmptyString(value);
128
+ if (!sprint) {
129
+ return "";
130
+ }
131
+ if (/^sprint:/i.test(sprint)) {
132
+ return sprint.replace(/^sprint:/i, "sprint:");
133
+ }
134
+ return `sprint:${sprint}`;
135
+ }
136
+
137
+ function normalizeOwnerLabel(value) {
138
+ const owner = toNonEmptyString(value);
139
+ if (!owner) {
140
+ return "owner:Engineering Agent";
141
+ }
142
+ if (/^owner:/i.test(owner)) {
143
+ return owner.replace(/^owner:/i, "owner:");
144
+ }
145
+ return `owner:${owner}`;
146
+ }
147
+
148
+ function normalizeTypeLabel(value) {
149
+ const kind = toNonEmptyString(value).toLowerCase() || "task";
150
+ if (kind.startsWith("type:")) {
151
+ return kind;
152
+ }
153
+ return `type:${kind}`;
154
+ }
155
+
156
+ function parseLabelsInput(value) {
157
+ if (!value) {
158
+ return [];
159
+ }
160
+
161
+ if (Array.isArray(value)) {
162
+ return value.map((item) => toNonEmptyString(item)).filter(Boolean);
163
+ }
164
+
165
+ return String(value)
166
+ .split(",")
167
+ .map((item) => item.trim())
168
+ .filter(Boolean);
169
+ }
170
+
171
+ function labelColor(name) {
172
+ if (STATUS_COLORS[name]) {
173
+ return STATUS_COLORS[name];
174
+ }
175
+
176
+ const lower = toNonEmptyString(name).toLowerCase();
177
+ if (lower.startsWith("type:")) {
178
+ return FALLBACK_COLORS.type;
179
+ }
180
+ if (lower.startsWith("owner:")) {
181
+ return FALLBACK_COLORS.owner;
182
+ }
183
+ if (lower.startsWith("tracker:")) {
184
+ return FALLBACK_COLORS.tracker;
185
+ }
186
+ if (lower.startsWith("sprint:")) {
187
+ return FALLBACK_COLORS.sprint;
188
+ }
189
+ return FALLBACK_COLORS.default;
190
+ }
191
+
192
+ function mergeUnique(values) {
193
+ return Array.from(new Set(values.map((item) => toNonEmptyString(item)).filter(Boolean)));
194
+ }
195
+
196
+ function buildCreateTaskLabels(payload) {
197
+ const status = normalizeStatus(payload.status || "TODO");
198
+ const labels = [
199
+ ...parseLabelsInput(payload.labels),
200
+ status,
201
+ normalizeTypeLabel(payload.type),
202
+ normalizeOwnerLabel(payload.owner || payload.ownerRole),
203
+ "tracker:github",
204
+ ];
205
+
206
+ const sprintLabel = normalizeSprintLabel(payload.sprint);
207
+ if (sprintLabel) {
208
+ labels.push(sprintLabel);
209
+ }
210
+
211
+ return mergeUnique(labels);
212
+ }
213
+
214
+ function renderBulletedValue(value, fallbackText) {
215
+ const text = toNonEmptyString(value);
216
+ if (!text) {
217
+ return `- ${fallbackText}`;
218
+ }
219
+ if (text.includes("\n")) {
220
+ const lines = text
221
+ .split("\n")
222
+ .map((line) => line.trim())
223
+ .filter(Boolean);
224
+ if (lines.length === 0) {
225
+ return `- ${fallbackText}`;
226
+ }
227
+ return lines.map((line) => `- ${line}`).join("\n");
228
+ }
229
+ return `- ${text}`;
230
+ }
231
+
232
+ function renderCreateTaskBody(payload, labels) {
233
+ const description = renderBulletedValue(
234
+ payload.description,
235
+ "заполнить описание задачи",
236
+ );
237
+ const verifiability = renderBulletedValue(
238
+ payload.verifiability || payload.verification,
239
+ "strict: TBD; statistical: TBD; human: TBD",
240
+ );
241
+ const doneBlock = renderBulletedValue(
242
+ payload.done,
243
+ "[ ] Не начато",
244
+ );
245
+
246
+ const metadata = [];
247
+ metadata.push(`- tracker: github`);
248
+ metadata.push(`- labels: ${labels.join(", ")}`);
249
+ const sprintLabel = normalizeSprintLabel(payload.sprint);
250
+ if (sprintLabel) {
251
+ metadata.push(`- sprint: ${sprintLabel.replace(/^sprint:/i, "")}`);
252
+ }
253
+ const epicRef = parseIssueNumber(payload.epic || payload.linkToEpic || payload.parent);
254
+ if (epicRef) {
255
+ metadata.push(`- epic: #${epicRef}`);
256
+ }
257
+
258
+ return [
259
+ "## PRD Step",
260
+ "",
261
+ "### Описание",
262
+ description,
263
+ "",
264
+ "### Проверяемость",
265
+ verifiability,
266
+ "",
267
+ "### Что сделано",
268
+ doneBlock,
269
+ "",
270
+ "## Metadata",
271
+ ...metadata,
272
+ ].join("\n");
273
+ }
274
+
275
+ function ensureLabelExists(name, repo) {
276
+ const repoArgs = toRepoArgs(repo);
277
+ const exists = readJson(
278
+ "gh",
279
+ ["label", "list", ...repoArgs, "--search", name, "--limit", "100", "--json", "name"],
280
+ true,
281
+ );
282
+
283
+ if (Array.isArray(exists) && exists.some((item) => item?.name === name)) {
284
+ return;
285
+ }
286
+
287
+ const color = labelColor(name);
288
+ const description = "Auto-created by meta:ops github adapter";
289
+ const created = run(
290
+ "gh",
291
+ ["label", "create", name, "--color", color, "--description", description, ...repoArgs],
292
+ true,
293
+ );
294
+
295
+ if (!created) {
296
+ const existsAfter = readJson(
297
+ "gh",
298
+ ["label", "list", ...repoArgs, "--search", name, "--limit", "100", "--json", "name"],
299
+ true,
300
+ );
301
+ if (!(Array.isArray(existsAfter) && existsAfter.some((item) => item?.name === name))) {
302
+ throw new Error(`failed to create label '${name}'`);
303
+ }
304
+ }
305
+ }
306
+
307
+ function ensureLabelsExist(labels, repo) {
308
+ for (const label of labels) {
309
+ ensureLabelExists(label, repo);
310
+ }
311
+ }
312
+
313
+ function resolveTaskIssue(payload) {
314
+ const issue = parseIssueNumber(payload.task || payload.issue || payload.taskId || payload.id);
315
+ if (!issue) {
316
+ throw new Error("missing issue reference in payload (task/issue/taskId/id)");
317
+ }
318
+ return issue;
319
+ }
320
+
321
+ function getIssueLabels(issue, repo) {
322
+ const repoArgs = toRepoArgs(repo);
323
+ const issueData = readJson(
324
+ "gh",
325
+ ["issue", "view", String(issue), "--json", "labels", ...repoArgs],
326
+ false,
327
+ );
328
+ const labels = issueData?.labels || [];
329
+ return labels.map((item) => toNonEmptyString(item?.name)).filter(Boolean);
330
+ }
331
+
332
+ function removeAndAddIssueLabels(issue, repo, labelsToRemove, labelsToAdd) {
333
+ const repoArgs = toRepoArgs(repo);
334
+ const args = ["issue", "edit", String(issue), ...repoArgs];
335
+
336
+ for (const label of labelsToRemove) {
337
+ args.push("--remove-label", label);
338
+ }
339
+ for (const label of labelsToAdd) {
340
+ args.push("--add-label", label);
341
+ }
342
+
343
+ run("gh", args, false);
344
+ }
345
+
346
+ function commentIssue(issue, repo, body) {
347
+ const content = toNonEmptyString(body);
348
+ if (!content) {
349
+ return;
350
+ }
351
+ const repoArgs = toRepoArgs(repo);
352
+ run("gh", ["issue", "comment", String(issue), "--body", content, ...repoArgs], false);
353
+ }
354
+
355
+ function gitCurrentBranch() {
356
+ return run("git", ["rev-parse", "--abbrev-ref", "HEAD"], true);
357
+ }
358
+
359
+ function gitAdd(files) {
360
+ if (Array.isArray(files) && files.length > 0) {
361
+ run("git", ["add", ...files.map((item) => String(item))], false);
362
+ return;
363
+ }
364
+ run("git", ["add", "-A"], false);
365
+ }
366
+
367
+ function ensureStagedChanges() {
368
+ const staged = run("git", ["diff", "--cached", "--name-only"], true);
369
+ if (!staged) {
370
+ throw new Error("no staged changes to commit");
371
+ }
372
+ }
373
+
374
+ function buildCommitMessage(payload) {
375
+ const explicit = pickFirstNonEmpty(payload.message, payload.commitMessage);
376
+ if (explicit) {
377
+ return explicit;
378
+ }
379
+
380
+ const issue = parseIssueNumber(payload.task || payload.issue || payload.taskId || payload.id);
381
+ if (!issue) {
382
+ throw new Error(
383
+ "missing commit message. Provide payload.message or payload.issue/task to build '#<issue> <summary>'",
384
+ );
385
+ }
386
+
387
+ const summary = pickFirstNonEmpty(payload.shortName, payload.title, payload.name, "update");
388
+ return `#${issue} ${summary}`;
389
+ }
390
+
391
+ export async function createTask(payload = {}) {
392
+ const title = pickFirstNonEmpty(payload.shortName, payload.title, payload.name);
393
+ if (!title) {
394
+ throw new Error("missing task title (shortName/title/name)");
395
+ }
396
+
397
+ const repo = pickFirstNonEmpty(payload.repo);
398
+ const labels = buildCreateTaskLabels(payload);
399
+ const body = renderCreateTaskBody(payload, labels);
400
+
401
+ if (payload.dryRun) {
402
+ console.log(
403
+ JSON.stringify(
404
+ {
405
+ command: "CREATE_TASK",
406
+ mode: "dry-run",
407
+ repo: repo || "current",
408
+ title,
409
+ labels,
410
+ },
411
+ null,
412
+ 2,
413
+ ),
414
+ );
415
+ return;
416
+ }
417
+
418
+ ensureGhReady();
419
+ ensureLabelsExist(labels, repo);
420
+
421
+ const repoArgs = toRepoArgs(repo);
422
+ const args = ["issue", "create", "--title", title, "--body", body, ...repoArgs];
423
+ for (const label of labels) {
424
+ args.push("--label", label);
425
+ }
426
+
427
+ const issueUrl = run("gh", args, false);
428
+ const issue = parseIssueNumber(issueUrl);
429
+ const epic = parseIssueNumber(payload.epic || payload.linkToEpic || payload.parent);
430
+ if (issue && epic) {
431
+ commentIssue(issue, repo, `Linked to epic #${epic}.`);
432
+ }
433
+
434
+ console.log(`github CREATE_TASK: ${issueUrl}`);
435
+ }
436
+
437
+ export async function setStatus(payload = {}) {
438
+ const issue = resolveTaskIssue(payload);
439
+ const repo = pickFirstNonEmpty(payload.repo);
440
+ const targetStatus = normalizeStatus(payload.status);
441
+
442
+ if (payload.dryRun) {
443
+ console.log(
444
+ JSON.stringify(
445
+ {
446
+ command: "SET_STATUS",
447
+ mode: "dry-run",
448
+ repo: repo || "current",
449
+ issue,
450
+ status: targetStatus,
451
+ },
452
+ null,
453
+ 2,
454
+ ),
455
+ );
456
+ return;
457
+ }
458
+
459
+ ensureGhReady();
460
+ ensureLabelsExist([targetStatus], repo);
461
+
462
+ const currentLabels = getIssueLabels(issue, repo);
463
+ const removeStatuses = currentLabels.filter(
464
+ (label) => STATUS_LABELS.includes(label) && label !== targetStatus,
465
+ );
466
+
467
+ removeAndAddIssueLabels(issue, repo, removeStatuses, [targetStatus]);
468
+ const reason = pickFirstNonEmpty(payload.reason);
469
+ if (reason) {
470
+ commentIssue(issue, repo, `Status -> ${targetStatus}. Reason: ${reason}`);
471
+ }
472
+
473
+ console.log(`github SET_STATUS: #${issue} -> ${targetStatus}`);
474
+ }
475
+
476
+ export async function commitByName(payload = {}) {
477
+ const branch = gitCurrentBranch();
478
+ const expectedBranch = pickFirstNonEmpty(payload.branch);
479
+ if (expectedBranch && branch && branch !== expectedBranch) {
480
+ throw new Error(`branch mismatch: current '${branch}', expected '${expectedBranch}'`);
481
+ }
482
+
483
+ const message = buildCommitMessage(payload);
484
+ const files = Array.isArray(payload.files) ? payload.files : [];
485
+
486
+ if (payload.dryRun) {
487
+ console.log(
488
+ JSON.stringify(
489
+ {
490
+ command: "COMMIT_BY_NAME",
491
+ mode: "dry-run",
492
+ branch: branch || "unknown",
493
+ message,
494
+ files: files.length > 0 ? files : ["-A"],
495
+ push: Boolean(payload.push),
496
+ },
497
+ null,
498
+ 2,
499
+ ),
500
+ );
501
+ return;
502
+ }
503
+
504
+ gitAdd(files);
505
+ ensureStagedChanges();
506
+ run("git", ["commit", "-m", message], false);
507
+ if (payload.push) {
508
+ run("git", ["push"], false);
509
+ }
510
+
511
+ console.log(`github COMMIT_BY_NAME: ${message}`);
512
+ }
513
+
514
+ export async function assignSprint(payload = {}) {
515
+ const issue = resolveTaskIssue(payload);
516
+ const repo = pickFirstNonEmpty(payload.repo);
517
+ const sprintLabel = normalizeSprintLabel(payload.sprint);
518
+ if (!sprintLabel) {
519
+ throw new Error("missing sprint in payload");
520
+ }
521
+
522
+ if (payload.dryRun) {
523
+ console.log(
524
+ JSON.stringify(
525
+ {
526
+ command: "ASSIGN_SPRINT",
527
+ mode: "dry-run",
528
+ repo: repo || "current",
529
+ issue,
530
+ sprint: sprintLabel,
531
+ },
532
+ null,
533
+ 2,
534
+ ),
535
+ );
536
+ return;
537
+ }
538
+
539
+ ensureGhReady();
540
+ ensureLabelsExist([sprintLabel], repo);
541
+
542
+ const currentLabels = getIssueLabels(issue, repo);
543
+ const removeSprints = currentLabels.filter(
544
+ (label) => label.toLowerCase().startsWith("sprint:") && label !== sprintLabel,
545
+ );
546
+ removeAndAddIssueLabels(issue, repo, removeSprints, [sprintLabel]);
547
+
548
+ console.log(`github ASSIGN_SPRINT: #${issue} -> ${sprintLabel}`);
549
+ }
@@ -3,7 +3,7 @@
3
3
  Проект инициализирован шаблоном `meta-agents-template`.
4
4
 
5
5
  ## Что уже подключено
6
- - `agents.md`: локальный входной файл с ссылками на canonical правила в npm-пакете;
6
+ - `AGENTS.md` (или существующий `agents.md`): локальный bootstrap-файл с ссылками на canonical правила в npm-пакете;
7
7
  - `.meta-agents/config/project-context.yaml`: project name + выбранный tracker provider;
8
8
  - `.meta-agents/config/trackers.yaml`: lock tracker provider и доступные providers;
9
9
  - `meta:*` npm scripts, которые запускают canonical tooling из `node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/*`.
@@ -64,7 +64,7 @@ npm run meta:status
64
64
  Если передать `--tracker`, он обязан совпадать с зафиксированным provider проекта, иначе команда блокируется.
65
65
  `meta:branch` валидирует ветку по Git Flow Lite (`main`, `develop`, `feature/*`, `release/*`, `hotfix/*`).
66
66
  `meta:task-start` делает branch-routing preflight: сравнивает задачу с текущей веткой, показывает dirty/ahead блокеры и готовит маршрут (`stay_on_current_branch` или `create_new_branch`).
67
- `meta:task-start` также делает context-protection check для `agents.md` между текущей и базовой веткой и требует явного подтверждения при diff.
67
+ `meta:task-start` также делает context-protection check для `AGENTS.md` (fallback: `agents.md`) между текущей и базовой веткой и требует явного подтверждения при diff.
68
68
  Именование branch task ref: использовать GitHub issue ref в формате `issue-<number>`.
69
69
  `meta:mr-review` формирует сводный pre-merge отчёт по MR/PR, `meta:mr-review-approve` фиксирует финальный PASS после подтверждения пользователя.
70
70
 
@@ -172,7 +172,7 @@ Name: <agent role>
172
172
  6. Согласовать с пользователем способ обработки (`commit/stash/discard/push`).
173
173
  7. Переключиться на базовую ветку (`develop` или `main`) и обновить её.
174
174
  8. Создать новую рабочую ветку по Git Flow Lite.
175
- 9. Перед переключением проверить консистентность `agents.md` между текущей и базовой веткой; при отличиях показать явное предупреждение и запросить подтверждение пользователя.
175
+ 9. Перед переключением проверить консистентность `AGENTS.md` (fallback: `agents.md`) между текущей и базовой веткой; при отличиях показать явное предупреждение и запросить подтверждение пользователя.
176
176
 
177
177
  Без этого preflight выполнение реализации запрещено.
178
178
 
@@ -201,7 +201,7 @@ Name: <agent role>
201
201
  21. Для релиза обновлён публичный `CHANGELOG.md`.
202
202
  22. Коммит следует формату `#issue-number <summary>` (issue ref в начале сообщения).
203
203
  23. Для branch routing собран e2e evidence по двум сценариям: `same feature` и `different feature`.
204
- 24. Перед `--apply` подтверждена консистентность контекста `agents.md` (или явно подтверждён осознанный switch при diff).
204
+ 24. Перед `--apply` подтверждена консистентность контекста `AGENTS.md` (fallback: `agents.md`) или явно подтверждён осознанный switch при diff.
205
205
 
206
206
  Если хотя бы один критерий не выполнен, задача не принимается.
207
207
 
@@ -67,7 +67,7 @@ result PASS
67
67
  - branch naming policy: использовать только GitHub issue ref в формате `issue-<number>`;
68
68
  - если задача атомарная и относится к текущей feature-ветке, допускается выполнение в текущей ветке после явного подтверждения пользователя;
69
69
  - если задача не относится к текущей ветке, требуется: проверить dirty/ahead, согласовать действие с пользователем, перейти на `develop` (или `main` для hotfix), обновить и создать новую ветку;
70
- - перед `--apply` обязателен контекстный check: сравнить `agents.md` текущей и базовой ветки, при diff запросить явное подтверждение пользователя;
70
+ - перед `--apply` обязателен контекстный check: сравнить `AGENTS.md` (fallback: `agents.md`) текущей и базовой ветки, при diff запросить явное подтверждение пользователя;
71
71
  - сообщение коммита обязательно следует формату `#issue-number <summary>` (issue ref в начале).
72
72
 
73
73
  Для GitHub трекера статус issue задаётся label-ом (одновременно только один статус из набора):
@@ -187,7 +187,7 @@ tracker __DEFAULT_TRACKER__,
187
187
  task "#12",
188
188
  task kind "atomic",
189
189
  command "npm run meta:task-start -- --task #12 --slug api-redirect",
190
- user dialogue "confirm branch context + dirty/ahead handling + agents.md context warning + switch decision"
190
+ user dialogue "confirm branch context + dirty/ahead handling + AGENTS.md context warning + switch decision"
191
191
  ```
192
192
 
193
193
  ### SET_STATUS -> REVIEW (обязателен перед review gate)