@k0t0vich/meta-agents-template 0.1.10 → 0.1.11

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,18 @@
2
2
 
3
3
  All notable changes to this package are documented in this file.
4
4
 
5
+ ## 0.1.11 - 2026-03-20
6
+
7
+ ### Added
8
+ - Added a hard pre-implementation command `VERIFY_IMPLEMENTATION_GATE` to enforce task readiness before coding starts.
9
+ - Added canonical Status Agent output contract (6 required sections) and corresponding smoke validation.
10
+ - Added robust empty-repository branch-routing behavior for `meta:task-start --apply` (no-commit bootstrap path).
11
+
12
+ ### Fixed
13
+ - Fixed TrackerGateway local script resolution for `PREPARE_TASK_BRANCH`, `RUN_MR_REVIEW_GATE`, and `VERIFY_IMPLEMENTATION_GATE` in npm-linked execution mode.
14
+ - Enforced structured verifiability schema (`strict`, `statistical`, `human`) for task creation and `SET_STATUS -> IN_PROGRESS`.
15
+ - Expanded local post-publish smoke checks for source repo, generated project, and unborn repository scenarios.
16
+
5
17
  ## 0.1.10 - 2026-03-20
6
18
 
7
19
  ### Fixed
package/README.md CHANGED
@@ -83,13 +83,14 @@ my-project/
83
83
  1. `CREATE_TASK`
84
84
  2. `PREPARE_TASK_BRANCH`
85
85
  3. `SET_STATUS`
86
- 4. `RUN_REVIEW_GATE`
87
- 5. `COMMIT_BY_NAME`
88
- 6. `RUN_MR_REVIEW_GATE`
89
- 7. `ASSIGN_SPRINT`
90
- 8. `PREPARE_RELEASE_NOTE`
91
- 9. `MARK_TASKS_PUBLISH`
92
- 10. `STATUS_SNAPSHOT`
86
+ 4. `VERIFY_IMPLEMENTATION_GATE`
87
+ 5. `RUN_REVIEW_GATE`
88
+ 6. `COMMIT_BY_NAME`
89
+ 7. `RUN_MR_REVIEW_GATE`
90
+ 8. `ASSIGN_SPRINT`
91
+ 9. `PREPARE_RELEASE_NOTE`
92
+ 10. `MARK_TASKS_PUBLISH`
93
+ 11. `STATUS_SNAPSHOT`
93
94
 
94
95
  `VERIFY_GOVERNANCE_GATE` выполняет `Governance Watchdog Agent` перед любой операцией и блокирует выполнение при нарушении PRD/acceptance/user-confirmation правил.
95
96
  `RUN_REVIEW_GATE` выполняет `Reviewer/Judge Agent` перед коммитом:
@@ -140,6 +141,7 @@ npm run self:bootstrap
140
141
  npm run meta:verify
141
142
  npm run meta:branch
142
143
  npm run meta:task-start -- --task #12 --slug api-redirect
144
+ npm run meta:implementation-gate -- --task #12
143
145
  npm run meta:review
144
146
  npm run meta:review-approve
145
147
  npm run meta:mr-review
@@ -155,6 +157,7 @@ npm run meta:status
155
157
  `meta:task-start` делает branch-routing preflight: сравнивает задачу с текущей веткой, показывает dirty/ahead блокеры и готовит маршрут (`stay_on_current_branch` или `create_new_branch`).
156
158
  `meta:task-start` также делает context-protection check для `AGENTS.md` (fallback: `agents.md`) между текущей и базовой веткой и требует явного подтверждения при diff.
157
159
  Именование branch task ref: использовать GitHub issue ref в формате `issue-<number>`.
160
+ `meta:implementation-gate` блокирует старт реализации, если не выполнены условия `IN_PROGRESS + PRD sections + structured verifiability(strict/statistical/human) + branch alignment`.
158
161
  `meta:mr-review` формирует сводный pre-merge отчёт по MR/PR, `meta:mr-review-approve` фиксирует финальный PASS после подтверждения пользователя.
159
162
  `meta:status` отдаёт единый статус-срез: trackers used, current sprint, current task, current branch context, git uncommitted и summary.
160
163
  В `github` режиме локальные `tasks/*` по умолчанию считаются cache/legacy и не являются источником истины.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k0t0vich/meta-agents-template",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Template system for verification-first agentic development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "meta:status": "node template/.meta-agents/scripts/sync-status.mjs",
15
15
  "meta:branch": "node template/.meta-agents/scripts/verify-branch-strategy.mjs",
16
16
  "meta:task-start": "node template/.meta-agents/scripts/task-branch-router.mjs",
17
+ "meta:implementation-gate": "node template/.meta-agents/scripts/verify-implementation-gate.mjs",
17
18
  "meta:verify": "node template/.meta-agents/scripts/verify-governance.mjs",
18
19
  "meta:review": "node template/.meta-agents/scripts/run-review-gate.mjs",
19
20
  "meta:review-approve": "node template/.meta-agents/scripts/run-review-gate.mjs --approve yes",
package/src/init.mjs CHANGED
@@ -110,6 +110,7 @@ function buildMetaScripts(packageName) {
110
110
  "meta:status": `${base}/sync-status.mjs`,
111
111
  "meta:branch": `${base}/verify-branch-strategy.mjs`,
112
112
  "meta:task-start": `${base}/task-branch-router.mjs`,
113
+ "meta:implementation-gate": `${base}/verify-implementation-gate.mjs`,
113
114
  "meta:verify": `${base}/verify-governance.mjs`,
114
115
  "meta:review": `${base}/run-review-gate.mjs`,
115
116
  "meta:review-approve": `${base}/run-review-gate.mjs --approve yes`,
@@ -217,14 +218,19 @@ async function mergePackageJsonMeta({
217
218
 
218
219
  function buildAgentsLinkBlock(packageName) {
219
220
  const packageRoot = `./node_modules/${packageName}`;
221
+ const templateAgentsPath = `${packageRoot}/template/agents.md`;
222
+ const trackerTemplatePath = `${packageRoot}/template/tracker-command-template.md`;
220
223
  const lines = [
221
224
  AGENTS_LINK_BLOCK_START,
222
225
  "## Meta Agents Template (npm-linked)",
223
226
  "ОБЯЗАТЕЛЬНО: перед выполнением любой команды сначала прочитай canonical rules по ссылкам ниже.",
224
227
  "При конфликте локального текста и шаблона приоритет у canonical files из node_modules.",
225
228
  "Базовые правила шаблона:",
226
- `- [Template agents.md](${packageRoot}/template/agents.md)`,
227
- `- [Template tracker-command-template.md](${packageRoot}/template/tracker-command-template.md)`,
229
+ `- [Template agents.md](${templateAgentsPath})`,
230
+ `- [Template tracker-command-template.md](${trackerTemplatePath})`,
231
+ "Canonical files (read by path in this exact order):",
232
+ `1. \`${templateAgentsPath}\``,
233
+ `2. \`${trackerTemplatePath}\``,
228
234
  "Локальные конфиги проекта:",
229
235
  "- [.meta-agents/config/project-context.yaml](./.meta-agents/config/project-context.yaml)",
230
236
  "- [.meta-agents/config/trackers.yaml](./.meta-agents/config/trackers.yaml)",
@@ -103,6 +103,7 @@ system:
103
103
  required: true
104
104
  required_before_commands:
105
105
  - CREATE_TASK
106
+ - VERIFY_IMPLEMENTATION_GATE
106
107
  - SET_STATUS
107
108
  - COMMIT_BY_NAME
108
109
  routing_command: "meta:task-start"
@@ -160,6 +161,7 @@ system:
160
161
  required: true
161
162
  before_commands:
162
163
  - VERIFY_GOVERNANCE_GATE
164
+ - VERIFY_IMPLEMENTATION_GATE
163
165
  - CREATE_TASK
164
166
  - PREPARE_TASK_BRANCH
165
167
  - SET_STATUS
@@ -225,6 +227,7 @@ system:
225
227
  - evidence_pack_complete
226
228
  - tracker_sync_complete
227
229
  - branch_routing_dialog_completed
230
+ - implementation_gate_passed
228
231
  - branch_routing_e2e_verified
229
232
  - agents_context_consistency_verified
230
233
  - watchdog_gate_passed
@@ -568,23 +568,29 @@ async function main() {
568
568
 
569
569
  console.log("# Status Snapshot");
570
570
  console.log("");
571
- console.log("## Trackers");
571
+ console.log("## 1) Trackers Used");
572
572
  console.log(`- locked provider: ${report.tracker.lockedProvider}`);
573
573
  console.log(`- available providers: ${report.tracker.availableProviders.join(", ") || "unknown"}`);
574
574
  console.log(`- workflow source: ${report.tracker.workflowSource}`);
575
575
 
576
576
  console.log("");
577
- console.log("## Process");
578
- console.log(`- current sprint: ${report.process.currentSprint}`);
579
- console.log(`- current task: ${report.process.currentTask}`);
580
- console.log(`- current task ref: ${report.process.currentTaskRef}`);
581
- console.log(`- branch/task alignment: ${report.process.branchTaskAlignment}`);
577
+ console.log("## 2) Current Sprint");
578
+ console.log(`- ${report.process.currentSprint}`);
582
579
 
583
580
  console.log("");
584
- console.log("## Git");
581
+ console.log("## 3) Current Task");
582
+ console.log(`- task: ${report.process.currentTask}`);
583
+ console.log(`- task ref: ${report.process.currentTaskRef}`);
584
+
585
+ console.log("");
586
+ console.log("## 4) Current Branch Context");
585
587
  console.log(`- branch: ${report.git.branch}`);
586
588
  console.log(`- branch type: ${report.git.branchType}`);
587
589
  console.log(`- branch task ref: ${report.git.branchTaskRef}`);
590
+ console.log(`- task/branch alignment: ${report.process.branchTaskAlignment}`);
591
+
592
+ console.log("");
593
+ console.log("## 5) Git Uncommitted");
588
594
  console.log(`- upstream: ${report.git.upstream}`);
589
595
  console.log(`- ahead/behind: ${report.git.ahead}/${report.git.behind}`);
590
596
  console.log(`- modified_or_staged: ${report.git.modifiedOrStaged}`);
@@ -601,7 +607,7 @@ async function main() {
601
607
  }
602
608
 
603
609
  console.log("");
604
- console.log("## Summary");
610
+ console.log("## 6) Summary");
605
611
  if (report.summary.length === 0) {
606
612
  console.log("- no additional notes");
607
613
  } else {
@@ -163,6 +163,7 @@ function collectGitContext() {
163
163
  branch,
164
164
  branchType: classifyBranch(branch),
165
165
  branchTaskRef: extractBranchTaskRef(branch),
166
+ hasCommits: hasCommits(),
166
167
  upstream: parsed.upstream || "not configured",
167
168
  ahead: parsed.ahead,
168
169
  behind: parsed.behind,
@@ -259,6 +260,10 @@ function hasRef(ref) {
259
260
  return Boolean(git(["show-ref", "--verify", ref], true));
260
261
  }
261
262
 
263
+ function hasCommits() {
264
+ return Boolean(git(["rev-parse", "--verify", "HEAD"], true));
265
+ }
266
+
262
267
  function hasOrigin() {
263
268
  return Boolean(git(["remote", "get-url", "origin"], true));
264
269
  }
@@ -333,6 +338,25 @@ function executePlan(plan, gitContext) {
333
338
  git(["fetch", "origin"], false);
334
339
  }
335
340
 
341
+ if (!gitContext.hasCommits) {
342
+ const baseRemote = hasRef(`refs/remotes/origin/${baseBranch}`);
343
+ if (baseRemote) {
344
+ git(["checkout", "-b", baseBranch, "--track", `origin/${baseBranch}`], false);
345
+ } else if (gitContext.branch !== baseBranch) {
346
+ git(["checkout", "-B", baseBranch], false);
347
+ }
348
+
349
+ if (plan.targetBranch !== baseBranch) {
350
+ git(["checkout", "-B", plan.targetBranch], false);
351
+ }
352
+
353
+ return {
354
+ changedBranch: plan.targetBranch !== gitContext.branch,
355
+ branch: plan.targetBranch,
356
+ message: `Repository has no commits yet. Bootstrapped branch routing and switched to '${plan.targetBranch}'.`,
357
+ };
358
+ }
359
+
336
360
  const baseLocal = hasRef(`refs/heads/${baseBranch}`);
337
361
  const baseRemote = hasRef(`refs/remotes/origin/${baseBranch}`);
338
362
 
@@ -411,12 +435,27 @@ function buildPlan(options, gitContext) {
411
435
  requiresDialogue.push(
412
436
  "Задача атомарная и относится к текущей feature-ветке. Подтвердите, что продолжаем работу в этой ветке.",
413
437
  );
438
+ } else if (!gitContext.hasCommits) {
439
+ requiresDialogue.push(
440
+ "Репозиторий без коммитов. Подтвердите bootstrap маршрута: создать базовую ветку и рабочую ветку без ручного обхода.",
441
+ );
414
442
  } else {
415
443
  requiresDialogue.push(
416
444
  "Задача не относится к текущей ветке. Подтвердите переключение на базовую ветку, обновление и создание новой рабочей ветки.",
417
445
  );
418
446
  }
419
447
 
448
+ const suggestedCommands = sameFeature
449
+ ? []
450
+ : !gitContext.hasCommits
451
+ ? [`git checkout -B ${baseBranch}`, `git checkout -B ${targetBranch}`]
452
+ : [
453
+ "git fetch origin",
454
+ `git checkout ${baseBranch}`,
455
+ `git pull --ff-only origin ${baseBranch}`,
456
+ `git checkout -b ${targetBranch}`,
457
+ ];
458
+
420
459
  return {
421
460
  requested: {
422
461
  task: options.task,
@@ -431,14 +470,7 @@ function buildPlan(options, gitContext) {
431
470
  namingWarnings,
432
471
  contextWarnings: getContextWarnings(baseBranch),
433
472
  requiresDialogue,
434
- suggestedCommands: sameFeature
435
- ? []
436
- : [
437
- "git fetch origin",
438
- `git checkout ${baseBranch}`,
439
- `git pull --ff-only origin ${baseBranch}`,
440
- `git checkout -b ${targetBranch}`,
441
- ],
473
+ suggestedCommands,
442
474
  };
443
475
  }
444
476
 
@@ -449,6 +481,7 @@ function printPlan(plan, applyResult) {
449
481
  console.log(`- branch: ${plan.current.branch}`);
450
482
  console.log(`- branch type: ${plan.current.branchType}`);
451
483
  console.log(`- branch task ref: ${plan.current.branchTaskRef || "not detected"}`);
484
+ console.log(`- repository state: ${plan.current.hasCommits ? "has commits" : "no commits yet"}`);
452
485
  console.log(`- upstream: ${plan.current.upstream}`);
453
486
  console.log(`- ahead/behind: ${plan.current.ahead}/${plan.current.behind}`);
454
487
  console.log(`- modified_or_staged: ${plan.current.modifiedOrStaged}`);
@@ -2,6 +2,7 @@ import { execFileSync } from "node:child_process";
2
2
  import process from "node:process";
3
3
 
4
4
  const STATUS_LABELS = ["TODO", "IN_PROGRESS", "REVIEW", "READY", "BLOCKED", "DONE", "PUBLISH"];
5
+ const REQUIRED_PRD_HEADINGS = ["### Описание", "### Проверяемость", "### Что сделано"];
5
6
  const STATUS_COLORS = {
6
7
  IN_PROGRESS: "FBCA04",
7
8
  REVIEW: "5319E7",
@@ -168,6 +169,129 @@ function parseLabelsInput(value) {
168
169
  .filter(Boolean);
169
170
  }
170
171
 
172
+ function extractSection(content, heading) {
173
+ const source = String(content || "");
174
+ if (!source || !heading) {
175
+ return "";
176
+ }
177
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
178
+ const regex = new RegExp(`${escapedHeading}\\s*\\n([\\s\\S]*?)(?:\\n###\\s+|$)`, "i");
179
+ const match = source.match(regex);
180
+ return (match?.[1] || "").trim();
181
+ }
182
+
183
+ function parsePrefixedValue(raw, key) {
184
+ const source = toNonEmptyString(raw);
185
+ if (!source) {
186
+ return "";
187
+ }
188
+ const regex = new RegExp(`${key}\\s*:\\s*([^\\n;]+)`, "i");
189
+ const match = source.match(regex);
190
+ return (match?.[1] || "").trim();
191
+ }
192
+
193
+ function buildVerifiability(payload = {}) {
194
+ const structured = payload.verifiability;
195
+ let strict = "";
196
+ let statistical = "";
197
+ let human = "";
198
+
199
+ if (structured && typeof structured === "object" && !Array.isArray(structured)) {
200
+ strict = pickFirstNonEmpty(structured.strict, payload.strict, payload.verificationStrict);
201
+ statistical = pickFirstNonEmpty(
202
+ structured.statistical,
203
+ payload.statistical,
204
+ payload.verificationStatistical,
205
+ );
206
+ human = pickFirstNonEmpty(structured.human, payload.human, payload.verificationHuman);
207
+ } else {
208
+ const flat = pickFirstNonEmpty(payload.verifiability, payload.verification);
209
+ strict = pickFirstNonEmpty(
210
+ payload.strict,
211
+ payload.verificationStrict,
212
+ parsePrefixedValue(flat, "strict"),
213
+ );
214
+ statistical = pickFirstNonEmpty(
215
+ payload.statistical,
216
+ payload.verificationStatistical,
217
+ parsePrefixedValue(flat, "statistical"),
218
+ );
219
+ human = pickFirstNonEmpty(
220
+ payload.human,
221
+ payload.verificationHuman,
222
+ parsePrefixedValue(flat, "human"),
223
+ );
224
+ }
225
+
226
+ return {
227
+ strict: strict || "schema/types/invariants defined",
228
+ statistical: statistical || "N/A (provide measurable threshold if metric is applicable)",
229
+ human: human || "manual review and acceptance by task owner",
230
+ };
231
+ }
232
+
233
+ function validateVerifiabilitySection(body) {
234
+ const section = extractSection(body, "### Проверяемость");
235
+ if (!section) {
236
+ return {
237
+ ok: false,
238
+ reasons: ["missing section '### Проверяемость'"],
239
+ };
240
+ }
241
+
242
+ const hasStrict = /(^|\n)\s*-\s*strict\s*:/i.test(section);
243
+ const hasStatistical = /(^|\n)\s*-\s*statistical\s*:/i.test(section);
244
+ const hasHuman = /(^|\n)\s*-\s*human\s*:/i.test(section);
245
+ const reasons = [];
246
+
247
+ if (!hasStrict) {
248
+ reasons.push("missing 'strict:' row in verifiability");
249
+ }
250
+ if (!hasStatistical) {
251
+ reasons.push("missing 'statistical:' row in verifiability");
252
+ }
253
+ if (!hasHuman) {
254
+ reasons.push("missing 'human:' row in verifiability");
255
+ }
256
+
257
+ const statisticalMatch = section.match(/(^|\n)\s*-\s*statistical\s*:\s*(.+)$/im);
258
+ if (statisticalMatch) {
259
+ const statisticalValue = toNonEmptyString(statisticalMatch[2]);
260
+ const hasThresholdOrNA = /(<=|>=|<|>|=|\b\d+(\.\d+)?\b|N\/A|^NA\b|not applicable)/i.test(
261
+ statisticalValue,
262
+ );
263
+ if (!hasThresholdOrNA) {
264
+ reasons.push("statistical row must include measurable threshold or explicit N/A");
265
+ }
266
+ }
267
+
268
+ return {
269
+ ok: reasons.length === 0,
270
+ reasons,
271
+ };
272
+ }
273
+
274
+ function validateIssueBodyForInProgress(body) {
275
+ const reasons = [];
276
+ for (const heading of REQUIRED_PRD_HEADINGS) {
277
+ if (!String(body || "").includes(heading)) {
278
+ reasons.push(`missing required PRD heading '${heading}'`);
279
+ }
280
+ }
281
+
282
+ const verifiabilityCheck = validateVerifiabilitySection(body);
283
+ if (!verifiabilityCheck.ok) {
284
+ for (const reason of verifiabilityCheck.reasons) {
285
+ reasons.push(reason);
286
+ }
287
+ }
288
+
289
+ return {
290
+ ok: reasons.length === 0,
291
+ reasons,
292
+ };
293
+ }
294
+
171
295
  function labelColor(name) {
172
296
  if (STATUS_COLORS[name]) {
173
297
  return STATUS_COLORS[name];
@@ -234,10 +358,7 @@ function renderCreateTaskBody(payload, labels) {
234
358
  payload.description,
235
359
  "заполнить описание задачи",
236
360
  );
237
- const verifiability = renderBulletedValue(
238
- payload.verifiability || payload.verification,
239
- "strict: TBD; statistical: TBD; human: TBD",
240
- );
361
+ const verifiability = buildVerifiability(payload);
241
362
  const doneBlock = renderBulletedValue(
242
363
  payload.done,
243
364
  "[ ] Не начато",
@@ -262,7 +383,9 @@ function renderCreateTaskBody(payload, labels) {
262
383
  description,
263
384
  "",
264
385
  "### Проверяемость",
265
- verifiability,
386
+ `- strict: ${verifiability.strict}`,
387
+ `- statistical: ${verifiability.statistical}`,
388
+ `- human: ${verifiability.human}`,
266
389
  "",
267
390
  "### Что сделано",
268
391
  doneBlock,
@@ -318,6 +441,15 @@ function resolveTaskIssue(payload) {
318
441
  return issue;
319
442
  }
320
443
 
444
+ function getIssueDetails(issue, repo) {
445
+ const repoArgs = toRepoArgs(repo);
446
+ return readJson(
447
+ "gh",
448
+ ["issue", "view", String(issue), "--json", "number,title,body,labels,url", ...repoArgs],
449
+ false,
450
+ );
451
+ }
452
+
321
453
  function getIssueLabels(issue, repo) {
322
454
  const repoArgs = toRepoArgs(repo);
323
455
  const issueData = readJson(
@@ -457,6 +589,15 @@ export async function setStatus(payload = {}) {
457
589
  }
458
590
 
459
591
  ensureGhReady();
592
+ if (targetStatus === "IN_PROGRESS") {
593
+ const issueData = getIssueDetails(issue, repo);
594
+ const validation = validateIssueBodyForInProgress(issueData?.body || "");
595
+ if (!validation.ok) {
596
+ throw new Error(
597
+ `BLOCKED: cannot set #${issue} -> IN_PROGRESS. ${validation.reasons.join("; ")}`,
598
+ );
599
+ }
600
+ }
460
601
  ensureLabelsExist([targetStatus], repo);
461
602
 
462
603
  const currentLabels = getIssueLabels(issue, repo);
@@ -1,8 +1,11 @@
1
1
  import process from "node:process";
2
2
  import { execFileSync } from "node:child_process";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
3
5
  import { resolveTrackerForCommand } from "./tracker/provider-lock.mjs";
4
6
 
5
7
  const COMMAND_MAP = {
8
+ VERIFY_IMPLEMENTATION_GATE: "__verifyImplementationGate",
6
9
  CREATE_TASK: "createTask",
7
10
  PREPARE_TASK_BRANCH: "__prepareTaskBranch",
8
11
  RUN_MR_REVIEW_GATE: "__runMrReviewGate",
@@ -11,12 +14,37 @@ const COMMAND_MAP = {
11
14
  ASSIGN_SPRINT: "assignSprint",
12
15
  };
13
16
 
17
+ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
18
+
19
+ function scriptPath(fileName) {
20
+ return path.join(SCRIPT_DIR, fileName);
21
+ }
22
+
23
+ function runNodeScript(args) {
24
+ try {
25
+ const output = execFileSync("node", args, {
26
+ cwd: process.cwd(),
27
+ encoding: "utf8",
28
+ stdio: ["ignore", "pipe", "pipe"],
29
+ });
30
+ return output.trim();
31
+ } catch (error) {
32
+ const stdout = String(error?.stdout || "").trim();
33
+ const stderr = String(error?.stderr || "").trim();
34
+ const details = [stderr, stdout].filter(Boolean).join("\n");
35
+ if (details) {
36
+ throw new Error(details);
37
+ }
38
+ throw error;
39
+ }
40
+ }
41
+
14
42
  function printHelp() {
15
43
  console.log("TrackerGateway CLI");
16
44
  console.log("");
17
45
  console.log("Usage:");
18
46
  console.log(
19
- " npm run meta:ops -- --command CREATE_TASK|PREPARE_TASK_BRANCH|RUN_MR_REVIEW_GATE|SET_STATUS|COMMIT_BY_NAME|ASSIGN_SPRINT [--tracker github|mcp|local|custom] [--payload '{\"k\":\"v\"}']",
47
+ " npm run meta:ops -- --command VERIFY_IMPLEMENTATION_GATE|CREATE_TASK|PREPARE_TASK_BRANCH|RUN_MR_REVIEW_GATE|SET_STATUS|COMMIT_BY_NAME|ASSIGN_SPRINT [--tracker github|mcp|local|custom] [--payload '{\"k\":\"v\"}']",
20
48
  );
21
49
  console.log("");
22
50
  console.log("Rules:");
@@ -27,7 +55,7 @@ function printHelp() {
27
55
  }
28
56
 
29
57
  function runTaskBranchRouter(payload) {
30
- const args = [".meta-agents/scripts/task-branch-router.mjs"];
58
+ const args = [scriptPath("task-branch-router.mjs")];
31
59
  if (payload.task) {
32
60
  args.push("--task", String(payload.task));
33
61
  }
@@ -53,18 +81,14 @@ function runTaskBranchRouter(payload) {
53
81
  args.push("--json");
54
82
  }
55
83
 
56
- const output = execFileSync("node", args, {
57
- cwd: process.cwd(),
58
- encoding: "utf8",
59
- stdio: ["ignore", "pipe", "pipe"],
60
- });
84
+ const output = runNodeScript(args);
61
85
  if (output.trim()) {
62
86
  console.log(output.trim());
63
87
  }
64
88
  }
65
89
 
66
90
  function runMrReviewGate(payload) {
67
- const args = [".meta-agents/scripts/run-mr-review-gate.mjs"];
91
+ const args = [scriptPath("run-mr-review-gate.mjs")];
68
92
  if (payload.pr) {
69
93
  args.push("--pr", String(payload.pr));
70
94
  }
@@ -78,11 +102,23 @@ function runMrReviewGate(payload) {
78
102
  args.push("--json");
79
103
  }
80
104
 
81
- const output = execFileSync("node", args, {
82
- cwd: process.cwd(),
83
- encoding: "utf8",
84
- stdio: ["ignore", "pipe", "pipe"],
85
- });
105
+ const output = runNodeScript(args);
106
+ if (output.trim()) {
107
+ console.log(output.trim());
108
+ }
109
+ }
110
+
111
+ function runImplementationGate(payload, trackerProvider) {
112
+ const args = [scriptPath("verify-implementation-gate.mjs")];
113
+ if (payload.task) {
114
+ args.push("--task", String(payload.task));
115
+ }
116
+ if (payload.json) {
117
+ args.push("--json");
118
+ }
119
+ args.push("--tracker", trackerProvider);
120
+
121
+ const output = runNodeScript(args);
86
122
  if (output.trim()) {
87
123
  console.log(output.trim());
88
124
  }
@@ -174,6 +210,11 @@ async function main() {
174
210
  console.log(`TrackerGateway PASS: ${options.command} via tracker '${trackerProvider}'.`);
175
211
  return;
176
212
  }
213
+ if (adapterMethod === "__verifyImplementationGate") {
214
+ runImplementationGate(payload, trackerProvider);
215
+ console.log(`TrackerGateway PASS: ${options.command} via tracker '${trackerProvider}'.`);
216
+ return;
217
+ }
177
218
  if (adapterMethod === "__runMrReviewGate") {
178
219
  runMrReviewGate(payload);
179
220
  console.log(`TrackerGateway PASS: ${options.command} via tracker '${trackerProvider}'.`);
@@ -0,0 +1,373 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import process from "node:process";
3
+ import { resolveTrackerForCommand } from "./tracker/provider-lock.mjs";
4
+
5
+ const STATUS_LABELS = ["TODO", "IN_PROGRESS", "REVIEW", "READY", "BLOCKED", "DONE", "PUBLISH"];
6
+ const REQUIRED_PRD_HEADINGS = ["### Описание", "### Проверяемость", "### Что сделано"];
7
+ const IMPLEMENTATION_BRANCH_TYPES = new Set(["feature", "release", "hotfix"]);
8
+
9
+ function run(command, args, allowFailure = false) {
10
+ try {
11
+ return execFileSync(command, args, {
12
+ encoding: "utf8",
13
+ cwd: process.cwd(),
14
+ stdio: ["ignore", "pipe", "pipe"],
15
+ }).trim();
16
+ } catch (error) {
17
+ if (allowFailure) {
18
+ return "";
19
+ }
20
+ const stderr = String(error?.stderr || "").trim();
21
+ const stdout = String(error?.stdout || "").trim();
22
+ throw new Error(`${command} ${args.join(" ")} failed: ${stderr || stdout || error.message}`);
23
+ }
24
+ }
25
+
26
+ function readJson(command, args, allowFailure = false) {
27
+ const raw = run(command, args, allowFailure);
28
+ if (!raw) {
29
+ return null;
30
+ }
31
+ return JSON.parse(raw);
32
+ }
33
+
34
+ function parseArgs(argv) {
35
+ const options = {
36
+ task: "",
37
+ tracker: "",
38
+ json: false,
39
+ };
40
+
41
+ for (let i = 0; i < argv.length; i += 1) {
42
+ const arg = argv[i];
43
+ if (arg === "--task") {
44
+ options.task = (argv[i + 1] || "").trim();
45
+ i += 1;
46
+ continue;
47
+ }
48
+ if (arg === "--tracker") {
49
+ options.tracker = (argv[i + 1] || "").trim();
50
+ i += 1;
51
+ continue;
52
+ }
53
+ if (arg === "--json") {
54
+ options.json = true;
55
+ continue;
56
+ }
57
+ }
58
+
59
+ return options;
60
+ }
61
+
62
+ function parseIssueNumber(value) {
63
+ const source = String(value || "").trim();
64
+ if (!source) {
65
+ return "";
66
+ }
67
+
68
+ const direct = source.match(/^#?(\d+)$/);
69
+ if (direct) {
70
+ return direct[1];
71
+ }
72
+
73
+ const issueToken = source.match(/(?:^|[\/\s])issue-(\d+)(?:-|$|\s)/i);
74
+ if (issueToken) {
75
+ return issueToken[1];
76
+ }
77
+
78
+ const githubUrl = source.match(/\/issues\/(\d+)(?:$|[/?#])/i);
79
+ if (githubUrl) {
80
+ return githubUrl[1];
81
+ }
82
+
83
+ const ref = source.match(/#(\d+)/);
84
+ if (ref) {
85
+ return ref[1];
86
+ }
87
+
88
+ return "";
89
+ }
90
+
91
+ function classifyBranch(branch) {
92
+ if (/^main$/.test(branch)) {
93
+ return "main";
94
+ }
95
+ if (/^develop$/.test(branch)) {
96
+ return "develop";
97
+ }
98
+ if (/^feature\/.+/.test(branch)) {
99
+ return "feature";
100
+ }
101
+ if (/^release\/.+/.test(branch)) {
102
+ return "release";
103
+ }
104
+ if (/^hotfix\/.+/.test(branch)) {
105
+ return "hotfix";
106
+ }
107
+ return "unknown";
108
+ }
109
+
110
+ function extractBranchTaskRef(branch) {
111
+ const source = String(branch || "").trim();
112
+ if (!source) {
113
+ return "";
114
+ }
115
+ const match = source.match(/^(feature|release|hotfix)\/(.+)$/);
116
+ if (!match) {
117
+ return "";
118
+ }
119
+ const tail = match[2];
120
+ const taskMatch = tail.match(/^([A-Za-z][A-Za-z0-9_]*-\d+)/);
121
+ if (taskMatch) {
122
+ return taskMatch[1].toUpperCase();
123
+ }
124
+ const issueMatch = tail.match(/^(\d+)(?:-|$)/);
125
+ if (issueMatch) {
126
+ return `ISSUE-${issueMatch[1]}`;
127
+ }
128
+ return "";
129
+ }
130
+
131
+ function detectBranch() {
132
+ const direct = run("git", ["rev-parse", "--abbrev-ref", "HEAD"], true);
133
+ if (direct && direct !== "HEAD") {
134
+ return direct;
135
+ }
136
+ const symbolic = run("git", ["symbolic-ref", "--short", "HEAD"], true);
137
+ if (symbolic) {
138
+ return symbolic;
139
+ }
140
+ const status = run("git", ["status", "--short", "--branch"], true);
141
+ const header = status.split("\n")[0] || "";
142
+ const noCommits = header.match(/^##\s+No commits yet on\s+(.+)$/);
143
+ if (noCommits) {
144
+ return noCommits[1].trim();
145
+ }
146
+ return "unknown";
147
+ }
148
+
149
+ function extractSection(content, heading) {
150
+ const source = String(content || "");
151
+ if (!source || !heading) {
152
+ return "";
153
+ }
154
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
155
+ const regex = new RegExp(`${escapedHeading}\\s*\\n([\\s\\S]*?)(?:\\n###\\s+|$)`, "i");
156
+ const match = source.match(regex);
157
+ return (match?.[1] || "").trim();
158
+ }
159
+
160
+ function validateIssueBody(issueBody) {
161
+ const blockers = [];
162
+
163
+ for (const heading of REQUIRED_PRD_HEADINGS) {
164
+ if (!String(issueBody || "").includes(heading)) {
165
+ blockers.push(`missing required PRD heading '${heading}'`);
166
+ }
167
+ }
168
+
169
+ const verifiability = extractSection(issueBody, "### Проверяемость");
170
+ if (!verifiability) {
171
+ blockers.push("missing section '### Проверяемость'");
172
+ return blockers;
173
+ }
174
+
175
+ if (!/(^|\n)\s*-\s*strict\s*:/i.test(verifiability)) {
176
+ blockers.push("missing 'strict:' row in verifiability");
177
+ }
178
+ if (!/(^|\n)\s*-\s*statistical\s*:/i.test(verifiability)) {
179
+ blockers.push("missing 'statistical:' row in verifiability");
180
+ }
181
+ if (!/(^|\n)\s*-\s*human\s*:/i.test(verifiability)) {
182
+ blockers.push("missing 'human:' row in verifiability");
183
+ }
184
+
185
+ const statisticalMatch = verifiability.match(/(^|\n)\s*-\s*statistical\s*:\s*(.+)$/im);
186
+ if (statisticalMatch) {
187
+ const value = String(statisticalMatch[2] || "").trim();
188
+ const hasThresholdOrNA = /(<=|>=|<|>|=|\b\d+(\.\d+)?\b|N\/A|^NA\b|not applicable)/i.test(value);
189
+ if (!hasThresholdOrNA) {
190
+ blockers.push("statistical row must include measurable threshold or explicit N/A");
191
+ }
192
+ }
193
+
194
+ return blockers;
195
+ }
196
+
197
+ function issueStatusFromLabels(labels) {
198
+ for (const label of labels || []) {
199
+ const name = String(label?.name || "").toUpperCase();
200
+ if (STATUS_LABELS.includes(name)) {
201
+ return name;
202
+ }
203
+ }
204
+ return "";
205
+ }
206
+
207
+ function ensureGhReady() {
208
+ run("gh", ["--version"], false);
209
+ const auth = run("gh", ["auth", "status"], true);
210
+ if (!auth) {
211
+ throw new Error("gh is not authenticated. Run 'gh auth login' first.");
212
+ }
213
+ }
214
+
215
+ function buildNextSteps({ issueNumber, branchType, branchTaskRef }) {
216
+ const steps = [];
217
+ if (!issueNumber) {
218
+ steps.push(
219
+ "Create a task first: npm run meta:ops -- --command CREATE_TASK --payload '{\"title\":\"<short-name>\",\"type\":\"task\"}'",
220
+ );
221
+ return steps;
222
+ }
223
+ steps.push(
224
+ `Ensure task is IN_PROGRESS: npm run meta:ops -- --command SET_STATUS --payload '{\"task\":\"#${issueNumber}\",\"status\":\"IN_PROGRESS\"}'`,
225
+ );
226
+ steps.push(
227
+ `Run branch preflight: npm run meta:task-start -- --task \"#${issueNumber}\" --slug <slug> --kind feature`,
228
+ );
229
+ if (!IMPLEMENTATION_BRANCH_TYPES.has(branchType) || branchTaskRef !== `ISSUE-${issueNumber}`) {
230
+ steps.push(
231
+ `Switch to aligned working branch: feature/issue-${issueNumber}-<slug>`,
232
+ );
233
+ }
234
+ steps.push(
235
+ `Re-check gate: npm run meta:implementation-gate -- --task \"#${issueNumber}\"`,
236
+ );
237
+ return steps;
238
+ }
239
+
240
+ function printHuman(report) {
241
+ console.log("# Implementation Gate");
242
+ console.log("");
243
+ console.log("## Inputs");
244
+ console.log(`- tracker: ${report.tracker}`);
245
+ console.log(`- task: ${report.task || "not provided"}`);
246
+ console.log(`- branch: ${report.branch}`);
247
+ console.log(`- branch type: ${report.branchType}`);
248
+ console.log(`- branch task ref: ${report.branchTaskRef || "not detected"}`);
249
+
250
+ console.log("");
251
+ console.log("## Checks");
252
+ console.log(`- issue: ${report.issue.number ? `#${report.issue.number}` : "not detected"}`);
253
+ console.log(`- issue status: ${report.issue.status || "not detected"}`);
254
+ console.log(`- prd/verifiability: ${report.prdOk ? "PASS" : "FAIL"}`);
255
+ console.log(`- branch alignment: ${report.branchAligned ? "PASS" : "FAIL"}`);
256
+
257
+ console.log("");
258
+ console.log("## Decision");
259
+ if (report.blockers.length === 0) {
260
+ console.log("- PASS");
261
+ return;
262
+ }
263
+ console.log("- BLOCKED");
264
+ for (const blocker of report.blockers) {
265
+ console.log(`- ${blocker}`);
266
+ }
267
+
268
+ console.log("");
269
+ console.log("## Next Steps");
270
+ for (const step of report.nextSteps) {
271
+ console.log(`- ${step}`);
272
+ }
273
+ }
274
+
275
+ function main() {
276
+ const options = parseArgs(process.argv.slice(2));
277
+ const tracker = resolveTrackerForCommand({
278
+ requestedTracker: options.tracker,
279
+ cwd: process.cwd(),
280
+ });
281
+
282
+ if (tracker !== "github") {
283
+ throw new Error(
284
+ `Implementation gate currently supports github tracker only. Locked tracker: '${tracker}'.`,
285
+ );
286
+ }
287
+
288
+ ensureGhReady();
289
+
290
+ const issueNumber = parseIssueNumber(options.task);
291
+ const branch = detectBranch();
292
+ const branchType = classifyBranch(branch);
293
+ const branchTaskRef = extractBranchTaskRef(branch);
294
+ const blockers = [];
295
+ let issue = { number: "", title: "", status: "", url: "" };
296
+ let prdOk = false;
297
+
298
+ if (!issueNumber) {
299
+ blockers.push("missing issue ref. Provide --task '#<number>'");
300
+ } else {
301
+ const issueData = readJson("gh", [
302
+ "issue",
303
+ "view",
304
+ issueNumber,
305
+ "--json",
306
+ "number,title,body,labels,url",
307
+ ]);
308
+ issue = {
309
+ number: String(issueData?.number || issueNumber),
310
+ title: issueData?.title || "",
311
+ status: issueStatusFromLabels(issueData?.labels || []),
312
+ url: issueData?.url || "",
313
+ };
314
+
315
+ if (issue.status !== "IN_PROGRESS") {
316
+ blockers.push(`issue #${issue.number} status must be IN_PROGRESS, got '${issue.status || "none"}'`);
317
+ }
318
+
319
+ const prdBlockers = validateIssueBody(issueData?.body || "");
320
+ if (prdBlockers.length > 0) {
321
+ for (const blocker of prdBlockers) {
322
+ blockers.push(`issue #${issue.number}: ${blocker}`);
323
+ }
324
+ } else {
325
+ prdOk = true;
326
+ }
327
+
328
+ if (!IMPLEMENTATION_BRANCH_TYPES.has(branchType)) {
329
+ blockers.push(
330
+ `branch type must be feature/release/hotfix for implementation, got '${branchType}'`,
331
+ );
332
+ }
333
+ if (branchTaskRef && branchTaskRef !== `ISSUE-${issue.number}`) {
334
+ blockers.push(
335
+ `branch task ref '${branchTaskRef}' is not aligned with issue '#${issue.number}'`,
336
+ );
337
+ }
338
+ if (!branchTaskRef) {
339
+ blockers.push("branch task ref not detected; run PREPARE_TASK_BRANCH first");
340
+ }
341
+ }
342
+
343
+ const nextSteps = buildNextSteps({ issueNumber, branchType, branchTaskRef });
344
+ const report = {
345
+ tracker,
346
+ task: options.task,
347
+ branch,
348
+ branchType,
349
+ branchTaskRef,
350
+ issue,
351
+ prdOk,
352
+ branchAligned: Boolean(issueNumber) && branchTaskRef === `ISSUE-${issueNumber}` && IMPLEMENTATION_BRANCH_TYPES.has(branchType),
353
+ blockers,
354
+ nextSteps,
355
+ };
356
+
357
+ if (options.json) {
358
+ console.log(JSON.stringify(report, null, 2));
359
+ } else {
360
+ printHuman(report);
361
+ }
362
+
363
+ if (blockers.length > 0) {
364
+ process.exit(1);
365
+ }
366
+ }
367
+
368
+ try {
369
+ main();
370
+ } catch (error) {
371
+ console.error(`implementation-gate FAIL: ${error.message}`);
372
+ process.exit(1);
373
+ }
@@ -50,6 +50,7 @@
50
50
  npm run meta:verify
51
51
  npm run meta:branch
52
52
  npm run meta:task-start -- --task #12 --slug api-redirect
53
+ npm run meta:implementation-gate -- --task #12
53
54
  npm run meta:review
54
55
  npm run meta:review-approve
55
56
  npm run meta:mr-review
@@ -66,6 +67,7 @@ npm run meta:status
66
67
  `meta:task-start` делает branch-routing preflight: сравнивает задачу с текущей веткой, показывает dirty/ahead блокеры и готовит маршрут (`stay_on_current_branch` или `create_new_branch`).
67
68
  `meta:task-start` также делает context-protection check для `AGENTS.md` (fallback: `agents.md`) между текущей и базовой веткой и требует явного подтверждения при diff.
68
69
  Именование branch task ref: использовать GitHub issue ref в формате `issue-<number>`.
70
+ `meta:implementation-gate` блокирует старт реализации, если не выполнены условия `IN_PROGRESS + PRD sections + structured verifiability(strict/statistical/human) + branch alignment`.
69
71
  `meta:mr-review` формирует сводный pre-merge отчёт по MR/PR, `meta:mr-review-approve` фиксирует финальный PASS после подтверждения пользователя.
70
72
 
71
73
  `meta:status` выдаёт сводный статус:
@@ -123,13 +123,14 @@ Name: <agent role>
123
123
  1. `CREATE_TASK`
124
124
  2. `PREPARE_TASK_BRANCH`
125
125
  3. `SET_STATUS`
126
- 4. `RUN_REVIEW_GATE`
127
- 5. `COMMIT_BY_NAME`
128
- 6. `RUN_MR_REVIEW_GATE`
129
- 7. `ASSIGN_SPRINT`
130
- 8. `PREPARE_RELEASE_NOTE`
131
- 9. `MARK_TASKS_PUBLISH`
132
- 10. `STATUS_SNAPSHOT`
126
+ 4. `VERIFY_IMPLEMENTATION_GATE`
127
+ 5. `RUN_REVIEW_GATE`
128
+ 6. `COMMIT_BY_NAME`
129
+ 7. `RUN_MR_REVIEW_GATE`
130
+ 8. `ASSIGN_SPRINT`
131
+ 9. `PREPARE_RELEASE_NOTE`
132
+ 10. `MARK_TASKS_PUBLISH`
133
+ 11. `STATUS_SNAPSHOT`
133
134
 
134
135
  ## 10) Коммиты и закрытие задач: только по подтверждению пользователя
135
136
  Запрещено выполнять автоматически:
@@ -148,6 +149,7 @@ Name: <agent role>
148
149
  - без подтверждения пользователя максимум допустимого статуса: `REVIEW`.
149
150
  - `SET_STATUS -> READY` разрешён только после `RUN_REVIEW_GATE: PASS_CONFIRMED`.
150
151
  - для задач реализации по умолчанию используется отдельная ветка `feature/*`; прямой рабочий поток на `develop`/`main` не допускается.
152
+ - перед началом реализации после `SET_STATUS -> IN_PROGRESS` обязателен `VERIFY_IMPLEMENTATION_GATE`.
151
153
  - сообщение коммита обязано начинаться с ссылки на задачу и summary в формате `#issue-number <summary>`.
152
154
  - `READY` означает: коммит сделан и отправлен (`push`) в `feature/*`, `release/*` или `hotfix/*` с открытым PR по правилам ветвления.
153
155
  - `DONE` означает: изменения интегрированы в `main`; для `release/*` и `hotfix/*` подтверждён back-merge в `develop`.
@@ -188,20 +190,21 @@ Name: <agent role>
188
190
  8. Если был коммит, есть явное подтверждение пользователя на коммит.
189
191
  9. Если задача закрыта в `DONE`, есть явное подтверждение пользователя на закрытие.
190
192
  10. Перед каждой командой пройден `VERIFY_GOVERNANCE_GATE` от `Governance Watchdog Agent`.
191
- 11. Перед коммитом пройден `RUN_REVIEW_GATE` от `Reviewer/Judge Agent`.
192
- 12. Есть явное подтверждение пользователя на прохождение review (`Review Approved: yes`).
193
- 13. Перед merge пройден `RUN_MR_REVIEW_GATE` от `MR Review Agent`.
194
- 14. Есть явное подтверждение пользователя на MR review (`MR Review Approved: yes`).
195
- 15. До review gate задача переведена в статус `REVIEW`.
196
- 16. Статус `READY` выставлен только после `RUN_REVIEW_GATE: PASS_CONFIRMED`.
197
- 17. Для статуса `READY` подтверждены `commit + push` в `feature/*|release/*|hotfix/*` и открытый PR в целевую ветку (`develop` или `main`).
198
- 18. Для merge подтверждён `RUN_MR_REVIEW_GATE: PASS_CONFIRMED`.
199
- 19. Для статуса `DONE` подтверждена интеграция в `main`; для `release/*` и `hotfix/*` подтверждён back-merge в `develop`.
200
- 20. Для статуса `PUBLISH` подтверждена публикация в последней версии.
201
- 21. Для релиза обновлён публичный `CHANGELOG.md`.
202
- 22. Коммит следует формату `#issue-number <summary>` (issue ref в начале сообщения).
203
- 23. Для branch routing собран e2e evidence по двум сценариям: `same feature` и `different feature`.
204
- 24. Перед `--apply` подтверждена консистентность контекста `AGENTS.md` (fallback: `agents.md`) или явно подтверждён осознанный switch при diff.
193
+ 11. Перед началом реализации пройден `VERIFY_IMPLEMENTATION_GATE` (issue `IN_PROGRESS` + structured verifiability + branch alignment).
194
+ 12. Перед коммитом пройден `RUN_REVIEW_GATE` от `Reviewer/Judge Agent`.
195
+ 13. Есть явное подтверждение пользователя на прохождение review (`Review Approved: yes`).
196
+ 14. Перед merge пройден `RUN_MR_REVIEW_GATE` от `MR Review Agent`.
197
+ 15. Есть явное подтверждение пользователя на MR review (`MR Review Approved: yes`).
198
+ 16. До review gate задача переведена в статус `REVIEW`.
199
+ 17. Статус `READY` выставлен только после `RUN_REVIEW_GATE: PASS_CONFIRMED`.
200
+ 18. Для статуса `READY` подтверждены `commit + push` в `feature/*|release/*|hotfix/*` и открытый PR в целевую ветку (`develop` или `main`).
201
+ 19. Для merge подтверждён `RUN_MR_REVIEW_GATE: PASS_CONFIRMED`.
202
+ 20. Для статуса `DONE` подтверждена интеграция в `main`; для `release/*` и `hotfix/*` подтверждён back-merge в `develop`.
203
+ 21. Для статуса `PUBLISH` подтверждена публикация в последней версии.
204
+ 22. Для релиза обновлён публичный `CHANGELOG.md`.
205
+ 23. Коммит следует формату `#issue-number <summary>` (issue ref в начале сообщения).
206
+ 24. Для branch routing собран e2e evidence по двум сценариям: `same feature` и `different feature`.
207
+ 25. Перед `--apply` подтверждена консистентность контекста `AGENTS.md` (fallback: `agents.md`) или явно подтверждён осознанный switch при diff.
205
208
 
206
209
  Если хотя бы один критерий не выполнен, задача не принимается.
207
210
 
@@ -6,6 +6,7 @@
6
6
  "meta:status": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/sync-status.mjs",
7
7
  "meta:branch": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/verify-branch-strategy.mjs",
8
8
  "meta:task-start": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/task-branch-router.mjs",
9
+ "meta:implementation-gate": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/verify-implementation-gate.mjs",
9
10
  "meta:verify": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/verify-governance.mjs",
10
11
  "meta:review": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/run-review-gate.mjs",
11
12
  "meta:review-approve": "node ./node_modules/@k0t0vich/meta-agents-template/template/.meta-agents/scripts/run-review-gate.mjs --approve yes",
@@ -64,6 +64,7 @@ result PASS
64
64
  - merge разрешён только после `RUN_MR_REVIEW_GATE: PASS_CONFIRMED`;
65
65
  - для задач реализации по умолчанию используется отдельная ветка `feature/*`; прямой поток на `develop`/`main` запрещён;
66
66
  - перед началом реализации обязателен `PREPARE_TASK_BRANCH` (веточный preflight и user dialogue);
67
+ - после `SET_STATUS -> IN_PROGRESS` и перед началом реализации обязателен `VERIFY_IMPLEMENTATION_GATE`;
67
68
  - branch naming policy: использовать только GitHub issue ref в формате `issue-<number>`;
68
69
  - если задача атомарная и относится к текущей feature-ветке, допускается выполнение в текущей ветке после явного подтверждения пользователя;
69
70
  - если задача не относится к текущей ветке, требуется: проверить dirty/ahead, согласовать действие с пользователем, перейти на `develop` (или `main` для hotfix), обновить и создать новую ветку;
@@ -153,7 +154,7 @@ repo your-org/your-repo,
153
154
  short name "API редирект офферов",
154
155
  owner "Engineering Agent",
155
156
  description "Добавить API редиректа на офферы",
156
- verifiability "strict: schema+contracts; statistical: p95 latency < 200ms",
157
+ verifiability "strict: schema+contracts; statistical: p95 latency < 200ms; human: reviewer approval required",
157
158
  sprint "SPRINT-3"
158
159
  ```
159
160
 
@@ -190,6 +191,23 @@ command "npm run meta:task-start -- --task #12 --slug api-redirect",
190
191
  user dialogue "confirm branch context + dirty/ahead handling + AGENTS.md context warning + switch decision"
191
192
  ```
192
193
 
194
+ ### VERIFY_IMPLEMENTATION_GATE (обязателен после IN_PROGRESS и до реализации)
195
+ ```text
196
+ [Agent Auto-Select]
197
+ Selected: Governance Watchdog Agent
198
+ Reason: Нужен жесткий pre-implementation gate с проверкой task статуса, PRD структуры и branch alignment.
199
+
200
+ [Task Agent]
201
+ Name: Governance Watchdog Agent
202
+
203
+ Governance Watchdog Agent: verify implementation gate,
204
+ tracker __DEFAULT_TRACKER__,
205
+ task #12,
206
+ command "npm run meta:implementation-gate -- --task #12",
207
+ required checks "issue_status_in_progress, prd_sections_complete, verifiability(strict/statistical/human), branch_alignment",
208
+ result PASS
209
+ ```
210
+
193
211
  ### SET_STATUS -> REVIEW (обязателен перед review gate)
194
212
  ```text
195
213
  [Agent Auto-Select]
@@ -422,18 +440,19 @@ reason "вошло в релиз v0.1.2"
422
440
  2. `CREATE_TASK`.
423
441
  3. `PREPARE_TASK_BRANCH`.
424
442
  4. `SET_STATUS -> IN_PROGRESS`.
425
- 5. Реализация + evidence.
426
- 6. `SET_STATUS -> REVIEW`.
427
- 7. `RUN_REVIEW_GATE` (получить report + `PASS_CANDIDATE`/`FAIL`).
428
- 8. Пользователь подтверждает review (`Review Approved: yes`).
429
- 9. `RUN_REVIEW_GATE` -> `PASS_CONFIRMED`.
430
- 10. `COMMIT_BY_NAME` только после review PASS_CONFIRMED и отдельного подтверждения пользователя на коммит.
431
- 11. Push + open PR в целевую ветку по Git Flow Lite.
432
- 12. `RUN_MR_REVIEW_GATE` (получить report + `PASS_CANDIDATE`/`FAIL`).
433
- 13. Пользователь подтверждает MR review (`MR Review Approved: yes`).
434
- 14. `RUN_MR_REVIEW_GATE` -> `PASS_CONFIRMED`.
435
- 15. Merge PR по Git Flow Lite.
436
- 16. `SET_STATUS -> READY`.
437
- 17. `SET_STATUS -> DONE` только после подтверждения пользователя.
438
- 18. `PREPARE_RELEASE_NOTE` (какие задачи вошли в релиз).
439
- 19. Для релизных задач после публикации: `MARK_TASKS_PUBLISH` (`SET_STATUS -> PUBLISH` для всех вошедших задач).
443
+ 5. `VERIFY_IMPLEMENTATION_GATE`.
444
+ 6. Реализация + evidence.
445
+ 7. `SET_STATUS -> REVIEW`.
446
+ 8. `RUN_REVIEW_GATE` (получить report + `PASS_CANDIDATE`/`FAIL`).
447
+ 9. Пользователь подтверждает review (`Review Approved: yes`).
448
+ 10. `RUN_REVIEW_GATE` -> `PASS_CONFIRMED`.
449
+ 11. `COMMIT_BY_NAME` только после review PASS_CONFIRMED и отдельного подтверждения пользователя на коммит.
450
+ 12. Push + open PR в целевую ветку по Git Flow Lite.
451
+ 13. `RUN_MR_REVIEW_GATE` (получить report + `PASS_CANDIDATE`/`FAIL`).
452
+ 14. Пользователь подтверждает MR review (`MR Review Approved: yes`).
453
+ 15. `RUN_MR_REVIEW_GATE` -> `PASS_CONFIRMED`.
454
+ 16. Merge PR по Git Flow Lite.
455
+ 17. `SET_STATUS -> READY`.
456
+ 18. `SET_STATUS -> DONE` только после подтверждения пользователя.
457
+ 19. `PREPARE_RELEASE_NOTE` (какие задачи вошли в релиз).
458
+ 20. Для релизных задач после публикации: `MARK_TASKS_PUBLISH` (`SET_STATUS -> PUBLISH` для всех вошедших задач).