@k0t0vich/meta-agents-template 0.1.9 → 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 +19 -0
- package/README.md +10 -7
- package/package.json +2 -1
- package/src/init.mjs +8 -2
- package/template/.meta-agents/config/system.yaml +3 -0
- package/template/.meta-agents/scripts/sync-status.mjs +14 -8
- package/template/.meta-agents/scripts/task-branch-router.mjs +41 -8
- package/template/.meta-agents/scripts/tracker/github.mjs +690 -4
- package/template/.meta-agents/scripts/tracker-gateway.mjs +54 -13
- package/template/.meta-agents/scripts/verify-implementation-gate.mjs +373 -0
- package/template/README.md +2 -0
- package/template/agents.md +24 -21
- package/template/package.json +1 -0
- package/template/tracker-command-template.md +35 -16
|
@@ -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 = ["
|
|
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 =
|
|
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 = ["
|
|
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 =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
}
|
package/template/README.md
CHANGED
|
@@ -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` выдаёт сводный статус:
|
package/template/agents.md
CHANGED
|
@@ -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. `
|
|
127
|
-
5. `
|
|
128
|
-
6. `
|
|
129
|
-
7. `
|
|
130
|
-
8. `
|
|
131
|
-
9. `
|
|
132
|
-
10. `
|
|
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. Перед
|
|
192
|
-
12.
|
|
193
|
-
13.
|
|
194
|
-
14.
|
|
195
|
-
15.
|
|
196
|
-
16.
|
|
197
|
-
17.
|
|
198
|
-
18. Для
|
|
199
|
-
19. Для
|
|
200
|
-
20. Для статуса `
|
|
201
|
-
21. Для
|
|
202
|
-
22.
|
|
203
|
-
23.
|
|
204
|
-
24.
|
|
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
|
|
package/template/package.json
CHANGED
|
@@ -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.
|
|
426
|
-
6.
|
|
427
|
-
7. `
|
|
428
|
-
8.
|
|
429
|
-
9. `
|
|
430
|
-
10. `
|
|
431
|
-
11.
|
|
432
|
-
12.
|
|
433
|
-
13.
|
|
434
|
-
14. `
|
|
435
|
-
15.
|
|
436
|
-
16.
|
|
437
|
-
17. `SET_STATUS ->
|
|
438
|
-
18. `
|
|
439
|
-
19.
|
|
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` для всех вошедших задач).
|